VS2019中C++调用YOLOv3动态链接库实现目标检测

VS2019中C++调用YOLOv3动态链接库实现目标检测


环境准备与依赖获取

在工业级视觉系统开发中,直接使用Python部署往往难以满足实时性和资源占用的要求。尤其是在嵌入式设备或高并发场景下,C++成为更优选择。本文聚焦于如何在 Visual Studio 2019 中通过 C++ 调用由 Darknet 编译生成的 yolo_cpp_dll.dll 动态链接库,结合 OpenCV 实现高效的目标检测功能。

整个流程的核心在于正确配置编译环境和外部依赖。如果你已经完成了基于 Darknet 框架的 YOLOv3 在 Windows 10 下的编译工作,那么接下来只需将生成的 DLL 文件集成到新项目中即可。若尚未完成这一步,建议先参考 AlexeyAB/darknet 官方仓库完成构建。

YOLO(You Only Look Once)自2015年提出以来,凭借其“单次前向传播完成检测”的机制,在速度与精度之间取得了良好平衡。其中 YOLOv3 因支持多尺度预测、对小物体检测表现优异,至今仍在许多实际工程中被广泛采用。

要成功运行后续代码,必须准备好以下关键组件:

yolo_cpp_dll.dll 与 yolo_cpp_dll.lib 的获取

这两个文件是调用 YOLOv3 推理能力的关键——.dll 是运行时所需的动态库,而 .lib 则是在链接阶段供编译器使用的导入库。

它们需从 Darknet 源码手动编译获得:

  1. 打开你的 Darknet 源码目录,例如:
    D:\darknet-master\build\darknet\
  2. 使用文本编辑器打开 yolo_cpp_dll.vcxproj 文件,检查并修改 CUDA 版本号以匹配本地安装版本。比如你使用的是 CUDA 10.2,则应确保所有 <CudaVersion>10.2</CudaVersion> 设置一致。
  3. 用 Visual Studio 2019 打开 yolo_cpp_dll.sln 解决方案。
  4. 将解决方案平台设为 x64,配置为 Release,然后执行【生成】→【生成解决方案】。
  5. 成功后可在 x64/Release/ 目录找到:
    - yolo_cpp_dll.dll
    - yolo_cpp_dll.lib
若出现头文件缺失或库路径错误,请确认是否已正确设置 OpenCV 和 CUDA 的包含目录与库目录。常见做法是在项目属性中添加类似路径:
- 包含目录:D:\opencv\build\include, C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\include
- 库目录:D:\opencv\build\x64\vc15\lib

pthreadGC2.dll 与 pthreadVC2.dll 的来源

这两个 DLL 属于 MinGW 提供的 POSIX 线程兼容库,用于跨平台线程调度支持。虽然 Darknet 主要面向 MSVC 构建,但在某些构建脚本中仍会引入这些依赖。

通常可以在已有构建输出目录中找到它们,如:

darknet/x64/pthreadGC2.dll darknet/x64/pthreadVC2.dll 

⚠️ 运行时必须将这两个文件复制到最终可执行程序所在目录,否则会出现“找不到指定模块”错误。

引入 yolo_v2_class.hpp 头文件

该头文件定义了核心类 Detector 及结构体 bbox_t,是 C++ 接口调用的基础。

从源码路径复制即可:

D:\darknet-master\include\yolo_v2_class.hpp 

将其放入项目文件夹,并在代码中通过相对路径引用:

#include "yolo_v2_class.hpp" 

也可以添加到项目的“附加包含目录”中,便于统一管理。

配置 OpenCV 环境变量(可选但推荐)

为了使系统能自动识别 OpenCV 的运行时库,建议配置全局环境变量。

操作步骤如下:

  1. 【此电脑】→【属性】→【高级系统设置】→【环境变量】
  2. 在【系统变量】中编辑 Path
  3. 添加以下两条路径(以 OpenCV 3.4.6 为例):
    D:\opencv\build\x64\vc15\bin D:\opencv\build\x64\vc15\lib

其中 vc15 对应 Visual Studio 2019(MSVC 15.0),若使用其他版本请相应调整。

验证方式:重启命令提示符,输入 set PATH 查看是否包含上述路径。


创建并配置 C++ 项目

新建空控制台项目

启动 VS2019 →【新建项目】→ 选择“空项目”或“控制台应用(.NET Framework)” → 语言选 C++,平台务必选择 x64

这一点非常关键:因为 Darknet 默认编译为 x64 架构,若项目为 Win32 平台会导致链接失败或运行崩溃。

