---
title: 目标检测应用样例开发介绍（Python）
description: "本章节介绍基于AscendCL接口如何开发一个基于Yolo模型的目标检测样例。"
url: https://www.hiascend.com/document/detail/zh/Atlas200IDKA2DeveloperKit/latest/Getting%20Started%20with%20Application%20Development/gswaad/gswaad_0002.html
sourcePath: /source/zh/Atlas200IDKA2DeveloperKit/23.0.RC2/Getting Started with Application Development/gswaad/gswaad_0002.html
indexId: 6b50551395ba135b450fb3bab08e185bb983c9138aa1a1c80a2fdf35467cb3e0114
---
# 目标检测应用样例开发介绍（Python）

本章节介绍基于AscendCL接口如何开发一个基于Yolo模型的目标检测样例。

#### 样例介绍

目标检测，即给定一张图片，识别出图片中的目标位置。

图1 目标检测应用

本例中使用的是pytorch框架的yolov5模型。可以直接使用训练好的开源模型，也可以基于开源模型的源码进行修改、重新训练，还可以基于算法、框架构建适合的模型。

模型的输入数据与输出数据格式：- 输入数据：RGB格式图片，分辨率为 640×640，输入形状为（1，3，640，640），也即（batchsize，channel，height，width）。
- 输出数据：目标检测框的坐标值、置信度、类别。
  输出数组需要经过一定后处理，才能显示为正常的图片。


#### 业务模块介绍

业务模块的操作流程如图2所示。

图2 业务流程图

1. 图片输入：收集待检测数据集做为输入数据。
2. 输入预处理：将图片读入为数组，并缩放到模型所需大小，然后调整像素范围。
3. 模型推理：经过模型推理后得到目标检测结果。
4. 后处理：从输出中解析出检测框坐标，并在原始图像上画出检测框。
5. 可视化或保存到文件：将图片显示在界面或保存到文件。


#### 获取代码

