> ## Content Index
> Fetch the complete content index at: https://yinguobing.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# 华为Atlas设备图像裁切
- URL: https://yinguobing.com/blog/ascend-developer-image-crop/
- Published: 2021-10-21T13:54:03.000Z
- Updated: 2024-04-02T07:44:24.000Z
- Description: 使用硬件加速的DVPP模块实现图像裁切功能。
- Author: 尹国冰
- Tags: Ascend, 图像处理

在华为Ascend设备与传统X86设备上的图像应用开发存在明显模式差异。主要体现在：

- 设备初始化与去初始化
- 内存申请与释放
- 图像格式
- 各种操作限制

以下内容以图像裁切为例，展示了在Ascend设备上完整的图像处理过程。包括四个部分：

1. 设备与环境初始化
2. 读取图像文件
3. 裁切图像
4. 收尾，释放资源

希望通过这个最小代码例子，你能建立在Ascend设备上图像开发的直观感受。

让我们开始吧！

---

首先导入必要的开发包。

```python
import acl
import cv2
import numpy as np
import atlas.common.atlas_utils.constants as constants
import atlas.common.atlas_utils.utils as utils
```

将重复代码抽象为一个辅助函数，方便复用。

```
def setup_desc(desc, width, height, w_align=16, h_align=2):
    """Setup the image description object.
    The image should be in YUV420SP format.
    
    Args:
        desc: the image description.
        width: the width of the image.
        height: the height of the image.
        w_align: the width align factor.
        h_align: the height align factor.
        
    Returns:
        ptr_on_device: the pointer of the image on the device.
    """
    # Calculate buffer size.
    width_stride = ((width + w_align - 1) // w_align) * w_align
    height_stride = ((height + h_align - 1) // h_align) * h_align
    buf_size = (width_stride * height_stride * 3) // 2

    # Allocate memory
    ptr_on_device, ret = acl.media.dvpp_malloc(buf_size)
    utils.check_ret("acl.media.dvpp_malloc", ret)

    # Setup the image.
    acl.media.dvpp_set_pic_desc_data(desc, ptr_on_device)
    acl.media.dvpp_set_pic_desc_size(desc, buf_size)
    acl.media.dvpp_set_pic_desc_format(
        desc, constants.PIXEL_FORMAT_YUV_SEMIPLANAR_420)
    acl.media.dvpp_set_pic_desc_width(desc, width)
    acl.media.dvpp_set_pic_desc_height(desc, height)
    acl.media.dvpp_set_pic_desc_width_stride(desc, width_stride)
    acl.media.dvpp_set_pic_desc_height_stride(desc, height_stride)

    return ptr_on_device
```

### 第一步：初始化

Ascend设备上运行至少需要初始化4个对象：ACL、Device、Context和Stream。这个过程对于Python开发者可能比较陌生。好在这个过程是一次性的。

ACL初始化。ACL是操作Ascend设备的Python库，有点像OpenCV。

```python
ret = acl.init()
assert ret == 0, "初始化ACL失败。"
```

Device初始化。一张Atlas加速卡上可能有多个Ascend芯片可以使用。在这里指定你要选用的芯片序号。

```python
device_id = 0
ret = acl.rt.set_device(device_id)
assert ret == 0, "初始化Device失败。"
```

 Context初始化。Context可以看做是程序运行的小环境，它起着隔离作用。

```python
context, ret = acl.rt.create_context(device_id)
assert ret == 0, "初始化Context失败。"
```

Stream初始化。Stream多用作程序执行的同步。

```python
stream, ret = acl.rt.create_stream()
assert ret == 0, "初始化Stream失败。"
```

成功执行到这一步，初始化完成。

### 第二步：图像读入

ACL中专门为图像相关操作提供了硬件加速模块。这些加速有一个前提条件：图像需要为YUV格式。另外请注意，OpenCV暂时无法将BGR图像转换为ACL支持的YUV420SP格式。所以我们将直接读入一张YUV图像。

```python
img_file = "wood_rabbit_1024_1068_nv12.yuv"
org_yuv = np.fromfile(img_file, dtype=np.byte)
```

为了验证读取结果，将读入的YUV图像转换为你熟悉的OpenCV图像：NumpyArray。并在窗口中查看图像内容。

```python
org_yuv = np.reshape(
    org_yuv, (int((1072)*3/2), int(1024))).astype(np.uint8)
org_bgr = cv2.cvtColor(org_yuv, cv2.COLOR_YUV2BGR_NV12)
```

### 第三步：图像裁切

如果你熟悉OpenCV的话，图像裁切可以通过数组slice的方式一行代码搞定。但是在ACL中，图像并不是以NumpyArray的形式存储。所以你无法通过slice的方式裁切图像，而只能依赖ACL提供的API。

完成一次裁切分为以下几步：

1\. 准备待裁切图像

虽然你已经将YUV图像读入内存，但是硬件加速的裁切操作需要在Ascend芯片上进行，这意味着你需要把图像数据从主机(host)一侧拷贝到Ascend芯片(Device)一侧。另外，Device一侧的数据并非Numpy数组，在这里生存的图像有自己独特的“描述方式”。

构建Device一侧的图像描述。

```python
input_desc = acl.media.dvpp_create_pic_desc()
assert input_desc is not None
```

图像描述对象构建之后，使用真实要处理的图像参数填充该描述，计算内存占用大小，并将具体的像数值拷贝到Device一侧。

