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

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

基于 Rockchip RK3588 NPU(RKNN)的 YOLOv8 实时目标检测工程,集成 Intel …

YOLOv8 RKNN 检测结果示例

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

评论交流

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注