单片机项目分享 stm32实现车牌识别系统 -物联网 嵌入式 单片机


0 前言

🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。

为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是

🚩 基于stm32的实现车牌识别系统

🥇学长这里给一个题目综合评分(每项满分5分)

  • 难度系数:4分
  • 工作量:4分
  • 创新点:3分

1 简介

本系统利用STM32单片机驱动摄像头采集图像,通过模式识别,匹配车牌的识别结果,并在屏幕上显示。

2 主要器件

  • STM32F103RCT6 主控芯片
  • TFT液晶屏
  • OV7670摄像头
  • 蜂鸣器
  • LED电路

3 实现效果

识别效果在这里插入图片描述
摄像头模块
在这里插入图片描述

4 设计原理

4.1 软件总体过程:

(1)OV7670带FIFO摄像头图像采集,采用GPIO模拟摄像头时序,通过读取FIFO输出值将图像直接显示在LCD屏上
(2)LCD屏相当于一个图像缓存,同时也做显示,通过读取LCD屏上的像素值进行图像处理;
(3)车牌定位处理,车牌定位常用二值化分割,腐蚀膨胀处理,连通域计算等操作,显然这些算法在stm32f1上实现是很困难的,且处理速度太慢,因此,采用RGB转HSV颜色空间变换和阈值选择进行车牌定位,然后将车牌定位区域进行二值化处理,不是蓝色车牌的部分就是字符区域;
(4)车牌字符分割处理,字符分割先采用行统计加列统计的方式,确定每行和每列的有效像素和,进一步确定字符区域;然后进行横向统计分割,通过每一列的像素和阈值判断字符的分界线和个数;
(5)车牌归一化处理,归一化处理先将每个字符提取出来,然后按照像素值进行横向和纵向压缩,最终处理成模板一样大小的字符;并在液晶屏上保存字符的数据;
(6)模板匹配,将归一化之后的字符,与模板中的字符通过像素值一一比较,确定相似度最高的字符就是目标值;

4.2 具体解释

图像采集
通过OV7670摄像头进行图像采集,采集的图像大小为320*240像素,像素格式为RGB565。每个像素由两字节组成,第一字节的高五位是Red,第一字节的低三位和第二字节的高三位组成Green,第二字节的低五位是Blue。

二值化
二值化就是让图像的像素点矩阵中的每个像素点的灰度值为0(黑色)或者255(白色),让整个图片呈现出只有黑色和白色的效果。二值化后的图像中灰度值范围是0或者255。这时需要设定一个阈值来对像素点进行设置。

常用二值化方法:

  1. 取中值:设置阈值为127,灰度值小于127的为0,大于127的为255。这样设置计算量小,计算快。缺点也严重:在不同的图像中,颜色分布差别大,处理效果也不会很好。程序开始之前设置R,G,B的阈值,通过阈值判断将像素设置为全黑(0x0000)或者全白(0xFFFF).同时根据色彩的变化记录每一行的颜色跳变点,由此识别出车牌区域。
  2. 取平均值:像素点平均值 = (像素点1灰度值 + 像素点2灰度值 + …… + 像素点n灰度值) / n
  3. 双峰法:此方法适用于具有明显双峰直方图的图像,不适合直方图中双峰差别很大或双峰间的谷比较宽广而平坦的图像。该方法认为图像由前景和背景组成,在灰度直方图上,前景和背景会形成高峰,在双峰之间的最低谷处就是阈值。

识别车牌区域
根据上一步的二值化,由于车牌区域跳变点多,由此可以得出车牌区域。分别记录车牌区域的上下高度。然后通过RGB-HSV颜色转换,识别出车牌区域的左右边界。

