AI视觉组仙人一步之高级玩法——从Python回归C语言
开心的程序猿@NXP
本文专为参加今年大学生智能车竞赛AI视觉组的同学们而写,也很适合其他对MCU上AI应用感兴趣的朋友。
读过之前两篇的童鞋们,想来已经开始着手开发属于自己的AI视觉应用了,当然,手中还没有OpenART套件的朋友们,也不用着急,可以先参照'智能车大赛AI视觉组参考答案'在PC上先试训一下AI模型,然后结合AI视觉组仙人一步之模型量化,熟悉一下模型部署流程。
在智能车大赛AI视觉组参考答案推文中,小编介绍了如何借助OpenART所提供的python接口,在openMV IDE中编写脚本以运行AI模型。Python以其简单易用的特点,可以说极大的减轻了我们的开发负荷。
但是,想必高级玩家已经不满足止步于只使用python了吧。
了解我们的OpenART套件的童鞋们肯定知道,套件本身是集成了RT-Thread这款RTOS的,何不尝试点新玩法?挑战点高难度,尝试下用C来直接写code,在小伙伴面前炫耀一下?
也正式因为有了RTOS的加持,每一个任务本身在RTOS中都作为一个独立线程存在,想要为项目添加新功能的话,只需要为其创建一个新的线程即可,无需更改已有裸机代码。
本文的目的就是展示给大家,如何以线程的方式在工程中添加一个新的功能,以实现对于tflite micro推理引擎的直接调用。
那么,话不多说,直接开整。
首先介绍一下如何在OpenART中创建一个新的线程(OpenART中集成了RT-Thread操作系统,因此如下操作都是以RT-Thread为基础的):
下面开始编写线程主函数,主要包括以下内容:
一、初始化摄像头:
因为我们需要通过摄像头读取指令,由于我们已经不再使用python脚本,摄像头初始化部分需要手动进行,到了这里,是不是要劝退一些小伙伴了呢?别急,还有更厉害的在后面,感受C语言的魅力吧!
初始化流程和python调用流程类似,只不过我们这次要直接进行函数调用,来实现摄像头模块的初始化,这里要感谢RT-Thread所提供的设备管理框架,能够让我们能够方便的实现这一功能:
二、读取图像:
大家可能注意到了这个fb_alloc,可能有同学会问,这个不是Micropython中的吗?这里还能用吗,当然,大家可以把它理解成一个内存管理器,是可以用C来直接进行函数调用的:
这个image_t结构体,实际上它包含了图像的所有信息,就可以直接进行处理了。
三、模型推理:
至此,代码中所涉及到的图像采集部分就到此为止了,下面开始介绍模型推理部分。
不过,要提前说明的是,在这里我们没有保留python API所实现的图像输入部分,即可以通过设置窗口滑动步长和窗口放缩比,来实现多尺度下的图像识别,我们假设要识别的物体充满整个屏幕,即识别区域为整副摄像头采集的图像,而这就丧失了多目标识别能力,有兴趣的玩家可以自行实现。
那么如何利用tflite micro进行模型推断呢?
⊙ 首先第一步是读取模型,model_data就是以二进制方式读取的tflite模型,在OpenART中,我们采用DFS文件系统来进行模型文件的读取,full_path根据:
⊙ 之后是实例化操作解析器和模型解释器,其中,解析器用来访问Tensorflow的操作,可以扩展此类以向项目中添加自定义操作。解释器是用来对模型进行结构上的解析,以进行模型求解。
⊙ 运算之前,我们需要预先为输入、输出以及中间数组分配一定的内存。因此,用户需要预分配一个大小为tensor_arena_size的uint8_t的数组,传递给模型解析器。
⊙ 随后还需要调用函数对预分配的tensor arena进行分配:
⊙ 至此,万事俱备,只欠东风了,而我们的东风就是待识别的图像,tflite模型的输入,是通过对input tensor进行赋值实现的,实现方法也很简单,我们只需要将第二步读取到的图像数据,按照模型输入的要求进行处理后,直接对其赋值即可:
这里我们假设image是已经按照模型出入要求处理好的图像数据,size是其大小,实际情况下,可以随机应变,宗旨就是要对model_input->data.data这个变量进行遍历赋值。
让我们的模型解释器开始干活,还需要最后一步:
⊙ 最后,怎样获取结果呢?和设置input tensor大同小异,我们这次要获取output tensor, 需要注意的是这里的model_output->data.data是个void*类型,需要根据model_output->type进行指针的强制类型转化:
至此,运行tflite模型的所有所需代码均已介绍完毕,当然,为了方便使用,小编已经为大家亲手烹制了对应的库,已经集成了这些函数,可以直接调用。
⊙ 这里面比较重要的是两个callback函数,要注意的是,这两个函数会在函数内部自动调用,用户需要传入input_callback_data和output_callback_data,下面进行一一说明。
首先是input_callback,其定义如下,用户需要自行实现其函数体,其作用是,根据传入的input_height,input_width, input_channels对采集的图像进行放缩处理,is_float表示数据是否转换为浮点数表示,最后对model_input进行赋值:
与其对应的是output_callback, 其定义如下,其作用是,根据传入的output_height,output_width, output_channels对采集的图像进行放缩处理,is_float表示结果是否为浮点数表示,最后将model_ouput的值赋值到callback_data中:
四、导出:
最后一步是导出到msh命令行,这样才可以在系统启动后,在RT-Thread的命令窗口中进行调用,这里我们通过EXPORT_MSH_CMD()这个特殊的宏进行命令的声明:
这里包含两部分的内容,以逗号进行分割,第一部分既代表命令行中所对应的命令,又代表当输入tflite_micro这一命令时所调用的函数名,没错,我们就是要把我们创建tflite线程的函数tflite_micro_main放到这里面,这样,我们就导出了一个叫做tflite_micro的命令,当我们运行这一命令时,所创建的tflite micro线程就会被调用。第二部分,是对于指令的描述。
至此,我们的菜就全部备齐了,这里略过1万字的代码调试以及下载过程,在出现的msh命令行窗口中输入tflite_micro,将待识别的图像对准摄像头,即可进行物体的识别:
msh /> tflite_micro
运行结果:
不过,由于我们是直接以线程的形式调用tflite micro的推理引擎,因此没有办法在使用openmv ide进行图像的预览了,不过,如果拥有一块LCD的小伙伴,就可以不用慌张了,可以显示在LCD屏幕上一睹芳容。
本期,小编为大家带来了如何在底层以新建线程的方式直接调用tflite micro推理引擎,参考代码位于https://gitee.com/crist_xu/tflite_thread_call.git,不过,代码并不包含OpenART代码包,同学们可以参考下实现,等到OpenART软件包正式和大家见面之后再进行动手。