前面的一篇帖子 从零上手地平线 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个坏消息:

  1. 🌄上次有本地的标签文件,只需要从中手动提取出需要的部分即可,而这次本地根本没有标签🏷️文件,需要自己下载,自己提起。
  2. 🌄上次数据集图片和标签文件从名称上编号是连续的,而COCO数据集是不连续的,这样从原本的完整数据集中去掉校准数据集的时候比以前麻烦很多。

首先,想办法下载COCO val2014数据集到本地。
在这里插入图片描述
其中,instances_val2014.json是标签文件。
在这里插入图片描述
现在需要验证,数据集与标签文件是否对应,由于标签文件太大,无法直接手动打开,所以可以手动写python脚本来实现。先在本地创建一个可以复用的脚本文件06_exclude_calib.sh,这个脚本到时候调用一个python程序,有2个作用:

  1. 可以用来验证标签文件与图像是否对应。
  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年的520日,爱更有趣的技术,更有趣的人,未来可期❤️❤️❤️

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