OpenCV昇腾原生支持

OpenCV (Open Source Computer Vision Library) 是一个开源计算机视觉和机器学习软件库,由Intel在1999年发布。OpenCV提供了丰富的图像和视频处理功能,广泛应用于各种计算机视觉任务,如面部识别、物体检测、运动跟踪、图像增强等。它支持多种编程语言(如C++、Python、Java等)和操作系统(如Windows、Linux、macOS等),并且可以与其他深度学习框架(如TensorFlow、PyTorch)无缝集成。

OpenCV-contrib 是一个附加模块的集合,为OpenCV核心库提供扩展功能。由于OpenCV核心库为了保持稳定性,通常只包含相对成熟和通用的模块,而OpenCV-contrib则提供了实验性和前沿的功能。这些模块包括一些新的特征检测算法、图像处理技术、深度学习工具、3D重建等。OpenCV-contrib项目中的模块也可能包含特定领域的工具包,例如面部识别、人脸标志检测、目标跟踪等。

在计算机视觉和深度学习任务中,硬件加速器(如GPU、TPU、NPU等)被广泛用于加速计算。OpenCV的性能也可以通过这些硬件加速器来提高,这通常通过使用专用的后端硬件加速库来实现。

1. 需求分析

华为Ascend系列AI处理器,凭借其强大的计算能力和高能效,在各种AI应用场景中得到了广泛应用。为了充分发挥Ascend硬件的优势,基于Ascend的OpenCV后端加速项目旨在利用Ascend的计算能力,加速OpenCV库中的部分核心算法。

1.1 目标

开发一个基于华为Ascend AI处理器的OpenCV硬件加速后端,优化并加速OpenCV中的特定算法。该后端将通过集成Ascend Computing Library (ACL) 来实现对OpenCV算法的硬件加速,从而提升计算性能,减少延迟,并提高能源效率。

1.2 需求概述

支持的算法类型

  • 识别出OpenCV中最常用的传统图像算法,并优先为这些算法实现Ascend后端加速支持。
  • 常见的候选算法包括算数运算,图像变换,色域转换等等。
  • 根据Ascend硬件特性,可能需要修改或重新设计部分算法,以适应硬件架构,进一步优化性能。

实现Ascend Runtime

  • 实现Ascend设备控制、Device-Host内存复制、流与事件管理等方面的功能。

异步算子支持

  • 接口调用使用异步任务提交,实现异步计算结果获取,提高设备利用率,系统的响应速度与吞吐量。
  • 支持ACL算子调用能力,以及AscendC自定义算子。
  • 支持OpenCV矩阵结构向ACL矩阵结构转换能力。

兼容性与集成

  • 确保该后端能够无缝集成到现有的OpenCV框架中,用户无需进行大量修改即可使用加速功能。
  • 保证与OpenCV其他后端的兼容性,用户可以根据具体硬件环境选择最优的加速方案。

性能评估与优化

  • 针对不同算法,设计性能评估测试用例,并基于测试结果持续优化算法与后端实现。
  • 与基线(CPU)性能进行对比,明确性能优势与改进方向。

文档与用户指南

  • 编写详细的开发文档与用户指南,帮助开发者理解如何使用该后端进行算法加速。
  • 提供API描述、使用示例、常见问题解答、性能优化建议等。

2. 架构设计

2.1 OpenCV项目架构

OpenCV项目架构

OpenCV内部模块较多,如上图所示,这是一个简化的OpenCV整体架构。按模块类型可以分为以下几类(含内部模块举例):

核心模块 (Core Modules)

  • 核心模块 (Core) 基础数据结构和算法(如矩阵运算、线性代数)。
  • 图像处理 (Imgproc) 图像滤波、形态学变换、边缘检测等。
  • 视频处理 (Video) 视频捕捉、帧处理、运动检测等。
  • 相机校正 (Calib3d) 相机标定、3D重建、立体匹配等。
  • 特征检测 (Features2d) 特征点检测与描述子计算(如SIFT、ORB)。

算法库 (Algorithm Libraries)

  • 机器学习 (ML)

    支持分类、回归、聚类、神经网络等机器学习算法。

  • 对象检测 (Objdetect)

    人脸检测、目标跟踪等高级检测算法。

  • 图像分割 (Imgsegm)

    超像素分割、图像聚类等。

硬件加速 (Hardware Acceleration)

  • CUDA 支持

    基于NVIDIA GPU的CUDA加速模块。

  • OpenCL 支持

    基于OpenCL的跨平台硬件加速支持。

  • Vulkan 支持

    基于Vulkan的图像处理加速。

第三方集成 (Third-Party Integrations)

  • Python 接口

    提供对Python的API绑定。

  • Java 接口

    提供对Java的API绑定。

  • Android/IOS 支持

    移动设备上的OpenCV应用开发支持。

应用层 (Application Layer)

  • 图像和视频处理应用
  • 增强现实 (AR)
  • 机器人视觉
  • 自动驾驶

2.2 OpenCV CANN硬件加速模块架构

OpenCV的昇腾原生支持将在硬件加速(Hardware Acceleration)中添加对Ascend NPU的支持。

OpenCV昇腾支持示意图

针对该项目的需求,需要实现以下模块:

Ascend Runtime

  • 设备控制:负责管理与Ascend NPU硬件的通信和控制。

  • 设备-主机内存复制:处理数据在Ascend NPU设备和主机之间的内存传输。

  • 流管理:管理计算任务流的调度和执行。

  • 事件管理:处理计算过程中的事件和同步问题。

