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

avatar
作者
猴君
阅读量:0

一、整体思路

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

二、识别过程

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的资料在网上是比较少的,我在调试摄像头时花费了很长的时间,经历最多的无非就是失败了,我已经习惯了它的花屏、没反应等情况,虽然它桀骜不驯,但是好在它没有坏,从头至尾调试几百次,只为一句莫问收获,但问耕耘,以此和看到这篇文章的同学们共勉。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!