Main Content

YOLO v4 딥러닝을 사용한 객체 검출

이 예제에서는 YOLO v4(You Only Look Once Version 4) 딥러닝 신경망을 사용하여 영상에서 객체를 검출하는 방법을 보여줍니다. 이 예제에서는 다음을 수행합니다

  • YOLO v4 객체 검출 신경망의 훈련, 검증, 테스트를 위한 데이터셋을 구성합니다. 또한 신경망 효율성을 향상하기 위해 훈련 데이터셋에 대한 데이터 증대를 수행합니다.

  • YOLO v4 객체 검출 신경망을 훈련시키는 데 사용할 앵커 상자를 훈련 데이터로부터 계산합니다.

  • yolov4ObjectDetector 함수를 사용하여 YOLO v4 객체 검출기를 만들고 trainYOLOv4ObjectDetector 함수를 사용하여 검출기를 훈련시킵니다.

이 예제에서는 또한 영상에서 차량을 검출하는 데 사용할 사전 훈련된 YOLO v4 객체 검출기를 제공합니다. 사전 훈련된 신경망은 백본 신경망으로 CSPDarkNet-53을 사용하고 차량 데이터셋에 대해 훈련됩니다. YOLO v4 객체 검출 신경망에 대한 자세한 내용은 Getting Started with YOLO v4 항목을 참조하십시오.

데이터셋 불러오기

이 예제에서는 295개의 영상을 포함하는 소규모의 차량 데이터셋을 사용합니다. 이러한 영상의 많은 부분은 Caltech Computational Vision 웹사이트에서 사용 가능한 Caltech Cars 1999 및 2001 데이터셋에서 가져온 것으로, Pietro Perona가 만들었으며 허락 하에 사용되었습니다. 각 영상에는 차량에 대해 레이블 지정된 건수가 한 건 또는 두 건 있습니다. 작은 데이터셋은 YOLO v4 훈련 절차를 살펴보기에 유용하지만, 실전에서 강건한 검출기를 훈련시키려면 레이블이 지정된 영상이 더 많이 필요합니다.

차량 영상의 압축을 풀고 차량 실측 데이터를 불러옵니다.

unzip vehicleDatasetImages.zip
data = load("vehicleDatasetGroundTruth.mat");
vehicleDataset = data.vehicleDataset;

차량 데이터는 2열 테이블에 저장되어 있습니다. 첫 번째 열은 영상 파일 경로를 포함하고, 두 번째 열은 경계 상자를 포함합니다.

% Display first few rows of the data set.
vehicleDataset(1:4,:)
ans=4×2 table
              imageFilename                   vehicle     
    _________________________________    _________________

    {'vehicleImages/image_00001.jpg'}    {[220 136 35 28]}
    {'vehicleImages/image_00002.jpg'}    {[175 126 61 45]}
    {'vehicleImages/image_00003.jpg'}    {[108 120 45 33]}
    {'vehicleImages/image_00004.jpg'}    {[124 112 38 36]}

% Add the fullpath to the local vehicle data folder.
vehicleDataset.imageFilename = fullfile(pwd,vehicleDataset.imageFilename);

데이터셋을 훈련 세트, 검증 세트, 테스트 세트로 분할합니다. 데이터의 60%를 훈련용으로, 데이터의 10%를 검증용으로, 나머지를 훈련된 검출기의 테스트용으로 선택합니다.

rng("default");
shuffledIndices = randperm(height(vehicleDataset));
idx = floor(0.6 * length(shuffledIndices) );

trainingIdx = 1:idx;
trainingDataTbl = vehicleDataset(shuffledIndices(trainingIdx),:);

validationIdx = idx+1 : idx + 1 + floor(0.1 * length(shuffledIndices) );
validationDataTbl = vehicleDataset(shuffledIndices(validationIdx),:);

testIdx = validationIdx(end)+1 : length(shuffledIndices);
testDataTbl = vehicleDataset(shuffledIndices(testIdx),:);

imageDatastoreboxLabelDatastore를 사용하여 훈련과 평가 과정에서 영상 및 레이블 데이터를 불러오기 위한 데이터저장소를 만듭니다.

imdsTrain = imageDatastore(trainingDataTbl{:,"imageFilename"});
bldsTrain = boxLabelDatastore(trainingDataTbl(:,"vehicle"));

imdsValidation = imageDatastore(validationDataTbl{:,"imageFilename"});
bldsValidation = boxLabelDatastore(validationDataTbl(:,"vehicle"));