AscendC内核

  • AscendC构建框架:提供内核构建的工具和框架。

  • 内核实现:实现具体的计算内核,提供加速计算功能。

  • 内核调用管理:管理内核的调用过程和参数。

  • 内核结果获取:从内核执行中获取计算结果。

ACL算子

  • OpenCV到ACL结构转换:将OpenCV数据结构转换为ACL支持的格式。

  • 算子编译和调用:编译并调用ACL算子来执行计算任务。

  • 异步结果获取:支持异步获取ACL算子的计算结果。

CANN模块

  • cann_module:作为核心模块,定义各类数据结构,Allocator,Ascend Runtime接口封装等。

  • element_operator:处理基本的元素级操作,例如加法、乘法等。

  • core:提供核心图像变换功能,例如,merge,flip等。

  • cvtcolor:专门处理颜色空间转换操作,如RGB到灰度转换等。

接口和绑定

  • C++接口:为C++应用程序提供接口,允许直接调用CANN模块的功能。

  • Python绑定:为Python应用程序提供接口,方便用户在Python环境中使用CANN模块。

其他

  • 错误处理:负责管理和记录各类级别日志,方便问题排查定位。
  • 功能测试:确保模块各项功能的正确性和稳定性。
  • 性能测试:对模块进行性能测试,验证加速效果和计算效率。
  • 样例:提供示例代码,帮助用户了解如何使用CANN模块。
  • 教程:提供详细的用户指南和教程,帮助用户快速上手和使用CANN模块。

CANN各个模块的依赖和调用关系如下图所示:

OpenCV算法调用时序图

3. Cann_Module

CANN模块中定义了OpenCV中的关键结构体,AscendMat,AscendStream和AscendEvent。其中AscendMat结构与Mat结构类似,需要有与InputArray(各类Mat的通用结构)相互转换的能力。与其他后端的Mat,Stream和Event类型类似,需要实现以下接口。AscendMat中存储着矩阵的shape和数据,并且有Device-Host内存拷贝能力,以及类型转换等能力。

Cann Module类图

3.1 类和组件

AscendMat 类

AscendMat 是一个封装了 Ascend 设备内存的矩阵类,类似于 OpenCV 的 Mat 类,但专为 Ascend 硬件设计。它支持各种矩阵操作,并通过内部的 Allocator 进行内存管理。

主要属性:

  • Allocator* allocator:用于内存分配的分配器。

  • int flags:包括魔术签名、连续性标志、深度和通道数等信息的位字段。

  • int rows, cols:矩阵的行数和列数。

  • size_t step:每行的字节数。

  • std::shared_ptr data:指向矩阵数据的智能指针。

  • uchar* datastart, const uchar* dataend:辅助字段用于 ROI 定位和调整。

主要方法:

  • 构造函数和拷贝构造函数,用于初始化和复制矩阵。

  • setTo:设置矩阵中的所有元素。

  • create:分配新的矩阵数据。

  • upload 和 download:将数据上传到设备或从设备下载。

  • convertTo:将矩阵转换为其他数据类型。

  • isContinuous, elemSize, size 等方法用于获取矩阵的属性和信息。

  • defaultAllocator():获取默认分配器。

  • setDefaultAllocator(Allocator* allocator):设置默认分配器。

Allocator 类

Allocator 是 AscendMat 的内部类,用于处理内存分配。

主要方法:

  • allocate(size_t size):分配指定大小的内存。

  • allocate(AscendMat* mat, int rows, int cols, size_t elemSize):为矩阵分配内存并初始化相关字段。

DefaultAllocator 类

DefaultAllocator 继承自 Allocator,实现了具体的内存分配和释放方法。

主要方法:

  • allocate(size_t size):使用 aclrtMalloc 分配内存。

  • allocate(AscendMat* mat, int rows, int cols, size_t elemSize):为 AscendMat 分配内存并设置步幅。

AscendStream 类

AscendStream 管理 Ascend 设备上的任务流,支持任务的异步执行和同步。

主要方法:

  • waitForCompletion():阻塞当前线程直到流中的所有操作完成。

  • waitAscendEvent(const AscendEvent& event):阻塞当前线程直到事件触发。

  • Null():返回默认的空流对象。

  • addTensorHolder(const std::shared_ptr& holder):向流中添加张量持有者。

AscendEvent 类

AscendEvent 用于流之间的同步。

主要方法:

  • record(AscendStream& stream):记录事件。
  • waitForComplete():等待事件完成。

4. ACL封装

AscendTensor结构与AscendMat相对应,用于将AscendMat转换成Ascend亲和的格式。其中矩阵数据使用智能指针,用户任务异步执行。

OperatorRunner是算子执行的类,用于设置算子执行所需的算子名称,属性,输入和输出矩阵,该类的成员函数均返回自身指针,方便设置多个属性。

除此之外,ACL封装也为了隔离ACL相关的库,整个工程中仅在此文件中会依赖ACL相关符号,避免OpenCV和ACL库的过度耦合,除了下述类图之外,其他的设备管理均做了封装,例如初始化,去初始化,内存管理和拷贝等。

ACL 模块类图

ACL算子允许异步提交,来提高硬件利用率,提高数据处理吞吐。所以当某个算子任务提交后,无法直接判断其执行进度,所以需要对矩阵数据进行保存,避免计算完成前数据被释放。

