通过C/C++语言能够实现深度学习模型的高效部署。在众多部署框架中,本文采用OpenCV结合ONNX Runtime的方案来部署YOLOv8目标检测模型。其中,ONNX Runtime作为核心推理引擎,而OpenCV则主要负责图像读取等辅助功能。
文章目录
前言
OpenCV(Open Source Computer Vision Library)集成了数百种先进的计算机视觉算法,涵盖图像处理、特征检测、目标跟踪及人脸识别等核心功能。该库支持包括C++、Python和Java在内的多种编程语言,并具备出色的跨平台兼容性,可在Windows、Linux、macOS、Android和iOS等主流操作系统上稳定运行。
ONNX Runtime(Open Neural Network Exchange Runtime)是一款开源的高性能推理引擎,专门用于执行 ONNX模型。作为机器学习模型的开放标准格式,ONNX 为模型交换提供了统一规范。ONNX Runtime 通过提供一致的 API 接口,实现了跨硬件平台和操作系统的无缝部署。
基于 OpenCV 和 ONNX Runtime 的目标检测流程主要包含以下步骤:
- 图像加载与预处理:通过 OpenCV 读取目标图像,并根据模型需求进行尺寸调整、归一化等预处理操作。
- 模型加载:使用 ONNX Runtime 加载预训练的 ONNX 格式目标检测模型,为推理做好准备。
- 模型推理:将预处理后的图像数据输入模型,利用 ONNX Runtime 进行推理计算,获取初步检测结果。
- 结果后处理:借助 OpenCV 对检测结果进行可视化处理,包括绘制目标边界框、添加类别标签等操作。
一、环境准备
- 系统:Win10;
- IDE:Visual Studio 2019;
- C++标准:>=C++17
- opencv4.9.0:https://opencv.org/releases/
- onnxruntime1.15.1(本文用的CPU版):https://github.com/microsoft/onnxruntime/releases


1.1 yolov8推理源码
获取yolov8代码:github:
https://github.com/ultralytics/ultralytics/tree/main/examples/YOLOv8-ONNXRuntime-CPP
或者gitcode(推荐,下载速度更快):
https://gitcode.com/gh_mirrors/ul/ultralytics/tree/main/examples/YOLOv8-ONNXRuntime-CPP?utm_source=csdn_github_accelerator&isLogin=1
重点是这三个文件:
- main.cpp
- inference.cpp
- inference.h
1.2 环境变量添加
不管是opencv还是onnxruntime,其本质都是给用户提供include和lib去使用。我这里将opencv和onnxruntime分别解压到以下文件夹里
D:\opencv
D:\onnxruntime

下载好opencv和onnxruntime后,将其加入到环境变量中。右键/设置 此电脑–》属性–》高级系统设置–》高级–》环境变量,将onnxruntime文件夹里include/lib以及opencv文件夹下…\build\x64\vc16添加进环境变量。
D:\onnxruntime\include
D:\onnxruntime\lib
D:\opencv\build\x64\vc16\bin
D:\opencv\build\x64\vc16\lib
二、项目属性
2.1 新建项目
打开VS2019创建一个空项目,命名为“yolov8”。将上面下载的yolov8中推理源码main.cpp、inference.cpp、inference.h复制到项目文件夹下,方便下一步添加进项目里:

然后将这三个文件添加进项目里:在相应文件夹上右键–》添加–》现有项,将代码添加进项目中

添加后是这样:

2.2 配置项目属性(opencv+onnxruntime)
然后重点来了,打开项目属性:(或者直接右击目标项目,选择属性)。在配置前一定确保配置在dubug、x64模式下!!!(也可以配置为release,但也需要在x64模式下)。
2.2.1 C++语言标准
在常规-》C++语言标准中修改为C++17(>=17就行)

2.2.2 附加包含目录
在C/C++栏–》常规中选择“附加包含项目”,将opencv和onnxruntime中相关文件添加进去:
D:\onnxruntime\include
D:\opencv\build\include\opencv2
D:\opencv\build\include

2.2.3 附加库目录
接着在链接器–》常规–》附加库目录中将opencv和onnxruntime的‘“lib”文件夹添加进去
D:\opencv\build\x64\vc16\lib
D:\onnxruntime\lib

2.2.4 附加依赖项
接着在链接器–》输入–》附加依赖项中将添加以下内容
opencv_world490d.lib
onnxruntime.lib
onnxruntime_providers_shared.lib
这三个lib文件在“D:\onnxruntime\lib”和“D:\opencv\build\x64\vc16\lib”
也可以直接复制以下代码:注意,opencv_world490d.lib中的"490"表示OpenCV版本为4.9.0,请根据您的实际版本修改该数字;后缀"d"代表调试模式(Debug),若为发布模式(Release),请使用opencv_world490.lib。

2.2.5 生成解决方案文件
运行时一定将将模式设置为和所配置的项目属性一致,我这里就是debug、x64(否则会找不到对应的头文件)
到这里打开main.cpp文件,执行编译操作以生成解决方案文件。(都没有报错不用管,这一步旨在创建一个…\x64\Debug文件夹)

然后将“D:\onnxruntime\lib下的DLL文件全部复制到…\x64\Debug文件夹中

