高通QCS8550部署YOLO11-pose模型与性能测试

伊利丹~怒风
企业官方账号
2025-07-04 12:09:43

 前言

在科技飞速发展的当下,边缘计算与人工智能的融合正在各个领域掀起创新的浪潮。在这一进程中,硬件平台的性能优劣起着举足轻重的作用。今天,我们将目光聚焦于一款实力强劲的硬件 —— 高通 QCS8550,并深入探索如何在该平台上部署热门的 YOLO11-pose 模型。

高通 QCS8550 采用先进的 4 纳米制程工艺,在提升性能的同时有效降低了功耗。其核心由八核 Kryo CPU 构成,具体包括一个主频高达 3.2GHz 的超大核,能轻松应对高强度任务;四个主频为 2.8GHz 的性能内核,兼顾性能与能效;还有三个主频 2.0GHz 的效率内核,负责日常轻负载操作,这种合理的内核配置实现了任务处理的高效与节能。在图形处理方面,集成的 Adreno 740 GPU 表现卓越,支持 4K@60Hz 的显示输出,为高清多媒体应用提供有力支撑。不仅如此,该平台在视频处理能力上也十分出色,支持 8K@30fps 的视频编码以及 8K@60fps 的视频解码,并且兼容 H.264 和 H.265 编码标准,能满足各类高品质视频应用的需求。而其最引人注目的,是高达 48TOPS 的 AI 算力,内部集成的高通 Hexagon 神经网络处理单元支持先进的 INT4 AI 精度格式,相比上一代产品,在持续 AI 推理方面实现了 60% 的能效提升,这使其在智能监控、机器人、自动驾驶辅助等对 AI 性能和能耗有严苛要求的长时间运行智能场景中表现出众。

YOLO11-pose 模型作为计算机视觉领域的重要成果,在姿态估计任务中发挥着关键作用。姿态估计旨在识别图像中特定点(即关键点)的位置,这些关键点可代表物体的各个部分,如关节、地标或其他显著特征 。通过输出一组代表图像中物体关键点的点及每个点的置信度分数,YOLO11-pose 模型能够助力我们理解物体的位置、姿势或运动状态,在体育分析、动物行为监测、机器人控制等众多场景中有着广泛应用 。

接下来,让我们一同深入探讨如何在高通 QCS8550 上成功部署 YOLO11-pose 模型,并对其性能展开全面测试,期望能为相关领域的研究与应用提供有价值的参考 。

Qualcomm Dragonwing™ QCM8550 | Qualcomm

YOLO11-pose

模型优化平台 (AIMO) 用户指南 | APLUX Doc Center

本次测试采用的硬件设备 

 YOLO11-pose模型性能指标

模型

尺寸640*640

CPU

NPU QNN2.31

NPU QNN2.31
FP32FP16INT8
YOLO11n-pose231.52 ms4.32 FPS4.77 ms209.64 FPS2.01 ms497.51 FPS
YOLO11s-pose510.9 ms1.96 FPS6.88 ms145.35 FPS2.89 ms346.02 FPS
YOLO11m-pose1247.41 ms0.80 FPS16.12 ms62.03 FPS5.32 ms187.97 FPS
YOLO11l-pose​1589.24 ms0.63 FPS20.41 ms49.00 FPS6.83 ms146.41 FPS
YOLO11x-pose​3062.26 ms0.33 FPS49.77 ms20.09 FPS13.4 ms74.63 FPS

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

(一)将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:将Yolov11系列模型的pt格式转换为onnx格式

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

from ultralytics import YOLO

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

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

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

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

提示:Yolo11s-pose,Yolo11m-pose,Yolo11l-pose,Yolo11x-pose替换代码中Yolo11n即可;

 

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

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

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

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

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

/model.23/Mul_2_output_0

/model.23/Sigmoid_output_0

/model.23/Reshape_7_output_0

 

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

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

 (三)在QCS8550的NPU中推理Yolov11n-pose_int8模型

检查aidlux环境中的aidlite版本是否与我们转换模型时选择的Qnn版本一致,终端执行:

