Main Content

GPU Coder를 사용한 차선 검출

이 예제에서는 NVIDIA® GPU에서 실행되는 딥러닝 차선 검출 애플리케이션을 개발하는 방법을 보여줍니다.

사전 훈련된 차선 검출 신경망은 영상에서 차선 표시줄 경계를 검출하여 출력할 수 있으며 AlexNet 신경망을 기반으로 합니다. AlexNet 신경망의 마지막 몇 개 계층이 상대적으로 크기가 작은 완전 연결 계층과 회귀 출력 계층으로 대체되었습니다. 이 예제에서는 호스트 컴퓨터의 CUDA® 지원 GPU에서 실행되는 CUDA 실행 파일을 생성합니다.

타사 선행 조건

  • CUDA 지원 NVIDIA GPU.

  • NVIDIA CUDA 툴킷 및 드라이버.

  • NVIDIA cuDNN 라이브러리.

  • 컴파일러 및 라이브러리 환경 변수. 컴파일러와 라이브러리의 지원되는 버전에 대한 자세한 내용은 Third-Party Hardware (GPU Coder) 항목을 참조하십시오. 환경 변수 설정에 대한 자세한 내용은 Setting Up the Prerequisite Products (GPU Coder) 항목을 참조하십시오.

GPU 환경 확인하기

coder.checkGpuInstall (GPU Coder) 함수를 사용하여 이 예제를 실행하는 데 필요한 컴파일러와 라이브러리가 올바르게 설치되었는지 확인합니다.

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

사전 훈련된 차선 검출 신경망 가져오기

이 예제에서는 사전 훈련된 차선 검출 신경망을 포함하는 trainedLaneNet MAT 파일을 사용합니다. 이 파일의 크기는 약 143MB입니다. MathWorks® 웹 사이트에서 파일을 다운로드합니다.

laneNetFile = matlab.internal.examples.downloadSupportFile('gpucoder/cnn_models/lane_detection', ...
    'trainedLaneNet.mat');

이 신경망은 영상을 입력값으로 받아서 자기 차량(ego vehicle)의 왼쪽, 오른쪽 차선에 대응하는 차선 경계를 출력합니다. 각 차선 경계는 포물선 방정식 y=ax2+bx+c로 표현됩니다. 여기서 y는 횡방향 오프셋이고 x는 차량으로부터의 종방향 거리입니다. 신경망은 차선당 3개의 파라미터 a, b, c를 출력합니다. 신경망 아키텍처는 마지막 몇 개의 계층이 상대적으로 크기가 작은 완전 연결 계층과 회귀 출력 계층으로 바뀐다는 점만 제외하면 AlexNet과 비슷합니다.

load(laneNetFile);
disp(laneNet)
  SeriesNetwork with properties:

         Layers: [23×1 nnet.cnn.layer.Layer]
     InputNames: {'data'}
    OutputNames: {'output'}

SeriesNetwork 객체를 dlnetwork 객체로 변환한 후 다른 MAT 파일에 저장합니다.

dlLaneNet = dag2dlnetwork(laneNet);
dlLaneNetFile = 'trainedDlLaneNet.mat';
save(dlLaneNetFile,'dlLaneNet');

테스트 비디오 다운로드하기

모델을 테스트하기 위해 이 예제에서는 Caltech의 차선 데이터셋의 비디오 파일을 사용합니다. 이 파일의 크기는 약 8MB입니다. MathWorks 웹 사이트에서 파일을 다운로드합니다.

videoFile = matlab.internal.examples.downloadSupportFile('gpucoder/media','caltech_cordova1.avi');

메인 진입점 함수

detectLanesInVideo.m 파일은 코드 생성을 위한 메인 진입점 함수입니다. detectLanesInVideo 함수는 VideoReader 객체를 사용하여 입력 비디오에서 프레임을 읽어 들이고 LaneNet network 객체의 predict 메서드를 호출한 후 입력 비디오에 검출된 차선을 그립니다. vision.DeployableVideoPlayer (Computer Vision Toolbox) System object는 차선이 검출된 비디오 출력을 표시하는 데 사용됩니다.