算子提交后,其智能指针会保存到AscendStream的tensorHolder中,即使超出某个AscendTensor的生命周期,该部分数据仍会保存,直到Stream Sync后,这些tensor才会真正释放。

为了避免内存浪费,可以在AscendStream中插入AscendEvent,通过判断Event是否完成,来判断有那些tensor已经计算完成,可以尽快释放。

为了进一步提高内存的分配效率,后续可以添加内存池,由应用来进行Device上的内存管理,避免频繁调用Device的内存申请释放。

5. 支持的算法

5.1 算数计算

本节详细描述针对 Ascend 硬件加速器的 OpenCV 算术操作实现。包括各种算术操作的计算公式及其在代码中的实现细节。

加法(Add)

  • 描述:计算两个输入图像对应像素值的和。支持输入图像的类型为 AscendMat 和 Scalar。

  • 计算公式\[ \text{dst}(i, j) = alpha\times\text{src1}(i, j) + beta\times\text{src2}(i, j) \]

  • 说明:alpha 和 beta 参数允许对输入图像进行加权和调整。

减法 (subtract)

  • 描述:计算两个输入图像对应像素值的差。支持输入图像的类型为 AscendMat 和 Scalar。

  • 计算公式\[ \text{dst}(i, j) = \text{src1}(i, j) - \text{src2}(i, j) \]

乘法 (multiply)

  • 描述:计算两个输入图像对应像素值的乘积。支持输入图像的类型为 AscendMat 和 Scalar,还支持缩放因子的应用。

  • 计算公式\[ \text{dst}(i, j) = scale\times(\text{src1}(i, j) \times \text{src2}(i, j)) \]

  • 说明:scale 参数允许对结果进行缩放,以调整输出图像的亮度或对比度。

除法 (divide)

  • 描述:计算两个输入图像对应像素值的商。支持输入图像的类型为 AscendMat 和 Scalar,还支持缩放因子的应用。

  • 计算公式\[ \text{dst}(i, j) = scale\times\frac{\text{src1}(i, j)}{\text{src2}(i, j)} \]

  • 说明:scale 参数用于调整除法结果的缩放。

按位与 (bitwise_and)

  • 描述:计算两个输入图像对应像素值的按位与操作。支持输入图像的类型为 AscendMat 和 Scalar。

  • 计算公式\[ \text{dst}(i, j) = \text{src1}(i, j) \& \text{src2}(i, j) \]

  • 说明:用于图像的遮罩和掩盖操作。

按位或 (bitwise_or)

  • 描述:计算两个输入图像对应像素值的按位或操作。支持输入图像的类型为 AscendMat 和 Scalar。

  • 计算公式\[ \text{dst}(i, j) = \text{src1}(i, j) \| \text{src2}(i, j) \]

  • 说明:用于图像合成和区域提取。

按位异或 (bitwise_xor)

  • 描述:计算两个输入图像对应像素值的按位异或操作。支持输入图像的类型为 AscendMat 和 Scalar。

  • 计算公式\[ \text{dst}(i, j) = \text{src1}(i, j) \oplus \text{src2}(i, j) \]

  • 说明:用于图像的特殊编码和数据加密。

按位取反 (bitwise_not)

  • 描述:计算图像像素值的按位取反操作。支持输入图像的类型为 AscendMat。

  • 计算公式\[ \text{dst}(i, j) = \sim \text{src}(i, j) \]

  • 说明:用于反转图像中的每个像素值。

加权和 (addWeighted)

  • 描述:计算两个输入图像的加权和。支持输入图像的类型为 AscendMat 和 Scalar,并且可以指定加权系数和加法常数。

  • 计算公式\[ \text{dst}(i, j) = \text{alpha} \times \text{src1}(i, j) + \text{beta} \times \text{src2}(i, j) + \text{gamma} \]

  • 说明:alpha 和 beta 用于加权输入图像,gamma 用于加法常数。

Threshold

  • 描述:对输入图像中的像素值进行阈值操作,根据指定的阈值和类型,将像素值调整为新的值。

  • 计算公式\[ Binary: \text{dst}(i, j) = \begin{cases} \text{maxVal} & \text{if } \text{src}(i, j) > \text{thresh} \\ 0 & \text{otherwise} \end{cases} \\ \\ Binary Inverted: \text{dst}(i, j) = \begin{cases} 0 & \text{if } \text{src}(i, j) > \text{thresh} \\ \text{maxVal} & \text{otherwise} \end{cases} \\ \\ Truncate: \text{dst}(i, j) = \begin{cases} \text{thresh} & \text{if } \text{src}(i, j) > \text{thresh} \\ \text{src}(i, j) & \text{otherwise} \end{cases} \\ \\ To Zero: \text{dst}(i, j) = \begin{cases} \text{src}(i, j) & \text{if } \text{src}(i, j) > \text{thresh} \\ 0 & \text{otherwise} \end{cases} \\ \\ To Zero Inverted: \text{dst}(i, j) = \begin{cases} 0 & \text{if } \text{src}(i, j) > \text{thresh} \\ \text{src}(i, j) & \text{otherwise} \end{cases} \]

  • 说明:Threshold 操作广泛用于图像分割和预处理阶段。不同的阈值类型允许对图像中的不同区域进行区分和处理。

上述算法接口较为类似,为了避免重复代码,需要将此类函数调用使用模板的方式进行抽象。

接口分为外部接口和内部接口,外部接口是对内部接口的封装,避免代码重复和额外的数据类型转换。

5.2 图像核心算法

