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

分类: 机器人感知

点云定位、多目标跟踪与机器人感知相关笔记

  • 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 之上继续扩展。