AI Demo说明文档
1. AI Demo开发框架介绍
1.1. AI Demo开发框架
为了帮助用户简化AI部分的开发,基于K230_CanMV提供的API接口,搭建了配套的AI 开发框架。框架结构如下图所示:

Camera默认出两路图像,一路 格式为YUV420,直接给到Display显示;另一路格式为RGB888,给到AI部分进行处理。AI主要实现任务的前处理、推理和后处理流程,得到后处理结果后将其绘制在osd image实例上,并送给Display叠加显示。
1.2. 接口介绍
1.2.1. PipeLine
我们将Media部分的代码封装在PipeLine类型中,通过固定的接口实现整个流程操作。
其中PipeLine类提供的接口包括:
-
初始化参数包括:
(1)rgb888p_size:list类型,预设给到AI部分的图像分辨率;如rgb888p_size=[1920,1080]。
(2)display_size:list类型,显示部分Display的分辨率;如display_size=[1920,1080]。
(3)display_mode:str类型,显示模式,包括”hdmi“和”lcd“;如display_mode=”hdmi“。
(4)debug_mode:int类型,耗时调试模式,如果大于0,打印操作耗时;如debug_mode=0。
-
create(sensor=None,hmirror=None,vfilp=None):
(1)sensor:参数为可选参数,类型为Sensor对象,可自主配置现有CanMV、01Studio和k230d zero开发板实现了自动探测,可以默认使用create()实现。
(2)hmirror:默认为None,当主动设置时为bool类型(True/False),表示是否实现水平方向镜像显示。
(3)vflip: 默认为None,当主动设置时为bool类型(True/False),表示是否实现垂直方向翻转。
-
get_frame():返回一帧ulab.numpy.ndarray类型图像数据,分辨率为rgb888p_size,排布为CHW。
-
show_image():PipeLine实例中预设一帧OSD图像,该接口将成员变量osd_img显示在屏幕上。
-
destroy():销毁PipeLine实例。
下面给出无AI部分的示例代码:
from libs.PipeLine import PipeLine, ScopedTiming
from media.media import *
import gc
import sys,os
if __name__ == "__main__":
# 显示模式,默认"hdmi",可以选择"hdmi"和"lcd"
display_mode="hdmi"
if display_mode=="hdmi":
display_size=[1920,1080]
else:
display_size=[800,480]
# 初始化PipeLine,用于图像处理流程
pl = PipeLine(rgb888p_size=[1920,1080], display_size=display_size, display_mode=display_mode)
pl.create() # 创建PipeLine实例
try:
while True:
os.exitpoint() # 检查是否有退出信号
with ScopedTiming("total",1):
img = pl.get_frame() # 获取当前帧数据
print(img.shape)
gc.collect() # 垃圾回收
except Exception as e:
sys.print_exception(e) # 打印异常信息
finally:
pl.destroy() # 销毁PipeLine实例
上述代码中,通过pl.get_frame()接口获取一帧分辨率为rgb888p_size的图像,类型为ulab.numpy.ndarray,排布为CHW。基于上面的代码得到了一帧图像给AI处理,您可以只关注AI推理部分的操作。
图像AI开发过程包括:图像预处理、模型推理、输出后处理的过程,我们将整个过程封装在Ai2d类和AIBase类中。
1.2.2. Ai2d
对于Ai2d类,我们给出了常见的几种预处理方法,包括crop/shift/pad/resize/affine。该类别提供的接口包括:
- 初始化参数包括:
(1)debug_mode:int类型,耗时调试模式,如果大于0,打印操作耗时;如debug_mode=0。
- set_ai2d_dtype(input_format,output_format,input_type,output_type)
(1)input_format:ai2d预处理输入格式。
(2)output_format:ai2d预处理输出格式。
输入输出格式支持如下所示:
enum class ai2d_format
{
YUV420_NV12 = 0,
YUV420_NV21 = 1,
YUV420_I420 = 2,
NCHW_FMT = 3,
RGB_packed = 4,
RAW16 = 5,
}
| 输入格式 | 输出格式 | 备注 |
|---|---|---|
| YUV420_NV12 | RGB_planar/YUV420_NV12 | |
| YUV420_NV21 | RGB_planar/YUV420_NV21 | |
| YUV420_I420 | RGB_planar/YUV420_I420 | |
| YUV400 | YUV400 | |
| NCHW(RGB_planar) | NCHW(RGB_planar) | |
| RGB_packed | RGB_planar/RGB_packed | |
| RAW16 | RAW16/8 | 深度图,执行shift操作 |
(3)input_type:输入数据类型。
(4)output_type:输出数据类型。
下面是接口调用示例:
from libs.AI2D import Ai2d
import nncase_runtime as nn
my_ai2d=Ai2d(debug_mode=1)
my_ai2d.set_ai2d_type(nn.ai2d_format.NCHW_FMT, nn.ai2d_format.NCHW_FMT, np.uint8, np.uint8)
my_ai2d.set_ai2d_type(nn.ai2d_format.RGB_packed, nn.ai2d_format.NCHW_FMT, np.uint8, np.uint8)
-
crop(start_x,start_y,width,height):预处理crop函数。
(1)start_x:宽度方向的起始像素,int类型;
(2)start_y: 高度方向的起始像素,int类型;
(3)width: 宽度方向的crop长度,int类型;
(4)height: 高度方向的crop长度,int类型;
my_ai2d.crop(0,0,200,300)
- shift(shift_val):预处理shift函数。
(1)shift_val:右移的比特数,int类型;
my_ai2d.shift(2)
- pad(paddings,pad_mode,pad_val):预处理padding函数。
(1)paddings:list类型,各维度两侧padding的大小,对于4维的图像(NCHW),该参数包含8个值,分别表示N/C/H/W四个维度两侧的padding大小,一般只在后两个维度做padding;
(2)pad_mode:只支持constant padding,直接设为0;
(3)pad_val:list类型,每个像素位置填充的值,比如[114,114,114]、[0,0,0]
my_ai2d.pad([0,0,0,0,5,5,15,15],0,[114,114,114])
- resize(interp_method,interp_mode):预处理resize函数。
(1)interp_method:resize插值方法,ai2d_interp_method类型,包括:nn.interp_method.tf_nearest、nn.interp_method.tf_bilinear、nn.interp_method.cv2_nearest、nn.interp_method.cv2_bilinear;
(2)interp_mode:resize模式,ai2d_interp_mode类型,包括:nn.interp_mode.none、nn.interp_mode.align_corner、nn.interp_mode.half_pixel;
my_ai2d.resize(nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel)
- affine(interp_method,crop_round,bound_ind,bound_val,bound_smooth,M):预处理affine函数。
(1)interp_method:Affine采用的插值方法,ai2d_interp_method类型,包括:nn.interp_method.tf_nearest、nn.interp_method.tf_bilinear、nn.interp_method.cv2_nearest、nn.interp_method.cv2_bilinear;
(2)cord_round:整数边界0或者1,uint32_t类型;
(3)bound_ind:边界像素模式0或者1,uint32_t类型;
(4)bound_val:边界填充值,uint32_t类型;
(5)bound_smooth:边界平滑0或者1,uint32_t类型;
(6)M:仿射变换矩阵对应的vector,仿射变换为Y=[a_0, a_1; a_2, a_3] \cdot X + [b_0, b_1] $, 则 M=[a_0,a_1,b_0,a_2,a_3,b_1 ],list类型。
affine_matrix=[0.2159457, -0.031286, -59.5312, 0.031286, 0.2159457, -35.30719]
my_ai2d.affine(nn.interp_method.cv2_bilinear,0, 0, 127, 1,affine_matrix)
- build(ai2d_input_shape,ai2d_output_shape):ai2d构造函数,前面配置的预处理方法起作用。
(1)ai2d_input_shape:ai2d输入shape,list类型;
(2)ai2d_output_shape:ai2d输出shape,list类型;
my_ai2d.build([1,3,224,224],[1,3,512,512])
- run(input_np):调用配置好的ai2d进行预处理的函数,返回一个tensor类型数据,可以直接给模型使用,也可以通过to_numpy()转换成ulab.numpy.ndarray类型的数据。
(1)input_np:ulab.numpy.ndarray类型,ai2d预处理的输入数据,shape和build函数中设置的ai2d_input_shape一致。
注意:
(1) Affine和Resize功能是互斥的,不能同时开启; (2) Shift功能的输入格式只能是Raw16; (3) Pad value是按通道配置的,对应的list元素个数要与channel数相等; (4) 当配置了多个功能时,执行顺序是Crop->Shift->Resize/Affine->Pad, 配置参数时注意要匹配;如果不符合该顺序,需要初始化多个Ai2d实例实现预处理过程;
下面是一个完整的示例:
from libs.PipeLine import PipeLine, ScopedTiming
from libs.AI2D import Ai2d
from media.media import *
import nncase_runtime as nn
import gc
import sys,os
if __name__ == "__main__":
# 显示模式,默认"hdmi",可以选择"hdmi"和"lcd"
display_mode="hdmi"
if display_mode=="hdmi":
display_size=[1920,1080]
else:
display_size=[800,480]
# 初始化PipeLine,用于图像处理流程
pl = PipeLine(rgb888p_size=[512,512], display_size=display_size, display_mode=display_mode)
pl.create() # 创建PipeLine实例
my_ai2d=Ai2d(debug_mode=0) #初始化Ai2d实例
# 配置resize预处理方法
my_ai2d.resize(nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel)
# 构建预处理过程
my_ai2d.build([1,3,512,512],[1,3,640,640])
try:
while True:
os.exitpoint() # 检查是否有退出信号
with ScopedTiming("total",1):
img = pl.get_frame() # 获取当前帧数据
print(img.shape) # 原图shape为[1,3,512,512]
ai2d_output_tensor=my_ai2d.run(img) # 执行resize预处理
ai2d_output_np=ai2d_output_tensor.to_numpy() # 类型转换
print(ai2d_output_np.shape) # 预处理后的shape为[1,3,640,640]
gc.collect() # 垃圾回收
except Exception as e:
sys.print_exception(e) # 打印异常信息
finally:
pl.destroy() # 销毁PipeLine实例
1.2.3. AIBase
AIBase部分封装了实现模型推理的主要接口,也是进行AI开发主要关注的部分。用户需要按照自己demo的要求实现前处理和后处理部分。
AIBase提供的接口包括:
- 初始化参数包括:
(1)kmodel_path:str类型,kmodel路径,用于初始化kpu对象并加载kmodel;
(2)model_input_size:list类型,可选,模型输入分辨率,在单输入时起作用,格式为[width,height],如:model_input_size=[512,512];
(3)rgb888p_size:list类型,可选,AI得到的图像的分辨率,在单输入时起作用,格式为[width,height],如:rgb888p_size=[640,640];
(4)debug_mode:int类型,耗时调试模式,如果大于0,打印操作耗时;如debug_mode=0。
- get_kmodel_inputs_num():返回当前模型的输入个数;
- get_kmodel_outputs_num():返回当前模型的输出个数;
- preprocess(input_np):使用ai2d对input_np做预处理,如果不使用单个ai2d实例做预处理,需要在子类重写该函数。
(1)input_np:ulab.numpy.ndarray类型,ai2d预处理输入数据;
(2)返回tensor列表;如果该方法重写,请注意返回类型:tensor类型的列表;
- inference(tensors):对预处理后得到的kmodel的输入(类型为tensor)进行推理,得到多个输出(类型为ulab.numpy.ndarray);
(1)tensors:列表类型,模型的输入,可以是一个可以是多个;
(2)返回ulab.numpy.ndarray类型的列表;
Tips:
Image对象转ulab.numpy.ndarray:
import image
img.to_rgb888().to_numpy_ref() #返回的array是HWC排布ulab.numpy.ndarray转Image对象:
import ulab.numpy as np
import image
img_np = np.zeros((height,width,4),dtype=np.uint8)
img = image.Image(width, height, image.ARGB8888, alloc=image.ALLOC_REF,data =img_np)ulab.numpy.ndarray转tensor类型:
import ulab.numpy as np
import nncase_runtime as nn
img_np = np.zeros((height,width,4),dtype=np.uint8)
tensor = nn.from_numpy(img_np)tensor 类型转ulab.numpy.ndarray:
import ulab.numpy as np
import nncase_runtime as nn
img_np=tensor.to_numpy()
- postprocess(results):模型输出后处理函数,该函数需要用户在任务子类重写,因为不同AI任务的后处理是不同的。
(1)results:list类型,list元素是ulab.numpy.ndarray类型,模型的推理输出。
- run(input_np):模型的前处理、推理、后处理流程,适用于单ai2d实例能解决的前处理的AI任务,其他任务需要用户在子类重写。
(1)input_np:ulab.numpy.ndarray类型,ai2d预处理输入数据;该数据通过ai2d预处理输出1个tensor,tensor通过模型推理得到输出列表results,results经过后处理过程得到AI结果。
- deinit():AIBase销毁函数。
1.2.4. ScopedTiming
ScopedTiming 类在PipeLine.py模块内,是一个用来测量代码块执行时间的上下文管理器。上下文管理器通过定义包含 __enter__ 和 __exit__ 方法的类来创建。当在 with 语句中使用该类的实例时,__enter__ 在进入 with 块时被调用,__exit__ 在离开时被调用。
from libs.PipeLine import ScopedTiming
def test_time():
with ScopedTiming("test",1):
#####代码#####
# ...
##############