交通标志识别学习指南
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 结合使用,对光照和阴影变化具有较好的不变性 |
列表:多类分类策略对比
- 一对多(one-vs-all)
- 训练 k 个分类器
- 每个分类器考虑所有训练样本
- 测试时所有分类器投票
- 一对一(one-vs-one)
- 训练 k*(k - 1)/2 个分类器
- 每个分类器只考虑两个类的样本
- 测试时所有分类器投票
5. 详细操作步骤总结
5.1 数据集加载与预处理
- 下载数据集 :从 http://benchmark.ini.rub.de/?section=gtsrb&subsection=dataset 免费获取 GTSRB 数据集。
- 解析数据集 :使用
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)
- 数据预处理 :在
_extract_feature函数中完成数据的预处理,包括调整图像大小、归一化和均值减法。
5.2 特征提取
- 选择特征类型 :根据具体需求选择合适的特征类型,如灰度、RGB、HSV、SURF 或 HOG。
- 提取特征 :调用
_extract_feature函数提取特征。
X = _extract_feature(X, feature="hog")
5.3 支持向量机训练与测试
- 初始化多类 SVM 分类器 :根据分类策略(一对多或一对一)初始化相应数量的 SVM 分类器。
multi_svm = MultiClassSVM(num_classes=10, mode="one-vs-all", params=None)
- 训练分类器 :调用
fit函数训练分类器。
multi_svm.fit(X_train, y_train, params=None)
- 测试分类器 :调用
evaluate函数测试分类器,并计算准确率、精度和召回率。
accuracy, precision, recall = multi_svm.evaluate(X_test, y_test, visualize=False)
print(f"Accuracy: {accuracy}, Precision: {precision}, Recall: {recall}")
5.4 评估指标计算
- 混淆矩阵 :调用
__confusion函数计算混淆矩阵。
conf_matrix = multi_svm.__confusion(y_test, Y_vote)
print(conf_matrix)
- 准确率 :调用
__accuracy函数计算准确率。
accuracy = multi_svm.__accuracy(y_test, Y_vote)
print(f"Accuracy: {accuracy}")
- 精度 :调用
__precision函数计算精度。
precision = multi_svm.__precision(y_test, Y_vote)
print(f"Precision: {precision}")
- 召回率 :调用
__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),可以自动学习更高级的特征表示。
- 集成学习 :将多个分类器组合起来,提高分类性能。
- 实时识别 :优化算法,实现交通标志的实时识别,以满足实际应用的需求。
总之,交通标志识别是一个具有挑战性和实际应用价值的研究领域,通过不断的研究和创新,我们可以构建更加准确、高效的交通标志识别系统。
超级会员免费看
1415

被折叠的 条评论
为什么被折叠?



