Main Content

使用 R-CNN 深度学习训练目标检测器

此示例说明如何使用深度学习和 R-CNN(区域卷积神经网络)训练目标检测器。

概述

此示例说明如何训练用于检测停车标志的 R-CNN 目标检测器。R-CNN 是一个目标检测框架,它使用卷积神经网络 (CNN) 对图像中的图像区域进行分类 [1]。R-CNN 检测器不使用滑动窗对每个区域进行分类,而是只处理那些可能包含对象的区域。这大幅降低了运行 CNN 时的计算成本。

为了说明如何训练 R-CNN 停车标志检测器,本示例遵循深度学习应用中常用的迁移学习工作流。在迁移学习中,您可以从基于大型图像集合(如 ImageNet [2])训练的网络开始,处理新的分类或检测任务。使用这种方法的优点是,预训练的网络已学习一系列适用于各种图像的丰富图像特征。这种学习可以通过微调网络迁移到新任务中。通过对权重进行细微调整来微调网络,使得针对原始任务学习的特征表示发生微调以支持新任务。

迁移学习的优点是可以减少训练所需的图像数量和训练时间。为了说明这些优点,此示例使用迁移学习工作流来训练停车标志检测器。首先,使用 CIFAR-10 数据集对一个 CNN 进行预训练,该数据集有 50,000 个训练图像。然后,只使用 41 个训练图像针对停车标志检测对这个预训练的 CNN 进行微调。如果没有预训练 CNN,训练停车标志检测器会需要更多图像。

注意:此示例需要 Computer Vision Toolbox™、Image Processing Toolbox™、Deep Learning Toolbox™ 和 Statistics and Machine Learning Toolbox™。

强烈推荐使用支持 CUDA 的 NVIDIA™ GPU 来运行此示例。使用 GPU 需要 Parallel Computing Toolbox™。有关支持的计算功能的信息,请参阅GPU Computing Requirements (Parallel Computing Toolbox)

下载 CIFAR-10 图像数据

下载 CIFAR-10 数据集 [1]。此数据集包含 50,000 个训练图像,将用于训练 CNN。

将 CIFAR-10 数据下载到一个临时目录

cifar10Data = tempdir;

url = 'https://www.cs.toronto.edu/~kriz/cifar-10-matlab.tar.gz';

helperCIFAR10Data.download(url,cifar10Data);

加载 CIFAR-10 训练和测试数据。

[trainingImages,trainingLabels,testImages,testLabels] = helperCIFAR10Data.load(cifar10Data);

每个图像是一个 32×32 RGB 图像,共有 50,000 个训练样本。

size(trainingImages)
ans = 1×4

          32          32           3       50000

CIFAR-10 有 10 个图像类别。列出图像类别:

numImageCategories = 10;
categories(trainingLabels)
ans = 10×1 cell
    {'airplane'  }
    {'automobile'}
    {'bird'      }
    {'cat'       }
    {'deer'      }
    {'dog'       }
    {'frog'      }
    {'horse'     }
    {'ship'      }
    {'truck'     }

您可以使用以下代码显示一些训练图像。

figure
thumbnails = trainingImages(:,:,:,1:100);
montage(thumbnails)

创建卷积神经网络 (CNN)

CNN 由一系列层组成,每层定义一项特定计算。Deep Learning Toolbox™ 的一些功能可以帮助您轻松逐层设计 CNN。本示例使用以下层创建 CNN:

此处定义的网络类似于 [4] 中所述的网络,以 imageInputLayer 开头。输入层定义 CNN 可以处理的数据的类型和大小。在本示例中,CNN 用于处理 CIFAR-10 图像,它们是 32×32 RGB 图像:

% Create the image input layer for 32x32x3 CIFAR-10 images.
[height,width,numChannels, ~] = size(trainingImages);

imageSize = [height width numChannels];
inputLayer = imageInputLayer(imageSize)
inputLayer = 
  ImageInputLayer with properties:

                      Name: ''
                 InputSize: [32 32 3]
   Hyperparameters
          DataAugmentation: 'none'
             Normalization: 'zerocenter'
    NormalizationDimension: 'auto'
                      Mean: []

