3,768
社区成员




在科技迅猛发展的当下,边缘智能计算与计算机视觉技术的融合,正为诸多行业带来变革性的突破。高通 QC6490 平台作为这一技术浪潮中的关键力量,凭借其卓越性能,在众多领域展现出巨大潜力。
高通 QC6490 采用先进的 6nm 制程工艺,集成了八核高通 Kryo 670 CPU,性能极为强劲。其中,4 个高性能 Cortex - A78 核心可睿频至 2.7GHz,能高效处理复杂任务;4 个 Cortex - A55 核心则以 1.9GHz 左右的频率保障日常轻负载工作流畅运行,大小核协同工作,实现了出色的性能功耗比。其 AI 运算能力同样不容小觑,借助第 6 代高通 AI Engine,搭配 Hexagon 处理器与融合 AI 加速器,拥有高达 12 TOPS 的算力,这使得设备即便在低功耗状态下,也能快速且精准地完成各类 AI 推理任务 。在连接性方面,它支持企业级 Wi-Fi 6/6E,数据传输速率高达多千兆位,延迟极低,完全能满足实时性应用对数据传输的严苛要求。而其高性能三重 ISP,最多可支持 5 路摄像头并发工作,且能捕捉高达 192MP 的图像,为视觉处理提供了丰富且高质量的图像源。
在机器人领域,高通 QC6490 的优势得以充分发挥。在服务机器人中,其强大的计算能力可快速处理传感器收集的环境信息,配合先进的 AI 算法,实现精准的路径规划与灵活的人机交互;在工业生产机器人方面,能够实时分析生产线上的产品图像,利用强大的 AI 推理能力检测产品缺陷,保障产品质量,其多核心 CPU 与高速数据传输能力确保了生产过程的高效与稳定 。在无人机领域,QC6490 平台助力无人机实现更智能的飞行控制与任务执行。在测绘、巡检等应用场景中,无人机通过高性能 ISP 连接的摄像头采集大量图像数据,QC6490 可实时对这些图像进行处理分析,如识别输电线路的故障点、监测农田的作物生长状况等,Wi-Fi 6/6E 的低延迟传输特性,则能将处理结果快速回传至地面控制站,保证了无人机作业的高效与安全 。
YOLOv5 系列模型作为目标检测领域的佼佼者,自开源以来便备受关注。它基于 PyTorch 框架开发,继承并发展了 YOLO 系列的核心设计理念,以高效的网络架构实现对输入图像的快速且精准的处理。YOLOv5 网络架构主要由输入端、Backbone、Neck 和 Prediction 四部分构成。在输入端,采用了 Mosaic 数据增强技术,将多张图片拼接进行训练,极大地丰富了训练数据的多样性,提升了模型的泛化能力;Backbone 部分借鉴了 CSPNet 的设计思路,构建了 C3 模块,有效减少了梯度信息的重复计算,显著增强了特征提取能力;Neck 部分运用 FPN(特征金字塔网络)和 PAN(路径聚合网络)相结合的结构,通过自顶向下与自底向上的双向特征融合,让模型能够更好地利用不同尺度的特征图,提高对各种大小目标的检测精度;在 Prediction 阶段,模型通过预设的先验锚点,进行目标置信度计算与边界框回归,并利用阈值过滤和非极大值抑制(NMS)算法,输出最终的检测结果 。YOLOv5 系列包含如 YOLOv5s、YOLOv5m、YOLOv5l、YOLOv5x 等多个不同版本,各版本在网络的深度和宽度上有所差异,开发者可依据实际项目中的计算资源限制、对检测精度以及检测速度的不同要求,灵活选择最适配的版本。该系列模型广泛应用于安防监控,能实时监测异常行为与入侵事件;在交通管理中,可对车辆、行人进行检测与识别,助力智能交通系统的高效运行;在工业检测领域,能够快速检测产品表面的缺陷与瑕疵,提高生产效率与产品质量;甚至在医学影像分析方面,也可辅助医生检测特定的病变区域,为医疗诊断提供有力支持 。
鉴于高通 QC6490 平台强大的计算、AI 及连接能力,以及 YOLOv5 系列模型在目标检测领域的领先优势,深入探究两者结合后的性能表现具有重要的现实意义。本次针对高通 QC6490 平台上 YOLOv5 系列模型的性能测试,将全面、深入地分析不同 YOLOv5 版本在该平台上的运行效率、检测精度等关键指标,为相关行业在边缘设备上部署智能视觉应用提供详实、可靠的数据参考与专业的技术指导,推动智能物联网应用迈向新的发展高度。
模型下载网站: YOLO-Ultralytics YOLO 文档
模型转换网站:AI Model Optimizer
模型广场: 端侧AI生态门户
接口参考文件AidLite Python 接口文档 | APLUX Doc Center
模型 尺寸640*640 |
CPU | NPU QNN2.31 | ||
FP32 | INT8 | |||
YOLOv5n | 154.02 ms | 6.49 FPS | 3.14 ms | 318.47 FPS |
YOLOv5s | 375.6 ms | 2.66 FPS | 4.89 ms | 204.50 FPS |
YOLOv5m | 1439.28 ms | 0.69 FPS | 13.42 ms | 74.52 FPS |
YOLOv5l | 2780 ms | 0.36 FPS | 23.19 ms | 43.12 FPS |
YOLOv5x | 3525 ms | 0.28 FPS | 41.09 ms | 24.34 FPS |
点击链接可以下载YOLOv5系列模型的pt格式,其他模型尺寸可以通过AIMO转换模型,并修改下面参考代码中的model_size测试即可
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)
pip install ultralytics onnx
方法 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.
新建一个python文件,命名自定义即可,用于模型转换以及导出:
from ultralytics import YOLO
# 加载同级目录下的.pt模型文件
model = YOLO('yolo5n.pt') # 替换为实际模型文件名
# 导出ONNX配置参数
export_params = {
'format': 'onnx',
'opset': 12, # 推荐算子集版本
'simplify': True, # 启用模型简化
'dynamic': False, # 固定输入尺寸
'imgsz': 640, # 标准输入尺寸
'half': False # 保持FP32精度
}
# 执行转换并保存到同级目录
model.export(**export_params)
执行该程序完成将pt模型导出为onnx模型。
提示:Yolov5s,Yolov5m,Yolov5l,Yolov5x替换代码中Yolo5n即可;
使用Netron工具查看onnx模型结构,选择剪枝位置
/model.24/m.0/Conv_output_0
/model.24/m.1/Conv_output_0
/model.24/m.2/Conv_output_0
参考上图中红色框部分填写,其他不变,注意开启自动量化功能,AIMO更多操作查看使用说明或参考AIMO平台
该代码实现了基于aidlite
库的 YOLOv5 目标检测流程,主要功能包括:
命令行参数解析(模型路径、图像路径、推理次数等);
aidlite
加载模型并执行推理(支持 QNN/SNPE 框架,DSP 加速);
代码遵循 YOLOv5 的经典检测流程,通过多尺度特征图融合实现不同大小目标的检测,适用于实时目标检测场景。
# 导入必要的库
import time # 用于计时
import numpy as np # 用于数值计算和数组操作
import cv2 # OpenCV库,用于图像处理
import aidlite # aidlite库,用于模型推理加速
import argparse # 用于解析命令行参数
# ------------------------------ 常量定义 ------------------------------
OBJ_CLASS_NUM = 80 # 目标类别数量(对应COCO数据集的80类)
NMS_THRESH = 0.45 # 非极大值抑制(NMS)的IOU阈值(用于去除重复检测框)
BOX_THRESH = 0.5 # 检测框置信度阈值(低于此值的框会被过滤)
MODEL_SIZE = 640 # 模型输入图像尺寸(宽/高)
OBJ_NUMB_MAX_SIZE = 64 # 最大目标数量限制
PROP_BOX_SIZE = (5 + OBJ_CLASS_NUM) # 每个预测框的属性维度(5:xywh+置信度,+类别数)
# 不同下采样率对应的特征图尺寸(YOLOv5使用3个尺度的特征图,下采样率8/16/32)
STRIDE8_SIZE = (MODEL_SIZE / 8) # 8倍下采样特征图尺寸(640/8=80)
STRIDE16_SIZE = (MODEL_SIZE / 16) # 16倍下采样特征图尺寸(640/16=40)
STRIDE32_SIZE = (MODEL_SIZE / 32) # 32倍下采样特征图尺寸(640/32=20)
# YOLOv5的锚点框(anchors),对应3个尺度的特征图,每个尺度3个锚点(格式:[w1,h1,w2,h2,w3,h3])
anchors = [
[10, 13, 16, 30, 33, 23], # 小尺度锚点(对应8倍下采样,检测小目标)
[30, 61, 62, 45, 59, 119], # 中尺度锚点(对应16倍下采样,检测中目标)
[116, 90, 156, 198, 373, 326] # 大尺度锚点(对应32倍下采样,检测大目标)
]
# COCO数据集的80个类别名称列表
coco_class = [
'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'
]
# ------------------------------ 图像预处理与坐标转换函数 ------------------------------
def eqprocess(image, size1, size2):
"""
对图像进行等比例缩放和填充,保持原始长宽比,避免图像变形
Args:
image: 输入原始图像(HWC格式)
size1: 目标图像高度
size2: 目标图像宽度
Returns:
mask: 处理后的图像(已填充至目标尺寸,背景为0)
scale: 缩放比例(原始图像 -> 处理后图像)
"""
h, w, _ = image.shape # 获取原始图像的高和宽
# 计算缩放比例(取宽和高中较小的比例,确保图像完全放入目标尺寸)
scale1 = h / size1
scale2 = w / size2
scale = max(scale1, scale2) # 缩放比例(保证图像不超出目标尺寸)
# 计算缩放后的图像尺寸
new_w, new_h = int(w / scale), int(h / scale)
# 缩放图像(使用线性插值)
img = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
# 创建目标尺寸的空白图像(填充0)
mask = np.zeros((size1, size2, 3), dtype=np.float32)
# 将缩放后的图像放置在空白图像的左上角(其余区域保持0)
mask[:new_h, :new_w] = img
return mask, scale
def xywh2xyxy(x):
"""
将边界框格式从(中心x, 中心y, 宽, 高)转换为(左上角x, 左上角y, 右下角x, 右下角y)
Args:
x: 输入边界框数组(N,4),每行对应一个框的xywh
Returns:
y: 转换后的边界框数组(N,4),每行对应一个框的xyxy
"""
y = np.copy(x) # 复制输入避免修改原数据
y[:, 0] = x[:, 0] - x[:, 2] / 2 # 左上角x = 中心x - 宽/2
y[:, 1] = x[:, 1] - x[:, 3] / 2 # 左上角y = 中心y - 高/2
y[:, 2] = x[:, 0] + x[:, 2] / 2 # 右下角x = 中心x + 宽/2
y[:, 3] = x[:, 1] + x[:, 3] / 2 # 右下角y = 中心y + 高/2
return y
def xyxy2xywh(box):
"""
将边界框格式从(左上角x, 左上角y, 右下角x, 右下角y)转换为(左上角x, 左上角y, 宽, 高)
Args:
box: 输入边界框数组(N,4),每行对应一个框的xyxy
Returns:
转换后的边界框数组(N,4),每行对应一个框的xywh(左上角坐标+宽高)
"""
box[:, 2:] = box[:, 2:] - box[:, :2] # 宽=右下角x-左上角x;高=右下角y-左上角y
return box
# ------------------------------ 非极大值抑制(NMS)与坐标裁剪 ------------------------------
def NMS(dets, scores, thresh):
"""
单类别非极大值抑制(NMS):去除重叠度高的检测框,保留置信度最高的框
Args:
dets: 边界框数组(N,4),格式为xyxy
scores: 每个框的置信度数组(N,)
thresh: IOU阈值(超过此值的框被视为重叠)
Returns:
keep: 保留的框的索引列表
"""
x1 = dets[:, 0] # 所有框的左上角x
y1 = dets[:, 1] # 所有框的左上角y
x2 = dets[:, 2] # 所有框的右下角x
y2 = dets[:, 3] # 所有框的右下角y
# 计算每个框的面积(+1是为了避免面积为0)
areas = (y2 - y1 + 1) * (x2 - x1 + 1)
keep = [] # 保存保留的框的索引
# 按置信度降序排序,获取排序索引
index = scores.argsort()[::-1]
while index.size > 0:
i = index[0] # 取当前置信度最高的框的索引
keep.append(i) # 保留该框
# 计算当前框与其他框的交叠区域坐标
x11 = np.maximum(x1[i], x1[index[1:]]) # 交叠区域左上角x
y11 = np.maximum(y1[i], y1[index[1:]]) # 交叠区域左上角y
x22 = np.minimum(x2[i], x2[index[1:]]) # 交叠区域右下角x
y22 = np.minimum(y2[i], y2[index[1:]]) # 交叠区域右下角y
# 计算交叠区域的宽和高(若为负则取0,表示无交叠)
w = np.maximum(0, x22 - x11 + 1)
h = np.maximum(0, y22 - y11 + 1)
overlaps = w * h # 交叠区域面积
# 计算IOU(交并比=交叠面积/(当前框面积+其他框面积-交叠面积))
ious = overlaps / (areas[i] + areas[index[1:]] - overlaps)
# 筛选出IOU小于等于阈值的框(保留这些框继续处理)
idx = np.where(ious <= thresh)[0]
# 更新索引(跳过已处理的框)
index = index[idx + 1] # +1是因为index[1:]比原index少一个元素
return keep
def clip_coords(boxes, img_shape):
"""
将边界框裁剪到图像范围内,防止坐标超出图像边界
Args:
boxes: 边界框数组(N,4),格式为xyxy
img_shape: 图像尺寸(高, 宽)
"""
boxes[:, 0].clip(0, img_shape[1], out=boxes[:, 0]) # x1限制在[0, 宽]
boxes[:, 1].clip(0, img_shape[0], out=boxes[:, 1]) # y1限制在[0, 高]
boxes[:, 2].clip(0, img_shape[1], out=boxes[:, 2]) # x2限制在[0, 宽]
boxes[:, 3].clip(0, img_shape[0], out=boxes[:, 3]) # y2限制在[0, 高]
# ------------------------------ 检测后处理函数 ------------------------------
def detect_postprocess(prediction, img0shape, img1shape, conf_thres=0.25, iou_thres=0.45):
"""
对模型输出的原始预测结果进行后处理,得到最终的检测框
Args:
prediction: 模型原始输出(经过Detect类处理后的预测框)
img0shape: 原始图像尺寸(高, 宽, 3)
img1shape: 输入模型的图像尺寸(高, 宽, 3)
conf_thres: 置信度阈值(低于此值的框被过滤)
iou_thres: NMS的IOU阈值
Returns:
out_boxes: 处理后的检测框数组(N,6),格式为(x1,y1,w,h,置信度,类别ID)
"""
h, w, _ = img1shape # 输入模型的图像尺寸
# 筛选出置信度高于阈值的框(第4列是置信度)
valid_condidates = prediction[prediction[..., 4] > conf_thres]
# 计算每个类别的得分(类别得分=置信度×类别概率)
valid_condidates[:, 5:] *= valid_condidates[:, 4:5] # 广播操作
# 将xywh格式转换为xyxy格式,便于后续处理
valid_condidates[:, :4] = xywh2xyxy(valid_condidates[:, :4])
# 限制最大检测框数量和尺寸,避免计算量过大
max_det = 300 # 最大检测框数量
max_wh = 7680 # 最大框尺寸(用于类别区分)
max_nms = 30000 # NMS处理的最大框数量
# 提取每个框的最高类别得分和对应的类别ID
valid_condidates[:, 4] = valid_condidates[:, 5:].max(1) # 最高类别得分(作为最终置信度)
valid_condidates[:, 5] = valid_condidates[:, 5:].argmax(1) # 最高得分对应的类别ID
# 按置信度降序排序,并限制最大数量
sort_id = np.argsort(valid_condidates[:, 4])[::-1] # 降序排序索引
valid_condidates = valid_condidates[sort_id[:max_nms]] # 取前max_nms个框
# 为NMS准备数据:将类别ID编码到坐标中(避免不同类别间的框被NMS过滤)
boxes = valid_condidates[:, :4] + valid_condidates[:, 5:6] * max_wh # 坐标+类别偏移
scores = valid_condidates[:, 4] # 置信度
# 应用NMS筛选框
index = NMS(boxes, scores, iou_thres)[:max_det] # 取前max_det个结果
out_boxes = valid_condidates[index]
# 将框裁剪到原始图像范围内
clip_coords(out_boxes[:, :4], img0shape)
# 将xyxy格式转换为xywh(左上角坐标+宽高)
out_boxes[:, :4] = xyxy2xywh(out_boxes[:, :4])
print(f"检测到{len(out_boxes)}个区域")
return out_boxes
def draw_detect_res(img, det_pred):
"""
在图像上绘制检测结果(边界框和类别标签)
Args:
img: 原始图像(HWC格式)
det_pred: 检测结果数组(N,6),格式为(x1,y1,w,h,置信度,类别ID)
Returns:
img: 绘制后的图像
"""
img = img.astype(np.uint8) # 确保图像格式正确
# 根据类别数量计算颜色步长,使不同类别有不同颜色
color_step = int(255 / len(coco_class))
for i in range(len(det_pred)):
# 提取边界框坐标(x1,y1是左上角,w,h是宽高)
x1, y1, w, h = [int(t) for t in det_pred[i][:4]]
score = det_pred[i][4] # 置信度
cls_id = int(det_pred[i][5]) # 类别ID
# 打印检测信息(控制台输出)
print(f"{i + 1} {[x1, y1, x1 + w, y1 + h]} {score:.2f} {coco_class[cls_id]}")
# 绘制类别标签(左上角文本)
cv2.putText(
img,
f'{coco_class[cls_id]}', # 标签文本
(x1, y1 - 6), # 文本位置(左上角上方)
cv2.FONT_HERSHEY_SIMPLEX, # 字体
0.5, # 字体大小
(255, 255, 255), # 文本颜色(白色)
1 # 线宽
)
# 绘制边界框(矩形)
cv2.rectangle(
img,
(x1, y1), # 左上角
(x1 + w, y1 + h), # 右下角(x1+w, y1+h)
(0, int(cls_id * color_step), int(255 - cls_id * color_step)), # 框颜色(随类别变化)
thickness=2 # 线宽
)
return img
# ------------------------------ YOLOv5检测头类 ------------------------------
class Detect():
"""
YOLOv5检测头类,用于处理模型输出的特征图,生成最终的检测框(xywh+置信度+类别概率)
"""
def __init__(self, nc=80, anchors=(), stride=[], image_size=640):
"""
初始化检测头
Args:
nc: 类别数量
anchors: 锚点框列表
stride: 下采样率列表(8,16,32)
image_size: 输入图像尺寸
"""
super().__init__()
self.nc = nc # 类别数量
self.no = nc + 5 # 每个框的输出维度(5+类别数)
self.stride = stride # 下采样率
self.nl = len(anchors) # 特征图层数(3层)
self.na = len(anchors[0]) // 2 # 每层特征图的锚点数量(3个)
# 初始化网格和锚点网格(用于坐标计算)
self.grid, self.anchor_grid = [0] * self.nl, [0] * self.nl
self.anchors = np.array(anchors, dtype=np.float32).reshape(self.nl, -1, 2) # 锚点数组(3,3,2)
# 为每个尺度的特征图生成网格和锚点网格
base_scale = image_size // 8 # 基础尺度(8倍下采样对应的尺寸)
for i in range(self.nl):
# 生成网格(grid)和锚点网格(anchor_grid)
self.grid[i], self.anchor_grid[i] = self._make_grid(
base_scale // (2 ** i), # 特征图宽度(80/40/20)
base_scale // (2 ** i), # 特征图高度(80/40/20)
i # 尺度索引
)
def _make_grid(self, nx=20, ny=20, i=0):
"""
生成特征图的网格坐标和锚点网格(用于计算绝对坐标)
Args:
nx: 特征图宽度(网格数量)
ny: 特征图高度(网格数量)
i: 尺度索引
Returns:
grid: 网格坐标(1,3,ny,nx,2),每个网格的相对坐标
anchor_grid: 锚点网格(1,3,ny,nx,2),每个网格的锚点尺寸
"""
# 生成网格坐标(x从0到nx-1,y从0到ny-1)
y, x = np.arange(ny, dtype=np.float32), np.arange(nx, dtype=np.float32)
yv, xv = np.meshgrid(y, x) # 生成网格(xv是x坐标网格,yv是y坐标网格)
yv, xv = yv.T, xv.T # 转置为(ny, nx)
# 堆叠x和y坐标,形成网格(形状:1,1,ny,nx,2),并偏移-0.5(中心化)
grid = np.stack((xv, yv), 2)
grid = grid[np.newaxis, np.newaxis, ...] # 扩展维度(1,1,ny,nx,2)
grid = np.repeat(grid, self.na, axis=1) # 复制到每个锚点(1,3,ny,nx,2)
grid -= 0.5 # 网格偏移(使坐标居中)
# 生成锚点网格(将锚点复制到每个网格)
anchor_grid = self.anchors[i].reshape((1, self.na, 1, 1, 2)) # 锚点形状(1,3,1,1,2)
anchor_grid = np.repeat(anchor_grid, repeats=ny, axis=2) # 沿高度复制(1,3,ny,1,2)
anchor_grid = np.repeat(anchor_grid, repeats=nx, axis=3) # 沿宽度复制(1,3,ny,nx,2)
return grid, anchor_grid
def sigmoid(self, arr):
"""sigmoid激活函数,将输出映射到[0,1]"""
return 1 / (1 + np.exp(-arr))
def __call__(self, x):
"""
处理模型输出的特征图,生成最终的检测框(xywh+置信度+类别概率)
Args:
x: 模型输出的3个尺度的特征图(列表)
Returns:
z: 合并后的检测框数组(1, N, 85),N是总框数
"""
z = [] # 存储所有尺度的检测结果
for i in range(self.nl):
bs, _, ny, nx = x[i].shape # 输入特征图形状(批次, 通道, 高, 宽)
# 调整特征图形状并转置(批次, 锚点数, 高, 宽, 输出维度)
x[i] = x[i].reshape(bs, self.na, self.no, ny, nx).transpose(0, 1, 3, 4, 2)
# 应用sigmoid激活函数,将输出映射到[0,1]
y = self.sigmoid(x[i])
# 计算绝对坐标(xywh)
# xy: (sigmoid输出×2 + 网格坐标)×下采样率
y[..., 0:2] = (y[..., 0:2] * 2. + self.grid[i]) * self.stride[i]
# wh: (sigmoid输出×2)² × 锚点尺寸
y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]
# 展平并添加到结果列表
z.append(y.reshape(bs, self.na * nx * ny, self.no))
# 合并所有尺度的检测框
return np.concatenate(z, 1)
# ------------------------------ 主函数与参数解析 ------------------------------
def main():
"""主函数:加载模型、处理图像、执行推理、计算性能、绘制结果"""
args = parser_args() # 解析命令行参数
target_model = args.target_model # 模型路径
model_type = args.model_type # 模型框架类型(QNN/SNPE)
size = int(args.size) # 模型输入尺寸
imgs = args.imgs # 测试图像路径
invoke_nums = int(args.invoke_nums) # 推理次数(用于性能统计)
print("Start main ... ...")
# 初始化aidlite配置
config = aidlite.Config.create_instance()
if config is None:
print("Create config failed !")
return False
# 配置aidlite参数
config.implement_type = aidlite.ImplementType.TYPE_LOCAL # 本地执行
# 设置框架类型(QNN或SNPE)
if model_type.lower() == "qnn":
config.framework_type = aidlite.FrameworkType.TYPE_QNN
elif model_type.lower() in ["snpe2", "snpe"]:
config.framework_type = aidlite.FrameworkType.TYPE_SNPE2
# 设置加速类型(DSP加速)
config.accelerate_type = aidlite.AccelerateType.TYPE_DSP
# config.accelerate_type = aidlite.AccelerateType.TYPE_NPU # 可选NPU加速
config.is_quantify_model = 0 # 非量化模型
# 加载模型
model = aidlite.Model.create_instance(target_model)
if model is None:
print("Create model failed !")
return False
# 设置模型输入输出形状和数据类型
input_shapes = [[1, size, size, 3]] # 输入形状(批次, 高, 宽, 通道)
# 输出形状(3个尺度的特征图,对应8/16/32倍下采样)
output_shapes = [[1, 20, 20, 255], [1, 40, 40, 255], [1, 80, 80, 255]]
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:
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!")
# 图像预处理
frame = cv2.imread(imgs) # 读取原始图像
# 等比例缩放并填充图像至模型输入尺寸
[height, width, _] = frame.shape # 原始图像尺寸
length = max((height, width)) # 原始图像的最大边长
scale = length / size # 缩放比例(原始图像 -> 模型输入)
ratio = [scale, scale] # 宽和高的缩放比例
# 创建空白图像(尺寸为length×length)
image = np.zeros((length, length, 3), np.uint8)
image[0:height, 0:width] = frame # 放置原始图像
# 转换颜色空间(BGR->RGB)并调整尺寸至模型输入大小
img_input = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
img_input = cv2.resize(img_input, (size, size))
# 归一化(像素值从[0,255]映射到[0,1])
mean_data = [0, 0, 0]
std_data = [255, 255, 255]
img_input = (img_input - mean_data) / std_data
img_input = img_input.astype(np.float32) # 转换为float32类型
# 执行推理并统计时间
invoke_time = [] # 存储每次推理的耗时(毫秒)
for i in range(invoke_nums):
# 设置输入张量
result = interpreter.set_input_tensor(0, img_input.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")
# 获取模型输出
out1 = interpreter.get_output_tensor(0) # 第一个尺度特征图输出
out2 = interpreter.get_output_tensor(1) # 第二个尺度特征图输出
out3 = interpreter.get_output_tensor(2) # 第三个尺度特征图输出
output = [out1, out2, out3]
# 按特征图尺寸排序(从小到大)
output = sorted(output, key=len)
# 销毁推理器(释放资源)
result = interpreter.destory()
# 计算并打印性能统计信息
max_invoke_time = max(invoke_time) # 最大耗时
min_invoke_time = min(invoke_time) # 最小耗时
mean_invoke_time = sum(invoke_time) / invoke_nums # 平均耗时
var_invoketime = np.var(invoke_time) # 方差(衡量耗时稳定性)
print("=======================================")
print(f"QNN inference {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_invoketime is {var_invoketime:.2f}")
print("=======================================")
# 检测结果后处理
stride = [8, 16, 32] # 下采样率
# 初始化YOLOv5检测头
yolo_head = Detect(OBJ_CLASS_NUM, anchors, stride, MODEL_SIZE)
# 调整输出特征图的形状并转置(适配检测头输入格式)
validCount0 = output[2].reshape(*output_shapes[2]).transpose(0, 3, 1, 2)
validCount1 = output[1].reshape(*output_shapes[1]).transpose(0, 3, 1, 2)
validCount2 = output[0].reshape(*output_shapes[0]).transpose(0, 3, 1, 2)
# 处理特征图,生成检测框
pred = yolo_head([validCount0, validCount1, validCount2])
# 后处理(筛选、NMS等)
det_pred = detect_postprocess(
pred,
frame.shape, # 原始图像尺寸
[MODEL_SIZE, MODEL_SIZE, 3], # 模型输入尺寸
conf_thres=0.5, # 置信度阈值
iou_thres=0.45 # IOU阈值
)
# 处理异常值(NaN)
det_pred[np.isnan(det_pred)] = 0.0
# 将检测框坐标映射回原始图像尺寸(反缩放)
det_pred[:, :4] = det_pred[:, :4] * scale
# 绘制检测结果
res_img = draw_detect_res(frame, det_pred)
# 保存结果图像
save_path = "yolov5s_result.jpg"
cv2.imwrite(save_path, res_img)
print("图片保存在", save_path)
print("=======================================")
return True
def parser_args():
"""解析命令行参数"""
parser = argparse.ArgumentParser(description="Run model benchmarks")
parser.add_argument('--target_model', type=str,
default='yolov5_text/yolov5x/cutoff_yolov5x_qcs6490_w8a8.qnn231.ctx.bin',
help="inference model path") # 模型路径
parser.add_argument('--imgs', type=str, default='bus.jpg',
help="Predict images path") # 测试图像路径
parser.add_argument('--invoke_nums', type=str, default=100,
help="Inference nums") # 推理次数
parser.add_argument('--model_type', type=str, default='QNN',
help="run backend") # 模型框架(QNN/SNPE)
parser.add_argument('--size', type=str, default=640,
help="model input size") # 模型输入尺寸
args = parser.parse_args()
return args
if __name__ == "__main__":
main() # 执行主函数