6.3 KiB
6.3 KiB
YOLOv8 FP16 数据解析错误修复
问题描述
现象: 使用 YOLOv8 RKNN 模型(如 object_det_yolov8n_coco_640_rk3588.rknn、ppe_det_yolov8_ppe11_768_rk3588.rknn)时,无法检测到目标,跟踪器显示 tracks=0,模型输出为垃圾值。
错误日志特征:
[ai_yolo] First box: x=125969024.000000, y=38730874880.000000, w=0.000000, h=0.000000
[ai_yolo] ProcessOutputV8 result: valid_count=8400 out of 8400 boxes
- 所有 8400 个 anchor 都通过了阈值检测
- 坐标值为超大异常数字
- 检测分数为
score=5561747627709562880.000000(溢出值)
修复历史
修复 #1: FP16 到 FP32 转换 (2025-02-26)
问题: FP16 数据被错误地当作 FP32 解析
解决: 添加 Fp16ToFp32 转换函数,单独处理 FP16 分支
文件: plugins/ai_yolo/ai_yolo_node.cpp
修复 #2: RKNN 输出格式配置 (2025-02-28)
问题: ai_scheduler 中 RKNN 输出配置错误导致数据类型混乱
详细说明见下方
修复 #2 详细说明
问题现象
使用新转换的模型 ppe_det_yolov8_ppe11_768_rk3588.rknn 时出现:
[ai_yolo] raw box i=0 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)
- 所有检测分数相同(0.998047)
- 坐标包含
inf或全图 - 或完全没有检测(
valid_count=0)
根本原因
src/ai_scheduler.cpp 中存在三个配置错误:
1. 使用 INT8 原始输出
// 错误代码 (line 620)
outputs[i].want_float = 0; // 获取 INT8 量化数据
2. 硬编码输出类型
// 错误代码 (line 647)
out.type = RKNN_TENSOR_FLOAT16; // 硬编码,与实际不符
3. 缓冲区大小不足
// 错误代码 (line 220)
uint32_t out_sz = ctx->output_attrs[j].n_elems * sizeof(uint16_t); // 仅 2字节/元素
为什么会导致 0.998047
0.998047≈ FP16 值0x3BFF转换为 FP32 的结果- 当 INT8 数据被错误解析为 FP16 时,数值被错误解释
- 由于所有 class scores 被相同方式解析,导致分数统一
解决方法
修改 1: 启用 FP32 输出
// src/ai_scheduler.cpp line 620
outputs[i].want_float = 1; // RKNN 自动完成反量化,返回 FP32
修改 2: 正确标记输出类型
// src/ai_scheduler.cpp line 647
out.type = RKNN_TENSOR_FLOAT32; // 与 want_float=1 对应
修改 3: 分配正确大小的缓冲区
// src/ai_scheduler.cpp line 220
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 或全图 | 合理的 bbox |
| 检测 | 异常 | 稳定有效 |
RKNN 输出类型参考
want_float |
返回值类型 | 说明 |
|---|---|---|
| 0 | INT8 (原始量化值) | 需要手动反量化: (val - zp) * scale |
| 1 | FP32 | RKNN 自动反量化,可直接使用 |
注意: want_float=1 时,无论模型内部是 INT8/FP16/FP32,RKNN 都返回 FP32。
完整修复代码 (src/ai_scheduler.cpp)
// 1. 请求 FP32 输出 (line 620)
for (uint32_t i = 0; i < ctx->n_output; ++i) {
outputs[i].want_float = 1; // 修改: 原来是 0
outputs[i].index = i;
// ...
}
// 2. 标记正确的输出类型 (line 647)
for (uint32_t i = 0; i < ctx->n_output; ++i) {
auto& out = result.outputs[i];
out.index = static_cast<int>(i);
out.size = outputs[i].size;
out.data = reinterpret_cast<const uint8_t*>(outputs[i].buf);
out.type = RKNN_TENSOR_FLOAT32; // 修改: 原来是 FLOAT16
out.zp = ctx->output_attrs[i].zp;
out.scale = ctx->output_attrs[i].scale;
// ...
}
// 3. 分配 FP32 缓冲区 (line 220)
for (uint32_t j = 0; j < ctx->n_output; ++j) {
// FP32 output: 4 bytes per element
uint32_t out_sz = ctx->output_attrs[j].n_elems * sizeof(float); // 修改: 原来是 uint16_t
if (out_sz > 0) {
ctx->output_buffers[j].resize(out_sz);
} else {
ctx->output_buffers[j].clear();
}
}
模型转换建议
PC 端导出 ONNX
from ultralytics import YOLO
model = YOLO('best.pt')
model.export(
format='onnx',
imgsz=768,
opset=12,
simplify=True,
dynamic=False,
)
PC 端转换为 RKNN
from rknn.api import RKNN
rknn = RKNN()
rknn.config(
target_platform='rk3588',
mean_values=[[0.0, 0.0, 0.0]], # 归一化到 [0,1]
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')
调试技巧
验证模型输出
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"Shape: {outputs[0].shape}") # 应为 (1, 15, 12096)
print(f"Min/Max: {outputs[0].min():.4f} / {outputs[0].max():.4f}") # 应无 inf
print(f"Values: {outputs[0].flatten()[:10]}") # 应无 0.998047 重复
检查数据类型
// 在 ai_yolo_node.cpp 中添加
LogInfo("[ai_yolo] output type=" + std::to_string(outputs[0].type) +
" size=" + std::to_string(outputs[0].size));
// 期望: type=0 (FLOAT32), size=725760 (对于 15*12096 FP32)
相关文件
| 文件 | 说明 |
|---|---|
src/ai_scheduler.cpp |
RKNN 推理核心,本次修复的主要文件 |
plugins/ai_yolo/ai_yolo_node.cpp |
YOLO 后处理,包含 FP16→FP32 转换 |
include/ai_scheduler.h |
InferOutput 结构定义 |
参考
- RKNN Toolkit2 文档
- RK3588 NPU 快速入门
rknn_api.h中的数据类型定义
修复日期: 2025-02-28
文档更新: 2026-02-28