Edge AI開發平台比一比
|

用OpenVINO C++ API編寫YOLOv8-Seg實例分割模型推論程式

   

作者:戰鵬州

1.1 簡介

本文章將介紹使用OpenVINO C++ API開發YOLOv8-Seg實例分割(Instance Segmentation)模型的AI推論程式。本文C++範例程式的開發環境是Windows + Visual Studio Community 2022,請讀者先配置基於Visual Studio的OpenVINO C++開發環境

請複製本文的程式碼倉:git clone https://gitee.com/ppov-nuc/yolov8_openvino_cpp.git

1.2 匯出YOLOv8-Seg OpenVINO IR 模型

YOLOv8是Ultralytics公司基於YOLO框架,發佈的一款針對物體檢測與追蹤、實例分割、影像分類和姿態估計任務的SOTA模型工具套件。

首先用命令pip install -r requirements.txt 安裝ultralyticsopenvino-dev

然後使用命令:yolo export model=yolov8n-seg.pt format=onnx,完成yolov8n-seg.onnx模型匯出,如下圖所示。

接著使用命令:mo -m yolov8n-seg.onnx --compress_to_fp16,

最佳化並匯出FP16精度的OpenVINO IR格式模型,如下圖所示。

最後使用命令:benchmark_app -m yolov8n-seg.xml -d GPU.1,獲得yolov8n-seg.xml模型在A770m獨立顯卡上的非同步推論運算性能,如下圖所示。

1.3 使用OpenVINO C++ API編寫YOLOv8-Seg實例分割模型推論程式

使用OpenVINO C++ API編寫YOLOv8-Seg實例分割模型推論程式主要有5個典型步驟:

  1. 採集影像&影像解碼
  2. 影像資料預處理
  3. AI推論運算(基於OpenVINO C++ API)
  4. 對推論結果進行後處理
  5. 將處理後的結果視覺化

YOLOv8-Seg實例分割模型推論程式的影像資料預處理和AI推論運算的實現方式跟YOLOv8目標檢測模型推論程式的實現方式幾乎一模一樣,可以直接重複使用。

1.3.1  影像資料預處理

使用Netron打開yolov8n-seg.onnx,如下圖所示,可以看到:

  • 輸入節點的名字:“images”;數據:float32[1,3,640,640]。
  • 輸出節點1的名字:“output0”;數據:float32[1,116,8400]。其中116的前84個欄位跟 YOLOv8目標檢測模型輸出定義完全一致,即cx,cy,w,h和80類的分數;後32個欄位為遮罩可靠度,用於運算遮罩資料。
  • 輸出節點2的名字:“output1”;數據:float32[1,32,160,160]。output0後32個欄位與output1的資料做矩陣乘法後得到的結果,即為對應目標的遮罩資料。

影像資料預處理的目標就是將任意尺寸的影像資料轉變為形狀為[1,3,640,640],精度為FP32的張量。YOLOv8-Seg模型的輸入尺寸為正方形,為了解決將任意尺寸資料縮放為正方形帶來的影像失真問題,在影像縮放前,採用letterbox演算法先保持影像的長寬比,如下圖所示,然後再使用cv::dnn::blobFromImage函數對影像進行縮放。

影像資料預處理的範例程式如下所示:

Mat letterbox(const Mat& source)
{
    int col = source.cols;
    int row = source.rows;
    int _max = MAX(col, row);
    Mat result = Mat::zeros(_max, _max, CV_8UC3);
    source.copyTo(result(Rect(0, 0, col, row)));
    return result;
}
Mat img = cv::imread("bus.jpg");
Mat letterbox_img = letterbox(img);
Mat blob = blobFromImage(letterbox_img, 1.0/255.0, Size(640,640), Scalar(), true);

1.3.2   AI同步推論運算

