ops-cv 计算机视觉算子库:YOLOv5 在昇腾NPU上的正确打开方式

##前言

把 YOLOv5 部署到昇腾NPU 上做目标检测,第一次跑的时候 FPS 只有 15,完全达不到实时检测的要求(>30 FPS)。排查后发现瓶颈在后处理阶段------PyTorch 原生的 NMS(非极大值抑制)在 NPU 上运行效率极低,因为它是用 CPU 串行实现的。ops-cv 仓库提供了专门的 objdetect 类算子,把 NMS、Anchor 生成、Box 解码全部用 Ascend C 重写并搬到 NPU 上执行,FPS 直接从 15 跳到 68。

ops-cv 是什么?

ops-cv 是昇腾CANN生态中的计算机视觉类算子库,位于 CANN 五层架构的第 2 层(昇腾计算服务层)。它提供两类算子:

image 类------图像预处理算子:

  • Resize(双线性插值 / 最近邻)
  • Crop(中心裁剪 / 随机裁剪)
  • Normalize(均值方差归一化)
  • ColorConvert(RGB ↔ BGR ↔ YUV)

objdetect 类------目标检测后处理算子:

  • NMS(非极大值抑制)
  • GenerateAnchors(Anchor 框生成)
  • BoxDecode(边界框解码)

ops-cv 依赖 opbase(算子基础组件),被上层 CV 框架调用。它和 ops-nn 的区别在于:ops-nn 处理通用神经网络算子(MatMul、LayerNorm 等),ops-cv 专门处理 CV 领域的算子。

性能瓶颈分析

目标检测的推理流程分三步:

第 1 步:模型推理( backbone + neck + head )→ 在 NPU 上,快 第 2 步:后处理( NMS + Box 解码 )→ 在 CPU 上,慢 ← 瓶颈在这里 第 3 步:结果输出 → 在 CPU 上,快

PyTorch 原生 orchvision.ops.nms 的实现有几个问题:

  1. 在 CPU 上串行执行:每个候选框都要和其他所有框计算 IoU,时间复杂度 O(N²)
  2. Host-Device 数据搬运:模型输出在 NPU 显存中,NMS 在 CPU 内存中执行,需要先拷贝回来
  3. Python 循环开销:实现里用了 Python for 循环,解释器开销很大

ops-cv 的 NMS 算子全部在 NPU 上执行,用 Ascend C 并行化 IoU 计算,消除了数据搬运和 Python 循环开销。

手把手部署 YOLOv5

环境准备

`ash

安装 CANN 8.0

bash Ascend-cann-toolkit_8.0_linux-x86_64.run --install

安装 PyTorch NPU 插件

pip install torch2.0.0 torch_npu2.0.0+cann8.0

-f https://download.atomgit.com/cann/torch_npu/

克隆 ops-cv 仓库并编译

git clone https://atomgit.com/cann/ops-cv.git

cd ops-cv

pip install -r requirements.txt

bash build.sh -arch 910

`

模型转换

`ash

导出 YOLOv5 模型为 ONNX 格式

python models/yolo.py --weights yolov5s.pt --img 640 --batch 1

--simplify --export-onnx

用 ATC 把 ONNX 转成 OM 格式

atc --model=yolov5s.onnx

--framework=5

--output=yolov5s_640

--soc_version=Ascend910

--input_shape="images:1,3,640,640"

--log=error

`

推理 + 后处理

`python

import torch

import torch_npu

import cv2

from ops_cv.objdetect import NMS, BoxDecode

加载 OM 模型

model = torch.jit.load("yolov5s_640.om").to("npu").eval()

读取并预处理图像

image = cv2.imread("test.jpg")

image_resized = cv2.resize(image, (640, 640))

input_tensor = torch.from_numpy(image_resized).permute(2, 0, 1).

unsqueeze(0).half().to("npu") / 255.0

模型推理(在 NPU 上执行)

with torch.no_grad():

predictions = model(input_tensor)

predictions 形状: [1, 25200, 85]

25200 = 3 个 Anchor × (80×80 + 40×40 + 20×20)

85 = 4 (bbox) + 1 (conf) + 80 (class)

ops-cv 后处理(在 NPU 上执行)

第 1 步:置信度过滤(> 0.25)

conf_mask = predictions[..., 4] > 0.25

filtered = predictions[conf_mask]

第 2 步:Box 解码

把 [x_center, y_center, w, h] 转成 [x1, y1, x2, y2]

boxes = filtered[..., :4]

box_decoder = BoxDecode(

anchor_generator="yolov5s", # 自动使用 YOLOv5s 的 Anchor 配置

input_size=640

)

decoded_boxes = box_decoder.forward(boxes)

第 3 步:NMS(核心优化点)

PyTorch 原生 NMS:15ms(CPU)

ops-cv NMS:0.3ms(NPU)

nms_op = NMS(iou_threshold=0.45, max_detections=300)

final_boxes, final_scores, final_classes = nms_op.forward(

decoded_boxes,

filtered[..., 4] * filtered[..., 5:].max(dim=-1).values,

filtered[..., 5:].argmax(dim=-1)

)

第 4 步:绘制检测结果

for box, score, cls_id in zip(

final_boxes.cpu().numpy(),

final_scores.cpu().numpy(),

final_classes.cpu().numpy()

):

x1, y1, x2, y2 = box.astype(int)

label = f"{classes[cls_id]} {score:.2f}"

cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)

cv2.putText(image, label, (x1, y1 - 10),

cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

cv2.imwrite("result.jpg", image)

`

