點選藍字 關註我
❝
英特爾發行版 OpenVINO™ 工具套件基於 oneAPI 而開發,可以加快高效能電腦視覺和深度學習視覺套用開發速度工具套件,適用於從邊緣到雲的各種英特爾平台上,幫助使用者更快地將更準確的真實世界結果部署到生產系統中。YOLOv10是清華大學研究人員近期提出的一種即時目標檢測方法,透過消除NMS、最佳化模型架構和引入創新模組等策略,在保持高精度的同時顯著降低了計算開銷,為即時目標檢測領域帶來了新的突破。
在本文中,我們將演示如何使用Intel OpenVINO™ C++ API 部署YOLOv10目標檢測模型,並使用 OpenVINO™ 異步推理介面實作模型推理加速。下面看一下YOLOv10模型在OpenVINO™上的執行效果吧:
1. 前言
英特爾發行版 OpenVINO™ 工具套件基於 oneAPI 而開發,可以加快高效能電腦視覺和深度學習視覺套用開發速度工具套件,適用於從邊緣到雲的各種英特爾平台上,幫助使用者更快地將更準確的真實世界結果部署到生產系統中。透過簡化的開發工作流程,OpenVINO™ 可賦能開發者在現實世界中部署高效能應用程式和演算法。
2024年4月25日,英特爾釋出了開源 OpenVINO™ 2024.1 工具包,用於在各種硬體上最佳化和部署人工智慧推理。更新了更多的 Gen AI 覆蓋範圍和框架整合,以最大限度地減少程式碼更改。同時提供了更廣泛的 LLM 模型支持和更多的模型壓縮技術。透過壓縮嵌入的額外最佳化減少了 LLM 編譯時間,改進了采用英特爾®高級矩陣擴充套件 (Intel® AMX) 的第 4 代和第 5 代英特爾®至強®處理器上 LLM 的第 1 令牌效能。透過對英特爾®銳炫™ GPU 的 oneDNN、INT4 和 INT8 支持,實作更好的 LLM 壓縮和改進的效能。最後實作了更高的可移植性和效能,可在邊緣、雲端或本地執行 AI。
YOLOv10是清華大學研究人員近期提出的一種即時目標檢測方法,該方法在Ultralytics Python包的基礎上進行了多項創新和改進,主要有以下特點
消除非極大值抑制(NMS) :YOLOv10透過引入一致的雙重分配策略,在訓練時使用一對多的標簽分配來提供豐富的監督訊號,在推理時使用一對一的匹配,從而消除了對NMS的依賴。這一改進在保持高精度的同時,減少了推理延遲和計算量。
全面最佳化的模型架構 :YOLOv10從推理效率和準確性的角度出發,全面最佳化了模型的各個組成部份。這包括采用輕量級分類頭、空間通道去耦下采樣和等級引導塊設計等,以減少計算冗余並提高模型效能。
引入大核摺積和部份自註意模組 :為了提高效能,YOLOv10在不增加大量計算成本的前提下,引入了大核摺積和部份自註意模組。
多種模型尺寸可選 :官方釋出了從N到X各種型號的模型,以滿足不同套用的需求。這些模型包括超小型版本YOLOv10-N(用於資源極其有限環境)、小型版本YOLOv10-S(兼顧速度和精度)、中型版本YOLOv10-M(通用)、平衡型版本YOLOv10-B(寬度增加,精度更高)、大型版本YOLOv10-L(精度更高,但計算資源增加)以及超大型版本YOLOv10-X(可實作最高的精度和效能)。
透過廣泛的實驗驗證,YOLOv10在多個模型尺度上實作了卓越的精度-延遲權衡。例如,在COCO數據集上,YOLOv10-S在相似精度下比其他即時目標檢測方法更快,同時參數和浮點運算量也大幅減少。綜上所述,YOLOv10透過消除NMS、最佳化模型架構和引入創新模組等策略,在保持高精度的同時顯著降低了計算開銷,為即時目標檢測領域帶來了新的突破。
2. 計畫開發環境
下面簡單介紹一下計畫的開發環境,開發者可以根據自己的裝置情況進行配置:
系統平台:Windows 11
Intel Core i7-1165G7
開發平台:Visual Studio 2022
OpenVINO™:2024.1.0
OpenCV:4.8.0
此處程式碼開發平台使用的是C++,因此在計畫配置時,需要配置第三方依賴庫,分別是 OpenVINO™和OpenCV兩個個依賴庫,其配置方式此處不做詳述。
3. 模型獲取與INT8量化
為了提升模型的推理速度,我們此處使用 OpenVINO™ 進行推理加速,並使用OpenVINO™NNCF 工具對模型進行一個INT8量化。量化的詳細流程可以參考下面notebooks,該notebooks記錄了YOLOv10使用OpenVINO™量化的詳細流程,連結如下所示:
https://github.com/openvinotoolkit/openvino_notebooks/tree/latest/notebooks/yolov10-optimization
模型量化完成後,我們對比了一下量化前後模型變化,如下圖所示:
4. 定義YOLOv10 Process
4.1 數據預處理
數據預處理此處透過OpenCV實作,將輸入的圖片數據轉為模型需要的數據情況,程式碼如下所示:
voidpre_process(cv::Mat* img, int length, float* factor, std::vector<float>& data){
cv::Mat mat;
int rh = img->rows;
int rw = img->cols;
int rc = img->channels();
cv::cvtColor(*img, mat, cv::COLOR_BGR2RGB);
int max_image_length = rw > rh ? rw : rh;
cv::Mat max_image = cv::Mat::zeros(max_image_length, max_image_length, CV_8UC3);
max_image = max_image * 255;
cv::Rect roi(0, 0, rw, rh);
mat.copyTo(cv::Mat(max_image, roi));
cv::Mat resize_img;
cv::resize(max_image, resize_img, cv::Size(length, length), 0.0f, 0.0f, cv::INTER_LINEAR);
*factor = (float)((float)max_image_length / (float)length);
resize_img.convertTo(resize_img, CV_32FC3, 1 / 255.0);
rh = resize_img.rows;
rw = resize_img.cols;
rc = resize_img.channels();
for (int i = 0; i < rc; ++i) {
cv::extractChannel(resize_img, cv::Mat(rh, rw, CV_32FC1, data.data() + i * rh * rw), i);
}
}
在呼叫時也相對簡單,將相關變量傳入即可,程式碼如下所示:
Mat frame = new frame();
std::vector<float> input_data(640 * 640 * 3);
float factor = 0;
pre_process(&frame, 640, &factor, input_data);
4.2 結果後處理
首先此處定義了一個結果類:
structDetResult {
cv::Rect bbox;
float conf;
int lable;
DetResult(cv::Rect bbox,float conf,int lable):bbox(bbox),conf(conf),lable(lable){}
};
然後定義模型的結果處理方式,程式碼如下所示:
std::vector<DetResult> post_process(float* result, float factor, int outputLength){
std::vector<cv::Rect> position_boxes;
std::vector <int> class_ids;
std::vector <float> confidences;
// Preprocessing output results
for (int i = 0; i < outputLength; i++)
{
int s = 6 * i;
if ((float)result[s + 4] > 0.2)
{
float cx = result[s + 0];
float cy = result[s + 1];
float dx = result[s + 2];
float dy = result[s + 3];
int x = (int)((cx)*factor);
int y = (int)((cy)*factor);
int width = (int)((dx - cx) * factor);
int height = (int)((dy - cy) * factor);
cv::Rect box(x, y, width, height);
position_boxes.push_back(box);
class_ids.push_back((int)result[s + 5]);
confidences.push_back((float)result[s + 4]);
}
}
std::vector<DetResult> re;
for (int i = 0; i < position_boxes.size(); i++)
{
DetResult det(position_boxes[i], confidences[i], class_ids[i]);
re.push_back(det);
}
return re;
}
最後為了讓結果視覺化,定義了結果繪制方法,程式碼如下所示:
voiddraw_bbox(cv::Mat& img, std::vector<DetResult>& res){
for (size_t j = 0; j < res.size(); j++) {
cv::rectangle(img, res[j].bbox, cv::Scalar(255, 0, 255), 2);
cv::putText(img, std::to_string(res[j].lable) + "-" + std::to_string(res[j].conf),
cv::Point(res[j].bbox.x, res[j].bbox.y - 1), cv::FONT_HERSHEY_PLAIN,
1.2, cv::Scalar(0, 0, 255), 2);
}
}
上述方式呼叫依舊十分容易,使用程式碼如下所示:
std::vector<float> output_data(300 * 6);
std::vector<DetResult> result = post_process(output_data.data(), factor, 300);
draw_bbox(frame, result);
5. 模型推理實作
5.1 基本推理實作
首先實作一下常規的同步推理程式碼,如下面所示:
voidyolov10_infer_without_process(){
std::string videoPath = "E:\\Text_dataset\\car_test.mov";
std::string model_path = "E:\\Text_Model\\yolov10s_openvino_model\\yolov10s.xml";
ov::Core core;
auto model = core.read_model(model_path);
auto compiled_model = core.compile_model(model, "CPU");
ov::InferRequest request =compiled_model.create_infer_request();
cv::VideoCapture capture(videoPath);
if (!capture.isOpened()) {
std::cerr << "ERROR: 視訊無法開啟" << std::endl;
return;
}
float factor = 0;
request.get_input_tensor().set_shape(std::vector<size_t>{1, 3, 640, 640});
std::vector<float> inputData(640 * 640 * 3);
std::chrono::time_point<std::chrono::steady_clock> t_beg;
std::chrono::time_point<std::chrono::steady_clock> t_end;
while (true)
{
cv::Mat frame;
if (!capture.read(frame)) {
break;
}
t_beg = std::chrono::high_resolution_clock::now();
pre_process(&frame, 640, &factor, inputData);
memcpy(request.get_input_tensor().data<float>(), inputData.data(), 640 * 640 * 3);
request.infer();
float* output_data = request.get_output_tensor().data<float>();
std::vector<DetResult> result = post_process(output_data, factor, 300);
t_end = std::chrono::high_resolution_clock::now();
cv::putText(frame, "FPS: " + std::to_string(1000.0 / std::chrono::duration<float, std::milli>(t_end - t_beg).count())
+ ", Time: " + std::to_string(std::chrono::duration<float, std::milli>(t_end - t_beg).count()) + "ms",
cv::Point(20, 40), 1, 2, cv::Scalar(255, 0, 255), 2);
draw_bbox(frame, result);
imshow("讀取視訊", frame);
cv::waitKey(1); //延時30
}
cv::destroyAllWindows();
return;
}
5.2 使用異步推理實作
視訊一般1s中有25幀左右,這就意味著我們需要1s推理25張圖片才可以實作視訊推理。一般情況下,在CPU裝置推理視覺模型很難實作即時推理,我們在使用 OpenVINO™推理時,經過一些最佳化技術後,勉強可以實作25FPS的推理,但是如果需要處理其他業務將會很難實作。因此為了提升推理速度,我們采用異步推理技術,實作程式碼如下所示:
voidyolov10_infer_ansy_without_process(){
std::string videoPath = "E:\\Text_dataset\\car_test.mov";
std::string model_path = "E:\\Text_Model\\yolov10s_openvino_model\\yolov10s.xml";
ov::Core core;
auto model = core.read_model(model_path);
auto compiled_model = core.compile_model(model, "CPU");
std::vector<ov::InferRequest>requests = { compiled_model.create_infer_request(), compiled_model.create_infer_request() };
cv::VideoCapture capture(videoPath);
// 檢查網路攝影機是否成功開啟
if (!capture.isOpened()) {
std::cerr << "ERROR: 視訊無法開啟" << std::endl;
return;
}
float factor = 0;
requests[0].get_input_tensor().set_shape(std::vector<size_t>{1, 3, 640, 640});
requests[1].get_input_tensor().set_shape(std::vector<size_t>{1, 3, 640, 640});
cv::Mat frame;
capture.read(frame);
std::vector<float> inputData(640 * 640 * 3);
pre_process(&frame, 640, &factor, inputData);
memcpy(requests[0].get_input_tensor().data<float>(), inputData.data(), 640 * 640 * 3);
requests[0].start_async();
std::chrono::time_point<std::chrono::steady_clock> t_beg;
std::chrono::time_point<std::chrono::steady_clock> t_end;
while (true)
{
cv::Mat next_frame;
if (!capture.read(next_frame)) {
break;
}
t_beg = std::chrono::high_resolution_clock::now();
pre_process(&next_frame, 640, &factor, inputData);
memcpy(requests[1].get_input_tensor().data<float>(), inputData.data(), 640 * 640 * 3);
requests[1].start_async();
requests[0].wait();
float* output_data = requests[0].get_output_tensor().data<float>();
std::vector<DetResult> result = post_process(output_data, factor, 300);
t_end = std::chrono::high_resolution_clock::now();
draw_bbox(frame, result);
cv::putText(frame, "FPS: " + std::to_string(1000.0 / std::chrono::duration<float, std::milli>(t_end - t_beg).count())
+ ", Time: " + std::to_string(std::chrono::duration<float, std::milli>(t_end - t_beg).count()) + "ms",
cv::Point(20, 40), 1, 2, cv::Scalar(255, 0, 255), 2);
imshow("讀取視訊", frame);
cv::waitKey(1); //延時30
frame = next_frame;
std::swap(requests[0], requests[1]);
}
cv::destroyAllWindows();
return;
}
文章中已經提供了全部程式碼檔以及模型的獲取方式,如果您還有任何疑問,可以下載現成的檔資源。為了方便開發者使用,我們將 程式碼、測試視訊與模型檔以及計畫所需依賴 打包,釋出到了CSDN上,大家可以根據自己需求進行下載,下載連結:
https://download.csdn.net/download/Grape_yan/89473342
6. 時間測試
最後我們對推理時間進行了測試,分別測試了量化前後模型在同步推理以及異步推理的表現,如下表所示:
Model | API | PrePocess | Inference | PostProcess | Total | FPS |
---|---|---|---|---|---|---|
Float 32 | Sync | 9.72 ms | 65.44 ms | 0 ms | 75.16 ms | 13.30 |
Float 32 | Async | 15.29 ms | 40.52 ms | 0 ms | 55.81 ms | 17.92 |
INT 8 | Sync | 15.25 ms | 25.64 ms | 0 ms | 43.89 ms | 22.78 |
INT 8 | Async | 17.84 ms | 1.86 ms | 0 ms | 19.70 ms | 50.76 |
透過該表可以看出,量化前後,模型推理速度提升了1.5倍,並且透過使用異步推理介面,經過IN8量化後的模型,在本裝置上可以實作50FPS的推理速度,與為量化的模型相比,速度提升了2.8倍。透過異步介面,我們可以在CPU下輕松的實作視訊推理。
7. 總結
在本文中,我們演示了如何使用Intel OpenVINO™ C++ API 部署YOLOv10目標檢測模型,並使用 OpenVINO™ 異步推理介面實作模型推理加速。 最後如果各位開發者在使用中有任何問題,歡迎大家與我聯系。
點選藍字 關註我們