用OpenVINO C++ API實現同步推論運算,主要有七步:

  1. 產生實體Core對象:ov::Core core;
  2. 編譯並載入模型:compile_model();
  3. 創建推論請求:infer_request = compiled_model.create_infer_request();
  4. 讀取影像資料並完成預處理;
  5. 將輸入數據導入模型:set_input_tensor(input_tensor);
  6. 啟動推論運算:infer();
  7. 獲得推論結果:output0 = infer_request.get_output_tensor(0);

output1 = infer_request.get_output_tensor(1);

範例程式碼如下所示:

// -------- Step 1. Initialize OpenVINO Runtime Core --------
ov::Core core;
// -------- Step 2. Compile the Model --------
auto compiled_model = core.compile_model("yolov8n-seg.xml", "CPU");
// -------- Step 3. Create an Inference Request --------
ov::InferRequest infer_request = compiled_model.create_infer_request();
// -------- Step 4.Read a picture file and do the preprocess --------
Mat img = cv::imread("bus.jpg");
// Preprocess the image
Mat letterbox_img = letterbox(img);
float scale = letterbox_img.size[0] / 640.0;
Mat blob = blobFromImage(letterbox_img, 1.0 / 255.0, Size(640, 640), Scalar(), true);
// -------- Step 5. Feed the blob into the input node of the Model -------
// Get input port for model with one input
auto input_port = compiled_model.input();
// Create tensor from external memory
ov::Tensor input_tensor(input_port.get_element_type(), input_port.get_shape(), blob.ptr(0));
// Set input tensor for model with one input
infer_request.set_input_tensor(input_tensor);
// -------- Step 6. Start inference --------
infer_request.infer();
// -------- Step 7. Get the inference result --------
auto output0 = infer_request.get_output_tensor(0); //output0
auto output1 = infer_request.get_output_tensor(1); //otuput1

1.3.3   推論結果後處理

實例分割推論程式的後處理是從結果中拆解出預測別類(class_id),類別分數(class_score),類別邊界框(box)和類別遮罩(mask),範例程式碼如下所示:

// -------- Step 8. Postprocess the result --------
Mat output_buffer(output0_shape[1], output0_shape[2], CV_32F, output0.data());
Mat proto(32, 25600, CV_32F, output1.data()); //[32,25600]
transpose(output_buffer, output_buffer); //[8400,116]
float score_threshold = 0.25;
float nms_threshold = 0.5;
std::vector class_ids;
std::vector class_scores;
std::vector boxes;
std::vector mask_confs;
// Figure out the bbox, class_id and class_score
for (int i = 0; i < output_buffer.rows; i++) { Mat classes_scores = output_buffer.row(i).colRange(4, 84); Point class_id; double maxClassScore; minMaxLoc(classes_scores, 0, &maxClassScore, 0, &class_id); if (maxClassScore > score_threshold) {
class_scores.push_back(maxClassScore);
class_ids.push_back(class_id.x);
float cx = output_buffer.at(i, 0);
float cy = output_buffer.at(i, 1);
float w = output_buffer.at(i, 2);
float h = output_buffer.at(i, 3);
int left = int((cx - 0.5 * w) * scale);
int top = int((cy - 0.5 * h) * scale);
int width = int(w * scale);
int height = int(h * scale);
cv::Mat mask_conf = output_buffer.row(i).colRange(84, 116);
mask_confs.push_back(mask_conf);
boxes.push_back(Rect(left, top, width, height));
}
}
//NMS
std::vector indices;
NMSBoxes(boxes, class_scores, score_threshold, nms_threshold, indices);

完整範例參考參見:yolov8_seg_ov_infer.cpp,執行結果如下圖所示:

1.4 結論:

OpenVINO C++ API簡單清晰,易學易用。本文用不到100行(不含視覺化檢測結果)C++程式碼就實現了基於OpenVINO的YOLOv8-Seg實例分割模型推論程式,在英特爾獨立顯卡A770m上獲得了較好的推論運算性能。

戰鵬州

訂閱MakerPRO知識充電報

與40000位開發者一同掌握科技創新的技術資訊!

Author: 戰鵬州

英特爾邊緣運算創新大使

Share This Post On
468 ad

Submit a Comment

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *