OrangePi3588Media/docs/bugfix/fix_rknn_output_format.md

4.8 KiB
Raw Permalink Blame History

RK3588 YOLOv8 模型输出格式修复记录

问题现象

使用新转换的 RKNN 模型 (ppe_det_yolov8_ppe11_768_rk3588.rknn) 进行推理时,出现以下异常现象:

  1. 所有检测框的置信度分数相同 - 均为 0.998047
  2. 框坐标异常 - 大量框为全图 (0,0,768,768) 或包含 inf
  3. 部分帧无检测 - valid_count=0global_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 原始输出

// 错误代码
outputs[i].want_float = 0;  // 获取 INT8 量化后的原始数据

want_float=0RKNN 返回 INT8 量化数据,但代码错误地将其当作 FP16 解析。

问题 B: 硬编码输出类型

// 错误代码
out.type = RKNN_TENSOR_FLOAT16;  // 硬编码为 FP16与实际数据类型不符

实际获取的是 INT8 数据(type=2),但标记为 FP16type=1),导致后续解析逻辑错误。

问题 C: 缓冲区大小不足

// 错误代码
uint32_t out_sz = ctx->output_attrs[j].n_elems * sizeof(uint16_t);  // 只分配 2字节/元素

want_float=1RKNN 返回 FP324字节/元素),但缓冲区只分配了 FP16 大小2字节/元素),导致数据截断。

2. 数值解释

0.998047 是 FP16 值 0x3BFF 转换为 FP32 后的结果:

  • FP16: 0x3BFF = 0.99951171875
  • 由于所有 class scores 被错误解析为相同的 FP16 值,导致所有检测分数相同

解决方法

修复 1: 启用 FP32 输出 (ai_scheduler.cpp:620)

// 修改前
outputs[i].want_float = 0;

// 修改后
outputs[i].want_float = 1;  // 请求 FP32 输出RKNN 自动完成反量化

修复 2: 正确标记输出类型 (ai_scheduler.cpp:647)

// 修改前
out.type = RKNN_TENSOR_FLOAT16;

// 修改后
out.type = RKNN_TENSOR_FLOAT32;  // 与 want_float=1 对应

修复 3: 分配正确大小的缓冲区 (ai_scheduler.cpp:220)

// 修改前
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

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]],
    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. 验证模型输出

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. 检查输出类型

// 在 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 - 数据结构定义

参考文档


修复日期: 2026-02-28
修复者: AI Assistant
测试模型: models/ppe_det_yolov8_ppe11_768_rk3588.rknn