熱影像物件偵測 (Object Detection on Thermal Image) - 3 使用TensorRT C++ Win10 - 寫TensorRT C++
上篇文章我們測試了onnx格式的model,確認這個model是可以使用的。接下來就要在 TensorRT 上寫C++ 程式執行。
一、安裝tensorRT
可以參考我之前寫安裝tensorRT的文章,依照步驟安裝。
二、複製tensorRT sample 程式修改
我使用 samples\sampleOnnxMNIST 做修改,因為都是讀入onnx格式的model。
將 sampleOnnxMNIST 資料夾複製一份出來,一樣放在 samples
資料夾下,修改檔名叫做sampleOnnxThermalObjDetection,由於model input output
格式跟sample的不一樣,所以要稍微修改。
三、專案屬性加入opencv設定
我們需要讀入圖片,以及對圖片的處理。
這裡我借用openvino裡的opencv來用。
1.開啟專案的屬性頁面,在"其他 Include 目錄"裡添加 opencv include 路徑:
開啟 sampleOnnxMNIST.cpp 檔案做修改
1. include opencv
#include <opencv2/core.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/imgproc.hpp>
2.修改 model input output name:
將 initializeSampleParams() 修改成以下,之後要將model及image放在
samples/sampleOnnxThermalObjDetection/ 路徑之下。
samplesCommon::OnnxSampleParams initializeSampleParams(const samplesCommon::Args& args) { samplesCommon::OnnxSampleParams params; if (args.dataDirs.empty()) //!< Use default directories if user hasn't provided directory paths { params.dataDirs.push_back("samples/sampleOnnxThermalObjDetection/"); } else //!< Use the data directory provided by the user { params.dataDirs = args.dataDirs; } params.onnxFileName = "export.onnx"; params.inputTensorNames.push_back("input0"); params.outputTensorNames.push_back("output0"); params.outputTensorNames.push_back("output1"); params.dlaCore = args.useDLACore; params.int8 = args.runInInt8; params.fp16 = args.runInFp16; return params; }
3.修改input output get dimension的部分:
在宣告 mOutputDims 的地方再宣告一個 mOutputDims1。
在 build()
裡面的後半部在mEngine之後,看到 mInputDims及 mOutputDims的地方修改成以下:
assert(network->getNbInputs() == 1); mInputDims = network->getInput(0)->getDimensions(); assert(mInputDims.nbDims == 4); assert(network->getNbOutputs() == 2); mOutputDims = network->getOutput(0)->getDimensions(); assert(mOutputDims.nbDims == 2); mOutputDims1 = network->getOutput(1)->getDimensions(); assert(mOutputDims1.nbDims == 2);
4.修改 processInput():
圖片 resize 成 model input大小,依照 [B,C,H,W] 的架構輸入,將每個 pixel
的顏色轉成浮點數,所以要除以 255.0。
bool SampleOnnxMNIST::processInput(const samplesCommon::BufferManager& buffers, cv::Mat &image) { const int inputChannels = mInputDims.d[1]; const int inputH = mInputDims.d[2]; const int inputW = mInputDims.d[3]; cv::Mat resized_image; cv::resize(image, resized_image, cv::Size(inputW, inputH)); int batchIndex = 0; int batchOffset = batchIndex * inputW * inputH * inputChannels; float* hostDataBuffer = static_cast<float*>(buffers.getHostBuffer(mParams.inputTensorNames[0])); // input shape [B,C,H,W] for (size_t c = 0; c < inputChannels; c++) { for (size_t h = 0; h < inputH; h++) { for (size_t w = 0; w < inputW; w++) { hostDataBuffer[batchOffset + c * inputW * inputH + h * inputW + w] = float(float(resized_image.at<cv::Vec3b>(h, w)[c]) / 255.0); } } } return true; }
5.修改 verifyOutput():
- output0_class_score 輸出大小是[N,80],80個種類每個種類的分數,所以要找出最高分的那個種類,N指的是有多少個物體。
- output1_bbox 輸出大小是[N,4],物體位置四個標記點 [x,y,width,height],而 x,y是物體的中心點位置,N指的是有多少個物體。
bool SampleOnnxMNIST::verifyOutput(const samplesCommon::BufferManager& buffers, cv::Mat image) { std::vector<cv::Rect2i> bbox_vec; float* output0_class_score = static_cast<float*>(buffers.getHostBuffer(mParams.outputTensorNames[0])); // 80 class score float* output1_bbox = static_cast<float*>(buffers.getHostBuffer(mParams.outputTensorNames[1])); // bbox float conf_thres = 0.5; const int output0_row = mOutputDims.d[0]; const int output0_col = mOutputDims.d[1]; const int output1_col = mOutputDims1.d[1]; for (int row = 0; row < output0_row; row++) { float maxConfidence = output0_class_score[row * output0_col]; int classIdx = 0.0; // get confidence highest score for (int col = 1, first_idx = row * output0_col; col < output0_col; col++) { float confidence = output0_class_score[first_idx + col]; if (maxConfidence < confidence) { maxConfidence = confidence; classIdx = col; } } if (maxConfidence > conf_thres) { int idx = row * output1_col; double x = double(output1_bbox[idx + 0]); double y = double(output1_bbox[idx + 1]); double w = double(output1_bbox[idx + 2]); double h = double(output1_bbox[idx + 3]); cv::Rect2i bbox; bbox.x = static_cast<int>((x - w / 2) * float(image.cols)); bbox.y = static_cast<int>((y - h / 2) * float(image.rows)); bbox.width = static_cast<int>(w * float(image.cols)); bbox.height = static_cast<int>(h * float(image.rows)); if ((bbox.width > 15) && (bbox.height > 15)) { bbox_vec.push_back(bbox); } } } // draw result for (int idx = 0; idx < bbox_vec.size(); idx++) { cv::Rect2i i_rect = bbox_vec.at(idx); cv::rectangle(image, i_rect, cv::Scalar(255, 0, 0), 2); } cv::imshow("img", image); int key = cv::waitKey(0); cv::destroyAllWindows(); return true; }
6.infer()
讀入圖片,processInput()、verifyOutput() 輸入的參數增加輸入圖片進去。
bool SampleOnnxMNIST::infer() { // Create RAII buffer manager object samplesCommon::BufferManager buffers(mEngine); auto context = SampleUniquePtr<nvinfer1::IExecutionContext>(mEngine->createExecutionContext()); if (!context) { return false; } // read image cv::Mat image = cv::imread("image.jpeg"); if (image.cols == 0 || image.rows == 0) { printf("image is empty\n"); return false; } // Read the input data into the managed buffers assert(mParams.inputTensorNames.size() == 1); if (!processInput(buffers, image)) { return false; } // Memcpy from host input buffers to device input buffers buffers.copyInputToDevice(); bool status = context->executeV2(buffers.getDeviceBindings().data()); if (!status) { return false; } // Memcpy from device output buffers to host output buffers buffers.copyOutputToHost(); // Verify results if (!verifyOutput(buffers, image)) { return false; } return true; }
五、放入model及image檔案
將model及image放在 samples/sampleOnnxThermalObjDetection/ 路徑之下。
六、建置專案及執行結果
下圖為執行結果:
會發現結果圖上有多個框重疊在同一個物體上,可以計算框與框之間的IOU值,來合併在同一個物體上的多個框。
八、參考資料來源:
https://medium.com/@joehoeller/object-detection-on-thermal-images-f9526237686a
https://github.com/joehoeller/Object-Detection-on-Thermal-Images
留言
張貼留言