高通 QCS6490 平台上 YOLOv6 系列模型的性能测试

伊利丹~怒风
企业官方账号
2025-07-23 09:09:56

 前言

在当今智能边缘计算蓬勃发展的时代,硬件性能与算法模型的高效适配成为推动各领域创新应用的关键。高通 QCS6490 平台作为专为高性能边缘计算打造的佼佼者,其卓越的硬件性能为众多复杂任务的高效执行提供了坚实基础。

 

QCS6490 集成多达 8 核的高通 Kryo 670 CPU,4 个高性能 Cortex - A78 核心最高频率可达 2.7GHz,4 个 Cortex - A55 核心以 1.9GHz 运行,可依据负载动态调配资源,实现出色的性能功耗比。其搭载的 Adreno 643 GPU,运行频率 812MHz,支持多种图形标准,保障图形密集型任务流畅运行。Adreno 633 VPU 能流畅编解码 4K 分辨率视频,编码支持 H.264 和 H.265 格式,最高 4K 30fps,解码支持 H.264、H.265、VP9 格式,最高 4K 60fps ,还支持 HDR10 和 HDR 10 + 视频播放。尤为突出的是,第 6 代高通 AI Engine 融合 Hexagon 处理器与 AI 加速器,AI 性能最高达 12 TOPS,在低功耗下即可完成高强度 AI 推理任务 。同时,企业级 Wi - Fi 6 和 Wi - Fi 6E(6GHz 频段)、高性能三重 ISP 支持最多 5 路并发摄像头及 192MP 图像捕捉、丰富的扩展接口以及对多系统的长期支持等特性,使其成为边缘计算场景的理想选择。

 

与此同时,YOLOv6 系列模型在目标检测领域大放异彩。这一由美团视觉智能部研发的目标检测框架,专为工业应用而生,在网络结构、训练策略等方面进行了深度优化。其设计的 EfficientRep 骨干网,通过训练与推理阶段结构的巧妙转换,充分利用硬件计算能力,大幅降低推理延迟。例如在小型模型中,训练阶段的 Rep - Block 在推理时转换为经高度优化的 3×3 卷积层(RepConv),享有更高计算密度。此外,Rep - PAN 颈部结构在保证多尺度特征融合能力的同时,实现硬件上的高效推理;解耦头设计及 Anchor - Free 机制,进一步降低计算成本与推断延迟 。在 COCO 数据集测试中,YOLOv6 - N 以 1234FPS 的推理速度达到 35.9% 的 AP,YOLOv6 - S 以 495 FPS 的速度达到 43.5% 的 AP ,展现出速度与精度的出色平衡。

 

YOLOv6 系列模型广泛的应用场景进一步凸显其价值。在智能安防领域,可实时精准识别监控画面中的异常行为、入侵物体等,为公共安全保驾护航;自动驾驶场景里,能够迅速且准确地检测车辆、行人、交通标识等目标,助力实现安全、高效的自动驾驶;工业生产中,可用于产品质量检测,识别瑕疵产品,保障生产质量;零售分析时,能够追踪顾客行为,为优化商品陈列、提升销售策略提供数据支撑。

 

鉴于高通 QCS6490 平台强大的硬件性能与 YOLOv6 系列模型在目标检测方面的卓越表现及广泛应用前景,深入探究两者结合后的性能表现具有重要意义。本次针对高通 QCS6490 平台上 YOLOv6 系列模型的性能测试,旨在全面评估该组合在不同场景下的运行效率、检测精度等关键指标,为相关领域的技术应用与产品开发提供极具价值的参考依据,推动智能边缘计算在各行业的深度落地与创新发展 。

高通6490硬件介绍

深度解析 QCS6490:硬件性能全揭秘-CSDN博客

 

YOLOv6模型性能指标

YOLOv6系列性能指标-QCS6490

模型

尺寸640*640

 

CPUNPU QNN2.31
FP32INT8
YOLOv6n208.19 ms4.80 FPS3.11 ms321.54 FPS
YOLOv6s730.3 ms1.37 FPS8.61 ms116.14  FPS
YOLOv6m2603.81 ms0.38 FPS15.06 ms66.40 FPS
YOLOv6l2937.03 ms0.34 FPS29.72 ms33.65 FPS

点击链接可以下载YOLOv6系列模型的pt格式,其他模型尺寸可以通过AIMO转换模型,并修改下面参考代码中的model_size测试即可。

原模型文件下载链接:

https://github.com/meituan/YOLOv6

(一)将pt模型转换为onnx格式

Step1:升级pip版本为25.1.1

