使用YOLO v3深度学习的对象检测
这个例子展示了如何训练一个YOLO v3意思对象探测器。
深度学习是一种强大的机器学习技术,可以用来训练强大的对象检测器。有几种用于物体检测的技术,包括Faster R-CNN、只看一次(YOLO) v2和单发检测器(SSD)。这个例子展示了如何训练一个YOLO v3对象检测器。YOLO v3在YOLO v2的基础上进行了改进,增加了多个尺度的检测,以帮助检测更小的对象。将用于训练的损失函数分为均方误差用于边界盒回归和二元交叉熵用于目标分类,以提高检测精度。
注意:本示例需要YOLO v3对象检测的计算机视觉工具箱™模型。您可以从插件资源管理器中安装用于YOLO v3对象检测的计算机视觉工具箱模型。有关安装插件的更多信息,请参见获取和管理插件.
下载预培训网络
通过使用助手功能下载预先训练的网络downloadPretrainedYOLOv3Detector
避免等待训练完成。如果您想用一组新的数据训练网络,请设置doTraining
变量来真正的
.
doTraining = false;如果~doTraining preTrainedDetector = downloadPretrainedYOLOv3Detector();结束
加载数据
本例使用了一个包含295张图像的小型标记数据集。这些图片大多来自加州理工学院赛车1999年和2001年的数据集,由Pietro Perona创建,并经许可使用。每张图片包含一个或两个标记的车辆实例。一个小的数据集对于探索YOLO v3训练过程是有用的,但在实践中,需要更多的标记图像来训练一个健壮的网络。
解压缩车辆图像并加载车辆地面真相数据。
解压缩vehicleDatasetImages.zip数据=负载(“vehicleDatasetGroundTruth.mat”);vehicleDataset = data.vehicleDataset;将完整路径添加到本地车辆数据文件夹。vehicleDataset。imageFilename = fullfile(pwd, vehicleDataset.imageFilename);
注意:在有多个类的情况下,数据也可以被组织为三列,其中第一列包含带有路径的图像文件名,第二列包含边界框,第三列必须是一个单元格向量,其中包含与每个边界框对应的标签名称。有关如何安排边界框和标签的更多信息,请参见boxLabelDatastore
.
所有的边界框都必须在表单中[x y宽度高度]
.这个向量指定左上角和边界框的大小(以像素为单位)。
将数据集分为训练集和测试集,分别用于训练网络和评估网络。使用60%的数据用于训练集,其余数据用于测试集。
rng (0);shuffledIndices = randperm(height(vehicleDataset));idx = floor(0.6 * length(shuffledIndices));trainingDataTbl = vehicleDataset(shuffledIndices(1:idx),:);testDataTbl = vehicleDataset(shuffledIndices(idx+1:end),:);
创建用于加载图像的图像数据存储。
imdsTrain = imageDatastore(trainingDataTbl.imageFilename);imdsTest = imageDatastore(testDataTbl.imageFilename);
为ground truth边界框创建一个数据存储。
bldsTrain = boxLabelDatastore(trainingDataTbl(:, 2:end));bldsTest = boxLabelDatastore(testDataTbl(:, 2:end));
组合图像和框标签数据存储。
trainingData = combine(imdsTrain, bldsTrain);testData = combine(imdsTest, bldsTest);
使用validateInputData
为了检测无效的图像、边界框或标签,即
图像格式无效或包含nan的示例
包含0 / nan / info /空的包围框
失踪/ non-categorical标签。
边界框的值应该是有限的、正的、非分数的、非nan的,并且应该在具有正的高度和宽度的图像边界内。任何无效的样本必须丢弃或进行适当的训练。
validateInputData (trainingData);validateInputData (testData);
数据增加
数据增强是通过训练过程中对原始数据的随机变换来提高网络精度的一种方法。通过使用数据增强,您可以为训练数据添加更多的多样性,而不必实际增加标记训练样本的数量。
使用变换
函数将自定义数据扩充应用于训练数据。的augmentData
在示例末尾列出的Helper函数对输入数据应用以下扩充。
HSV空间中的颜色抖动增强
随机水平翻转
随机缩放10%
augmentedTrainingData = transform(trainingData, @augmentData);
读取同一图像四次,并显示增强训练数据。
可视化增强图像。augmentedData = cell(4,1);为k = 1:4 data = read(augmentedTrainingData);augmentedData{k} = insertShape(data{1,1},“矩形”、数据{1,2});重置(augmentedTrainingData);结束图蒙太奇(augmentedData,“BorderSize”, 10)
定义YOLO v3对象检测器
本例中的YOLO v3检测器基于SqueezeNet,使用了SqueezeNet中的特征提取网络,并在末端增加了两个检测头。第二探测头的尺寸是第一探测头的两倍,因此能够更好地探测小物体。注意,您可以根据想要检测的对象的大小指定任意数量的不同大小的检测头。YOLO v3检测器使用训练数据估计的锚盒,以获得与数据集类型相对应的更好的初始先验,并帮助检测器学习准确预测锚盒。有关锚框的信息,请参见对象检测的锚框.
YOLO v3探测器中的YOLO v3网络如下图所示。
你可以用深度网络设计器(深度学习工具箱)来创建图中所示的网络。
指定网络输入大小。在选择网络输入大小时,要考虑运行网络本身所需的最小大小、训练图像的大小以及以所选大小处理数据所产生的计算成本。在可行的情况下,选择一个接近训练图像大小且大于网络所需输入大小的网络输入大小。为了减少运行示例的计算成本,指定网络输入大小为[227 227 3]。
networkInputSize = [227 227 3];
首先,使用变换
预处理训练数据以计算锚框,因为本例中使用的训练图像大于227 * 227,且大小不同。将锚点数量指定为6,以实现锚点数量与平均IoU之间的良好权衡。使用estimateAnchorBoxes
函数估计锚框。有关估计锚框的详细信息,请参见根据训练数据估计锚盒.如果使用预先训练的YOLOv3对象检测器,则需要指定在特定训练数据集上计算的锚框。注意,估计过程不是确定的。在调优其他超参数时,为了防止估计的锚框发生变化,可以在使用rng进行估计之前设置随机种子。
rng(0) trainingdataforetimation = transform(trainingData, @(data)preprocessData(data, networkInputSize));numAnchors = 6;[锚,meanIoU] = estimateAnchorBoxes(trainingdatafestimtimation, numAnchors)
锚=6×241 34 163 130 98 93 144 125 33 24 69 66
meanIoU = 0.8507
指定anchorBoxes
用于两个探测头。anchorBoxes
为单元数组[Mx1],其中M为检测头数。的[Nx2]矩阵组成每个探测头锚
,其中N为要使用的锚的数量。选择anchorBoxes
对于每个检测头基于特征图大小。使用更大的锚
在更小的尺度上锚
在更高的尺度上。要做到这一点,请对锚
首先使用较大的锚盒,并将前三个锚盒分配给第一个检测头,后三个锚盒分配给第二个检测头。
Area = anchor (:, 1).*anchor (:, 2);[~, idx] = sort(area,“下”);锚=锚(idx,:);锚固箱={锚(1:3,:)锚(4:6,:)};
在Imagenet数据集上加载预训练的SqueezeNet网络,然后指定类名。您还可以选择加载在COCO数据集上训练的不同预训练网络,例如tiny-yolov3-coco
或darknet53-coco
或Imagenet数据集,如MobileNet-v2或ResNet-18。当使用预先训练的网络时,YOLO v3性能更好,训练更快。
baseNetwork = squeezenet;classNames = trainingDataTbl.Properties.VariableNames(2:end);
接下来,创建yolov3ObjectDetector
对象中添加检测网络源。选择最优的检测网络源需要试错,并且可以使用analyzeNetwork
在网络中查找潜在的检测网络源的名称。对于本例,使用fire9-concat
而且fire5-concat
层,DetectionNetworkSource
.
yolov3Detector = yolov3ObjectDetector(baseNetwork, classNames, anchorBoxes,“DetectionNetworkSource”,{“fire9-concat”,“fire5-concat”}, InputSize = networkInputSize);
另外,与上面使用SqueezeNet创建的网络不同,使用更大的数据集(如MS-COCO)训练的其他预先训练的YOLOv3体系结构可以用于在自定义对象检测任务中转移学习检测器。迁移学习可以通过更改类名和锚点箱来实现。
预处理训练数据
对增强后的训练数据进行预处理,为训练做准备。的进行预处理
方法yolov3ObjectDetector
,对输入数据应用以下预处理操作。
通过维护纵横比,将图像调整为网络输入大小。
缩放范围内的图像像素
[0 1]
.
preprocedtrainingdata = transform(augmentedTrainingData, @(data)预处理(yolov3Detector, data));
读取预处理后的训练数据。
data = read(预处理的trainingdata);
显示带有边框的图像。
I = data{1,1};Bbox = data{1,2};annotation = insertShape(I,“矩形”, bbox);annotatedImage = imresize(annotatedImage,2);图imshow (annotatedImage)
重置数据存储。
重置(preprocessedTrainingData);
指定培训选项
指定这些培训选项。
设置代数为80。
设置小批量大小为
8
.当使用更小的批处理大小时,稳定的训练可以有更高的学习率.
不过,这应该根据可用内存来设置。将学习率设置为0.001。
设置预热时间为
1000
迭代。该参数表示根据公式指数级提高学习率的迭代次数 .它有助于在较高的学习率下稳定梯度。将L2正则化因子设为0.0005。
指定惩罚阈值为0.5。检测到的与ground truth重叠小于0.5的将被扣分。
初始化梯度的速度为
[]
.SGDM使用它来存储梯度的速度。
numEpochs = 80;miniBatchSize = 8;learningRate = 0.001;warmupPeriod = 1000;l2Regularization = 0.0005;penaltyThreshold = 0.5;速度= [];
火车模型
如果有GPU,请使用GPU进行训练。使用GPU需要并行计算工具箱™和CUDA®支持的NVIDIA®GPU。有关支持的计算功能的信息,请参见GPU计算要求(并行计算工具箱).
使用minibatchqueue
函数将预处理后的训练数据进行批量分解,并具有辅助函数createBatchData
返回批处理图像和结合各自类id的边界框。为了更快地提取用于训练的批数据,dispatchInBackground
应设置为“true”,以确保并行池的使用。
minibatchqueue
自动检测GPU的可用性。如果您没有图形处理器,或不想使用图形处理器进行训练,请设置OutputEnvironment
参数“cpu”
.
如果canUseParallelPool dispatchInBackground = true;其他的dispatchInBackground = false;结束mbqTrain = minibatchqueue(预处理的训练数据,2,...“MiniBatchSize”miniBatchSize,...“MiniBatchFcn”createBatchData(图像,框,标签,类名),...“MiniBatchFormat”, [“SSCB”,""),...“DispatchInBackground”dispatchInBackground,...“OutputCast”, ["",“替身”]);
使用辅助功能创建培训进度绘图仪configureTrainingProgressPlotter
在使用自定义训练循环训练检测器对象时查看情节。
最后,指定自定义训练循环。对于每次迭代:
从
minibatchqueue
.如果没有更多数据,则重置minibatchqueue
和洗牌。评估模型梯度使用
dlfeval
和modelGradients
函数。这个函数modelGradients
,作为支持函数,返回损失相对于可学习参数的梯度网
,对应的小批量损耗,以及当前批次的状态。为正则化的梯度应用一个权值衰减因子,以获得更健壮的训练。
方法根据迭代确定学习率
piecewiseLearningRateWithWarmup
支持功能。更新探测器参数
sgdmupdate
函数。更新
状态
具有移动平均线的探测器参数。显示每次迭代的学习率、总损失和单个损失(盒子损失、对象损失和类损失)。这些可以用来解释每个迭代中各自的损失是如何变化的。例如,在经过几次迭代之后,盒子损失突然出现峰值,这意味着在预测中存在Inf或nan。
更新训练进度图。
如果损失已经饱和了几期,训练也可以终止。
如果doTraining为学习率和小批丢失创建子图。图=图;[lossPlotter, learningRatePlotter] = configureTrainingProgressPlotter(图);迭代= 0;%自定义训练循环。为epoch = 1:numEpochs reset(mbqTrain);洗牌(mbqTrain);而(hasdata(mbqTrain))迭代=迭代+ 1;[XTrain, YTrain] = next(mbqTrain);使用dlfeval和梯度函数。[gradients, state, lossInfo] = dlfeval(@modelGradients, yolov3Detector, XTrain, YTrain, penaltyThreshold);%应用L2正则化。gradients = dlupdate(@(g,w) g + l2Regularization*w, gradients, yolov3Detector.Learnables);确定当前学习率值。currentLR = piecewiseLearningRateWithWarmup(iteration, epoch, learningRate, warmupPeriod, numEpochs);使用SGDM优化器更新检测器可学习参数。[yolov3Detector。学习对象,速度]= sgdmupdate(yolov3Detector.)学习对象,梯度,速度,currentLR);更新dlnetwork的状态参数。yolov3Detector。国家=国家;%显示进度。displayLossInfo(epoch, iteration, currentLR, lossInfo);用新点更新训练图。updatePlots(lossPlotter, learningRatePlotter, iteration, currentLR, lossInfo.totalLoss);结束结束其他的yolov3Detector = preTrainedDetector;结束
评估模型
计算机视觉工具箱™提供对象检测器评估功能,以测量常见指标,如平均精度(evaluateDetectionPrecision
)和对数平均失踪率(evaluateDetectionMissRate
).在本例中,使用了平均精度度量。平均精度提供了一个单一的数字,该数字结合了检测器做出正确分类(精度)的能力和检测器找到所有相关对象(召回)的能力。
结果=检测(yolov3Detector,testData,“MiniBatchSize”8);使用平均精度度量评估目标检测器。[ap,recall,precision] = evaluateDetectionPrecision(results,testData);
精度-召回(PR)曲线显示了检测器在不同召回水平下的精确程度。理想情况下,所有召回级别的精确度都是1。
绘制精度-召回曲线。图(召回率,精度)“回忆”) ylabel (“精度”网格)在标题(sprintf ('平均精度= %.2f'据美联社)),
使用YOLO v3检测对象
使用探测器进行物体检测。
读取数据存储。data = read(testData);%获取图像。I = data{1};[bboxes,scores,labels] = detect(yolov3Detector,I);%在图像上显示检测结果。I = insertObjectAnnotation(I,“矩形”bboxes,分数);图imshow(我)
支持功能
模型梯度函数
这个函数modelGradients
以yolov3ObjectDetector
对象,即输入数据的小批处理XTrain
对应的地面真值框YTrain
中指定的惩罚阈值作为输入参数,并返回损失相对于可学习参数的梯度yolov3ObjectDetector
,对应的小批丢失信息,以及当前批的状态。
模型梯度函数通过执行这些操作计算总损失和梯度。
方法从输入批处理的图像中生成预测
向前
方法。收集CPU上的预测以进行后处理。
将预测从YOLO v3网格单元格坐标转换为边界框坐标,以方便与地面真相数据进行比较
anchorBoxGenerator
的方法yolov3ObjectDetector
.使用转换后的预测和地面真实数据生成损失计算目标。这些目标是为边界框位置(x、y、宽度、高度)、对象置信度和类概率生成的。参见配套函数
generateTargets
.计算预测边界框坐标与目标框坐标的均方误差。参见配套函数
bboxOffsetLoss
.确定预测对象置信度评分与目标对象置信度评分的二元交叉熵。参见配套函数
objectnessLoss
.确定目标与预测对象类别的二元交叉熵。参见配套函数
classConfidenceLoss
.计算总损失为所有损失的总和。
计算可学习对象相对于总损失的梯度。
函数[gradients, state, info] = modelGradients(detector, XTrain, YTrain, penaltyThreshold) inputImageSize = size(XTrain,1:2);在CPU中收集地面真相以进行后处理。YTrain = gather(extractdata(YTrain));从检测器中提取预测。。[gatheredprediction, YPredCell, state] = forward(检测器,XTrain);从地面真实数据生成预测目标。[boxTarget, objectnessTarget, classTarget, objectMaskTarget, boxErrorScale] = generateTargets(gatheredprediction,...YTrain, inputImageSize,检测器。AnchorBoxes penaltyThreshold);%计算损失。boxLoss = bboxOffsetLoss(YPredCell(:,[2 3 7 8]),boxTarget,objectMaskTarget,boxErrorScale);objLoss = objectnessLoss(YPredCell(:,1),objectnessTarget,objectMaskTarget);clsLoss = classConfidenceLoss(YPredCell(:,6),classTarget,objectMaskTarget);totalLoss = boxLoss + objLoss + clsLoss;信息。boxLoss = boxLoss;信息。objLoss = objLoss;信息。clsLoss = clsLoss; info.totalLoss = totalLoss;计算与损失相关的可学习变量的梯度。。gradients = dlgradient(totalLoss,检测器. learnables);结束函数boxLoss = bboxOffsetLoss(boxPredCell, boxDeltaTarget, boxMaskTarget, boxErrorScaleTarget)%包围框位置的均方误差。lossX = sum(cellfun(@(a,b,c,d) mse(a.*c.*d,b.*c.*d),boxPredCell(:,1),boxDeltaTarget(:,1),boxMaskTarget(:,1),boxErrorScaleTarget));lossY = sum(cellfun(@(a,b,c,d) mse(a.*c.*d,b.*c.*d),boxPredCell(:,2),boxDeltaTarget(:,2),boxMaskTarget(:,1),boxErrorScaleTarget));lossW = sum(cellfun(@(a,b,c,d) mse(a.*c.*d,b.*c.*d),boxPredCell(:,3),boxDeltaTarget(:,3),boxMaskTarget(:,1),boxErrorScaleTarget));lossH = sum(cellfun(@(a,b,c,d) mse(a.*c.*d,b.*c.*d),boxPredCell(:,4),boxDeltaTarget(:,4),boxMaskTarget(:,1),boxErrorScaleTarget));boxLoss = lossX+lossY+lossW+lossH;结束函数objLoss = objectnessLoss(objectnessPredCell, objectnessDeltaTarget, boxMaskTarget)目标度评分的二元交叉熵损失。objLoss = sum(cellfun(@(a,b,c) crossentropy(a.*c,b.*c,)“TargetCategories”,“独立”)、objectnessPredCell objectnessDeltaTarget boxMaskTarget (:, 2)));结束函数(classPredCell, classTarget, boxMaskTarget)%二值交叉熵损失的类信心评分。clsLoss = sum(cellfun(@(a,b,c) crossentropy(a.*c,b.*c,)“TargetCategories”,“独立”)、classPredCell classTarget boxMaskTarget (:, 3)));结束
扩充和数据处理功能
函数数据= augmentData(A)应用随机水平翻转和随机X/Y缩放。盒子如果重叠超过0.25,则裁剪边界外缩放的%。同时,抖动图像颜色。data = cell(size(A));为ii = 1: I = A{ii,1};bboxes = A{ii,2};标签= A{ii,3};sz = size(I);如果numel(sz) == 3 && sz(3) == 3 I = jitterColorHSV(I,...“对比”, 0.0,...“颜色”, 0.1,...“饱和”, 0.2,...“亮度”, 0.2);结束%随机翻转图像。tform = randomAffine2d(“XReflection”,真的,“规模”1.1 [1]);路由= affineOutputView(sz,tform,“BoundsStyle”,“centerOutput”);I = imwarp(I,tform,“OutputView”,溃败);对盒子应用相同的变换。[bboxes,indices] = bboxwarp(bboxes,tform,rout,“OverlapThreshold”, 0.25);Bboxes =圆形(Bboxes);标签=标签(指数);只有在通过翘曲删除所有框时才返回原始数据。如果isempty(indexes) data(ii,:) = A(ii,:);其他的data(ii,:) = {I, bboxes, labels};结束结束结束函数data = preprocessData(data, targetSize)调整图像的大小,并将像素缩放到0和1之间。也可以缩放%对应的边界框。为ii = 1:size(data,1) I = data{ii,1};imgSize = size(I);将单通道输入图像转换为3通道。如果numel(imgSize) < 3 I = repmat(I,1,1,3);结束Bboxes = data{ii,2};I = im2single(imresize(I,targetSize(1:2)));scale = targetSize(1:2)./imgSize(1:2);Bboxes = bboxresize(Bboxes,scale);data(ii, 1:2) = {I, bboxes};结束结束函数[XTrain, YTrain] = createBatchData(data, groundTruthBoxes, groundTruthClasses, classNames)返回XTrain和中的批处理维度组合的图像%归一化边界框与YTrain中的classid连接沿批处理尺寸连接图像。。XTrain = cat(4, data{:,1});从类名中获取类id。classNames = repmat({categorical(classNames')}, size(groundTruthClasses));[~, classIndices] = cellfun(@(a,b)ismember(a,b), groundTruthClasses, classNames,“UniformOutput”、假);将标签索引和训练图像大小附加到缩放的边界框。%,并创建响应的单个单元格数组。combedresponses = cellfun(@(bbox, classid)[bbox, classid], groundTruthBoxes, classindexes,“UniformOutput”、假);len = max(cellfun(@(x)size(x,1), combinedResponses));paddedBBoxes = cellfun(@(v) padarray(v,[len-size(v,1),0],0,“职位”)、combinedResponses“UniformOutput”、假);YTrain = cat(4, paddedBBoxes{:,1});结束
学习率表命令功能
函数currentLR = piecewiseLearningRateWithWarmup(iteration, epoch, learningRate, warmupPeriod, numEpochs)%分段elearningratewithwarmup函数计算当前基于迭代次数的学习率%。持续的warmUpEpoch;如果迭代<= warmupPeriod增加热身期间迭代次数的学习率。currentLR = learningRate *((迭代/warmupPeriod)^4);warmUpEpoch =纪元;elseif迭代>= warmupPeriod && epoch < warmUpEpoch+floor(0.6*(numepoch -warmUpEpoch))热身期结束后,如果剩余课时数小于60%,则保持学习率不变。currentLR = learningRate;elseifepoch >= warmUpEpoch+floor(0.6*(numepoch -warmUpEpoch)) && epoch < warmUpEpoch+floor(0.9*(numepoch -warmUpEpoch))%如果剩余的代数大于但小于60%比90%的学生,学习速度乘以0.1。currentLR = learningRate*0.1;其他的如果剩余的课时数超过90%,则将学习内容相乘%率0.01。currentLR = learningRate*0.01;结束结束
效用函数
函数[lossPlotter, learningRatePlotter] = configureTrainingProgressPlotter(f)创建子图来显示损失和学习速率。图(f);clf次要情节(2,1,1);ylabel (学习速率的);包含(“迭代”);learningRatePlotter = animatedline;次要情节(2,1,2);ylabel (“全损”);包含(“迭代”);lossPlotter = animatedline;结束函数displayLossInfo(epoch, iteration, currentLR, lossInfo)显示每次迭代的丢失信息。disp (“时代:”+ epoch +" |迭代:"+迭代+“|学习率:”+ currentLR +..." |全损:"+ double(gather(extractdata(lossInfo.totalLoss)))) +..." |盒子丢失:"+ double(gather(extractdata(lossInfo.boxLoss)))) +..." |对象丢失:"+ double(gather(extractdata(lossInfo.objLoss))) +..." |班级损失:"+双(收集(extractdata (lossInfo.clsLoss))));结束函数updateplot (lossPlotter, learningRatePlotter,迭代,currentLR, totalLoss)更新损耗和学习率图。addpoints(lossPlotter,迭代,double(extractdata(gather(totalLoss))));添加点(learningRatePlotter,迭代,currentLR);drawnow结束函数detector = downloadPretrainedYOLOv3Detector()下载一个预先训练的yolov3检测器。。如果~ (“yolov3SqueezeNetVehicleExample_21aSPKG.mat”,“文件”)如果~ (“yolov3SqueezeNetVehicleExample_21aSPKG.zip”,“文件”) disp (“下载预先训练过的检测器……”);pretrainedURL =“https://ssd.mathworks.com/supportfiles/vision/data/yolov3SqueezeNetVehicleExample_21aSPKG.zip”;websave (“yolov3SqueezeNetVehicleExample_21aSPKG.zip”, pretrainedURL);结束解压缩(“yolov3SqueezeNetVehicleExample_21aSPKG.zip”);结束预训练=装载(“yolov3SqueezeNetVehicleExample_21aSPKG.mat”);检测器=预先训练好的检测器;结束
参考文献
雷德蒙,约瑟夫和阿里·法哈迪。“YOLOv3:增量改进。”预印本,2018年4月8日提交。https://arxiv.org/abs/1804.02767。
另请参阅
检测
|进行预处理
|向前
|yolov3ObjectDetector
|analyzeNetwork
(深度学习工具箱)|evaluateDetectionPrecision
|evaluateDetectionMissRate