zynq手写数字识别

September 06, 2024

总体设计

block-diagram.png

该图像由下述命令生成,精度较高,可点开大图详细观看

write_bd_layout -force -format svg -verbose /tmp/block.design.svg

PL端:

  • 读取ov5640摄像头采集的数据
  • 使用Vitis HLS生成的IP核,将ov5640摄像头采集的rgb数据转为灰度数据,选定大小为112x112框,将框内灰度数据二值化,然后将灰度数据重新转为rgb数据,传向vdma
  • rom_sel模块接收PS端传来的识别结果,根据识别结果选择对应的rom数据,传向data_handler
  • data_handler模块接收rom数据,显示在屏幕左上角,并且将先前选定的识别框四周描上黑边,然后传给dvi_transmitter
  • dvi_transmitter模块将rgb数据进行编码、并转串等操作,转为TMDS标准,进而通过HDMI口输出

PS端:

  • 使用ps端spi控制ov5640摄像头
  • 配置vdma
  • 将vdma传到DDR的数据通过PS端编写的神经网络后得到10个输出, 分别代表0-9的概率,选取概率最大的作为识别结果, 将识别结果通过axi-lite传到PL端的rom_sel中

HLS图像处理具体实现

Vitis HLS代码中rgb数据转为灰度数据,并且在指定区域进行二值化代码如下:

template<int ROWS, int COLS>
void rgb2gray(xf::cv::Mat<XF_8UC3, ROWS, COLS, XF_NPPC1>&src, 
    xf::cv::Mat<XF_8UC1, ROWS, COLS, XF_NPPC1>&dst, ap_uint<8>threshold = 128)
{
    for(int i = 0; i < ROWS; i++)
    {
        for(int j = 0; j < COLS; j++)
        {
        #pragma HLS PIPELINE
            XF_TNAME(XF_8UC3, XF_NPPC1) pixel = src.read(i*COLS+j);
            ap_uint<8> rgb[3];
            extract_rgb(pixel, rgb);
            ap_uint<8> gray = CalculateGRAY(rgb[0], rgb[1], rgb[2]);
            if(i >= 184 && i <= 296 && j >= 320 && j <= 432)
            {
                gray = gray < threshold ? 255 : 0;
            }
            XF_TNAME(XF_8UC1,XF_NPPC1) gray_packed;
            gray_packed.range(7, 0) = gray;
            dst.write(i*COLS+j, gray_packed);
        }
    }
}

灰度数据转为rgb数据代码与之类似,不再赘述

模块总数据输入接口为axis流接口,输出接口为axis,并由vio控制ap_none类型的threshold变量的值

神经网络具体实现

1. 从DDR中读取数据,并将数据传入神经网络中

(注意:cmos_data的index需要乘3是因为cmos_data是rgb数据,实际我们只取了其中的一个通道作为灰度数据,这与我们对灰度转rgb的处理是相对应的)

    // 初始化
    unsigned int const frame_buffer_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR
										+ 0x1000000);
	u8 *cmos_data = frame_buffer_addr;
    // 读取数据       
        for(int row=0; row<VSIZE; row++)
        {
            for(int col=0; col<HSIZE; col++)
            {
                if(col>=320 && col<432 && row>=184 && row<296)
                {
                	if(col%4 == 0 && row%4 == 0)
                	{
                		img_data[index] = cmos_data[(row*HSIZE+col)*3];
                		index++;
                	}
                }
            }
        }

2. 神经网络各部分实现(以卷积层为例)

(为了保存中间数据,我们将数据保存到了ddr中的某个特定地址) 下面是卷积层的实现

    // 初始化
        float *cnn_param_w = 0x2500000;
    	float *cnn_param_b = 0x2500C00;

        conv_param_init();
        float *conv_rlst = 0x2500D00;
        // 卷积层
        for(int n=0; n<30; n++)
        {
            for(int row=0; row<=23; row++)
            {
                for(int col=0; col<=23; col++)
                {
                    conv_temp = 0;
                    for(int x=0; x<5; x++)
                    {
                        for(int y=0; y<5; y++)
                        {
                            conv_temp += img_data[row*28+col+x*28+y] * cnn_param_w[x*5+y+n*25];
                        }
                    }
                    conv_temp += cnn_param_b[n];

                    // 激活函数
                    if(conv_temp > 0)
                        conv_rlst[row*24+col+n*24*24] = conv_temp;
                    else
                        conv_rlst[row*24+col+n*24*24] = 0;
                }
            }
        }

3. 输出

        for(int n=0; n<10; n++)
        {
            affine2_temp = 0;
            for(int i=0; i<100;i++)
            {
                affine2_temp = affine2_temp + affine2_w[i+100*n] * affine1_rslt[i];
            }
            affine2_rslt[n] = affine2_temp;
        // 选取最大值
            if(temp <= affine2_rslt[n])
            {
                temp = affine2_rslt[n];
                predict_num = n;
            }
        }
        // 输出给axi-lite, 然后传给rom_sel
        Xil_Out32(AXI_LITE_BASEADDR, predict_num);

最终效果

zero.jpg

one.jpg

five.jpg

参考资料


Profile picture

Written by Prosumer , an undergraduate student at ShanghaiTech.
Welcome to my GitHub:)