基于Matlab的深度学习的汽车目标检测
目前大部分的深度学习或者主流的深度学习算法大部分都是基于python所做的“前端”所实现的,python的语言足够简洁,社区相对成熟(框架多,类库多,教程丰富),从目前的来看,scala等后起之秀在短时间内代替不了Python。
但是在这一篇小Q想用matlab自带的深度学习做一回汽车的目标检测,抛砖引玉,用另外一种思维。也是给自己回忆一下,毕竟一年多没有经手过深度学习的项目了,之后我会复现一些ECCV2020一些热门论文的project,当然啦,在以后的文章里面。
这次我们是用的是已标记的小汽车样本数据训练RCNN(Reigions with Convolutional Neural Networks, RCNN)神经网络得到检测器模型,并采用测试样本队训练好的检测器模型进行评测,实现汽车目标检测的效果。
这是一个比较基础的实战项目,对于新手友好。
理论基础
虽然很千万遍了,但是我还要讲一讲CNN卷积网络的一些理论结构 ,给新手回顾一下。但是不是零基础理解深度学习的入门。网上有很多大神写的深度学习入门篇,就不献丑。
基本架构
卷积神经网络基本架构包括特征提取器和分类器。特征提取器通常由若干个卷积层和池化层叠加组成,卷经济和池化过程不断将特征图缩小,同食会导致特征图数量的怎繁多。特征提取器后面一般连接分类器,同城有一个多层感知机组成。特别的,在最后一个特征抽取其后面,将所有的特征图展开并排列成一个向量,这个就是特征向量,作为后分类器的输入。
接下来说说卷积层和池化层layer。
卷积层
卷积层的基本操作是将卷积核与图像的对应区域进行卷积得到一个值,通过在图像上不断移动卷积核和来计算倦极至,进而完成对整幅图像的卷积运算。在神经网络中,卷积层不仅涉及一般的图像卷积,还设计深度和步长的概念。深度对应于同一个区域内的神经元数就是有几个卷积在对同一块区域进行卷积运算;步长是对应于卷积和移动多少个像素,也就是前后距离的远近程度。
上图就是一次卷积运算的简单示例,我们通过输入的图像元素用一个卷积核映射到上面的区域上的单元进行卷积运算,然后将卷积计算的结果映射到新的卷积层,之后卷积核向右或者向下移动一个步长,进行下一次运算。直到用步长走完整个输入图像。
但是,一般情况下来说,图像的像素数量级是非常大的,仅仅举个例子,1000 * 1000已经是非常小的图像,它可以被表示为一个长度为1*10^6的向量。如果设置隐含层与输入层的数目相同,则输入层到隐含层的参数个数就会为 10^12,就会带来大量的参数,无法进行训练。所以,应用卷积层来训练图像数据,必须注意减少参数来保证计算速度。一般来说,卷积神经网络减低参数数量的放大有两种,局部感知和参数共享。
局部感知
人对外界的认知一般可以归纳为从局部到全局的过程,而图像的像素空间联系也是局部间的相关性强,远距离的弱。所以在卷积神经网络的每一个神经元实际上只需关注图像局部的感知,对于全局图像的感知可通过更高层综合局部信息来获得,这也说明了卷积神经网络部分联通的思想。类似于生物学中的视觉系统结构,视觉皮层的神禁苑用于拒不接受信息,及这些神经元只影响默写特定区域的刺激,呈现出部分联通的特点。
就像上面我们看到的例子,一个卷积核就是3*3的像素值就是一个神经元的链接,假设我们每个神经元和
10 *10大小的像素值连接。以10^6个神经元看,单层卷积计算之后有的输出只剩下了100 *10^6个参数。明显减少。
参数共享
局部感知过程假设每个神经元都对应100个参数,共10^6神经元,则参数一共有100* 106个,依然会是一个非常庞大的数字。如果这106个神经元100个参数相等,那么参数的个数就会变为100,及每个神经元用同样的卷积核执行卷积操作,大大降低计算量。
用具体的人脸识别的例子来说,到高层时,CNN将这些浅层基础特征组成成更高级的特征,比如人的眼睛、鼻子等,直到全连接层将这种两个眼睛一个鼻子的组合识别为人脸。因此,即使对图像进行平移、缩放、旋转,在浅层依旧能提取出点、线、边缘等基础特征,到高层就能将它们组合成眼睛鼻子,从而最终依旧能被识别为人脸。(一个说法是,平移旋转后的图像到高层对应的特征图一定程度上也是平移旋转后的。过多的平移旋转,最终可能影响到图像的识别,因此更鼓励用data augmentation来实现对旋转的鲁棒。)
多核卷积
如果10 * 10维度的卷积核都是一样的,那么只能提取出来图像的一种特征,局限性太大。所以我们可以增加卷积核来提高,比如使用32个不同的卷积核用于学习32种不同特性的。用不同卷积图得到的图像的不同特征可以称之为特征图(Feature map)。这时候卷积层包含了10* 10* 32个参数。
池化层
从理论上来看,经过卷积层得到的特征集合,可直接用于训练分类器(例如经典的Softmax分类器),但是这往往会带来巨大的计算量。试想一下,一个1000 * 1000的图像,卷积层神经元有1000 * 1000个,卷积核有32个,所以卷积特征向量的长度会达到32 * 1000 * 1000 .这不仅是一个海量的运算量,也会出现过拟合的现象.在对此问题的实际处理过程中,可以对通过不同位置的特征进行聚合统计等处理来降低数据规模,提高运行速度。;哦如,通过计算图像局部区域上的某些特定的平均值或者最大值等来计算该要统计特征。这些概要统计特征相对于卷积层计算得到的特征图,不仅达到了将为目的,同时还会提高训练效率,这种特征聚合的操作叫做池化(Pooling),根据统计方式的不同也可以叫做平均池化或者最大池化。下图为最大池化的一个例子:在输入的图层像素中我们以一个22的像素图为一个filter单位,求这个单位的最大值,下图中红色部分为6,所以过滤后22的像素就变成了一个像素,像素的值为原来的2*2像素图当中最大的值:6,以此类推,从而使得输入数据量的线性下降。
平均池化的方法类似,只不过从求最大值变成了求平均。
程序讲解
下面我用一个简单的例子讲一讲在matlab上面的CNN实现,在运行程序之前,确保本机上面已经安装了对应的CUDA和CUDNN版本。不会装请看下面的链接文章:
Windows 安装 CUDA/cuDNN
和必要的tensorflow。
Matlab加载数据
我们这里加载的是matlab自带的数据集,一共295张图像,每幅图像当中标记了1~2辆汽车。但是在实际应用场景中,将需要更多的训练数据来提高CNN鲁棒性。数据加载的核心代码如下所示
%% 加载数据
% vehicleDataset是一个dataset数据类型,第一列是图片的相对路径,第二列是图片中小汽车的位置
data = load('fasterRCNNVehicleTrainingData.mat');
% 提取训练集
vehicleDataset = data.vehicleTrainingData;
% 提取图片路径
dataDir = fullfile(toolboxdir('vision'),'visiondata');
vehicleDataset.imageFilename = fullfile(dataDir, vehicleDataset.imageFilename);
接下来读取了这295张照片的前12张
% 展示前9幅图片
k = 12;
I=zeros(128,228,3,k);
for i = 1 : k
% 读取图片
tmp = imread(vehicleDataset.imageFilename{i});
% 添加标识框
tmp = insertShape(tmp, 'Rectangle', vehicleDataset.vehicle{i});
I(:,:,:,i) = mat2gray(tmp);
end
% 显示
Is = I;
hfig = figure; montage(Is);
set(hfig, 'Units', 'Normalized', 'Position', [0, 0, 1, 1]);
pause(1);
当程序运行到这里的时候,训练的数据已经加载,并显示了前12张添加了矩形标记的图像,如下图所示:
构建CNN网络
CNN网络是进行CNN目标识别的基础,MATLAB神经网络工具箱提供了构建CNN网络的基本函数,主要是有网络的输入层,中间层和输出层组成。
首先,定义网络输入层。通过imageInputLayer
函数定义CNN网络的类型和维度。根据应用场景的不同,输入的维度也会有差别。其中,目标检测应用的输入维度一般等于检测目标的最小尺寸;图像分类的输入维度一般等于训练图像的尺寸。这次我们是进行目标检测,并且训练数据中最小汽车区域的像素约为32 * 32,所以设置为32 * 32。核心代码如下面所示:
%% 构建CNN网络
% 输入层,最小检测对象约32*32
inputLayer = imageInputLayer([32 32 3]);
接下来我们需要定义网络中间层。他是卷积卷积神经网络的核心内容,一般会有卷积函数,激活函数和池化函数构成。中间层可以多次重复使用卷积函数但是为了避免对图像过度采样导致图像细节丢失,建议尽量使用最小数量池化层。
% 中间层
% 定义卷基层参数
filterSize = [3 3];
% 一个卷积核3*3大小
numFilters = 32;
middleLayers = [
% 第一轮,只包含CNN和ReLU
% padding的用途:保持边界信息,如果没有加padding的话,输入图片最边缘的像素点信息只会被卷积核操作一次
% 但是图像中间的像素点会被扫描到很多遍,那么就会在一定程度上降低边界信息的参考程度,但是在加入padding后,在实际处理过程中就会从新的边界进行操作,就从一定程度上解决了这个问题。
% 在卷积神经网络的卷积层加入Padding,可以使得卷积层的输入维度和输出维度一致。
convolution2dLayer(filterSize, numFilters, 'Padding', 1)
reluLayer()
% 第二轮,包含CNN、ReLU和Pooling
convolution2dLayer(filterSize, numFilters, 'Padding', 1)
reluLayer()
maxPooling2dLayer(3, 'Stride',2)
% 步长为2的最大池化层
];
之后就是定义网络输出层。输出层一般由经典的全连接层和分类层构成,用于结果的输出。
% 组合所有层
layers = [
inputLayer
middleLayers
finalLayers
];
训练CNN网络
为了利用数据,在训练之前将数据分为训练和测试两个部分。为此,按数据标号,前60%用于训练,后40%用于测试。
%% 训练CNN网络
% 将数据划分两部分
% 前60%的数据用于训练,后面40%用于测试
ind = round(size(vehicleDataset,1) * 0.6);
trainData = vehicleDataset(1 : ind, :);
testData = vehicleDataset(ind+1 : end, :);
在Matlab里面的神经网络工具箱提供了trainFasterRCNNObjectDetector来进行CNN网络的训练,整个训练的过程包含了四步,每一步都可以制定不同的训练参数,也可以用相同的参数。在这个例子里面,设置前两部的学习速率为1e-5 后两步的学习速率为1e-6.
什么是学习速率我就不解释了,毕竟算是最基础知识,不知道的同学请点下面链接:
为什么我们会使用两种不同的学习速率?
学习率过大,在前期会加速学习,使得模型更容易接近局部或全局最优解。但是在后期会有较大波动,甚至出现损失函数的值围绕最小值徘徊,波动很大,始终难以达到最优。所以引入学习率衰减的概念,直白点说,就是在模型训练初期,会使用较大的学习率进行模型优化,随着迭代次数增加,学习率会逐渐进行减小,保证模型在训练后期不会有太大的波动,从而更加接近最优解。而这里,我们在前期使用较大的学习速率,后两部采用较小的学习速率,期望得到一个稳定的模型。这两个数据可以大家修改看看,调出自己的最优解。
代码如下:
options = [
% 第1步,Training a Region Proposal Network (RPN)
trainingOptions('sgdm', 'MaxEpochs', 10,'InitialLearnRate', 1e-5,'CheckpointPath', tempdir)
% 第2步,Training a Fast R-CNN Network using the RPN from step 1
trainingOptions('sgdm', 'MaxEpochs', 10,'InitialLearnRate', 1e-5,'CheckpointPath', tempdir)
% 第3步,Re-training RPN using weight sharing with Fast R-CNN
trainingOptions('sgdm', 'MaxEpochs', 10,'InitialLearnRate', 1e-6,'CheckpointPath', tempdir)
% 第4步,Re-training Fast R-CNN using updated RPN
trainingOptions('sgdm', 'MaxEpochs', 10,'InitialLearnRate', 1e-6,'CheckpointPath', tempdir)
];
% 设置模型的本地存储
doTrainingAndEval = 1;
if doTrainingAndEval
% 训练 R-CNN 神经网络,其实神经网络工具箱提供了3个函数
% (1)trainRCNNObjectDetector,训练快检测慢,允许指定proposalFcn
% (2)trainFastRCNNObjectDetector,速度较快,允许指定proposalFcn
% (3)trainFasterRCNNObjectDetector,优化运行性能,不需要指定proposalFcn
detector = trainFasterRCNNObjectDetector(trainData, layers, options, ...
'NegativeOverlapRange', [0 0.3], ...
'PositiveOverlapRange', [0.6 1], ...
'BoxPyramidScale', 1.2);
else
% 加载已经训练好的神经网络
detector = data.detector;
end
上述代码中 “sgdm”是优化方法:SGD with momentum 的简称,Momentum算法借用了物理中的动量概念,它模拟的是物体运动时的惯性,即更新的时候在一定程度上保留之前更新的方向,同时利用当前batch的梯度微调最终的更新方向。
当然,除了sgdm还有很多期的优化方法:如下图所示:
epochs被定义为向前和向后传播中所有批次的单次训练迭代。这意味着1个epoch是整个输入数据的单次向前和向后传递。简单说,epochs指的就是训练过程中数据将被“轮到”多少次。 亦即应当完整遍历数据集多少次(一次为一个epoch)。
如果epoch数量太少,网络有可能发生欠拟合;如果epoch数量太多,则有可能发生过拟合。
举个例子来说,训练集有10000个样本,batchsize=10,那么,训练完整个样本集可以需要100次iteration,10次epoch。
接下的代码里面训练模型时提到的三个系数:
‘PositiveOverlapRange’ - 一个双元素向量,指定0和1之间的边界框重叠比例范围,与指定范围内(即之前做图片标注画出的框)的边界框重叠的区域提案被用作正训练样本。Default: [0.5 1]
‘NegativeOverlapRange’ - 一个双元素向量,指定0和1之间的边界框重叠比例范围,与指定范围内(即之前做图片标注画出的框)的边界框重叠的区域提案被用作负训练样本。Default: [0.1 0.5]
经过一定时间的训练之后,得到了CNN网络模型。为了测试,我们选择了highway.png进行输入,结果表明此CNN可以检测到目标车辆并各处位置。
代码如下:
I = imread('highway.png');
% 运行检测器,输出目标位置和得分
[bboxes, scores] = detect(detector, I);
% 在图像上标记处识别的小汽车
I = insertObjectAnnotation(I, 'rectangle', bboxes, scores);
figure; imshow(I)
训练CNN网络
为了验证CNN的训练效果,必须进行大规模测试,。Matlab计算机视觉工具箱提供了平均精确度evaluateDetectionPrecision来评估,并计算召回率和精确率指标来作为评估标准。一般来说,针对单目标的检测问题,存在下面四种概率。
包含 | 不包含 |
---|---|
TP(True Positive) | FP(False Positive) |
FN(False Negative) | TN(True Negative) |
定义精确率P = TP/(TP+FP)就是检测到目标的图像中真正包含目标的比例。
定义召回率R = TP/(TP+FN)就是包含目标的图像被成功检测出来的比例。
核心代码如下:
% 评估训练效果
if doTrainingAndEval
results = struct;
for i = 1:size(testData,1)
% 读取测试图片
I = imread(testData.imageFilename{i});
% 运行CNN检测器
[bboxes, scores, labels] = detect(detector, I);
% 结果保存到结构体
results(i).Boxes = bboxes;
results(i).Scores = scores;
results(i).Labels = labels;
end
% 将结构体转换为table数据类型
results = struct2table(results);
else
% 加载之前评估好的数据
results = data.results;
end
% 从测试数据中提取期望的小车位置
expectedResults = testData(:, 2:end);
%采用平均精确度评估检测效果
[ap, recall, precision] = evaluateDetectionPrecision(results, expectedResults);
% 绘制召回率-精确率曲线
figure;
plot(recall, precision);
xlabel('Recall');
ylabel('Precision')
grid on;
title(sprintf('Average Precision = %.2f', ap));
这时候会出现一个代表精确度走向的图标,这个图我就不放了,每个人训练出来的精度不一样,图标显示也就不一样。
一般训练出来的精确度较低,就说明本文的CNN网络层数显然是不够的,需要在增加网络层数来改善精确度,大家可以按照上面的例子尝试增加layer,再次训练,已达到高精确率要求。