字符分割
我国常见车牌以及排列顺序大部分都是按照如下设计的:汉字、英文字母、点、英文字母、阿拉伯数字、阿拉伯数字、阿拉伯数字、阿拉伯数字。基于这个规律,以及图像采集高度一致,设计了如下的分割方法:

  1. 在内存中开辟七个长为车牌长的七分之一和宽为车牌宽的区域
  2. 从车牌图像长边的巾问向下开始扫描车牌图像,并把扫描到的所有的点灰度值复制到0区域的第四个区域对应位置上。然后再从上向下扫描刚扫描过这一努的左边或右边,直到所扫描的这一峰上的所有点的灰度都是0时为止,并把这一竖认为是字符的分离处。
  3. 切割第五到第七个字符。方法就是,切割完了第四个字符之后,再依次扫描剩下的空间,直到所扫描的这一竖上的所有点的灰度值不全为0时,认为是字符的开始并依次扫描直到所扫描的这一竖上的所有点的灰度值全为0时认为是字符的结束。
  4. 切割第三到第四个字符。这两个字符的切割方式与第五到第七个字符一样。
  5. 切割第一到第二个字符。当第三个字符切割完之后,我们将遇到一个点,我们也把它看作一个字符,只不过这个点扫描之后就不要了。扫描完这个点之后,我们来切割第二个字符,它的切割方式与前面一样。切割完了第二个字符之后,再向左扫描,直到所扫描的这一竖上的所有点的灰度值不全为0时,认为是字符的开始,并依次扫描直到所扫描所有剩下的,并填到相应的位置,直到剩下的空间填满。经过粗分割后,可以得到一些单个字符区域和多余的空间。下一步我们将把这些多余的空间去掉。这将更有利于下一步字符的识别。
  6. 去除图像上多余空间:车牌上的字符经过了粗切割所得到的是一些单的字符,但在分配空间时是按照车牌的宽和长的七分之一来分配的;所以这个空间可能大于字符应该占的空问。所以,要将多余空间去除。对于第一个字符从第一行开始向下扫描,把那些一行中所有的点的灰度值全为0的点去掉,直到扫描到有一行不全为0时为止。然后再从第一列开始向右扫描把那些一列中所有的点的灰度值全为0的点去掉,直到扫描到有一列不全为0时为止。接下来从最后一行开始向上扫描,把那些一行中所有的点的灰度值全为0的点去掉,直到扫描到有一行不全为0时为止。最后从最后一列开始向左扫描把那些一列中所有的点的灰度值全为0的点去掉,直到扫描到有一列不全为0时为止。重复上面的步骤完成剩下字符的切割。
  7. 根据二值化的结果,以及记录的跳变点位置,对字符进行分割,同时记录字符的左右边界。

字符匹配
对分割出来的字符进行归一化处理,这里用到图片的扩大算法,扩大之后逐一的去进行字符匹配。字符模板事前通过字模软件转换成二进制数据保存在数组中。最后根据匹配结果相似度最大的做为输出结果。
归一化图像就是要把原来各不相同的字符统一到同一尺寸。因为扫描进来的图像中字符大小存在较大的差异,而相对来说,统一尺寸的字符识别的标准性更强,准确率自然也更高。具体算法如下:先得到原来字符的高度和宽度,与系统已存字模的数据作比较,得出要变换的系数,然后根据得到的系数按照插值的方法映射到原图像中。

5 部分核心代码

#define COLOR_RGB565_TO_R8(pixel) \
({ \
__typeof__ (pixel) __pixel = (pixel); \
__pixel = (__pixel >> 8) & 0xF8; \
__pixel | (__pixel >> 5); \
})

#define COLOR_RGB565_TO_G8(pixel) \
({ \
 __typeof__ (pixel) __pixel = (pixel); \
    __pixel = (__pixel >> 3) & 0xFC; \
    __pixel | (__pixel >> 6); \
})
#define COLOR_RGB565_TO_B8(pixel) \
({ \
   __typeof__ (pixel) __pixel = (pixel); \
   __pixel = (__pixel << 3) & 0xF8; \
   __pixel | (__pixel >> 5); \
 })	
int8_t imlib_rgb565_to_l(uint16_t pixel)
{   
float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)];    
	float g_lin = 	xyz_table[COLOR_RGB565_TO_G8(pixel)];   
	float b_lin = xyz_table[COLOR_RGB565_TO_B8(pixel)];
	float y = ((r_lin * 0.2126f) + (g_lin * 0.7152f) + (b_lin * 0.0722f)) * (1.0f / 100.000f);
	y = (y>0.008856f) ? fast_cbrtf(y) : ((y * 7.787037f) + 0.137931f);	    
	return fast_floorf(116 * y) - 16;
}
	
int8_t imlib_rgb565_to_a(uint16_t pixel)
{  
	 float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)];   
	 float g_lin =  xyz_table[COLOR_RGB565_TO_G8(pixel)];  
	 float b_lin =  xyz_table[COLOR_RGB565_TO_B8(pixel)];
	 float x = ((r_lin * 0.4124f) + (g_lin * 0.3576f) + (b_lin * 0.1805f)) * (1.0f / 095.047f);    float y = ((r_lin * 0.2126f) + (g_lin * 0.7152f) + (b_lin * 0.0722f)) * (1.0f / 100.000f);
	 x = (x>0.008856f) ? fast_cbrtf(x) : ((x * 7.787037f) + 0.137931f);    y = (y>0.008856f) ? fast_cbrtf(y) : ((y * 7.787037f) + 0.137931f);		
	 return fast_floorf(500 * (x-y));
}

int8_t imlib_rgb565_to_b(uint16_t pixel)
{   
	float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)];   
	float g_lin = xyz_table[COLOR_RGB565_TO_G8(pixel)];   
	float b_lin = xyz_table[COLOR_RGB565_TO_B8(pixel)];				
	float y = ((r_lin * 0.2126f) + (g_lin * 0.7152f) + (b_lin * 0.0722f)) * (1.0f / 100.000f);    float z = ((r_lin * 0.0193f) + (g_lin * 0.1192f) + (b_lin * 0.9505f)) * (1.0f / 108.883f);				
	y = (y>0.008856f) ? fast_cbrtf(y) : ((y * 7.787037f) + 0.137931f);    z = (z>0.008856f) ? fast_cbrtf(z) : ((z * 7.787037f) + 0.137931f);				
	return fast_floorf(200 * (y-z));
}

	unsigned short xrIntFloat_16=(Src_width<<8)/Dst_width+1;   //扩大倍数    
	unsigned short yrIntFloat_16=(Src_height<<8)/Dst_height+1;    
	 
	unsigned char* pDstLine=Dst_y8;  
	unsigned short srcy_16=0;    
	  
        for (y=0;y<Dst_height;++y)   
       {        
             unsigned char* pSrcLine=((unsigned char*)((unsigned char*)Src_y8+Src_width*(srcy_16>>8)));    
             unsigned short srcx_16=0;     
             for (x=0;x<Dst_width;++x)      
              {          
		pDstLine[x]=pSrcLine[srcx_16>>8];         
		srcx_16+=xrIntFloat_16;       
              }      
               srcy_16+=yrIntFloat_16;    
               pDstLine+=Dst_width;   
      } 
}

模板匹配算法

/**
* @function 欧几里得距离计算,用于图片相似度计算
* @param[in] src1和src2,必须是相同大小灰度图片
* @param[out] 欧几里得距离
* @retval ERROR -1 错误
*/
float Euclidean_Distance(unsigned char *Src1,unsigned char *Src2,int length,float Euclideandis)
{
	if(Src1==NULL||Src2==NULL)
	{
		return -1;
	}
	int sum=0;
	int i=0;
	for(i=0;i<length;i++)
	{
		sum+=(int)pow((*Src1-*Src2),2);
		Src1++;
		Src2++;
	}
	
	Euclideandis=(float)sqrt(sum);
	return Euclideandis;
}


/**
* @function 余弦相似度计算,用于图片相似度计算
* @param[in] src1和src2,必须是相同大小灰度图片
* @param[out] 余弦相似度
* @retval 0 相似度小于0
* @retval ERROR -1 错误
* @retval CosineSimilar
*/
float Cosine_Similarity(unsigned char *Src1,unsigned char *Src2,int length,float CosineSimilar)
{
	if(Src1==NULL||Src2==NULL)
	{
		return -1;
	}
	int sum=0,sum1=0,sum2=0;
	float temp0=0,temp1=0;
	int i=0;
	for(i=0;i<length;i++)
	{
	
		sum+=(int)(*Src1)*(*Src2);
		sum1+=(int)pow((*Src1),2);
		sum2+=(int)pow((*Src2),2);
		Src1++;
		Src2++;
	}
	if(sum<=0)
	{
		return 0;
	}
	temp0=(float)(sqrt(sum1));
	temp1=(float)(sqrt(sum2));
	CosineSimilar=(float)((sum/temp0)/temp1);

	return CosineSimilar;
}


/**
* @function 皮尔逊相似度计算,用于图片相似度计算
* @param[in] src1和src2,必须是相同大小灰度图片
* @param[out] 皮尔逊相似度
* @retval ERROR -1 错误
* @par 2021年5月28日 zhengmf
*/
float Pearson_Correlation(unsigned char *Src1,unsigned char *Src2,int length,float PearsonSimilar)
{
	if(Src1==NULL||Src2==NULL)
	{
		return -1;
	}
	unsigned char aver1=0,aver2=0;
	int sum=0,sum1=0,sum2=0;
	float temp0=0,temp1=0;
	int i=0;
	for(i=0;i<length;i++)
	{
		sum1+=*Src1;
		sum2+=*Src2;
		Src1++;
		Src2++;
	}
	aver1=(unsigned char)(sum1/length);
	aver2=(unsigned char)(sum2/length);
	sum1=0;
	sum2=0;
	
	for(i=0;i<length;i++)
	{
		sum+=(int)(*Src1-aver1)*(*Src2-aver2);
		sum1+=(int)pow((*Src1-aver1),2);
		sum2+=(int)pow((*Src2-aver2),2);
		Src1++;
		Src2++;
	}
	if(sum<=0)
	{
		return 0;
	}
	temp0=(float)(sqrt(sum1));
	temp1=(float)(sqrt(sum2));
	PearsonSimilar=(float)((sum/temp0)/temp1);		
	return PearsonSimilar;
}


6 最后

  • 15
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值