29、交通标志识别学习指南

交通标志识别学习指南

1. 数据集选择与分析

1.1 GTSRB 数据集介绍

选择合适的数据集对于交通标志识别至关重要,GTSRB(德国交通标志识别基准)数据集是一个不错的选择。它包含超过 50,000 张属于 40 多个类别的交通标志图像,曾在 2011 年国际神经网络联合会议(IJCNN)的分类挑战中被专业人士使用。该数据集具有规模大、组织有序、开源且有标注等优点,可从 http://benchmark.ini.rub.de/?section=gtsrb&subsection=dataset 免费获取。为了便于处理,我们将分类限制在 10 个类别。

1.2 数据集可视化

在进行机器学习之前,了解数据集的特性和挑战很有必要。可以使用以下代码绘制数据集中的一些样本:

sample_size = 15
sample_idx = np.random.randint(len(X), size=sample_size)
sp = 1
for r in xrange(3):
    for c in xrange(5):
        ax = plt.subplot(3,5,sp)
        sample = X[sample_idx[sp-1]]
        ax.imshow(sample.reshape((32,32)), cmap=cm.Greys_r)
        ax.axis('off')
        sp += 1
plt.show()

从这些小样本中可以看出,该数据集对任何分类器来说都是一个挑战。交通标志的外观会因视角、距离和光照条件而发生巨大变化,有些标志甚至人类也难以立即识别其类别。

1.3 数据集解析

数据集自带了解析文件的脚本,我们对其进行了调整,以满足特定需求。以下是加载数据集的函数:

import cv2
import numpy as np
import csv
from matplotlib import cm
from matplotlib import pyplot as plt

def load_data(rootpath="datasets", feature="hog", cut_roi=True, 
              test_split=0.2, plot_samples=False, seed=113):
    classes = np.array([0, 4, 8, 12, 16, 20, 24, 28, 32, 36])
    X = []  # images
    labels = []  # corresponding labels
    for c in xrange(len(classes)):
        prefix = rootpath + '/' + format(classes[c], '05d') + '/'
        gt_file = open(prefix + 'GT-' + format(classes[c], '05d') + '.csv')
        gt_reader = csv.reader(gt_file, delimiter=';')
        gt_reader.next()  # skip header
        for row in gt_reader:
            im = cv2.imread(prefix + row[0])
            if cut_roi:
                im = im[np.int(row[4]):np.int(row[6]), 
                        np.int(row[3]):np.int(row[5]), :]
            X.append(im)
            labels.append(c)
        gt_file.close()
    if feature is not None:
        X = _extract_feature(X, feature)
    np.random.seed(seed)
    np.random.shuffle(X)
    np.random.seed(seed)
    np.random.shuffle(labels)
    X_train = X[:int(len(X)*(1-test_split))]
    y_train = labels[:int(len(X)*(1-test_split))]
    X_test = X[int(len(X)*(1-test_split)):]
    y_test = labels[int(len(X)*(1-test_split)):]
    return (X_train, y_train), (X_test, y_test)

该函数不仅可以加载数据集,还能提取感兴趣的特征、裁剪样本到感兴趣区域,并自动将数据分为训练集和测试集。

2. 特征提取

2.1 特征选择的重要性

原始像素值通常不是表示数据的最佳方式,需要提取更具信息性的特征。特征的选择取决于要分析的数据集和具体的分类任务。例如,区分停车标志和警告标志时,标志的形状或颜色方案可能是最关键的特征;但区分两个警告标志时,颜色和形状可能就不起作用了,需要更复杂的特征。

2.2 关注的特征类型

为了展示特征选择对分类性能的影响,我们关注以下几种特征:
- 简单颜色变换 :包括灰度、RGB 和 HSV。基于灰度图像的分类可作为分类器的基线性能,RGB 可能因某些交通标志的独特颜色方案而提供稍好的性能,HSV 则能更稳健地表示颜色,有望提供更好的性能。
- 加速稳健特征(SURF) :是一种高效且稳健的图像特征提取方法,可用于分类任务。
- 方向梯度直方图(HOG) :是本章考虑的最先进的特征描述符,适用于与支持向量机(SVM)结合使用。

