GPU Coder により最適化された車線検出
この例では、SeriesNetwork
オブジェクトによって表された深層学習ネットワークから CUDA® コードを生成する方法を説明します。この例では、系列ネットワークはイメージから車線マーカーの境界を検出して出力できる畳み込みニューラル ネットワークです。
必要条件
CUDA 対応 NVIDIA® GPU。
NVIDIA CUDA ツールキットおよびドライバー。
NVIDIA cuDNN ライブラリ。
OpenCV ライブラリ (ビデオ読み取りとイメージ表示操作用)。
コンパイラおよびライブラリの環境変数。サポートされているコンパイラおよびライブラリのバージョンの詳細は、サードパーティ ハードウェア (GPU Coder)を参照してください。環境変数の設定は、前提条件となる製品の設定 (GPU Coder)を参照してください。
GPU 環境の検証
関数coder.checkGpuInstall
(GPU Coder)を使用して、この例を実行するのに必要なコンパイラおよびライブラリが正しく設定されていることを検証します。
envCfg = coder.gpuEnvConfig('host'); envCfg.DeepLibTarget = 'cudnn'; envCfg.DeepCodegen = 1; envCfg.Quiet = 1; coder.checkGpuInstall(envCfg);
事前学習済みの SeriesNetwork の取得
[laneNet, coeffMeans, coeffStds] = getLaneDetectionNetworkGPU();
このネットワークは入力としてイメージを取り、自車の左右の車線に対応する 2 つの車線境界線を出力します。各車線境界線は、放物線方程式 によって表すことができます。ここで、y は横方向オフセット、x は車両からの縦方向の距離です。このネットワークは、車線ごとに 3 つのパラメーター a、b、c を出力します。ネットワーク アーキテクチャは AlexNet
に似ていますが、最後の数層は、規模の小さい全結合層と回帰出力層に置き換えられています。ネットワーク アーキテクチャを表示するには、関数 analyzeNetwork
を使用します。
analyzeNetwork(laneNet)
メイン エントリポイント関数の確認
type detect_lane.m
function [laneFound, ltPts, rtPts] = detect_lane(frame, laneCoeffMeans, laneCoeffStds) % From the networks output, compute left and right lane points in the image % coordinates. The camera coordinates are described by the caltech mono % camera model. %#codegen % A persistent object mynet is used to load the series network object. At % the first call to this function, the persistent object is constructed and % setup. When the function is called subsequent times, the same object is % reused to call predict on inputs, thus avoiding reconstructing and % reloading the network object. persistent lanenet; if isempty(lanenet) lanenet = coder.loadDeepLearningNetwork('laneNet.mat', 'lanenet'); end lanecoeffsNetworkOutput = lanenet.predict(permute(frame, [2 1 3])); % Recover original coeffs by reversing the normalization steps params = lanecoeffsNetworkOutput .* laneCoeffStds + laneCoeffMeans; isRightLaneFound = abs(params(6)) > 0.5; %c should be more than 0.5 for it to be a right lane isLeftLaneFound = abs(params(3)) > 0.5; vehicleXPoints = 3:30; %meters, ahead of the sensor 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 function yWorld = computeBoundaryModel(model, xWorld) yWorld = polyval(model, xWorld); end function tform = get_tformToImage % Compute extrinsics based on camera setup 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 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 function intrinsicMat = intrinsicMatrix(FocalLength, Skew, PrincipalPoint) intrinsicMat = ... [FocalLength(1) , 0 , 0; ... Skew , FocalLength(2) , 0; ... PrincipalPoint(1), PrincipalPoint(2), 1]; end
ネットワーク用コードと後処理コードの生成
このネットワークは、左右の車線境界線の放物線方程式を記述するパラメーター a、b、c を計算します。
これらのパラメーターから、車線の位置に対応する x 座標と y 座標を計算します。これらの座標をイメージ座標にマッピングしなければなりません。関数 detect_lane.m
で、これらすべての計算を行います。'lib'
ターゲットの GPU コード構成オブジェクトを作成することによってこの関数の CUDA コードを生成し、ターゲット言語を C++ に設定します。関数 coder.DeepLearningConfig
(GPU Coder) を使用して CuDNN
深層学習構成オブジェクトを作成し、それを GPU コード構成オブジェクトの DeepLearningConfig
プロパティに割り当てます。codegen
コマンドを実行します。
cfg = coder.gpuConfig('lib'); cfg.DeepLearningConfig = coder.DeepLearningConfig('cudnn'); cfg.GenerateReport = true; cfg.TargetLang = 'C++'; inputs = {ones(227,227,3,'single'),ones(1,6,'double'),ones(1,6,'double')}; codegen -args inputs -config cfg detect_lane
Code generation successful: View report
生成されたコードの説明
系列ネットワークは、23 個の層クラスから成る配列を含む C++ クラスとして生成されます。
class c_lanenet { public: int32_T batchSize; int32_T numLayers; real32_T *inputData; real32_T *outputData; MWCNNLayer *layers[23]; public: c_lanenet(void); void setup(void); void predict(void); void cleanup(void); ~c_lanenet(void); };
このクラスの setup()
メソッドは、ハンドルを設定し、各層オブジェクトにメモリを割り当てます。predict()
メソッドは、ネットワーク内の 23 個の層それぞれについて予測を呼び出します。
cnn_lanenet_conv*_w および cnn_lanenet_conv*_b ファイルは、ネットワーク内の畳み込み層のバイナリ重みおよびバイアス ファイルです。cnn_lanenet_fc*_w および cnn_lanenet_fc*_b ファイルは、ネットワーク内の全結合層のバイナリ重みおよびバイアス ファイルです。
codegendir = fullfile('codegen', 'lib', 'detect_lane'); dir(codegendir)
. MWMaxPoolingLayer.o .. MWNormLayer.cpp .gitignore MWNormLayer.hpp DeepLearningNetwork.cu MWNormLayer.o DeepLearningNetwork.h MWOutputLayer.cpp DeepLearningNetwork.o MWOutputLayer.hpp MWActivationFunctionType.hpp MWOutputLayer.o MWCNNLayer.cpp MWRNNParameterTypes.hpp MWCNNLayer.hpp MWReLULayer.cpp MWCNNLayer.o MWReLULayer.hpp MWCNNLayerImplBase.hpp MWReLULayer.o MWCUSOLVERUtils.cpp MWTargetNetworkImplBase.hpp MWCUSOLVERUtils.hpp MWTargetTypes.hpp MWCUSOLVERUtils.o MWTensor.hpp MWCudaDimUtility.hpp MWTensorBase.cpp MWCudaMemoryFunctions.hpp MWTensorBase.hpp MWCudnnCNNLayerImpl.cu MWTensorBase.o MWCudnnCNNLayerImpl.hpp _clang-format MWCudnnCNNLayerImpl.o buildInfo.mat MWCudnnCommonHeaders.hpp cnn_lanenet0_0_conv1_b.bin MWCudnnCustomLayerBase.cu cnn_lanenet0_0_conv1_w.bin MWCudnnCustomLayerBase.hpp cnn_lanenet0_0_conv2_b.bin MWCudnnCustomLayerBase.o cnn_lanenet0_0_conv2_w.bin MWCudnnElementwiseAffineLayerImpl.cu cnn_lanenet0_0_conv3_b.bin MWCudnnElementwiseAffineLayerImpl.hpp cnn_lanenet0_0_conv3_w.bin MWCudnnElementwiseAffineLayerImpl.o cnn_lanenet0_0_conv4_b.bin MWCudnnFCLayerImpl.cu cnn_lanenet0_0_conv4_w.bin MWCudnnFCLayerImpl.hpp cnn_lanenet0_0_conv5_b.bin MWCudnnFCLayerImpl.o cnn_lanenet0_0_conv5_w.bin MWCudnnFusedConvActivationLayerImpl.cu cnn_lanenet0_0_data_offset.bin MWCudnnFusedConvActivationLayerImpl.hpp cnn_lanenet0_0_data_scale.bin MWCudnnFusedConvActivationLayerImpl.o cnn_lanenet0_0_fc6_b.bin MWCudnnInputLayerImpl.hpp cnn_lanenet0_0_fc6_w.bin MWCudnnLayerImplFactory.cu cnn_lanenet0_0_fcLane1_b.bin MWCudnnLayerImplFactory.hpp cnn_lanenet0_0_fcLane1_w.bin MWCudnnLayerImplFactory.o cnn_lanenet0_0_fcLane2_b.bin MWCudnnMaxPoolingLayerImpl.cu cnn_lanenet0_0_fcLane2_w.bin MWCudnnMaxPoolingLayerImpl.hpp cnn_lanenet0_0_responseNames.txt MWCudnnMaxPoolingLayerImpl.o codeInfo.mat MWCudnnNormLayerImpl.cu codedescriptor.dmr MWCudnnNormLayerImpl.hpp compileInfo.mat MWCudnnNormLayerImpl.o detect_lane.a MWCudnnOutputLayerImpl.cu detect_lane.cu MWCudnnOutputLayerImpl.hpp detect_lane.h MWCudnnOutputLayerImpl.o detect_lane.o MWCudnnReLULayerImpl.cu detect_lane_data.cu MWCudnnReLULayerImpl.hpp detect_lane_data.h MWCudnnReLULayerImpl.o detect_lane_data.o MWCudnnTargetNetworkImpl.cu detect_lane_initialize.cu MWCudnnTargetNetworkImpl.hpp detect_lane_initialize.h MWCudnnTargetNetworkImpl.o detect_lane_initialize.o MWElementwiseAffineLayer.cpp detect_lane_internal_types.h MWElementwiseAffineLayer.hpp detect_lane_rtw.mk MWElementwiseAffineLayer.o detect_lane_terminate.cu MWElementwiseAffineLayerImplKernel.cu detect_lane_terminate.h MWElementwiseAffineLayerImplKernel.o detect_lane_terminate.o MWFCLayer.cpp detect_lane_types.h MWFCLayer.hpp examples MWFCLayer.o gpu_codegen_info.mat MWFusedConvActivationLayer.cpp html MWFusedConvActivationLayer.hpp interface MWFusedConvActivationLayer.o networkParamsInfo_lanenet0_0.bin MWInputLayer.cpp predict.cu MWInputLayer.hpp predict.h MWInputLayer.o predict.o MWKernelHeaders.hpp rtw_proj.tmw MWLayerImplFactory.hpp rtwtypes.h MWMaxPoolingLayer.cpp shared_layers_export_macros.hpp MWMaxPoolingLayer.hpp
出力の後処理用の追加ファイルの生成
実行中に使用する学習済みネットワークから平均値と標準偏差値をエクスポートします。
codegendir = fullfile(pwd, 'codegen', 'lib','detect_lane'); fid = fopen(fullfile(codegendir,'mean.bin'), 'w'); A = [coeffMeans coeffStds]; fwrite(fid, A, 'double'); fclose(fid);
メイン ファイル
メイン ファイルを使用してネットワーク コードをコンパイルします。メイン ファイルは、OpenCV の VideoCapture
メソッドを使用して、入力ビデオからフレームを読み取ります。読み取るフレームがなくなるまで、各フレームが処理されて分類されます。各フレームの出力を表示する前に、出力は、detect_lane.cu
で生成される関数 detect_lane
を使用して後処理されます。
type main_lanenet.cu
/* Copyright 2016 The MathWorks, Inc. */ #include <stdio.h> #include <stdlib.h> #include <cuda.h> #include <opencv2/opencv.hpp> #include <opencv2/imgproc.hpp> #include <opencv2/core/core.hpp> #include <opencv2/core/types.hpp> #include <opencv2/highgui.hpp> #include <list> #include <cmath> #include "detect_lane.h" using namespace cv; void readData(float *input, Mat& orig, Mat & im) { Size size(227,227); resize(orig,im,size,0,0,INTER_LINEAR); for(int j=0;j<227*227;j++) { //BGR to RGB input[2*227*227+j]=(float)(im.data[j*3+0]); input[1*227*227+j]=(float)(im.data[j*3+1]); input[0*227*227+j]=(float)(im.data[j*3+2]); } } void addLane(float pts[28][2], Mat & im, int numPts) { std::vector<Point2f> iArray; for(int k=0; k<numPts; k++) { iArray.push_back(Point2f(pts[k][0],pts[k][1])); } Mat curve(iArray, true); curve.convertTo(curve, CV_32S); //adapt type for polylines polylines(im, curve, false, CV_RGB(255,255,0), 2, LINE_AA); } void writeData(float *outputBuffer, Mat & im, int N, double means[6], double stds[6]) { // get lane coordinates boolean_T laneFound = 0; float ltPts[56]; float rtPts[56]; detect_lane(outputBuffer, means, stds, &laneFound, ltPts, rtPts); if (!laneFound) { return; } float ltPtsM[28][2]; float rtPtsM[28][2]; for(int k=0; k<28; k++) { ltPtsM[k][0] = ltPts[k]; ltPtsM[k][1] = ltPts[k+28]; rtPtsM[k][0] = rtPts[k]; rtPtsM[k][1] = rtPts[k+28]; } addLane(ltPtsM, im, 28); addLane(rtPtsM, im, 28); } void readMeanAndStds(const char* filename, double means[6], double stds[6]) { FILE* pFile = fopen(filename, "rb"); if (pFile==NULL) { fputs ("File error",stderr); return; } // obtain file size fseek (pFile , 0 , SEEK_END); long lSize = ftell(pFile); rewind(pFile); double* buffer = (double*)malloc(lSize); size_t result = fread(buffer,sizeof(double),lSize,pFile); if (result*sizeof(double) != lSize) { fputs ("Reading error",stderr); return; } for (int k = 0 ; k < 6; k++) { means[k] = buffer[k]; stds[k] = buffer[k+6]; } free(buffer); } // Main function int main(int argc, char* argv[]) { float *inputBuffer = (float*)calloc(sizeof(float),227*227*3); float *outputBuffer = (float*)calloc(sizeof(float),6); if ((inputBuffer == NULL) || (outputBuffer == NULL)) { printf("ERROR: Input/Output buffers could not be allocated!\n"); exit(-1); } // get ground truth mean and std double means[6]; double stds[6]; readMeanAndStds("mean.bin", means, stds); if (argc < 2) { printf("Pass in input video file name as argument\n"); return -1; } VideoCapture cap(argv[1]); if (!cap.isOpened()) { printf("Could not open the video capture device.\n"); return -1; } cudaEvent_t start, stop; float fps = 0; cudaEventCreate(&start); cudaEventCreate(&stop); Mat orig, im; namedWindow("Lane detection demo",WINDOW_NORMAL); while(true) { cudaEventRecord(start); cap >> orig; if (orig.empty()) break; readData(inputBuffer, orig, im); writeData(inputBuffer, orig, 6, means, stds); cudaEventRecord(stop); cudaEventSynchronize(stop); char strbuf[50]; float milliseconds = -1.0; cudaEventElapsedTime(&milliseconds, start, stop); fps = fps*.9+1000.0/milliseconds*.1; sprintf (strbuf, "%.2f FPS", fps); putText(orig, strbuf, Point(200,30), FONT_HERSHEY_DUPLEX, 1, CV_RGB(0,0,0), 2); imshow("Lane detection demo", orig); if( waitKey(50)%256 == 27 ) break; // stop capturing by pressing ESC */ } destroyWindow("Lane detection demo"); free(inputBuffer); free(outputBuffer); return 0; }
サンプル ビデオのダウンロード
if ~exist('./caltech_cordova1.avi', 'file') url = 'https://www.mathworks.com/supportfiles/gpucoder/media/caltech_cordova1.avi'; websave('caltech_cordova1.avi', url); end
実行可能ファイルのビルド
if ispc setenv('MATLAB_ROOT', matlabroot); vcvarsall = mex.getCompilerConfigurations('C++').Details.CommandLineShell; setenv('VCVARSALL', vcvarsall); system('make_win_lane_detection.bat'); cd(codegendir); system('lanenet.exe ..\..\..\caltech_cordova1.avi'); else setenv('MATLAB_ROOT', matlabroot); system('make -f Makefile_lane_detection.mk'); cd(codegendir); system('./lanenet ../../../caltech_cordova1.avi'); end