简介:基于海思Hi3559A芯片的YOLOv5目标检测部署包,面向嵌入式视觉、边缘计算及安防智能分析开发者。该资源解决在Hi3559A平台上高效运行实时目标检测模型的难题,帮助工程师绕过复杂的交叉编译与依赖配置,直接获得可移植的工程基础。包内共835个文件,包含358个hpp头文件、248个h头文件、7个cpp源文件、16个so动态库、42个a静态库、17个cmake构建脚本,以及xml配置文件、jpg示例图片和文本说明等,压缩包整体约43.37MB。目录结构清晰,便于按模块学习与复用,能覆盖从底层库到应用层的完整依赖链条。已有240人学习使用,适合具备深度学习基础并希望在边缘设备落地YOLOv5的开发者参考,尤其适用于智能交通监控、工业视觉等实时检测场景。资源不仅提供源码与库文件,还包含针对NPU加速的模型优化思路,对模型量化、剪枝及推理性能调优具有直接借鉴价值,可作为从环境搭建到应用验证的完整参考。
1. hisi3559a 上跑 YOLOv5:这份 OpenCV 4.1 动态库是绕不开的前置条件
拿到一块 hisi3559a 开发板,想把 YOLOv5 检测跑起来,大多数人第一反应是去折腾模型转换,真正把工程卡住两天的反而是 OpenCV。YOLOv5 训练可以在 PC 上完成,到了板端,读图、letterbox 缩放、BGR 转换、坐标映射全都要调用 OpenCV;交叉编译环境里缺一套 aarch64 动态库,程序要么链不过,要么上板就报 shared object file 找不到。这份 Hi3559A_Yolov5.zip 解出来就是一组 OpenCV 4.1 版本 libopencv_.so.,做安防、工业视觉、智能交通的部署工程师可以直接接进自己的 YOLOv5 工程。老手能省掉自行交叉编译 OpenCV 的时间,新手也能先把环境跑通,再回来补量化细节。
2. 部署链路先看全:NNIE 量化与 OpenCV 在 YOLOv5 里各管哪一段
很多人把“YOLOv5 部署”理解成把权重拷到板子上就能跑,海思平台实际走的是一条截然不同的链路。先花 10 分钟把这条链路看清楚,后面无论调精度还是调性能,你都知道该找谁要数据。
2.1 Hi3559A 的算力模型:大小核 CPU 与 NNIE 的职责边界
Hi3559A 是海思面向智能视频推出的 SoC,芯片里有常规的 Cortex-A73、A53 大小核 CPU,也集成了专门的神经网络推理单元 NNIE(Neural Network Inference Engine)。CPU 不是不能用,但要在边缘端把 YOLOv5 跑出实时性,算力重心必须落在 NNIE 上。
NNIE 的硬性约束是:它不接受你训练好的 PyTorch 权重,也不直接读 ONNX,它只认海思工具链离线转换出来的 wk 模型。这一条约束决定了整个部署流程的基本走向。常见的落地流程是这样走的:先把 PyTorch 权重导出成 ONNX,再把 ONNX 转成 Caffe 格式,最后用 nnie_mapper 或 RuyiStudio 生成 wk,烧到板端由 NNIE 加载推理。中间为什么绕 Caffe 这一步,纯粹是海思工具链的输入接口被固定成了 Caffe 约定,实战里不用纠结这一步是不是够现代,照着走就行。
量化和校准是接下来的关键。NNIE 推理以 8bit 量化为主,少数场景会用到 16bit。做量化时要准备几百张有代表性的校准图片,让工具统计激活值分布并确定每层量化阈值。校准集的选择直接影响小目标检出效果——如果校准图全是白天场景,晚上低照度的检测质量大概率会塌下去。这是拿模型精度换取推理速度的常见代价。你训练自己的数据集时尤其要注意,类别不均衡也会在量化后被放大,个别低频类别在 wk 里可能直接丢框。
还有一个实际工程细节:NNIE 对输入分辨率有固定要求,多数项目会把检测输入固定成 416×416 或 640×640。这个尺寸会反向影响 OpenCV 前处理阶段的参数设定,后面写预处理代码时会反复用到,先把输入尺寸定死,后续再做动态分辨率就要重新过一轮量化验证。
2.2 OpenCV 4.1 在 YOLOv5 工程里的真实职责
把模型转换链路捋顺之后,再看 OpenCV 的角色。YOLOv5 的板端推理程序一般由三块组成:数据前处理、NNIE 推理调用、后处理与坐标还原。前处理和后处理这两块,几乎离不开 OpenCV。
前处理上,程序从摄像头或本地文件拿到一帧图像,第一步是用 cv::Mat 容器接住数据,接着要 resize 到模型输入尺寸。YOLOv5 工程用的又多是 letterbox 等比缩放而不是直接拉伸,这一步要调 cv::resize 并自己维护填充比例;随后做 BGR 到 RGB 的通道转换和归一化,调用 cvtColor 和 convertTo。哪怕你不开 dnn 模块,core 和 imgproc 这两个库都是一定会被链接进来的。
后处理上,bbox 坐标、置信度、类别索引从 NNIE 输出里解析出来之后,要通过缩放比例映射回原图坐标,再画框,draw 操作也依赖 imgproc。作为对比基线,如果你想在 PC 上跑一版 PyTorch 之外的参照实现,cv::dnn::readNetFromONNX 或 readNetFromDarknet 可以直接吃下导出后的模型。这份包里保留了 opencv_dnn,就是方便这种“CPU 参照实现”和 NNIE 正式链路做交叉验证的。要注意 OpenCV 4.1 的 ONNX 算子支持有限,导出时 opset 不要开太高,常见卡在 11 以下;树莓派 4B 部署 YOLOv5 可以直接读 onnx 跑 CPU,Hi3559A 必须转 wk,两条路线不能互换。
其余几个 so 也不是白附的:calib3d 常用于单目测距和相机标定,features2d 与 stitching 在多路拼接项目里能复用;ml 是传统机器学习模型的运行时;gapi 是 OpenCV 4.x 新增的图执行引擎,个别工程会用它做异构调度。也就是说,这套 4.1 动态库不只服务于 YOLOv5 这一条路,同一块板子上如果再跑 ORB-SLAM 类视觉里程计,features2d 和 calib3d 这些库还能继续用。这也是我推荐直接把这套包收进工具链的原因,省一次编译,长期受益。
2.3 预编译 so 的价值:绕开自编译 OpenCV 的三道坎
自己从源码交叉编译 OpenCV 到 Hi3559A 板端,通常要过三道坎。第一道是交叉编译工具链配置。要用板卡 SDK 里配套的 aarch64 编译器,设好 sysroot、库路径和头文件路径,稍有偏差编译出的程序在板端运行就会弹段错误。第二道是 CMake 选项裁剪。OpenCV 的 modules 很多,全编耗时几小时,还容易在下载 IPPICV 第三方包时卡死;只编 core/imgproc/dnn 又要反复调试模块依赖,dnn 依赖 imgproc,imgproc 依赖 core,缺一个模块整个编译就白跑。第三道是版本 ABI。海思 SDK 自带的三方库往往绑定特定版本,自己编的 OpenCV 4.5 或 5.x 可能与板子上的 libpng、libjpeg 产生符号冲突,出现运行时崩在“打开图片”这种最基础调用上的问题。
这份压缩包里带的就是 4.1 版本的预编译动态库,版本对齐工作已经做完。它的边界也很清楚:不是拿来跑通用算法的,而是服务你基于 Hi3559A 的 YOLOv5 或其他视觉工程。把 10 个 so 放进 lib 目录后可以直接用,不用维护一套 OpenCV 源码树。如果你后续确实要加自定义模块,再回源码编译也不迟,前期先让整个链路跑通是更实际的策略。
3. 落地实操:把 so 库接进交叉编译环境与板端系统
3.1 板端部署:先让 so 文件被系统找到
解压这份 zip 后,你会看到一个 lib 目录,里面躺着 libopencv_core.so.4.1、libopencv_imgproc.so.4.1、libopencv_dnn.so.4.1 这一组带版本号的文件。常见的打包情况是只有带版本号的实体文件,没有 libopencv_*.so 这种软链接,这会影响后面的链接,我先说清楚,免得你卡在编译阶段。
上板部署的第一步是把这些库放进板端文件系统。开发阶段我一般用 adb 或 scp:
# 假设板子已经通过 USB 连上,并用 adb 获取 root 权限 adb root for f in lib/*.so*; do adb push "$f" /usr/lib/ done adb shell ldconfig没有 adb 环境时,用 scp root@<板子IP> 把 lib 目录推到板端也可以。放 /usr/lib 后运行 ldconfig,系统就能找到这些库。如果想隔离到自建目录,比如 /opt/opencv410/lib,需要把目录写进 /etc/ld.so.conf.d/opencv410.conf,再执行 ldconfig。开发阶段更随性的做法是 export LD_LIBRARY_PATH,但重启就丢,量产方案不要依赖它。
库放好后做一个快速验证:
adb shell "readelf -d /usr/lib/libopencv_core.so.4.1 | grep NEEDED" adb shell "ldd /usr/lib/libopencv_dnn.so.4.1"readelf 输出的是 libopencv_core 依赖的基础库,比如 libstdc++、libm、libgcc。这一步能提前暴露目标板缺 libstdc++.so.6 的问题——海思 SDK 提供的 rootfs 不一定带全,缺了就顺手把 SDK 里匹配的版本也推上去。ldd 能确认依赖解析是否完整,凡是显示 not found 的都要补。
3.2 交叉编译工程链接:CMake 与命令行两条路
工程侧推荐直接手写链接路径,不要依赖 find_package。原因很简单:这套预编译包一般不带 OpenCVConfig.cmake,find_package 大概率失败,还得回头手改。CMake 里这样写最稳:
cmake_minimum_required(VERSION 3.10) project(yolo_deploy) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 假设你把这套库解压到了 /opt/hi3559a/opencv410 set(OpenCV_DIR "/opt/hi3559a/opencv410") include_directories(${OpenCV_DIR}/include/opencv4) add_executable(detect_yolo main.cpp) target_link_libraries(detect_yolo opencv_dnn opencv_imgproc opencv_imgcodecs opencv_core pthread)链接顺序是有讲究的:opencv_dnn 依赖 imgproc,imgproc 依赖 core,GNU ld 从左到右解析符号,依赖者要写在被依赖者前面。不想管顺序的话,可以在 target_link_libraries 里用 -Wl,--start-group 和 -Wl,--end-group 把 OpenCV 库包起来,但就这几行依赖,没必要。pthread 是隐式依赖,加上省得某些编译环境下报错。
如果直接命令行编译,对应的是这条:
aarch64-himix100-linux-g++ -std=c++14 \ -I/opt/hi3559a/opencv410/include/opencv4 \ -L/opt/hi3559a/opencv410/lib \ main.cpp -lopencv_dnn -lopencv_imgproc -lopencv_imgcodecs -lopencv_core \ -o detect_yoloaarch64-himix100-linux-g++ 是海思 SDK 里常见的编译器前缀,不同版本 SDK 可能叫 aarch64-linux-gnu-g++,以你拿到的工具链为准。这里有一个高频报错:如果 lib 目录里只有 .so.4.1 没有软链接,链接器会报 cannot find -lopencv_core。解决方法是进 lib 目录补软链接:
cd /opt/hi3559a/opencv410/lib for f in libopencv_*.so.4.1; do ln -s "$f" "${f%.4.1}" done或者不想动目录,就在链接参数里写 -l:libopencv_core.so.4.1,GNU ld 支持用全名直接指定。运行期程序记录的 SONAME 仍然是 libopencv_core.so.4.1,所以软链接只影响链接期,版本文件必须留着。
提示:如果宿主机的系统目录里恰好也装了 OpenCV,注意 -I 路径顺序,把本项目的 include/opencv4 放在最前面,避免头文件被旧版本抢占。
3.3 最小验证程序:先证明 OpenCV 在板端能正常干活
链接通过只算一半,上板能跑才算数。写一个最小的验证程序,不接 NNIE,只验证 OpenCV 的读写和预处理链路:
#include <opencv2/opencv.hpp> #include <iostream> int main(int argc, char** argv) { if (argc < 2) { std::cerr << "usage: " << argv[0] << " <image>" << std::endl; return 1; } cv::Mat frame = cv::imread(argv[1]); if (frame.empty()) { std::cerr << "failed to load image" << std::endl; return -1; } // 手动实现 YOLOv5 的 letterbox:等比缩放 + 灰色填充到 640x640 float scale = std::min(640.0f / frame.cols, 640.0f / frame.rows); int new_w = static_cast<int>(frame.cols * scale); int new_h = static_cast<int>(frame.rows * scale); cv::Mat resized; cv::resize(frame, resized, cv::Size(new_w, new_h)); cv::Mat letterbox; cv::copyMakeBorder(resized, letterbox, 0, 640 - new_h, 0, 640 - new_w, cv::BORDER_CONSTANT, cv::Scalar(114, 114, 114)); // swapRB=true 是因为 YOLOv5 预训练模型喂的是 RGB cv::Mat blob = cv::dnn::blobFromImage(letterbox, 1.0 / 255.0, cv::Size(640, 640), cv::Scalar(0, 0, 0), true, false); std::cout << "blob shape: " << blob.size << std::endl; std::cout << "opencv works on board, scale=" << scale << std::endl; return 0; }这段代码的逻辑要和 YOLOv5 官方预处理严格对齐。blobFromImage 的 6 个参数分别是输入 Mat、归一化系数 1/255、输出尺寸、均值、是否交换 R/B 通道、是否中心裁剪,最后一个 false 表示不裁剪。letterbox 里 scale 取宽高比例的最小值,这样图像不会被拉伸变形,剩余区域用 114 的灰色填充,这是 YOLOv5 仓库里默认的 padding 值。如果你拿任意一张测试图直接 resize 成 640×640 而不是 letterbox,竖图里的物体会被横向压扁,检测框比例也会偏,后面查起来很费劲。
交叉编译命令沿用 3.2 的 CMake 流程:
mkdir -p build && cd build cmake .. -DCMAKE_CXX_COMPILER=aarch64-himix100-linux-g++ make -j$(nproc) adb push detect_yolo /root/ adb push test.jpg /root/ adb shell /root/detect_yolo /root/test.jpg我在海思板子上第一次跑通时,看到 “blob shape” 正常打印,心里就踏实了一半:core、imgproc、imgcodecs、dnn 四个库的加载没有问题,接下来才轮到和 NNIE 的联调。
4. 避坑排查:链接、运行与 NNIE 交互的五个现场
4.1 链接期三个高频坑
先看编译阶段最常见的三个问题。这三个坑我基本每次换新工程都会遇到一次,属于不踩不算完整。
第一个高频坑是链接器报cannot find -lopencv_core。现象很直白:CMake 配置通过,make 阶段直接报错。原因是库目录里只有 libopencv_core.so.4.1,没有 libopencv_core.so 软链接,而 -l 参数默认找 lib .so。解决方法是按 3.2 里的 for 循环补软链接,或者用 -l:libopencv_core.so.4.1 指定全名。记住软链接只解决链接期,运行期的 NEEDED 记录的仍是版本文件名,千万不要顺手把 .so.4.1 删了。
第二个坑是头文件与库版本错位,现象分两种:编译时爆opencv2/core/core_c.h: No such file or directory,或者链接时报一堆undefined reference to cv::parallel_for_。原因是宿主机的系统目录里装了另一套 OpenCV,编译器先找到了 /usr/include/opencv 的头文件,链接却用了我们这份 4.1 的库,头文件和库的 ABI 对不上。解决方法是把 -I/opt/hi3559a/opencv410/include/opencv4 放在所有 -I 的最前面,且 -L 只指这一个 lib 目录。最好把 build 目录清了重配一次,防止 CMake 缓存把系统路径藏进去。
第三个坑是undefined reference to cv::dnn::readNetFromDarknet(...)这类符号缺失。原因基本是两个:要么调用了 dnn 的接口但链接列表里没写 opencv_dnn,要么写了 opencv_dnn 但把 opencv_core 放在了它前面。GNU ld 从左到右扫描,core 先被处理,后面 dnn 的符号就找不到归属。解决办法就是维持“功能模块在前、依赖库在后”的顺序:opencv_dnn、opencv_imgproc、opencv_core。项目里如果链接的库变多,可以用 -Wl,--start-group 和 -Wl,--end-group 把需要循环扫描的库包起来。
4.2 运行期两个高危问题
编译顺利通过后,运行期还有两个高危问题拦路。
第一个是经典的error while loading shared libraries: libopencv_core.so.4.1: cannot open shared object file。现象就是程序 push 到板端后一跑就退。原因无非三种:库没推上去,放的位置不在默认搜索路径,或者 LD_LIBRARY_PATH 没设。解决方法是先 adb shell ldconfig 刷新,再用 ldd /root/detect_yolo 查看所有 NEEDED 是否都解析成功。我个人的经验是不要依赖 export LD_LIBRARY_PATH,重启就丢;开发期用 NFS 挂载 lib 目录,量产时把这 10 个 so 直接打进 rootfs,才是长期稳定的做法。
第二个问题更容易翻车:预处理看起来正常,NNIE 推理接口却报错,或者输出的框错位、跑到目标旁边。原因是多方面的,但绝大多数集中在内存布局上。NNIE 对输入 buffer 普遍要求 16 字节对齐,且图像 stride 要按对齐后的跨度计算;直接拿 cv::Mat.data 的首地址传给 NNIE,Mat 的 step 未必是对齐值。另外 NCHW 与 NHWC 通道序搞反也是重灾区。我的做法是新建一块对齐后的连续内存,按行拷贝:
// 假设模型输入 640x640,RGB 三通道,stride 对齐到 16 int aligned_stride = (640 * 3 + 15) / 16 * 16; unsigned char* nnie_input = (unsigned char*)malloc(640 * aligned_stride); for (int y = 0; y < 640; y++) { memcpy(nnie_input + y * aligned_stride, rgb.ptr<unsigned char>(y), 640 * 3); }这段逻辑很朴素:逐行拷贝,每行起始位置按对齐后的 stride 走,避免 Mat 内部 step 不连续的问题。这样既绕开了对齐检查,也把通道序统一成 RGB planar。如果你手上的 SDK 头文件里定义了输入结构体,里面一般有 stride 字段,把 aligned_stride 填进去即可。malloc 的这块 buffer 建议在初始化时分配一次,循环里复用,不要在每帧都申请释放,否则帧率会很难看。
5. 上板验证技巧:用同一组图片把板端输出和 PC 端对齐
5.1 先做运行时基线:算清楚时间花在哪个环节
拿到能跑的 YOLOv5 程序后,不要直接调帧率,先建立运行时基线。命令行上先粗跑一遍:
adb shell "time /root/detect_yolo /root/test.jpg /root/yolov5.wk"time 输出的 real 是整帧从读到画框的总耗时,user 和 sys 可以帮你判断 CPU 占用分布。更细的做法是在 C++ 代码里用 chrono 在预处理、NNIE 推理、后处理三段各打一个时间戳,打印毫秒数。这类插桩代码几十行,排查性能问题时比猜管用得多。如果预处理就吃掉 30ms,先检查是不是每帧都重新分配 Mat 或反复 malloc;如果 NNIE 推理耗时异常高,先用同一张图在 PC 端跑一遍 CPU 推理,确认 wk 的算子转换没有引入过大开销。
5.2 三个不能放过的检查点
模型在板端能跑只是第一步,输出要和训练端对齐。我会用同一组图片在 PC 端 PyTorch 脚本和板端程序各跑一遍,检查三个点。
第一,置信度列表。把 PC 端输出的 top3 类别 ID 与置信度排序,和板端打印的内容对比。正常情况下类别顺序应该一致,个别目标两个置信度接近时排序互换不算错,但如果类别集合整个对不上,基本是预处理时 swapRB 或归一化参数写错了。第二,坐标映射。板端输出的是模型输入坐标系里的框,要按 letterbox 的 scale 和 pad 还原回原图坐标。这一步最容易把填充边距算错,框会整体向右下偏移。把还原后的坐标和 PC 端比,偏差超过几个像素就要回头查预处理。第三,IoU 一致性。随机选 5 张图,把两端检出的同一个目标做 IoU 计算,大部分框 IoU 超过 0.5 说明链路是健康的;如果多帧全为 0,应该优先查输入尺寸和通道序。置信度阈值和 NMS 阈值这些超参数在两端也要保持一致,不然对比结果会被阈值差异干扰。
从那以后我每次接一款新的海思板子,都会先跑这一遍自检,把 PC 和板端的输出放在同一张画布里对比,再谈 mAP 和帧率,而不是直接把模型丢上去蒙头调。这套 OpenCV 4.1 动态库把最底层的 ABI 和依赖问题先排掉,后面再动 NNIE 算子才有明确方向。希望帮到你。
本文还有配套的精品资源,点击获取