目标检测应用样例开发介绍(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. 获取代码文件。

    单击获取链接或使用wget命令,下载代码文件压缩包,以root用户登录开发者套件。

    wget https://ascend-devkit-tool.obs.cn-south-1.myhuaweicloud.com/models/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初始化函数

acl.rt.set_device(device_id)

指定当前进程或线程中用于运算的Device,同时隐式创建默认Context

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摄像头和手机摄像头。具体使用方式可参考《摄像头拉流》,用户只需将前处理、推理及后处理代码放入摄像头推理代码的循环中即可,注意有些细节地方需进行相应修改,具体逻辑可参照图像分类应用中的样例总结与扩展。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值