上一节介绍了如何使用搭建好的卷积和池化模块实现对SD卡中存储的手写数字的图像进行识别,这一节将介绍如何对摄像头采集到的图像数据进行手写数字的识别。
一、HLS图像处理部分
卷积和池化模块的输入是28*28的单通道灰度图,而ov5640摄像头采集到的是1024*768的三通道彩色图。这里首先对摄像头采集到的画面进行裁剪,考虑到实际手写数字在采集图像中的位置,截取位于图像中心112*112大小的图片;接着对图像进行灰度化和二值化的操作,使得数据格式与训练输入相符合;再对图片大小进行缩放,将112*112大小的灰度图缩放到28*28大小。
void doKernel(AXI_STREAM & inStream,AXI_STREAM & outStream,unsigned char result[],int num)
{
#pragma HLS INTERFACE m_axi depth=4294967295 port=result offset=slave
#pragma HLS INTERFACE s_axilite port=return bundle=CTRL_BUS
#pragma HLS INTERFACE axis register both port=outStream
#pragma HLS INTERFACE axis register both port=inStream
#pragma HLS INTERFACE s_axilite port=num
unsigned char tmp,cnt;
unsigned char pix;
unsigned char valOut_m=255;
unsigned char valOut_z=0;
RGB_image image1(4*MINST_WIDTH,4*MINST_WIDTH);
Resize_image image2(MINST_WIDTH,MINST_WIDTH);
Gray_image gray(MINST_WIDTH,MINST_WIDTH);
uint_side_channel dataOutChannel;
uint_side_channel currentChannel;
for(int i = 0; i < (IMG_HEIGHT); i++ )
{
for(int j = 0; j < (IMG_WIDTH); j++)
{
#pragma HLS PIPELINE
currentChannel = inStream.read();
hls::Scalar<3, unsigned char> pixel_val;
if((i==(IMG_HEIGHT/2-2*MINST_WIDTH-1) || i==(IMG_HEIGHT/2+2*MINST_WIDTH)) && (j>=(IMG_WIDTH/2-2*MINST_WIDTH-1) && j<=(IMG_WIDTH/2+2*MINST_WIDTH)))
{
dataOutChannel.data = (valOut_m << 16) | (valOut_z << 8) | valOut_z;
}
else if((i>=(IMG_HEIGHT/2-2*MINST_WIDTH-1) && i<=(IMG_HEIGHT/2+2*MINST_WIDTH)) && (j==(IMG_WIDTH/2-2*MINST_WIDTH-1) || j==(IMG_WIDTH/2+2*MINST_WIDTH)))
{
dataOutChannel.data = (valOut_m << 16) | (valOut_z << 8) | valOut_z;
}
else
{
dataOutChannel.data = currentChannel.data;
}
if((i>=(IMG_HEIGHT/2-2*MINST_WIDTH) && i<=(IMG_HEIGHT/2+2*MINST_WIDTH-1)) && (j>=(IMG_WIDTH/2-2*MINST_WIDTH) && j<=(IMG_WIDTH/2+2*MINST_WIDTH-1)))
{
unsigned char R = currentChannel.data & 0xFF;
unsigned char G = (currentChannel.data >> 8) & 0xFF;
unsigned char B = (currentChannel.data >> 16) & 0xFF;
pix = (R*76 + G*150 + B*30) >> 8;
if(pix<16)
pix=0;
else if(pix<128)
pix=pix>>3;
else
pix=255;
dataOutChannel.data = (pix << 16) | (pix << 8) | pix;
pixel_val.val[0] = pix;
pixel_val.val[1] = pix;
pixel_val.val[2] = pix;
image1.write(pixel_val);
}
if(num<10){
tmp = number[num][i][j/8];
if(i<128 && (j-959)>0){
if(tmp!=0){
for(cnt=0;cnt<j%8;cnt++)
tmp = tmp/2;
if(tmp%2==1){
dataOutChannel.data = (valOut_m << 16) | (valOut_z << 8) | valOut_z;
}
}
}
}
dataOutChannel.dest = currentChannel.dest;
dataOutChannel.id = currentChannel.id;
dataOutChannel.keep = currentChannel.keep;
dataOutChannel.strb = currentChannel.strb;
dataOutChannel.user = currentChannel.user;
dataOutChannel.last = currentChannel.last;
outStream.write(dataOutChannel);
}
}
#pragma HLS dataflow
hls::Resize(image1,image2,1);
hls::CvtColor<HLS_BGR2GRAY>(image2,gray);
hls::Mat2Array<MINST_WIDTH>(gray,result);
}
除了图像处理之外,这里还将识别出来的数字显示在屏幕上。同时为了提取图像处理后的结果,将result数组配置为m_axi类型。
二、Vivado系统搭建
这里将图像处理模块的输入连接摄像头模块的输出,图像处理模块的输出连接HDMI显示的输入。
三、Visit SDK设计
SDK的设计先启动初始化摄像头、图像处理模块以及卷积池化模块,接着初始化SD卡,读取卷积池化层的权值参数,最后将图像采集的结果输入到卷积池化模块输出结果。
#include <stdio.h>
#include "platform.h"
#include <stdlib.h>
#include <string.h>
#include "xil_types.h"
#include "xil_cache.h"
#include "xparameters.h"
#include "xaxivdma.h"
#include "xaxivdma_i.h"
#include "vdma_api/vdma_api.h"
#include "emio_sccb_cfg/emio_sccb_cfg.h"
#include "ov5640/ov5640_init.h"
#include "xdokernel.h"
#include "xil_printf.h"
#include "xil_cache.h"
#include "Convolution.h"
#include "Pool.h"
#include "sd.h"
#define HLS_VDMA_DEV_ID XPAR_AXI_VDMA_0_DEVICE_ID
#define DISP_VDMA_DEV_ID XPAR_AXI_VDMA_1_DEVICE_ID
#define DOKERNEL_DEV_ID XPAR_DOKERNEL_0_DEVICE_ID
#define HLS_BASE_ADDR 0x08000000
#define DISP_BASE_ADDR 0x03000000
#define SCREEN_X 1024
#define SCREEN_Y 768
static XAxiVdma vdma;
static XDokernel dokernel;
static XDokernel_Config *dokernel_Cfg;
//Weight of Conv1
unsigned char orional_pic[28][28];
float image[28][28][1];
float W_conv1[3][3][1][12];
float b_conv1[12];
float h_conv1[28][28][12];
float h_pool1[14][14][12];
//Weight of Conv2
float W_conv2[3][3][12][24];
float b_conv2[24];
float h_conv2[14][14][24];
float h_pool2[7][7][24];
//Weight of FC1
float W_fc1[7*7*24][96];
float b_fc1[96];
float h_fc1[96];
//Weight of FC2
float W_fc2[96][10];
float b_fc2[10];
float h_fc2[10];
int main()
{
init_platform();
Xil_DCacheDisable();
int i,j;
u32 status;
u16 cmos_h_pixel;
u16 cmos_v_pixel;
u16 total_h_pixel;
u16 total_v_pixel;
cmos_h_pixel = 1024;
cmos_v_pixel = 768;
total_h_pixel = 2570;
total_v_pixel = 980;
emio_init();
status = ov5640_init( cmos_h_pixel,
cmos_v_pixel,
total_h_pixel,
total_v_pixel);
if(status == 0)
xil_printf("OV5640 detected successful!\r\n");
else
xil_printf("OV5640 detected failed!\r\n");
dokernel_Cfg = XDokernel_LookupConfig(DOKERNEL_DEV_ID);
status = XDokernel_CfgInitialize(&dokernel,dokernel_Cfg);
if(status != XST_SUCCESS){
printf("dokernel initialize failed! \n");
}
XConv xconv;
if(XConv_Initialize(&xconv,XPAR_CONV_0_DEVICE_ID)!=XST_SUCCESS)
xil_printf("XConv device not found\r\n");
XPool xpool;
if(XPool_Initialize(&xpool,XPAR_POOL_0_DEVICE_ID)!=XST_SUCCESS)
xil_printf("XPool device not found\r\n");
//初始化SD卡
SD_Init();
print("Hello World\r\n");
LoadWeight("W_conv1.bin",3*3*1*12,W_conv1[0][0][0]);
LoadWeight("b_conv1.bin",12,b_conv1);
LoadWeight("W_conv2.bin",3*3*12*24,W_conv2[0][0][0]);
LoadWeight("b_conv2.bin",24,b_conv2);
LoadWeight("W_fc1.bin",7*7*24*96,W_fc1[0]);
LoadWeight("b_fc1.bin",96,b_fc1);
LoadWeight("W_fc2.bin",96*10,W_fc2[0]);
LoadWeight("b_fc2.bin",10,b_fc2);
run_vdma_frame_buffer(&vdma, DISP_VDMA_DEV_ID, SCREEN_X, SCREEN_Y,
DISP_BASE_ADDR,0,0,BOTH);
run_vdma_frame_buffer(&vdma, HLS_VDMA_DEV_ID, SCREEN_X, SCREEN_Y,
HLS_BASE_ADDR,0,0,BOTH);
while(1)
{
XDokernel_Set_result(&dokernel,(unsigned int)orional_pic);
XDokernel_Start(&dokernel);
while(!XDokernel_IsDone(&dokernel));
for(i=0;i<28;i++){
for(j=0;j<28;j++){
image[i][j][0] = 1-orional_pic[i][j]/255.0;
}
}
//Conv1
RunConv(&xconv,1,28,28,12,//CHin,Hin,Win,CHout
3,3,1,1,1,1,//Kx,Ky,Sx,Sy,mode,relu_en
image[0][0],W_conv1[0][0][0],b_conv1,h_conv1[0][0]);//feature_in,W,bias,feature_out
RunPool(&xpool,12,28,28,//CHin,Hin,Win
2,2,2,//Kx,Ky,mode
h_conv1[0][0],h_pool1[0][0]);//feature_in,feature_out
//Conv2
RunConv(&xconv,12,14,14,24,//CHin,Hin,Win,CHout
3,3,1,1,1,1,//Kx,Ky,Sx,Sy,mode,relu_en
h_pool1[0][0],W_conv2[0][0][0],b_conv2,h_conv2[0][0]);//feature_in,W,bias,feature_out
RunPool(&xpool,24,14,14,//CHin,Hin,Win
2,2,2,//Kx,Ky,mode
h_conv2[0][0],h_pool2[0][0]);//feature_in,feature_out
//FC1
RunConv(&xconv,24,7,7,96,//CHin,Hin,Win,CHout
7,7,1,1,0,1,//Kx,Ky,Sx,Sy,mode,relu_en
h_pool2[0][0],W_fc1[0],b_fc1,h_fc1);//feature_in,W,bias,feature_out
//FC2
RunConv(&xconv,96,1,1,10,//CHin,Hin,Win,CHout
1,1,1,1,0,1,//Kx,Ky,Sx,Sy,mode,relu_en
h_fc1,W_fc2[0],b_fc2,h_fc2);//feature_in,W,bias,feature_out
//相当于softmax的效果
float max=-10000;int num=0;
for(int m=0;m<10;m++)
{
if(h_fc2[m]>max)
{
max=h_fc2[m];
num=m;
}
}
xil_printf("predicted=%d \r\n",num);
}
cleanup_platform();
return 0;
}
演示效果:b站