```python
h, w, _ = org_bgr.shape
intput_ptr = setup_desc(input_desc, w, h)
in_buffer_size = org_yuv.itemsize * org_yuv.size
np_yuv_ptr = acl.util.numpy_to_ptr(org_yuv)
ret = acl.rt.memcpy(intput_ptr, in_buffer_size,
                    np_yuv_ptr, in_buffer_size,
                    constants.ACL_MEMCPY_HOST_TO_DEVICE)
assert ret == 0, "图像内存拷贝失败。"
```

至此，待裁切图像准备完成。

2\. 设定裁切区域。

由于输入图像不是Numpy数组，你只能使用ACL提供的API来设定裁切区域。另外，Ascend设备上的开发有诸多限制。例如裁切区域\[x0, x1, y0, y1\]必须满足\[偶数、奇数、偶数、奇数\]的约束。

```python
crop_w = 320
crop_h = 420
x = 480
y = 360
crop_area = acl.media.dvpp_create_roi_config(
    x, x + crop_w-1, y, y + crop_h-1)
```

3\. 构建输出图像。

与输入图像类似，输出图像默认生存在Device一侧，你需要提前为它准备好内存空间。这个过程与输入图像的构建过程非常相似。

```python
output_desc = acl.media.dvpp_create_pic_desc()
assert output_desc is not None
```

设置输出图片描述。输出图像的宽度与高度为截取后的大小。

```python
setup_desc(output_desc, crop_w, crop_h)
```

4\. 开始裁切

在正式执行裁切指令前，还需要为裁切操作创建“数据处理通道”，这也是ACL开发的不同之处。

```python
dvpp_channel_desc = acl.media.dvpp_create_channel_desc()
ret = acl.media.dvpp_create_channel(dvpp_channel_desc)
```

一切就绪，执行裁切指令。

```python
ret = acl.media.dvpp_vpc_crop_async(dvpp_channel_desc,     input_desc,output_desc, crop_area, stream)
```

裁切指令为异步执行，所以需要等待它执行完毕才能获得裁切结果。

```python
ret = acl.rt.synchronize_stream(stream)
```

至此裁切结束。

5\. 获取结果。

别忘了裁切后的图像生存在Device一侧，并且是YUV420SP格式。我们需要将它拷贝到主机(host)一侧，然后转换成熟悉的Numpy数组。

获取裁切后的图像信息。

```python
data = acl.media.dvpp_get_pic_desc_data(output_desc)
data_size = acl.media.dvpp_get_pic_desc_size(output_desc)
ret_code = acl.media.dvpp_get_pic_desc_ret_code(output_desc)
utils.check_ret("acl.media.dvpp_get_pic_desc_ret_code", ret_code)
```

拷贝数据到主机一侧并转换为Numpy数组。

```python
np_pic = np.zeros(data_size, dtype=np.byte)
np_pic_ptr = acl.util.numpy_to_ptr(np_pic)
ret = acl.rt.memcpy(np_pic_ptr, data_size,
                    data, data_size,
                    constants.ACL_MEMCPY_DEVICE_TO_HOST)
utils.check_ret("acl.rt.memcpy", ret)
```

6\. 查阅结果。

最后，在窗口中查阅裁切前后的对比。这一步需要将YUV420SP转换为BGR格式。

```python
result_yuv = np.reshape(
    np_pic, (int((crop_h)*3/2), int(crop_w))).astype(np.uint8)
result_bgr = cv2.cvtColor(result_yuv, cv2.COLOR_YUV2BGR_NV12)
cv2.imshow('Original', org_bgr)
cv2.imshow('Cropped', result_bgr)
cv2.waitKey()
```

### 第四步：收尾、释放资源

裁切结束后，释放资源，包括输入/输出图片的描述信息、输入/输出内存、通道描述信息、通道等。

释放图片描述。

```python
acl.media.dvpp_destroy_pic_desc(input_desc)
acl.media.dvpp_destroy_pic_desc(output_desc)
```

释放Device侧内存。

```python
ret = acl.media.dvpp_free(intput_ptr)
```

释放操作通道。

```python
if dvpp_channel_desc:
    ret = acl.media.dvpp_destroy_channel(dvpp_channel_desc)
    assert ret == 0
    ret = acl.media.dvpp_destroy_channel_desc(dvpp_channel_desc)
    assert ret == 0
```

释放运行资源。

```
ret = acl.rt.destroy_stream(stream)
ret = acl.rt.destroy_context(context)
ret = acl.rt.reset_device(device_id)
ret = acl.finalize()

print('结束。')
```

至此DVPP加速的图像裁切完成。

### 总结

与Numpy下的图像裁切相比，基于DVPP的图像存在诸多限制、代码量也很可观。这直接导致开发工作量变大。但是使用DVPP裁切图像可以避免将图像传输到HOST一侧造成的性能损耗。在实际应用中，付出这些代价是值得的。

你可以在这里找到完整的代码：

[pyACL\_standalone\_samples/crop.py at main · yinguobing/pyACL\_standalone\_samplesStandalone samples for Python ACL (Ascend Computing Language) development. - pyACL\_standalone\_samples/crop.py at main · yinguobing/pyACL\_standalone\_samples![](https://github.com/fluidicon.png)GitHubyinguobing![](https://repository-images.githubusercontent.com/418129081/ca498ac6-51ce-4b6f-8d2d-db657db98767)](https://github.com/yinguobing/pyACL%5Fstandalone%5Fsamples/blob/main/crop.py?ref=yinguobing.com)