STM32F103C8T6单片机通过OV7670摄像头和1.8寸TFT屏幕实现简单的数字识别

一、整体思路

        首先单片机获取摄像头的图片并存储,对图片中的像素做二值化处理,然后和数字的点阵数组作比较,找到偏差最小的一个数字作为识别结果。这个想法是我自然而然想出来的,并没有深刻思考效率和正确率,所以它的误差比较大,主要和图片中数字的位置和字形相关。

二、识别过程

1.获取图片并存储

        STM32F103C8T6单片机运行时的数据内存是20KB,而一帧分辨率为128*160的图片大小为40KB,所以不能直接完整地存储下来,我的解决办法是只存储32*64大小的部分图片,也可以认为这一小部分是识别区域。准备白纸写下待识别的数字

        差不多这样就可以,不过那个数字1还是写小了,就是需要描粗一点,不然二值化处理的时候就成全白了,然后拍摄图片,将纸上的数字放到识别区域内(红线框起来的,也就是数字7)

        关于单片机连接摄像头和显示屏的方法我已经发过了,这个识别区域也只是在正常显示的基础上加了几条线,所以下面写一下图片显示函数的代码

void ov7670()
{
	    while(OV7670_VS()==0){};
		OV7670_W_WRST(0);
        OV7670_W_WRST(1);
        OV7670_W_WEN(1);
		Delay_us(40000);
		while(OV7670_VS()==0);
		OV7670_W_WRST(0);
        OV7670_W_WRST(1);
        OV7670_W_WEN(0);
				
		Delay_us(50000);	
				
		OV7670_W_OE(0);		
		OV7670_W_RRST(0);
		
		OV7670_W_RCLK(0);
		Delay_us(20000);
		OV7670_W_RCLK(1);
		Delay_us(20000);
		OV7670_W_RCLK(0);
		Delay_us(20000);

        OV7670_W_RRST(1);
		Delay_us(20000);
		OV7670_W_RCLK(1);
		

		set_windows(0,0,lcddev.width-1,lcddev.height-1);
		for(h=0;h<160;h++)//128
		{
			for(w=0;w<128;w++)
			{
				OV7670_W_RCLK(0);
				value1 = (uint8_t)GPIOA->IDR & 0xff;
				OV7670_W_RCLK(1);
				OV7670_W_RCLK(0);
				value2 = (uint8_t)GPIOA->IDR & 0xff;
				OV7670_W_RCLK(1);
				color = (value1 << 8) | value2;
				TFT_WRITE_u16_DATA(color);
				if((w<weight) && (h<hight))//设置weight为32,hight为64
				{
					dat[h*4+w/8][w%8] = color;//把左上角识别区域内的像素存储下来
				}

			}
		}		
		OV7670_W_OE(1);
}


void boundary(uint8_t a,uint8_t b)//划红线的函数,a的值是weight,b的值是hight
{
	uint8_t i;
	set_windows(0,0,a,0);
	for(i=0; i<=(a+1); i++)
	{
		TFT_WRITE_u16_DATA(0xf800);
	}
	set_windows(0,0,0,b);
	for(i=0; i<=(b+1); i++)
	{
		TFT_WRITE_u16_DATA(0xf800);
	}
	
	set_windows(0,b,a,b);
	for(i=0; i<=(a+1); i++)
	{
		TFT_WRITE_u16_DATA(0xf800);
	}
	set_windows(a,0,a,b);
	for(i=0; i<=(b+1); i++)
	{
		TFT_WRITE_u16_DATA(0xf800);
	}
}

2.处理图片

        对存储的部分图片进行二值化处理,让图片变成黑白两色,便于接下来的识别。二值化的依据是像素点的灰度值,所以先计算出每个像素点的灰度值。对于RGB565格式的像素,采用式子((R>>8)*77+(G>>3)*150+(B<<3)*29+128)/256可以大概算出灰度值,代码如下

uint8_t togrey(uint16_t color)//适用于RGB565格式的像素值
{
	uint8_t grey;
	grey = (uint8_t)((((color&0xf800)>>8)*77+((color&0x07e0)>>3)*150+((color&0x001f)<<3)*29+128)/256);
	return grey;
}

        之后根据灰度值进行二值化处理,将灰度值大于100的像素点改成白色,其余改成黑色。在数字的点阵数组中,一个像素点用一个二进制位表示,0代表白色1代表黑色,相邻八个像素点组成一个字节,所以也把存储图片的像素点用这种方式表示出来,即白色的像素点用0表示,黑色的像素点用1表示,并把相邻八个像素点组合,代码如下