这段代码是 OpenCV 项目中的一部分,专门为 Ascend 硬件加速器提供了图像处理操作的实现。这些操作包括数据格式转换、图像合并与分割、转置、翻转、旋转、裁剪和缩放。以下是对每个操作的简要介绍:

数据转换 (transData)

  • 描述:将输入数据从一种格式转换为另一种格式,例如从 NCHW 转换为 NHWC。

  • 说明:此函数用于将输入图像或矩阵的存储格式在不同的维度顺序之间进行转换,以适应不同的深度学习模型或计算需求。

图像合并 (merge)

  • 描述:将多个输入矩阵按通道维度合并为一个矩阵。输入矩阵的数量和类型必须相同。

  • 计算公式

\[ \text{dst}(x, y) = \begin{bmatrix} \text{B}(x, y) \ \text{G}(x, y) \ \text{R}(x, y) \end{bmatrix} \]

  • 说明:此函数在图像处理和深度学习中,用于将多通道图像合并为单一矩阵,以适应后续的处理或模型输入要求。

图像分割 (split)

  • 描述:将一个多通道矩阵按通道维度分割为多个单通道矩阵。
  • 计算公式

\[ \text{B}(x, y) = \text{dst}(x, y)[0] \\ \text{G}(x, y) = \text{dst}(x, y)[1] \\ \text{R}(x, y) = \text{dst}(x, y)[2] \]

  • 说明:此函数用于将多通道图像分割成独立的单通道图像,通常用于图像分析和预处理。

转置 (transpose)

  • 描述:对输入矩阵执行转置操作,交换指定的维度。
  • 计算公式

\[ \text{dst}(i, j) = \text{src}(j, i) \]

  • 说明:转置操作通常用于调整矩阵的维度顺序,以适应特定的算法或网络层要求。

翻转 (flip)

  • 描述:根据指定的轴对图像进行翻转操作。
  • 计算公式

\[ 水平翻转:\text{dst}(x, y) = \text{src}(x, H - 1 - y) \\ 垂直翻转:\text{dst}(x, y) = \text{src}(H - 1 - x, y) \\ \]

  • 说明:图像翻转常用于数据增强,帮助模型学习不同的视角和方向。

旋转 (rotate)

  • 描述:根据指定的模式对图像进行旋转操作,支持 90 度顺时针、180 度和 90 度逆时针旋转。
  • 计算公式

\[ 90度顺时针:\text{dst}(x, y) = \text{src}(H - y - 1, x) \\ 180度:\text{dst}(x, y) = \text{src}(H - x - 1, W - y - 1) \\ 90度逆时针:\text{dst}(x, y) = \text{src}(y, W - x - 1) \\ \]

  • 说明:旋转操作在图像处理和数据增强中常用,用于产生不同角度的视图。

裁剪 (crop)

  • 描述:从输入图像中裁剪指定矩形区域。
  • 计算公式

\[ \text{dst}(x, y) = \text{src}(x + x_{offset}, y + y_{offset}) \\ 其中 x_{offset} 和 y_{offset} 为裁剪区域的偏移量。 \]

  • 说明:裁剪操作用于提取图像中的特定区域,以进行更细致的分析或处理。

调整大小 (resize)

  • 描述:将输入图像缩放到指定的大小,支持不同的插值方法,如双三次插值和区域插值。
  • 计算公式

\[ 双线性插值(Bilinear):dst(x, y) = (y_2 - y) \cdot \left[ (x_2 - x) \cdot src(x_1, y_1) + (x - x_1) \cdot src(x_2, y_1) \right] +\\ (y - y_1) \cdot \left[ (x_2 - x) \cdot src(x_1, y_2) + (x - x_1) \cdot src(x_2, y_2) \right] \\ 双三次差值(Cubic):\text{dst}(x, y) = \sum_{m=-1}^{2} \sum_{n=-1}^{2} w(m) w(n) \text{src}(x+m, y+n) \\ 区域插值(Area):\text{dst}(x, y) = \frac{1}{\text{area}} \sum_{(x{\prime}, y{\prime}) \in \text{region}} \text{src}(x{\prime}, y{\prime}) \]

  • 说明:调整大小操作在图像预处理阶段非常重要,用于将图像缩放到模型要求的输入尺寸。

裁剪+调整大小 (crop_resize)

  • 描述:从一张大图中扣出一张或多张子图,并缩放到指定尺寸。

  • 计算公式为crop和resize算子依次执行,此处不做赘述。

  • 说明

    • 若crop的宽高与resize之后的宽高一致,则不进行缩放;resize宽高必须与输出宽高一致。

    • 输入图片分辨率在[10×6, 4096×4096]范围内,支持图片格式、宽高对齐、内存约束处说明的输入图片格式。

原地边框填充 (copyMakeBorder)

  • 描述:该函数在使用指定的外推边界模式时,计算并返回与指定外推像素相对应的供体像素的坐标。

  • 计算公式\[ \text{常数填充}: src'(x, y) = \text{value}, \quad \text{如果} \ (x, y) \text{是边界元素}\\ \text{复制边缘}: src'(x, y) = src(x', y'), \quad \text{其中} \ (x', y') \text{为最近的图像内的像素坐标} \]

  • 说明

    • scalar_value仅在填充类型为HI_BORDER_CONSTANT的时候有效,指定填充的像素值。
    • 输入图片分辨率在[10×6, 4096×4096]范围内,支持图片格式、宽高对齐、内存约束处说明的输入图片格式。