创建完成后,建议立即清理默认预编译头文件(如 pch.h),避免干扰第三方库的正常包含。

组织项目目录结构

良好的文件组织有助于后期维护。建议在项目根目录下创建两个子文件夹:

文件夹名用途
params存放模型相关文件:.cfg, .weights, .names
test放置测试图像、视频等输入数据

示例内容包括:

  • params/coco.names —— COCO 数据集类别名称列表
  • params/yolov3.cfg —— YOLOv3 网络结构配置
  • params/yolov3.weights —— 预训练权重文件(约 237MB)
  • test/dog.jpg —— 测试图片
💡 coco.namesyolov3.weights 可从官方下载:
- weights: https://pjreddie.com/media/files/yolov3.weights
- names: https://raw.githubusercontent.com/pjreddie/darknet/master/data/coco.names

复制必要依赖文件至输出目录

为了让程序顺利运行,需将以下 .dll 文件拷贝到最终的可执行文件同目录(通常是 x64/Debug/x64/Release/):

  • yolo_cpp_dll.dll
  • pthreadGC2.dll
  • pthreadVC2.dll
  • opencv_world346.dll(或其他对应版本)

这是 Windows 动态链接机制的基本要求:运行时找不到 DLL 就会报“无法启动此程序,因为缺少 xxx.dll”。

同时,在项目中引用静态库文件:

  • yolo_cpp_dll.lib
  • opencv_world346.lib

可在【项目属性】→【链接器】→【输入】→【附加依赖项】中添加:

yolo_cpp_dll.lib opencv_world346.lib 

或者使用 #pragma comment(lib, "...") 直接写在代码里。


核心代码实现与细节解析

下面是一个完整的主程序示例,展示了如何加载模型、执行推理、绘制结果。

#include <iostream> #include <fstream> #include <string> #include <vector> #ifdef _WIN32 #define OPENCV #define GPU #endif #include "yolo_v2_class.hpp" // Detector类声明 #include <opencv2/opencv.hpp> #include <opencv2/highgui/highgui.hpp> #pragma comment(lib, "opencv_world346.lib") // 替换为你自己的OpenCV版本 #pragma comment(lib, "yolo_cpp_dll.lib") // 绘制检测框与标签 void draw_boxes(cv::Mat mat_img, std::vector<bbox_t> result_vec, std::vector<std::string> obj_names, int current_det_fps = -1, int current_cap_fps = -1) { int const colors[6][3] = {{1,0,1}, {0,0,1}, {0,1,1}, {0,1,0}, {1,1,0}, {1,0,0}}; for (auto& i : result_vec) { cv::Scalar color = cv::Scalar(colors[i.obj_id % 6][0] * 255, colors[i.obj_id % 6][1] * 255, colors[i.obj_id % 6][2] * 255); cv::rectangle(mat_img, cv::Rect(i.x, i.y, i.w, i.h), color, 2); if (!obj_names.empty() && i.obj_id < obj_names.size()) { std::string label = obj_names[i.obj_id]; if (i.track_id > 0) label += " - ID:" + std::to_string(i.track_id); int baseline; cv::Size text_size = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.6, 1, &baseline); cv::rectangle(mat_img, cv::Point(i.x, i.y - 20), cv::Point(i.x + text_size.width, i.y), color, -1); cv::putText(mat_img, label, cv::Point(i.x, i.y - 5), cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(0,0,0), 1); } } if (current_det_fps >= 0 && current_cap_fps >= 0) { std::string fps_str = "Det FPS: " + std::to_string(current_det_fps) + " | Cap FPS: " + std::to_string(current_cap_fps); cv::putText(mat_img, fps_str, cv::Point(10, 20), cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(0, 255, 0), 2); } } // 从文件加载类别名称 std::vector<std::string> objects_names_from_file(const std::string& filename) { std::ifstream file(filename); std::vector<std::string> lines; if (!file.is_open()) { std::cerr << "Error: cannot open " << filename << "\n"; return lines; } std::string line; while (std::getline(file, line)) { if (!line.empty()) lines.push_back(line); } std::cout << "Loaded " << lines.size() << " object names.\n"; return lines; } int main() { // 路径配置(请根据实际路径修改) std::string names_file = ".\\params\\coco.names"; std::string cfg_file = ".\\params\\yolov3.cfg"; std::string weights_file = ".\\params\\yolov3.weights"; // 初始化检测器 Detector detector(cfg_file, weights_file, 0); // 第三个参数为GPU ID,0表示使用GPU 0 // 加载类别名称 std::vector<std::string> obj_names = objects_names_from_file(names_file); if (obj_names.empty()) { std::cerr << "Failed to load object names!\n"; return -1; } // 加载测试图像 cv::Mat frame = cv::imread(".\\test\\dog.jpg"); if (frame.empty()) { std::cerr << "Error: cannot load image 'dog.jpg'\n"; return -1; } // 执行推理 std::vector<bbox_t> detections = detector.detect(frame); std::cout << "Detected " << detections.size() << " objects.\n"; // 绘制结果 draw_boxes(frame, detections, obj_names); // 显示图像 cv::namedWindow("YOLOv3 Detection Result", cv::WINDOW_AUTOSIZE); cv::imshow("YOLOv3 Detection Result", frame); cv::waitKey(0); // 按任意键退出 return 0; } 

