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

Home / C++ MrLee 23天前 116

一、准备工作

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()  # 显示检测结果

测试代码

#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <onnxruntime_cxx_api.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <memory>
#include <string>
struct Detection {
    cv::Rect box;
    float confidence;
    int class_id;
};
class YOLOv8ONNX {
public:
    YOLOv8ONNX(const std::string& model_path, float conf_threshold = 0.35f, float nms_threshold = 0.45f)
        : m_confThreshold(conf_threshold), m_nmsThreshold(nms_threshold), m_originalWidth(0), m_originalHeight(0), m_scale(0), m_xOffset(0), m_yOffset(0) {
        try {
            // 初始化ONNX Runtime环境 (新版API)
            m_env = std::make_unique<Ort::Env>(ORT_LOGGING_LEVEL_WARNING, "yolov8");
            // 创建Session选项
            m_sessionOptions = std::make_unique<Ort::SessionOptions>();
            m_sessionOptions->SetIntraOpNumThreads(1);
            m_sessionOptions->SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);
            // 参数0表示使用第一块GPU,如有多个GPU可改为其他编号 需要安装CUDA
            //Ort::ThrowOnError(Ort::GetApi().SessionOptionsAppendExecutionProvider_CUDA(*m_sessionOptions, 0));
            // 加载模型 (使用窄字符路径,Windows需要处理宽字符)
#ifdef _WIN32
            // Windows下需要转换路径为宽字符
            std::wstring wmodel_path(model_path.begin(), model_path.end());
            m_session = std::make_unique<Ort::Session>(*m_env, wmodel_path.c_str(), *m_sessionOptions);
#else
            m_session = std::make_unique<Ort::Session>(*m_env, model_path.c_str(), *m_sessionOptions);
#endif
            // 在构造函数中获取名称
            Ort::AllocatorWithDefaultOptions allocator;
            // 获取输入名称
            size_t num_input_nodes = m_session->GetInputCount();
            m_inputNamesStr.resize(num_input_nodes);
            m_inputNames.resize(num_input_nodes);
            m_inputShapes.resize(num_input_nodes);
            for (size_t i = 0; i < num_input_nodes; i++) {
                auto input_name_ptr = m_session->GetInputNameAllocated(i, allocator);
                m_inputNamesStr[i] = input_name_ptr.get();  // 复制字符串内容
                m_inputNames[i] = m_inputNamesStr[i].c_str();  // 存储指针
                std::cout << "输入名称 " << i << ": " << m_inputNames[i] << std::endl;
                // 获取 shape 信息
                Ort::TypeInfo type_info = m_session->GetInputTypeInfo(i);
                auto tensor_info = type_info.GetTensorTypeAndShapeInfo();
                m_inputShapes[i] = tensor_info.GetShape();
            }
            // 获取输出名称
            size_t num_output_nodes = m_session->GetOutputCount();
            m_outputNamesStr.resize(num_output_nodes);
            m_outputNames.resize(num_output_nodes);
            m_outputShapes.resize(num_output_nodes);
            for (size_t i = 0; i < num_output_nodes; i++) {
                auto output_name_ptr = m_session->GetOutputNameAllocated(i, allocator);
                m_outputNamesStr[i] = output_name_ptr.get();  // 复制字符串内容
                m_outputNames[i] = m_outputNamesStr[i].c_str();
                std::cout << "输出名称 " << i << ": " << m_outputNames[i] << std::endl;
                // 获取 shape 信息
                Ort::TypeInfo type_info = m_session->GetOutputTypeInfo(i);
                auto tensor_info = type_info.GetTensorTypeAndShapeInfo();
                m_outputShapes[i] = tensor_info.GetShape();
            }
            // 设置输入尺寸
            if (!m_inputShapes.empty() && m_inputShapes[0].size() >= 4) {
                // YOLOv8 输入格式通常是 [batch, channels, height, width]
                m_inputHeight = static_cast<int>(m_inputShapes[0][2]);
                m_inputWidth = static_cast<int>(m_inputShapes[0][3]);
            }
            else {
                // 默认值
                m_inputWidth = 640;
                m_inputHeight = 640;
            }
            std::cout << "模型加载成功!" << std::endl;
            std::cout << "输入尺寸: " << m_inputWidth << "x" << m_inputHeight << std::endl;
            std::cout << "输入节点数: " << num_input_nodes << std::endl;
            std::cout << "输出节点数: " << num_output_nodes << std::endl;
        }
        catch (const Ort::Exception& e) {
            std::cerr << "ONNX Runtime错误: " << e.what() << std::endl;
            throw;
        }
    }
    // 预处理
    cv::Mat preprocess(const cv::Mat& image) {
        m_originalWidth = image.cols;
        m_originalHeight = image.rows;
        float scale = std::min(
            static_cast<float>(m_inputWidth) / image.cols,
            static_cast<float>(m_inputHeight) / image.rows
        );
        int new_width = static_cast<int>(image.cols * scale);
        int new_height = static_cast<int>(image.rows * scale);
        cv::Mat resized;
        cv::resize(image, resized, cv::Size(new_width, new_height));
        m_letterbox = cv::Mat(m_inputHeight, m_inputWidth, CV_8UC3, cv::Scalar(114, 114, 114));
        resized.copyTo(m_letterbox(cv::Rect((m_inputWidth - new_width) / 2,
            (m_inputHeight - new_height) / 2,
            new_width, new_height)));
        m_xOffset = (m_inputWidth - new_width) / 2;
        m_yOffset = (m_inputHeight - new_height) / 2;
        m_scale = scale;
        return m_letterbox;
    }
    // 推理
    std::vector<Detection> detect(cv::Mat& frame) {
        auto start = std::chrono::high_resolution_clock::now();
        cv::Mat preprocessed = preprocess(frame);
        // 转换为ONNX Runtime输入格式
        cv::Mat blob;
        cv::dnn::blobFromImage(preprocessed, blob, 1.0 / 255.0, cv::Size(), cv::Scalar(), true, false);
        // 准备输入张量
        std::vector<int64_t> input_shape = { 1, 3, m_inputHeight, m_inputWidth };
        size_t input_tensor_size = static_cast<size_t>(1 * 3 * m_inputHeight * m_inputWidth);
        std::vector<float> input_tensor_values(blob.begin<float>(), blob.end<float>());
        Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
        std::vector<Ort::Value> input_tensors;
        input_tensors.push_back(Ort::Value::CreateTensor<float>(
            memory_info, input_tensor_values.data(), input_tensor_size,
            input_shape.data(), input_shape.size()
        ));
        // 运行推理 - 动态构建指针数组避免悬空指针问题
        std::vector<const char*> input_ptrs;
        input_ptrs.reserve(m_inputNames.size());
        for (const auto& name : m_inputNamesStr) {
            input_ptrs.push_back(name.c_str());
        }
        std::vector<const char*> output_ptrs;
        output_ptrs.reserve(m_outputNamesStr.size());
        for (const auto& name : m_outputNamesStr) {
            output_ptrs.push_back(name.c_str());
        }
        std::vector<Ort::Value> output_tensors = m_session->Run(
            Ort::RunOptions{ nullptr },
            input_ptrs.data(), input_tensors.data(), input_ptrs.size(),
            output_ptrs.data(), output_ptrs.size()
        );
        auto end = std::chrono::high_resolution_clock::now();
        std::chrono::duration<double, std::milli> inference_time = end - start;
        std::cout << "推理时间: " << inference_time.count() << " ms" << std::endl;
        // 后处理
        std::vector<Detection> detections = postprocess(output_tensors[0]);
        return detections;
    }
    // 后处理 - 只检测人(class_id = 0)
    std::vector<Detection> postprocess(Ort::Value& output) {
        std::vector<Detection> detections;
        float* output_data = output.GetTensorMutableData<float>();
        std::vector<int64_t> output_shape = output.GetTensorTypeAndShapeInfo().GetShape();
        // YOLOv8输出格式: [1, 84, 8400] 或 [1, 8400, 84]
        int num_detections;
        int num_classes;
        bool is_channels_first;
        if (output_shape.size() < 3) {
            std::cerr << "输出维度不正确" << std::endl;
            return detections;
        }
        if (output_shape[1] == 84 && output_shape[2] > 1000) {
            // 格式 [1, 84, 8400]
            num_detections = static_cast<int>(output_shape[2]);
            num_classes = static_cast<int>(output_shape[1]) - 4;
            is_channels_first = true;
        }
        else if (output_shape[1] > 1000 && output_shape[2] == 84) {
            // 格式 [1, 8400, 84]
            num_detections = static_cast<int>(output_shape[1]);
            num_classes = static_cast<int>(output_shape[2]) - 4;
            is_channels_first = false;
        }
        else {
            std::cerr << "未知的输出格式" << std::endl;
            return detections;
        }
        std::vector<cv::Rect> boxes;
        std::vector<float> confidences;
        std::vector<int> class_ids;
        for (int i = 0; i < num_detections; i++) {
            float cx, cy, w, h;
            // 获取人的置信度(class_id = 0)
            float person_conf;
            if (is_channels_first) {
                // 格式 [1, 84, 8400] 按通道优先读取
                cx = output_data[i + 0 * num_detections];
                cy = output_data[i + 1 * num_detections];
                w = output_data[i + 2 * num_detections];
                h = output_data[i + 3 * num_detections];
                // 人的置信度在索引 4(因为 person 的 class_id = 0)
                person_conf = output_data[i + 4 * num_detections];
            }
            else {
                // 格式 [1, 8400, 84] 按检测框顺序读取
                int offset = i * (num_classes + 4);
                cx = output_data[offset + 0];
                cy = output_data[offset + 1];
                w = output_data[offset + 2];
                h = output_data[offset + 3];
                // 人的置信度在偏移 4 处
                person_conf = output_data[offset + 4];
            }
            // 只保留人的检测(class_id = 0)
            if (person_conf < m_confThreshold) {
                continue;
            }
            // 坐标映射回原图
            float x1 = (cx - w / 2 - m_xOffset) / m_scale;
            float y1 = (cy - h / 2 - m_yOffset) / m_scale;
            float x2 = (cx + w / 2 - m_xOffset) / m_scale;
            float y2 = (cy + h / 2 - m_yOffset) / m_scale;
            x1 = std::max(0.0f, std::min(x1, static_cast<float>(m_originalWidth)));
            y1 = std::max(0.0f, std::min(y1, static_cast<float>(m_originalHeight)));
            x2 = std::max(0.0f, std::min(x2, static_cast<float>(m_originalWidth)));
            y2 = std::max(0.0f, std::min(y2, static_cast<float>(m_originalHeight)));
            cv::Rect box(static_cast<int>(x1), static_cast<int>(y1),
                static_cast<int>(x2 - x1), static_cast<int>(y2 - y1));
            if (box.width > 0 && box.height > 0) {
                boxes.push_back(box);
                confidences.push_back(person_conf);
                class_ids.push_back(0);  // 人的 class_id
            }
        }
        // NMS
        if (!boxes.empty()) {
            std::vector<int> indices;
            cv::dnn::NMSBoxes(boxes, confidences, m_confThreshold, m_nmsThreshold, indices);
            for (int idx : indices) {
                Detection det;
                det.box = boxes[idx];
                det.confidence = confidences[idx];
                det.class_id = 0;  // 人的 class_id
                detections.push_back(det);
            }
        }
        return detections;
    }
    // 绘制检测结果
    void drawDetections(cv::Mat& frame, const std::vector<Detection>& detections) {
        // 为不同类别设置不同颜色,但这里都是人,用固定颜色
        cv::Scalar color = cv::Scalar(0, 255, 0);  // 绿色
        for (const auto& det : detections) {
            cv::rectangle(frame, det.box, color, 2);
            std::string label = "person: " + std::to_string(det.confidence).substr(0, 4);
            int baseLine;
            cv::Size labelSize = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
            cv::rectangle(frame,
                cv::Point(det.box.x, det.box.y - labelSize.height - baseLine),
                cv::Point(det.box.x + labelSize.width, det.box.y),
                color, cv::FILLED);
            cv::putText(frame, label,
                cv::Point(det.box.x, det.box.y - baseLine),
                cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0), 1);
        }
    }