接下来,定义网络的中间层。中间层包含多个由卷积层、ReLU(修正线性单元)层和池化层组成的重复模块。这三个层构成卷积神经网络的核心构建模块。卷积层定义滤波器权重集,这些权重集在网络训练期间会更新。ReLU 层在网络中引入非线性,让网络能够逼近非线性函数,这些函数将图像像素映射到图像语义内容。池化层在数据流经网络时对其进行下采样。在具有许多层的网络中,应谨慎使用池化层,以避免过早对网络中的数据进行下采样。

% Convolutional layer parameters
filterSize = [5 5];
numFilters = 32;

middleLayers = [
    
% The first convolutional layer has a bank of 32 5x5x3 filters. A
% symmetric padding of 2 pixels is added to ensure that image borders
% are included in the processing. This is important to avoid
% information at the borders being washed away too early in the
% network.
convolution2dLayer(filterSize,numFilters,'Padding',2)

% Note that the third dimension of the filter can be omitted because it
% is automatically deduced based on the connectivity of the network. In
% this case because this layer follows the image layer, the third
% dimension must be 3 to match the number of channels in the input
% image.

% Next add the ReLU layer:
reluLayer()

% Follow it with a max pooling layer that has a 3x3 spatial pooling area
% and a stride of 2 pixels. This down-samples the data dimensions from
% 32x32 to 15x15.
maxPooling2dLayer(3,'Stride',2)

% Repeat the 3 core layers to complete the middle of the network.
convolution2dLayer(filterSize,numFilters,'Padding',2)
reluLayer()
maxPooling2dLayer(3, 'Stride',2)

convolution2dLayer(filterSize,2 * numFilters,'Padding',2)
reluLayer()
maxPooling2dLayer(3,'Stride',2)

]
middleLayers = 
  9x1 Layer array with layers:

     1   ''   Convolution   32 5x5 convolutions with stride [1  1] and padding [2  2  2  2]
     2   ''   ReLU          ReLU
     3   ''   Max Pooling   3x3 max pooling with stride [2  2] and padding [0  0  0  0]
     4   ''   Convolution   32 5x5 convolutions with stride [1  1] and padding [2  2  2  2]
     5   ''   ReLU          ReLU
     6   ''   Max Pooling   3x3 max pooling with stride [2  2] and padding [0  0  0  0]
     7   ''   Convolution   64 5x5 convolutions with stride [1  1] and padding [2  2  2  2]
     8   ''   ReLU          ReLU
     9   ''   Max Pooling   3x3 max pooling with stride [2  2] and padding [0  0  0  0]

通过重复这三个基本层,可以创建更深的网络。但是,应该减少池化层的数量,以避免过早地对数据进行下采样。在网络中过早进行下采样会丢弃对学习有用的图像信息。

CNN 的最终层通常包括全连接层和 softmax 损失层。

finalLayers = [
    
% Add a fully connected layer with 64 output neurons. The output size of
% this layer will be an array with a length of 64.
fullyConnectedLayer(64)

% Add an ReLU non-linearity.
reluLayer

% Add the last fully connected layer. At this point, the network must
% produce 10 signals that can be used to measure whether the input image
% belongs to one category or another. This measurement is made using the
% subsequent loss layers.
fullyConnectedLayer(numImageCategories)

% Add the softmax loss layer and classification layer. The final layers use
% the output of the fully connected layer to compute the categorical
% probability distribution over the image classes. During the training
% process, all the network weights are tuned to minimize the loss over this
% categorical distribution.
softmaxLayer
classificationLayer
]
finalLayers = 
  5x1 Layer array with layers:

     1   ''   Fully Connected         64 fully connected layer
     2   ''   ReLU                    ReLU
     3   ''   Fully Connected         10 fully connected layer
     4   ''   Softmax                 softmax
     5   ''   Classification Output   crossentropyex