几点值得注意的技术细节:

  • #define OPENCV#define GPU 必须在包含 yolo_v2_class.hpp 前定义,否则可能触发编译错误。
  • 若使用 OpenCV 4.x,部分字体常量已变更,可将 cv::FONT_HERSHEY_COMPLEX_SMALL 改为 cv::FONT_HERSHEY_SIMPLEX
  • 安全警告 C4996(如 sprintf 不安全)可通过添加 _CRT_SECURE_NO_WARNINGS 宏解决。

推荐在【项目属性】→【C/C++】→【预处理器】→【预处理器定义】中加入:

_CRT_SECURE_NO_WARNINGS 

这样可以避免大量无关警告干扰调试。


测试结果与问题排查

运行程序后,若一切正常,将弹出窗口显示带有边界框和标签的检测图像。常见的成功标志包括:

  • 图像中的人物、狗、汽车等目标被准确标注
  • 控制台输出类似信息:
    Loaded 80 object names. Detected 3 objects.
  • 窗口标题为 “YOLOv3 Detection Result”,按任意键关闭

如果遇到问题,以下是典型错误及其解决方案汇总:

问题现象可能原因解决方法
编译错误:无法打开 yolo_cpp_dll.lib未添加库路径在【链接器】→【附加库目录】中指定 .lib 所在路径
运行时报错:“缺少 xxx.dll”DLL 未就位将所有 .dll 文件复制到 x64/Debug/ 目录
报错 C4996:sprintf 不安全启用了安全检查添加 _CRT_SECURE_NO_WARNINGS
GPU 初始化失败显卡驱动或CUDA不匹配更新 NVIDIA 驱动,确认 CUDA Toolkit 版本兼容
检测结果为空权重文件损坏或路径错误重新下载 yolov3.weights 并校验 SHA256

特别提醒:.weights 文件较大(约237MB),建议使用迅雷或 aria2 下载,避免因网络中断导致文件不完整。


扩展方向与性能优化建议

尽管当前实现了基本的功能闭环,但从工程角度看仍有多个值得深入的方向:

开发图形界面应用

可将现有逻辑封装进 Qt 或 MFC 框架,构建一个具备以下功能的 GUI 工具:

  • 实时摄像头检测
  • 视频流播放与暂停
  • 参数调节面板(置信度阈值、NMS 阈值)
  • 拖拽加载图片、导出检测结果(JSON / XML)

这类工具非常适合演示或交付给非技术人员使用。

封装为通用 DLL 供多语言调用

利用 extern "C" 导出函数接口,可让 C#、Python 等语言调用底层推理模块。

示例导出函数:

extern "C" __declspec(dllexport) int* detect_image(unsigned char* data, int w, int h, float threshold); 

