— outline: deep
基于 Rockchip RK3588 NPU(RKNN) 的 YOLOv8 实时目标检测工程,集成 Intel RealSense D435 深度相机、C++ 线程池多实例推理、OpenCV/RGA 预处理、检测框测距 与 串口联动输出。目标平台为瑞芯微 RK3588(aarch64 / Linux),适合边端视觉感知、设备联动与工程教学。
对应代码仓库:yolov8_rknn_best_d435。
实际运行效果
下图为使用本工程在 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)
└─►(可选)串口发送目标框与距离
核心能力:
- YOLOv8 图片推理:加载
.rknn,输出result.jpg - YOLOv8 视频推理:逐帧推理,统计单帧耗时与平均 FPS,可录制结果视频
- C++ 线程池多实例推理:一线程一模型实例,异步提交帧、按帧号取回结果
- D435 深度相机检测 + 测距:彩色/深度对齐、检测框中心深度、多点交互测距
- 串口联动:检测结果按文本协议发到
/dev/ttyUSB0(115200) - NPU / RGA / OpenCV 联合加速:模型在 NPU 上跑,预处理可用 RGA,可视化用 OpenCV
技术栈总览
| 层级 | 技术 | 作用 |
|---|---|---|
| 硬件 | RK3588 | 3 核 NPU,边端 AI 推理 |
| 硬件 | Intel RealSense D435 | RGB + 深度流,用于检测与测距 |
| 运行时 | RKNN Runtime(librknnrt.so) | 加载 .rknn 并在 NPU 上执行 |
| 视觉库 | OpenCV | 读图/视频、绘制、LetterBox、窗口 UI |
| 硬件加速 | RGA(librga.so) | 可选的硬件 resize / 颜色转换 |
| 深度 SDK | librealsense2 | D435 流配置、对齐、深度读取 |
| 语言 / 标准 | 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_process | preprocess.cpp + postprocess.cpp | LetterBox、tensor 转换、YOLOv8 解码与 NMS |
rknn_engine | rknn_engine.cpp | 封装 rknn_init / inputs_set / run / outputs_get / destroy |
yolov8_lib | yolov8_custom.cpp | 业务层:加载模型 + Preprocess + Inference + Postprocess |
draw_lib | cv_draw.cpp | 在图像上画框与标签 |
yolov8_img | yolov8_img.cpp | 图片 Demo |
yolov8_video | yolov8_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 × 640 | input_w / input_h |
| 检测头数量 | 3 | headNum = 3 |
| 特征图尺寸 | 80×80 / 40×40 / 20×20 | 对应 stride 8 / 16 / 32 |
| 类别数 | 默认 80 | COCO;自定义模型需改 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::GetConvDetectionResult | float 模型,或希望 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)
流程:
- 收集所有超过
objectThreshold的候选框 - 按 score 降序排序
- 依次保留最高分框,与后续框计算 IoU
- 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:是否水平方向 padpad:填充像素数
后处理坐标必须做 letterbox 逆变换(letterbox_decode),否则框会整体偏移。
工程支持两种实现:
| 后端 | 函数 | 特点 |
|---|---|---|
| OpenCV | letterbox + cvimg2tensor | 默认路径,易调试 |
| RGA | letterbox_rga + cvimg2tensor_rga | 利用 RK 硬件 2D 加速 |
默认 Run() 中使用 "opencv",可改为 "rga"。
5. 颜色空间与张量布局
业务侧准备:
- BGR → RGB(OpenCV 读入为 BGR)
- Resize / LetterBox 到模型输入
- 以 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 检测框中心测距
- 取检测框中心
(cx, cy) - 叠加对齐偏移
(g_align_offset_x, g_align_offset_y) - 在半径
kDepthSampleRadius = 3邻域采样 - 过滤无效深度(
≤0.01 m或≥20 m) - 距离加权复制 + 中位数,得到稳健距离
这样可抑制单点噪声、玻璃反射与边缘飞点。
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:
load_model()把.rknn读入内存rknn_init(&ctx, model, model_len, 0, NULL)创建 NPU 上下文rknn_query(SDK_VERSION)打印 API / Driver 版本rknn_query(IN_OUT_NUM)得到输入输出个数- 查询每个 input/output 的
rknn_tensor_attr(dims、type、zp、scale、layout) - 转为工程内部
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++ 线程池设计与实现
核心类:Yolov8ThreadPool(src/task/yolov8_thread_pool.h/.cpp)
1. 设计目标
- 提高吞吐:视频流连续进帧,多路 NPU 上下文并行推理
- 顺序可回取:每帧带
id,结果按 id 查询,便于显示线程对齐 - 一线程一模型:每个 worker 绑定独立
Yolov8Custom/rknn_context,避免同一 ctx 并发不安全
2. 内部数据结构
| 成员 | 类型 | 作用 |
|---|---|---|
tasks | queue<pair<int, cv::Mat>> | 待推理任务(帧号 + 图像) |
Yolov8_instances | vector<shared_ptr<Yolov8Custom>> | 每线程一个模型实例 |
results | map<int, vector<Detection>> | 帧号 → 检测框 |
img_results | map<int, cv::Mat> | 帧号 → 已绘制图像 |
threads | vector<thread> | worker 线程 |
mtx1 | mutex | 保护任务队列 |
mtx2 | mutex | 保护结果 map |
cv_task | condition_variable | 任务到达通知 |
stop | bool | 停止标志 |
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 出现,取出检测框并 erasegetTargetImgResult(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_idkMaxPendingTasks = 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 = nullptr;LoadModel 中 malloc 输入输出缓冲;析构中:
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 → RKEngine → rknn_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::Run→DrawDetections→ 写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 | 类别名 |
| Box | x,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 | 必须 | 线程池 |
| librealsense2 | D435 可选 | 缺失则 不编译 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
启动后将看到:
- Color Detection:彩色图 + 检测框 + 类别 + 置信度 + 中心距离(cm)
- Depth Map:深度伪彩 + 右侧信息面板
- 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.rknn | Float | 精度参考、调试后处理 |
yolov8s.int.rknn | INT8 | 实时部署常用 |
yolov8_m_int.rknn / yolov8_x_int.rknn | INT8 | 更大模型,精度↑速度↓ |
face_*.int.rknn | INT8 | 人脸等自定义场景 |
earphone_*.rknn | INT8 | 耳机等单类/少类示例 |
2. 更换类别数
若模型类别不是 80:
- 修改
src/process/postprocess.cpp:
static int class_num = 1; // 改为你的类别数
- 修改
src/task/yolov8_custom.cpp中g_classes名称表,使其与训练标签顺序一致。
- 若输入尺寸不是 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.cpp中objectThreshold(默认 0.3) - NMS 阈值:
nmsThreshold(默认 0.15) - D435 显示阈值:界面滑条
Score Threshold
4. 检测框颜色
在 yolov8_custom.cpp 的 Postprocess 中:
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)
│
▼
[展示/测距/串口线程]
部署检查清单
- 确认板卡架构为 aarch64,与
librknn_api/aarch64一致 - 编译通过,
build/bin中有目标程序 - 权重路径正确,
.rknn与类别配置匹配 - 摄像头 / D435 权限:用户加入
video/plugdev等组,必要时udev规则 - USB3 连接 D435,
rs-enumerate-devices能看到设备 - 串口权限:
/dev/ttyUSB0可读写(dialout组) - LD_LIBRARY_PATH 包含 RKNN / RGA / 自建 lib
- 先跑
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 / RGA | src/process/preprocess.cpp |
| 解码 + NMS | src/process/postprocess.cpp |
| 画框 | src/draw/cv_draw.cpp |
| D435 + 测距 + 串口 | src/d435_yolov8_thread_pool.cpp |
| 构建脚本 | CMakeLists.txt |
| 命令备忘 | use |
小结
本工程在 RK3588 上打通了从 RKNN 模型部署 到 实时多线程推理,再到 D435 深度测距与串口联动 的完整路径。读完本文并完成构建后,你应能:
- 理解 YOLOv8 在 NPU 上的预处理 / 推理 / 后处理全链路
- 看懂并配置 C++ 线程池多实例推理
- 理解 RAII 如何管理
rknn_context、tensor 内存、锁与串口 - 独立运行图片、视频、摄像头、D435 四类入口
- 根据自定义模型修改类别数、阈值与框样式
- 将检测框与距离通过串口送给下位机或其它业务模块
更细的仓库说明也可参考工程内 README.md 与 GitHub Wiki。若你接下来要接 ROS 2、多相机或跟踪模块,可在本检测链路输出的 Detection + distance 之上继续扩展。
发表回复