【OpenVINO™】YOLOv10在CPU上也能实现50+FPS推理—使用OpenVINO C++部署YOLOv10
英特尔发行版 OpenVINO 工具套件基于 oneAPI 而开发,可以加快高性能计算机视觉和深度学习视觉应用开发速度工具套件,适用于从边缘到云的各种英特尔平台上,帮助用户更快地将更准确的真实世界结果部署到生产系统中。YOLOv10是清华大学研究人员近期提出的一种实时目标检测方法,通过消除NMS、优化模型架构和引入创新模块等策略,在保持高精度的同时显著降低了计算开销,为实时目标检测领域带来了新的突破。
在本文中,我们将演示如何使用Intel OpenVINO C++ API 部署YOLOv10目标检测模型,并使用 OpenVINO 异步推理接口实现模型推理加速。下面看一下YOLOv10模型在OpenVINO上的运行效果吧:
【B站】YOLOv10在CPU上也能轻松实现50+FPS推理—使用OpenVINO C++部署YOLOv10实现异步推理
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 进行推理加速,并使用OpenVINONNCF 工具对模型进行一个INT8量化。量化的详细流程可以参考下面notebooks,该notebooks记录了YOLOv10使用OpenVINO量化的详细流程,链接如下所示:
openvino_notebooks/notebooks/yolov10-optimization
模型量化完成后,我们对比了一下量化前后模型变化,如下图所示:
4. 定义YOLOv10 Process
4.1 数据预处理
数据预处理此处通过OpenCV实现,将输入的图片数据转为模型需要的数据情况,代码如下所示:
void pre_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 结果后处理
首先此处定义了一个结果类:
struct DetResult {
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;
}
最后为了让结果可视化,定义了结果绘制方法,代码如下所示:
void draw_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 基本推理实现
首先实现一下常规的同步推理代码,如下面所示:
void yolov10_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的推理,但是如果需要处理其他业务将会很难实现。因此为了提升推理速度,我们采用异步推理技术,实现代码如下所示:
void yolov10_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 异步推理接口实现模型推理加速。 最后如果各位开发者在使用中有任何问题,欢迎大家与我联系。
【OpenVINO™】YOLOv10在CPU上也能实现50+FPS推理—使用OpenVINO C++部署YOLOv10的更多相关文章
- YOLOv5】LabVIEW+OpenVINO让你的YOLOv5在CPU上飞起来
前言 上一篇博客给大家介绍了使用opencv加载YOLOv5的onnx模型,但我们发现使用CPU进行推理检测确实有些慢,那难道在CPU上就不能愉快地进行物体识别了吗?当然可以啦,这不LabVIEW和O ...
- LabVIEW+OpenVINO在CPU上部署新冠肺炎检测模型实战
前言 之前博客:[YOLOv5]LabVIEW+OpenVINO让你的YOLOv5在CPU上飞起来给大家介绍了在LabVIEW上使用openvino加速推理,在CPU上也能感受丝滑的实时物体识别.那我 ...
- 【翻译】借助 NeoCPU 在 CPU 上进行 CNN 模型推理优化
本文翻译自 Yizhi Liu, Yao Wang, Ruofei Yu.. 的 "Optimizing CNN Model Inference on CPUs" 原文链接: h ...
- 在英特尔 CPU 上加速 Stable Diffusion 推理
前一段时间,我们向大家介绍了最新一代的 英特尔至强 CPU (代号 Sapphire Rapids),包括其用于加速深度学习的新硬件特性,以及如何使用它们来加速自然语言 transformer 模型的 ...
- linux下将不同线程绑定到不同core和cpu上——pthread_setaffinity_np
=============================================================== linux下的单进程多线程的程序,要实现每个线程平均分配到多核cpu,主 ...
- mobile cpu上禁用alpha test的相关总结
因为,每家芯片的特性不同,根据向framebuffer写法的不同,分为tile-based的mobile cpu,如ImgTec PowerVR,ARM Mali,一部分老版本Qualcomm ...
- <转>Python 多线程的单cpu与cpu上的多线程的区别
你对Python 多线程有所了解的话.那么你对python 多线程在单cpu意义上的多线程与多cpu上的多线程有着本质的区别,如果你对Python 多线程的相关知识想有更多的了解,你就可以浏览我们的文 ...
- 限制某个进程只能在某个CPU上运行
首先可以调用GetSystemInfo查看有多少个CPU,再通过调用: BOOL WINAPI SetProcessAffinityMask( __in HANDLE hProcess, __in D ...
- 多线程程序在单核cpu与多核cpu上是怎么工作的?
转自 1.多线程在单核和多核CPU上的执行效率问题的讨论 a1: 多线程在单cpu中其实也是顺序执行的,不过系统可以帮你切换那个执行而已,其实并没有快(反而慢) 多个cpu的话就可以在两个cpu中同时 ...
- nginx——绑定 Nginx 进程到不同的 CPU 上
为什么要绑定 Nginx 进程到不同的 CPU 上 :默认情况下,Nginx 的多个进程有可能跑在某一个 CPU 或 CPU 的某一核上,导致 Nginx 进程使用硬件的资源不均,因此绑定 Nginx ...
随机推荐
- 网络拓扑—NAT内外网映射
使用Windows Server 2003 网络拓扑 Router 外网:NATIP 网段 = 192.168.17.0/24 内网:仅主机模式IP = 172.16.29.4 Client1:仅主机 ...
- 智能制造 | AIRIOT智慧工厂管理解决方案
工厂生产运转中,设备数量多,环境复杂.企业往往需要承担很高的维修.保养.备件和人力成本.传统的工厂改革遇到了诸多前所未有的挑战: 1.管理系统较多,数据隔离,系统集成困难重重: 2.大量老旧设 ...
- C#关键字 sealed
定义在自定义类上,该类就不能被继承. 定义在基类的方法上,子类就不能用override 重写该方法.
- WPF 制作高性能的透明背景异形窗口(使用 WindowChrome 而不要使用 AllowsTransparency=True)
在 WPF 中,如果想做一个背景透明的异形窗口,基本上都要设置 WindowStyle="None".AllowsTransparency="True" 这两个 ...
- docker lnmp配置
1.lnmp网络与目录规划 172.16.10.0/24 nginx:172.16.10.10 mysql:172.16.10.20 php:172.16.10.30 网站访问主目录:/wwwroot ...
- .NET开源、跨平台、使用简单的面部识别库
前言 今天给大家分享一个.NET开源(MIT License).免费.跨平台(适用于 Windows.MacOS 和 Linux ).使用简单的面部识别库:FaceRecognitionDotNet. ...
- 用 Sentence Transformers v3 训练和微调嵌入模型
Sentence Transformers 是一个 Python 库,用于使用和训练各种应用的嵌入模型,例如检索增强生成 (RAG).语义搜索.语义文本相似度.释义挖掘 (paraphrase min ...
- SELinux 基本原理
首发公号:Rand_cs SELinux 基本原理 本文讲述 SELinux 保护安全的基本原理 安全检查顺序 不废话,直接先来看张图 当我们执行系统调用的时候,会首先对某些错误情况进行检查,如果失败 ...
- 使用线程池实现为多个客户端提供Echo服务
import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; im ...
- Linux扩展篇-shell编程(八)-shell字符串截取
shell字符串截取,一般包含从指定位置和从指定字符截取. 一.从指定位置截取 1) 从字符串左边开始计数 格式: ${string: start :length} 从 string 字符串的左边第 ...