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. 图像分类算法
本示例我们要实现:
在旭日X3派上加载
mobilenetv1
图像分类模型(ImageNet预训练的1000分类模型)读取
zebra_cls.jpg
静态图片作为模型的输入分析算法结果,得到图片中事物的类别
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 224
的 NCHW
类型的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)
以上代码中input
是 pyDNNTensor 类型的输入数据,properties
是TensorProperties 类型的数据,用于描述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)
定义 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. 图像目标检测算法
本示例我们要实现:
在旭日X3派上加载
fcos
图像目标检测算法模型(基于COCO数据集训练的80个类别的目标检测)从USB摄像头读取视频图像
把检测结果渲染到图片上
把图像数据通过 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 512
的 NCHW
类型的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)
以上代码中input
是 pyDNNTensor 类型的输入数据,properties
是TensorProperties 类型的数据,用于描述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的目标检测
本示例我们要实现:
在旭日X3派上加载
fcos
图像目标检测算法模型(基于COCO数据集训练的80个类别的目标检测)从MIPI摄像头(F37)读取视频图像,了解如何使用地平线 hobot_vio 模块的Camera功能,查看 Camera部分 了解更多信息。
把检测结果渲染到图片上
把图像数据通过 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 512
的 NCHW
类型的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)
以上代码中input
是 pyDNNTensor 类型的输入数据,properties
是TensorProperties 类型的数据,用于描述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())