裁剪+调整大小+边框填充 (cropResizeMakeBorder)

  • 描述:按指定区域从一张输入图片中抠出一个或多个子图,对子图缩放后,再将每个子图按指定类型填充,作为一张或多张目标图片输出,主要用于等比例缩放场景。

  • 计算公式为crop、resize和copyMakeBorder算子依次执行,此处不做赘述。

  • 说明

    • 若crop的宽高与resize之后的宽高一致,则不进行缩放。
    • scalar_value仅在填充类型为HI_BORDER_CONSTANT的时候有效,指定填充的像素值。
    • 输入图片分辨率在[10×6, 4096×4096]范围内,支持图片格式、宽高对齐、内存约束处说明的输入图片格式。

接口分为外部接口和内部接口,外部接口是对内部接口的封装,避免代码重复和额外的数据类型转换。

5.3 色域转换

本节是用于在华为Ascend硬件上加速OpenCV图像处理操作的。以下是对主要函数的解释:

cvtBGRtoBGR

  • 描述: 将 BGR 图像转换为指定的通道数(DCN)并根据选项交换蓝色通道的位置。
  • 说明 将 BGR 图像的三个通道分离,并根据是否需要交换蓝色通道进行处理。然后将处理后的通道合并为指定的通道数(3 或 4)。

cvtBGRtoGray

  • 描述:将 BGR 图像转换为灰度图像。
  • 计算公式

\[ \text{Gray} = 0.299 \times \text{Red} + 0.587 \times \text{Green} + 0.114 \times \text{Blue} \]

  • 说明:将 BGR 图像转换为灰度图像,通过应用加权系数来计算每个像素的灰度值。

cvtGraytoBGR

  • 描述: 将灰度图像转换为 BGR 图像。
  • 计算公式

\[ \text{BGR} = \begin{bmatrix} \text{Gray} \ \text{Gray} \ \text{Gray} \end{bmatrix} \]

  • 说明:将灰度图像的单通道复制为三个通道(或四个通道),然后合并为 BGR(或 BGRA)图像。

cvtBGRtoXYZ

  • 描述:将 BGR 图像转换为 XYZ 颜色空间。
  • 计算公式
$$ \[\begin{bmatrix} X \\ Y \\ Z \end{bmatrix}\] \[\begin{bmatrix} 0.412453 & 0.357580 & 0.180423 \\ 0.212671 & 0.715160 & 0.072169 \\ 0.019334 & 0.119193 & 0.950227 \end{bmatrix} \begin{bmatrix} \text{Blue} \\ \text{Green} \\ \text{Red} \end{bmatrix}\]

$$

说明:将 BGR 图像通过矩阵乘法转换为 XYZ 颜色空间。

cvtXYZtoBGR

  • 描述:将 XYZ 颜色空间图像转换为 BGR 图像。
  • 计算公式
$$ \[\begin{bmatrix} \text{Blue} \\ \text{Green} \\ \text{Red} \end{bmatrix}\] \[\begin{bmatrix} 3.240479 & -1.53715 & -0.498535 \\ -0.969256 & 1.875991 & 0.041556 \\ 0.055648 & -0.204043 & 1.057311 \end{bmatrix} \begin{bmatrix} X \\ Y \\ Z \end{bmatrix}\]

$$

  • 说明:将 XYZ 图像通过矩阵乘法转换为 BGR 图像,并根据需要添加 alpha 通道。

cvtBGRtoYCrCb

  • 描述:将 BGR 图像转换为 YCrCb 颜色空间。
  • 计算公式

\[ Y = 0.299 \times \text{Red} + 0.587 \times \text{Green} + 0.114 \times \text{Blue} \\ \text{Cr} = 0.713 \times (\text{Red} - Y) + 128 \\ \text{Cb} = 0.564 \times (\text{Blue} - Y) + 128 \]

  • 说明:将 BGR 图像转换为 YCrCb 颜色空间,并根据需要调整通道顺序。

cvtYCrCbtoBGR

  • 描述:将 YCrCb 颜色空间图像转换为 BGR 图像。
  • 计算公式

\[ \text{Red} = Y + 1.402 \times (\text{Cr} - 128) \\ \text{Blue} = Y + 1.772 \times (\text{Cb} - 128) \\ \text{Green} = Y - 0.344136 \times (\text{Cr} - 128) - 0.714136 \times (\text{Cb} - 128) \]

  • 说明:将 YCrCb 图像转换为 BGR 图像,通过矩阵计算恢复到 BGR 颜色空间,并根据需要添加 alpha 通道。

RGB月YUV相互转换的计算方法与YCrCb计算方法类似,以及RGB,BGR, RGBA,BGRA与其他色域转换实现类似,使用不用参数的方式复用上述色域转换代码。

6. Python绑定

OpenCV的python绑定通过一个Python脚本对C++的接口函数解析实现,重载的函数会在python绑定中生成多个绑定,根据参数类型试探的方式选择正确的重载函数。

OpenCV对输入和输出有一个通用结构,分别是InputArray和OutputArray,这两个结构会在python绑定中自动与Mat(UMat,GpuMat)进行转换,在python调用中,这些算法接口可以传入numpy结构或者任意一种Mat结构。由于后端加速器包在OpenCV-Contrib中,为了避免修改OpenCV主仓库,所以InputArray和OutputArray无法识别到AscendMat,也就无法做自动转换。