至此已经完成了项目属性的配置。掌握这一流程至关重要,因为在Visual Studio中配置其他库的步骤与此基本一致。
三、YOLOv8源代码修改
3.1 inference.cpp
inference.cpp:
第一行加上#define _CRT_SECURE_NO_WARNINGS 1(否则会出现strcpy报警告,程序无法运行!!!)
#define _CRT_SECURE_NO_WARNINGS 1
两行飘红的字符串前加上(char*):
Ret = (char*) “[YOLO_V8]:Your model path is error.Change your model path without chinese characters.”;(102行附近)
Ret = (char*)"[YOLO_V8]:Your model path is error.Change your model path without chinese characters.";
return (char*) “[YOLO_V8]:Create session failed.”;(166行附近)
return (char*)"[YOLO_V8]:Create session failed.";
3.2 opencv测试
在 main.cpp 文件中,通过编写测试代码来验证上述配置是否成功。测试代码只需在 int main() 函数中实现,无需修改其他部分。图片正常显示,说明配置成功;若异常,再检查一下自己的配置流程。
int main()
{
//DetectTest();
//ClsTest();
cv::Mat image = cv::imread("D:\\test.png");//输入测试图片
cv::imshow("test", image);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}

3.3 main.cpp修改
由于本文是做目标检测任务,因此仅需调整与目标检测相关的函数。
- void Detector(YOLO_V8*& p)
- void DetectTest()
- int main()
void Detector(YOLO_V8*& p)
std::filesystem::path imgs_path:将其修改为待检测图片所在的文件夹;
std::string output_folder:检测后的图片输出位置;
在函数末尾添加保存图片的代码片段。
void Detector(YOLO_V8*& p) {
/*std::filesystem::path current_path = std::filesystem::current_path();*/
std::filesystem::path imgs_path = "D:/result/test";//这里一定输入待检测图片所在的文件夹!!!
std::string output_folder = "D:/run"; // 输出文件夹路径
std::filesystem::create_directories(output_folder);
for (auto& i : std::filesystem::directory_iterator(imgs_path))
{
if (i.path().extension() == ".jpg" || i.path().extension() == ".png" || i.path().extension() == ".jpeg")
{
std::string img_path = i.path().string();
cv::Mat img = cv::imread(img_path);
std::vector<DL_RESULT> res;
p->RunSession(img, res);
for (auto& re : res)
{
cv::RNG rng(cv::getTickCount());
cv::Scalar color(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
cv::rectangle(img, re.box, color, 3);
float confidence = floor(100 * re.confidence) / 100;
std::cout << std::fixed << std::setprecision(2);
std::string label = p->classes[re.classId] + " " +
std::to_string(confidence).substr(0, std::to_string(confidence).size() - 4);
cv::rectangle(
img,
cv::Point(re.box.x, re.box.y - 25),
cv::Point(re.box.x + label.length() * 15, re.box.y),
color,
cv::FILLED
);
cv::putText(
img,
label,
cv::Point(re.box.x, re.box.y - 5),
cv::FONT_HERSHEY_SIMPLEX,
0.75,
cv::Scalar(0, 0, 0),
2
);
}
std::cout << "Press any key to exit" << std::endl;
cv::imshow("Result of Detection", img);
std::string save_path = output_folder + "/" + i.path().filename().string();
cv::imwrite(save_path, img);
cv::waitKey(0);
cv::destroyAllWindows();
img.release();
}
}
}
void DetectTest()
函数中的ReadCocoYaml(yoloDetector)注释掉,改为:
yoloDetector->classes = { “person”};
yoloDetector->classes = { "person"};
//可根据实际需求自定义类别设置,本文仅需进行人物识别以验证效果即可。
函数中params.modelPath修改为onnx模型的路径
函数末尾加上delete yoloDetector;
void DetectTest()
{
YOLO_V8* yoloDetector = new YOLO_V8;
/*ReadCocoYaml(yoloDetector);*/
yoloDetector->classes = { "person" };
DL_INIT_PARAM params;
params.rectConfidenceThreshold = 0.1;
params.iouThreshold = 0.5;
params.modelPath = "D:/result/yolov8n.onnx";
params.imgSize = { 640, 640 };
#ifdef USE_CUDA
params.cudaEnable = true;
// GPU FP32 inference
params.modelType = YOLO_DETECT_V8;
// GPU FP16 inference
//Note: change fp16 onnx model
//params.modelType = YOLO_DETECT_V8_HALF;
#else
// CPU inference
params.modelType = YOLO_DETECT_V8;
params.cudaEnable = false;
#endif
yoloDetector->CreateSession(params);
Detector(yoloDetector);
delete yoloDetector;
}
int main()
int main()
{
DetectTest();
return 0;
}
main.cpp 文件中包含的以下函数与目标检测功能无关,删除后不会影响检测结果。
- void Classifier(YOLO_V8*& p)
- int ReadCocoYaml(YOLO_V8*& p)
- void ClsTest()
3.4 运行检测
完成代码修改后即可开始检测。
- 若使用CPU,直接编译运行即可。
- 如需在GPU上运行,请在main.cpp文件首行添加#define USE_CUDA指令。
检测结果将统一保存在指定目录下,默认路径为std::string output_folder = “D:/run”。

四、一些可能遇到的问题和解决方法
经过实际测试,按照上述步骤配置程序可以确保正常运行。然而,某些容易被忽视的细节仍可能导致程序出现错误。
4.1 filesystem飘红
文件系统类filesystem是从C++17开始引入的。VS2019默认标准是C++14,因此需要将标准修改为17及以上。见2.1节。
4.2 应用程序无法正常启动
编译的时候正常,但在运行时出现“应用程序无法正常启动”。出现这个问题基本是因为缺少DLL文件。检查是否将D:\onnxruntime\lib**下的DLL文件全部复制到…\x64\Debug文件夹中,见2.4节。这样问题基本就能解决。

4.3 Debug Error
Debug Error 的出现通常源于文件路径错误或包含中文字符。建议仔细核对文件路径的准确性,并将文件名统一改为英文格式。

4.4 头文件报错
检查运行时的模式(Debug,x64)是否和配置的项目属性一致,见第2节。