miniconda使用 YOLOv8 + OpenCV 的 C++ 实现

Home / C++ MrLee 4小时前 6

一、准备工作

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 本身对小目标检测能力较强。如需进一步提升,可以:

  1. 适当增大输入尺寸(如将 inpWidth/inpHeight 设为 960 或 1280)

  2. 使用更大的模型(如 yolov8x 而非 yolov8n

  3. 在预处理时对图像中感兴趣区域进行局部放大检测

五、常见问题

Q:OpenCV 不支持 CUDA 怎么办?

  • 可以自行编译 OpenCV 时开启 -DWITH_CUDA=ON 选项;或使用 CPU 模式运行(速度较慢,但功能完整)。

Q:模型加载失败?

  • 检查 ONNX 文件路径是否正确,确认导出时的 opset 版本(推荐 12),并确保 OpenCV 版本 >= 4.5。

Q:检测框位置偏移?

  • 问题通常出在 Letterbox 坐标还原环节。请确保 scale 计算和边框坐标转换时使用的是正确的缩放比例与偏移量。

如果你还需要了解如何结合 DeepSORT 进行多目标跟踪,或者需要在嵌入式平台(如 Jetson) 上部署,我可以进一步展开说明。


为 YOLOv8 创建专属环境

安装完成后,建议为你的 YOLOv8 项目创建一个独立的“干净房间”,避免不同项目的包版本互相打架。

  1. 创建新环境

# 创建一个名为 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()  # 显示检测结果


本文链接:https://it72.com/12806.htm

推荐阅读
最新回复 (0)
返回