请添加图片描述
个人主页:ujainu

前言

你拍了一张照片,想让 AI 找出里面的人脸和车牌——这个过程中,缩放图片、筛选候选框、裁剪感兴趣区域,全靠视觉算子撑着。昇腾 CANN 里的 ops-cv 仓库,就是专门给昇腾NPU准备的计算机视觉算子集合:Resize、NMS、ROIAlign、PSROIPooling 这些检测和图像处理的核心算子,都住在这里。

打个比方,ops-cv 就像照相馆的后期处理车间——底片进来,裁切、缩放、筛选、精修,每个步骤都有专门的师傅负责,最后才出成片。

为什么视觉推理需要专门的算子仓库

一个目标检测模型从接收图片到输出结果,做的事情大致是:输入一张图(比如 1080p),先缩放到模型期望的尺寸,再通过卷积网络提取特征,最后从特征里框出目标位置。听起来很顺畅,但里面藏着两个容易被忽视的痛点。

第一,视觉前处理的计算量远超直觉。 一张 1920×1080 的图片做双线性插值缩放到 640×640,要对 69 万个输出像素、每个像素在原图上找最近的 2×2 邻域做加权平均——这在 CPU 上要几毫秒,但检测模型一秒可能要处理几十帧,光 Resize 就能吃掉不少时间预算。

第二,后处理不是"小尾巴",而是延迟大头。 YOLO 系列输出上万个候选框,NMS 要逐一计算两两之间的 IoU(交并比),复杂度接近 O(n²)。候选框多的时候,NMS 耗时甚至能超过 backbone 的推理时间。把这些操作放在 NPU 上而不是 CPU 上,端到端延迟才能压下来。

ops-cv 仓库存放的就是这类"前后夹击"的算子。把它们做成 Ascend C 原生实现,直接跑在昇腾 NPU 上,图像数据从输入到输出全程不下芯片。

先搞懂:ops-cv 到底管哪些事

ops-cv 里的算子分成两大类:image 类objdetect 类

image 类处理像素级操作——把 4K 图缩到 256×256、做双线性插值、调整图像格式,归它管。

objdetect 类更偏检测流程——NMS 把重叠的候选框砍掉、ROIAlign 把不规则区域裁成固定大小的特征图、PSROIPooling 做位置敏感的池化。YOLO、Faster R-CNN、Mask R-CNN 这些模型推理的时候都绕不开这几个算子。

来看几个递进的例子,从最简单到最常用:

1️⃣ Resize:最基础的图像缩放

Resize 的原理不复杂:目标图上每个像素,映射回原图找到对应位置,然后从原图该位置附近采样。用哪种插值策略(最近邻、双线性、双三次),决定了图像质量和计算量的权衡。

import torch_npu  # 昇腾 NPU 后端

x = torch.randn(1, 3, 1080, 1920).npu()  # 4K 输入直接放 NPU
out = torch.nn.functional.interpolate(
    x, size=(256, 256), mode='bilinear'
)
# 4K 缩到 256×256,NPU 上这步是微秒级

也可以只指定缩放系数而不是固定尺寸:

out = torch.nn.functional.interpolate(
    x, scale_factor=0.5, mode='nearest'
)
# nearest 插值更快但锯齿明显,适合对精度不敏感的场景

2️⃣ NMS:检测模型的后处理关键步

NMS 解决的问题是:检测网络会在同一个物体周围生成大量重叠框,需要筛掉冗余的,只保留置信度最高的那个。

它的逻辑是:先把所有框按置信度排序,取最高分的框保留下来,然后把跟它 IoU(交并比)超过阈值的框全部删除;接着对剩下的框重复这个过程,直到没有框剩下来。

from torchvision.ops import nms

boxes = torch.tensor([[10,10,50,50],[12,12,48,48],[200,200,250,250]]).npu()
scores = torch.tensor([0.9, 0.7, 0.8]).npu()

keep = nms(boxes, scores, iou_threshold=0.5)
# 重叠框砍掉,只留最靠谱的——NMS 延迟直接关系端到端速度