python3.10 -m pip install --upgrade pip
pip -V
aidlux@aidlux:~/aidcode$ pip -V
pip 25.1.1 from /home/aidlux/.local/lib/python3.10/site-packages/pip (python 3.10)

Step2:安装ultralytics和onnx

pip install ultralytics onnx

Step3:设置yolo命令的环境变量

方法 1:临时添加环境变量(立即生效)

在终端中执行以下命令,将 ~/.local/bin 添加到当前会话的环境变量中

export PATH="$PATH:$HOME/.local/bin"
  • 说明:此操作仅对当前终端会话有效,关闭终端后失效。
  • 验证:执行 yolo --version,若输出版本号(如 0.0.2),则说明命令已生效。

方法 2:永久添加环境变量(长期有效)

echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.bashrc
source ~/.bashrc  # 使修改立即生效

验证:执行 yolo --version,若输出版本号(如 0.0.2),则说明命令已生效。

测试环境中安装yolo版本为8.3.152

 提示:如果遇到用户组权限问题,可以忽悠,因为yolo命令会另外构建临时文件,也可以执行下面命令更改用户组,执行后下面的警告会消失:

sudo chown -R aidlux:aidlux ~/.config/
sudo chown -R aidlux:aidlux ~/.config/Ultralytics

可能遇见的报错如下:

WARNING ⚠️ user config directory '/home/aidlux/.config/Ultralytics' is not writeable, defaulting to '/tmp' or CWD.Alternatively you can define a YOLO_CONFIG_DIR environment variable for this path.

Step4:将Yolov6系列模型的pt格式转换为onnx格式

新建一个python文件,命名自定义即可,用于模型转换以及导出:

from ultralytics import YOLO

# 加载同级目录下的.pt模型文件
model = YOLO('yolo6n.pt')  # 替换为实际模型文件名

# 导出ONNX配置参数
export_params = {
    'format': 'onnx',
    'opset': 12,          # 推荐算子集版本
    'simplify': True,     # 启用模型简化
    'dynamic': False,     # 固定输入尺寸
    'imgsz': 640,         # 标准输入尺寸
    'half': False         # 保持FP32精度
}

# 执行转换并保存到同级目录
model.export(**export_params)

执行该程序完成将pt模型导出为onnx模型。

提示:Yolo6s,Yolo6m,Yolo6l替换代码中Yolo6n即可;

(二)使用AIMO将onnx模型转换高通NPU可以运行的模型格式

Step1:选择模型优化,模型格式选择onnx格式上传模型

 

Step2:选择芯片型号以及目标框架,这里我们选择QCS6490+Qnn2.31

 

Step3:点击查看模型,使用Netron查看模型结构,进行输入输出的填写

使用Netron工具查看onnx模型结构,选择剪枝位置

 

/detect/Transpose_output_0
/detect/Mul_output_0

 

 

参考上图中红色框部分填写,其他不变,注意开启自动量化功能,AIMO更多操作查看使用说明或参考AIMO平台

 

Step4:接下来进行提交即可,转换完成后将目标模型文件下载,解压缩后其中的.bin.aidem文件即为模型文件

 

测试代码

# run_test.py
# 导入必要的库
import os
import torch  # PyTorch库,用于张量操作和后处理
import cv2  # OpenCV库,用于图像处理
import numpy as np  # NumPy库,用于数值计算
import time  # 时间库,用于计时
import aidlite  # Aidlite库,用于模型推理加速
import argparse  # 命令行参数解析库

# 从工具模块导入辅助函数
from utils import (
    letterbox,  # 图像等比例缩放并填充函数
    plot_box_and_label,  # 绘制检测框和标签函数
    rescale,  # 坐标缩放函数
    generate_colors,  # 生成类别颜色函数
    non_max_suppression  # 非极大值抑制函数,用于过滤重叠检测框
)


def process_image(path, img_size):
    """
    处理输入图像,使其符合模型输入要求
    :param path: 图像文件路径
    :param img_size: 模型输入尺寸 (height, width)
    :return: 
        resize_img: 等比例缩放后的图像
        input_img: 经过预处理、符合模型输入格式的图像
        img_src: 原始图像(RGB格式)
    """
    # 读取图像(默认BGR格式)
    img_src = cv2.imread(path)
    # 转换为RGB格式(模型通常期望RGB输入)
    img_src = cv2.cvtColor(img_src, cv2.COLOR_BGR2RGB)
    # 使用letterbox函数进行等比例缩放,保持原图比例并填充黑边
    resize_img = letterbox(img_src, img_size)[0]
    # 获取缩放后图像的高和宽
    new_h, new_w, _ = resize_img.shape
    # 创建符合模型输入尺寸的空白图像(填充0)
    input_img = np.zeros((img_size[0], img_size[1], 3), np.uint8)
    # 将缩放后的图像放入空白图像的左上角
    input_img[0:new_h, 0:new_w] = resize_img
    # 转换为float32类型并归一化到[0.0, 1.0]
    input_img = input_img.astype(np.float32)
    input_img /= 255.0
    # 添加批次维度(模型输入通常为[batch, height, width, channel])
    input_img = np.expand_dims(input_img, 0)
    return resize_img, input_img, img_src


