Main Content

交通標識の検出と認識

この例では、深層学習を使用する交通標識の検出および認識用途の CUDA® MEX コードを生成する方法を説明します。交通標識の検出および認識は、ドライバーに道路標識についての情報を提供して支援する運転者支援システムのための重要な用途です。

この交通標識の検出および認識の例では、検出、非最大抑制 (NMS)、および認識の 3 つのステップを実行します。この例では、最初に You Only Look Once (YOLO) ネットワークのバリアントであるオブジェクト検出ネットワークを使用して、入力イメージにある交通標識を検出します。次に、NMS アルゴリズムを使用して、重なりのある検出を抑制します。最後に、認識ネットワークによって検出された交通標識を分類します。

サードパーティの必要条件

必須

この例では、CUDA MEX を生成します。以下のサードパーティ要件が適用されます。

  • CUDA® 対応 NVIDIA® GPU および互換性のあるドライバー。

オプション

スタティック ライブラリ、ダイナミック ライブラリ、または実行可能ファイルなどの MEX 以外のビルドについて、この例では以下の要件も適用されます。

GPU 環境の検証

関数coder.checkGpuInstall (GPU Coder)を使用して、この例を実行するのに必要なコンパイラおよびライブラリが正しく設定されていることを検証します。

envCfg = coder.gpuEnvConfig('host');
envCfg.DeepLibTarget = 'cudnn';
envCfg.DeepCodegen = 1;
envCfg.Quiet = 1;
coder.checkGpuInstall(envCfg);

検出ネットワークと認識ネットワーク

検出ネットワークは、Darknet フレームワークで学習が行われ、推論のために MATLAB® にインポートされています。交通標識のサイズはイメージのサイズと比べて比較的小さく、学習データ内のクラスあたりの学習サンプル数が少ないため、すべての交通標識を検出ネットワークに学習させるための単一のクラスと見なします。

検出ネットワークは、入力イメージを 7 行 7 列のグリッドに分割します。各グリッド セルは、交通標識の中心がそのグリッド セル内にある場合に交通標識を検出します。各セルは、2 つの境界ボックスとそれらの境界ボックスの信頼スコアを予測します。信頼スコアは、そのボックスがオブジェクトを含んでいるかどうかを示します。各セルは、グリッド セル内で交通標識が見つかる確率を予測します。最終的なスコアは前述のスコアの積です。この最終スコアに 0.2 のしきい値を適用して検出を選択します。

認識ネットワークは、MATLAB を使用して同じイメージについて学習済みです。

trainRecognitionnet.m 補助スクリプトは、認識ネットワークの学習について示しています。

事前学習済みの検出器と認識ネットワークの入手

この例では、yolo_tsr と、事前学習済みのネットワークを含む RecognitionNet MAT ファイルを使用します。ファイルのサイズはそれぞれ、約 6 MB と約 992 MB です。MathWorks の Web サイトからファイルをダウンロードします。

detectorNet = matlab.internal.examples.downloadSupportFile('gpucoder/cnn_models/traffic_sign_detection/v001','yolo_tsr.mat');
recognitionNet = matlab.internal.examples.downloadSupportFile('gpucoder/cnn_models/traffic_sign_detection/v001','RecognitionNet.mat');

検出ネットワークには、畳み込み層、leaky ReLU 層、全結合層など、58 個の層が含まれています。

load(detectorNet);
yolo
yolo = 
  SeriesNetwork with properties:

         Layers: [58×1 nnet.cnn.layer.Layer]
     InputNames: {'input'}
    OutputNames: {'classoutput'}

ネットワーク アーキテクチャを表示するには、関数analyzeNetworkを使用します。

analyzeNetwork(yolo)

認識ネットワークには、畳み込み層、全結合層、分類出力層など、14 個の層が含まれています。

load(recognitionNet);
convnet
convnet = 
  SeriesNetwork with properties:

         Layers: [14×1 nnet.cnn.layer.Layer]
     InputNames: {'imageinput'}
    OutputNames: {'classoutput'}

エントリポイント関数 tsdr_predict

エントリポイント関数 tsdr_predict.m は、イメージ入力を受け取り、検出ネットワークを使用してイメージ内の交通標識を検出します。この関数は、selectStrongestBbox を使用して重なりのある検出を抑制し (NMS)、認識ネットワークを使用して交通標識を認識します。この関数は、ネットワーク オブジェクトを yolo_tsr.mat から永続変数 detectionnet に、RecognitionNet.mat から永続変数 recognitionnet に読み込みます。この関数のそれ以降の呼び出しでは、この永続オブジェクトが再利用されます。