实际部署中候选框数量大,NMS 的 O(n²) 比较会变成长板。ops-cv 对此做了专门的并行优化——NPU 上多核同时做 IoU 比较,延迟比纯 CPU 实现低一个量级。

3️⃣ ROIAlign:检测头里的精密切割

Faster R-CNN 和 Mask R-CNN 里,RPN(区域提议网络)输出的候选框是浮点数坐标,直接在特征图上取整会产生量化误差——差半个像素的偏移,mAP 可能掉好几个点。ROIAlign 的做法是:不做取整,而是在浮点坐标上做双线性插值采样。

from torchvision.ops import roi_align

feat = torch.randn(1, 256, 64, 64).npu()  # backbone 输出的特征图
rois = torch.tensor([[0, 10, 10, 50, 50]]).float().npu()

out = roi_align(feat, rois, output_size=(7, 7))
# 不管候选框多大,都切成整整齐齐的 7×7
# 精度敏感——差一个像素的插值都可能影响检测准确率

ROIAlign 的输出是固定尺寸(比如 7×7),这样后续的分类头和回归头就不用处理变长输入,全连接层的权重尺寸也固定下来了。

4️⃣ PSROIPooling:位置敏感的 ROI 池化

PSROIPooling 是 R-FCN 网络的核心组件。跟 ROIAlign 的区别在于:它不是对整张特征图做池化,而是把特征图按空间位置分成若干"组"(比如 k×k),每个组只编码一个位置的类别信息。池化时,只从对应位置的组里取值。

# PSROIPooling 伪代码示意
# score_maps shape: (batch, k*k*(C+1), H, W)
# k 是空间分组数,C 是类别数
group_size = 7  # 把每个 ROI 区域切成 7×7
output = psroi_pooling(score_maps, rois, group_size)
# 输出 shape: (num_rois, C+1, 7, 7)

这个设计让 R-FCN 的主干网络几乎全是卷积,没有全连接层,推理速度更快——代价是特征图通道数变大了(要乘以 k×k)。

5️⃣ 其他常用算子

ops-cv 里还有一些在特定场景中不可或缺的算子:

  • BboxEncode / BboxDecode:候选框和真实框之间的编码与解码,目标检测训练和推理都用到。
  • GridSample:更灵活的空间变换,光流估计、图像配准等任务中用到。
  • AffineGrid:生成仿射变换的采样网格,跟 GridSample 配合使用。
# GridSample + AffineGrid 的典型用法:图像仿射变换
from torchvision.transforms.functional import affine

transformed = affine(img, angle=30, translate=(10, 0), scale=1.0, shear=0)
# 旋转 30 度 + 平移 10 像素,底层就是 AffineGrid 生成网格 + GridSample 采样

这几个算子串起来就是视觉推理的主线:缩图 → 检测 → 精修。ops-cv 的价值在于,这些算子全在昇腾 NPU 上跑,数据不用在 CPU 和 NPU 之间来回倒腾。

它在 CANN 五层架构里待在哪

ops-cv 跟 ops-nn、ops-math 一样,住在第 2 层昇腾计算服务层的 AOL 算子库里。这一层就是给上层框架提供"弹药"的——PyTorch 里调一个视觉算子,底层就是 ops-cv 在 NPU 上执行。

AOL 算子库内部按算子域分仓管理:

AOL 算子库
├── ops-math        # 基础数学算子:Add、Mul、Reduce 等
├── ops-nn          # 神经网络算子:Conv2D、MatMul、LayerNorm 等
├── ops-cv          # 计算机视觉算子:Resize、NMS、ROIAlign 等
└── ops-transformer # Transformer 算子:FlashAttention、RMSNorm 等

每个仓库的算子实现都遵循同样的开发范式——用 Ascend C 写 kernel,通过 opbase 提供的公共框架注册到昇腾算子库,上层框架(PyTorch、MindSpore)通过算子适配层调用。

如图:

CANN ops-cv:计算机视觉算子仓库结构导读