def main(args):
    """
    主函数:加载模型、处理图像、执行推理、后处理并保存结果
    :param args: 命令行参数对象
    :return: 成功返回True,失败返回False
    """
    print("Start main ... ...")

    # 模型输入尺寸和输出尺寸配置
    size = 640  # 模型输入图像的宽高(正方形)
    out_size = 8400  # 模型输出的预测框数量

    # 创建Aidlite配置实例
    config = aidlite.Config.create_instance()
    if config is None:
        print("Create config failed !")
        return False

    # 配置模型运行类型为本地推理
    config.implement_type = aidlite.ImplementType.TYPE_LOCAL

    # 根据模型类型配置框架类型
    if args.model_type.lower() == "qnn":
        # QNN框架(Qualcomm Neural Network)
        config.framework_type = aidlite.FrameworkType.TYPE_QNN223
    elif args.model_type.lower() in ["snpe2", "snpe"]:
        # SNPE框架(Snapdragon Neural Processing Engine)
        config.framework_type = aidlite.FrameworkType.TYPE_SNPE2

    # 配置加速类型为DSP(数字信号处理器)
    config.accelerate_type = aidlite.AccelerateType.TYPE_DSP
    # 标记为量化模型
    config.is_quantify_model = 1

    # 创建模型实例,加载目标模型文件
    model = aidlite.Model.create_instance(args.target_model)
    if model is None:
        print("Create model failed !")
        return False

    # 配置模型输入输出形状和数据类型
    input_shapes = [[1, size, size, 3]]  # 输入形状:[batch, height, width, channel]
    output_shapes = [
        [1, out_size, 80],  # 输出1:类别概率,形状为[batch, 8400, 80](80个类别)
        [1, out_size, 4]    # 输出2:边界框坐标,形状为[batch, 8400, 4](x1,y1,x2,y2)
    ]
    # 设置模型输入输出属性
    model.set_model_properties(
        input_shapes,
        aidlite.DataType.TYPE_FLOAT32,  # 输入数据类型:32位浮点数
        output_shapes,
        aidlite.DataType.TYPE_FLOAT32   # 输出数据类型:32位浮点数
    )

    # 根据模型和配置构建解释器(推理执行器)
    interpreter = aidlite.InterpreterBuilder.build_interpretper_from_model_and_config(model, config)
    if interpreter is None:
        print("build_interpretper_from_model_and_config failed !")
        return None

    # 初始化解释器
    result = interpreter.init()
    if result != 0:
        print(f"interpreter init failed !")
        return False

    # 加载模型到解释器
    result = interpreter.load_model()
    if result != 0:
        print("interpreter load model failed !")
        return False
    print("detect model load success!")

    # 处理输入图像
    img_size = [size, size]
    resize_img, input_img, img_src = process_image(args.imgs, img_size)

    # 执行推理并记录时间
    invoke_time = []  # 存储每次推理的耗时(毫秒)
    for i in range(args.invoke_nums):
        # 设置输入张量
        result = interpreter.set_input_tensor(0, input_img.data)
        if result != 0:
            print("interpreter set_input_tensor() failed")

        # 记录推理开始时间
        t1 = time.time()
        # 执行推理
        result = interpreter.invoke()
        # 计算推理耗时(转换为毫秒)
        cost_time = (time.time() - t1) * 1000
        invoke_time.append(cost_time)

        if result != 0:
            print("interpreter invoke() failed")

        # 获取输出张量并调整形状
        # 输出1:类别概率 [1, 8400, 80]
        qnn_trans = interpreter.get_output_tensor(0).reshape(1, out_size, 80)
        # 输出2:边界框坐标 [1, 8400, 4]
        qnn_mul = interpreter.get_output_tensor(1).reshape(1, out_size, 4)

    # 销毁解释器,释放资源
    result = interpreter.destory()

    # 统计推理时间信息
    max_invoke_time = max(invoke_time)  # 最大耗时
    min_invoke_time = min(invoke_time)  # 最小耗时
    mean_invoke_time = sum(invoke_time) / args.invoke_nums  # 平均耗时
    var_invoketime = np.var(invoke_time)  # 方差(衡量耗时稳定性)
    print("=======================================")
    print(f"QNN inference {args.invoke_nums} times :")
    print(f" --mean_invoke_time is {mean_invoke_time} ms")
    print(f" --max_invoke_time is {max_invoke_time} ms")
    print(f" --min_invoke_time is {min_invoke_time} ms")
    print(f" --var_invoketime is {var_invoketime}")
    print("=======================================")

    # 后处理参数配置
    conf_thres = 0.25  # 置信度阈值,过滤低置信度检测框
    iou_thres = 0.45   # IOU阈值,非极大值抑制参数
    max_det = 1000     # 最大检测框数量
    agnostic_nms = False  # 是否使用类别无关的非极大值抑制
    classes = None       # 只检测特定类别(None表示检测所有类别)
    hide_labels = False  # 是否隐藏标签
    hide_conf = False    # 是否隐藏置信度

    # 构造完整的预测结果(合并边界框、置信度和类别概率)
    # 生成置信度张量(初始化为1,实际应用中应从模型输出获取)
    qnn_conf = np.ones((1, out_size, 1))
    # 拼接预测结果:[x1,y1,x2,y2,conf,class1,class2,...,class80]
    qnn_predict = np.concatenate((qnn_mul, qnn_conf, qnn_trans), axis=2)
    # 转换为PyTorch张量用于后处理
    pred_results = torch.from_numpy(qnn_predict.copy())
    # 执行非极大值抑制,过滤重叠检测框
    det = non_max_suppression(
        pred_results,
        conf_thres,
        iou_thres,
        classes,
        agnostic_nms,
        max_det=max_det
    )[0]  # 取第一个批次的结果

    # COCO数据集80个类别的名称列表
    class_names = [
        'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light',
        'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
        'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
        'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard',
        'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
        'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
        'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
        'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear',
        'hair drier', 'toothbrush'
    ]

    # 复制原始图像用于绘制结果
    img_ori = img_src.copy()
    print(f"Detected {len(det)} targets.")  # 打印检测到的目标数量

    # 如果有检测结果,绘制边界框和标签
    if len(det):
        # 将检测框坐标从缩放后的图像尺寸映射回原始图像尺寸
        det[:, :4] = rescale(resize_img.shape[:2], det[:, :4], img_src.shape).round()

        # 遍历所有检测结果(逆序遍历,确保先绘制的框不被后绘制的覆盖)
        for *xyxy, conf, cls in reversed(det):
            class_num = int(cls)  # 类别索引
            # 生成标签文本(根据配置决定是否包含类别名和置信度)
            label = None if hide_labels else (
                class_names[class_num] if hide_conf else f'{class_names[class_num]} {conf:.2f}'
            )
            # 绘制边界框和标签
            plot_box_and_label(
                img_ori,
                max(round(sum(img_ori.shape) / 2 * 0.003), 2),  # 线条粗细
                xyxy,  # 边界框坐标
                label,  # 标签文本
                color=generate_colors(class_num, True)  # 类别对应的颜色
            )

    # 将结果图像从RGB转换为BGR(OpenCV默认格式)并保存
    cv2.imwrite("./python/results.png", cv2.cvtColor(img_ori, cv2.COLOR_RGB2BGR))