type('tsdr_predict.m')
function [selectedBbox,idx] = tsdr_predict(img,detectorMATFile,recogMATFile)
%#codegen

coder.gpu.kernelfun;

% resize the image
img_rz = imresize(img,[448,448]);

% Converting into BGR format
img_rz = img_rz(:,:,3:-1:1);
img_rz = im2single(img_rz);

%% TSD
persistent detectionnet;
if isempty(detectionnet)   
    detectionnet = coder.loadDeepLearningNetwork(detectorMATFile,'Detection');
end

predictions = detectionnet.activations(img_rz,56,'OutputAs','channels');


%% Convert predictions to bounding box attributes
classes = 1;
num = 2;
side = 7;
thresh = 0.2;
[h,w,~] = size(img);


boxes = single(zeros(0,4));    
probs = single(zeros(0,1));    
for i = 0:(side*side)-1
    for n = 0:num-1
        p_index = side*side*classes + i*num + n + 1;
        scale = predictions(p_index);       
        prob = zeros(1,classes+1);
        for j = 0:classes
            class_index = i*classes + 1;
            tempProb = scale*predictions(class_index+j);
            if tempProb > thresh
                
                row = floor(i / side);
                col = mod(i,side);
                
                box_index = side*side*(classes + num) + (i*num + n)*4 + 1;
                bxX = (predictions(box_index + 0) + col) / side;
                bxY = (predictions(box_index + 1) + row) / side;
                
                bxW = (predictions(box_index + 2)^2);
                bxH = (predictions(box_index + 3)^2);
                
                prob(j+1) = tempProb;
                probs = [probs;tempProb];
                                
                boxX = (bxX-bxW/2)*w+1;
                boxY = (bxY-bxH/2)*h+1;
                boxW = bxW*w;
                boxH = bxH*h;
                boxes = [boxes; boxX,boxY,boxW,boxH];
            end
        end
    end
end

%% Run Non-Maximal Suppression on the detected bounding boxess
coder.varsize('selectedBbox',[98, 4],[1 0]);
[selectedBbox,~] = selectStrongestBbox(round(boxes),probs);

%% Recognition

persistent recognitionnet;
if isempty(recognitionnet) 
    recognitionnet = coder.loadDeepLearningNetwork(recogMATFile,'Recognition');
end

idx = zeros(size(selectedBbox,1),1);
inpImg = coder.nullcopy(zeros(48,48,3,size(selectedBbox,1)));
for i = 1:size(selectedBbox,1)
    
    ymin = selectedBbox(i,2);
    ymax = ymin+selectedBbox(i,4);
    xmin = selectedBbox(i,1);
    xmax = xmin+selectedBbox(i,3);

    
    % Resize Image
    inpImg(:,:,:,i) = imresize(img(ymin:ymax,xmin:xmax,:),[48,48]);
end

for i = 1:size(selectedBbox,1)
    output = recognitionnet.predict(inpImg(:,:,:,i));
    [~,idx(i)]=max(output);
end

% Copyright 2017-2022 The MathWorks, Inc.

関数 tsdr_predict の CUDA MEX の生成

MEX ターゲットの GPU 構成オブジェクトを作成し、ターゲット言語を C++ に設定します。関数 coder.DeepLearningConfig (GPU Coder) を使用して CuDNN 深層学習構成オブジェクトを作成し、それを GPU コード構成オブジェクトの DeepLearningConfig プロパティに割り当てます。CUDA MEX を生成するには、codegen コマンドを使用し、入力のサイズを [480,704,3] に指定します。この値は関数 tsdr_predict の入力イメージ サイズに対応します。

cfg = coder.gpuConfig('mex');
cfg.TargetLang = 'C++';
cfg.DeepLearningConfig = coder.DeepLearningConfig('cudnn');
inputArgs = {ones(480,704,3,'uint8'),coder.Constant(detectorNet),...
    coder.Constant(recognitionNet)};
codegen -config cfg tsdr_predict -args inputArgs -report
Code generation successful: View report

TensorRT を使用してコードを生成するには、'cudnn' の代わりに、coder.DeepLearningConfig('tensorrt') をオプションとしてコーダー構成オブジェクトに渡します。

生成された MEX の実行

入力イメージを読み込みます。

im = imread('stop.jpg');
imshow(im);

入力イメージに対して tsdr_predict_mex を呼び出します。

im = imresize(im, [480,704]);
[bboxes,classes] = tsdr_predict_mex(im,detectorNet,recognitionNet);

