Main Content

이 번역 페이지는 최신 내용을 담고 있지 않습니다. 최신 내용을 영문으로 보려면 여기를 클릭하십시오.

교통 표지판 검출 및 인식

이 예제에서는 딥러닝을 사용하는 교통 표지판 검출 및 인식 응용 분야를 위해 CUDA® MEX 코드를 생성하는 방법을 보여줍니다. 교통 표지판 검출 및 인식은 운전자 보조 시스템에 적용되는 주요 응용 분야로서, 운전자의 주행을 돕고 도로 표지판에 대한 정보를 제공합니다.

이 교통 표지판 검출 및 인식 예제에서는 검출, NMS(Non-Maximal Suppression, 비최댓값 억제), 인식이라는 세 단계를 수행합니다. 먼저 YOLO(You Only Look Once) 신경망의 변형인 객체 검출 신경망을 사용하여 입력 영상에서 교통 표지판을 검출합니다. 그런 다음 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 MAT 파일과 RecognitionNet MAT 파일을 사용합니다. 파일의 크기는 대략 6MB와 992MB입니다. MathWorks® 웹사이트에서 파일을 다운로드합니다.

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의 network 객체를 영속 변수 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를 생성하려면 입력 크기를 [480,704,3]으로 지정하여 codegen 명령을 실행하십시오. 이 값은 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

관련 항목