为了提供一致的使用体验,在提供CANN模块接口时,需要提供InputArray/OutputArray的接口,也需要提供AscendMat的接口,在CANN模块内部做InputArray/OutputArray与AscendMat的类型转换。

该接口既提供C++接口,也提供python绑定接口。该接口是使用CANN模块的入口,根据OpenCV的项目要求,需要提供详细的Doxygen描述,以生成标准的doc手册。

7. AscendC支持

AscendC是自定义算子的编程语言,为了提高算子的执行效率,最好的方式使用AscendC来编写合适的融合算子。在OpenCV昇腾支持中,AscendC支持是一个实验性质的特性。

整体AscendC应该当做昇腾支持的一个子模块,于OpenCV一同链接到二进制中。需要实现AscendC编译链接框架,并且实现一个简单的算子(Threshold),当做自定义算子的样例。

1
2
3
opencv
└── opencv_cann.so
└── ascendc_kernels.so

8. DVPP支持

DVPP是昇腾AI处理器内置的图像处理单元,专门用于图像和视频的处理和加速,可以通过AscendCL的媒体数据处理接口进行调用,提供了强大的媒体处理硬加速能力。在OpenCV昇腾支持中,DVPP支持主要提供高性能算子特性。

DVPP支持应作为昇腾支持的子模块,由于其数据对齐及读取格式等约束不同于执行于AI core和AI cpu的Aclop算子,需为其单独设计运行时管理、内存管理及数据处理流程的类及方法,初步设计如下:

DVPP类图

其中,DvppOperatorDesc管理DVPP初始化、重置,及创建通道、输入、输出、获取结果等算子运行流程管理,WrapperFunctions完成具体的参数配置、算子调用等。

9. 测试

9.1 功能测试

功能测试使用OpenCV的功能测试框架,验证所有的接口以及不同入参组合,将Ascend执行结果与CPU结果做比对,要求误差在允许范围内。

9.1.1 AscendMat验证

AscendMat 的构造函数测试 测试目的:

  • 验证 AscendMat 的默认构造函数和自定义构造函数的正确性。
  • 验证 AscendMat 构造时传递的大小、类型、值是否正确分配。

测试步骤:

  1. 使用默认构造函数创建 AscendMat 对象,并检查默认分配器是否正确。
  2. 设置和获取自定义分配器,确保分配器设置和获取功能正常。
  3. 创建指定大小和类型的 AscendMat 对象,并检查行数、列数、深度和通道数是否与预期一致。
  4. 使用特定值填充 AscendMat 对象,并检查填充值是否正确。
  5. 从主机内存创建 AscendMat 对象,并检查数据的正确性。

预期结果:

  • 默认构造函数应使用默认分配器。
  • 自定义分配器应成功设置和获取。
  • 创建的 AscendMat 对象的大小、类型和填充值应与预期匹配。
  • 从主机内存构造的 AscendMat 数据应与输入数据一致。

AscendMat 的赋值操作测试 测试目的:

  • 验证 AscendMat 对象间的赋值操作是否能正确复制数据和元数据。

测试步骤:

  1. 创建两个 AscendMat 对象,一个使用自定义分配器分配内存。
  2. 将一个 AscendMat 对象赋值给另一个,并检查行数、列数、深度、通道数及数据指针是否一致。

预期结果:

  • 赋值操作应正确复制行数、列数、深度、通道数及数据指针。

**

AscendMat 的 setTo 方法测试 测试目的:

  • 验证 AscendMat 的 setTo 方法能否正确将矩阵的所有元素设置为指定值。

测试步骤:

  1. 创建一个 AscendMat 对象,并使用 setTo 方法将其所有元素设置为随机生成的标量值。
  2. 下载数据到主机并与手动创建的矩阵数据进行比较。

预期结果:

  • AscendMat 对象应成功设置所有元素,并且与主机上的期望数据一致。

AscendMat 的 convertTo 方法测试 测试目的:

  • 验证 AscendMat 的 convertTo 方法能否正确转换矩阵数据类型。

测试步骤:

  1. 创建一个 AscendMat 对象,并使用随机生成的标量值初始化。
  2. 使用 convertTo 方法将矩阵数据类型转换为另一个类型。
  3. 下载数据到主机并与手动创建的转换后矩阵进行比较。

预期结果:

  • AscendMat 对象应成功转换数据类型,且转换后的数据与主机上的期望数据一致。

合并测试(MERGE) 测试目的:

  • 验证merge函数在Ascend后端的实现是否与CPU一致。

测试步骤:

  1. 创建三个单通道的矩阵m1、m2和m3。
  2. 使用merge函数在CPU上合并这三个矩阵为一个多通道矩阵。
  3. 在Ascend后端上分别通过数组和向量的方式合并矩阵,并将结果下载到主机端。
  4. 比较CPU和Ascend后端的结果,确保它们一致。

预期结果:

  • Ascend后端的合并结果应与CPU的结果一致。

9.1.2 算术算法验证

算术算法需要验证一下几种类型:

基础矩阵运算操作:

  • 测试包括矩阵加法、减法、乘法、除法、按位与、按位或、按位异或等操作。
  • 验证以上操作在 Ascend 设备与 CPU 上的结果一致性。

带掩码的矩阵运算:

  • 验证矩阵运算操作在引入掩码后的正确性,包括掩码的生成与使用。

带缩放参数的矩阵运算:

  • 验证矩阵乘法和除法操作在引入缩放参数后的正确性。

流管理:

  • 测试 Ascend Stream 的创建、运算调度与结果同步功能,确保异步处理与同步处理的结果一致。

