4.1. AI推理示例

旭日X3拥有高达 5TOPS 的等效算力,可以在单板上运行非常丰富的AI算法。本章将用几个简单的示例程序带大家熟悉地平线Python版本AI推理引擎API、相关数据结构和使用方法。利用API通过简单的函数调用,来完成模型加载、数据及Tensor的准备、模型推理和获取模型输出等操作。

结合从本地读取图片、从USB camera获取视频数据和从MIPI camera获取视频数据这三种方式逐步介绍地平线的Python API。

本文使用到的所有测试代码及数据都已经存放在 /app/ai_inference/ 目录下,可以参照代码来阅读文档,会有更好的效果。

4.1.1. 模块导入

旭日X3派默认已经安装了hobot_dnn可以通过导入模块,查看其基本信息。

sunrise@ubuntu:~$ sudo python3
Python 3.8.10 (default, Mar 15 2022, 12:22:08) 
Type "help", "copyright", "credits" or "license" for more information.
>>> from hobot_dnn import pyeasy_dnn as dnn
>>> dir(dnn)
['Model', 'TensorProperties', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'load', 'pyDNNTensor']

hobot_dnn 模块实现以下三个类和load接口:

  • Model : AI 算法模型类,执行加载算法模型、推理计算, 更多信息请查阅 Model

  • pyDNNTensor:AI 算法输入、输出数据 tensor 类, 更多信息请查阅 pyDNNTensor

  • TensorProperties :模型输入 tensor 的属性类, 更多信息请查阅 TensorProperties

  • load:加载算法模型,更多信息请查阅 API接口

4.1.2. 图像分类算法

本示例我们要实现:

  1. 在旭日X3派上加载 mobilenetv1 图像分类模型(ImageNet预训练的1000分类模型)

  2. 读取 zebra_cls.jpg 静态图片作为模型的输入

  3. 分析算法结果,得到图片中事物的类别

4.1.2.1. 快速使用

本示例的完整代码和测试数据安装在 /app/ai_inference/01_basic_sample/ 目录下,调用以下命令运行

cd /app/ai_inference/01_basic_sample/
sudo python3 ./test_mobilenetv1.py

运行成功后,会输出图像的分类结果,如下所示:

========== Classification result ==========
cls id: 340 Confidence: 0.991851

4.1.2.2. 导入python模块

  • 导入算法推理模块hobot_dnn

  • 数据处理模块numpy和opencv模块

from hobot_dnn import pyeasy_dnn as dnn
import numpy as np
import cv2

4.1.2.3. 加载AI模型

调用 load 方法加载算法模型,并返回一个 hobot_dnn.pyeasy_dnn.Model 类的 list。

例如加载 mobilenetv1_224x224_nv12.bin 图像分类模型:

models = dnn.load('../models/mobilenetv1_224x224_nv12.bin')

在执行模型加载的同时会有当前推理库的版本信息:

[C][3282][05-01][19:11:54:852][configuration.cpp:51][EasyDNN]EasyDNN version: 0.3.5
[BPU_PLAT]BPU Platform Version(1.3.1)!
[HBRT] set log level as 0. version = 3.13.27
[DNN] Runtime version = 1.8.4_(3.13.27 HBRT)
[HorizonRT] The model builder version = 1.5.2

mobilenetv1_224x224_nv12.bin 模型的输入是一个 1 x 3 x 224 x 224NCHW 类型的tensor,输出是一个有 1000 个值的list,表示1000个类别的置信度。

定义一个函数用来输出 properties

def print_properties(pro):
    print("tensor type:", pro.tensor_type)
    print("data type:", pro.dtype)
    print("layout:", pro.layout)
    print("shape:", pro.shape)

显示算法模型的输入、输出信息:

# 打印输入 tensor 的属性
print_properties(models[0].inputs[0].properties)
# 打印输出 tensor 的属性
print_properties(models[0].outputs[0].properties)

以上代码中inputpyDNNTensor 类型的输入数据,propertiesTensorProperties 类型的数据,用于描述tensor的属性信息。

4.1.2.4. 准备输入数据

使用opencv打开图片zebra_cls.jpg,是一只斑马的图片(在ImageNet对应类别 340: ‘zebra’ ),把图片缩放到符合模型输入tensor的尺寸(244 x 224),最后把bgr格式的图像转换成符合模型输入的 NV12 格式。这一步操作一般也称为算法数据的前处理。

