机器人感知 · ROS 2 · 点云与边缘计算

标签: 推理

模型推理

  • YOLOv8 8.2 完整实战:环境搭建、数据准备、训练、推理与边缘部署

    基于仓库 daved1210/yolov8_8.2.0(Ultralytics YOLOv8 8.2.22)整理的中文全流程教程。 从 Conda 环境、VOC→YOLO 标注转换、单卡/多卡训练、验证与视频推理,到 ONNX / TensorRT / RKNN 导出,覆盖日常训练与部署的关键路径。

    技术范围:目标检测、实例分割、姿态估计、图像分类、旋转框检测与多目标跟踪;包含 CLI、Python API、K 折训练、VOC 转换以及 ONNX / TensorRT / RKNN 部署。

    阅读建议:首次使用可按“环境搭建 → 数据准备 → 训练 → 验证 → 推理 → 导出”顺序操作;已有 YOLOv8 经验可直接查看参数速查与边缘部署章节。


    目录

    1. 项目概述:这个仓库实现了什么
    2. 仓库结构与自定义增强
    3. 环境搭建(Windows / Linux / GPU)
    4. 数据集准备与 VOC XML 转 YOLO
    5. 如何训练(单卡 / 多卡 / 断点续训)
    6. K 折交叉验证训练
    7. 模型验证
    8. 推理:图片 / 视频 / 姿态
    9. 模型导出与边缘部署
    10. 关键超参数速查
    11. 常见问题与实践建议
    12. 总结

    一、项目概述:这个仓库实现了什么

    yolov8_8.2.0 是基于 Ultralytics YOLOv8 8.2.x 的完整工程仓库,而不是只有权重文件的 demo。 版本号在源码中声明为 __version__ = "8.2.22",核心能力继承自官方 Ultralytics:

    任务能力说明
    目标检测
    Detect
    边界框 + 类别,适合行人、工件、耳机等目标。
    实例分割
    Segment
    在检测基础上输出像素级掩膜。
    姿态估计
    Pose
    人体关键点检测与跟踪,支持骨骼锁定式预测。
    图像分类
    Classify
    整图类别识别,仓库内含 yolov8s-cls.pt
    旋转框检测
    OBB
    适合航拍、仓库货架等倾斜目标。
    多目标跟踪
    Track
    BoT-SORT / ByteTrack 等跟踪器,可直接跑视频。

    本仓库的“增量价值”: 在官方框架之上,补充了中文参数注释(use.py)、VOC XML→YOLO 转换、标签统计、 10 折交叉验证拆分与训练脚本、测试视频、以及自定义权重目录 权重文件/lxh/ (含 .pt / .onnx / .engine / .xml / .bin 等导出形态),更贴近本地实战与嵌入式部署。

    二、仓库结构与自定义增强

    理解目录,能让训练与导出路径不踩坑:

    路径 / 文件作用
    ultralytics/YOLOv8 核心库:模型、训练、验证、推理、导出、跟踪器、solutions
    use.py中文参数手册 + 训练 / 验证 / 预测 / 导出的可复制 CLI 示例
    xml转YOLO格式.pyPascal VOC XML 标注批量转为 YOLO class x y w h 标签
    查看自定义数据集标签类别及数量.py统计 Annotations 中各类别数量
    k折交叉验证.py / k折训练模型.py10 折拆分数据集并循环训练
    测试.py检查 CUDA / GPU 名称 / torch CUDA 版本
    测试视频/推理用样例视频
    权重文件/lxh/自定义最佳权重及多格式导出产物
    examples/ONNXRuntime、OpenCV、C++、SAHI、Region Counter 等部署示例
    docker/GPU / CPU / Jetson / Conda 等 Dockerfile
    docs/完整文档源(数据集、任务、部署指南)
    数据流(实战视角)
    
    标注(VOC XML / YOLO txt)
            │  xml转YOLO格式.py / 标签统计
            ▼
    数据集 YAML(path / train / val / names)
            │  yolo train 或 k折训练
            ▼
    runs/detect/train*/weights/best.pt
            │  val 评估 → predict 推理
            ▼
    export:ONNX / TensorRT engine / OpenVINO / RKNN
            │
            ▼
    PC 实时推理 · RK3588 · Jetson · 业务系统

    三、环境搭建(Windows / Linux / GPU)

    3.1 推荐软件版本

    组件建议
    Python3.8 – 3.12(仓库声明支持)
    PyTorch≥ 1.8(建议匹配本机 CUDA 的官方 wheel)
    CUDA / cuDNN与显卡驱动、PyTorch 构建版本一致
    系统Windows 10/11、Ubuntu 20.04/22.04 均可
    可选Anaconda / Miniconda、Git、NVIDIA 驱动

    3.2 创建 Conda 环境(仓库注释中的习惯用法)

    步骤 1:创建并激活环境

    conda create -n yolov8_GPU python=3.10 -y
    conda activate yolov8_GPU

    步骤 2:安装 PyTorch(示例:CUDA 11.8)

    pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118

    请按本机 CUDA 版本选择对应命令,见 PyTorch 官网

    步骤 3:安装本仓库(可编辑模式)

    cd yolov8_8.2.0
    pip install -e .
    # 或直接依赖核心包
    # pip install ultralytics

    步骤 4:验证 GPU 是否可用

    仓库提供 测试.py

    python 测试.py

    正常时应看到 True、GPU 名称,以及 torch.version.cuda

    Windows 小贴士: 训练时若 DataLoader 多进程异常,可将 workers=0。 若 OpenMP 冲突,可设置 KMP_DUPLICATE_LIB_OK=TRUE(仓库注释中也有提及)。

    3.3 依赖一览(pyproject.toml)

    核心依赖包括:torchtorchvisionopencv-pythonpillowpyyamlmatplotlibscipypandasseaborntqdmthop 等。导出相关可选依赖:

    • onnx:ONNX 导出
    • openvino:Intel OpenVINO
    • tensorrt(环境单独安装):TensorRT engine
    • 日志集成:TensorBoard / Comet / MLflow 等

    四、数据集准备与 VOC XML 转 YOLO

    4.1 YOLO 检测数据目录约定

    data/
      images/          # 图片:xxx.jpg
      labels/          # 标签:xxx.txt(与图片同名)
      Annotations/     # 可选:VOC XML 原始标注
      ImageSets/       # train.txt / val.txt / test.txt(无扩展名 id)
      people.yaml      # 数据集配置(示例名)
      classes.yaml     # K 折脚本使用的类别名

    每条标签一行,格式为归一化坐标:

    <class_id> <x_center> <y_center> <width> <height>
    # 所有值均相对原图宽高,范围 0~1

    4.2 数据集 YAML 示例

    # data/people.yaml
    path: E:/yolov8/yolov8/data
    train: images/train
    val: images/val
    # test: images/test
    
    names:
      0: person
      1: helmet
      # ...

    4.3 使用仓库脚本:XML → YOLO

    xml转YOLO格式.py 会读取 data/Annotations/*.xmldata/ImageSets/{train,test,val}.txt,写出:

    • data/labels/*.txt:YOLO 标签
    • data/train.txt 等:图片路径列表

    脚本中类别列表需改成你的真实类别,示例默认为耳机检测:

    classes = ['earphone']  # 改成你的类别顺序

    运行:

    python xml转YOLO格式.py

    4.4 标签类别与数量统计

    python 查看自定义数据集标签类别及数量.py
    # 默认统计目录:data/Annotations

    开训前先看类别是否均衡;长尾类别往往需要更多样本、重采样或类别权重策略。

    五、如何训练(单卡 / 多卡 / 断点续训)

    仓库推荐优先使用 CLI(与 use.py 注释一致)。先激活环境:

    conda activate yolov8_GPU

    5.1 单卡训练(最常用)

    yolo task=detect mode=train model=yolov8s.pt data=data/people.yaml batch=-1 epochs=1000 imgsz=640 workers=0 cache=True pretrained=True device=0 patience=1000
    • model=yolov8s.pt:从小/中模型起步更稳;精度优先可换 yolov8m/l/x.pt
    • batch=-1:自动 batch(AutoBatch),按显存自适应
    • cache=True:缓存加速读图(内存够时很香)
    • workers=0:Windows 更稳妥
    • 结果默认写入 runs/detect/train*/,最佳权重为 weights/best.pt

    5.2 从已有 best 权重继续微调

    yolo task=detect mode=train model=runs/detect/train11/weights/best.pt data=data/people.yaml batch=-1 epochs=500 imgsz=640 workers=20 cache=True device=0 patience=200

    5.3 多卡训练

    yolo task=detect mode=train model=yolov8n.pt data=data/people.yaml batch=32 epochs=100 imgsz=640 workers=16 device=0,1,2,3

    5.4 中断恢复(resume)

    yolo train resume model=runs/detect/train4/weights/last.pt

    last.pt 保存优化器状态,适合意外中断后接着跑;迁移到新数据时更常见是加载 best.pt 重新开训。

    5.5 Python API 等价写法

    from ultralytics import YOLO
    
    model = YOLO("yolov8s.pt")  # 或 yolov8s.yaml + 预训练权重
    results = model.train(
        data="data/people.yaml",
        epochs=100,
        imgsz=640,
        batch=-1,
        device=0,
        workers=0,
        cache=True,
        pretrained=True,
    )

    六、K 折交叉验证训练

    小样本场景下,单次 train/val 划分容易“运气好/运气差”。仓库提供 10 折流程:

    1. 修改 k折交叉验证.py 中的 dataset_pathclasses.yaml 路径
    2. 运行拆分:生成 split_1 ... split_10 与各自 yaml,并写入 data/file_paths.txt
    3. 运行 k折训练模型.py 循环训练每一折
    python k折交叉验证.py
    python k折训练模型.py

    训练脚本核心逻辑(简化):

    from ultralytics import YOLO
    model = YOLO("checkpoints/yolov8s.pt", task="train")
    for dataset_yaml in ds_yamls:
        model.train(data=dataset_yaml, batch=-1, epochs=10, imgsz=640,
                    device=0, workers=0, cache=True)

    实践建议: K 折用于评估方法稳定性;最终上线模型可用全量数据再训一版,或挑选验证集表现最好的折权重做集成。

    七、模型验证

    yolo task=detect mode=val model=runs/detect/train/weights/best.pt data=data/people.yaml device=0

    关注指标:

    • mAP50:IoU=0.5 时的平均精度,业务上常用
    • mAP50-95:更严格,衡量定位质量
    • Precision / Recall:误检与漏检权衡
    • 速度:结合后续 export 在目标硬件复测

    验证侧常见参数(见 use.py):

    参数默认说明
    conf0.001(val)置信度阈值;预测时常设 0.25~0.5
    iou0.6NMS IoU 阈值
    max_det300每图最大检测数
    halfTrueFP16 加速(硬件支持时)
    plotsFalse是否输出可视化曲线/混淆矩阵等

    八、推理:图片 / 视频 / 姿态

    8.1 检测推理(视频示例)

    yolo task=detect mode=predict model=runs/detect/train8/weights/best.pt source=测试视频/2.mp4 device=0 conf=0.4 iou=0.5

    指定输入分辨率(例如 1080p 视频):

    yolo task=detect mode=predict model=yolov8x.pt source=1.mp4 device=0 imgsz=1080,1920 conf=0.6

    使用 TensorRT engine 加速:

    yolo task=detect mode=predict model=yolov8x.engine source=1.mp4 device=0 imgsz=1080,1920 conf=0.5

    8.2 姿态估计(骨骼)

    yolo pose predict model=yolov8x-pose.pt source=测试视频/2.mp4

    8.3 Python 推理 API

    from ultralytics import YOLO
    
    model = YOLO("权重文件/lxh/best.pt")  # 或 best.engine / best.onnx
    results = model.predict(source="测试视频/2.mp4", conf=0.4, device=0, save=True)
    
    for r in results:
        boxes = r.boxes   # 框、置信度、类别
        # r.plot() 可得到绘制结果图

    8.4 常用预测参数

    参数含义
    source图片 / 文件夹 / 视频 / 摄像头(如 0
    conf置信度阈值,越高越“保守”
    iouNMS 重叠抑制
    save / save_txt / save_crop保存可视化、txt 标签、裁剪目标
    show实时窗口显示
    classes只保留指定类别,如 classes=0
    line_thickness框线粗细
    vid_stride视频抽帧步长,提速用

    九、模型导出与边缘部署

    训练得到的 best.pt 适合研究迭代;上线通常需要导出:

    # ONNX
    yolo task=detect mode=export model=runs/detect/train/weights/best.pt format=onnx
    
    # TensorRT engine(需本机 TensorRT)
    yolo task=detect mode=export model=runs/detect/train/weights/best.pt format=engine
    
    # 指定动态输入尺寸示例
    yolo task=detect mode=export model=yolov8x.pt format=engine imgsz=1080,1920
    
    # TorchScript
    yolo task=detect mode=export model=yolov8n.pt format=torchscript

    9.1 导出参数速记

    参数说明
    formatonnx / engine / torchscript / openvino
    halfFP16,减体积、提速
    int8INT8 量化,边缘端常用
    dynamic动态输入尺寸(ONNX/TensorRT)
    simplify简化 ONNX 图
    workspaceTensorRT 构建工作空间(GB)

    9.2 RK3588 + RKNN(仓库注释中的路径)

    典型流程: PyTorch .pt → 导出 .onnx → 在瑞芯微 rknn_model_zoo 中量化转换 → 板端推理。

    # 1) 导出 onnx 后,在 Ubuntu + conda 环境中:
    conda activate yolov8
    cd rknn_model_zoo-1.6.0/examples/yolov8/python
    python convert.py yolov8n.onnx rk3588 i8
    # 含义:转换脚本 + onnx 路径 + 芯片型号 + int8 量化

    仓库 权重文件/lxh/ 中同时保留 best.pt / best.onnx / best.engine / best.xml / best.bin,说明该项目已走过多后端导出链路,可按目标芯片选择对应产物。

    9.3 更多部署示例

    examples/ 目录提供 ONNXRuntime(Python/C++/Rust)、OpenCV DNN、LibTorch C++、SAHI 切片推理、区域计数等模板,适合接到实际业务工程。

    十、关键超参数速查

    以下摘自仓库 use.py 中文注释,训练时优先调这几项:

    参数常见起点备注
    epochs100~300小数据可更大并配合 early stop
    imgsz640小目标可试 960/1280,显存与速度换精度
    batch16 或 -1-1 为 AutoBatch
    lr00.01(SGD)Adam 系列通常更小
    lrf0.01最终学习率 = lr0 * lrf
    optimizerSGD / AdamW检测任务 SGD 很常用
    patience50~200验证指标不提升则早停
    close_mosaic10最后 N 个 epoch 关闭马赛克增强
    weight_decay0.0005抑制过拟合
    box / cls / dfl7.5 / 0.5 / 1.5损失权重,类别极不平衡时可微调 cls

    十一、常见问题与实践建议

    Q1. 显存不够?

    • 减小 imgszbatch,或使用 batch=-1
    • 换更小模型:n/s 优先
    • 导出时再考虑 FP16/INT8,而不是先上最大模型硬训

    Q2. Windows 训练 DataLoader 报错?

    workers=0;必要时检查杀毒软件对大量小文件读取的拦截。

    Q3. mAP 上不去?

    • 先用 查看自定义数据集标签类别及数量.py 查类别分布与漏标
    • 确认 XML→YOLO 时 classes 顺序与 yaml 的 names 一致
    • 小目标:提高分辨率、检查标注框质量
    • 过拟合:增强数据、早停、增大训练样本多样性

    Q4. 推理结果框太多 / 太少?

    confiou:框太多升高 conf 或降低 iou 敏感性;漏检则适当降低 conf,并回到验证集看 Recall。

    Q5. 直接用官方 pip 包还是本仓库源码?

    学习与二次开发建议用本仓库可编辑安装;若只做标准训练推理,也可 pip install ultralytics。本仓库的差异主要在中文实战脚本、K 折流程、导出产物与测试素材

    十二、总结

    daved1210/yolov8_8.2.0 把 Ultralytics YOLOv8 8.2 的完整能力,收敛成一套可本地落地的中文实战工程: 环境可复现、数据可转换、训练可恢复、验证可量化、推理可跑视频、导出可对接 TensorRT / RKNN。

    如果你按本文顺序推进,推荐路径是:

    1. 搭好 GPU 环境并用 测试.py 验收
    2. 整理数据 → XML 转换 → 标签统计 → 写好 yaml
    3. 小模型快速 baseline(如 yolov8s)
    4. 看 val 指标,再考虑换大模型或 K 折评估
    5. 按部署目标 export,并在真实视频上回归 conf/iou

    参考仓库:GitHub daved1210/yolov8_8.2.0(私有实践仓)· 上游项目 Ultralytics YOLOv8 · 文档:docs.ultralytics.com
    本文根据仓库 README、源码版本号、自定义脚本(use.py / 数据转换 / K 折 / 导出注释)整理,便于复现实验与部署。

  • RK3588 + D435:YOLOv8 目标检测与深度测距

    RK3588 + D435:YOLOv8 目标检测与深度测距

    — outline: deep


    基于 Rockchip RK3588 NPU(RKNN) 的 YOLOv8 实时目标检测工程,集成 Intel RealSense D435 深度相机、C++ 线程池多实例推理OpenCV/RGA 预处理检测框测距串口联动输出。目标平台为瑞芯微 RK3588(aarch64 / Linux),适合边端视觉感知、设备联动与工程教学。

    对应代码仓库:yolov8_rknn_best_d435

    实际运行效果

    下图为使用本工程在 YOLOv8 + RKNN 链路下对图片做目标检测后的可视化结果(绿色/红色检测框 + 类别名 + 置信度):

    YOLOv8 RKNN 检测结果示例

    运行不同入口后,可得到的典型效果如下:

    运行入口现象说明
    yolov8_img输出 result.jpg单张图片上的检测框与类别标签
    yolov8_video终端打印耗时/FPS,可选 result1.mp4逐帧推理并统计帧率
    yolov8_thread_pool实时窗口显示检测框,可接普通摄像头多线程池提高吞吐,适合视频流
    d435_yolov8_thread_pool彩色窗 + 深度伪彩窗 + 控制滑条检测框中心深度(cm)、多点测距、串口输出

    效果解读

    在 D435 模式下,彩色画面会画出目标框、类别、置信度与中心距离;深度图使用 JET 伪彩色,并在右侧显示对齐偏移、测量点数量等信息。检测结果可通过串口按协议发给下位机。


    项目做什么

    本工程把 模型加载、图像预处理、NPU 推理、后处理 NMS、多线程调度、深度测距、可视化与串口输出 串成一条可直接上手的链路:

    输入源(图片 / 视频 / UVC 摄像头 / D435 RGB)
            │
            ▼
      LetterBox + BGR→RGB 预处理(OpenCV 或 RGA)
            │
            ▼
      RKNN Runtime → RK3588 NPU 推理(.rknn)
            │
            ▼
      YOLOv8 Head 解码 + 置信度筛选 + NMS
            │
            ├─► OpenCV 绘制检测框 / 标签 / FPS
            ├─►(D435)中心点深度采样 → 距离(m/cm)
            └─►(可选)串口发送目标框与距离

    核心能力:

    1. YOLOv8 图片推理:加载 .rknn,输出 result.jpg
    2. YOLOv8 视频推理:逐帧推理,统计单帧耗时与平均 FPS,可录制结果视频
    3. C++ 线程池多实例推理:一线程一模型实例,异步提交帧、按帧号取回结果
    4. D435 深度相机检测 + 测距:彩色/深度对齐、检测框中心深度、多点交互测距
    5. 串口联动:检测结果按文本协议发到 /dev/ttyUSB0(115200)
    6. NPU / RGA / OpenCV 联合加速:模型在 NPU 上跑,预处理可用 RGA,可视化用 OpenCV

    技术栈总览

    层级技术作用
    硬件RK35883 核 NPU,边端 AI 推理
    硬件Intel RealSense D435RGB + 深度流,用于检测与测距
    运行时RKNN Runtime(librknnrt.so加载 .rknn 并在 NPU 上执行
    视觉库OpenCV读图/视频、绘制、LetterBox、窗口 UI
    硬件加速RGA(librga.so可选的硬件 resize / 颜色转换
    深度 SDKlibrealsense2D435 流配置、对齐、深度读取
    语言 / 标准C++14主工程语言
    构建CMake ≥ 3.11生成可执行文件与共享库
    并发std::thread + mutex + condition_variable线程池与结果收集
    外设POSIX 串口(termios检测结果下发

    目录与模块结构

    yolov8_rknn_best_d435/
    ├─ CMakeLists.txt                 # 构建脚本(aarch64 / RK3588)
    ├─ use                            # 常用运行命令与调参备忘
    ├─ media/                         # 示例图片与视频
    ├─ weights/                       # RKNN 模型(float / int 量化)
    ├─ librknn_api/                   # RKNN Runtime 头文件与 so
    ├─ 3rdparty/                      # OpenCV / RGA 等第三方
    └─ src/
       ├─ yolov8_img.cpp              # 图片推理入口
       ├─ yolov8_video.cpp            # 视频推理入口
       ├─ yolov8_thread_pool.cpp      # 多线程视频/摄像头入口
       ├─ d435_yolov8_thread_pool.cpp # D435 深度相机入口
       ├─ engine/                     # NN 引擎抽象 + RKNN 实现
       ├─ process/                    # 预处理 / 后处理
       ├─ task/                       # Yolov8Custom + 线程池
       ├─ draw/                       # 检测框绘制
       ├─ types/                      # 张量、Detection、错误码
       └─ utils/                      # 日志、模型加载辅助

    共享库拆分(CMake 产物):

    库 / 目标源文件职责
    nn_processpreprocess.cpp + postprocess.cppLetterBox、tensor 转换、YOLOv8 解码与 NMS
    rknn_enginerknn_engine.cpp封装 rknn_init / inputs_set / run / outputs_get / destroy
    yolov8_libyolov8_custom.cpp业务层:加载模型 + Preprocess + Inference + Postprocess
    draw_libcv_draw.cpp在图像上画框与标签
    yolov8_imgyolov8_img.cpp图片 Demo
    yolov8_videoyolov8_video.cpp视频 Demo
    yolov8_thread_pool入口 + task/yolov8_thread_pool.cpp线程池视频/摄像头
    d435_yolov8_thread_pool入口 + 线程池实现D435 检测测距(需 librealsense2)

    构建后输出:

    • 可执行文件:build/bin/
    • 动态库:build/lib/

    用到的算法

    1. YOLOv8 目标检测(Anchor-Free 多尺度 Head)

    本工程面向 已导出的 YOLOv8 RKNN 模型,后处理按 3 个检测头 解码:

    项目取值说明
    输入分辨率640 × 640input_w / input_h
    检测头数量3headNum = 3
    特征图尺寸80×80 / 40×40 / 20×20对应 stride 8 / 16 / 32
    类别数默认 80COCO;自定义模型需改 class_num 与类别名表
    置信度阈值objectThreshold = 0.3低于阈值的候选直接丢弃
    NMS IoU 阈值nmsThreshold = 0.15抑制重叠框

    每个网格输出:

    • 回归分支(reg):到左右上下边界的距离,解码为 xmin/ymin/xmax/ymax
    • 分类分支(cls):各类别分数,经 Sigmoid 后取最大类

    解码公式(float 版本示意):

    xmin = (mesh_x - reg_l) * stride
    ymin = (mesh_y - reg_t) * stride
    xmax = (mesh_x + reg_r) * stride
    ymax = (mesh_y + reg_b) * stride

    其中 mesh_x/y 为特征图网格中心(i+0.5, j+0.5)。

    2. 量化感知后处理(INT8 / FLOAT)

    工程同时支持:

    模式函数适用模型
    Float 后处理yolo::GetConvDetectionResultfloat 模型,或希望 Runtime 反量化到 float 的输出
    INT8 后处理yolo::GetConvDetectionResultInt8量化模型,CPU 侧按 zp/scale 反量化

    INT8 反量化:

    value_f32 = (q - zp) * scale

    Yolov8Custom 会根据输出 tensor 类型自动选择:若输出为 float16,则强制 want_float_ = true,让 RKNN 输出 float32 再走 float 后处理。

    3. NMS(Non-Maximum Suppression)

    流程:

    1. 收集所有超过 objectThreshold 的候选框
    2. 按 score 降序排序
    3. 依次保留最高分框,与后续框计算 IoU
    4. IoU > nmsThreshold 的框抑制

    IoU 定义:

    IoU = IntersectionArea / UnionArea

    NMS 后每个框格式为 6 元组:

    [classId, score, xmin_norm, ymin_norm, xmax_norm, ymax_norm]

    坐标为相对输入尺寸的归一化值,再在 Yolov8Custom::Postprocess 中映射回 letterbox 后的图像尺寸,最后经 letterbox_decode 去掉 padding,还原到原图坐标系。

    4. LetterBox 预处理

    为保持宽高比,将原图缩放到适配 640×640 输入,并在短边方向 pad:

    原图 ──缩放保持比例──► 长边贴合 640 ──短边对称/单侧填充──► 640×640

    LetterBoxInfo 记录:

    • hor:是否水平方向 pad
    • pad:填充像素数

    后处理坐标必须做 letterbox 逆变换letterbox_decode),否则框会整体偏移。

    工程支持两种实现:

    后端函数特点
    OpenCVletterbox + cvimg2tensor默认路径,易调试
    RGAletterbox_rga + cvimg2tensor_rga利用 RK 硬件 2D 加速

    默认 Run() 中使用 "opencv",可改为 "rga"

    5. 颜色空间与张量布局

    业务侧准备:

    1. BGR → RGB(OpenCV 读入为 BGR)
    2. Resize / LetterBox 到模型输入
    3. NHWC / UINT8 形式交给 RKNN

    RKNN Runtime 内部可完成归一化与布局转换(与模型导出配置相关),因此 CPU 侧主要做几何变换与颜色顺序。

    6. D435 深度测距算法

    6.1 彩色-深度对齐

    rs2::align align_to_color(RS2_STREAM_COLOR);
    aligned = align_to_color.process(frameset);

    把深度对齐到彩色坐标系,使检测框中心可直接取深度。

    6.2 检测框中心测距

    1. 取检测框中心 (cx, cy)
    2. 叠加对齐偏移 (g_align_offset_x, g_align_offset_y)
    3. 在半径 kDepthSampleRadius = 3 邻域采样
    4. 过滤无效深度(≤0.01 m≥20 m
    5. 距离加权复制 + 中位数,得到稳健距离

    这样可抑制单点噪声、玻璃反射与边缘飞点。

    6.3 多点交互测距

    MeasPoint 支持最多 10 个手动点:

    • 左键添加 / 拖动
    • 右键删除最后一个
    • C 清空
    • + / - 调 X 对齐,[ / ] 调 Y 对齐,R 复位

    7. 置信度 UI 过滤

    D435 界面滑条 Score Threshold(0–100)在绘制阶段二次过滤:

    if (round(confidence * 100) < g_score_threshold) skip

    这与后处理 objectThreshold 是两层:前者在 NMS 内,后者在显示/串口输出前。


    NPU / RKNN 调用详解

    1. 引擎抽象(可替换后端)

    NNEngine 是纯虚接口:

    class NNEngine {
    public:
        virtual ~NNEngine() {};
        virtual nn_error_e LoadModelFile(const char *model_file) = 0;
        virtual const std::vector<tensor_attr_s> &GetInputShapes() = 0;
        virtual const std::vector<tensor_attr_s> &GetOutputShapes() = 0;
        virtual nn_error_e Run(std::vector<tensor_data_s> &inputs,
                               std::vector<tensor_data_s> &outputs,
                               bool want_float) = 0;
    };

    RKEngine 继承并实现;工厂函数:

    std::shared_ptr<NNEngine> CreateRKNNEngine();

    好处:业务层 Yolov8Custom 只依赖接口,不直接散落 RKNN API,便于维护与扩展。

    2. 模型加载流程

    RKEngine::LoadModelFile

    1. load_model().rknn 读入内存
    2. rknn_init(&ctx, model, model_len, 0, NULL) 创建 NPU 上下文
    3. rknn_query(SDK_VERSION) 打印 API / Driver 版本
    4. rknn_query(IN_OUT_NUM) 得到输入输出个数
    5. 查询每个 input/output 的 rknn_tensor_attr(dims、type、zp、scale、layout)
    6. 转为工程内部 tensor_attr_s

    代码中预留了 rknn_set_core_mask(RKNN_NPU_CORE_AUTO)(注释状态),可按 SDK 版本开启以提升多核 NPU 利用率。

    3. 单次推理流程

    RKEngine::Run

    校验 IO 数量
       → tensor_data_to_rknn_input
       → rknn_inputs_set
       → rknn_run          // NPU 真正执行
       → rknn_outputs_get  // want_float 控制是否 Runtime 反量化
       → 拷贝到 output tensors
       → free(rknn_outputs[i].buf)

    4. 业务层一次完整检测

    Yolov8Custom::Run

    Preprocess(letterbox + BGR2RGB + 填 input_tensor)
       → Inference()  // engine_->Run
       → Postprocess() // 解码 + NMS → Detection 列表
       → letterbox_decode() // 坐标回到原图

    5. 与 NPU 相关的性能建议

    建议说明
    使用 INT8 量化模型*.int.rknn 通常更快、更省带宽
    线程数贴近 NPU 能力D435 入口默认 kThreadPoolSize = 9;可按负载调节
    控制任务队列长度线程池 tasks.size() > 10 时提交端 sleep,防内存暴涨
    监控 NPU / RGA 负载watch -n 1 cat /sys/kernel/debug/rknpu/load /sys/kernel/debug/rkrga/load
    避免每帧重复 rknn_init线程池启动时一次性为每线程创建模型实例

    C++ 线程池设计与实现

    核心类:Yolov8ThreadPoolsrc/task/yolov8_thread_pool.h/.cpp

    1. 设计目标

    • 提高吞吐:视频流连续进帧,多路 NPU 上下文并行推理
    • 顺序可回取:每帧带 id,结果按 id 查询,便于显示线程对齐
    • 一线程一模型:每个 worker 绑定独立 Yolov8Custom / rknn_context,避免同一 ctx 并发不安全

    2. 内部数据结构

    成员类型作用
    tasksqueue<pair<int, cv::Mat>>待推理任务(帧号 + 图像)
    Yolov8_instancesvector<shared_ptr<Yolov8Custom>>每线程一个模型实例
    resultsmap<int, vector<Detection>>帧号 → 检测框
    img_resultsmap<int, cv::Mat>帧号 → 已绘制图像
    threadsvector<thread>worker 线程
    mtx1mutex保护任务队列
    mtx2mutex保护结果 map
    cv_taskcondition_variable任务到达通知
    stopbool停止标志

    3. 初始化 setUp(model_path, num_threads)

    for i in [0, num_threads):
        创建 Yolov8Custom
        LoadModel(model_path)     // 各自 rknn_init
        放入 Yolov8_instances
    
    for i in [0, num_threads):
        启动 thread(worker, i)

    注意:多实例会占用多份 NPU 上下文与内存,线程数不是越大越好。

    4. Worker 循环

    while (!stop):
        unique_lock(mtx1)
        cv_task.wait( 任务非空 || stop )
        if stop: return
        取 tasks.front() 并 pop
        unlock
        instance->Run(img, detections)   // 耗时推理在锁外
        lock(mtx2)
        results[id] = detections
        DrawDetections(img, detections)
        img_results[id] = img

    要点:

    • 推理在锁外,只保护队列与结果表,减少锁竞争
    • 条件变量避免空转轮询任务队列(提交侧仍用短 sleep 做背压)

    5. 提交任务 submitTask(img, id)

    while (tasks.size() > 10) sleep 3ms   // 背压,防止积压
    lock(mtx1); tasks.push({id, img}); unlock
    cv_task.notify_one()

    主线程(读摄像头 / D435)持续 submitTask,与 worker 解耦。

    6. 取结果

    • getTargetResult(objects, id):阻塞等到该 id 出现,取出检测框并 erase
    • getTargetImgResult(img, id):取已画框图像;超时约 5s 则失败,避免死等

    7. 与入口程序的协作模型

    普通线程池入口(yolov8_thread_pool.cpp

    线程 A: read_stream  ──submitTask(frame, id)──► 线程池 workers
    线程 B: get_results  ◄──getTargetImgResult(id)── 结果 map
    主线程: join A/B

    D435 入口(d435_yolov8_thread_pool.cpp

    主循环: wait_for_frames → align → submitTask(color)
    N 个 collector 线程: getTargetResult → 写入 g_detection_results
    主循环: 取最新检测结果 → 深度采样 → 绘制 → imshow → 串口发送

    D435 还用:

    • std::atomic<int> g_next_frame_id / g_last_processed_id
    • kMaxPendingTasks = 18 限制在途帧
    • 结果 deque 超限时丢最旧结果,保证实时性优先

    8. 停止与析构

    void stopAll() { stop = true; cv_task.notify_all(); }
    
    ~Yolov8ThreadPool() {
        stop = true;
        cv_task.notify_all();
        for (auto &t : threads) if (t.joinable()) t.join();
    }

    析构 join 所有 worker,避免悬空线程;模型实例由 shared_ptr 自动释放。


    RAII 机制在本工程中的体现

    RAII(Resource Acquisition Is Initialization)即:资源获取即初始化,对象生命周期结束即自动释放

    1. RKNN 上下文

    RKEngine::~RKEngine() {
        if (ctx_created_) {
            rknn_destroy(rknn_ctx_);
        }
    }

    rknn_init 成功则置 ctx_created_ = true;对象销毁时必定 rknn_destroy,避免 NPU 上下文泄漏。

    2. 输入 / 输出 tensor 内存

    Yolov8Custom 构造时 input_tensor_.data = nullptrLoadModelmalloc 输入输出缓冲;析构中:

    if (input_tensor_.data) { free(...); data = nullptr; }
    for (auto &t : output_tensors_) if (t.data) { free(...); }

    即使中途异常返回,只要对象正常析构,缓冲区会被回收。

    3. shared_ptr 管理引擎与模型实例

    engine_ = CreateRKNNEngine();                 // shared_ptr<NNEngine>
    std::shared_ptr<Yolov8Custom> Yolov8 = ...;  // 线程池实例

    引用计数归零时自动调用析构链:Yolov8Custom → 释放 tensor → RKEnginerknn_destroy

    4. 锁的 RAII

    std::unique_lock<std::mutex> lock(mtx1);  // wait 需要 unique_lock
    std::lock_guard<std::mutex> lock(mtx2);  // 作用域结束自动 unlock

    异常路径也不会漏解锁。

    5. 串口与 RealSense 资源

    D435 主程序:

    • 启动时 openSerialPort("/dev/ttyUSB0", B115200)
    • 退出前 if (serial_fd != -1) close(serial_fd)
    • 关闭激光发射器(RS2_OPTION_EMITTER_ENABLED = 0
    • pool.stopAll() + join collector 线程
    • cv::destroyAllWindows()

    串口打开失败不终止检测主流程,属于 容错设计

    6. OpenCV / RealSense 帧

    color.clone() 再提交任务,避免 RealSense 内部缓冲复用导致数据竞争;深度帧在本帧处理周期内使用,不跨线程长期持有裸指针缓冲。


    数据结构与错误码

    Detection

    struct Detection {
        int class_id;
        std::string className;
        float confidence;
        cv::Scalar color;
        cv::Rect box;   // 原图坐标系像素框
    };

    张量描述

    tensor_attr_s  // dims / type / layout / zp / scale / size
    tensor_data_s  // attr + void* data

    布局枚举:NN_TENSOR_NCHW / NN_TENSOR_NHWC 类型枚举:INT8 / UINT8 / FLOAT / FLOAT16

    错误码(nn_error_e

    覆盖模型加载失败、rknn_init 失败、query 失败、IO 数量不匹配、输入设置失败、推理失败、输出获取失败等,便于日志定位。


    实现了什么功能

    1. 图片检测

    入口:src/yolov8_img.cpp

    • 读取图片 → Yolov8Custom::RunDrawDetections → 写 result.jpg

    2. 视频检测与性能统计

    入口:src/yolov8_video.cpp

    • 逐帧推理
    • Method1:单帧读图耗时 / 推理耗时 / 总耗时与瞬时 FPS
    • Method2:每秒处理帧数的平均 FPS
    • 可选第三个参数开启录制 result1.mp4

    3. 多线程实时检测

    入口:src/yolov8_thread_pool.cpp + task/yolov8_thread_pool.cpp

    • 读流线程 + 结果线程 + N 个推理 worker
    • 支持视频文件或摄像头(当前代码默认 deviceID = 0,640×480@60,MJPG)
    • 窗口显示,按 q 退出

    4. D435 检测 + 深度 + UI + 串口

    入口:src/d435_yolov8_thread_pool.cpp

    能力细节
    流配置Color/Depth 640×480,默认 60 FPS,Z16 + BGR8
    对齐Depth → Color
    传感器视觉预设、激光功率、曝光、帧队列深度可配
    推理9 线程池(可改 kThreadPoolSize
    显示Color Detection / Depth Map / Controls 三窗口
    滑条Score、Laser Power、Align X/Y Offset
    测距框中心自动距离 + 最多 10 个手动点
    串口检测结果文本协议输出
    退出q / ESC,安全关串口、关激光、停线程池

    5. 串口协议

    波特率:115200 设备:/dev/ttyUSB0

    正常帧示例:

    START,Class Id: 0, Class Name: person, Box: 120,80,200,360, Distance: 135.2cm,END

    字段:

    字段含义
    Class Id类别编号
    Class Name类别名
    Boxx,y,width,height(像素)
    Distance中心点深度(厘米)

    无效框防呆会发送 9999,9999,9999,9999


    环境依赖与构建

    1. 硬件 / 系统

    • 开发板:RK3588(aarch64)
    • 系统:Linux(常见 Debian/Ubuntu 系板卡系统)
    • 可选:Intel RealSense D435 + USB3
    • 可选:串口设备 /dev/ttyUSB0

    2. 软件依赖

    依赖是否必须说明
    CMake ≥ 3.11必须构建
    C++14 编译器必须g++ 等
    OpenCV必须工程 find_package(OpenCV)
    RKNN Runtime必须工程内 librknn_api/aarch64/librknnrt.so
    RGA必须(链接)3rdparty/rga/RK3588/.../librga.so
    pthread必须线程池
    librealsense2D435 可选缺失则 不编译 d435_yolov8_thread_pool
    pkg-config推荐查找 librealsense2

    3. 构建命令

    在工程根目录:

    cmake -S . -B build
    cmake --build build -j8

    成功后检查:

    ls build/bin
    # 期望至少有:yolov8_img  yolov8_video  yolov8_thread_pool
    # 若安装了 librealsense2,还有:d435_yolov8_thread_pool

    CMake 会打印:

    • OpenCV include
    • RKNN API path
    • D435 support ENABLED / DISABLED
    • 可执行文件与库输出目录

    4. 运行时库路径

    若运行时报找不到 .so,可:

    export LD_LIBRARY_PATH=$PWD/build/lib:$PWD/librknn_api/aarch64:$PWD/3rdparty/rga/RK3588/lib/Linux/aarch64:$LD_LIBRARY_PATH

    D435 还需系统已正确安装 librealsense2 与 udev 规则。


    如何运行

    以下命令默认在工程根目录执行,权重与媒体路径可按实际修改。

    1. 图片推理

    ./build/bin/yolov8_img ./weights/yolov8s.float.rknn ./media/000057.jpg

    输出:当前目录 result.jpg

    2. 视频推理

    ./build/bin/yolov8_video ./weights/yolov8s.float.rknn ./media/bj_short.mp4

    录制结果:

    ./build/bin/yolov8_video ./weights/yolov8s.float.rknn ./media/bj_short.mp4 1

    输出:终端 FPS 日志;可选 result1.mp4

    3. 多线程视频 / 摄像头

    # 参数:模型  视频路径占位/设备说明  线程数
    ./build/bin/yolov8_thread_pool ./weights/yolov8s.int.rknn ./media/bj_full.mp4 6

    说明:

    • 当前 read_stream 默认打开 摄像头 0(视频读取代码被注释,可按需切换)
    • 最后一个参数为线程池大小(示例 6
    • 窗口按 q 退出

    摄像头相关设置(代码内):

    640×480, MJPG, 60 FPS

    4. D435 深度相机(推荐完整演示)

    ./build/bin/d435_yolov8_thread_pool ./weights/yolov8s.int.rknn

    启动后将看到:

    1. Color Detection:彩色图 + 检测框 + 类别 + 置信度 + 中心距离(cm)
    2. Depth Map:深度伪彩 + 右侧信息面板
    3. Controls:Score / 激光功率 / 对齐偏移滑条

    交互快捷键

    操作功能
    鼠标左键添加测量点(最多 10)或选中拖动
    鼠标右键删除最后一个测量点
    C清空所有测量点
    + / -对齐 X 偏移 +1 / -1
    ] / [对齐 Y 偏移 +1 / -1
    R对齐偏移归零
    q / ESC退出程序

    运行后得到什么

    输出内容
    实时可视化目标框、类别、置信度、距离
    深度图JET 伪彩,便于观察场景结构
    终端日志线程池启动信息、串口状态
    串口数据每目标一行 START,...END 协议文本
    性能指示画面左上角 FPS

    5. 监控 NPU / RGA

    watch -n 1 cat /sys/kernel/debug/rknpu/load /sys/kernel/debug/rkrga/load

    可用于对比 float / int 模型、不同线程数下的 NPU 利用率。


    模型与类别配置

    1. 仓库内常见权重

    weights/ 目录示例:

    文件类型典型用途
    yolov8s.float.rknnFloat精度参考、调试后处理
    yolov8s.int.rknnINT8实时部署常用
    yolov8_m_int.rknn / yolov8_x_int.rknnINT8更大模型,精度↑速度↓
    face_*.int.rknnINT8人脸等自定义场景
    earphone_*.rknnINT8耳机等单类/少类示例

    2. 更换类别数

    若模型类别不是 80:

    1. 修改 src/process/postprocess.cpp
    static int class_num = 1; // 改为你的类别数
    1. 修改 src/task/yolov8_custom.cppg_classes 名称表,使其与训练标签顺序一致。
    1. 若输入尺寸不是 640,需同步:
    static int input_w = 640;
    static int input_h = 640;
    static int mapSize[3][2] = {{80,80},{40,40},{20,20}};
    static int strides[3] = {8,16,32};

    3. 调节检测阈值

    • 后处理阈值postprocess.cppobjectThreshold(默认 0.3)
    • NMS 阈值nmsThreshold(默认 0.15)
    • D435 显示阈值:界面滑条 Score Threshold

    4. 检测框颜色

    yolov8_custom.cppPostprocess 中:

    result.color = cv::Scalar(0, 0, 255); // BGR:红色

    可改为固定色,或恢复注释掉的随机色逻辑。


    端到端数据流总览

                        ┌────────────────────┐
                        │  .rknn 模型权重     │
                        └─────────┬──────────┘
                                  │ LoadModel / rknn_init
                                  ▼
    图片/视频/相机/D435 RGB ──► LetterBox/RGA ──► input_tensor(NHWC u8)
                                  │
                                  ▼
                         rknn_inputs_set + rknn_run
                                  │
                                  ▼
                         3 个 Head 输出 (reg/cls × 3)
                                  │
                                  ▼
                         Sigmoid / Dequant + 阈值过滤
                                  │
                                  ▼
                               NMS 去重
                                  │
                                  ▼
                         Detection{box, class, score}
                                  │
                  ┌───────────────┼────────────────┐
                  ▼               ▼                ▼
             绘制窗口显示     深度中心测距        串口协议发送
             FPS 统计        多点手动测距       下位机联动

    线程池视角:

    [采集线程]  frame_id++  submitTask(img, id)
                    │
                    ▼
            tasks 队列(有界背压)
             │    │    │
             ▼    ▼    ▼
          worker0 worker1 ... workerN-1
          (独立 Yolov8Custom / 独立 rknn_ctx)
             │    │    │
             └────┴────┘
                    │
                    ▼
            results / img_results  (按 id)
                    │
                    ▼
            [展示/测距/串口线程]

    部署检查清单

    1. 确认板卡架构为 aarch64,与 librknn_api/aarch64 一致
    2. 编译通过build/bin 中有目标程序
    3. 权重路径正确.rknn 与类别配置匹配
    4. 摄像头 / D435 权限:用户加入 video/plugdev 等组,必要时 udev 规则
    5. USB3 连接 D435rs-enumerate-devices 能看到设备
    6. 串口权限/dev/ttyUSB0 可读写(dialout 组)
    7. LD_LIBRARY_PATH 包含 RKNN / RGA / 自建 lib
    8. 先跑 yolov8_img 验证模型与后处理,再跑线程池与 D435

    最小验证路径:

    # 1) 静态图片
    ./build/bin/yolov8_img ./weights/yolov8s.int.rknn ./media/bus.jpg
    
    # 2) 线程池摄像头
    ./build/bin/yolov8_thread_pool ./weights/yolov8s.int.rknn 0 6
    
    # 3) D435 全功能
    ./build/bin/d435_yolov8_thread_pool ./weights/yolov8s.int.rknn

    常见问题速查

    现象优先处理
    找不到 librknnrt.so / librga.so配置 LD_LIBRARY_PATH,确认 aarch64 库
    CMake 提示 D435 DISABLED安装 librealsense2,保证 pkg-config --libs librealsense2 可用
    D435 打不开流USB3、供电、占用进程、rs-enumerate-devices
    检测框整体偏移检查 letterbox 逆变换;D435 下调 Align X/Y
    距离跳动大增大采样半径、避开强反光/透明物体,调激光功率
    类别名错乱g_classes 顺序与训练标签不一致
    自定义模型几乎无检出检查 class_num、输入尺寸、mapSize/strides、阈值
    线程数很大反而变慢NPU/内存打满;降到 3–9 试验
    串口无数据设备节点、权限、线序;程序允许串口失败仍可继续检测
    画面卡顿降低分辨率/FPS、用 int 模型、减小线程池排队

    关键源码索引

    主题路径
    RKNN 引擎封装src/engine/rknn_engine.cpp
    引擎接口src/engine/engine.h
    YOLOv8 业务类src/task/yolov8_custom.cpp
    线程池src/task/yolov8_thread_pool.cpp
    LetterBox / RGAsrc/process/preprocess.cpp
    解码 + NMSsrc/process/postprocess.cpp
    画框src/draw/cv_draw.cpp
    D435 + 测距 + 串口src/d435_yolov8_thread_pool.cpp
    构建脚本CMakeLists.txt
    命令备忘use

    小结

    本工程在 RK3588 上打通了从 RKNN 模型部署实时多线程推理,再到 D435 深度测距与串口联动 的完整路径。读完本文并完成构建后,你应能:

    1. 理解 YOLOv8 在 NPU 上的预处理 / 推理 / 后处理全链路
    2. 看懂并配置 C++ 线程池多实例推理
    3. 理解 RAII 如何管理 rknn_context、tensor 内存、锁与串口
    4. 独立运行图片、视频、摄像头、D435 四类入口
    5. 根据自定义模型修改类别数、阈值与框样式
    6. 将检测框与距离通过串口送给下位机或其它业务模块

    更细的仓库说明也可参考工程内 README.md 与 GitHub Wiki。若你接下来要接 ROS 2、多相机或跟踪模块,可在本检测链路输出的 Detection + distance 之上继续扩展。