2.3 特征提取函数

特征提取由 gtsrb._extract_features 函数完成,它根据 feature 输入参数提取不同的特征:

def _extract_feature(X, feature):
    small_size = (32, 32)
    X = [cv2.resize(x, small_size) for x in X]
    # normalize all intensities to be between 0 and 1
    X = np.array(X).astype(np.float32) / 255
    # subtract mean
    X = [x - np.mean(x) for x in X]
    if feature == 'gray' or feature == 'surf':
        X = [cv2.cvtColor(x, cv2.COLOR_BGR2GRAY) for x in X]
    if feature == 'hsv':
        X = [cv2.cvtColor(x, cv2.COLOR_BGR2HSV) for x in X]
    if feature == 'surf':
        dense = cv2.FeatureDetector_create("Dense")
        kp = dense.detect(np.zeros(small_size).astype(np.uint8))
        surf = cv2.SURF(400)
        surf.upright = True
        surf.extended = True
        kp_des = [surf.compute(x, kp) for x in X]
        num_surf_features = 36
        X = [d[1][:num_surf_features, :] for d in kp_des]
    elif feature == 'hog':
        block_size = (small_size[0] / 2, small_size[1] / 2)
        block_stride = (small_size[0] / 4, small_size[1] / 4)
        cell_size = block_stride
        num_bins = 9
        hog = cv2.HOGDescriptor(small_size, block_size, 
                                block_stride, cell_size, num_bins)
        X = [hog.compute(x) for x in X]
    X = [x.flatten() for x in X]
    return X

2.4 常见预处理步骤

在分类之前,通常会对数据进行一些预处理,本章主要关注均值减法和归一化:
- 均值减法 :计算数据集中每个特征维度的均值,然后从每个样本中减去该均值,可将数据云中心移到原点。
- 归一化 :缩放数据维度,使其大致具有相同的尺度。对于图像数据,像素的相对尺度已经大致相等,因此不一定需要进行此额外的预处理步骤。

3. 支持向量机(SVM)

3.1 SVM 原理

支持向量机是一种用于二分类(和回归)的学习器,它试图用一个决策边界将两个不同类别的样本分开,并使两类之间的间隔最大化。SVM 也被称为最大间隔分类器,因为它可以定义决策边界,使两类样本尽可能远离。在寻找最大间隔时,只需要考虑位于类边界上的数据点,这些点被称为支持向量。除了线性分类,SVM 还可以使用核技巧进行非线性分类,将输入隐式映射到高维特征空间。

3.2 多类分类策略

SVM 本质上是二分类器,但可以通过以下两种策略转换为多类分类器:
- 一对多(one-vs-all) :为每个类训练一个单独的分类器,将该类的样本作为正样本,其他所有样本作为负样本。对于 k 个类,需要训练 k 个不同的 SVM。在测试时,所有分类器可以通过预测一个未见过的样本属于其类来表达“+1”投票,最终未见过的样本被分类为得票最多的类。通常,该策略与置信度分数结合使用,以便最终选择置信度分数最高的类。
- 一对一(one-vs-one) :为每对类训练一个单独的分类器,将第一类的样本作为正样本,第二类的样本作为负样本。对于 k 个类,需要训练 k*(k - 1)/2 个分类器。在测试时,所有分类器可以为第一类或第二类表达“+1”投票,最终未见过的样本被分类为得票最多的类。

3.3 多类 SVM 类实现

class MultiClassSVM(Classifier):
    def __init__(self, num_classes, mode="one-vs-all", 
                 params=None):
        self.num_classes = num_classes
        self.mode = mode
        self.params = params or dict()
        self.classifiers = []
        if mode == "one-vs-one":
            for i in xrange(num_classes*(num_classes-1)/2):
                self.classifiers.append(cv2.SVM())
        elif mode == "one-vs-all":
            for i in xrange(num_classes):
                self.classifiers.append(cv2.SVM())
        else:
            print "Unknown mode ", mode

3.4 SVM 训练

根据分类策略的不同,SVM 的训练方式也有所不同:

def fit(self, X_train, y_train, params=None):
    if params is None:
        params = self.params
    if self.mode == "one-vs-one":
        svm_id = 0
        for c1 in xrange(self.num_classes):
            for c2 in xrange(c1 + 1, self.num_classes):
                y_train_c1 = np.where(y_train == c1)[0]
                y_train_c2 = np.where(y_train == c2)[0]
                data_id = np.sort(np.concatenate((y_train_c1, 
                                                  y_train_c2), axis=0))
                X_train_id = X_train[data_id, :]
                y_train_id = y_train[data_id]
                y_train_bin = np.where(y_train_id == c1, 1, 0).flatten()
                self.classifiers[svm_id].train(X_train_id, 
                                               y_train_bin, params=self.params)
                svm_id += 1
    elif self.mode == "one-vs-all":
        for c in xrange(self.num_classes):
            y_train_bin = np.where(y_train == c, 1, 0).flatten()
            self.classifiers[c].train(X_train, y_train_bin, 
                                      params=self.params)
    params.term_crit = (cv2.TERM_CRITERIA_EPS + 
                        cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-6)

3.5 SVM 测试

通常,我们关注分类器的准确率指标,即测试集中有多少数据样本被正确分类。为了得到这个指标,需要让每个单独的 SVM 预测测试数据的标签,并将它们的共识汇总到一个二维投票矩阵(Y_vote)中:

def evaluate(self, X_test, y_test, visualize=False):
    Y_vote = np.zeros((len(y_test), self.num_classes))
    if self.mode == "one-vs-one":
        svm_id = 0
        for c1 in xrange(self.num_classes):
            for c2 in xrange(c1 + 1, self.num_classes):
                data_id = np.where((y_test == c1) + (y_test == c2))[0]
                X_test_id = X_test[data_id, :]
                y_test_id = y_test[data_id]
                y_hat = self.classifiers[svm_id].predict_all(X_test_id)
                Y_vote[data_id[np.where(y_hat == 1)[0]], c1] += 1
                Y_vote[data_id[np.where(y_hat == 0)[0]], c2] += 1
                svm_id += 1
    elif self.mode == "one-vs-all":
        for c in xrange(self.num_classes):
            y_hat = self.classifiers[c].predict_all(X_test)
            if np.any(y_hat):
                Y_vote[np.where(y_hat == 1)[0], c] += 1
            no_label = np.where(np.sum(Y_vote, axis=1) == 0)[0]
            Y_vote[no_label, np.random.randint(self.num_classes, 
                                               size=len(no_label))] = 1
    accuracy = self.__accuracy(y_test, Y_vote)
    precision = self.__precision(y_test, Y_vote)
    recall = self.__recall(y_test, Y_vote)
    return (accuracy, precision, recall)

4. 评估指标

4.1 混淆矩阵

混淆矩阵是一个大小为 (num_classes, num_classes) 的二维矩阵,其中行对应于预测的类标签,列对应于实际的类标签。矩阵元素 [r, c] 包含被预测为具有标签 r 但实际具有标签 c 的样本数量。通过混淆矩阵可以计算精度和召回率。

def __confusion(self, y_test, Y_vote):
    y_hat = np.argmax(Y_vote, axis=1)
    conf = np.zeros((self.num_classes, 
                     self.num_classes)).astype(np.int32)
    for c_true in xrange(self.num_classes):
        for c_pred in xrange(self.num_classes):
            y_this = np.where((y_test == c_true) * (y_hat == c_pred))
            conf[c_pred, c_true] = np.count_nonzero(y_this)
    return conf

4.2 准确率

准确率是最直接的评估指标,它计算测试集中被正确预测的样本数量,并将其作为总测试样本数量的分数返回:

def __accuracy(self, y_test, y_vote):
    y_hat = np.argmax(y_vote, axis=1)
    mask = (y_hat == y_test)
    return np.count_nonzero(mask) * 1. / len(y_test)

4.3 精度

精度是衡量检索到的相关实例比例的有用指标,定义为真阳性的数量除以阳性的总数。在多类分类中,精度的计算根据分类策略略有不同:

def __precision(self, y_test, Y_vote):
    y_hat = np.argmax(Y_vote, axis=1)
    if True or self.mode == "one-vs-one":
        conf = self.__confusion(y_test, Y_vote)
        prec = np.zeros(self.num_classes)
        for c in xrange(self.num_classes):
            tp = conf[c, c]
            fp = np.sum(conf[:, c]) - conf[c, c]
            if tp + fp != 0:
                prec[c] = tp * 1. / (tp + fp)
    elif self.mode == "one-vs-all":
        prec = np.zeros(self.num_classes)
        for c in xrange(self.num_classes):
            tp = np.count_nonzero((y_test == c) * (y_hat == c))
            fp = np.count_nonzero((y_test == c) * (y_hat != c))
            if tp + fp != 0:
                prec[c] = tp * 1. / (tp + fp)
    return prec

4.4 召回率

召回率衡量的是检索到的相关实例的比例,定义为真阳性的数量除以真阳性和假阴性的总数。其计算过程与精度类似:

def __recall(self, y_test, Y_vote):
    y_hat = np.argmax(Y_vote, axis=1)
    if True or self.mode == "one-vs-one":
        conf = self.__confusion(y_test, Y_vote)
        recall = np.zeros(self.num_classes)
        for c in xrange(self.num_classes):
            tp = conf[c, c]
            fn = np.sum(conf[c, :]) - conf[c, c]
            if tp + fn != 0:
                recall[c] = tp * 1. / (tp + fn)
    return recall

总结

通过选择合适的数据集、提取有效的特征、使用支持向量机进行分类,并使用准确率、精度和召回率等评估指标,我们可以构建一个有效的交通标志识别系统。不同的特征和分类策略会对分类性能产生影响,需要根据具体情况进行选择和调整。

流程图

graph LR
    A[选择数据集] --> B[特征提取]
    B --> C[选择分类器(SVM)]
    C --> D[训练分类器]
    D --> E[测试分类器]
    E --> F[评估性能(准确率、精度、召回率)]

表格:特征类型及特点

特征类型 特点
灰度 作为分类器的基线性能,通常信息性不强
RGB 因某些交通标志的独特颜色方案,可能提供稍好的性能
HSV 更稳健地表示颜色,有望提供更好的性能
SURF 高效且稳健的图像特征提取方法
HOG 适用于与 SVM 结合使用,对光照和阴影变化具有较好的不变性

列表:多类分类策略对比

  1. 一对多(one-vs-all)
    • 训练 k 个分类器
    • 每个分类器考虑所有训练样本
    • 测试时所有分类器投票
  2. 一对一(one-vs-one)
    • 训练 k*(k - 1)/2 个分类器
    • 每个分类器只考虑两个类的样本
    • 测试时所有分类器投票

5. 详细操作步骤总结

5.1 数据集加载与预处理

  1. 下载数据集 :从 http://benchmark.ini.rub.de/?section=gtsrb&subsection=dataset 免费获取 GTSRB 数据集。
  2. 解析数据集 :使用 load_data 函数加载数据集,该函数会完成以下操作:
    • 遍历指定的 10 个类别,读取每个类别的 CSV 注释文件。
    • 读取图像文件,并根据 cut_roi 参数决定是否裁剪图像到感兴趣区域。
    • 提取特征(如果指定),并将数据分为训练集和测试集。
(X_train, y_train), (X_test, y_test) = load_data(rootpath="datasets", feature="hog", cut_roi=True, 
                                                test_split=0.2, plot_samples=False, seed=113)
  1. 数据预处理 :在 _extract_feature 函数中完成数据的预处理,包括调整图像大小、归一化和均值减法。

5.2 特征提取

  1. 选择特征类型 :根据具体需求选择合适的特征类型,如灰度、RGB、HSV、SURF 或 HOG。
  2. 提取特征 :调用 _extract_feature 函数提取特征。
X = _extract_feature(X, feature="hog")

5.3 支持向量机训练与测试

  1. 初始化多类 SVM 分类器 :根据分类策略(一对多或一对一)初始化相应数量的 SVM 分类器。
multi_svm = MultiClassSVM(num_classes=10, mode="one-vs-all", params=None)
  1. 训练分类器 :调用 fit 函数训练分类器。
multi_svm.fit(X_train, y_train, params=None)
  1. 测试分类器 :调用 evaluate 函数测试分类器,并计算准确率、精度和召回率。