sudo aid-pkg installed 

如果没有aidlite-qnn231,需要安装:

sudo aid-pkg update
sudo aid-pkg install aidlite-sdk
 
# Install the latest version of AidLite (latest QNN version)
sudo aid-pkg install aidlite

💡注意

Linux环境下,安装指定QNN版本的AidLite SDK:sudo aid-pkg install aidlite-{QNN Version}

例如:安装QNN2.31版本的AidLite SDK —— sudo aid-pkg install aidlite-qnn231

 模型进行AI推理:

import time
import numpy as np
import cv2
import aidlite
import argparse

# --------------------- COCO‑17 骨架拓扑与颜色 ---------------------
# 定义COCO数据集的17个关键点连接关系,用于绘制骨架
SKELETON = [
    (15, 13), (13, 11), (16, 14), (14, 12), (11, 12),  # 下肢和躯干连接
    (5, 11),  (6, 12),  (5, 6),   (5, 7),   (6, 8),       # 躯干和上肢连接
    (7, 9),   (8, 10),  (1, 2),   (0, 1),   (0, 2),       # 头部和肩部连接
    (1, 3),   (2, 4),   (3, 5),   (4, 6)                   # 面部和四肢连接
]

# 定义可视化颜色方案:检测框、关键点和骨架连接线的颜色
COLORS = {
    "bbox": (0, 255, 0),   # 绿色框
    "kpt":  (0, 0, 255),   # 红色点
    "link": (255, 0, 0)    # 蓝色骨架
}

# ----------------------------- 工具函数 -----------------------------
def iou_xyxy(box1, box2):
    """
    计算两个边界框的交并比(Intersection over Union, IoU)
    输入格式为[x1,y1,x2,y2],表示左上角和右下角坐标
    """
    # 计算交集区域的左上角和右下角坐标
    xa, ya = max(box1[0], box2[0]), max(box1[1], box2[1])
    xb, yb = min(box1[2], box2[2]), min(box1[3], box2[3])
    
    # 计算交集面积
    inter = max(0, xb - xa) * max(0, yb - ya)
    
    # 计算两个边界框的面积
    area1 = (box1[2]-box1[0]) * (box1[3]-box1[1])
    area2 = (box2[2]-box2[0]) * (box2[3]-box2[1])
    
    # 计算IoU,添加小常数避免除零
    return inter / (area1 + area2 - inter + 1e-6)

def nms(dets, confs, iou_thres):
    """
    非极大值抑制(Non-Maximum Suppression, NMS)算法
    过滤重叠的检测框,保留置信度最高且不重叠的框
    返回保留的检测框索引
    """
    # 按置信度降序排序
    idxs = np.argsort(-confs)
    keep = []
    
    while idxs.size:
        # 选择置信度最高的框
        i = idxs[0]
        keep.append(i)
        
        if idxs.size == 1:
            break
            
        # 计算当前框与剩余框的IoU
        ious = np.array([iou_xyxy(dets[i], dets[j]) for j in idxs[1:]])
        
        # 保留IoU小于阈值的框
        idxs = idxs[1:][ious < iou_thres]
        
    return keep

def scale_coords(coords, ratio):
    """
    将模型输出的0-640归一化坐标缩放回原图尺寸
    ratio: 原图与模型输入尺寸的比例因子
    """
    return coords * ratio

def draw_pose(img, bbox, kpts, kpt_thr=0.3):
    """
    在原图上绘制检测框、关键点及骨架连接
    img: 输入图像
    bbox: 边界框坐标 [x1, y1, x2, y2]
    kpts: 关键点数组,形状为[17, 3],每个关键点包含(x, y, 置信度)
    kpt_thr: 关键点显示阈值,低于此值的关键点不显示
    """
    # 绘制边界框
    x1, y1, x2, y2 = map(int, bbox)
    cv2.rectangle(img, (x1, y1), (x2, y2), COLORS["bbox"], 2)

    # 绘制关键点
    for x, y, s in kpts:
        if s < kpt_thr:  # 过滤低置信度关键点
            continue
        cv2.circle(img, (int(x), int(y)), 3, COLORS["kpt"], -1)

    # 绘制骨架连接线
    for a, b in SKELETON:
        if kpts[a][2] < kpt_thr or kpts[b][2] < kpt_thr:  # 过滤低置信度连接
            continue
        xa, ya = int(kpts[a][0]), int(kpts[a][1])
        xb, yb = int(kpts[b][0]), int(kpts[b][1])
        cv2.line(img, (xa, ya), (xb, yb), COLORS["link"], 2)

