RDK X5小车巡线缺少赛道检测功能包

你好!针对你提到的语义分割 ONNX 模型效果好,但转成 .bin 模型后效果差的问题,这是 RDK 模型部署中非常典型的量化精度损失问题。

结合社区最新案例(特别是 量化后.bin 的模型与原始.onnx 模型精度出现很大偏差 这个帖子),核心原因通常出在校准数据(Calibration Data)量化配置上。

以下是详细的排查路径和解决方案:


:magnifying_glass_tilted_left: 核心原因分析

模型转换流程:ONNX (Float32)Quantized ONNX (Int8/Int16).bin (BPU 指令)

问题通常出现在两个阶段:

  1. 标定阶段(Calibration):生成的 *_calibrated_model.onnx 精度就已经下降。
    • 原因:校准数据集(Calibration Data)的预处理方式与模型训练时的预处理不一致
  2. 编译阶段(Compilation)quantized.onnx 正常,但 .bin 效果差。
    • 原因:BPU 算子不支持某些数据类型(如 int16),强制转换导致精度损失,或前后处理代码(Post-processing)在板端实现有误。

:white_check_mark: 解决方案步骤

第一步:检查校准数据(最关键!)

90% 的问题都出在这里。 校准数据必须是经过预处理后的模型输入数据,而不是原始图片。

检查清单:

  1. 数据格式

    • 如果你的模型输入是 1x3x512x512 的 Float32 数据。
    • 校准数据必须是同样形状、同样类型.bin.npy 文件。
    • 错误做法:直接把原始 JPEG/PNG 图片丢进校准目录。
    • 正确做法:写一个 Python 脚本,读取验证集图片,执行与训练完全一致的预处理(Resize, Normalize, Mean/Std, HWC→CHW),保存为二进制文件。
  2. 预处理一致性验证

    • 选取一张测试图片,分别用以下方式处理:
      1. Python 训练代码预处理 → 输入原始 ONNX → 输出结果 A
      2. 校准数据生成脚本预处理 → 输入原始 ONNX → 输出结果 B
    • 判定:如果 A 和 B 不一致,说明预处理脚本写错了,必须修正。

参考代码(生成校准数据):

import cv2
import numpy as np
import os

def preprocess(image_path, input_shape=(512, 512)):
    # 1. 读取图片 (BGR)
    img = cv2.imread(image_path)
    
    # 2. Resize (注意插值方式,通常用 INTER_LINEAR)
    img_resized = cv2.resize(img, input_shape, interpolation=cv2.INTER_LINEAR)
    
    # 3. 归一化 (根据你的训练代码调整 mean 和 std)
    # 例如:img = (img / 255.0 - mean) / std
    img_normalized = (img_resized.astype(np.float32) / 255.0 - 0.485) / 0.224
    
    # 4. HWC -> CHW
    img_chw = np.transpose(img_normalized, (2, 0, 1))
    
    # 5. 添加 Batch 维度
    img_final = np.expand_dims(img_chw, axis=0).astype(np.float32)
    
    return img_final

# 批量生成
cal_data_dir = './calibration_data'
os.makedirs(cal_data_dir, exist_ok=True)

image_list = [...] # 你的校准图片列表(建议 100-200 张,覆盖各种场景)
for i, img_path in enumerate(image_list):
    data = preprocess(img_path)
    # 保存为二进制文件,文件名随意,后缀建议 .bin
    data.tofile(f'{cal_data_dir}/cal_{i:04d}.bin')

第二步:检查 YAML 配置文件

查看你的 hb_mapper 配置文件,重点检查以下参数:

calibration_parameters:
  cal_data_dir: './calibration_data'  # 确保指向预处理后的数据
  cal_data_type: 'float32'            # 必须与预处理输出类型一致
  preprocess_on: False                # ⚠️ 关键!如果数据已预处理,必须设为 False
  calibration_type: 'max'             # 语义分割推荐用 max 或 entropy
  per_channel: True                   # 建议开启,精度更高

model_parameters:
  # 检查是否有特殊算子被强制指定到 CPU
  # 如果某些层在 BPU 上精度损失大,可以尝试放到 CPU
  node_info: {
    # "Some_Layer": {'ON': 'CPU'} 
  }

常见错误

  • preprocess_on: True:工具链会尝试自己做一些简单的预处理(如 Resize),这通常与你的训练预处理不匹配,导致精度崩盘。务必设为 False 并自己预处理数据。

第三步:验证中间模型

在转换过程中,工具链会生成几个中间模型,按顺序验证它们:

  1. *_original_float_model.onnx:原始模型,应该正常。
  2. *_optimized_float_model.onnx:优化后的浮点模型,应该正常。
  3. *_calibrated_model.onnx标定后的模型(关键检查点)
    • 测试方法:在 PC 上用 Python (onnxruntime) 加载这个模型,输入同样的校准数据,看输出是否和原始模型接近。
    • 结论
      • 如果这个模型效果就差了 → 回到第一步,检查校准数据
      • 如果这个模型效果好,但 .bin 差 → 进入第四步

第四步:检查 BPU 算子支持与前后处理

如果标定模型正常,但 .bin 不行:

  1. 算子精度问题

    • 查看转换日志,是否有 The output of Node ... does not support data type: int16 类似的警告。
    • 语义分割的某些层(如 Resize, Sigmoid, ArgMax)在 BPU 上可能用低精度计算,导致边缘模糊。
    • 解决:在 YAML 的 node_info 中,将这些敏感层强制指定到 CPU 运行。
    node_info: {
      "Resize_xxx": {'ON': 'CPU'},
      "Sigmoid_xxx": {'ON': 'CPU'},
      "ArgMax_xxx": {'ON': 'CPU'}
    }
    
  2. 后处理代码(Post-processing)

    • ONNX 模型输出通常是 Float32 的 Logits 或 Probability Map。
    • 板端 C++ 代码中,你是否正确解析了 .bin 的输出?
    • 检查点
      • 输出 Tensor 的 Shape 是否正确?(有些模型输出是 1xNumClassesxHxW,有些是 1xHxW
      • argmax 操作是否正确?
      • 是否需要对输出进行反归一化?

:rocket: 快速排查流程总结

步骤 操作 预期结果 下一步
1 检查校准数据生成脚本 预处理逻辑与训练代码完全一致 不一致则修改脚本
2 设置 preprocess_on: False 工具链不重复预处理 继续
3 测试 *_calibrated_model.onnx 在 PC 上推理效果与原始模型接近 效果差 → 重做校准数据
4 检查转换日志警告 unsupported data type 警告 有警告 → 将对应层放 CPU
5 检查板端后处理代码 正确解析输出 Tensor 并做 ArgMax 仍不行 → 发帖提供模型和日志

:books: 推荐参考资源

  1. 详细案例量化后.bin 的模型与原始.onnx 模型精度出现很大偏差(这个帖子详细记录了排查过程,特别是校准数据部分)
  2. 官方教程万字长文,学弟一看就会的 RDKX5 模型转换及部署
  3. 分割网络参考RDK 分割网络 FCN PTQ 部署参考

建议:先花 10 分钟检查你的校准数据生成脚本,这是最高频的坑。如果确认数据没问题,可以把 *_calibrated_model.onnx 和转换日志发到社区,方便工程师帮你定位是哪一层算子出了问题。