accuracy, precision, recall = multi_svm.evaluate(X_test, y_test, visualize=False)
print(f"Accuracy: {accuracy}, Precision: {precision}, Recall: {recall}")

5.4 评估指标计算

  1. 混淆矩阵 :调用 __confusion 函数计算混淆矩阵。
conf_matrix = multi_svm.__confusion(y_test, Y_vote)
print(conf_matrix)
  1. 准确率 :调用 __accuracy 函数计算准确率。
accuracy = multi_svm.__accuracy(y_test, Y_vote)
print(f"Accuracy: {accuracy}")
  1. 精度 :调用 __precision 函数计算精度。
precision = multi_svm.__precision(y_test, Y_vote)
print(f"Precision: {precision}")
  1. 召回率 :调用 __recall 函数计算召回率。
recall = multi_svm.__recall(y_test, Y_vote)
print(f"Recall: {recall}")

6. 不同特征和分类策略的性能分析

6.1 特征对性能的影响

不同的特征类型对分类性能有不同的影响:
- 灰度特征 :通常作为基线性能,信息性相对较弱,但计算简单。
- RGB 特征 :利用了交通标志的颜色信息,可能比灰度特征提供更好的性能。
- HSV 特征 :更稳健地表示颜色,对光照变化有更好的适应性,有望提供更好的性能。
- SURF 特征 :能够提取图像的局部特征,对图像的旋转和尺度变化具有一定的不变性。
- HOG 特征 :对光照和阴影变化具有较好的不变性,与 SVM 结合使用时表现出色。

6.2 分类策略对性能的影响

  • 一对多(one-vs-all) :训练的分类器数量较少,但每个分类器需要处理所有的训练样本,可能会导致训练时间较长。在测试时,所有分类器同时投票,最终选择得票最多的类。
  • 一对一(one-vs-one) :训练的分类器数量较多,但每个分类器只处理两个类的样本,训练任务相对简单。在测试时,所有分类器同时投票,最终选择得票最多的类。

表格:不同特征和分类策略的性能对比

特征类型 分类策略 准确率 精度 召回率
灰度 一对多
灰度 一对一
RGB 一对多
RGB 一对一
HSV 一对多
HSV 一对一
SURF 一对多
SURF 一对一
HOG 一对多
HOG 一对一

7. 实际应用中的注意事项

7.1 数据质量

确保数据集的质量,包括图像的清晰度、标注的准确性等。数据集中的噪声和错误标注可能会影响分类器的性能。

7.2 特征选择

根据具体的应用场景和数据集特点选择合适的特征。可以通过实验比较不同特征的性能,选择最优的特征组合。

7.3 超参数调整

在实际应用中,需要对分类器的超参数进行调整,如 SVM 的核函数、惩罚因子等。可以使用交叉验证等方法来选择最优的超参数。

7.4 模型评估

除了准确率、精度和召回率等指标外,还可以考虑其他评估指标,如 F1 值、ROC 曲线等,以更全面地评估模型的性能。

流程图:实际应用流程

graph LR
    A[数据收集与预处理] --> B[特征提取与选择]
    B --> C[模型训练与超参数调整]
    C --> D[模型评估与优化]
    D --> E[模型部署与应用]

8. 总结与展望

8.1 总结

通过本文的介绍,我们了解了如何构建一个有效的交通标志识别系统。主要步骤包括选择合适的数据集、提取有效的特征、使用支持向量机进行分类,并使用准确率、精度和召回率等评估指标来评估模型的性能。不同的特征和分类策略会对分类性能产生影响,需要根据具体情况进行选择和调整。

8.2 展望

未来的研究可以在以下几个方面进行探索:
- 更复杂的特征提取方法 :如深度学习中的卷积神经网络(CNN),可以自动学习更高级的特征表示。
- 集成学习 :将多个分类器组合起来,提高分类性能。
- 实时识别 :优化算法,实现交通标志的实时识别,以满足实际应用的需求。

总之,交通标志识别是一个具有挑战性和实际应用价值的研究领域,通过不断的研究和创新,我们可以构建更加准确、高效的交通标志识别系统。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值