type detectLanesInVideo.m
function detectLanesInVideo(videoFile,net,laneCoeffMeans,laneCoeffsStds)
%   Copyright 2022-2024 The MathWorks, Inc.

%#codegen

%% Create Video Reader and Video Player Object 
videoFReader   = VideoReader(videoFile);
depVideoPlayer = vision.DeployableVideoPlayer(Name='Lane Detection on GPU');
videoHeight = videoFReader.Height;
videoWidth = videoFReader.Width;
%% Video Frame Processing Loop
while hasFrame(videoFReader)
    videoFrame = imresize(readFrame(videoFReader),[videoHeight videoWidth]);
    scaledFrame = imresize(videoFrame,[227 227]);

    [laneFound,ltPts,rtPts] = laneNetPredict(net,scaledFrame, ...
        laneCoeffMeans,laneCoeffsStds);
    lnaeFound = 1;
    if(laneFound)
        pts = [reshape(ltPts',1,[]);reshape(rtPts',1,[])];
        videoFrame = insertShape(videoFrame, 'Line', pts, 'LineWidth', 4);
    
    depVideoPlayer(videoFrame);
    end
end

LaneNet 예측 함수

laneNetPredict 함수는 단일 비디오 프레임에서 오른쪽 차선과 왼쪽 차선의 위치를 계산합니다. laneNet 신경망이 왼쪽, 오른쪽 차선 경계에 대한 포물선 방정식을 나타내는 파라미터 a, b, c를 계산합니다. 이러한 파라미터로부터 차선 위치에 대응하는 x, y좌표를 구합니다. 이들 좌표는 영상 좌표에 매핑되어야 합니다.

type laneNetPredict.m
function [laneFound,ltPts,rtPts] = laneNetPredict(net,frame,means,stds) 
%#codegen

% laneNetPredict Predict lane markers on the input image frame using the
% lane detection network

%   Copyright 2017-2024 The MathWorks, Inc.

dlFrame = dlarray(single(frame),'SSC');
persistent dllaneNet;
if isempty(dllaneNet)
    dllaneNet = coder.loadDeepLearningNetwork(net, 'dllaneNet');
end

dllanecoeffsNetworkOutput = predict(dllaneNet,dlFrame);
lanecoeffsNetworkOutput = extractdata(dllanecoeffsNetworkOutput);

% Recover original coeffs by reversing the normalization steps.
params = lanecoeffsNetworkOutput' .* stds + means;

% 'c' should be more than 0.5 for it to be a lane.
isRightLaneFound = abs(params(6)) > 0.5;
isLeftLaneFound =  abs(params(3)) > 0.5;

% From the networks output, compute left and right lane points in the image
% coordinates.
vehicleXPoints = 3:30;
ltPts = coder.nullcopy(zeros(28,2,'single'));
rtPts = coder.nullcopy(zeros(28,2,'single'));

if isRightLaneFound && isLeftLaneFound
    rtBoundary = params(4:6);
    rt_y = computeBoundaryModel(rtBoundary, vehicleXPoints);
    
    ltBoundary = params(1:3);
    lt_y = computeBoundaryModel(ltBoundary, vehicleXPoints);

    % Visualize lane boundaries of the ego vehicle.
    tform = get_tformToImage;

    % Map vehicle to image coordinates.
    ltPts =  tform.transformPointsInverse([vehicleXPoints', lt_y']);
    rtPts =  tform.transformPointsInverse([vehicleXPoints', rt_y']);
    laneFound = true;
else
    laneFound = false;
end
end

%% Helper Functions

% Compute boundary model.
function yWorld = computeBoundaryModel(model, xWorld)
yWorld = polyval(model, xWorld);
end

% Compute extrinsics.
function tform = get_tformToImage

%The camera coordinates are described by the caltech mono
% camera model.
yaw = 0;
pitch = 14; % Pitch of the camera in degrees
roll = 0;

translation = translationVector(yaw, pitch, roll);
rotation    = rotationMatrix(yaw, pitch, roll);