void dis()//二值化处理
{
	uint8_t w,h,n=0;
	set_windows(0,0,weight-1,hight-1);
	for(h=0; h<hight; h++)
	{
		for(w=0; w<weight; w++)
		{
			n <<= 1;
			if(togrey(dat[h*4+w/8][w%8])>100)
			{
				TFT_WRITE_u16_DATA(0xffff);
				
			}
			else 
			{
				TFT_WRITE_u16_DATA(0x0000);
				n |= 0x01;
			}
			if(w%8==7)
			{
				number[h*4+w/8] = n;//运行这一句之后得到可以和点阵数组作比较的数组
				n=0;
			}

		}
	}
}

        如果把二值化处理之后的数组再次显示出来,就是下图红线框里的样子,中间是识别结果。

3.识别数字

        我认为,识别是显示的逆过程,一个数字显示在屏幕上,是将一些固定的点显示成黑色或白色,反过来,只要找到这些位置相似的点,就可以认为它是这个数字。在TFT屏幕上一个分辨率为32*64大小的数字0,其点阵数组如下

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xE0,0x00,
0x00,0x1F,0xF8,0x00,0x00,0x3C,0x1E,0x00,0x00,0x70,0x0F,0x00,0x00,0xE0,0x07,0x00,
0x01,0xE0,0x03,0x80,0x03,0xC0,0x03,0xC0,0x03,0xC0,0x01,0xC0,0x07,0x80,0x01,0xE0,
0x07,0x80,0x00,0xE0,0x07,0x00,0x00,0xE0,0x0F,0x00,0x00,0xF0,0x0F,0x00,0x00,0xF0,
0x0F,0x00,0x00,0xF0,0x0F,0x00,0x00,0x70,0x1E,0x00,0x00,0x78,0x1E,0x00,0x00,0x78,
0x1E,0x00,0x00,0x78,0x1E,0x00,0x00,0x78,0x1E,0x00,0x00,0x78,0x1E,0x00,0x00,0x78,
0x1E,0x00,0x00,0x78,0x1E,0x00,0x00,0x78,0x1E,0x00,0x00,0x78,0x1E,0x00,0x00,0x78,
0x1E,0x00,0x00,0x78,0x1E,0x00,0x00,0x78,0x1E,0x00,0x00,0x78,0x1E,0x00,0x00,0x78,
0x0F,0x00,0x00,0x70,0x0F,0x00,0x00,0xF0,0x0F,0x00,0x00,0xF0,0x0F,0x00,0x00,0xF0,
0x07,0x00,0x00,0xE0,0x07,0x80,0x01,0xE0,0x07,0x80,0x01,0xE0,0x03,0xC0,0x01,0xC0,
0x03,0xC0,0x03,0xC0,0x01,0xE0,0x03,0x80,0x00,0xE0,0x07,0x00,0x00,0x70,0x0F,0x00,
0x00,0x3C,0x1E,0x00,0x00,0x1F,0xF8,0x00,0x00,0x07,0xE0,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}//数字0

        这个点阵数组和上面二值化处理后得到的数组大小一样,所以可以使其两两比较,选出其中差别最小的,认为它们是同一个数字。我的比较方法是,将同位置像素点的差值的绝对值加在一起,然后选出十个值当中最小的,代码如下

uint8_t compare()
{
	uint8_t RGB=0;
	int rgb[10]={0};
	uint16_t i=0,j=0;
	for(j=0; j<10; j++)
	{
		for(i=0; i<256; i++)
		{
			if(number[i] >= num[j][i])//取绝对值
			{
				rgb[j] += number[i] - num[j][i];
			}
			else rgb[j] += num[j][i] - number[i] ;
		}
	}
	for(j=1; j<10; j++)//选出值最小的
	{
		if(rgb[RGB] > rgb[j])
			RGB = j;
	}
	return RGB;
}

 4.结果

        图片的显示和处理应该明确地区分开,我选用了一个按键,每按一次就在显示和处理当中切换一次,按键在下图红色椭圆圈起来的位置。

        在识别区域内的数字清晰之后,按下按键开始识别,识别结果就会显示在屏幕中间的位置,如下

        可见,识别的结果对于数字的字形和摆放位置关联较大。这个数字识别是关于摄像头和单片机的简单应用,没有用到更深刻更高效的算法,所以它的误差是显而易见的,写这三篇文章是想记录一下学习过程,现在F103C8T6接OV7670的资料在网上是比较少的,我在调试摄像头时花费了很长的时间,经历最多的无非就是失败了,我已经习惯了它的花屏、没反应等情况,虽然它桀骜不驯,但是好在它没有坏,从头至尾调试几百次,只为一句莫问收获,但问耕耘,以此和看到这篇文章的同学们共勉。

作者:我唤醒大海

物联沃分享整理
物联沃-IOTWORD物联网 » STM32F103C8T6单片机通过OV7670摄像头和1.8寸TFT屏幕实现简单的数字识别

发表回复