项目背景
本项目基于 ONNX Runtime 和 OpenCV,实现了一个轻量、高效、可扩展的 YOLOv8 C++ 推理模块。它不依赖 PyTorch,可直接加载 .onnx 模型进行推理,适用于 Windows/Linux 平台,支持 CPU 与 CUDA 加速。
项目有三个文件:inference.h,inference.cpp 和 main.cpp,核心文件为 inference.cpp。

代码讲解
1. inference.cpp 注释版代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include "inference.h"
#include <regex>
#define benchmark
#define min(a,b) (((a) < (b)) ? (a) : (b))
YOLO_V8::YOLO_V8() { }
YOLO_V8::~YOLO_V8() { delete session;
#ifdef USE_CUDA
namespace Ort {
template<>
struct TypeToTensorType<half> {
static constexpr ONNXTensorElementDataType type = ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16;
};
}
#endif
template<typename T>
char* BlobFromImage(cv::Mat& iImg, T& iBlob) {
int channels = iImg.channels();
int imgHeight = iImg.rows;
int imgWidth = iImg.cols;
for (int c = 0; c < channels; c++) {
for (int h = 0; h < imgHeight; h++) {
for (int w = 0; w < imgWidth; w++) {
iBlob[c * imgWidth * imgHeight + h * imgWidth + w] = typename std::remove_pointer<T>::type((iImg.at<cv::Vec3b>(h, w)[c]) / 255.0f);
}
}
}
return RET_OK;
}
char* YOLO_V8::PreProcess(cv::Mat& iImg, std::vector<int> iImgSize, cv::Mat& oImg) {
if (iImg.channels() == 3) {
oImg = iImg.clone();
cv::cvtColor(oImg, oImg, cv::COLOR_BGR2RGB);
} else {
cv::cvtColor(iImg, oImg, cv::COLOR_GRAY2RGB);
}
switch (modelType) {
case YOLO_DETECT_V8:
case YOLO_POSE:
case YOLO_DETECT_V8_HALF:
case YOLO_POSE_V8_HALF: {
if (iImg.cols >= iImg.rows) {
resizeScales = iImg.cols / (float)iImgSize.at(0);
cv::resize(oImg, oImg, cv::Size(iImgSize.at(0), int(iImg.rows / resizeScales)));
} else {
resizeScales = iImg.rows / (float)iImgSize.at(0);
cv::resize(oImg, oImg, cv::Size(int(iImg.cols / resizeScales), iImgSize.at(1)));
}
cv::Mat tempImg = cv::Mat::zeros(iImgSize.at(0), iImgSize.at(1), CV_8UC3);
oImg.copyTo(tempImg(cv::Rect(0, 0, oImg.cols, oImg.rows)));
oImg = tempImg;
break;
}
case YOLO_CLS: {
int h = iImg.rows;
int w = iImg.cols;
int m = min(h, w);
int top = (h - m) / 2;
int left = (w - m) / 2;
cv::resize(oImg(cv::Rect(left, top, m, m)), oImg, cv::Size(iImgSize.at(0), iImgSize.at(1)));
break;
}
}
return RET_OK;
}
char* YOLO_V8::CreateSession(DL_INIT_PARAM& iParams) {
char* Ret = RET_OK;
std::regex pattern("[\u4e00-\u9fa5]");
bool result = std::regex_search(iParams.modelPath, pattern);
if (result) {
Ret = (char*)"[YOLO_V8]:Your model path is error.Change your model path without chinese characters.";
std::cout << Ret << std::endl;
return Ret;
}
try {
rectConfidenceThreshold = iParams.rectConfidenceThreshold;
iouThreshold = iParams.iouThreshold;
imgSize = iParams.imgSize;
modelType = iParams.modelType;
cudaEnable = iParams.cudaEnable;
env = Ort::Env(ORT_LOGGING_LEVEL_WARNING, "Yolo");
Ort::SessionOptions sessionOption;
if (iParams.cudaEnable) {
OrtCUDAProviderOptions cudaOption;
cudaOption.device_id = 0;
sessionOption.AppendExecutionProvider_CUDA(cudaOption);
}
sessionOption.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
sessionOption.SetIntraOpNumThreads(iParams.intraOpNumThreads);
sessionOption.SetLogSeverityLevel(iParams.logSeverityLevel);
#ifdef _WIN32
int ModelPathSize = MultiByteToWideChar(CP_UTF8, 0, iParams.modelPath.c_str(), static_cast<int>(iParams.modelPath.length()), nullptr, 0);
wchar_t* wide_cstr = new wchar_t[ModelPathSize + 1];
MultiByteToWideChar(CP_UTF8, 0, iParams.modelPath.c_str(), static_cast<int>(iParams.modelPath.length()), wide_cstr, ModelPathSize);
wide_cstr[ModelPathSize] = L'\0';
const wchar_t* modelPath = wide_cstr;
#else
const char* modelPath = iParams.modelPath.c_str();
#endif
session = new Ort::Session(env, modelPath, sessionOption);
Ort::AllocatorWithDefaultOptions allocator;
size_t inputNodesNum = session->GetInputCount();
for (size_t i = 0; i < inputNodesNum; i++) {
Ort::AllocatedStringPtr input_node_name = session->GetInputNameAllocated(i, allocator);
char* temp_buf = new char[50];
strcpy(temp_buf, input_node_name.get());
inputNodeNames.push_back(temp_buf);
}
size_t OutputNodesNum = session->GetOutputCount();
for (size_t i = 0; i < OutputNodesNum; i++) {
Ort::AllocatedStringPtr output_node_name = session->GetOutputNameAllocated(i, allocator);
char* temp_buf = new char[10];
strcpy(temp_buf, output_node_name.get());
outputNodeNames.push_back(temp_buf);
}
options = Ort::RunOptions{ nullptr };
WarmUpSession();
return RET_OK;
} catch (const std::exception& e) {
const char* str1 = "[YOLO_V8]:";
const char* str2 = e.what();
std::string result = std::string(str1) + std::string(str2);
char* merged = new char[result.length() + 1];
strcpy(merged, result.c_str());
std::cout << merged << std::endl;
delete[] merged;
return (char*)"[YOLO_V8]:Create session failed.";
}
}
char* YOLO_V8::RunSession(cv::Mat& iImg, std::vector<DL_RESULT>& oResult) {
#ifdef benchmark
clock_t starttime_1 = clock();
#endif
char* Ret = RET_OK;
cv::Mat processedImg;
PreProcess(iImg, imgSize, processedImg);
if (modelType < 4) {
float* blob = new float[processedImg.total() * 3];
BlobFromImage(processedImg, blob);
std::vector<int64_t> inputNodeDims = { 1, 3, imgSize.at(0), imgSize.at(1) };
TensorProcess(starttime_1, iImg, blob, inputNodeDims, oResult);
} else {
#ifdef USE_CUDA
half* blob = new half[processedImg.total() * 3];
BlobFromImage(processedImg, blob);
std::vector<int64_t> inputNodeDims = { 1,3,imgSize.at(0),imgSize.at(1) };
TensorProcess(starttime_1, iImg, blob, inputNodeDims, oResult);
#endif
}
return Ret;
}
template<typename N>
char* YOLO_V8::TensorProcess(clock_t& starttime_1, cv::Mat& iImg, N& blob, std::vector<int64_t>& inputNodeDims, std::vector<DL_RESULT>& oResult) {
Ort::Value inputTensor = Ort::Value::CreateTensor<typename std::remove_pointer<N>::type>(
Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU), blob, 3 * imgSize.at(0) * imgSize.at(1), inputNodeDims.data(), inputNodeDims.size());
#ifdef benchmark
clock_t starttime_2 = clock();
#endif
auto outputTensor = session->Run(options, inputNodeNames.data(), &inputTensor, 1, outputNodeNames.data(), outputNodeNames.size());
#ifdef benchmark
clock_t starttime_3 = clock();
#endif
Ort::TypeInfo typeInfo = outputTensor.front().GetTypeInfo();
auto tensor_info = typeInfo.GetTensorTypeAndShapeInfo();
std::vector<int64_t> outputNodeDims = tensor_info.GetShape();
auto output = outputTensor.front().GetTensorMutableData<typename std::remove_pointer<N>::type>();
delete[] blob;
switch (modelType) {
case YOLO_DETECT_V8:
case YOLO_DETECT_V8_HALF: {
int signalResultNum = outputNodeDims[1];
int strideNum = outputNodeDims[2];
std::vector<int> class_ids;
std::vector<float> confidences;
std::vector<cv::Rect> boxes;
cv::Mat rawData;
if (modelType == YOLO_DETECT_V8) {
rawData = cv::Mat(signalResultNum, strideNum, CV_32F, output);
} else {
rawData = cv::Mat(signalResultNum, strideNum, CV_16F, output);
rawData.convertTo(rawData, CV_32F);
}
rawData = rawData.t();
float* data = (float*)rawData.data;
for (int i = 0; i < strideNum; ++i) {
float* classesScores = data + 4;
cv::Mat scores(1, this->classes.size(), CV_32FC1, classesScores);
cv::Point class_id;
double maxClassScore;
cv::minMaxLoc(scores, 0, &maxClassScore, 0, &class_id);
if (maxClassScore > rectConfidenceThreshold) {
confidences.push_back(maxClassScore);
class_ids.push_back(class_id.x);
float x = data[0];
float y = data[1];
float w = data[2];
float h = data[3];
int left = int((x - 0.5 * w) * resizeScales);
int top = int((y - 0.5 * h) * resizeScales);
int width = int(w * resizeScales);
int height = int(h * resizeScales);
boxes.push_back(cv::Rect(left, top, width, height));
}
data += signalResultNum;
}
std::vector<int> nmsResult;
cv::dnn::NMSBoxes(boxes, confidences, rectConfidenceThreshold, iouThreshold, nmsResult);
for (int i = 0; i < nmsResult.size(); ++i) {
int idx = nmsResult[i];
DL_RESULT result;
result.classId = class_ids[idx];
result.confidence = confidences[idx];
result.box = boxes[idx];
oResult.push_back(result);
}
#ifdef benchmark
clock_t starttime_4 = clock();
double pre_process_time = (double)(starttime_2 - starttime_1) / CLOCKS_PER_SEC * 1000;
double process_time = (double)(starttime_3 - starttime_2) / CLOCKS_PER_SEC * 1000;
double post_process_time = (double)(starttime_4 - starttime_3) / CLOCKS_PER_SEC * 1000;
if (cudaEnable) {
std::cout << "[YOLO_V8(CUDA)]: " << pre_process_time << "ms pre-process, " << process_time << "ms inference, " << post_process_time << "ms post-process." << std::endl;
} else {
std::cout << "[YOLO_V8(CPU)]: " << pre_process_time << "ms pre-process, " << process_time << "ms inference, " << post_process_time << "ms post-process." << std::endl;
}
#endif
break;
}
case YOLO_CLS:
case YOLO_CLS_HALF: {
cv::Mat rawData;
if (modelType == YOLO_CLS) {
rawData = cv::Mat(1, this->classes.size(), CV_32F, output);
} else {
rawData = cv::Mat(1, this->classes.size(), CV_16F, output);
rawData.convertTo(rawData, CV_32F);
}
float* data = (float*)rawData.data;
DL_RESULT result;
for (int i = 0; i < this->classes.size(); i++) {
result.classId = i;
result.confidence = data[i];
oResult.push_back(result);
}
break;
}
default:
std::cout << "[YOLO_V8]: " << "Not support model type." << std::endl;
}
return RET_OK;
}
char* YOLO_V8::WarmUpSession() {
clock_t starttime_1 = clock();
cv::Mat iImg = cv::Mat(cv::Size(imgSize.at(0), imgSize.at(1)), CV_8UC3);
cv::Mat processedImg;
PreProcess(iImg, imgSize, processedImg);
if (modelType < 4) {
float* blob = new float[iImg.total() * 3];
BlobFromImage(processedImg, blob);
std::vector<int64_t> YOLO_input_node_dims = { 1, 3, imgSize.at(0), imgSize.at(1) };
Ort::Value input_tensor = Ort::Value::CreateTensor<float>(
Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU), blob, 3 * imgSize.at(0) * imgSize.at(1), YOLO_input_node_dims.data(), YOLO_input_node_dims.size());
auto output_tensors = session->Run(options, inputNodeNames.data(), &input_tensor, 1, outputNodeNames.data(), outputNodeNames.size());
delete[] blob;
clock_t starttime_4 = clock();
double post_process_time = (double)(starttime_4 - starttime_1) / CLOCKS_PER_SEC * 1000;
if (cudaEnable) {
std::cout << "[YOLO_V8(CUDA)]: " << "Cuda warm-up cost " << post_process_time << " ms. " << std::endl;
}
} else {
#ifdef USE_CUDA
half* blob = new half[iImg.total() * 3];
BlobFromImage(processedImg, blob);
std::vector<int64_t> YOLO_input_node_dims = { 1,3,imgSize.at(0),imgSize.at(1) };
Ort::Value input_tensor = Ort::Value::CreateTensor<half>(Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU), blob, 3 * imgSize.at(0) * imgSize.at(1), YOLO_input_node_dims.data(), YOLO_input_node_dims.size());
auto output_tensors = session->Run(options, inputNodeNames.data(), &input_tensor, 1, outputNodeNames.data(), outputNodeNames.size());
delete[] blob;
clock_t starttime_4 = clock();
double post_process_time = (double)(starttime_4 - starttime_1) / CLOCKS_PER_SEC * 1000;
if (cudaEnable) {
std::cout << "[YOLO_V8(CUDA)]: " << "Cuda warm-up cost " << post_process_time << " ms. " << std::endl;
}
#endif
}
return RET_OK;
}
2. inference.cpp 代码框架讲解:
(1)整体思路
- CreateSession:加载 ONNX 模型 → 创建
Ort::Session(可选 CUDA)→ 记录输入/输出节点名 → 预热一次。
- RunSession:对输入图像做预处理(按模型种类)→ 组装 NCHW 的输入张量 → 推理 → 按模型类型做后处理(目标框/NMS 或 分类)。
- WarmUpSession:构造一张空图,走一遍预处理 + 推理,避免首次调用慢。
- 其余是工具函数/模板:
BlobFromImage 把 cv::Mat 转为连续内存(按通道排布),TensorProcess 模板负责真正的 session->Run 和解析输出。
(2)文件头与杂项
#define benchmark:打开后会打印前处理、推理、后处理耗时。
#define min(a,b):自定义了一个 min 宏(不建议,容易和 std::min 冲突)。
- 构/析:构造函数空;析构里只
delete session;(注意:没释放输入/输出节点名的 new char[],有内存泄露,下文详述)。
FP16 支持(仅在 USE_CUDA 下)
template<>
struct TypeToTensorType<half> { ... };
让 ORT 能识别 half 类型建张量。
(3)BlobFromImage(Mat → NCHW 浮点数组)
template<typename T>
char* BlobFromImage(cv::Mat& iImg, T& iBlob)
- 输入:BGR/RGB 的
cv::Mat(8UC3)。
- 输出:把像素按 通道优先 NCHW 排列到一段连续内存
iBlob(在外面 new 出来的)。
- 逐像素取值 /255.0f 归一化;不做减均值/除方差。
- 注意:使用
iImg.at<cv::Vec3b>(h,w)[c],默认假定图像是 8bit 3 通道;理论上传灰度图会越界,不过在 PreProcess 里已经统一成 3 通道 RGB。
(4)PreProcess(根据模型类型做图像预处理)
char* YOLO_V8::PreProcess(cv::Mat& iImg, std::vector<int> iImgSize, cv::Mat& oImg)
- 先保证输入为 RGB:BGR→RGB;灰度→RGB。
- 分两类:
- 检测/姿态(
YOLO_DETECT_V8、YOLO_POSE,以及它们的 FP16 版本)
- 计算
resizeScales = 原边/目标边,按长边对齐缩放:
- 若宽≥高:目标宽=
iImgSize[0],高按比例;反之相同。
- 然后把缩放后的图复制到一个 全 0 的大图(
iImgSize[0]×iImgSize[1])的左上角(0,0)。
- 这相当于 Letterbox,但只在右边或下边补零(不是居中)。后处理时按同一
resizeScales 去恢复坐标。
- 分类(
YOLO_CLS)
- CenterCrop:从中间裁一个正方形(边长=min(h,w)),再缩放到
iImgSize。
很多实现会把 Letterbox 居中,这里是 左上角对齐,对应地后处理没有加偏移,只乘了 resizeScales,保持了一致。
(5)CreateSession(会话创建与参数)
char* YOLO_V8::CreateSession(DL_INIT_PARAM& iParams)
- 校验 模型路径不能含中文(用正则查
[一-龥])。
- 记录阈值、输入尺寸、模型类型、是否启用 CUDA、线程数、日志等级等。
Ort::Env + Ort::SessionOptions:
- 若
cudaEnable==true:设置 OrtCUDAProviderOptions。
SetGraphOptimizationLevel(ORT_ENABLE_ALL)。
SetIntraOpNumThreads(...)。
- Windows 下把 utf-8 路径转宽字符;非 Windows 直接用
char*。
session = new Ort::Session(env, modelPath, sessionOption);
- 获取 I/O 节点名:调用
GetInputNameAllocated / GetOutputNameAllocated,拷贝到 new char[] 再 push_back 保存。
WarmUpSession() 预热。
- 发生异常会返回统一的错误文本。
(6)RunSession(一次完整推理)
char* YOLO_V8::RunSession(cv::Mat& iImg, std::vector<DL_RESULT>& oResult)
- 计时(可选)。
PreProcess 得到 processedImg。
- 根据
modelType 选择 FP32 或(在 USE_CUDA 编译时)FP16 的 blob:
inputNodeDims = {1, 3, imgH, imgW}(NCHW)。
- 调
TensorProcess(...) 执行推理与后处理。
如果把 modelType 设到 FP16 路径,但工程没有定义 USE_CUDA,这段代码不会给出替代逻辑(编译能过,但不会走 FP16 分支),要保证两者匹配。
(7)TensorProcess(核心:Run + 解码输出)
template<typename N>
char* YOLO_V8::TensorProcess(clock_t& starttime_1, cv::Mat& iImg, N& blob, std::vector<int64_t>& inputNodeDims, std::vector<DL_RESULT>& oResult)
session->Run(...) 得到 outputTensor(只取第一个输出)。
- 取形状
outputNodeDims,拿到数据指针 output。
- 模型类型分支:
- 检测(YOLO v8 Detect)
- 读取输出形状:
[batch, 84, 8400](示例),其中 84=4(bbox)+num_classes。
- 遍历每一行:
data[0..3] 是 cx, cy, w, h;data[4..] 是各类分数。
- 取最大类分数,若
> rectConfidenceThreshold:
- 计算左上角与宽高,仅用
resizeScales 等比放大回原图坐标(因为前处理是左上填充,不需要加偏移)。
- 存入
boxes/confidences/class_ids。
cv::dnn::NMSBoxes(..., iouThreshold) 做 NMS,组装 DL_RESULT 返回。
- 若
benchmark:打印三段耗时(pre/infer/post)。
- 分类(YOLO v8 Cls)
- 输出 shape 就是一行
num_classes,FP16 时先 convertTo(CV_32F)。
- 逐类把
(id, score) 推到 oResult(是否取 top-k 交由调用方自己处理)。
- 其它类型:打印不支持。
- 释放:
delete[] blob;(输入缓存释放及时)。
代码把原始数据构成 cv::Mat(signalResultNum, strideNum, CV_32F/16F, output),再 转置 成 (8400, 84):
将输出 84x8400 的矩阵转置为 8400x84,便于按行处理每个检测框。
创建输入张量:
Ort::Value::CreateTensor<typename std::remove_pointer<N>::type>(..., blob, 3*H*W, dims...)
N 是 float* 或 half*,通过 remove_pointer 得到元素类型。
(8)WarmUpSession(预热)
- 构造一张
imgSize 大小的三通道黑图;跑一次 PreProcess +(按模型类型选择 FP32/FP16)+ session->Run。
- 打印 CUDA 预热耗时(只有
cudaEnable=true 时打印)。
- 作用:避免首次真实推理时的内存分配、内核 JIT 等导致的抖动。
(9)关键参数/结构
DL_INIT_PARAM(在头文件里定义):包含
rectConfidenceThreshold(置信度阈值)
iouThreshold
imgSize(例如 {640,640})
modelType(枚举:Detect/Cls/Pose 及 FP16 版本)
cudaEnable
- 线程数/日志等级/
modelPath 等
DL_RESULT:后处理输出
- 检测:
classId, confidence, box
- 分类:
classId, confidence(每类一条)
3. inference.h 代码:
#pragma once
#define RET_OK nullptr
#ifdef _WIN32
#include <Windows.h>
#include <direct.h>
#include <io.h>
#endif
#include <string>
#include <vector>
#include <cstdio>
#include <opencv2/opencv.hpp>
#include "onnxruntime_cxx_api.h"
#ifdef USE_CUDA
#include <cuda_fp16.h>
#endif
enum MODEL_TYPE {
YOLO_DETECT_V8 = 1,
YOLO_POSE = 2,
YOLO_CLS = 3,
YOLO_DETECT_V8_HALF = 4,
YOLO_POSE_V8_HALF = 5,
YOLO_CLS_HALF = 6
};
typedef struct _DL_INIT_PARAM {
std::string modelPath;
MODEL_TYPE modelType = YOLO_DETECT_V8;
std::vector<int> imgSize = { 640, };
rectConfidenceThreshold = ;
iouThreshold = ;
keyPointsNum = ;
cudaEnable = ;
logSeverityLevel = ;
intraOpNumThreads = ;
} DL_INIT_PARAM;
{
classId;
confidence;
cv::Rect box;
std::vector<cv::Point2f> keyPoints;
} DL_RESULT;
{
:
();
~();
:
;
;
;
;
;
std::vector<std::string> classes{};
:
Ort::Env env;
Ort::Session* session;
cudaEnable;
Ort::RunOptions options;
std::vector< *> inputNodeNames;
std::vector< *> outputNodeNames;
MODEL_TYPE modelType;
std::vector<> imgSize;
rectConfidenceThreshold;
iouThreshold;
resizeScales;
};
4. main.cpp 注释版代码:
#include <iostream>
#include <iomanip>
#include "inference.h"
#include <filesystem>
#include <fstream>
#include <random>
void Detector(YOLO_V8*& p) {
std::filesystem::path current_path = std::filesystem::current_path();
std::filesystem::path imgs_path = current_path / "images";
for (auto& i : std::filesystem::directory_iterator(imgs_path))
{
if (i.path().extension() == ".jpg" || i.path().extension() == || i.().() == ) {
std::string img_path = i.().();
cv::Mat img = cv::(img_path);
std::vector<DL_RESULT> res;
p->(img, res);
(& re : res) {
;
;
cv::(img, re.box, color, );
confidence = ( * re.confidence) / ;
std::cout << std::fixed << std::();
std::string label = p->classes[re.classId] + + std::(confidence).(, std::(confidence).() - );
cv::(
img, cv::(re.box.x, re.box.y - ), cv::(re.box.x + label.() * , re.box.y), color, cv::FILLED
);
cv::(
img, label, cv::(re.box.x, re.box.y - ), cv::FONT_HERSHEY_SIMPLEX, , cv::(, , ),
);
}
std::cout << << std::endl;
cv::(, img);
cv::();
cv::();
}
}
}
{
std::filesystem::path current_path = std::filesystem::();
std::filesystem::path imgs_path = current_path;
std::random_device rd;
;
;
(& i : std::filesystem::(imgs_path)) {
(i.().() == || i.().() == ) {
std::string img_path = i.().();
cv::Mat img = cv::(img_path);
std::vector<DL_RESULT> res;
* ret = p->(img, res);
positionY = ;
( i = ; i < res.(); i++) {
r = (gen);
g = (gen);
b = (gen);
cv::(img, std::(i) + , cv::(, positionY), cv::FONT_HERSHEY_SIMPLEX, , cv::(b, g, r), );
cv::(img, std::(res.(i).confidence), cv::(, positionY), cv::FONT_HERSHEY_SIMPLEX, , cv::(b, g, r), );
positionY += ;
}
cv::(, img);
cv::();
cv::();
}
}
}
{
;
(!file.()) {
std::cerr << << std::endl;
;
}
std::string line;
std::vector<std::string> lines;
(std::(file, line)) {
lines.(line);
}
std:: start = ;
std:: end = ;
(std:: i = ; i < lines.(); i++) {
(lines[i].() != std::string::npos) {
start = i + ;
} (start > && lines[i].() == std::string::npos) {
end = i;
;
}
}
std::vector<std::string> names;
(std:: i = start; i < end; i++) {
;
std::string name;
std::(ss, name, );
std::(ss, name);
names.(name);
}
p->classes = names;
;
}
{
YOLO_V8* yoloDetector = YOLO_V8;
yoloDetector->classes = { };
DL_INIT_PARAM params;
params.rectConfidenceThreshold = ;
params.iouThreshold = ;
params.modelPath = ;
params.imgSize = { , };
params.cudaEnable = ;
params.modelType = YOLO_DETECT_V8;
params.modelType = YOLO_DETECT_V8;
params.cudaEnable = ;
yoloDetector->(params);
(yoloDetector);
yoloDetector;
}
{
YOLO_V8* yoloDetector = YOLO_V8;
std::string model_path = ;
(yoloDetector);
DL_INIT_PARAM params{ model_path, YOLO_CLS, {, } };
yoloDetector->(params);
(yoloDetector);
}
{
();
;
}
推理结果如图所示