对输入层、中间层和最终层进行合并。

layers = [
    inputLayer
    middleLayers
    finalLayers
    ]
layers = 
  15x1 Layer array with layers:

     1   ''   Image Input             32x32x3 images with 'zerocenter' normalization
     2   ''   Convolution             32 5x5 convolutions with stride [1  1] and padding [2  2  2  2]
     3   ''   ReLU                    ReLU
     4   ''   Max Pooling             3x3 max pooling with stride [2  2] and padding [0  0  0  0]
     5   ''   Convolution             32 5x5 convolutions with stride [1  1] and padding [2  2  2  2]
     6   ''   ReLU                    ReLU
     7   ''   Max Pooling             3x3 max pooling with stride [2  2] and padding [0  0  0  0]
     8   ''   Convolution             64 5x5 convolutions with stride [1  1] and padding [2  2  2  2]
     9   ''   ReLU                    ReLU
    10   ''   Max Pooling             3x3 max pooling with stride [2  2] and padding [0  0  0  0]
    11   ''   Fully Connected         64 fully connected layer
    12   ''   ReLU                    ReLU
    13   ''   Fully Connected         10 fully connected layer
    14   ''   Softmax                 softmax
    15   ''   Classification Output   crossentropyex

使用标准差为 0.0001 的正态分布随机数初始化第一个卷积层的权重。这有助于改善训练的收敛性。

layers(2).Weights = 0.0001 * randn([filterSize numChannels numFilters]);

使用 CIFAR-10 数据训练 CNN

完成网络架构定义后,就可以使用 CIFAR-10 训练数据对其进行训练。首先,使用 trainingOptions 函数设置网络训练算法。网络训练算法使用具有动量的随机梯度下降 (SGDM),初始学习率为 0.001。在训练期间,初始学习率每 8 轮降低一次(1 轮定义为对整个训练数据集进行一次完整遍历)。训练算法运行 40 轮。

请注意,训练算法使用包含 128 个图像的小批量。如果使用 GPU 进行训练,则由于 GPU 上的内存约束,可能需要减少一些图像。

% Set the network training options
opts = trainingOptions('sgdm', ...
    'Momentum', 0.9, ...
    'InitialLearnRate', 0.001, ...
    'LearnRateSchedule', 'piecewise', ...
    'LearnRateDropFactor', 0.1, ...
    'LearnRateDropPeriod', 8, ...
    'L2Regularization', 0.004, ...
    'MaxEpochs', 40, ...
    'MiniBatchSize', 128, ...
    'Verbose', true);

使用 trainNetwork 函数训练网络。这是一个计算密集型过程,需要 20-30 分钟才能完成。为了在运行此示例时节省时间,此处磁盘加载预训练网络。如果您要自己训练网络,请将下列代码中的 doTraining 变量设置为 true。

请注意,强烈推荐使用支持 CUDA 的 NVIDIA™ GPU 进行训练。

% A trained network is loaded from disk to save time when running the
% example. Set this flag to true to train the network.
doTraining = false;

if doTraining    
    % Train a network.
    cifar10Net = trainNetwork(trainingImages, trainingLabels, layers, opts);
else
    % Load pre-trained detector for the example.
    load('rcnnStopSigns.mat','cifar10Net')       
end

验证 CIFAR-10 网络训练

在网络经过训练后,应对其进行验证,以确保训练是成功的。首先,快速可视化第一个卷积层的滤波器权重有助于识别训练中的任何直接问题。

% Extract the first convolutional layer weights
w = cifar10Net.Layers(2).Weights;

% rescale the weights to the range [0, 1] for better visualization
w = rescale(w);

figure
montage(w)

第一层权重应该有一些明确定义的结构。如果权重看起来仍然是随机的,则表明网络可能需要额外的训练。在本例中,如上所示,第一层滤波器已从 CIFAR-10 训练数据中学习到类似边缘的特征。

要完全验证训练结果,请使用 CIFAR-10 测试数据来测量网络的分类准确度。低准确度分数表示需要更多训练或训练数据。此示例的目标并不是在测试集上实现 100% 的准确度,而是充分训练网络以用于训练目标检测器。

