从零上手地平线 OE 工具链:YOLOv3 的 PTQ 量化与精度验证
目录
前面的一篇帖子 从零上手地平线 OE 工具链:ResNet50 的 PTQ 量化与精度验证已经详细阐述了利用地平线征程 6(J6)量化与精度验证的具体实验步骤与实验方法,属于分类任务,基于此,最近又做了一个检测任务,也就是YOLOv3,一起看看与ResNet50 有什么异同。
与上一篇一样,本篇主要还是倾向于实验步骤和实验方法,至于原理性的内容,比如说OE工具链里面很多具体的运行细节,图片的预处理,后处理等内容,会在后面单独写帖子进行阐述。
官方给的例程中,有2个关于YOLOv3的网络模型可供选择,一个是darknet53,另一个是vargdarknet,从名称上看,是骨干网络有点区别,此次实验我们选择第一个网络模型。
环境准备和镜像加载与此前实验没有什么区别,可直接参考上一篇帖子。我们直接从具体实验开始。
1 YOLOv3 模型实战:从准备到量化全流程
1.1 步骤 1:模型与数据初始化(00_init.sh)
直接执行脚本即可
bash 00_init.sh

运行成功。
与Resnet50类似,执行 00_init.sh 脚本来获取示例对应的校准数据集和原始模型。如果我们能在本地找到校准数据集和原始模型,就说明初始化成功!
在路径D:\Horizon_J6\horizon_j6_open_explorer_v3.8.1-py310_20260326\samples\ai_toolchain\model_zoo\mapper\detection\yolov3_darknet53下,找到了原始模型。
在路径D:\Horizon_J6\horizon_j6_open_explorer_v3.8.1-py310_20260326\samples\ai_toolchain\horizon_model_convert_sample\01_common\calibration_data下,找到了校准数据集。
1.2 步骤 2:模型预检(01_check.sh)
在浮点模型准备好之后,需要先进行快速的模型验证,验证是否有地平线硬件平台不支持的算子,为后续步骤做准备。
执行脚本:
bash 01_check.sh
输出如下(只展示部分结果):


运行正常,没有发现不支持的算子。
在开发者社区,也可以自己手动查找不同的地平线平台所支持的算子。
1.3 步骤 3:校准数据预处理(02_preprocess.sh)
脚本将原始 JPG 图片转换为量化工具可直接读取的.npy格式,完成数据归一化、尺寸调整等预处理,为后续 PTQ 量化提供校准数据。
运行以下脚本,进行校准数据的预处理,
bash 02_preprocess.sh
终端打印输出如下:
顺利地将校准数据集变换成了.npy文件。
在路径D:\Horizon_J6\horizon_j6_open_explorer_v3.8.1-py310_20260326\samples\ai_toolchain\horizon_model_convert_sample\04_detection\02_yolov3_darknet53\calibration_data_rgb_f32下,可以很方便地找到导出的.npy文件。
1.4 步骤 4:模型量化与编译(03_build.sh)
上篇帖子有提及,这个步骤有两个核心任务,一个是量化,一个是编译。
🍉量化:将将 FP32 模型转换为 INT8 模型。
🍉模型编译:将量化后的模型编译为地平线 J6 芯片可直接加载的.hbm格式文件,最终生成model_output/yolov3_darknet53_416x416_nv12.hbm,即端侧部署模型。


在本地,可以看到量化编译后的模型文件:
不同文件的意义和作用,可以在开发者平台上看到:
1.5 步骤 5:模拟推理验证(04_inference.sh)
这一步,是执行一次单个图像的推理。
执行脚本:
bash 04_inference.sh