测试方法如下:

测试目的:

  • 验证加法、减法、乘法、除法、按位与、按位或、按位异或,加权和,阈值计算等操作方法计算结果是否正确。

测试步骤:

  1. 创建一个矩阵对象,并使用随机数据进行填充。
  2. 使用需要验证的算法进行计算,分别使用默认流,以及显式创建的流进行计算。
  3. 比较CPU和Ascend后端的结果,确保他们的精度一致。

预期结果:

  • Ascend后端的合并结果应与CPU的结果一致。

9.1.3 图像核心算法验证

拆分测试(SPLIT) 测试目的:

  • 验证split函数在Ascend后端的实现是否与CPU一致。

测试步骤:

  1. 创建一个多通道的矩阵m,并使用split函数在CPU上将其拆分为三个单通道矩阵。
  2. 使用Ascend后端进行同样的拆分操作,并将结果下载到主机端。
  3. 比较CPU和Ascend后端的结果,确保它们一致。

预期结果:

  • Ascend后端的拆分结果应与CPU的结果一致。

转置测试(TRANSPOSE) 测试目的:

  • 验证transpose函数在Ascend后端的实现是否与CPU一致。

测试步骤:

  1. 创建一个随机矩阵cpuMat。
  2. 使用transpose函数在CPU上转置矩阵,并存储结果。
  3. 在Ascend后端上进行相同的转置操作,并将结果下载到主机端。
  4. 比较CPU和Ascend后端的结果,确保它们一致。

预期结果:

  • Ascend后端的转置结果应与CPU的结果一致。

翻转测试(FLIP) 测试目的:

  • 验证flip函数在Ascend后端的实现是否与CPU一致。

测试步骤:

  1. 创建一个随机矩阵cpuMat。
  2. 使用flip函数在CPU上分别以不同的翻转模式(水平、垂直、同时翻转)进行翻转,并存储结果。
  3. 在Ascend后端上执行相同的翻转操作,并将结果下载到主机端。
  4. 比较CPU和Ascend后端的结果,确保它们一致。

预期结果:

  • Ascend后端的翻转结果应与CPU的结果一致。

旋转测试(ROTATE) 测试目的:

  • 验证rotate函数在Ascend后端的实现是否与CPU一致。

测试步骤:

  1. 创建一个随机矩阵cpuMat。
  2. 使用rotate函数在CPU上以不同的旋转模式进行旋转,并存储结果。
  3. 在Ascend后端上进行相同的旋转操作,并将结果下载到主机端。
  4. 比较CPU和Ascend后端的结果,确保它们一致。

预期结果:

  • Ascend后端的旋转结果应与CPU的结果一致。

裁剪测试(CROP) 测试目的:

  • 验证裁剪操作在Ascend后端的实现是否与CPU一致。

测试步骤:

  1. 创建一个矩阵cpuMat,并定义一个裁剪区域Rect b。
  2. 使用Mat对象在CPU上执行裁剪操作,并存储结果。
  3. 在Ascend后端上执行相同的裁剪操作,并将结果下载到主机端。
  4. 比较CPU和Ascend后端的结果,确保它们一致。

预期结果:

  • Ascend后端的裁剪结果应与CPU的结果一致。

调整大小测试(RESIZE) 测试目的:

  • 验证resize函数在Ascend后端的实现是否与CPU一致。

测试步骤:

  1. 创建一个随机矩阵cpuMat。
  2. 使用resize函数在CPU上对矩阵进行调整大小操作,使用不同的插值方法,并存储结果。
  3. 在Ascend后端上进行相同的调整大小操作,并将结果下载到主机端。
  4. 比较CPU和Ascend后端的结果,确保它们一致。

预期结果:

  • Ascend后端的调整大小结果应与CPU的结果一致。

裁剪及调整大小测试(CROP_RESIZE) 测试目的:

  • 验证cropResize函数在Ascend后端的实现是否与CPU一致。

测试步骤:

  1. 创建一个随机矩阵cpuMat,并定义裁剪区域Rect b,目的矩阵大小Size dsize。
  2. 依次使用crop和resize函数在CPU上对矩阵进行裁剪和调整大小操作,使用不同的插值方法,并存储结果。
  3. 在Ascend后端上调用cropResize函数执行相同操作,并将结果下载到主机端。
  4. 比较CPU和Ascend后端的结果,确保它们一致。

预期结果:

  • Ascend后端的调整大小结果应与CPU的结果一致。

裁剪及调整大小测试(COPY_MAKE_BORDER) 测试目的:

  • 验证copyMakeborder函数在Ascend后端的实现是否与CPU一致。

测试步骤:

  1. 创建一个随机矩阵cpuMat,并定义top、bottom、left、right四个方向的border宽度。
  2. 使用copyMakeBorder函数在CPU上对矩阵进行填充边框操作,使用不同的边框填充插值方法,并存储结果。
  3. 在Ascend后端上调用copyMakeborder函数执行相同操作,并将结果下载到主机端。
  4. 比较CPU和Ascend后端的结果,确保它们一致。

预期结果:

  • Ascend后端的调整大小结果应与CPU的结果一致。

裁剪及调整大小测试(CROP_RESIZE_MAKE_BORDER) 测试目的:

  • 验证cropResizeMakeborder函数在Ascend后端的实现是否与CPU一致。