# ----------------------------- 主函数 -----------------------------
def main(args):
    print("Start image inference ... ...")
    size = 640  # 模型输入分辨率

    # ---------- 1. 创建模型 & 解释器 ----------
    # 初始化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":
        config.framework_type = aidlite.FrameworkType.TYPE_QNN231
    elif args.model_type.lower() in ("snpe2", "snpe"):
        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]]  # 输入: [批次, 高度, 宽度, 通道]
    output_shapes = [[1, 51, 8400], [1, 1, 8400], [1, 4, 8400]]  # 输出: [关键点, 置信度, 边界框]
    model.set_model_properties(input_shapes, aidlite.DataType.TYPE_FLOAT32,
                               output_shapes, aidlite.DataType.TYPE_FLOAT32)

    # 构建并初始化模型解释器
    interpreter = aidlite.InterpreterBuilder.build_interpretper_from_model_and_config(model, config)
    if interpreter is None or interpreter.init() != 0 or interpreter.load_model() != 0:
        print("Interpreter init/load failed !")
        return False
    print("detect model load success!")

    # ---------- 2. 读取并预处理图像 ----------
    # 读取输入图像
    img = cv2.imread(args.image_path)
    if img is None:
        print("Error: Could not open image file")
        return False
    
    # 获取原始图像尺寸并计算填充后的尺寸
    h0, w0 = img.shape[:2]
    length = max(h0, w0)  # 取最大边作为填充后的尺寸
    ratio  = length / size  # 计算缩放比例

    # 创建填充画布并将图像居中放置
    canvas = np.zeros((length, length, 3), np.uint8)
    canvas[0:h0, 0:w0] = img
    
    # 颜色空间转换、调整大小并归一化
    img_in = cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB)
    img_in = cv2.resize(img_in, (size, size))
    img_in = img_in.astype(np.float32) / 255.0  # 归一化到 0-1

    # ---------- 3. 预热 ----------
    # 进行3次推理预热,确保性能测试准确性
    for _ in range(3):
        interpreter.set_input_tensor(0, img_in.data)
        interpreter.invoke()
        
    # ---------- 4. 推理 ----------

    # 性能测试:执行多次推理并记录时间
    invoke_nums = 100
    invoke_times = []
    print(f"Running performance test with {invoke_nums} iterations...")
    
    for i in range(invoke_nums):
        interpreter.set_input_tensor(0, img_in.data)
        t1 = time.time()
        result = interpreter.invoke()
        t2 = time.time()
        
        if result != 0:
            print("interpreter invoke() failed")
            return False
            
        invoke_time = (t2 - t1) * 1000  # 转换为毫秒
        invoke_times.append(invoke_time)
        
        # 每10次打印一次进度
        if (i + 1) % 10 == 0:
            print(f"Completed {i + 1}/{invoke_nums} iterations")

    # 计算性能统计指标
    mean_invoke_time = np.mean(invoke_times)  # 平均推理时间
    max_invoke_time = np.max(invoke_times)    # 最大推理时间
    min_invoke_time = np.min(invoke_times)    # 最小推理时间
    var_invoke_time = np.var(invoke_times)    # 推理时间方差
    fps = 1000 / mean_invoke_time  # 计算FPS (每秒帧数)
    
    # 打印性能测试结果
    print(f"\nInference {invoke_nums} times:\n"
          f"-- mean_invoke_time is {mean_invoke_time:.2f} ms\n"
          f"-- max_invoke_time is {max_invoke_time:.2f} ms\n"
          f"-- min_invoke_time is {min_invoke_time:.2f} ms\n"
          f"-- var_invoke_time is {var_invoke_time:.2f}\n"
          f"-- FPS: {fps:.2f}\n")
    
    # ---------- 5. 取输出 ----------
    # 获取模型输出并重塑形状
    qnn_local = interpreter.get_output_tensor(0).reshape(1, 51, 8400)  # 关键点输出
    qnn_conf  = interpreter.get_output_tensor(1).reshape(1, 1, 8400)  # 置信度输出
    qnn_bbox  = interpreter.get_output_tensor(2).reshape(1, 4, 8400)  # 边界框输出

    # 合并输出并调整形状为 [检测数量, 56]
    pred = np.concatenate((qnn_local, qnn_conf, qnn_bbox), axis=1)  # (1,56,8400)
    pred = pred.transpose(0, 2, 1)[0]                               # (8400,56)

    # ---------- 6. 后处理 ----------
    # 提取置信度并过滤低于阈值的预测
    confs = pred[:, 51]
    mask  = confs > args.conf_thres
    if not np.any(mask):
        print("No objects found.")
        interpreter.destory()
        return True

    # 应用掩码过滤预测结果
    pred  = pred[mask]
    confs = confs[mask]

    # 将边界框从中心点+宽高格式转换为左上角+右下角格式
    b = pred[:, 52:56]
    boxes_xyxy = np.empty_like(b)
    boxes_xyxy[:, 0] = b[:, 0] - b[:, 2] / 2  # x1 = cx - w/2
    boxes_xyxy[:, 1] = b[:, 1] - b[:, 3] / 2  # y1 = cy - h/2
    boxes_xyxy[:, 2] = b[:, 0] + b[:, 2] / 2  # x2 = cx + w/2
    boxes_xyxy[:, 3] = b[:, 1] + b[:, 3] / 2  # y2 = cy + h/2

    # 应用非极大值抑制过滤重叠框
    keep = nms(boxes_xyxy, confs, args.iou_thres)
    boxes_xyxy = boxes_xyxy[keep]
    kpts = pred[keep, :51].reshape(-1, 17, 3)  # 重塑关键点数据为 [人数, 17关键点, 3坐标值]

    # ---------- 7. 可视化 ----------
    # 复制原图用于绘制结果
    out_img = img.copy()
    
    # 遍历每个检测结果并绘制
    for box, kp in zip(boxes_xyxy, kpts):
        # 将模型坐标缩放回原图尺寸
        box_scaled = scale_coords(box, ratio)
        kp_scaled  = scale_coords(kp[:, :2], ratio)
        
        # 合并坐标和置信度
        kp_vis = np.concatenate([kp_scaled, kp[:, 2:3]], axis=1)
        
        # 绘制人体姿态
        draw_pose(out_img, box_scaled, kp_vis, kpt_thr=0.25)

    # 保存结果图像
    cv2.imwrite("result1111.jpg", out_img)
    print("Result saved to result11.jpg")

    # 释放资源
    interpreter.destory()
    return True

# --------------------------- 参数解析 ---------------------------
def parser_args():
    """解析命令行参数"""
    parser = argparse.ArgumentParser(description="Run image inference with YOLOv11‑pose")
    
    # 模型路径参数
    parser.add_argument('--target_model', type=str,
                        default='yolov11n_pose/cutoff_yolo11n-pose_qcs8550_w8a8.qnn231.ctx.bin',
                        help="Path to model binary")
    
    # 输入图像路径参数
    parser.add_argument('--image_path', type=str, default='bus.jpg', help="Input image path")
    
    # 模型后端类型参数
    parser.add_argument('--model_type', type=str, default='QNN', help="Backend: QNN / SNPE2")
    
    # 置信度阈值参数
    parser.add_argument('--conf_thres', type=float, default=0.25, help="Confidence threshold")
    
    # NMS IoU阈值参数
    parser.add_argument('--iou_thres',  type=float, default=0.45, help="NMS IoU threshold")
    
    return parser.parse_args()

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

效果:

 

 

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

3,231

社区成员

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

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