# 打开图片
img_file = cv2.imread('./zebra_cls.jpg')
# 把图片缩放到模型的输入尺寸
# 获取算法模型的输入tensor 的尺寸
h, w = models[0].inputs[0].properties.shape[2], models[0].inputs[0].properties.shape[3]
print("input tensor size: %d x %d" % (h, w))
des_dim = (w, h)
resized_data = cv2.resize(img_file, des_dim, interpolation=cv2.INTER_AREA)

zebra_cls

定义 bgr2nv12_opencv 函数用来完成对图像格式的转换:

# bgr格式图片转换成 NV12格式
def bgr2nv12_opencv(image):
    height, width = image.shape[0], image.shape[1]
    area = height * width
    yuv420p = cv2.cvtColor(image, cv2.COLOR_BGR2YUV_I420).reshape((area * 3 // 2,))
    y = yuv420p[:area]
    uv_planar = yuv420p[area:].reshape((2, area // 4))
    uv_packed = uv_planar.transpose((1, 0)).reshape((area // 2,))

    nv12 = np.zeros_like(yuv420p)
    nv12[:height * width] = y
    nv12[height * width:] = uv_packed
    return nv12

把bgr格式的图片转换成 NV12 格式, nv12_data 是一个numpy类型的符合 mobilenetv1_224x224_nv12 模型输入要求的数据,详细信息请参考工具链开发手册。

nv12_data = bgr2nv12_opencv(resized_data)

4.1.2.5. 模型推理

调用 Model 类的 forward 接口进行算法推理,得到算法结果,mobilenetv1_224x224_nv12 的输出是一个有 1000 个值的list,表示1000个类别的预测概率值。

outputs = models[0].forward(nv12_data)

4.1.2.6. 算法后处理

算法模型的直接输出一般都是抽象的数字信息,比如分类算法返回每个类别的概率,检测算法一般会返回大量冗余的检测框,所以我们要对这些结果做一些处理。

ImageNet的1000个类别对应的名称可以查看 imagenet1000_clsidx_to_labels.txt 文件。

print("=" * 10, "Classification result", "=" * 10)
# 从输出结果中得到值最大的那个序号,比如 zebra 就是第 340 个值,应该大于 0.99
np.argmax(outputs[0].buffer)
# 输出类别序号和预测概率值
print("cls id: %d Confidence: %f" % (np.argmax(outputs[0].buffer), outputs[0].buffer[0][np.argmax(outputs[0].buffer)]))

4.1.3. 图像目标检测算法

本示例我们要实现:

  1. 在旭日X3派上加载 fcos 图像目标检测算法模型(基于COCO数据集训练的80个类别的目标检测)

  2. 从USB摄像头读取视频图像

  3. 把检测结果渲染到图片上

  4. 把图像数据通过 hdmi 输出到显示器上,了解如何使用地平线 hobot_vio 模块的Display功能,查看 Display部分 了解更多信息。

4.1.3.1. 快速使用

请查阅 USB摄像头 了解如何快速运行本示例。

4.1.3.2. 导入python模块

  • 导入算法推理模块hobot_dnn

  • 通过hdmi向显示器输出视频的模块 hobot_vio

  • 数据处理依赖numpy和opencv模块

  • colorsys 用于在绘制检测框时候的颜色处理

from hobot_dnn import pyeasy_dnn as dnn
from hobot_vio import libsrcampy as srcampy
import numpy as np
import cv2
import colorsys

4.1.3.3. 加载AI模型

调用 load 方法加载算法模型,并返回一个 hobot_dnn.pyeasy_dnn.Model 类的 list。

models = dnn.load('../models/fcos_512x512_nv12.bin')

在执行模型加载的同时会有当前推理库的版本信息:

[C][3282][05-01][19:11:54:852][configuration.cpp:51][EasyDNN]EasyDNN version: 0.3.5
[BPU_PLAT]BPU Platform Version(1.3.1)!
[HBRT] set log level as 0. version = 3.13.27
[DNN] Runtime version = 1.8.4_(3.13.27 HBRT)
[HorizonRT] The model builder version = 1.5.2

fcos_512x512_nv12.bin 模型的输入是一个 1 x 3 x 512 x 512NCHW 类型的tensor,输出15组数据,用来表示检测到的物体检测框。

定义一个函数用来输出 properties

def print_properties(pro):
    print("tensor type:", pro.tensor_type)
    print("data type:", pro.dtype)
    print("layout:", pro.layout)
    print("shape:", pro.shape)

显示算法模型的输入、输出信息:

# 打印输入 tensor 的属性
print_properties(models[0].inputs[0].properties)
# 打印输出 tensor 的属性
print(len(models[0].outputs))
for output in models[0].outputs:
    print_properties(output.properties)

以上代码中inputpyDNNTensor 类型的输入数据,propertiesTensorProperties 类型的数据,用于描述tensor的属性信息。

4.1.3.4. 准备输入数据

使用opencv打开USB 摄像头(/dev/video8),获取实时的视频帧图像,把图像缩放到符合模型输入tensor的尺寸(512x 512), 最后把bgr格式的图像转换成符合模型输入的 NV12 格式。这一步操作一般也称为算法数据的前处理。

# 打开 usb camera: /dev/video8
cap = cv2.VideoCapture(8)
if(not cap.isOpened()):
    exit(-1)
print("Open usb camera successfully")
# 设置usb camera的输出图像格式为 MJPEG, 分辨率 1920 x 1080
codec = cv2.VideoWriter_fourcc( 'M', 'J', 'P', 'G'  )
cap.set(cv2.CAP_PROP_FOURCC, codec)
cap.set(cv2.CAP_PROP_FPS, 30)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)

定义 bgr2nv12_opencv 函数用来完成对图像格式的转换:

# bgr格式图片转换成 NV12格式
def bgr2nv12_opencv(image):
    height, width = image.shape[0], image.shape[1]
    area = height * width
    yuv420p = cv2.cvtColor(image, cv2.COLOR_BGR2YUV_I420).reshape((area * 3 // 2,))
    y = yuv420p[:area]
    uv_planar = yuv420p[area:].reshape((2, area // 4))
    uv_packed = uv_planar.transpose((1, 0)).reshape((area // 2,))

    nv12 = np.zeros_like(yuv420p)
    nv12[:height * width] = y
    nv12[height * width:] = uv_packed
    return nv12

把bgr格式的图片转换成 NV12 格式, nv12_data 是一个numpy类型的符合 fcos_512x512_nv12 模型输入要求的数据,详细信息请参考工具链开发手册。

nv12_data = bgr2nv12_opencv(resized_data)

4.1.3.5. 模型推理

调用 Model 类的 forward 接口进行算法推理,得到算法结果,fcos_512x512_nv12 输出15组数据,用来表示检测到的物体检测框。

outputs = models[0].forward(nv12_data)

4.1.3.6. 算法后处理

算法模型的直接输出一般都是抽象的数字信息,比如分类算法返回每个类别的概率,检测算法一般会返回大量冗余的检测框,所以我们要对这些结果做一些处理。

fcos算法返回物件类别、检测框和每个检测框的置信度信息,总共包含COCO数据集的80个类别。

# 对算法结果进行过滤,去掉执行度低的检测框,计算检测框的交并比去除冗余框,把检测框的坐标还原到原图位置上
prediction_bbox = postprocess(outputs, input_shape, origin_img_shape=(1080,1920))

4.1.3.7. 检测结果可视化

到此我们已经完成了数据的采集,算法运算和算法结果的后处理,但是现在的结果很不直观,不方便观察,所以我们接下来把物体检测框绘制到图像上,在把绘制后的图像输出到显示器上,就可以直观的观察算法的运行效果了。其中用到了地平线的 hobot_vio模块的 Display功能,更多关于该模块的信息请查看 Display部分

# 获取 Display 实例
disp = srcampy.Display()
# 初始化视频输出通道0, 输出分辨率 1920 x 1080
disp.display(0, 1920, 1080)

# 把算法运行后得到的物体检测框绘制到图像上
box_bgr = draw_bboxs(frame, prediction_bbox)

# X3 的HDMI输出模块的输入图像格式需要是NV12的,所以需要先把bgr格式转成NV12
box_nv12 = bgr2nv12_opencv(box_bgr)
# 把 NV12 格式的图像输出给显示器
disp.set_img(box_nv12.tobytes())

4.1.4. 基于MIPI Camera的目标检测

本示例我们要实现:

  1. 在旭日X3派上加载 fcos 图像目标检测算法模型(基于COCO数据集训练的80个类别的目标检测)

  2. 从MIPI摄像头(F37)读取视频图像,了解如何使用地平线 hobot_vio 模块的Camera功能,查看 Camera部分 了解更多信息。

  3. 把检测结果渲染到图片上

  4. 把图像数据通过 hdmi 输出到显示器上,了解如何使用地平线 hobot_vio 模块的Display功能,查看 Display部分 了解更多信息。

4.1.4.1. 快速使用

请查阅 MIPI摄像头 了解如何快速运行本示例。

4.1.4.2. 导入python模块

  • 导入算法推理模块hobot_dnn

  • 通过hdmi向显示器输出视频的模块 hobot_vio

  • 数据处理依赖numpy和opencv模块

  • colorsys 用于在绘制检测框时候的颜色处理

import numpy as np
import cv2
import colorsys

from hobot_dnn import pyeasy_dnn as dnn
from hobot_vio import libsrcampy as srcampy

4.1.4.3. 加载AI模型

调用 load 方法加载算法模型,并返回一个 hobot_dnn.pyeasy_dnn.Model 类的 list。

models = dnn.load('../models/fcos_512x512_nv12.bin')

在执行模型加载的同时会有当前推理库的版本信息:

[C][3282][05-01][19:11:54:852][configuration.cpp:51][EasyDNN]EasyDNN version: 0.3.5
[BPU_PLAT]BPU Platform Version(1.3.1)!
[HBRT] set log level as 0. version = 3.13.27
[DNN] Runtime version = 1.8.4_(3.13.27 HBRT)
[HorizonRT] The model builder version = 1.5.2

fcos_512x512_nv12.bin 模型的输入是一个 1 x 3 x 512 x 512NCHW 类型的tensor,输出15组数据,用来表示检测到的物体检测框。

定义一个函数用来输出 properties

def print_properties(pro):
    print("tensor type:", pro.tensor_type)
    print("data type:", pro.dtype)
    print("layout:", pro.layout)
    print("shape:", pro.shape)

显示算法模型的输入、输出信息:

# 打印输入 tensor 的属性
print_properties(models[0].inputs[0].properties)
# 打印输出 tensor 的属性
print(len(models[0].outputs))
for output in models[0].outputs:
    print_properties(output.properties)

以上代码中inputpyDNNTensor 类型的输入数据,propertiesTensorProperties 类型的数据,用于描述tensor的属性信息。

4.1.4.4. 准备输入数据

通过调用srcampy.Camera类的get_cam方法获取MIPI摄像头实时的视频帧图像,把图像缩放到符合模型输入tensor的尺寸(512x 512), 最后把bgr格式的图像转换成符合模型输入的 NV12 格式。这一步操作一般也称为算法数据的前处理。查看 Camera部分 了解更多信息。

# 获取 Camera 句柄
cam = srcampy.Camera()
# 打开 f37 摄像头,并且把输出突出缩小成算法模型的输入尺寸
# For the meaning of parameters, please refer to the relevant documents of camera
h, w = get_hw(models[0].inputs[0].properties)
# 打开 F37, 初始化视频 pipeline 0 ,设置帧率30fps,缩放图像为 512 x 512
cam.open_cam(0, 1, 30, w, h)
# 从相机获取分辨率为 512x512 的nv12格式的图像数据, 参数 2 代表从硬件模块IPU中获取
img = cam.get_img(2, 512, 512)
# 把图像数据转成 numpy 数据类型
img = np.frombuffer(img, dtype=np.uint8)

4.1.4.5. 模型推理

调用 Model 类的 forward 接口进行算法推理,得到算法结果,fcos_512x512_nv12 输出15组数据,用来表示检测到的物体检测框。

outputs = models[0].forward(img)

4.1.4.6. 算法后处理

算法模型的直接输出一般都是抽象的数字信息,比如分类算法返回每个类别的概率,检测算法一般会返回大量冗余的检测框,所以我们要对这些结果做一些处理。

fcos算法返回物件类别、检测框和每个检测框的置信度信息,总共包含COCO数据集的80个类别。

# 对算法结果进行过滤,去掉执行度低的检测框,计算检测框的交并比去除冗余框,把检测框的坐标还原到原图位置上
prediction_bbox = postprocess(outputs, input_shape, origin_img_shape=(1080,1920))

4.1.4.7. 检测结果可视化

到此我们已经完成了数据的采集,算法运算和算法结果的后处理,但是现在的结果很不直观,不方便观察,所以我们接下来把物体检测框绘制到图像上,在把绘制后的图像输出到显示器上,就可以直观的观察算法的运行效果了。

# 从新获取一张图像,大小缩放成与显示器的分辨率一样的 1920 x 1080, 并且转换成 bgr格式,方便进行绘图操作
origin_image = cam.get_img(2, 1920, 1080)
origin_nv12 = np.frombuffer(origin_image, dtype=np.uint8).reshape(1620, 1920)
origin_bgr = cv2.cvtColor(origin_nv12, cv2.COLOR_YUV420SP2BGR)

# 把算法运行后得到的物体检测框绘制到图像上
box_bgr = draw_bboxs(origin_bgr, prediction_bbox)

# X3 的HDMI输出模块的输入图像格式需要是NV12的,所以需要先把bgr格式转成NV12
box_nv12 = bgr2nv12_opencv(box_bgr)
# 把 NV12 格式的图像输出给显示器
disp.set_img(box_nv12.tobytes())