你拍了一张照片,想让 AI 找出里面的人脸和车牌——这个过程中,缩放图片、筛选候选框、裁剪感兴趣区域,全靠视觉算子撑着。昇腾 CANN 里的 ops-cv 仓库,就是专门给昇腾NPU准备的计算机视觉算子集合:Resize、NMS、ROIAlign、PSROIPooling 这些检测和图像处理的核心算子,都住在这里。

打个比方,ops-cv 就像照相馆的后期处理车间——底片进来,裁切、缩放、筛选、精修,每个步骤都有专门的师傅负责,最后才出成片。

为什么视觉推理需要专门的算子仓库

一个目标检测模型从接收图片到输出结果,做的事情大致是:输入一张图(比如 1080p),先缩放到模型期望的尺寸,再通过卷积网络提取特征,最后从特征里框出目标位置。听起来很顺畅,但里面藏着两个容易被忽视的痛点。

第一,视觉前处理的计算量远超直觉。 一张 1920×1080 的图片做双线性插值缩放到 640×640,要对 69 万个输出像素、每个像素在原图上找最近的 2×2 邻域做加权平均——这在 CPU 上要几毫秒,但检测模型一秒可能要处理几十帧,光 Resize 就能吃掉不少时间预算。

第二,后处理不是"小尾巴",而是延迟大头。 YOLO 系列输出上万个候选框,NMS 要逐一计算两两之间的 IoU(交并比),复杂度接近 O(n²)。候选框多的时候,NMS 耗时甚至能超过 backbone 的推理时间。把这些操作放在 NPU 上而不是 CPU 上,端到端延迟才能压下来。

ops-cv 仓库存放的就是这类"前后夹击"的算子。把它们做成 Ascend C 原生实现,直接跑在昇腾 NPU 上,图像数据从输入到输出全程不下芯片。

先搞懂:ops-cv 到底管哪些事

ops-cv 里的算子分成两大类:image 类objdetect 类

image 类处理像素级操作——把 4K 图缩到 256×256、做双线性插值、调整图像格式,归它管。

objdetect 类更偏检测流程——NMS 把重叠的候选框砍掉、ROIAlign 把不规则区域裁成固定大小的特征图、PSROIPooling 做位置敏感的池化。YOLO、Faster R-CNN、Mask R-CNN 这些模型推理的时候都绕不开这几个算子。

来看几个递进的例子,从最简单到最常用:

1️⃣ Resize:最基础的图像缩放

Resize 的原理不复杂:目标图上每个像素,映射回原图找到对应位置,然后从原图该位置附近采样。用哪种插值策略(最近邻、双线性、双三次),决定了图像质量和计算量的权衡。

import torch_npu  # 昇腾 NPU 后端

x = torch.randn(1, 3, 1080, 1920).npu()  # 4K 输入直接放 NPU
out = torch.nn.functional.interpolate(
    x, size=(256, 256), mode='bilinear'
)
# 4K 缩到 256×256,NPU 上这步是微秒级

也可以只指定缩放系数而不是固定尺寸:

out = torch.nn.functional.interpolate(
    x, scale_factor=0.5, mode='nearest'
)
# nearest 插值更快但锯齿明显,适合对精度不敏感的场景

2️⃣ NMS:检测模型的后处理关键步

NMS 解决的问题是:检测网络会在同一个物体周围生成大量重叠框,需要筛掉冗余的,只保留置信度最高的那个。

它的逻辑是:先把所有框按置信度排序,取最高分的框保留下来,然后把跟它 IoU(交并比)超过阈值的框全部删除;接着对剩下的框重复这个过程,直到没有框剩下来。

from torchvision.ops import nms

boxes = torch.tensor([[10,10,50,50],[12,12,48,48],[200,200,250,250]]).npu()
scores = torch.tensor([0.9, 0.7, 0.8]).npu()

keep = nms(boxes, scores, iou_threshold=0.5)
# 重叠框砍掉,只留最靠谱的——NMS 延迟直接关系端到端速度

实际部署中候选框数量大,NMS 的 O(n²) 比较会变成长板。ops-cv 对此做了专门的并行优化——NPU 上多核同时做 IoU 比较,延迟比纯 CPU 实现低一个量级。

3️⃣ ROIAlign:检测头里的精密切割

