OrangePi3588Media/docs/bugfix/bugfix_yolov8_fp16.md

6.3 KiB
Raw Permalink Blame History

YOLOv8 FP16 数据解析错误修复

问题描述

现象: 使用 YOLOv8 RKNN 模型(如 object_det_yolov8n_coco_640_rk3588.rknnppe_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/FP32RKNN 都返回 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 结构定义

参考


修复日期: 2025-02-28
文档更新: 2026-02-28