safesight-edge/docs/fix_rknn_output_format.md

182 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# RK3588 YOLOv8 模型输出格式修复记录
## 问题现象
使用新转换的 RKNN 模型 (`best-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 返回 FP324字节/元素),但缓冲区只分配了 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('best-rk3588.rknn')
```
## 调试技巧
### 1. 验证模型输出
```python
from rknnlite.api import RKNNLite
import numpy as np
rknn = RKNNLite()
rknn.load_rknn('best-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/best-rk3588.rknn`