% Construct a camera matrix.
focalLength    = [309.4362, 344.2161];
principalPoint = [318.9034, 257.5352];
Skew = 0;

camMatrix = [rotation; translation] * intrinsicMatrix(focalLength, ...
    Skew, principalPoint);

% Turn camMatrix into 2-D homography.
tform2D = [camMatrix(1,:); camMatrix(2,:); camMatrix(4,:)]; % drop Z

tform = projective2d(tform2D);
tform = tform.invert();
end

% Translate to image co-ordinates.
function translation = translationVector(yaw, pitch, roll)
SensorLocation = [0 0];
Height = 2.1798;    % mounting height in meters from the ground
rotationMatrix = (...
    rotZ(yaw)*... % last rotation
    rotX(90-pitch)*...
    rotZ(roll)... % first rotation
    );


% Adjust for the SensorLocation by adding a translation.
sl = SensorLocation;

translationInWorldUnits = [sl(2), sl(1), Height];
translation = translationInWorldUnits*rotationMatrix;
end

% Rotation around X-axis.
function R = rotX(a)
a = deg2rad(a);
R = [...
    1   0        0;
    0   cos(a)  -sin(a);
    0   sin(a)   cos(a)];

end

% Rotation around Y-axis.
function R = rotY(a)
a = deg2rad(a);
R = [...
    cos(a)  0 sin(a);
    0       1 0;
    -sin(a) 0 cos(a)];

end

% Rotation around Z-axis.
function R = rotZ(a)
a = deg2rad(a);
R = [...
    cos(a) -sin(a) 0;
    sin(a)  cos(a) 0;
    0       0      1];
end

% Given the Yaw, Pitch, and Roll, determine the appropriate Euler angles
% and the sequence in which they are applied to align the camera's
% coordinate system with the vehicle coordinate system. The resulting
% matrix is a Rotation matrix that together with the Translation vector
% defines the extrinsic parameters of the camera.
function rotation = rotationMatrix(yaw, pitch, roll)
rotation = (...
    rotY(180)*...            % last rotation: point Z up
    rotZ(-90)*...            % X-Y swap
    rotZ(yaw)*...            % point the camera forward
    rotX(90-pitch)*...       % "un-pitch"
    rotZ(roll)...            % 1st rotation: "un-roll"
    );
end

% Intrinsic matrix computation.
function intrinsicMat = intrinsicMatrix(FocalLength, Skew, PrincipalPoint)
intrinsicMat = ...
    [FocalLength(1)  , 0                     , 0; ...
    Skew             , FocalLength(2)   , 0; ...
    PrincipalPoint(1), PrincipalPoint(2), 1];
end

CUDA 실행 파일 생성하기

detectLanesInVideo 진입점 함수에 대한 독립형 CUDA 실행 파일을 생성하려면 'exe' 타깃에 대한 GPU 코드 구성 객체를 만들고 타깃 언어를 C++로 설정하십시오. coder.DeepLearningConfig (GPU Coder) 함수를 사용하여 CuDNN 딥러닝 구성 객체를 만들고 이 객체를 GPU 코드 구성 객체의 DeepLearningConfig 속성에 할당합니다.

cfg = coder.gpuConfig('exe');
cfg.DeepLearningConfig = coder.DeepLearningConfig('cudnn');
cfg.GenerateReport = true;
cfg.GenerateExampleMain = "GenerateCodeAndCompile";
cfg.TargetLang = 'C++';
inputs = {coder.Constant(videoFile),coder.Constant(dlLaneNetFile), ...
    coder.Constant(laneCoeffMeans),coder.Constant(laneCoeffsStds)};

codegen 명령을 실행합니다.

codegen -args inputs -config cfg detectLanesInVideo
Code generation successful: View report

실행 파일 실행하기

실행 파일을 실행하려면 다음 코드 라인의 주석을 해제하십시오.

if ispc
    [status,cmdout] = system("detectLanesInVideo.exe");
else
    [status,cmdout] = system("./detectLanesInVideo");
end

gpuLaneDetectionOutput.png

관련 항목