private:
    // ONNX Runtime相关 (使用智能指针管理)
    std::unique_ptr<Ort::Env> m_env;
    std::unique_ptr<Ort::SessionOptions> m_sessionOptions;
    std::unique_ptr<Ort::Session> m_session;
    // 存储字符串内容(确保生命周期)
    std::vector<std::string> m_inputNamesStr;
    std::vector<std::string> m_outputNamesStr;
    // 运行时需要的指针数组
    std::vector<const char*> m_inputNames;
    std::vector<const char*> m_outputNames;
    std::vector<std::vector<int64_t>> m_inputShapes;
    std::vector<std::vector<int64_t>> m_outputShapes;
    float m_confThreshold;
    float m_nmsThreshold;
    int m_inputWidth;
    int m_inputHeight;
    int m_originalWidth;
    int m_originalHeight;
    int m_xOffset;
    int m_yOffset;
    float m_scale;
    cv::Mat m_letterbox;
};
int onnx_demo() {
    std::string model_path = "D:\\yolov8n.onnx";
    std::string rtmp_url = "http://demo-videos.qnsdk.com/bbk-H265-50fps.mp4";  // 默认RTMP测试地址
    std::cout << "模型路径: " << model_path << std::endl;
    std::cout << "RTMP地址: " << rtmp_url << std::endl;
    try {
        // 初始化YOLO模型
        YOLOv8ONNX yolo(model_path, 0.5f, 0.45f);
        // 打开RTMP视频流
        cv::VideoCapture cap;
        // 尝试使用 FFMPEG 后端打开 RTMP 流
        std::cout << "正在连接RTMP流..." << std::endl;
        cap.open(rtmp_url, cv::CAP_FFMPEG);
        if (!cap.isOpened()) {
            std::cerr << "错误:无法打开RTMP流!请检查:" << std::endl;
            std::cerr << "  1. RTMP地址是否正确" << std::endl;
            std::cerr << "  2. 网络连接是否正常" << std::endl;
            std::cerr << "  3. 视频流是否在线" << std::endl;
            return -1;
        }
        // 获取视频流信息
        double fps = cap.get(cv::CAP_PROP_FPS);
        int width = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));
        int height = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
        std::cout << "视频流信息:" << std::endl;
        std::cout << "  分辨率: " << width << "x" << height << std::endl;
        std::cout << "  帧率: " << fps << " fps" << std::endl;
        // 创建窗口
        cv::namedWindow("YOLOv8 ONNX RTMP Detection", cv::WINDOW_NORMAL);
        cv::Mat frame;
        int frame_count = 0;
        auto last_time = std::chrono::steady_clock::now();
        std::cout << "开始实时检测,按 'q' 或 'ESC' 退出..." << std::endl;
        while (true) {
            // 读取一帧
            cap >> frame;
            if (frame.empty()) {
                std::cerr << "警告:读取帧失败,尝试重新连接..." << std::endl;
                // 尝试重新打开流
                cap.release();
                cap.open(rtmp_url, cv::CAP_FFMPEG);
                if (!cap.isOpened()) {
                    std::cerr << "重连失败,退出程序" << std::endl;
                    break;
                }
                continue;
            }
            frame_count++;
            // 计算实际帧率
            auto now = std::chrono::steady_clock::now();
            auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_time).count();
            if (duration >= 1000) {
                std::cout << "实时帧率: " << frame_count << " fps" << std::endl;
                frame_count = 0;
                last_time = now;
            }
            // 进行目标检测
            auto detections = yolo.detect(frame);
            // 绘制检测结果
            yolo.drawDetections(frame, detections);
            // 显示帧率和检测数量
            std::string info = "Persons: " + std::to_string(detections.size());
            cv::putText(frame, info, cv::Point(10, 30),
                cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(0, 255, 0), 2);
            // 显示结果
            cv::imshow("YOLOv8 ONNX RTMP Detection", frame);
            // 按键退出
            char key = static_cast<char>(cv::waitKey(1));
            if (key == 'q' || key == 'Q' || key == 27) {  // 27 = ESC
                std::cout << "用户退出" << std::endl;
                break;
            }
        }
        // 释放资源
        cap.release();
        cv::destroyAllWindows();
        std::cout << "处理完成" << std::endl;
    }
    catch (const std::exception& e) {
        std::cerr << "错误: " << e.what() << std::endl;
        return -1;
    }
    return 0;
}


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

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