推理管线上,模型本身只消耗不到一半的时间。剩下的时间——图像解码、缩放、归一化、后处理——全部在 ops-cv 的射程之内。

ops-cv 是 CANN 计算机视觉算子库,覆盖图片预处理(Resize、Crop、Normalize、Pad)、检测后处理(NMS、ROI Align、AnchorGenerate)和传统视觉操作(WarpAffine、Histogram)。和 DVPP(数字视觉预处理引擎)的关系是:DVPP 做硬件加速的 JPEG 解码和基础裁剪,ops-cv 做需要运算的像素级操作。

算子品类

图像预处理

算子 功能 加速路径
Resize 缩放至指定尺寸 Vector 单元(双线性插值)
Crop 中心/随机裁剪 零拷贝 strided copy
Normalize 减均值除方差 Vector 单元逐元素操作
Pad 边缘填充 Vector 单元
ColorConvert 色域转换 (RGB-BGR) Vector 单元
CenterCrop 固定中心裁剪 零拷贝

目标检测后处理

算子 功能 加速路径
NMS 非极大值抑制去重 Vector 单元排序+过滤
ROIAlign 区域特征对齐 Cube 单元双线性插值
AnchorGenerate 锚框生成 Vector 单元
BBoxDecode 边界框解码 Vector 单元

Resize 的双线性插值

图像缩放在 Ascend NPU 上走 Vector 单元的双线性插值。内核在 L1 缓存里维护一个 4x4 的像素块,按缩放比例做加权插值:

// ops-cv/resize/bilinear_resize_kernel.cpp
__aicore__ void bilinear_resize_kernel(
    GlobalTensor<uint8_t>& output,
    GlobalTensor<uint8_t>& input,
    float scale_h, float scale_w,
    int32_t out_h, int32_t out_w,
    int32_t in_h, int32_t in_w
) {
    for (int y = 0; y < out_h; y++) {
        float src_y = (y + 0.5f) / scale_h - 0.5f;
        int y0 = (int)floor(src_y);
        int y1 = (y0 + 1 < in_h) ? y0 + 1 : in_h - 1;
        float wy = src_y - y0;

        for (int x = 0; x < out_w; x += 256) {
            float src_x = (x + 0.5f) / scale_w - 0.5f;
            int x0 = (int)floor(src_x);
            int x1 = (x0 + 1 < in_w) ? x0 + 1 : in_w - 1;
            float wx = src_x - x0;

            // 2x2 双线性插值
            float tl = input[y0 * in_w + x0];
            float tr = input[y0 * in_w + x1];
            float bl = input[y1 * in_w + x0];
            float br = input[y1 * in_w + x1];

            output[y * out_w + x] = (uint8_t)(
                tl * (1 - wx) * (1 - wy) +
                tr * wx * (1 - wy) +
                bl * (1 - wx) * wy +
                br * wx * wy
            );
        }
    }
}

性能拐点:输出尺寸小于输入的 1/4 时,Vector 单元每轮 256 个像素大量命中同一输入区间,缓存命中率高。输出接近输入时,内存访问退化到跳变模式,性能下降 30-40%。

预处理管道实战

完整的 CV 推理预处理管道,从原始图片到模型输入:

import torch
import torch_npu

def preprocess_pipeline(image_npu):
    # 1. Resize 到模型输入尺寸
    resized = torch_npu.ops.ops_cv.resize(
        image_npu, (224, 224),
        interpolation='bilinear'
    )
    # 2. 归一化(ImageNet 均值/方差)
    normalized = torch_npu.ops.ops_cv.normalize(
        resized,
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225]
    )
    # 3. 维度调整:HWC -> CHW
    tensor = normalized.permute(2, 0, 1)
    return tensor

管道内部:DVPP 解码(HBM)-> ops-cv Resize(同一块 HBM,零拷贝)-> ops-cv Normalize -> 模型输入。DVPP 和 ops-cv 之间不经过 CPU。

NMS 的排序加速

非极大值抑制(NMS)是目标检测删除重复框的核心。传统实现:按置信度排序 -> 选最高分框 -> 删除 IOU 过高的框 -> 循环。ops-cv 在 NPU 上走 Vector 单元的并行 bitonic sort,10,000 个检测框从 CPU 的 2ms 压到 0.1ms。

import torch_npu

boxes = torch.randn(1024, 4).npu()   # [x1, y1, x2, y2]
scores = torch.rand(1024).npu()

keep_indices = torch_npu.ops.ops_cv.nms(
    boxes, scores,
    iou_threshold=0.5,
    score_threshold=0.3,
    max_output_size=100
)
final_boxes = boxes[keep_indices]
final_scores = scores[keep_indices]

踩坑一:通道顺序的四重标准

预处理管道四个组件各有默认通道格式:

组件 默认通道 说明
DVPP 解码 BGR 和 OpenCV 一致
ops-cv Normalize RGB 输入假定为 RGB
PyTorch 模型 RGB ImageNet 训出来的
OpenCV imread BGR 默认读取格式