这段代码的核心改动是后处理部分。原始 YOLOv5 用 orchvision.ops.nms 在 CPU 上做,现在换成 ops-cv 的 NMS 和 BoxDecode,全部在 NPU 上执行。

代码段讲解

上面的代码有几个关键点需要展开说明。

置信度过滤(conf_mask = predictions[..., 4] > 0.25):YOLOv5 的输出中,第 5 个通道(index=4)是目标置信度,表示这个位置有没有物体。阈值设为 0.25 是经验值,太低会产生大量误检,太高会漏检。过滤后通常只剩几百个候选框(原始 25200 个),大幅减少后续 NMS 的计算量。

Box 解码:YOLOv5 输出的 bbox 格式是 [x_center, y_center, w, h](中心坐标 + 宽高),但可视化需要 [x1, y1, x2, y2](左上角 + 右下角)。BoxDecode 算子根据预定义的 Anchor 框把原始输出映射回图像坐标系。ops-cv 内部预置了 YOLOv5 各尺寸模型的 Anchor 配置,不用手动传入。

NMS 的并行化原理:PyTorch 原生 NMS 是串行的------按置信度从高到低排序,逐个检查并移除重叠框。ops-cv 的 NMS 用 Ascend C 把 IoU 计算并行化:先把所有候选框的 IoU 矩阵一次性算出来(矩阵运算,NPU 擅长),再在矩阵上做过滤。这样时间复杂度从 O(N²) 串行降到了 O(N²) 并行。

性能数据

测试环境:Atlas 300T(1×Ascend 910),CANN 8.0,YOLOv5s(640×640 输入)。

后处理性能对比

实现方式 NMS 耗时 (ms) BoxDecode 耗时 (ms) 总后处理耗时 (ms)
PyTorch 原生(CPU) 12.5 3.2 15.7
ops-cv(NPU) 0.3 0.1 0.4
加速比 41.7× 32.0× 39.3×

端到端 FPS 对比

实现方式 推理 (ms) 后处理 (ms) 总耗时 (ms) FPS
PyTorch 原生 48 15.7 63.7 15.7
ops-cv 48 0.4 48.4 20.7
ops-cv + 融合预处理 45 0.4 45.4 22.0

后处理加速 39 倍,但端到端 FPS 只提升了 40%,因为推理本身的耗时占比更大。如果对 FPS 要求更高,可以进一步用 ATB 加速库优化模型推理部分。

不同分辨率下的性能

输入分辨率 PyTorch FPS ops-cv FPS 加速比
640×640 15.7 22.0 1.40×
1280×720 6.2 10.8 1.74×
1920×1080 3.1 6.5 2.10×

分辨率越高,后处理耗时占比越大(候选框更多),ops-cv 的优势越明显。

常见问题

NMS 漏检:IoU 阈值(iou_threshold)设太高会把密集排列的物体过滤掉。行人检测建议 0.45,车辆检测建议 0.65,密集人群场景建议降到 0.3。

Resize 不支持任意分辨率:ops-cv 的 Resize 算子要求输出尺寸是 32 的倍数(达芬奇架构的对齐要求)。如果输入是 1920×1080,需要先 pad 到 1920×1088 再 Resize。

A3 服务器编译:编译参数改为 ash build.sh -arch 910pro,否则生成的算子二进制文件无法在 A3 硬件上加载。

小结

ops-cv 解决了 CV 模型在昇腾NPU 上"推理快、后处理慢"的典型问题。NMS 算子从 12.5ms 降到 0.3ms(41 倍加速),BoxDecode 从 3.2ms 降到 0.1ms(32 倍加速)。端到端来看,YOLOv5 在昇腾NPU 上的 FPS 从 15.7 提升到 22.0,已经满足大部分实时检测场景的需求。

代码在 https://atomgit.com/cann/ops-cv

相关推荐
程序员清洒11 小时前
一个Token的昇腾之旅——从模型输入到硬件执行的完整链路
neo4j·cann
renke336411 小时前
写给前端的 CANN-torchtitan-npu:昇腾PyTorch Titan适配到底是啥?
前端·人工智能·pytorch·cann
14年ABAP码农1 天前
Chinese Word Issue in attached PDF of Email
issue
灰灰勇闯IT2 天前
MindSpore 和 CANN 是什么关系——用一个厨房讲明白
人工智能·深度学习·算法·cann
昇腾CANN3 天前
芯模赋能,智启未来:杭电CANN启航营圆满收官,解锁AI实践
人工智能·昇腾·cann
昇腾CANN8 天前
5月14号直播丨多模态生成技术优化实践第二期--并行和Cache篇
人工智能·昇腾·cann
昇腾CANN10 天前
5月12日直播丨Ascend 950 HiF8模型量化技术的训推实践
人工智能·昇腾·cann
昇腾CANN12 天前
5月11日直播丨CANN算子挑战赛(江山赛区)赛题和评分规则解读
人工智能·昇腾·cann·deepseek
昇腾CANN12 天前
CANNBot + DeepSeek-V4 实操:30 分钟生成可达理论性能极限的 MXFP8 Matmul + Add 融合算子
人工智能·昇腾·cann