Faster R-CNN 和 Mask R-CNN 里,RPN(区域提议网络)输出的候选框是浮点数坐标,直接在特征图上取整会产生量化误差——差半个像素的偏移,mAP 可能掉好几个点。ROIAlign 的做法是:不做取整,而是在浮点坐标上做双线性插值采样。

from torchvision.ops import roi_align

feat = torch.randn(1, 256, 64, 64).npu()  # backbone 输出的特征图
rois = torch.tensor([[0, 10, 10, 50, 50]]).float().npu()

out = roi_align(feat, rois, output_size=(7, 7))
# 不管候选框多大,都切成整整齐齐的 7×7
# 精度敏感——差一个像素的插值都可能影响检测准确率

ROIAlign 的输出是固定尺寸(比如 7×7),这样后续的分类头和回归头就不用处理变长输入,全连接层的权重尺寸也固定下来了。

4️⃣ PSROIPooling:位置敏感的 ROI 池化

PSROIPooling 是 R-FCN 网络的核心组件。跟 ROIAlign 的区别在于:它不是对整张特征图做池化,而是把特征图按空间位置分成若干"组"(比如 k×k),每个组只编码一个位置的类别信息。池化时,只从对应位置的组里取值。

# PSROIPooling 伪代码示意
# score_maps shape: (batch, k*k*(C+1), H, W)
# k 是空间分组数,C 是类别数
group_size = 7  # 把每个 ROI 区域切成 7×7
output = psroi_pooling(score_maps, rois, group_size)
# 输出 shape: (num_rois, C+1, 7, 7)

这个设计让 R-FCN 的主干网络几乎全是卷积,没有全连接层,推理速度更快——代价是特征图通道数变大了(要乘以 k×k)。

5️⃣ 其他常用算子

ops-cv 里还有一些在特定场景中不可或缺的算子:

  • BboxEncode / BboxDecode:候选框和真实框之间的编码与解码,目标检测训练和推理都用到。
  • GridSample:更灵活的空间变换,光流估计、图像配准等任务中用到。
  • AffineGrid:生成仿射变换的采样网格,跟 GridSample 配合使用。
# GridSample + AffineGrid 的典型用法:图像仿射变换
from torchvision.transforms.functional import affine

transformed = affine(img, angle=30, translate=(10, 0), scale=1.0, shear=0)
# 旋转 30 度 + 平移 10 像素,底层就是 AffineGrid 生成网格 + GridSample 采样

这几个算子串起来就是视觉推理的主线:缩图 → 检测 → 精修。ops-cv 的价值在于,这些算子全在昇腾 NPU 上跑,数据不用在 CPU 和 NPU 之间来回倒腾。

它在 CANN 五层架构里待在哪

ops-cv 跟 ops-nn、ops-math 一样,住在第 2 层昇腾计算服务层的 AOL 算子库里。这一层就是给上层框架提供"弹药"的——PyTorch 里调一个视觉算子,底层就是 ops-cv 在 NPU 上执行。

AOL 算子库内部按算子域分仓管理:

AOL 算子库
├── ops-math        # 基础数学算子:Add、Mul、Reduce 等
├── ops-nn          # 神经网络算子:Conv2D、MatMul、LayerNorm 等
├── ops-cv          # 计算机视觉算子:Resize、NMS、ROIAlign 等
└── ops-transformer # Transformer 算子:FlashAttention、RMSNorm 等

每个仓库的算子实现都遵循同样的开发范式——用 Ascend C 写 kernel,通过 opbase 提供的公共框架注册到昇腾算子库,上层框架(PyTorch、MindSpore)通过算子适配层调用。

图1:CANN 五层架构定位,ops-cv 高亮于第2层 AOL 算子库

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上游靠谁,下游谁在用

ops-cv 不是孤军奋战:

上游:opbase 是所有算子仓库的公共底座——公共头文件、数据结构、调度框架全从这儿来,没它 ops-cv 编译都过不了。

# ops-cv 的 CMakeLists.txt 里会引用 opbase
find_package(opbase REQUIRED)
target_link_libraries(ops_cv PRIVATE opbase::opbase)