クラス番号をクラス ディクショナリ内の交通標識名にマッピングします。

classNames = {...
    'addedLane','slow','dip','speedLimit25','speedLimit35','speedLimit40',...
    'speedLimit45','speedLimit50','speedLimit55','speedLimit65',...
    'speedLimitUrdbl','doNotPass','intersection','keepRight','laneEnds',...
    'merge','noLeftTurn','noRightTurn','stop','pedestrianCrossing',...
    'stopAhead','rampSpeedAdvisory20','rampSpeedAdvisory45',...
    'truckSpeedLimit55','rampSpeedAdvisory50','turnLeft',...
    'rampSpeedAdvisoryUrdbl','turnRight','rightLaneMustTurn','yield',...
    'yieldAhead','school','schoolSpeedLimit25','zoneAhead45','signalAhead'};

classRec = classNames(classes);

検出した交通標識を表示します。

outputImage = insertShape(im,'Rectangle',bboxes,'LineWidth',3);

for i = 1:size(bboxes,1)
    outputImage = insertText(outputImage,[bboxes(i,1)+ ...
        bboxes(i,3) bboxes(i,2)-20],classRec{i},'FontSize',20,...
        'TextColor','red');
end

imshow(outputImage);

ビデオにある交通標識の検出と認識

用意されている補助ファイル tsdr_testVideo.m は、テスト ビデオからフレームを取得して、交通標識の検出および認識を実行し、テスト ビデオの各フレームについて結果をプロットします。

type tsdr_testVideo
function tsdr_testVideo

% Copyright 2017-2022 The MathWorks, Inc.

% Input video
v = VideoReader('stop.avi');


%% Generate Code for Traffic Sign Detection and Recognition
% Create a GPU Configuration object for MEX target setting target language
% to C++. Run the |codegen| command specifying an input of input video
% frame size. This corresponds to the input image size of tsdr_predict
% function.
cfg = coder.gpuConfig('mex');
cfg.TargetLang = 'C++';
cfg.DeepLearningConfig = coder.DeepLearningConfig('cudnn');
inputArgs = {ones(480,704,3,'uint8'),coder.constant(detectorNet),...
    coder.Constant(recognitionNet)};
codegen -config cfg tsdr_predict -args inputArgs -report

fps = 0;

while hasFrame(v)
    % Take a frame
    picture = readFrame(v);
    picture = imresize(picture,[480,704]);
    % Call MEX function for Traffic Sign Detection and Recognition
    tic;
    [bboxes,clases] = tsdr_predict_mex(picture,detectorNet,recognitionNet);
    newt = toc;
    
    % fps
    fps = .9*fps + .1*(1/newt);
    
    % display
   
        diplayDetections(picture,bboxes,clases,fps);
end


end

function diplayDetections(im,boundingBoxes,classIndices,fps)
% Function for inserting the detected bounding boxes and recognized classes
% and displaying the result
%
% Inputs :
%
% im            : Input test image
% boundingBoxes : Detected bounding boxes
% classIndices  : Corresponding classes
%

% Traffic Signs (35)
classNames = {'addedLane','slow','dip','speedLimit25','speedLimit35',...
    'speedLimit40','speedLimit45','speedLimit50','speedLimit55',...
    'speedLimit65','speedLimitUrdbl','doNotPass','intersection',...
    'keepRight','laneEnds','merge','noLeftTurn','noRightTurn','stop',...
    'pedestrianCrossing','stopAhead','rampSpeedAdvisory20',...
    'rampSpeedAdvisory45','truckSpeedLimit55','rampSpeedAdvisory50',...
    'turnLeft','rampSpeedAdvisoryUrdbl','turnRight','rightLaneMustTurn',...
    'yield','yieldAhead','school','schoolSpeedLimit25','zoneAhead45',...
    'signalAhead'};

outputImage = insertShape(im,'Rectangle',boundingBoxes,'LineWidth',3);

for i = 1:size(boundingBoxes,1)
    
     ymin = boundingBoxes(i,2);
     xmin = boundingBoxes(i,1);
     xmax = xmin+boundingBoxes(i,3);
    
    % inserting class as text at YOLO detection
    classRec = classNames{classIndices(i)};
    outputImage = insertText(outputImage,[xmax ymin-20],classRec,...
        'FontSize',20,'TextColor','red');
    
end
outputImage = insertText(outputImage,...
    round(([size(outputImage,1) 40]/2)-20),...
    ['Frame Rate: ',num2str(fps)],'FontSize',20,'TextColor','red');
imshow(outputImage);
end

関連するトピック