% Run the network on the test set.
YTest = classify(cifar10Net, testImages);

% Calculate the accuracy.
accuracy = sum(YTest == testLabels)/numel(testLabels)
accuracy = 0.7456

进一步的训练能提高准确度,但这对于训练 R-CNN 目标检测器来说不是必需的。

加载训练数据

该网络在 CIFAR-10 分类任务中表现良好,因此可以使用迁移学习方法来微调网络,以便检测停车标志。

首先加载停车标志的真实值数据。

% Load the ground truth data
data = load('stopSignsAndCars.mat', 'stopSignsAndCars');
stopSignsAndCars = data.stopSignsAndCars;

% Update the path to the image files to match the local file system
visiondata = fullfile(toolboxdir('vision'),'visiondata');
stopSignsAndCars.imageFilename = fullfile(visiondata, stopSignsAndCars.imageFilename);

% Display a summary of the ground truth data
summary(stopSignsAndCars)
Variables:
    imageFilename: 41×1 cell array of character vectors
    stopSign: 41×1 cell
    carRear: 41×1 cell
    carFront: 41×1 cell

训练数据包含在一个表中,该表包含停车标志、车头和车尾的图像文件名和关注区域标签。每个感兴趣的区域标签是图像内感兴趣的对象周围的边界框。训练停车标志检测器时,只需要停车标志的关注区域标签。您必须删除车头和车尾的感兴趣区域标签:

% Only keep the image file names and the stop sign ROI labels
stopSigns = stopSignsAndCars(:, {'imageFilename','stopSign'});

% Display one training image and the ground truth bounding boxes
I = imread(stopSigns.imageFilename{1});
I = insertObjectAnnotation(I,'Rectangle',stopSigns.stopSign{1},'stop sign','LineWidth',8);

figure
imshow(I)

请注意,此数据集中只有 41 个训练图像。仅使用 41 个图像从零开始训练一个 R-CNN 目标检测器是不切实际的,也不会得到可靠的停车标志检测器。由于此示例中停车标志检测器是通过微调已基于较大数据集(CIFAR-10 有 50000 个训练图像)预训练的网络来训练的,因此使用小得多的数据集是可行的。

训练 R-CNN 停车标志检测器

最后,使用 trainRCNNObjectDetector (Computer Vision Toolbox) 训练 R-CNN 目标检测器。该函数的输入是真实值表,其中包含标注的停车标志图像、预训练的 CIFAR-10 网络和训练选项。原始 CIFAR-10 网络将图像分为 10 个类,训练函数自动对该网络进行修改以使图像分为 2 个类:停车标志类和一般背景类。

在训练期间,使用从真实值数据中提取的图像补片来微调输入网络权重。'PositiveOverlapRange' 和 'NegativeOverlapRange' 参数控制哪些图像补片用于训练。正训练样本是与真实值框的重叠率为 0.5 到 1.0 的样本(按边界框交并比测量)。负训练样本是重叠率为 0 到 0.3 的样本。这些参数的最佳值应该通过在验证集上测试经过训练的检测器来选择。

对于 R-CNN 训练,强烈推荐使用 MATLAB 工作进程的并行池来缩短训练时间trainRCNNObjectDetector 会根据您的Computer Vision Toolbox Preferences (Computer Vision Toolbox)自动创建和使用并行池。确保在训练前启用并行池的使用。

为了在运行此示例时节省时间,此处磁盘加载预训练网络。如果您要自己训练网络,请将下列代码中的 doTraining 变量设置为 true。

请注意,强烈推荐使用支持 CUDA 的 NVIDIA™ GPU 进行训练。

% A trained detector is loaded from disk to save time when running the
% example. Set this flag to true to train the detector.
doTraining = false;