ascend-boost-comm 是算子公共平台,做 M×N 的算子复用,ops-cv 也靠它对接上层框架。比如同一个 Resize 算子,PyTorch 和 MindSpore 各有自己的调用约定,ascend-boost-comm 负责抹平这种差异。

下游:cann-recipes-infer 直接受益。视觉模型推理配方(比如 Faster R-CNN 的端到端部署),底层调的就是 ops-cv 里的 Resize + NMS + ROIAlign 组合。

# cann-recipes-infer 中的 CV 推理样例结构
cann-recipes-infer/
└── modelzoo/
    └── faster_rcnn/
        ├── README.md
        ├── infer.py          # 推理入口
        └── aipp.cfg          # 图像预处理配置(Resize 等在此配置)

同层兄弟:ops-nn 管神经网络算子(Conv2D、MatMul),ops-transformer 管大模型算子(FlashAttention),ops-cv 管视觉算子——三个仓库各管一摊,合起来覆盖主流 AI 工作负载。

图2:ops-cv 依赖关系图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

跟 ops-nn 怎么分工

一个容易搞混的点:ops-nn 有 Conv2D,ops-cv 有 Resize,这俩仓库怎么分工?

简单说,ops-nn 管学习——卷积、归一化、激活函数,模型里反复迭代的计算。ops-cv 管感知——图像缩放、目标筛选、区域裁切,模型"看懂"图像的前处理和后处理。

# 典型视觉推理流水线
image → Resize(ops-cv) → Conv2D(ops-nn)... → NMS(ops-cv) → 结果
        ^^^^^^^^^^                    ^^^^^^      ^^^^^^^^^^
        前处理                        特征提取     后处理

再看一个完整的 Faster R-CNN 推理流程,标注每个算子属于哪个仓库:

# Faster R-CNN 推理流水线——算子归属一览
image = read_image("test.jpg")
image = resize(image, (800, 800))            # ops-cv
feat  = backbone(image)                     # ops-nn(多层 Conv2D + FPN)
props = rpn_head(feat)                      # ops-nn(3×3 Conv + 分类/回归头)
props = nms(props.boxes, props.scores, 0.5)  # ops-cv
roi_feat = roi_align(feat, props.boxes)       # ops-cv
cls, reg = detection_head(roi_feat)          # ops-nn
result  = nms(cls, reg, 0.5)                 # ops-cv(最终去重)

可以看到,ops-cv 的算子出现在流水线的头和尾,ops-nn 的算子集中在中间的特征计算部分。这个分工不是随便定的——前处理和后处理算子的计算模式跟卷积差别很大,更多是坐标变换和插值,而不是大规模矩阵乘。分开仓库,代码组织和优化策略都能更聚焦。

想跑起来?环境准备

# 确认 NPU 在位
npu-smi info

# 拉代码
git clone https://atomgit.com/cann/ops-cv.git
cd ops-cv

# 编译(通用模式)
bash build.sh
# 跑视觉推理配方,看 ops-cv 算子的实际效果
git clone https://atomgit.com/cann/cann-recipes-infer.git
# 找到 CV 相关的推理样例,按 README 走就行

编译完成后,算子库会安装到昇腾 CANN 的算子目录下,PyTorch NPU 后端在首次调用对应算子时会自动加载。

写在最后

ops-cv 仓库不大,但它管的算子是视觉推理绕不开的。Resize 是每张图进模型的第一步,NMS 是每个检测模型出结果的最后一步,ROIAlign 是精度敏感的中间环节。三个算子串起来,就是一条完整的视觉推理管线。

想自己跑跑看?去 https://atomgit.com/cann/ops-cv 拉代码,照着 cann-recipes-infer 里的视觉模型配方走一遍,对这些算子的配合会有更直接的感受。

对了,opbase 的公共基础设施值得单独看看——所有算子仓库的调度和编译都靠它,理解了 opbase,ops-cv 的内部结构也就清楚了。

上游靠谁,下游谁在用

ops-cv 不是孤军奋战:

上游:opbase 是所有算子仓库的公共底座——公共头文件、数据结构、调度框架全从这儿来,没它 ops-cv 编译都过不了。