与上一篇的分类任务不同,这次是目标检测任务,由于一张图片上可能会有多个物体,因此会有多个输出,从终端可以看出,总共检测到15个目标,8个人和7个风筝。
在脚本的同级路径下,可以看到推理的结果:
从图片中可以看出,网络预测的结果非常理想,甚至连肉眼非常难以辨别的目标也能检测出来。
虽然没能得出具体预测的坐标,也可以从后续的实验中得到一些参数指标,证明模型的预测能力。
1.6 步骤 6:模型精度评估(05_evaluate.sh)
默认脚本会加载完整 COCO 2014 验证集,可通过test参数使用小数据集快速验证。最常见的命令是直接执行脚本:
bash 05_evaluate.sh
与上次做Resnet50实验的时候一样,这次仍然是找不到数据集。而且这次的问题更加严重。有2个坏消息:
- 🌄上次有本地的标签文件,只需要从中手动提取出需要的部分即可,而这次本地根本没有标签🏷️文件,需要自己下载,自己提起。
- 🌄上次数据集图片和标签文件从名称上编号是连续的,而COCO数据集是不连续的,这样从原本的完整数据集中去掉校准数据集的时候比以前麻烦很多。
首先,想办法下载COCO val2014数据集到本地。
其中,instances_val2014.json是标签文件。
现在需要验证,数据集与标签文件是否对应,由于标签文件太大,无法直接手动打开,所以可以手动写python脚本来实现。先在本地创建一个可以复用的脚本文件06_exclude_calib.sh,这个脚本到时候调用一个python程序,有2个作用:
- 可以用来验证标签文件与图像是否对应。
- 可以用来生成我们需要的评估数据集。
先写脚本里面的内容:
set -v -e
cd $(dirname $0) || exit
# 使用绝对路径(根据你的实际目录调整)
CALIB_IMG_DIR="../../01_common/calibration_data/coco" # 相对路径也可,但确保存在
FULL_ANNOTATION_FILE="/mnt/e/BaiduNetdiskDownload/annotations_trainval2014/annotations/instances_val2014.json"
FULL_IMG_DIR="/mnt/e/BaiduNetdiskDownload/COCO_val2014/val2014/val2014/val2014"
OUTPUT_DIR="../../01_common/test_data/coco/eval_subset_excluding_calib"
COPY_IMAGES="true"
# ============================================================
# 如果通过命令行参数覆盖,可以这样使用:
# sh 06_exclude_calib.sh --calib_dir xxx --anno_file xxx ...
# 解析简单的命令行参数(可选)
while [[ $# -gt 0 ]]; do
case $1 in
--calib_dir)
CALIB_IMG_DIR="$2"
shift 2
;;
--anno_file)
FULL_ANNOTATION_FILE="$2"
shift 2
;;
--img_dir)
FULL_IMG_DIR="$2"
shift 2
;;
--output_dir)
OUTPUT_DIR="$2"
shift 2
;;
--no_copy)
COPY_IMAGES="false"
shift
;;
*)
echo "未知参数: $1"
exit 1
;;
esac
done
echo "=========================================="
echo "校准图片目录: $CALIB_IMG_DIR"
echo "完整标注文件: $FULL_ANNOTATION_FILE"
echo "完整图片目录: $FULL_IMG_DIR"
echo "输出目录: $OUTPUT_DIR"
echo "复制图片: $COPY_IMAGES"
echo "=========================================="
# 调用 Python 脚本,传递参数
python3 -u exclude_calib_images.py \
--calib_dir "$CALIB_IMG_DIR" \
--anno_file "$FULL_ANNOTATION_FILE" \
--img_dir "$FULL_IMG_DIR" \
--output_dir "$OUTPUT_DIR" \
--copy_images "$COPY_IMAGES"
本地创建python文件exclude_calib_images.py,并撰写内容:
import json
import cv2
import numpy as np
import os
# ========== 请修改为你的实际路径 ==========
ANNO_FILE = "/mnt/e/BaiduNetdiskDownload/annotations_trainval2014/annotations/instances_val2014.json"
IMAGE_DIR = "/mnt/e/BaiduNetdiskDownload/COCO_val2014/val2014/val2014/val2014" # 你的图片所在目录
OUTPUT_DIR = "./verified" # 输出目录
TARGET_IMAGE_ID = 558840 # 你要验证的图片ID
# ==========================================
os.makedirs(OUTPUT_DIR, exist_ok=True)
# 1. 加载标注文件
print("加载标注文件...")
with open(ANNO_FILE, 'r') as f:
data = json.load(f)
# 2. 构建 id -> 类别名 的映射
cat_id_to_name = {cat['id']: cat['name'] for cat in data['categories']}
# 3. 找到目标图片的信息
img_info = None
for img in data['images']:
if img['id'] == TARGET_IMAGE_ID:
img_info = img
break
if img_info is None:
print(f"错误:未找到图片 ID {TARGET_IMAGE_ID}")
exit(1)
img_file = img_info['file_name']
img_path = os.path.join(IMAGE_DIR, img_file)
if not os.path.exists(img_path):
print(f"错误:图片文件不存在 {img_path}")
exit(1)
print(f"图片文件: {img_file}")
# 4. 找到该图片的所有标注
anns = [ann for ann in data['annotations'] if ann['image_id'] == TARGET_IMAGE_ID]
if len(anns) == 0:
print("该图片没有标注框。")
else:
print(f"找到 {len(anns)} 个标注框。")
# 5. 读取图片并画框
image = cv2.imread(img_path)
height, width = image.shape[:2]
colors = [(0, 255, 0), (255, 0, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255)] # 不同颜色
for idx, ann in enumerate(anns):
bbox = ann['bbox'] # [x, y, width, height]
cat_id = ann['category_id']
cat_name = cat_id_to_name.get(cat_id, "unknown")
x, y, w, h = [int(v) for v in bbox]
color = colors[idx % len(colors)]
cv2.rectangle(image, (x, y), (x + w, y + h), color, 2)
label = f"{cat_name} (ID:{cat_id})"
cv2.putText(image, label, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
# 6. 保存结果
output_path = os.path.join(OUTPUT_DIR, f"bbox_{img_file}")
cv2.imwrite(output_path, image)
print(f"带标注框的图片已保存至: {output_path}")
终端输入:
bash 06_exclude_calib.sh
打印输出:
终端打印输出已经告诉了我们保存路径,直接在该路径下找到图片即可。
可以看到,数据集的图片与标签文件是一一对应的,没有问题,数据集太大,并没有做太多的实验。
紧接着就可以继续写程序制作验证的数据集了,受限于电脑的性能,我只挑出了200张照片。还是复用刚才的python文件,只需将内容改一下就行:
import json
import os
import shutil
import argparse
def main():
parser = argparse.ArgumentParser(description='从COCO验证集中排除校准图片,并保留前N张')
parser.add_argument('--calib_dir', required=True, help='校准图片目录(50张)')
parser.add_argument('--anno_file', required=True, help='完整的instances_val2014.json路径')
parser.add_argument('--img_dir', required=True, help='完整的val2014图片目录')
parser.add_argument('--output_dir', required=True, help='输出目录(存放新标注和图片子集)')
parser.add_argument('--copy_images', type=str, default='true', choices=['true', 'false'],
help='是否复制图片文件到输出目录(默认true)')
parser.add_argument('--max_images', type=int, default=200,
help='最多保留多少张图片(排除校准图片后取前N张,默认200)')
args = parser.parse_args()
calib_dir = args.calib_dir
full_anno_file = args.anno_file
full_img_dir = args.img_dir
output_dir = args.output_dir
copy_images = args.copy_images.lower() == 'true'
max_images = args.max_images
# 1. 收集校准图片的文件名
calib_files = set()
for f in os.listdir(calib_dir):
if f.lower().endswith('.jpg'):
calib_files.add(f)
print(f"校准图片数量: {len(calib_files)}")
if len(calib_files) == 0:
print("错误:校准图片目录中没有找到.jpg文件")
return
# 2. 加载完整标注文件
print("正在加载完整的标注文件(约240MB,请稍等)...")
with open(full_anno_file, 'r') as f:
full_data = json.load(f)
# 3. 构建文件名->图片ID的映射
filename_to_id = {img['file_name']: img['id'] for img in full_data['images']}
print(f"标注文件中总图片数: {len(filename_to_id)}")
# 4. 找出需要排除的图片ID
exclude_ids = set()
missing_files = []
for f in calib_files:
if f in filename_to_id:
exclude_ids.add(filename_to_id[f])
else:
missing_files.append(f)
if missing_files:
print(f"警告:以下校准图片在标注文件中未找到:{missing_files}")
print(f"需要排除的图片ID数量: {len(exclude_ids)}")
# 5. 构建保留的图片列表(排除校准图片,并按原顺序取前max_images张)
kept_images = []
for img in full_data['images']:
if img['id'] not in exclude_ids:
kept_images.append(img)
if len(kept_images) >= max_images:
break
print(f"实际保留的图片数量: {len(kept_images)}")
# 6. 收集这些图片的ID集合
kept_ids = {img['id'] for img in kept_images}
# 7. 筛选标注:只保留属于kept_ids的标注
kept_annotations = []
for ann in full_data['annotations']:
if ann['image_id'] in kept_ids:
kept_annotations.append(ann)
print(f"保留的标注数量: {len(kept_annotations)}")
# 8. 构建新的JSON结构
new_data = {
"info": full_data.get("info", {}),
"licenses": full_data.get("licenses", []),
"categories": full_data["categories"],
"images": kept_images,
"annotations": kept_annotations
}
# 9. 保存新标注文件(使用紧凑格式,与原文件一致)
os.makedirs(output_dir, exist_ok=True)
output_anno_path = os.path.join(output_dir, 'instances_val2014_excl_calib.json')
with open(output_anno_path, 'w') as f:
json.dump(new_data, f, separators=(',', ':')) # 紧凑格式,无多余空格和换行
print(f"新标注文件已保存至: {output_anno_path}")
# 10. 可选:复制对应的图片文件
if copy_images:
img_output_dir = os.path.join(output_dir, 'images')
os.makedirs(img_output_dir, exist_ok=True)
for img in kept_images:
src = os.path.join(full_img_dir, img['file_name'])
dst = os.path.join(img_output_dir, img['file_name'])
if os.path.exists(src):
shutil.copy2(src, dst)
else:
print(f"警告:图片文件不存在 {src}")
print(f"图片已复制到: {img_output_dir}")
print("完成!")
if __name__ == "__main__":
main()
还是用刚才的06脚本,运行即可。运行完会在D:\Horizon_J6\horizon_j6_open_explorer_v3.8.1-py310_20260326\samples\ai_toolchain\horizon_model_convert_sample\01_common\test_data\coco\coco_val2017_subset路径下生成我们所需的数据集图像和标签文件。
至此,所有工作准备就绪,只需要将05_evaluate.sh脚本里面的读取路径替换成我们当前数据集的相关路径即可。然后运行脚本:
bash 05_evaluate.sh
终端输出:

可以看到,运行很顺利,准确率指标:
🌂mAP@0.50:0.95=0.302
🌂mAP@0.50=0.505
同时,我们运行原本的浮点模型,比较一下:
bash 05_evaluate.sh origin

运行也比较顺利,准确率指标:
🌂mAP@0.50:0.95=0.306
🌂mAP@0.50=0.492
🌈对比发现,在200个图片的预测实验中,量化后的模型,mAP@0.50:0.95仅仅降低了0.4%,mAP@0.50甚至比原来的效果更好,基本符合预期!
2 总结
本文从零上手地平线 OE 工具链,在本地 WSL 环境下完成了 yolov3_darknet53 模型的 PTQ 量化、仿真推理与精度对比验证,最终实现了浮点模型 mAP@0.50:0.95=0.306 到量化模型 mAP@0.50:0.95=0.302 的精度表现,掉点仅 0.4%,验证了量化策略的有效性。
后续将逐步深入理解车载 AI 部署的工程细节,敬请关注!
❤️❤️❤️今天是 2026 年的 5 月 20 日,爱更有趣的技术,更有趣的人,未来可期❤️❤️❤️ ❤️❤️❤️今天是2026年的5月20日,爱更有趣的技术,更有趣的人,未来可期❤️❤️❤️ ❤️❤️❤️今天是2026年的5月20日,爱更有趣的技术,更有趣的人,未来可期❤️❤️❤️
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)