可以,但需导出时禁用NMS并手动后处理:用model.export(format="onnx", task="detect", simplify=True, opset=12)导出,输入须BGR、640×640、归一化至[0,1],输出reshape为[8400,84]后sigmoid解码,再反归一化并调用cv::dnn::NMSBoxes。

YOLOv8 的 ONNX 模型能否直接用 OpenCV DNN 加载?
可以,但必须是导出为 ONNX 格式且满足 OpenCV DNN 兼容要求的模型。OpenCV 4.8.0+ 原生支持 YOLOv8 的 ONNX 推理,但官方 cv::dnn::readNetFromONNX 无法直接加载带自定义算子(如 NonMaxSuppression)的原始导出模型——你会遇到 Unsupported op 'NonMaxSuppression' 错误。
解决路径只有一条:导出时禁用后处理,让模型只输出原始检测头(logits),把 NMS 留给 OpenCV 或自己实现。
- 用
model.export(format="onnx", dynamic=False, simplify=True, opset=12)导出(Ultralytics 8.1.0+) - 务必加
task="detect"并确保agnostic_nms=False,否则输出 shape 不匹配 - 导出后用 Netron 查看输出节点名,常见为
output0或images→output,不是boxes/scores
如何用 cv::dnn::readNetFromONNX 正确加载并预处理输入?
OpenCV DNN 对 YOLOv8 输入有硬性要求:必须是 NHWC 格式、归一化到 [0, 1]、BGR 通道顺序(注意不是 RGB)、尺寸需为 640×640 或其他被训练时指定的固定尺寸(YOLOv8 默认是 640)。
- 读取图像后调用
cv::cvtColor(img, img, cv::COLOR_BGR2RGB)→ 再手动翻转通道?错!OpenCV DNN 默认按 BGR 处理,不需转 RGB,保持原图 BGR 即可 - 缩放必须用
cv::resize(img, blob, cv::Size(640, 640), 0, 0, cv::INTER_LINEAR),不能用cv::INTER_AREA(会导致 bbox 偏移) - 构造 blob 时用
cv::dnn::blobFromImage(blob, 1.0/255.0, cv::Size(640,640), cv::Scalar(), true, false):其中第 5 个参数true表示 swapRB=false(保持 BGR),第 6 个false表示 crop=false
怎么解析 YOLOv8 ONNX 输出的 tensor 并做 NMS?
YOLOv8 导出的 ONNX 模型输出是一个形状为 [1, 84, 8400] 的 float32 tensor(以 YOLOv8n 为例):84 = 4 bbox + 80 class scores;8400 是 anchors 总数(如 80×80 + 40×40 + 20×20)。OpenCV 不会自动解码,你得手动 reshape + sigmoid + 解耦坐标。
- 先用
net.forward()获取output,再output = output.reshape(1, 84, 8400).t()(转置成 [8400, 84]) - 对 class scores 部分(索引 4~84)做
cv::expit()(即 sigmoid),再乘以置信度(索引 4)得到最终 score - box 坐标部分(索引 0~3)需反归一化:x = (x * stride + grid_x) * scale_x,y 同理;w/h 要 exp() 后乘 stride —— 这些 stride 和 grid 信息必须从模型结构反推或硬编码(YOLOv8n 三输出层 stride 分别是 8,16,32)
- OpenCV 自带
cv::dnn::NMSBoxes只接受std::vector<:rect>+std::vector,所以你要先把所有 >0.25 的 box 提出来,再喂进去,score_threshold=0.25、nms_threshold=0.45
为什么推理结果 bbox 全部偏右下?或者完全不出现?
90% 是预处理和后处理的尺度链断裂。YOLOv8 的 anchor-free 解码比 YOLOv5 更敏感,尤其在 resize 和归一化环节。
- 检查
blobFromImage的scalefactor:必须是1.0/255.0,写成1/255就是整数除法 → 0 - 确认输入 blob 的
cv::Mat类型是CV_32F,不是CV_8U;net.setInput()前最好显式blob.convertTo(blob, CV_32F) - 输出 tensor reshape 错位:若模型实际输出是
[1, 8400, 84],却按[1, 84, 8400]reshape,坐标就全乱了 —— 用output.size[0], output.size[1], output.size[2]打印真实 shape - 没做坐标反归一化:输出是归一化到 [0,1] 的值,直接当像素坐标画,肯定全挤在左上角
最易忽略的是:YOLOv8 ONNX 模型默认输入是 images(NHWC),但 OpenCV blobFromImage 默认输出是 NCHW —— 必须在 blobFromImage 后加 cv::transpose(blob, blob) 再 cv::flip(blob, blob, 0) 手动转 NHWC,或改用 cv::dnn::blobFromImages 并设 swapRB=false, crop=false 配合正确 reshape。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