管道里漏一步 BGR->RGB,模型认识的红色变成蓝色,分类结果完全对不上。

错误写法:OpenCV BGR 直接送 RGB 假设的 Normalize。

import cv2, torch_npu

# 错误:BGR 直接用 RGB 均值标准化
image_bgr = cv2.imread("apple.jpg")
image_npu = torch.from_numpy(image_bgr).npu()
normalized = torch_npu.ops.ops_cv.normalize(
    image_npu,                          # BGR 格式
    mean=[0.485, 0.456, 0.406],         # ImageNet RGB 均值
    std=[0.229, 0.224, 0.225]
)
# 结果:红色通道的值被蓝色通道的均值和方差标准化,输出全错

正确写法:显式色域转换。

import cv2, torch_npu, torch
from torchvision import transforms

image_bgr = cv2.imread("apple.jpg")
image_npu = torch.from_numpy(image_bgr).npu()

# 显式 BGR -> RGB
image_rgb = torch_npu.ops.ops_cv.color_convert(
    image_npu, cv2.COLOR_BGR2RGB
)

normalized = torch_npu.ops.ops_cv.normalize(
    image_rgb,
    mean=[0.485, 0.456, 0.406],
    std=[0.229, 0.224, 0.225]
)

# 验证:和 torchvision 对标
ref_img = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
ref = transforms.Normalize(
    mean=[0.485, 0.456, 0.406],
    std=[0.229, 0.224, 0.225]
)(transforms.ToTensor()(ref_img))

assert torch.allclose(
    normalized.cpu().permute(1, 2, 0), ref.permute(1, 2, 0), atol=1e-5
)

踩坑二:NMS 坐标格式不一致

YOLO 用中心格式 [x_center, y_center, width, height],Faster R-CNN 用顶点格式 [x1, y1, x2, y2]。ops-cv 的 NMS 默认用中心格式,传了顶点格式进去 IOU 计算结果偏移。

错误写法

# 错误:Faster R-CNN 输出是 [x1, y1, x2, y2]
boxes_xyxy = model_output['boxes'].npu()
keep = torch_npu.ops.ops_cv.nms(boxes_xyxy, scores, 0.5)
# IOU 值和预期不同,过滤掉的框不对

正确写法:先转换坐标格式。

# 正确:x1,y1,x2,y2 -> cx,cy,w,h
def xyxy_to_cxcywh(boxes):
    cx = (boxes[:, 0] + boxes[:, 2]) / 2.0
    cy = (boxes[:, 1] + boxes[:, 3]) / 2.0
    w = boxes[:, 2] - boxes[:, 0]
    h = boxes[:, 3] - boxes[:, 1]
    return torch.stack([cx, cy, w, h], dim=1)

boxes_cxcywh = xyxy_to_cxcywh(boxes_xyxy.npu())
keep = torch_npu.ops.ops_cv.nms(boxes_cxcywh, scores.npu(), 0.5)
final_boxes = boxes_xyxy[keep]  # 用原始顶点格式输出

踩坑三:Resize 的 align_corners

双线性插值有两种 API 约定:align_corners=True 和 align_corners=False。区别在像素中心映射方式——偏差在半个像素,累积到边缘产生 1-2 像素偏移。分割模型对此极其敏感。

错误写法

# 错误:分割 mask 上采样时没对齐
mask_256 = model(image).npu()
mask_1024 = torch_npu.ops.ops_cv.resize(
    mask_256, (1024, 1024),
    interpolation='bilinear'      # 默认 align_corners=False
)
# 分割结果偏移 1-2 像素,边界处误分割

正确写法

# 正确:分割上采样强制 align_corners=True
mask_1024 = torch_npu.ops.ops_cv.resize(
    mask_256, (1024, 1024),
    interpolation='bilinear',
    align_corners=True
)

# 验证:和 torch.nn.functional.interpolate 对标
import torch.nn.functional as F
ref_mask = F.interpolate(
    mask_256.cpu().unsqueeze(0).unsqueeze(0).float(),
    size=(1024, 1024),
    mode='bilinear',
    align_corners=True
).squeeze()
assert torch.allclose(mask_1024.cpu().float(), ref_mask, atol=1e-3)

流水线性能

Atlas 300I 推理卡上,单张图片全预处理管道:

阶段 耗时 硬件
JPEG 解码 0.8 ms DVPP
Resize 1920->224 0.3 ms Vector 单元
Normalize 0.05 ms Vector 单元
BGR->RGB 0.02 ms Vector 单元
总计 1.2 ms

CPU(OpenCV)同等操作 5-8ms,预处理加速 4-7 倍。


ops-cv 的角色是推理管道里的胶水层——把图片从文件格式变成模型能吃的张量。CV 预处理最大的复杂点不在单个算子的性能,而在管道组合的正确性:通道顺序、归一化参数、坐标格式、插值对齐。对齐了,1.2ms 从头跑到尾。没对齐,结果全错。

Logo

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

更多推荐