# ops-cv 的 CMakeLists.txt 里会引用 opbase
find_package(opbase REQUIRED)
target_link_libraries(ops_cv PRIVATE opbase::opbase)

ascend-boost-comm 是算子公共平台,做 M×N 的算子复用,ops-cv 也靠它对接上层框架。比如同一个 Resize 算子,PyTorch 和 MindSpore 各有自己的调用约定,ascend-boost-comm 负责抹平这种差异。

下游:cann-recipes-infer 直接受益。视觉模型推理配方(比如 Faster R-CNN 的端到端部署),底层调的就是 ops-cv 里的 Resize + NMS + ROIAlign 组合。

# cann-recipes-infer 中的 CV 推理样例结构
cann-recipes-infer/
└── modelzoo/
    └── faster_rcnn/
        ├── README.md
        ├── infer.py          # 推理入口
        └── aipp.cfg          # 图像预处理配置(Resize 等在此配置)

同层兄弟:ops-nn 管神经网络算子(Conv2D、MatMul),ops-transformer 管大模型算子(FlashAttention),ops-cv 管视觉算子——三个仓库各管一摊,合起来覆盖主流 AI 工作负载。

跟 ops-nn 怎么分工

一个容易搞混的点:ops-nn 有 Conv2D,ops-cv 有 Resize,这俩仓库怎么分工?

简单说,ops-nn 管学习——卷积、归一化、激活函数,模型里反复迭代的计算。ops-cv 管感知——图像缩放、目标筛选、区域裁切,模型"看懂"图像的前处理和后处理。

# 典型视觉推理流水线
image → Resize(ops-cv) → Conv2D(ops-nn)... → NMS(ops-cv) → 结果
        ^^^^^^^^^^                    ^^^^^^      ^^^^^^^^^^
        前处理                        特征提取     后处理

再看一个完整的 Faster R-CNN 推理流程,标注每个算子属于哪个仓库:

# Faster R-CNN 推理流水线——算子归属一览
image = read_image("test.jpg")
image = resize(image, (800, 800))            # ops-cv
feat  = backbone(image)                     # ops-nn(多层 Conv2D + FPN)
props = rpn_head(feat)                      # ops-nn(3×3 Conv + 分类/回归头)
props = nms(props.boxes, props.scores, 0.5)  # ops-cv
roi_feat = roi_align(feat, props.boxes)       # ops-cv
cls, reg = detection_head(roi_feat)          # ops-nn
result  = nms(cls, reg, 0.5)                 # ops-cv(最终去重)

可以看到,ops-cv 的算子出现在流水线的头和尾,ops-nn 的算子集中在中间的特征计算部分。这个分工不是随便定的——前处理和后处理算子的计算模式跟卷积差别很大,更多是坐标变换和插值,而不是大规模矩阵乘。分开仓库,代码组织和优化策略都能更聚焦。

想跑起来?环境准备

# 确认 NPU 在位
npu-smi info

# 拉代码
git clone https://atomgit.com/cann/ops-cv.git
cd ops-cv

# 编译(通用模式)
bash build.sh
# 跑视觉推理配方,看 ops-cv 算子的实际效果
git clone https://atomgit.com/cann/cann-recipes-infer.git
# 找到 CV 相关的推理样例,按 README 走就行

编译完成后,算子库会安装到昇腾 CANN 的算子目录下,PyTorch NPU 后端在首次调用对应算子时会自动加载。

写在最后

ops-cv 仓库不大,但它管的算子是视觉推理绕不开的。Resize 是每张图进模型的第一步,NMS 是每个检测模型出结果的最后一步,ROIAlign 是精度敏感的中间环节。三个算子串起来,就是一条完整的视觉推理管线。

想自己跑跑看?去 https://atomgit.com/cann/ops-cv 拉代码,照着 cann-recipes-infer 里的视觉模型配方走一遍,对这些算子的配合会有更直接的感受。

对了,opbase 的公共基础设施值得单独看看——所有算子仓库的调度和编译都靠它,理解了 opbase,ops-cv 的内部结构也就清楚了。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