测试步骤:

  1. 创建一个随机矩阵cpuMat,并定义裁剪区域Rect b,目的矩阵大小Size dsize,top、bottom、left、right四个方向的border宽度。
  2. 依次使用crop、resize和copyMakeBorder函数在CPU上对矩阵进行裁剪、调整大小和填充边框操作,使用不同的边框填充插值方法,并存储结果。
  3. 在Ascend后端上调用cropResizeMakeborder函数执行相同操作,并将结果下载到主机端。
  4. 比较CPU和Ascend后端的结果,确保它们一致。

预期结果:

  • Ascend后端的调整大小结果应与CPU的结果一致。

9.1.4 色域转换算法验证

色域转换算法验证需要覆盖以下转换操作:

  • BGR到BGRA
  • BGRA到BGR
  • BGR到RGBA
  • RGBA到BGR
  • BGR到RGB
  • BGRA到RGBA
  • BGR到灰度图
  • RGB到灰度图
  • 灰度图到BGR
  • 灰度图到BGRA
  • BGRA到灰度图
  • RGBA到灰度图
  • RGB到XYZ
  • BGR到XYZ
  • XYZ到BGR
  • XYZ到RGB
  • BGR到YCrCb
  • RGB到YCrCb
  • YCrCb到BGR
  • YCrCb到RGB
  • BGR到YUV
  • RGB到YUV
  • YUV到BGR
  • YUV到RGB

测试目的:

  • 验证色域转换操作在Ascend后端的实现是否与CPU一致。

测试步骤:

  1. 生成随机图像矩阵。
  2. 执行颜色空间转换。
  3. 比较CPU和NPU计算结果,确认在允许误差范围内。

预期结果:

  • Ascend后端的计算结果应与CPU的结果一致。

9.1.5 其他说明

所有功能测试需要同步测试C++接口以及Python接口,保证二者可用性及准确性。

9.2 性能测试

性能测试使用OpenCV性能测试框架,使用不同图像大小,不同图像数据类型,将数据在Ascend NPU上执行多次,计算每次运行的平均时间。

初始化:

  1. 生成给定尺寸的测试矩阵。
  2. 构造随机输入矩阵,并完成算子预热。

Ascend NPU 测试:

  1. 设置 Ascend 设备 (cv::cann::setDevice),并上传矩阵至 AscendMat。
  2. 在 TEST_CYCLE() 内执行操作(如 merge、split 等)。
  3. 复位 Ascend 设备 (cv::cann::resetDevice)。

CPU 测试:

  • 使用 OpenCV 自带的方法执行相同操作。

验证:

  • 每个测试用例执行完后,通过 SANITY_CHECK_NOTHING() 检查无异常。

性能验证结果如下

算子/平均运算耗时*(ms) CPU GPU NPU 相对CPU性能提升 相对GPU性能提升
16 Intel(R) Xeon(R) Gold 6151 Nvidia V100 Ascend 310P GPU NPU NPU
merge 83.50 48.00 30.50 42.51% 63.47% 36.46%
split 142.00 59.00 81.50 58.45% 42.61% -38.14%
flip 221.25 77.75 1069.00 64.86% -383.16% -1274.92%
crop 49.25 61.50 75.75 -24.87% -53.81% -23.17%
transpose 446.00 84.50 277.50 81.05% 37.78% -228.40%
resize 136.50 188.75 229.75 -38.28% -68.32% -21.72%
threshold 172.75 174.00 251.50 -0.72% -45.59% -44.54%
rotate 584.50 76.50 1067.50 86.91% -82.63% -1295.42%
add 529.63 324.50 268.75 38.73% 49.26% 17.18%
addWeighted 553.88 326.38 422.88 41.07% 23.65% -29.57%
subtract 528.25 355.63 269.75 32.68% 48.94% 24.15%
multiply 534.63 354.25 265.88 33.74% 50.27% 24.95%
divide 542.50 355.63 266.75 34.45% 50.83% 24.99%
bitwise_add 529.25 355.13 266.00 32.90% 49.74% 25.10%
bitwise_or 529.50 354.63 266.00 33.03% 49.76% 24.99%
bitwise_xor 529.88 354.50 268.00 33.10% 49.42% 24.40%
bitwise_not 324.38 177.38 448.38 45.32% -38.23% -152.78%
cvtColor 57.31 66.96 118.76 -16.83% -107.21% -77.35%

9.3. CI

OpenCV社区没有昇腾测试设备,后续昇腾相关特性提交需要经过昇腾设备的CI验证。

  1. 向社区贡献昇腾机器用于CI验证。
  2. 使用PR label的方式,仅针对昇腾相关特性运行CI。
  3. 使用容器的方式运行CI,将Dockerfile合入OpenCV的基础设施仓库,并归档容器镜像。
  4. 配置github workflow,配置昇腾CI相关逻辑。

10. 教程

10.1 样例

为了方便用户使用该模块,需要提供调用样例,选择一个常见的图像预处理逻辑,实现C++版本以及Python版本的样例代码。

10.2 使用文档

按照社区的文档规范,编写模块使用指南,该使用指南将会构建到OpenCV doc中,作为教程供用户参考。

PR 代码量
Add operators support for Ascend NPU (CANN backend) +5880 -0
Link lib_acl_op_compiler when compile with CANN +12 -0
Base OpenEuler 22.03.SP2 docker image for CI. +71 -0
Added CI pipeline with openEuler22.03.SP2 and Ascend310 +120 -0
Enable AscendC kernel operator +697 -92
Update openEuler image tag +1 -1

OpenCV昇腾开源使用手册