环境配置
在本项目中,既可以选择 CPU 推理,也可以选择 GPU(CUDA)推理。
两种方式的配置方法略有不同,下面分别说明。
1. CPU 推理环境
如果只打算在 CPU 上运行,那么只需要在 Visual Studio 中正确配置好 OpenCV + ONNXRuntime 的依赖环境即可。
另外,还需要将 D:\onnxruntime\lib 下的部分 DLL 文件复制到 ...\x64\Debug 文件夹中。如下图:

这是因为:
- VS 在调试运行时,会默认到 可执行文件所在目录(如
x64\Debug 或 x64\Release)寻找依赖库;
- 如果 DLL 只存在于
D:\onnxruntime\lib 而没有被拷贝过来,程序运行时就会提示 缺少某某 DLL,无法启动;
- 把 DLL 复制到输出目录,就能确保可执行文件能在运行时直接加载到所需的动态链接库。
完成配置后,程序会自动调用 CPU 进行推理,控制台会显示类似下面的信息:

此时无需额外操作,就能正常跑通推理与检测,适合在没有 NVIDIA GPU 的环境中使用。
2. GPU 推理环境(CUDA 加速)
如果希望利用 GPU 进行加速,则需要在 CPU 的环境配置基础上进行以下额外步骤:
- 启用 CUDA 宏
打开 inference.h 头文件,在文件开头加入:

此时直接运行会报错,报错内容是找不到 #include <cuda_fp16.h>,此时需要在 Visual Studio 项目属性中,正确添加 CUDA 的 Include 目录 和 Lib 目录。
此时生成依旧会报错,显示Could not locate zlibwapi.dll. Please make sure it is in your library path!
这是因为 OpenCV 在运行时依赖 zlibwapi.dll,但系统中找不到。
解决方法:需要将 zlibwapi.dll 下载并放置到 c:\windows\system32 下面。
重新运行程序,皆可以正常运行,命令行也显示启用 GPU 了。
