你好!针对你提到的语义分割 ONNX 模型效果好,但转成 .bin 模型后效果差的问题,这是 RDK 模型部署中非常典型的量化精度损失问题。
结合社区最新案例(特别是 量化后.bin 的模型与原始.onnx 模型精度出现很大偏差 这个帖子),核心原因通常出在校准数据(Calibration Data)和量化配置上。
以下是详细的排查路径和解决方案:
核心原因分析
模型转换流程:ONNX (Float32) → Quantized ONNX (Int8/Int16) → .bin (BPU 指令)
问题通常出现在两个阶段:
- 标定阶段(Calibration):生成的
*_calibrated_model.onnx精度就已经下降。- 原因:校准数据集(Calibration Data)的预处理方式与模型训练时的预处理不一致。
- 编译阶段(Compilation):
quantized.onnx正常,但.bin效果差。- 原因:BPU 算子不支持某些数据类型(如 int16),强制转换导致精度损失,或前后处理代码(Post-processing)在板端实现有误。
解决方案步骤
第一步:检查校准数据(最关键!)
90% 的问题都出在这里。 校准数据必须是经过预处理后的模型输入数据,而不是原始图片。
检查清单:
-
数据格式:
- 如果你的模型输入是
1x3x512x512的 Float32 数据。 - 校准数据必须是同样形状、同样类型的
.bin或.npy文件。 - 错误做法:直接把原始 JPEG/PNG 图片丢进校准目录。
- 正确做法:写一个 Python 脚本,读取验证集图片,执行与训练完全一致的预处理(Resize, Normalize, Mean/Std, HWC→CHW),保存为二进制文件。
- 如果你的模型输入是
-
预处理一致性验证:
- 选取一张测试图片,分别用以下方式处理:
- Python 训练代码预处理 → 输入原始 ONNX → 输出结果 A
- 校准数据生成脚本预处理 → 输入原始 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并自己预处理数据。
第三步:验证中间模型
在转换过程中,工具链会生成几个中间模型,按顺序验证它们:
*_original_float_model.onnx:原始模型,应该正常。*_optimized_float_model.onnx:优化后的浮点模型,应该正常。*_calibrated_model.onnx:标定后的模型(关键检查点)。- 测试方法:在 PC 上用 Python (onnxruntime) 加载这个模型,输入同样的校准数据,看输出是否和原始模型接近。
- 结论:
- 如果这个模型效果就差了 → 回到第一步,检查校准数据。
- 如果这个模型效果好,但
.bin差 → 进入第四步。
第四步:检查 BPU 算子支持与前后处理
如果标定模型正常,但 .bin 不行:
-
算子精度问题:
- 查看转换日志,是否有
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'} } - 查看转换日志,是否有
-
后处理代码(Post-processing):
- ONNX 模型输出通常是 Float32 的 Logits 或 Probability Map。
- 板端 C++ 代码中,你是否正确解析了
.bin的输出? - 检查点:
- 输出 Tensor 的 Shape 是否正确?(有些模型输出是
1xNumClassesxHxW,有些是1xHxW) argmax操作是否正确?- 是否需要对输出进行反归一化?
- 输出 Tensor 的 Shape 是否正确?(有些模型输出是
快速排查流程总结
| 步骤 | 操作 | 预期结果 | 下一步 |
|---|---|---|---|
| 1 | 检查校准数据生成脚本 | 预处理逻辑与训练代码完全一致 | 不一致则修改脚本 |
| 2 | 设置 preprocess_on: False |
工具链不重复预处理 | 继续 |
| 3 | 测试 *_calibrated_model.onnx |
在 PC 上推理效果与原始模型接近 | 效果差 → 重做校准数据 |
| 4 | 检查转换日志警告 | 无 unsupported data type 警告 |
有警告 → 将对应层放 CPU |
| 5 | 检查板端后处理代码 | 正确解析输出 Tensor 并做 ArgMax | 仍不行 → 发帖提供模型和日志 |
推荐参考资源
- 详细案例:量化后.bin 的模型与原始.onnx 模型精度出现很大偏差(这个帖子详细记录了排查过程,特别是校准数据部分)
- 官方教程:万字长文,学弟一看就会的 RDKX5 模型转换及部署
- 分割网络参考:RDK 分割网络 FCN PTQ 部署参考
建议:先花 10 分钟检查你的校准数据生成脚本,这是最高频的坑。如果确认数据没问题,可以把 *_calibrated_model.onnx 和转换日志发到社区,方便工程师帮你定位是哪一层算子出了问题。