imdsTest = imageDatastore(testDataTbl{:,"imageFilename"});
bldsTest = boxLabelDatastore(testDataTbl(:,"vehicle"));

영상 데이터저장소와 상자 레이블 데이터저장소를 결합합니다.

trainingData = combine(imdsTrain,bldsTrain);
validationData = combine(imdsValidation,bldsValidation);
testData = combine(imdsTest,bldsTest);

validateInputData를 사용하여 다음과 같이 유효하지 않은 영상, 경계 상자 또는 레이블을 검출합니다.

  • 샘플이 유효하지 않은 영상 형식이거나 NaN을 포함하고 있음

  • 경계 상자가 0, NaN, Inf를 포함하고 있거나 비어 있음

  • 레이블이 누락되었거나 범주형이 아님

경계 상자의 값은 유한한 양의 정수여야 하며 NaN이 아니어야 합니다. 경계 상자 값의 높이와 너비는 양수여야 하며 영상 경계 내에 있어야 합니다.

validateInputData(trainingData);
validateInputData(validationData);
validateInputData(testData);

상자 레이블과 함께 훈련 영상 중 하나를 표시합니다.

data = read(trainingData);
I = data{1};
bbox = data{2};
annotatedImage = insertShape(I,"Rectangle",bbox);
annotatedImage = imresize(annotatedImage,2);
figure
imshow(annotatedImage)

reset(trainingData);

YOLO v4 객체 검출기 신경망 만들기

훈련에 사용할 신경망 입력 크기를 지정합니다.

inputSize = [608 608 3];

검출할 객체 클래스의 이름을 지정합니다.

className = "vehicle";

estimateAnchorBoxes 함수를 사용하여 훈련 데이터의 사물 크기를 기반으로 앵커 상자를 추정합니다. 훈련 전 이루어지는 영상 크기 조정을 고려하기 위해 앵커 상자 추정에 사용하는 훈련 데이터를 크기 조정하십시오. transform을 사용하여 훈련 데이터를 전처리한 후에 앵커 상자의 개수를 정의하고 앵커 상자를 추정합니다. preprocessData 헬퍼 함수를 사용하여 훈련 데이터의 크기를 신경망의 입력 크기로 조정합니다.

rng("default")
trainingDataForEstimation = transform(trainingData,@(data)preprocessData(data,inputSize));
numAnchors = 9;
[anchors,meanIoU] = estimateAnchorBoxes(trainingDataForEstimation,numAnchors);

모든 검출 헤드에 사용할 anchorBoxes를 지정합니다. anchorBoxes는 [Mx1] 크기의 셀형 배열입니다. 여기서 M은 검출 헤드의 개수를 나타냅니다. 각 검출 헤드는 anchors로 구성된 [Nx2] 행렬로 구성됩니다. 여기서 N은 사용할 앵커 개수입니다. 특징 맵 크기를 기준으로 각 검출 헤드에 대해 anchorBoxes를 선택합니다. 낮은 스케일에서는 더 큰 anchors를 사용하고 높은 스케일에서는 더 작은 anchors를 사용합니다. 그렇게 하려면 먼저 더 큰 앵커 상자를 사용하여 anchors를 정렬하고 처음 3개 항목을 첫 번째 검출 헤드에 할당하고 다음 3개 항목을 두 번째 검출 헤드에 할당하고 마지막 3개 항목을 세 번째 검출 헤드에 할당합니다.

area = anchors(:, 1).*anchors(:,2);
[~,idx] = sort(area,"descend");

anchors = anchors(idx,:);
anchorBoxes = {anchors(1:3,:)
    anchors(4:6,:)
    anchors(7:9,:)
    };

앵커 상자 선택에 관한 자세한 내용은 Estimate Anchor Boxes From Training Data(Computer Vision Toolbox™) 및 객체 검출용 앵커 상자 항목을 참조하십시오.

yolov4ObjectDetector 함수를 사용하여 YOLO v4 객체 검출기를 만듭니다. COCO 데이터셋에서 훈련된 사전 훈련된 YOLO v4 검출 신경망의 이름을 지정합니다. 클래스 이름과 추정된 앵커 상자를 지정합니다.

detector = yolov4ObjectDetector("csp-darknet53-coco",className,anchorBoxes,InputSize=inputSize);

데이터 증대 수행하기

