一、准备工作
1.1 环境要求
OpenCV 4.5+(需包含
dnn模块,推荐 4.8 或更高版本)C++11 或更高版本的编译器
(可选)CUDA + cuDNN,用于 GPU 加速推理
1.2 模型准备
将训练好的 YOLOv8 PyTorch 模型(.pt 文件)导出为 ONNX 格式
yolo export model=yolov8n.pt imgsz=640 format=onnx opset=12
推荐使用 opset=12 或更高版本,兼容性更好。模型导出后会生成 yolov8n.onnx 文件。
二、核心代码实现
2.1 整体架构
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
using namespace std;
using namespace cv;
using namespace dnn;
class YOLOv8Detector {
public:
YOLOv8Detector(const string& modelPath, const vector<string>& classNames,
float confThreshold = 0.25, float nmsThreshold = 0.45);
vector<Rect> detect(Mat& frame);
void drawResults(Mat& frame, const vector<Rect>& boxes);
private:
Net net;
vector<string> classes;
float confThreshold;
float nmsThreshold;
int inpWidth;
int inpHeight;
Mat preprocess(const Mat& frame);
vector<Rect> postprocess(Mat& frame, const vector<Mat>& outputs);
};2.2 初始化模型
YOLOv8Detector::YOLOv8Detector(const string& modelPath, const vector<string>& classNames,
float confThreshold, float nmsThreshold) {
this->classes = classNames;
this->confThreshold = confThreshold;
this->nmsThreshold = nmsThreshold;
this->inpWidth = 640; // 与导出时 imgsz 保持一致
this->inpHeight = 640;
// 加载 ONNX 模型
net = readNetFromONNX(modelPath);
// 可选:配置后端和硬件加速
net.setPreferableBackend(DNN_BACKEND_OPENCV); // 或 DNN_BACKEND_CUDA
net.setPreferableTarget(DNN_TARGET_CPU); // 或 DNN_TARGET_CUDA
if (net.empty()) {
cerr << "Failed to load model: " << modelPath << endl;
exit(-1);
}
}2.3 图像预处理(Letterbox)
YOLOv8 使用 Letterbox 技术,在保持图像宽高比的同时将图像缩放到目标尺寸,多余部分填充灰色边框
Mat YOLOv8Detector::preprocess(const Mat& frame) {
Mat blob;
// Letterbox 处理
Mat letterbox;
int top, bottom, left, right;
double scale = min((double)inpWidth / frame.cols, (double)inpHeight / frame.rows);
int newWidth = int(frame.cols * scale);
int newHeight = int(frame.rows * scale);
resize(frame, letterbox, Size(newWidth, newHeight));
top = (inpHeight - newHeight) / 2;
bottom = inpHeight - newHeight - top;
left = (inpWidth - newWidth) / 2;
right = inpWidth - newWidth - left;
copyMakeBorder(letterbox, letterbox, top, bottom, left, right, BORDER_CONSTANT, Scalar(114, 114, 114));
// 转换为 blob 格式:归一化到 [0,1],通道顺序 BGR->RGB
blobFromImage(letterbox, blob, 1.0 / 255.0, Size(inpWidth, inpHeight), Scalar(), true, false);
return blob;
}2.4 推理与后处理
YOLOv8 的输出格式与 YOLOv5 不同:输出维度为
[batch, 84, num_detections]每列包含:
[cx, cy, w, h, class1_score, class2_score, ...]需要对每个检测框执行 NMS 去除重叠框
vector<Rect> YOLOv8Detector::postprocess(Mat& frame, const vector<Mat>& outputs) {
vector<Rect> boxes;
vector<float> confidences;
vector<int> classIds;
// outputs[0] 形状: [1, 84, 8400] (8400 = 特征图格子总数)
float* data = (float*)outputs[0].data;
const int numDetections = outputs[0].size[2]; // 8400
const int numClasses = classes.size(); // COCO 为 80
for (int i = 0; i < numDetections; ++i) {
float* detection = data + i * (numClasses + 4);
float* scores = detection + 4;
float maxScore = *max_element(scores, scores + numClasses);
if (maxScore > confThreshold) {
int classId = max_element(scores, scores + numClasses) - scores;
float cx = detection[0];
float cy = detection[1];
float w = detection[2];
float h = detection[3];
// 将坐标还原到原图尺寸
int left = int((cx - w / 2) * frame.cols);
int top = int((cy - h / 2) * frame.rows);
int width = int(w * frame.cols);
int height = int(h * frame.rows);
boxes.push_back(Rect(left, top, width, height));
confidences.push_back(maxScore);
classIds.push_back(classId);
}
}
// NMS 过滤
vector<int> indices;
NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);
vector<Rect> finalBoxes;
for (int idx : indices) {
finalBoxes.push_back(boxes[idx]);
// 可选:绘制标签和置信度
rectangle(frame, boxes[idx], Scalar(0, 255, 0), 2);
string label = classes[classIds[idx]] + ": " + to_string(confidences[idx]);
putText(frame, label, Point(boxes[idx].x, boxes[idx].y - 5),
FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0), 2);
}
return finalBoxes;
}2.5 主函数
int main() {
// 加载类别标签(COCO 数据集共 80 类,人形对应第 0 类 "person")
vector<string> classNames = {"person", "bicycle", "car", "motorcycle", "airplane", "bus",
"train", "truck", "boat", "traffic light", "fire hydrant",
/* ... 其余 69 类 ... */};
// 初始化检测器
YOLOv8Detector detector("yolov8n.onnx", classNames, 0.25, 0.45);
// 读取图像或视频
Mat frame = imread("test.jpg");
if (frame.empty()) {
cerr << "Failed to load image" << endl;
return -1;
}
// 执行检测
auto boxes = detector.detect(frame);
// 显示结果
imshow("YOLOv8 + OpenCV Detection", frame);
waitKey(0);
return 0;
}三、编译与运行
3.1 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(YOLOv8_OpenCV)
set(CMAKE_CXX_STANDARD 11)
find_package(OpenCV REQUIRED)
add_executable(yolov8_demo main.cpp)
target_link_libraries(yolov8_demo ${OpenCV_LIBS})3.2 编译
mkdir build && cd build cmake .. make ./yolov8_demo
四、优化建议
| 优化项 | 实现方式 | 效果 |
|---|---|---|
| GPU 加速 | net.setPreferableBackend(DNN_BACKEND_CUDA) + 编译支持 CUDA 的 OpenCV | 推理速度提升 5-10 倍 |
| 多尺度检测 | 在预处理时使用图像金字塔,对不同尺度的图像分别检测 | 提升远处小目标(如远处行人)检出率 |
| 异步流水线 | 视频采集与检测放在不同线程,使用双缓冲或队列 | 提升视频处理的整体帧率 |
| TensorRT 集成 | 将 ONNX 转换为 TensorRT engine 格式 | 推理速度进一步提升(NVIDIA GPU 专属) |
关于远处人形检测:YOLOv8 本身对小目标检测能力较强。如需进一步提升,可以:
适当增大输入尺寸(如将
inpWidth/inpHeight设为 960 或 1280)使用更大的模型(如
yolov8x而非yolov8n)在预处理时对图像中感兴趣区域进行局部放大检测
五、常见问题
Q:OpenCV 不支持 CUDA 怎么办?
可以自行编译 OpenCV 时开启
-DWITH_CUDA=ON选项;或使用 CPU 模式运行(速度较慢,但功能完整)。
Q:模型加载失败?
检查 ONNX 文件路径是否正确,确认导出时的
opset版本(推荐 12),并确保 OpenCV 版本 >= 4.5。
Q:检测框位置偏移?
问题通常出在 Letterbox 坐标还原环节。请确保
scale计算和边框坐标转换时使用的是正确的缩放比例与偏移量。
如果你还需要了解如何结合 DeepSORT 进行多目标跟踪,或者需要在嵌入式平台(如 Jetson) 上部署,我可以进一步展开说明。
为 YOLOv8 创建专属环境
安装完成后,建议为你的 YOLOv8 项目创建一个独立的“干净房间”,避免不同项目的包版本互相打架。
创建新环境
# 创建一个名为 yolov8_env,Python 版本为 3.10 的独立环境 conda create -n yolov8_env python=3.10 -y
激活环境
conda activate yolov8_env
如果你更倾向于使用镜像源,可以配置 Conda 使用国内镜像来完全绕过这个提示:
# 配置清华源(示例) conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2/ conda config --set show_channel_urls yes
安装yolo
pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple
创建demo
from ultralytics import YOLO
# 加载一个官方提供的预训练模型
model = YOLO('yolov8n.pt')
# 执行检测(对图片、视频或摄像头都可以)
results = model('你的图片路径.jpg')
results[0].show() # 显示检测结果- 文章2323
- 用户1338
- 访客12426998
风捎来更柔软、更青翠的气息。
如何删除(360流氓)鲁大师360base64.dll
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is
全网首发——ThinkPad T430i黑苹果Yosemite 10.10.5安装教程
Galaxy X:三星的可折叠手机必须向中兴通讯学习
语法错误: 意外的令牌“标识符”
新手安装黑苹果OS X 10.11教程
Swift 4.0扩展 WCDB 支持 SQL 语句
阿里云的域名给七牛云的配置CDN
搜索算法——新手篇
关于Android Studio不能查看源码