182 lines
4.8 KiB
Markdown
182 lines
4.8 KiB
Markdown
# RK3588 YOLOv8 模型输出格式修复记录
|
||
|
||
## 问题现象
|
||
|
||
使用新转换的 RKNN 模型 (`ppe_det_yolov8_ppe11_768_rk3588.rknn`) 进行推理时,出现以下异常现象:
|
||
|
||
1. **所有检测框的置信度分数相同** - 均为 `0.998047`
|
||
2. **框坐标异常** - 大量框为全图 `(0,0,768,768)` 或包含 `inf` 值
|
||
3. **部分帧无检测** - `valid_count=0`,`global_max_score=0.000580`
|
||
|
||
### 错误日志示例
|
||
```
|
||
[ai_yolo] raw box i=0 cx=inf cy=inf w=inf h=inf score=0.998047 cls=0
|
||
[ai_yolo] raw box i=1 cx=inf cy=inf w=inf h=inf score=0.998047 cls=0
|
||
[ai_yolo] det: cls=0 score=0.998047 bbox=(0.000000,0.000000,768.000000,768.000000)
|
||
```
|
||
|
||
## 根本原因
|
||
|
||
### 1. RKNN 输出配置错误 (`src/ai_scheduler.cpp`)
|
||
|
||
原代码存在三个关键问题:
|
||
|
||
#### 问题 A: 使用 INT8 原始输出
|
||
```cpp
|
||
// 错误代码
|
||
outputs[i].want_float = 0; // 获取 INT8 量化后的原始数据
|
||
```
|
||
|
||
当 `want_float=0` 时,RKNN 返回 INT8 量化数据,但代码错误地将其当作 FP16 解析。
|
||
|
||
#### 问题 B: 硬编码输出类型
|
||
```cpp
|
||
// 错误代码
|
||
out.type = RKNN_TENSOR_FLOAT16; // 硬编码为 FP16,与实际数据类型不符
|
||
```
|
||
|
||
实际获取的是 INT8 数据(`type=2`),但标记为 FP16(`type=1`),导致后续解析逻辑错误。
|
||
|
||
#### 问题 C: 缓冲区大小不足
|
||
```cpp
|
||
// 错误代码
|
||
uint32_t out_sz = ctx->output_attrs[j].n_elems * sizeof(uint16_t); // 只分配 2字节/元素
|
||
```
|
||
|
||
当 `want_float=1` 时,RKNN 返回 FP32(4字节/元素),但缓冲区只分配了 FP16 大小(2字节/元素),导致数据截断。
|
||
|
||
### 2. 数值解释
|
||
|
||
`0.998047` 是 FP16 值 `0x3BFF` 转换为 FP32 后的结果:
|
||
- FP16: `0x3BFF` = 0.99951171875
|
||
- 由于所有 class scores 被错误解析为相同的 FP16 值,导致所有检测分数相同
|
||
|
||
## 解决方法
|
||
|
||
### 修复 1: 启用 FP32 输出 (ai_scheduler.cpp:620)
|
||
|
||
```cpp
|
||
// 修改前
|
||
outputs[i].want_float = 0;
|
||
|
||
// 修改后
|
||
outputs[i].want_float = 1; // 请求 FP32 输出,RKNN 自动完成反量化
|
||
```
|
||
|
||
### 修复 2: 正确标记输出类型 (ai_scheduler.cpp:647)
|
||
|
||
```cpp
|
||
// 修改前
|
||
out.type = RKNN_TENSOR_FLOAT16;
|
||
|
||
// 修改后
|
||
out.type = RKNN_TENSOR_FLOAT32; // 与 want_float=1 对应
|
||
```
|
||
|
||
### 修复 3: 分配正确大小的缓冲区 (ai_scheduler.cpp:220)
|
||
|
||
```cpp
|
||
// 修改前
|
||
uint32_t out_sz = ctx->output_attrs[j].n_elems * sizeof(uint16_t);
|
||
|
||
// 修改后
|
||
uint32_t out_sz = ctx->output_attrs[j].n_elems * sizeof(float); // 4字节/元素
|
||
```
|
||
|
||
## 修复后的效果
|
||
|
||
### 正常检测输出
|
||
```
|
||
[ALARM][info] detections=[{cls=6 score=0.53 bbox=(129,344,427,407)}]
|
||
[ALARM][info] detections=[{cls=6 score=0.43 bbox=(73,316,382,452)}]
|
||
[ALARM][info] detections=[{cls=6 score=0.52 bbox=(108,315,299,453)}]
|
||
```
|
||
|
||
### 关键改进
|
||
| 指标 | 修复前 | 修复后 |
|
||
|------|--------|--------|
|
||
| 分数分布 | 固定 0.998047 | 0.43 ~ 0.53 变化 |
|
||
| 坐标范围 | 全图或 inf | 合理的 (x,y,w,h) |
|
||
| 有效检测 | valid_count=0 或异常 | 稳定的有效检测 |
|
||
|
||
## 模型转换注意事项
|
||
|
||
### PC 端导出 ONNX
|
||
```python
|
||
from ultralytics import YOLO
|
||
|
||
model = YOLO('best.pt')
|
||
model.export(
|
||
format='onnx',
|
||
imgsz=768,
|
||
opset=12,
|
||
simplify=True,
|
||
dynamic=False,
|
||
)
|
||
```
|
||
|
||
### PC 端转换为 RKNN
|
||
```python
|
||
from rknn.api import RKNN
|
||
|
||
rknn = RKNN()
|
||
rknn.config(
|
||
target_platform='rk3588',
|
||
mean_values=[[0.0, 0.0, 0.0]],
|
||
std_values=[[255.0, 255.0, 255.0]],
|
||
)
|
||
|
||
rknn.load_onnx(
|
||
model='best.onnx',
|
||
input_size_list=[[1, 3, 768, 768]] # NCHW 格式
|
||
)
|
||
|
||
rknn.build(do_quantization=False) # 先测试 FP16
|
||
rknn.export_rknn('ppe_det_yolov8_ppe11_768_rk3588.rknn')
|
||
```
|
||
|
||
## 调试技巧
|
||
|
||
### 1. 验证模型输出
|
||
```python
|
||
from rknnlite.api import RKNNLite
|
||
import numpy as np
|
||
|
||
rknn = RKNNLite()
|
||
rknn.load_rknn('ppe_det_yolov8_ppe11_768_rk3588.rknn')
|
||
rknn.init_runtime()
|
||
|
||
input_data = np.ones((1, 3, 768, 768), dtype=np.float32) * 128
|
||
outputs = rknn.inference(inputs=[input_data])
|
||
|
||
print(f"Output shape: {outputs[0].shape}")
|
||
print(f"Min/Max: {outputs[0].min():.4f} / {outputs[0].max():.4f}")
|
||
print(f"First 10 values: {outputs[0].flatten()[:10]}")
|
||
```
|
||
|
||
### 2. 检查输出类型
|
||
```cpp
|
||
// 在 ai_scheduler.cpp 中添加日志
|
||
LogInfo("[ai_scheduler] output[" + std::to_string(i) + "] type=" +
|
||
std::to_string(ctx->output_attrs[i].type) +
|
||
" qnt_type=" + std::to_string(ctx->output_attrs[i].qnt_type));
|
||
```
|
||
|
||
## 相关文件
|
||
|
||
- `src/ai_scheduler.cpp` - RKNN 推理核心逻辑
|
||
- `plugins/ai_yolo/ai_yolo_node.cpp` - YOLO 后处理
|
||
- `include/ai_scheduler.h` - 数据结构定义
|
||
|
||
## 参考文档
|
||
|
||
- [RKNN Toolkit2 文档](https://github.com/rockchip-linux/rknn-toolkit2)
|
||
- [RK3588 NPU 用户手册](https://www.rock-chips.com/uploads/pdf/RK3588%20NPU%20User%20Manual.pdf)
|
||
- [YOLOv8 RKNN 部署指南](https://github.com/ultralytics/ultralytics)
|
||
|
||
---
|
||
|
||
**修复日期**: 2026-02-28
|
||
**修复者**: AI Assistant
|
||
**测试模型**: `models/ppe_det_yolov8_ppe11_768_rk3588.rknn`
|