훈련 정확도를 향상하기 위해 데이터 증대를 수행합니다. transform 함수를 사용하여 훈련 데이터에 사용자 지정 데이터 증대를 적용합니다. augmentData 헬퍼 함수는 입력 데이터에 다음과 같은 증대를 적용합니다.

  • HSV 공간에서 색 지터 증대

  • 무작위 좌우 반전

  • 10% 무작위 스케일링

테스트 데이터와 검증 데이터에는 데이터 증대가 적용되지 않는다는 것에 유의하십시오. 이상적인 경우라면 테스트 데이터와 검증 데이터가 원본 데이터를 대표해야 하므로 편향되지 않은 평가를 위해 수정되지 않은 상태로 남겨 두는 것이 좋습니다.

augmentedTrainingData = transform(trainingData,@augmentData);

증대한 훈련 데이터의 샘플을 읽어 들이고 표시합니다.

augmentedData = cell(4,1);
for k = 1:4
    data = read(augmentedTrainingData);
    augmentedData{k} = insertShape(data{1},"rectangle",data{2});
    reset(augmentedTrainingData);
end
figure
montage(augmentedData,BorderSize=10)

훈련 옵션 지정하기

trainingOptions를 사용하여 신경망 훈련 옵션을 지정합니다. Adam 솔버를 사용하여 상수 학습률 0.001로 Epoch 70회만큼 객체 검출기를 훈련시킵니다. "ResetInputNormalization"은 false로 설정하고 "BatchNormalizationStatistics"는 "moving"으로 설정해야 합니다. "ValidationData"를 검증 데이터로 설정하고 "ValidationFrequency"를 1000으로 설정합니다. 데이터를 더 자주 검증하기 위해 “ValidationFrequency”를 줄일 수 있으며, 그러면 훈련 시간이 늘어납니다. "ExecutionEnvironment"를 사용하여 신경망 훈련에 사용할 하드웨어 리소스를 결정합니다. 이에 대한 디폴트 값은 "auto"입니다. 이 경우, GPU를 사용할 수 있으면 GPU가 선택되고 그렇지 않으면 CPU가 선택됩니다. "CheckpointPath"를 임시 위치로 설정합니다. 이렇게 하면 훈련 과정 도중에 부분적으로 훈련된 검출기를 저장할 수 있습니다. 정전이나 시스템 장애 등으로 인해 훈련이 중단될 경우, 저장된 검사 지점에서 훈련을 재개할 수 있습니다.

options = trainingOptions("adam",...
    GradientDecayFactor=0.9,...
    SquaredGradientDecayFactor=0.999,...
    InitialLearnRate=0.001,...
    LearnRateSchedule="none",...
    MiniBatchSize=4,...
    L2Regularization=0.0005,...
    MaxEpochs=70,...
    BatchNormalizationStatistics="moving",...
    DispatchInBackground=true,...
    ResetInputNormalization=false,...
    Shuffle="every-epoch",...
    VerboseFrequency=20,...
    ValidationFrequency=1000,...
    CheckpointPath=tempdir,...
    ValidationData=validationData);

YOLO v4 객체 검출기 훈련하기

trainYOLOv4ObjectDetector 함수를 사용하여 YOLO v4 객체 검출기를 훈련시킵니다. 이 예제는 24GB의 메모리가 탑재된 NVIDIA™ Titan RTX GPU에서 실행되었습니다. 이 설정을 사용하여 이 신경망을 훈련시키는 데 약 6시간이 걸렸습니다. 훈련 시간은 사용하는 하드웨어에 따라 달라집니다. 신경망을 훈련시키는 대신 Computer Vision Toolbox ™에서 사전 훈련된 YOLO v4 객체 검출기를 사용해도 됩니다.

downloadPretrainedYOLOv4Detector 헬퍼 함수를 사용하여 사전 훈련된 검출기를 다운로드합니다. doTraining 값을 false로 설정합니다. 증대된 훈련 데이터에 대해 검출기를 훈련시키려면 doTraining 값을 true로 설정합니다.

doTraining = false;
if doTraining       
    % Train the YOLO v4 detector.
    [detector,info] = trainYOLOv4ObjectDetector(augmentedTrainingData,detector,options);
else
    % Load pretrained detector for the example.
    detector = downloadPretrainedYOLOv4Detector();
end
Downloading pretrained detector...

테스트 영상에 대해 검출기를 실행합니다.

I = imread("highway.png");
[bboxes,scores,labels] = detect(detector,I);

결과를 표시합니다.

I = insertObjectAnnotation(I,"rectangle",bboxes,scores);
figure
imshow(I)

테스트 세트를 사용하여 검출기 평가하기

