3.4 基于传统特征的传统图像分类方法
3.3节介绍了如何使用图片的颜色以及形状特征,在图像中标注足球的位置。这个标注过程实际上还是基于人工规则进行的,存在很多问题:
- 只能识别蓝黄相间的球。
- 只能识别绿色草地上的球。
- 摄像机距离拉远、拉近后球的像素大小改变,仍然无法识别。
由此可见,人工规则在实际应用的场景中会遇到各种预想不到的结果,在这些情况下,人工规则的表现会大打折扣。为了解决这些问题,图像处理技术引入了机器学习方法,希望通过机器规则来代替人工规则。这一思(tao)路(lu),总体如下:
(1)针对某一图片,将几百、上千像素图片简化为少数几个区域,计算每个区域中轮廓特征的走向。
(2)将正负样本所有图片执行第三步,将轮廓走向图放入机器学习分类器进行训练。
(3)将训练好的分类器应用在新的图片中。
我们审视这一思路,发现这个过程的核心思想其实是在简化图片——第一步中,原本图片里各种不同的物体被减少到只剩下轮廓了,但这还不够,最后计算的是一个区域内的轮廓走向,此时一张1200×800×3大小的图片,可能只剩下1000个点了,信息减少了上千倍。
这么做的原因首先是传统的机器学习分类器,对输入数据的大小有一个大致的上限,几千张图像数据如果不经过简化,直接送入模型,模型是无法训练输入数据的;其次,图像数据最有趣的一点是图中一个像素点和周围像素点周围联系非常紧密,数据的冗余度很大,这种冗余体现在很多时候,即使给图片压缩、涂黑、打马赛克,我们也大致知道这张图片的内容;同理,对图片进行简化,也是基于这一思想。
本节基于这个思路来介绍一下如何用python-opencv处理图像,对图片进行简化处理,然后用上一章的机器学习方法进行图像分类。我们使用优达学城(Udacity)自动驾驶纳米学位的一个开源项目一部分内容作为讲解材料,希望了解完整部分的读者可以去优达学城官网udacity.cn以及开源地址https://github.com/udacity/CarND-Vehicle-Detection查看完整内容。
我们使用了优达学城标注的一个行车记录仪数据集,这个数据集来自GTI以及KITTI数据集,标注了车辆以及非车辆的情况,具体如下:
运算结果:
其结果如图3-18所示。
图3-18 运行结果
我们的任务是利用几千张这样的标注图片组成的数据集来训练一个分类器,以区别输入图片是否是车辆。
3.4.1 将图片简化为少数区域并计算每个区域轮廓特征的方向
这里其实是运用方向梯度直方图算法(Histogram of Oriented Gradients, HOG)来实现的。这一算法可以分为以下步骤(http://www.learnopencv.com/histogram-of-oriented-gradients/):
- 图像归一化(可选)。
- 计算图像中x以及y方向的梯度。
- 根据梯度,计算梯度柱状图。
- 对块状区域进行归一化处理。
- 展开结果,将一张图片转换成一个一维的特征向量。
- 提取的特征,交给支持向量机或神经网络分类器进行训练分类。
例如,可以将如图3-19所示的左图作为HOG算法的输入文件,得到类似右图的结果。首先,HOG算法的结果只表示轮廓线的变换,无关颜色(如白色的背景以及右下角黑色部分)都没有轮廓线;其次,整张图x、y方向有成百上千的像素点,这里被缩小到了32×32的网格区域,这一点类似前面提到的二维码的读取。当然,这里与二维码不同的是,除了分组求平均值以外,还考虑了轮廓线的方向性,即线条颜色深浅表示了轮廓线数目的多少,同时线条的方向也可以表示这块区域内轮廓线的总体走势。
图3-19 HOG算法实例(图片来自skimage的官方文档)
进一步阅读官方文档,发现用法如下:
fd, hog_image = hog(image, orientations=8, pixels_per_cell=(16, 16), cells_per_block=(1, 1), visualize=True)
这里有三个可以调的参数,即orientations、pixels_per_cell以及cells_per_block。这三个参数的含义简单说明如下:
- pixels_per_cell是指多少个像素作为一个网格来计算,这个值越高,切出来的网格就越少,整个HOG的结果就越粗略。
- orientation是指切出来每个网格中有几种方向的走势,如果是四种,就是上、下、左、右;如果是八种,就再增加上左、上右、下左、下右四种方向。
- cells_per_block是指一个网格中使用几个方向指针,如果是十字交叉的情况,则至少需要两个方向指针进行交叉,才可以表示出十字。
然后就是借助matplotlib可视化包查看一下不同参数的结果,如图3-20所示。
图3-20 使用不同的HOG参数分析输入图片得出不同的结果
这里决定用cell_per_block = 2,因为感觉结果中最多只出现了两个方向的交叉。然后orientations = 9对拐角处的特征保留得更好,看起来像车的形状。最后,pix_per_cell = 8看起来正好可以保持车的形状,取6得到的网格过多,取10则得到的网格过少。
3.4.2 将HOG变换运用在所有正负样本中
这里将正负样本抽样相关的函数写成一个class,然后在这里引用class进行相应的操作,得到正负样本在各个图片中的区域。然后从这些区域中提取图像文件,resize到64×64,计算HOG值,进而保存在矩阵中。
注意,下一步需要利用机器学习进行相应的判别,为了评价分类的准确性,这里需要将正负样本进一步切割为训练集和测试集。
3.4.3 训练模型
完成前面的步骤后将开始模型的训练。训练的第一步是需要对数据进行标准化处理:
X_scalerM = StandardScaler() X_trainT = X_scalerM.fit_transform(X_train) X_testT = X_scalerM.transform(X_test)
X_trainTs,y_trainTs = sklearn.utils.shuffle(X_trainT, y_train)
使用sklearn的支持向量机模块进行训练。训练后,看看验证集的表现:
svc = SVC(random_state=0, C=1) t=time.time() svc.fit(X_trainTs, y_trainTs) t2 = time.time() print(round(t2-t, 2), 'Seconds to train SVC...') #51.54 Seconds to train SVC...
训练完成。接下来,用划分出来的验证集查看模型的表现。
3.4.4 将训练好的分类器运用在新的图片中
开始划分数据时就使用train_test_split(pd_SampClass, test_size=0.33, random_state=42),分别划分了训练集以及验证集。其中,66%的数据被划分为训练集,用于上一步的模型训练。
这里,将用剩下的33%作为验证集来检验模型的表现。首先看准确率:
print('Test Accuracy of SVC = ', round(svc.score(X_testT, y_test), 4))
运行结果:
# out: Test Accuracy of SVC = 0.9869
98%的分类准确率还是不错的。接下来选十个样本看结果:
n_predict = 10 print('My SVC predicts: ', svc.predict(X_testT[0:n_predict]))
运行结果:
# out: My SVC predicts: [1 1 1 0 0 0 0 1 1 1]
print('For these',n_predict, 'labels: ', y_test[0:n_predict])
运行结果:
# out: For these 10 labels: [1 1 1 0 0 0 0 1 1 1]
选的前十个测试样本,结果都是预测正确。最后看AUC值:
pred = svc.predict(X_testT) print("AUC for Merge dataset = %1.2f,\n" % (roc_auc_score(pred, y_test)))
运行结果:
# out: AUC for Merge dataset = 0.99,
print(confusion_matrix(pred, y_test))
运行结果:
# out: [[2929 55] [ 22 2855]]
发现大多数预测准确,假阳性的数量是真阴性的2倍多,是一个基本可用的模型。至于这个模型的适用性如何,读者可以在网上找图片,resize到64×64,看看是否可以成功预测。
最后,总结本章所学的内容。本章开始部分提到,要用机器学习方法分析传统图像数据,可以有三种思路:
- 手动提取重要的特征,用数字表示。如鸢尾花数据集,当年就是用尺子量出来的长度、宽度,交给机器学习分类器。
- 用简单的图像处理操作,将图片转换为少数几个简单的轮廓特征,交给机器学习分类器。
- 用深度神经网络,让深度学习模型自动提取图片的各种特征,再用模型自动提取的特征训练分类器。
本章介绍的是第二种方法,即提取简单特征,交给机器学习分类器,如支持向量机。这种方法的问题想必读者也有切身体会:
(1)“笨”——连足球都不认识,需要手动提取颜色、轮廓,然后过滤噪声,最后交给霍夫变换检测器,计算机才认识这是个圆形。
(2)“眼花”——我们拿到的是一个高像素的图片,需要将图片的信息不断模糊、减少信息量,机器学习模型才能“认识”这张图片,才能用这种简化后的图片进行模型训练。
我们再思考一下,为什么传统计算机模型会显得很“笨”,而且眼神不好?一个很重要的原因是,传统的机器学习模型缺乏特征组合能力,尤其是对图像输入,计算机可以理解单独的一个像素,但是把单一像素与周围三五个点一起考虑,计算机模型在组合的时候,似乎不太能把握这一组点的关系,所以我们会用opencv skimage把人类在识别诸如球体、车辆这样的物体的关键因素提取出来,然后将提取的信息交给机器学习模型。
接下来介绍的基于深度神经网络的方法,特征提取部分就并不完全由人手动完成,计算机模型可以帮助数据分析者提取诸如球形、车体框架这样的特征。于是我们发现,相比传统的图像处理技术,计算机不“笨”了、不需要人提取特征了,眼神也变好了,可以直接识别原始图片了。这也是深度学习技术总是和人工智能相提并论的一个很重要的原因。