1. 获取代码文件。
  单击获取链接(https://ascend-repo.obs.cn-east-2.myhuaweicloud.com/Atlas 200I DK A2/DevKit/models/sdk_cal_samples/yolo_acl_sample.zip)或使用wget命令获取代码（使用wget时需确保开发者套件能够连接外网），下载代码文件压缩包，以root用户登录开发者套件。

```
wget https://ascend-repo.obs.cn-east-2.myhuaweicloud.com/Atlas%20200I%20DK%20A2/DevKit/models/sdk_cal_samples/yolo_acl_sample.zip
```


2. 将“yolo_acl_sample.zip”压缩包上传到开发者套件，解压并进入解压后的目录。

```
unzip yolo_acl_sample.zip
cd yolo_acl_sample
```


  代码目录结构如下所示，按照正常开发流程，需要将框架模型文件转换成昇腾AI处理器支持推理的om格式模型文件，鉴于当前是入门内容，用户可直接获取已转换好的om模型进行推理。

```
|-- yolo_acl_sample
|-- infer              # 推理文件夹
|-- main.py            # 主程序
|-- det_utils.py       # 模型相关前后处理函数，函数为通用函数，和AscnedCL接口无关联
|-- coco_names.txt     # coco数据集所有类别名
|-- world_cup.jpg      # 测试图片
|-- yolov5s_bs1.om  # om模型
```


#### 代码解析

开发代码过程中，在“yolo_acl_sample/infer/main.py”文件中已包含读入数据、前处理、推理、后处理等功能，串联整个应用代码逻辑，此处仅对代码进行解析。

1. 导入需要的第三方库以及调用AscendCL接口推理所需文件，定义模型相关变量，如设备ID，内存申请策略等。

```
# coding=utf-8
from abc import abstractmethod, ABC  # 用于定义抽象类
import cv2  # 图片处理三方库，用于对图片进行前后处理
import numpy as np  # 用于对多维数组进行计算
import torch  # 深度学习运算框架，此处主要用来处理数据
import acl  # AscnedCL推理相关接口
from det_utils import get_labels_from_txt, letterbox, scale_coords, nms, draw_bbox  # 模型前后处理相关函数
DEVICE_ID = 0  # 设备id
SUCCESS = 0  # 成功状态值
FAILED = 1  # 失败状态值
ACL_MEM_MALLOC_NORMAL_ONLY = 2  # 申请内存策略, 仅申请普通页
trained_model_path = '../yolov5s_bs1.om'  # 模型路径
image_path = 'world_cup.jpg'  # 测试图片路径
```


2. 资源初始化。使用AscendCL接口开发应用时，必须先初始化AscnedCL，否则可能会导致后续系统内部资源初始化出错，进而导致其它业务异常。

```
# acl初始化
def init_acl(device_id):
acl.init()
ret = acl.rt.set_device(device_id)  # 指定运算的Device
if ret:  # 若指定出错，则抛出异常
raise RuntimeError(ret)
context, ret = acl.rt.create_context(device_id)  # 显式创建一个Context
if ret:  # 若创建出错，则抛出异常
raise RuntimeError(ret)
print('Init ACL Successfully')
return context
# acl 去初始化
def deinit_acl(context, device_id):
ret = acl.rt.destroy_context(context)  # 释放 Context
if ret:  # 若释放出错，则抛出异常
raise RuntimeError(ret)
ret = acl.rt.reset_device(device_id)  # 释放Device
if ret:  # 若释放出错，则抛出异常
raise RuntimeError(ret)
ret = acl.finalize()  # 去初始化
if ret:  # 若去初始化出错，则抛出异常
raise RuntimeError(ret)
print('Deinit ACL Successfully')
```


3. 定义模型资源相关基类，承担初始化模型资源、创建输入输出数据集、执行推理、解析输出、释放模型资源等功能，之后的Yolo模型推理可继承此类。

```
class Model(ABC):
def __init__(self, model_path):
print(f"load model {model_path}")
self.model_path = model_path  # 模型路径
self.model_id = None  # 模型 id
self.input_dataset = None  # 输入数据结构
self.output_dataset = None  # 输出数据结构
self.model_desc = None  # 模型描述信息
self._input_num = 0  # 输入数据个数
self._output_num = 0  # 输出数据个数
self._output_info = []  # 输出信息列表
self._is_released = False  # 资源是否被释放
self._init_resource()
def _init_resource(self):
''' 初始化模型、输出相关资源。相关数据类型: aclmdlDesc aclDataBuffer aclmdlDataset'''
print("Init model resource")
# 加载模型文件
self.model_id, ret = acl.mdl.load_from_file(self.model_path)  # 加载模型
self.model_desc = acl.mdl.create_desc()  # 初始化模型信息对象
ret = acl.mdl.get_desc(self.model_desc, self.model_id)  # 根据模型获取描述信息
print("[Model] Model init resource stage success")
# 创建模型输出 dataset 结构
self._gen_output_dataset()  # 创建模型输出dataset结构
def _gen_output_dataset(self):
''' 组织输出数据的dataset结构 '''
ret = SUCCESS
self._output_num = acl.mdl.get_num_outputs(self.model_desc)  # 获取模型输出个数
self.output_dataset = acl.mdl.create_dataset()  # 创建输出dataset结构
for i in range(self._output_num):
temp_buffer_size = acl.mdl.get_output_size_by_index(self.model_desc, i)  # 获取模型输出个数
temp_buffer, ret = acl.rt.malloc(temp_buffer_size, ACL_MEM_MALLOC_NORMAL_ONLY)  # 为每个输出申请device内存
dataset_buffer = acl.create_data_buffer(temp_buffer, temp_buffer_size)  # 创建输出的data buffer结构,将申请的内存填入data buffer
_, ret = acl.mdl.add_dataset_buffer(self.output_dataset, dataset_buffer)  # 将 data buffer 加入输出dataset
if ret == FAILED:
self._release_dataset(self.output_dataset)   # 失败时释放dataset
print("[Model] create model output dataset success")
def _gen_input_dataset(self, input_list):
''' 组织输入数据的dataset结构 '''
ret = SUCCESS
self._input_num = acl.mdl.get_num_inputs(self.model_desc)  # 获取模型输入个数
self.input_dataset = acl.mdl.create_dataset()  # 创建输入dataset结构
for i in range(self._input_num):
item = input_list[i]  # 获取第 i 个输入数据
data_ptr = acl.util.bytes_to_ptr(item.tobytes())  # 获取输入数据字节流
size = item.size * item.itemsize  # 获取输入数据字节数
dataset_buffer = acl.create_data_buffer(data_ptr, size)  # 创建输入dataset buffer结构, 填入输入数据
_, ret = acl.mdl.add_dataset_buffer(self.input_dataset, dataset_buffer)  # 将dataset buffer加入dataset
if ret == FAILED:
self._release_dataset(self.input_dataset)  # 失败时释放dataset
print("[Model] create model input dataset success")
def _unpack_bytes_array(self, byte_array, shape, datatype):
''' 将内存不同类型的数据解码为numpy数组 '''
np_type = None
# 获取输出数据类型对应的numpy数组类型和解码标记
if datatype == 0:  # ACL_FLOAT
np_type = np.float32
elif datatype == 1:  # ACL_FLOAT16
np_type = np.float16
elif datatype == 3:  # ACL_INT32
np_type = np.int32
elif datatype == 8:  # ACL_UINT32
np_type = np.uint32
else:
print("unsurpport datatype ", datatype)
return
# 将解码后的数据组织为numpy数组,并设置shape和类型
return np.frombuffer(byte_array, dtype=np_type).reshape(shape)
def _output_dataset_to_numpy(self):
''' 将模型输出解码为numpy数组 '''
dataset = []
# 遍历每个输出
for i in range(self._output_num):
buffer = acl.mdl.get_dataset_buffer(self.output_dataset, i)  # 从输出dataset中获取buffer
data_ptr = acl.get_data_buffer_addr(buffer)  # 获取输出数据内存地址
size = acl.get_data_buffer_size(buffer)  # 获取输出数据字节数
narray = acl.util.ptr_to_bytes(data_ptr, size)  # 将指针转为字节流数据
# 根据模型输出的shape和数据类型,将内存数据解码为numpy数组
dims = acl.mdl.get_output_dims(self.model_desc, i)[0]["dims"]  # 获取每个输出的维度
datatype = acl.mdl.get_output_data_type(self.model_desc, i)  # 获取每个输出的数据类型
output_nparray = self._unpack_bytes_array(narray, tuple(dims), datatype)  # 解码为numpy数组
dataset.append(output_nparray)
return dataset
def execute(self, input_list):
'''创建输入dataset对象, 推理完成后, 将输出数据转换为numpy格式'''
self._gen_input_dataset(input_list)  # 创建模型输入dataset结构
ret = acl.mdl.execute(self.model_id, self.input_dataset, self.output_dataset)  # 调用离线模型的execute推理数据
out_numpy = self._output_dataset_to_numpy()  # 将推理输出的二进制数据流解码为numpy数组, 数组的shape和类型与模型输出规格一致
return out_numpy
def release(self):
''' 释放模型相关资源 '''
if self._is_released:
return
print("Model start release...")
self._release_dataset(self.input_dataset)  # 释放输入数据结构
self.input_dataset = None  # 将输入数据置空
self._release_dataset(self.output_dataset)  # 释放输出数据结构
self.output_dataset = None  # 将输出数据置空
if self.model_id:
ret = acl.mdl.unload(self.model_id)  # 卸载模型
if self.model_desc:
ret = acl.mdl.destroy_desc(self.model_desc)  # 释放模型描述信息
self._is_released = True
print("Model release source success")
def _release_dataset(self, dataset):
''' 释放 aclmdlDataset 类型数据 '''
if not dataset:
return
num = acl.mdl.get_dataset_num_buffers(dataset)  # 获取数据集包含的buffer个数
for i in range(num):
data_buf = acl.mdl.get_dataset_buffer(dataset, i)  # 获取buffer指针
if data_buf:
ret = acl.destroy_data_buffer(data_buf)  # 释放buffer
ret = acl.mdl.destroy_dataset(dataset)  # 销毁数据集
@abstractmethod
def infer(self, inputs): # 保留接口, 子类必须重写
pass
```


4. 定义yolo模型的具体推理功能，包含前处理、推理、后处理等功能。YoloV5继承自  3
中的Model，并重写其推理接口（infer函数），得到模型推理输出结果。

```
class YoloV5(Model):
def __init__(self, model_path):
super().__init__(model_path)
self.neth = 640  # 缩放的目标高度, 也即模型的输入高度
self.netw = 640  # 缩放的目标宽度, 也即模型的输入宽度
self.conf_threshold = 0.1  # 置信度阈值
def infer(self, img_bgr):
labels_dict = get_labels_from_txt('./coco_names.txt')  # 得到类别信息，返回序号与类别对应的字典
# 数据前处理
img, scale_ratio, pad_size = letterbox(img_bgr, new_shape=[640, 640])  # 对图像进行缩放与填充
img = img[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, HWC to CHW
img = np.ascontiguousarray(img, dtype=np.float32) / 255.0  # 转换为内存连续存储的数组
# 模型推理, 得到模型输出
output = self.execute([img, ])[0]
# 后处理
boxout = nms(torch.tensor(output), conf_thres=0.4, iou_thres=0.5)  # 利用非极大值抑制处理模型输出，conf_thres 为置信度阈值，iou_thres 为iou阈值
pred_all = boxout[0].numpy()  # 转换为numpy数组
scale_coords([640, 640], pred_all[:, :4], img_bgr.shape, ratio_pad=(scale_ratio, pad_size))  # 将推理结果缩放到原始图片大小
img_dw = draw_bbox(pred_all, img_bgr, (0, 255, 0), 2, labels_dict)  # 画出检测框、类别、概率
return img_dw
```


5. 定义一个acl初始化“main”函数。

```
if __name__ == '__main__':
context = init_acl(DEVICE_ID)  # 初始化acl相关资源
det_model = YoloV5(model_path=trained_model_path)  # 初始化模型
# 读入文件并推理
img = cv2.imread(image_path, cv2.IMREAD_COLOR)  # 读入图片
img_res = det_model.infer(img)  # 前处理、推理、后处理, 得到最终推理图片
cv2.imwrite('img_res.png', img_res)
# 释放相关资源
det_model.release()  # 释放 acl 模型相关资源, 包括输入数据、输出数据、模型等
deinit_acl(context, 0)  # acl 去初始化
```


#### 运行推理

进入“yolo_acl_sample/infer”目录，运行主程序“main.py”。
```
cd infer
python main.py
```


界面显示结果如下。

```
Init ACL Successfully
load model yolov5s_bs1.om
Init model resource
[Model] Model init resource stage success
[Model] create model output dataset success
[Model] create model input dataset success
start infer image: test.jpg
Model start release...
Model release source success
Deinit ACL Successfully
```

在“yolo_acl_sample/infer”目录下，保存相应的推理结果：img_res.png。

图3 推理结果图


#### 样例总结与扩展

以上代码包括以下几个步骤：

1. 初始化acl资源：在调用AscendCL相关资源时，必须先初始化AscendCL，否则可能会导致后续系统内部资源初始化出错。此样例中，包括指定计算设备、创建context等操作，再初始化了模型类，初始化时，进行了模型的加载以及输出数据集的创建。

2. 推理：读入图片，调用model.infer进行推理，其中包含数据的前处理、输入数据集结构的创建、推理、将推理结果转换为numpy、并进行后处理等操作，得到最终带有检测框的图片结果，最后将结果保存到图片。

3. 资源销毁：最后记得释放相关资源，包括卸载模型、销毁输入输出数据集、释放 Context、释放指定的计算设备、以及AscendCL去初始化等操作。

AscendCL接口分类总结：

| 分类 | 接口函数 | 描述 |
| --- | --- | --- |
| AscendCL初始化相关 | acl.init() | pyACL初始化函数 |
| AscendCL初始化相关 | acl.rt.set\_device(device\_id) | 指定当前进程或线程中用于运算的Device，同时隐式创建默认Context |
| AscendCL初始化相关 | acl.rt.create\_context(device\_id) | 在当前进程或线程中显式创建一个Context |
| 模型描述信息相关 | acl.mdl.load\_from\_file(model\_path) | 从文件加载离线模型数据（适配昇腾AI处理器的离线模型） |
| 模型描述信息相关 | acl.mdl.create\_desc() | 创建aclmdlDesc类型的数据 |
| 模型描述信息相关 | acl.mdl.get\_desc(model\_desc, model\_id) | 根据模型ID获取该模型的aclmdlDesc类型数据 |
| 模型描述信息相关 | acl.mdl.get\_num\_inputs(model\_desc) | 根据aclmdlDesc类型的数据，获取模型的输入个数 |
| 模型描述信息相关 | acl.mdl.get\_num\_outputs(model\_desc) | 根据aclmdlDesc类型的数据，获取模型的输出个数 |
| 模型描述信息相关 | acl.mdl.get\_output\_size\_by\_index(model\_desc, i) | 根据aclmdlDesc类型的数据，获取指定输出的大小，单位为Byte |
| 模型描述信息相关 | acl.mdl.get\_output\_dims(model\_desc, i) | 根据模型描述信息获取指定的模型输出tensor的维度信息 |
| 模型描述信息相关 | acl.mdl.get\_output\_data\_type(model\_desc, i) | 根据模型描述信息获取模型中指定输出的数据类型 |
| 数据集结构相关 | acl.mdl.create\_dataset() | 创建aclmdlDataset类型的数据 |
| 数据集结构相关 | acl.create\_data\_buffer(data\_addr, size) | 创建aclDataBuffer类型的数据，该数据类型用于描述内存地址、大小等内存信息 |
| 数据集结构相关 | acl.mdl.add\_dataset\_buffer(dataset, date\_buffer) | 向aclmdlDataset中增加aclDataBuffer |
| 数据集结构相关 | acl.mdl.get\_dataset\_num\_buffers(dataset) | 获取aclmdlDataset中aclDataBuffer的个数 |
| 数据集结构相关 | acl.mdl.get\_dataset\_buffer(dataset, i) | 获取aclmdlDataset中的第i个aclDataBuffer |
| 数据集结构相关 | acl.get\_data\_buffer\_addr(buffer) | 获取aclDataBuffer类型中的数据的地址对象 |
| 数据集结构相关 | acl.get\_data\_buffer\_size(buffer) | 获取aclDataBuffer类型中数据的内存大小，单位Byte |
| 数据集结构相关 | acl.util.bytes\_to\_ptr(bytes\_data) | 将bytes对象转换成为void\*数据，可以将转换好的数据传递给C函数直接使用 |
| 数据集结构相关 | acl.util.ptr\_to\_bytes(ptr, size) | 将void\*数据转换为bytes对象，可以使python代码直接访问 |
| 数据集结构相关 | acl.rt.malloc(size, policy) | 申请Device上的内存 |
| 推理相关 | acl.mdl.execute(model\_id, input\_dataset, output\_dataset) | 执行模型推理，直到返回推理结果 |
| 销毁资源相关 | acl.destroy\_data\_buffer(data\_buffer) | 销毁aclDataBuffer类型的数据 |
| 销毁资源相关 | acl.mdl.destroy\_dataset(dataset) | 销毁aclmdlDataset类型的数据 |
| 销毁资源相关 | acl.mdl.unload(model\_id) | 系统完成模型推理后，可调用该接口卸载模型，释放资源 |
| 销毁资源相关 | acl.mdl.destroy\_desc(model\_desc) | 销毁aclmdlDesc类型的数据 |
| 销毁资源相关 | acl.rt.destroy\_context(context) | 销毁一个Context，释放Context的资源 |
| 销毁资源相关 | acl.rt.reset\_device(device\_id) | 复位当前运算的Device，释放Device上的资源，包括默认Context、默认Stream以及默认Context下创建的所有Stream |
| 销毁资源相关 | acl.finalize() | pyACL去初始化函数，用于释放进程内的pyACL相关资源 |


理解各个接口含义后，用户可进行灵活运用。除此外，此样例中只示范了图片推理，若需要对视频流数据进行推理，可用三种方式输入视频流数据：USB摄像头和手机摄像头。具体使用方式可参考《摄像头拉流(https://www.hiascend.com/document/detail/zh/Atlas200IDKA2DeveloperKit/23.0.RC2/Getting Started with Application Development/cps/cps_0002.html)》，用户只需将前处理、推理及后处理代码放入摄像头推理代码的循环中即可，注意有些细节地方需进行相应修改，具体逻辑可参照图像分类应用中的样例总结与扩展。