훈련된 객체 검출기를 대규모 영상 세트를 대상으로 평가하여 성능을 측정합니다. Computer Vision Toolbox™에는 평균 정밀도, 로그-평균 미검출율과 같은 통상적인 메트릭을 측정해 주는 객체 검출기 평가 함수(evaluateObjectDetection)가 있습니다. 이 예제에서는 평균 정밀도를 메트릭으로 사용하여 성능을 평가합니다. 평균 정밀도는 검출기가 올바른 분류를 수행하는 능력(정밀도)과 모든 관련 사물을 찾는 능력(재현율)을 통합하여 하나의 수치로 나타냅니다.

모든 테스트 영상에 대해 검출기를 실행합니다.

detectionResults = detect(detector,testData);

평균 정밀도를 메트릭으로 사용하여 객체 검출기를 평가합니다.

metrics = evaluateObjectDetection(detectionResults,testData);
classID = 1;
precision = metrics.ClassMetrics.Precision{classID};
recall = metrics.ClassMetrics.Recall{classID};

정밀도-재현율(PR) 곡선은 각기 다른 재현율 수준에서의 검출기의 정밀도를 나타냅니다. 이상적인 정밀도는 모든 재현율 수준에서 1입니다. 더 많은 데이터를 사용하면 평균 정밀도를 개선하는 데 도움이 될 수 있으나 더 많은 훈련 시간이 필요할 수 있습니다. PR 곡선을 플로팅합니다.

figure
plot(recall,precision)
xlabel("Recall")
ylabel("Precision")
grid on
title(sprintf("Average Precision = %.2f",metrics.ClassMetrics.mAP(classID)))

지원 함수

데이터 증대를 수행하기 위한 헬퍼 함수.

function data = augmentData(A)
% Apply random horizontal flipping, and random X/Y scaling. Boxes that get
% scaled outside the bounds are clipped if the overlap is above 0.25. Also,
% jitter image color.

data = cell(size(A));
for ii = 1:size(A,1)
    I = A{ii,1};
    bboxes = A{ii,2};
    labels = A{ii,3};
    sz = size(I);

    if numel(sz) == 3 && sz(3) == 3
        I = jitterColorHSV(I,...
            contrast=0.0,...
            Hue=0.1,...
            Saturation=0.2,...
            Brightness=0.2);
    end
    
    % Randomly flip image.
    tform = randomAffine2d(XReflection=true,Scale=[1 1.1]);
    rout = affineOutputView(sz,tform,BoundsStyle="centerOutput");
    I = imwarp(I,tform,OutputView=rout);
    
    % Apply same transform to boxes.
    [bboxes,indices] = bboxwarp(bboxes,tform,rout,OverlapThreshold=0.25);
    labels = labels(indices);
    
    % Return original data only when all boxes are removed by warping.
    if isempty(indices)
        data(ii,:) = A(ii,:);
    else
        data(ii,:) = {I,bboxes,labels};
    end
end
end

function data = preprocessData(data,targetSize)
% Resize the images and scale the pixels to between 0 and 1. Also scale the
% corresponding bounding boxes.

for ii = 1:size(data,1)
    I = data{ii,1};
    imgSize = size(I);
    
    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};
end
end

사전 훈련된 YOLO v4 객체 검출기를 다운로드하기 위한 헬퍼 함수.

function detector = downloadPretrainedYOLOv4Detector()
% Download a pretrained yolov4 detector.
if ~exist("yolov4CSPDarknet53VehicleExample_22a.mat", "file")
    if ~exist("yolov4CSPDarknet53VehicleExample_22a.zip", "file")
        disp("Downloading pretrained detector...");
        pretrainedURL = "https://ssd.mathworks.com/supportfiles/vision/data/yolov4CSPDarknet53VehicleExample_22a.zip";
        websave("yolov4CSPDarknet53VehicleExample_22a.zip", pretrainedURL);
    end
    unzip("yolov4CSPDarknet53VehicleExample_22a.zip");
end
pretrained = load("yolov4CSPDarknet53VehicleExample_22a.mat");
detector = pretrained.detector;
end

참고 문헌

[1] Alexey Bochkovskiy, Chien-Yao Wang, and Hong-Yuan Mark Liao. “YOLOv4: Optimal Speed and Accuracy of Object Detection.” 2020, arXiv:2004.10934. https://arxiv.org/abs/2004.10934.

참고 항목

| | | | (Deep Learning Toolbox) |

관련 예제

세부 정보