def parser_args():
    """
    解析命令行参数
    :return: 解析后的参数对象
    """
    parser = argparse.ArgumentParser(description="Run model benchmarks")
    # 模型路径参数
    parser.add_argument(
        '--target_model',
        type=str,
        default='./models/cutoff_yolov6l_w8a16.qnn223.ctx.bin.aidem',
        help="inference model path"
    )
    # 测试图像路径参数
    parser.add_argument(
        '--imgs',
        type=str,
        default='./python/test.png',
        help="Predict images path"
    )
    # 推理次数参数
    parser.add_argument(
        '--invoke_nums',
        type=int,
        default=10,
        help="Inference nums"
    )
    # 模型类型参数(QNN或SNPE)
    parser.add_argument(
        '--model_type',
        type=str,
        default='QNN',
        help="run backend"
    )
    return parser.parse_args()


# 程序入口
if __name__ == "__main__":
    args = parser_args()  # 解析命令行参数
    main(args)  # 执行主函数

结果

...全文
166 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

4,662

社区成员

发帖
与我相关
我的任务
社区描述
本论坛以AI、WoS 、XR、IoT、Auto、生成式AI等核心板块组成,为开发者提供便捷及高效的学习和交流平台。 高通开发者专区主页:https://qualcomm.csdn.net/
人工智能物联网机器学习 技术论坛(原bbs) 北京·东城区
社区管理员
  • csdnsqst0050
  • chipseeker
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