1 简介
距该系列上一篇文章已经挺久了,最近才有时间推进了一下。 我原意是想复现ST官方发布的视频上的那种效果,例如人体识别等,但是官方使用的disco板子实在太贵,又去看了官方提供的FP-AI-VISION包,奈何能力有限,实在无法理清楚,没办法只能拿手里的东西自己摸索着搞搞。
这个系列采取的主线如下:
- OV5640采集图片输出JPEG格式
- 保存获取的JPEG数据到SD卡
- 使用JPEG硬件解码读取SD卡的JPEG数据
- 解码得到YCbCr格式数据
- 将YCbCr格式数据转为RGB888格式
- 将RGB888格式数据处理存入AI模型的输入缓冲区
- 运行AI模型打印预测结果
其实除了这种方式外我还考虑过一种更简单的,后面头铁用前一种去做了: 1: OV5640输出RGB565 2: RGB565转RGB888 这种方式我没有试过,应该是更方便一些
2 使用的工具
从模型端向设备端方向介绍。
2.1模型搭建
这次从一个比较流行简单的识别开始,没记错应该是Kaggle上的猫狗识别(CSND上就有很多博文介绍),我这里一切从简,选了猫狗图片各3000张完成模型训练,代码在Anaconda的Spyder上完成编写,之后放到MistGPU上完成的训练(电脑没显卡没办法TOT)。
之前看到TensorFlow提供了一些预训练的模型,里面的如MobileNet等模型看介绍说是适合边缘端的模型,本来打算拿这些模型来试试,模型搭建以后参数量在200W左右,我这设备明显跑不起。最后自己手动搭建了一个CNN模型,参数量在18W左右,准确率在77%左右。下面是两种模型代码:
mobilenetv2 = tf.keras.applications.MobileNetV2(input_shape=(256,256,3),
alpha=1.0,
include_top = False,
weights = 'imagenet',
pooling = 'avg')
model = tf.keras.Sequential([
mobilenetv2,
tf.keras.layers.Dense(256,activation = 'relu'),
tf.keras.layers.Dense(64,activation = 'relu'),
tf.keras.layers.Dense(16,activation = 'relu'),
tf.keras.layers.Dense(2,activation='softmax')
])
因为是在MistGPU上训练的代码,我在写这篇记录的时候机器被占用了,只能上图片了。关于迁移学习的细节可以参考之前的文章。
搭建模型需要注意的细节如下:
- **图片的大小。**特别是使用迁移学习的话要保证图片高宽一致,这里的宽高需要使用的摄像头能够输出才行
- **图片的格式。**测试之后发现tf处理后的图片为RGB888格式,存储方式为[height,weight,channel],RGB888格式正是我需要的,需要注意的就是存储方式最好是JPEG解码为RGB888后的格式,不然就在这里改(不要放在STM32上去改存储方式,太麻烦了)。其实我没有找到JPEG硬件解码后保存的方式(最终输出数据放在一维数组中),最后是通过串口输出一张小图片解码后的数据,然后将这个数据利用python显示图像,测试出来硬件JPEG解码后的图像保存格式为[height,weight,channel]。没错,运气很好,和TensorFlow解码后的图片存储格式相同,也就是说这一步虽然很重要但可以忽略,不过为了保证万无一失最好还是测试一下。
- **TensorFlow版本。**我现在最新应该是TensorFlow2.8,但是Cube支持转换的代码版本为TensorFloaw2.6及以下
- **参数量:**我使用的是正点原子的阿波罗STM32F767IG,1M的flash和512K的RAM,参数量过30W就不能直接转换了。但Cube有提供一个split功能,应该是可以将模型分开存放到SDRAM和外扩的ROM中,本着一切从简的原则就不搞这个了。
- **数据格式:**JPEG硬件解码后格式为u8,而TensorFlow搭建的模型输入可以是u8也可以是float(最终都会转为float)。这里我也测试了一下,使用u8类型数据去训练模型,最后转换的C代码模型输入还是float,应该是TensorFlow自己内部处理了。这也意味着只能在STM32上把JPEG硬件解码的u8类型RGB888数据转为float类型了。(这里需要注意,很占内存,我使用的是图片大小为(128,128,3),这里float类型一转就要占用(128 *128 *3 *4)字节的内存,还要加上一个保存的u8类型图片的内存(加起来都快250k字节内存了),我在这里因为内存的问题报了各种错)
模型搭建部分需要注意的差不多就这么多了,第一次做尽量使用小一点的方形图片。
2.2 摄像头驱动 DCMI
正点的代码摄像头驱动逻辑如下: 首先是OV5640的初始化。需要使用SCCB总线和PCF控制DCMI_PWDN脚,主要就是管脚置位以及通过SCCB总线向OV5640寄存器发送指令或数据。而PCF的使用需要使用IIC驱动,所以还需要配置IIC。 具体的可以看正点摄像头部分的代码,这里额外介绍一下使用CuBeMx配置DCMI,将会使用DCMI获取摄像头采集的图像数据。
具体配置如下: 这里的JPEG mode可配可不配,讲道理是要使能DCMI才能接收JPEG格式,但是不使能也可以。DCMI_DMA配置如下: 需要注意的是DMCI的配置需要修改很多管脚,我这块板子修改后如下: 配置好了以后就可以直接生成代码了,生成的代码需要注意的几个点如下:
void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA2_CLK_ENABLE();
/* DMA interrupt init */
/* DMA2_Stream1_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Stream1_IRQn, 2, 3);
HAL_NVIC_EnableIRQ(DMA2_Stream1_IRQn);
}
HAL_DCMI_MspDeInit最后添加如下代码 最后注意初始化要将DMA的初始化放在DCMI之前,其他参考正点的教程就可以了。
2.3 FMC-SDRAM
顺带也介绍一下使用CuBeMx配置FMC驱动SDRAM。摄像头采集的图像数据较大,将会被保存到SDRAM中。
这里从Write recovery time 开始的最后三个参数改成2,2,2,不需要修改管脚,添加初始化序列代码就可以使用SDRAM了。
2.4 Fatfs
也介绍下CubeMx配置Fatfs,用于将SDRAM中的JPEG数据存储为.jpg格式放在SD卡中。
首先配置SDIO: 需要修改的主要就是SDIOCLK时钟分频因子,需要保证分频后SDMMC_CK(卡时钟)在合理范围,SD卡为0MHz-25MHz。需要保证48/(2+CLKDIV)在这个范围。其实这里的值不管为多少都是在范围里面的。以上公式为不旁路的时候,开启了旁路,SDMMC_CK直接等于SDMMCCLK(48MHz),这个模型超过了SD卡的范围,不能打开。 文件系统的配置就很简单了,修改CODE_PAGE和USE_LFN就行了。
配置Platform Settings,用于监测SD卡有无插上,若插上该管脚为低电平。这里随便搞一个管脚配置为输入给它就行。 最后开启CRC,放在SD卡报CRC错误,最后配置X-CUBE-AI也需要开启CRC。
需要注意的是我使用的板子SDIO和DCMI冲突,需要分时复用,然而Cube不让我配置两个冲突的功能,最后没办法单独配置了DCMI和FATFS的代码,然后在手动组合在一起。
各部分具体的代码可以参考正点的历程(摄像头和照相机部分),这一部分需要实现将OV5640采集的数据以JPEG格式保存到SD卡中。
3 小结
后续会在写一篇介绍STM32硬件JPEG解码,并将最终的RGB888数据转换格式传到转换模型的输入缓冲区中。这里不得不提一下X-CUBE-AI对模型输入的格式要求实在是很友好,对于图像这种三维数据它支持一维数组输入、二维数组输入、三维数组输入,也就意味着硬件JPEG解码后得到的一维RGB888数据不需要做任何存储方式的处理,只需要简单的处理数据格式到float即可直接输入模型。
补充:记得将模型保存下来,忘记下载模型了,不知道谁一直占着机器,搞得我无法。
贴一张预测效果:
明天要开始准备软考了,有时间在更新吧
|