if doTraining
    
    % Set training options
    options = trainingOptions('sgdm', ...
        'MiniBatchSize', 128, ...
        'InitialLearnRate', 1e-3, ...
        'LearnRateSchedule', 'piecewise', ...
        'LearnRateDropFactor', 0.1, ...
        'LearnRateDropPeriod', 100, ...
        'MaxEpochs', 100, ...
        'Verbose', true);
    
    % Train an R-CNN object detector. This will take several minutes.    
    rcnn = trainRCNNObjectDetector(stopSigns, cifar10Net, options, ...
    'NegativeOverlapRange', [0 0.3], 'PositiveOverlapRange',[0.5 1])
else
    % Load pre-trained network for the example.
    load('rcnnStopSigns.mat','rcnn')       
end

测试 R-CNN 停车标志检测器

R-CNN 目标检测器现在可用于检测图像中的停车标志。对测试图像试用该检测器:

% Read test image
testImage = imread('stopSignTest.jpg');

% Detect stop signs
[bboxes,score,label] = detect(rcnn,testImage,'MiniBatchSize',128)
bboxes = 1×4

   419   147    31    20

score = single
    0.9955
label = categorical categorical
     stopSign 

R-CNN 对象 detect (Computer Vision Toolbox) 方法返回每个检测的对象边界框、检测分数和类标签。标签在检测多个对象时很有用,例如停车标志、让路标志或限速标志。分数(范围在 0 和 1 之间)表示对检测的信心,可以据此忽略得分较低的检测。

% Display the detection results
[score, idx] = max(score);

bbox = bboxes(idx, :);
annotation = sprintf('%s: (Confidence = %f)', label(idx), score);

outputImage = insertObjectAnnotation(testImage, 'rectangle', bbox, annotation);

figure
imshow(outputImage)

调试技巧

R-CNN 检测器中使用的网络也可用于处理整个测试图像。直接处理大于网络输入大小的整个图像时,会生成分类分数的二维热图。这是有用的调试工具,因为它有助于识别图像中让网络产生混淆的物体,还可能有助于深入了解如何改进训练。

% The trained network is stored within the R-CNN detector
rcnn.Network
ans = 
  SeriesNetwork with properties:

    Layers: [15×1 nnet.cnn.layer.Layer]

从 softmax 层(网络中的第 14 个层)提取activations。这些是网络在扫描图像时产生的分类分数。

featureMap = activations(rcnn.Network, testImage, 14);

% The softmax activations are stored in a 3-D array.
size(featureMap)
ans = 1×3

    43    78     2

featureMap 中的第三个维度对应于对象类。

rcnn.ClassNames
ans = 2×1 cell
    {'stopSign'  }
    {'Background'}

停车标志特征图存储在第一个通道中。

stopSignMap = featureMap(:, :, 1);

由于网络中的下采样操作,激活输出的大小小于输入图像。要生成更好的可视化效果,请将 stopSignMap 调整为输入图像的大小。这是非常粗略的近似,它将激活映射到图像像素,并且只能用于说明目的。

% Resize stopSignMap for visualization
[height, width, ~] = size(testImage);
stopSignMap = imresize(stopSignMap, [height, width]);

% Visualize the feature map superimposed on the test image. 
featureMapOnImage = imfuse(testImage, stopSignMap); 

figure
imshow(featureMapOnImage)

测试图像中的停车标志与网络激活值中的最大峰值准确对应。这验证了 R-CNN 检测器所用的 CNN 确实已学会识别停车标志。如果有其他峰值,则可能表明训练需要更多负数据来帮助防止误报。如果是这样的话,则您可以增大 trainingOptions 中的 'MaxEpochs' 并重新训练。

总结

此示例说明如何使用基于 CIFAR-10 数据训练的网络来训练一个 R-CNN 停车标志目标检测器。可以遵循类似的步骤来使用深度学习训练其他目标检测器。

另请参阅

(Computer Vision Toolbox) | | | | | | (Computer Vision Toolbox) | (Computer Vision Toolbox) | (Computer Vision Toolbox) | (Computer Vision Toolbox) | (Computer Vision Toolbox) | | | (Computer Vision Toolbox)

相关主题