之后可通过 P/Invoke(C#)或 ctypes(Python)进行调用,实现高性能跨语言协作。

升级至 YOLOv8 提升精度与效率

虽然本文围绕 YOLOv3 展开,但 Ultralytics 推出的 YOLOv8 在各项指标上均有显著提升。

可通过 PyTorch 训练模型后导出为 ONNX 格式,再使用 ONNX Runtime 在 C++ 中加载推理,兼顾灵活性与性能。

示例验证命令(在预装镜像中运行):
bash cd /root/ultralytics python -c " from ultralytics import YOLO; model = YOLO('yolov8n.pt'); results = model('bus.jpg'); results[0].show() "

该方式适合快速原型验证,后续可导出 ONNX 模型供 C++ 加载。

性能优化策略

为进一步提升吞吐量,可考虑以下手段:

  • 启用 TensorRT:将 Darknet 模型转换为 TensorRT 引擎,大幅加速推理速度。
  • 多线程流水线设计:分离图像采集、预处理、推理、后处理等阶段,形成生产者-消费者模式。
  • 内存池管理:复用 Mat 对象和检测缓冲区,减少频繁分配带来的开销。

尤其在工业相机连续采集场景中,这些优化能有效降低延迟、提高帧率稳定性。


这种高度集成的 C++ 部署方案,不仅提升了系统的稳定性和运行效率,也让我们更贴近底层机制,理解每一个像素是如何经过卷积、激活、锚框匹配最终变成屏幕上那个彩色边框的全过程。即便未来转向更新的模型架构,这套工程化思维依然具有长久价值。

Read more

C++/数据结构:哈希表知识点

C++/数据结构:哈希表知识点

目录 哈希表 理解哈希表 哈希值(整形) BKDR哈希   异或组合  hash_combine 哈希函数 直接定址法 除留余数法 平方取中法 基数转换法 哈希冲突 开放定址法 哈希桶 unordered_map和unorder_set如何共用一个哈希桶模板类 stl的哈希桶中Insert如何得到的键值 键为自定义类型的处理         前言:本篇文章前半部分内容为哈希表的原理, 从上到下按照理解链逐层递进。 最后三个小标题占了比较大的篇幅, 是结合c++代码来叙述, 主要内容为stl中的哈希桶如何封装的。 如果有错误的地方, 欢迎友友们指正哦。         ps:本篇文章一直到哈希桶,除了最后三个小标题,c++和java的同学都可以看, 讲的是数据结构, 即便有c++代码也很简单哦。 哈希表         首先要理解哈希和哈希表有什么不同。 哈希就是映射, 是一种算法思想。 哈希表就是映射表, 是利用映射这种思想写出的一种数据结构。          所有的哈希表的算法流程都是类似的——拿到一个key, 利用哈希函数进行hash

By Ne0inhk
C++之多态

C++之多态

多态 * 什么是多态? * 多态的定义及实现 * 多态的构成条件 * 虚函数 * 虚函数的重写/覆盖 * 关键技术原理 * 最佳实践指南 * 虚函数重写 * 协变 * 析构函数的重写 * override和final关键字 * 纯虚函数和抽象类 * 多态的原理 * 多态是如何实现的 * 1. 虚函数表(vtable) * 虚函数表知识要点 * 2. 虚函数的声明 * 3. 多态的实现过程 * 动态绑定与静态绑定 什么是多态? 多态(Polymorphism)是面向对象编程的三大核心特性之一(封装、继承、多态),源于希腊语"多种形态"。在C++中,它允许我们使用统一的接口处理不同类型的对象,显著提高了代码的灵活性和可扩展性。 核心概念 1. 同一接口,多种形态 不同的对象可以通过相同的方法名调用,但实际执行的逻辑由对象自身的类决定。 2. 解耦调用与实现 调用者只需关注接口(方法名和参数)

By Ne0inhk
Visual C++ 6.0中文版安装包下载教程及win11安装教程

Visual C++ 6.0中文版安装包下载教程及win11安装教程

本文分享的是Visual C++ 6.0(简称VC++6.0)中文版安装包下载及安装教程,关于win11系统下安装和使用VC++6.0使用问题解答,大家在安装使用的过程中会遇到不同的问题,如遇到解决不了的问题请给我留言! 一、安装包的下载 vc6.0安装包下载连接: https://pan.quark.cn/s/979dd8ba4f35 二、安装vc++6.0 1.鼠标右键解压到“VC++ 6.0”安装包,解压后如图所示: 2.双击Steup.exe,进行安装; 3.点击下一步 4.更改路径,建议不要安装在C盘(默认盘符),可以选择其他的盘符,点击浏览进行更改盘符。 5.选择C盘(默认盘或系统盘)以外的盘符。

By Ne0inhk
【C++高阶系列】:线程库和多线程

【C++高阶系列】:线程库和多线程

🔥 本文专栏:c++ 🌸作者主页:努力努力再努力wz 💪 今日博客励志语录: 选择决定了方向,勇气决定了能走多远。没有勇气的选择是纸上蓝图,没有选择的勇气是迷失的航船。 ★★★ 本文前置知识: 线程(上) 线程(下) 引入 在上一篇文章中,我们详细介绍了在 Linux 平台下如何进行线程管理,包括线程的创建、等待与退出等操作。具体而言,主要是通过调用 Linux 原生 pthread 线程库提供的接口,例如 pthread_create 和pthread_join 等。 需要注意的是,pthread 线程库所提供的接口遵循 POSIX 标准,因此主要适用于 Linux 及其他类 Unix 系统,例如 Unix 和 macOS。然而,在 Windows

By Ne0inhk