直接附上我完整的工程代码,点击此处
ov7670摄像头识别车牌号
识别,二值化,识别区域,分割,匹配
一,找车牌的上下位置
ov7670摄像头输出图像后进行二值化,图像上就只有黑色和白色两种颜色,至于阈值就需要自己慢慢找了,只有车牌上的字是白色的,其他全部是黑色的,扫描整个屏幕的像素点,记录下来黑白跳变点比较多的行号,上下行间距就是车牌的上下位置。
二,找车牌的左右位置
找到上下位置之后就可以在车牌的上下位置进行读像素解析,将读到的RGB值转化成HSL值,与蓝色的HSL值得范围进行对比,可以找到蓝色区域,也就找到车牌的左右边界。
三,将车牌区域二值化
将车牌区域二值化,字是白色的其他区域是黑色的。
四,字符分割
从后向前进行字符分割,将每个字符所在开始坐标和结束坐标记录下来。
五,字符归一化
将分割出来的字符读取出来,放大为标准像素25*50.
六,转化为数据
将分割出来的图像转化为字符数据。
七,字符识别
将转化出来的字符数据和标准的字符数据进行对比,每个字节的八位数据都要对比,将相似的数据数记录下来,默认识别为相似度最大的那个数。
请参考以下文章
stm32+ov7670数字识别
基于STM32单片机的车牌识别
色彩空间中的 HSL、HSV、HSB 有什么区别?
图像识别-纯数字识别
但学生作品有时并不需要这样的识别,演示过程没有真正车牌给你识别,自己打印小小的印刷体纸条粘上,好像也不是那么一回事,所以就通过识别颜色或形状等分辨车辆。
这里,我用的颜色识别
//color.c
#include "colorcfg.h"
#include "sys.h"
#include "lcd.h"
#include
RESULT_t result[TRACE_NUM];
//红色0 绿色80 蓝色160
//50 200
u8 global_page=0;
TARGET_CONDITION_t condition[TRACE_NUM]=
{
{220,240, 50,200,5,200,40,40,200,200},//红0
{80,120, 50,200,5,200,40,40,200,200},//绿80
{140,160, 50,200,5,200,40,40,200,200},//蓝160
{0,40, 50,200,5,200,40,40,200,200},//黄40
};
SEARCH_AREA_t area = {IMG_X, IMG_X+IMG_W, IMG_Y, IMG_Y+IMG_H};//定义搜索区域
//读取某点的颜色
void ReadColor( uint16_t usX, uint16_t usY, COLOR_RGB_t* color_rgb )
{
uint16_t rgb;
rgb = LCD_READPOINT( usX, usY ); //获取颜色数据
//转换成值域为[0,255]的三原色值
color_rgb->Red = (uint8_t)( ( rgb & 0xF800 ) >> 8 );
color_rgb->Green = (uint8_t)( ( rgb & 0x07E0 ) >> 3 );
color_rgb->Blue = (uint8_t)( ( rgb & 0x001F ) << 3 );
}
/*************************************/
//RGB转换为HLS
//H:色度
//L:亮度
//S:饱和度
void RGB2HSL( const COLOR_RGB_t* color_rgb, COLOR_HLS_t* color_hls )
{
int r, g, b;
int h, l, s;
int max, min, dif;
r = color_rgb->Red;
g = color_rgb->Green;
b = color_rgb->Blue;
max = maxOf3Values( r, g, b );
min = minOf3Values( r, g, b );
dif = max - min;
//计算l,亮度
l = ( max + min ) * 240 / 255 / 2;
//计算h,色度
if( max == min )//无定义 RGB一样 黑灰白
{
s = 0;//饱和度0
h = 0;//色度0
}
else
{
/*计算色度h*/
if( max == r )//如果R值最大
{
if( min == b )//h介于0到40
{
h = 40 * ( g - b ) / dif;
}
else if( min == g )//h介于200到240
{
h = 40 * ( g - b ) / dif + 240;
}
}
else if( max == g )
{
h = 40 * ( b - r ) / dif + 80;
}
else if( max == b )
{
h = 40 * ( r - g ) / dif + 160;
}
//计算饱和度s
if( l == 0 )
{
s = 0;
}
else if( l <= 120 )
{
s = dif * 240 / ( max + min );
}
else
{
//s = dif * 240 / ( 480 - ( max + min ) );
s = (dif)*240/(511 - (max+min));
}
}
color_hls->Hue = h; //色度
color_hls->Lightness = l; //亮度
color_hls->Saturation = s; //饱和度
}
/************************************************/
// 颜色匹配
//color_hls :COLOR_HLS结构体,存储HLS格式颜色数据
//condition :TARGET_CONDITION结构体,存放希望的颜色数据阈值
// 1:像素点颜色在目标范围内;0:像素点颜色不在目标范围内。
int ColorMatch(const COLOR_HLS_t* color_hls, const TARGET_CONDITION_t* condition )
{
if(
color_hls->Lightness > condition->L_MIN &&
color_hls->Lightness < condition->L_MAX &&
color_hls->Saturation > condition->S_MIN &&
color_hls->Saturation < condition->S_MAX
)//比较饱和度和亮度
{
if( color_hls->Hue > condition->H_MIN &&
color_hls->Hue < condition->H_MAX )//颜色在范围内
return 1;
else
if (condition->H_MAX < condition->H_MIN) //设定的最大颜色小于最小颜色 说明有向下溢出 可能需要和高位颜色匹配
{
if(color_hls->Hue < condition->H_MAX )
return 1;
if(color_hls->Hue > (condition->H_MIN-65295) )
return 1;
}else
if(condition->H_MAX>240)//设定的最大颜色超过240 说明有向上溢出 可能需要和低位颜色匹配
{
if(color_hls->Hue > condition->H_MAX )
return 1;
if(color_hls->Hue < (condition->H_MAX-240) )
return 1;
}
return 0;
}
else
return 0;
}
/****************************************************/
// 寻找腐蚀中心
// x :腐蚀中心x坐标
// y :腐蚀中心y坐标
// condition :TARGET_CONDITION结构体,存放希望的颜色数据阈值
// area :SEARCH_AREA结构体,查找腐蚀中心的区域
// 1:找到了腐蚀中心,x、y为腐蚀中心的坐标;0:没有找到腐蚀中心。
int SearchCenter(uint16_t* x, uint16_t* y, const TARGET_CONDITION_t* condition,const SEARCH_AREA_t* area )
{
uint16_t i, j, k;
uint16_t FailCount=0;
uint16_t SpaceX, SpaceY;
COLOR_RGB_t rgb;
COLOR_HLS_t hls;
SpaceX = condition->WIDTH_MIN / 3;//以最小宽度除以3 为 横向查询的步进的一个单位
SpaceY = condition->HEIGHT_MIN / 3;//以最小高度除以3 为 垂直查询的步进的一个单位
/*横向步进单位+垂直步进单位 组成了一个矩形的色块*/
for(i=area->Y_Start; i<area->Y_End; i+=SpaceY)
{
for(j=area->X_Start; j<area->X_End; j+=SpaceX)
{
FailCount = 0;
for(k=0; k<SpaceX+SpaceY; k++)
{
if(k<SpaceX)
ReadColor( j+k, i+SpaceY/2, &rgb );//查询色块中间一横的颜色
else
ReadColor( j+SpaceX/2, i+k-SpaceX, &rgb );//查询色块中间一竖的颜色
RGB2HSL( &rgb, &hls );
if(!ColorMatch( &hls, condition ))
FailCount++;//颜色不匹配 失败计数+1
if(FailCount>( (SpaceX+SpaceY) >> ALLOW_FAIL_PER ))//失败计数大于 色块需要查询的总点数/2^容错率
break;//失败次数太多 退出
}
if(k == SpaceX+SpaceY)//k坚持到查询完毕,说明基本匹配
{
/*记录该色块的中心点为腐蚀中心*/
*x = j + SpaceX / 2;
*y = i + SpaceY / 2;
return 1; //记录到第一个腐蚀中心后退出函数
}
}
}
return 0;
}
/***************************************************/
// 从腐蚀中心向外腐蚀,得到新的腐蚀中心
// oldX :先前的腐蚀中心x坐标
// oldX :先前的腐蚀中心y坐标
// condition :TARGET_CONDITION结构体,存放希望的颜色数据阈值
// result :RESULT结构体,存放检测结果
// 1:检测成功;0:检测失败。
int Corrode(uint16_t oldX, uint16_t oldY, const TARGET_CONDITION_t* condition, RESULT_t* result )
{
uint16_t Xmin, Xmax, Ymin, Ymax;
uint16_t i;
uint16_t FailCount=0;
COLOR_RGB_t rgb;
COLOR_HLS_t hls;
for(i=oldX; i>IMG_X; i--)//从旧x点向左腐蚀
{
ReadColor(i, oldY, &rgb);//读点
RGB2HSL(&rgb, &hls);//转换
if(!ColorMatch(&hls, condition))
FailCount++;//不匹配计数自加1
if( FailCount> ((condition->WIDTH_MIN)/ALLOW_FAIL_PER) )//当识别失败点大于最小宽度/2是跳出
break;
}
Xmin=i;//获得最新蔓延的x最左边的值
FailCount=0;
for(i=oldX; i<IMG_X+IMG_W; i++)//从旧x点向右腐蚀
{
ReadColor(i, oldY, &rgb);
RGB2HSL(&rgb, &hls);
if(!ColorMatch(&hls, condition))
FailCount++;
if( FailCount> ((condition->WIDTH_MIN)/ALLOW_FAIL_PER) )
break;
}
Xmax=i;
FailCount=0;
for(i=oldY; i>IMG_Y; i--)//从旧y点向上腐蚀
{
ReadColor(oldX, i, &rgb);
RGB2HSL(&rgb, &hls);
if(!ColorMatch(&hls, condition))
FailCount++;
if( FailCount> ((condition->WIDTH_MIN)/ALLOW_FAIL_PER) )
break;
}
Ymin=i;
FailCount=0;
for(i=oldY; i<IMG_Y+IMG_H; i++)//从旧y点向下腐蚀
{
ReadColor(oldX, i, &rgb);
RGB2HSL(&rgb, &hls);
if(!ColorMatch(&hls, condition))
FailCount++;
if( FailCount> ((condition->WIDTH_MIN)/ALLOW_FAIL_PER) )
break;
}
Ymax=i;
FailCount=0;
//获得腐蚀区域的中点和xy范围
result->x = (Xmin + Xmax) / 2;
result->y = (Ymin + Ymax) / 2;
result->w = (Xmax - Xmin);
result->h = (Ymax - Ymin);
if( (result->w > condition->WIDTH_MIN) && (result->w < condition->WIDTH_MAX) &&
(result->h > condition->HEIGHT_MIN) && (result->h < condition->HEIGHT_MAX) )
return 1;//如果腐蚀后的区域没有超过最大限定区域且没有小于最小限定区域 有效!!
else
return 0;
}
/*
*返回0识别失败,1成功
*得到匹配色块的信息
*/
int Trace(const TARGET_CONDITION_t* condition, RESULT_t* result_final)
{
uint16_t i;
static uint16_t x0, y0;
RESULT_t result;
if(!SearchCenter(&x0, &y0, condition, &area))//寻找腐蚀中心
{
area.X_Start = IMG_X;
area.X_End = IMG_X+IMG_W;
area.Y_Start = IMG_Y;
area.Y_End = IMG_Y+IMG_H;
return 0;
}
//找到腐蚀中心 得到中点
result.x = x0;
result.y = y0;
for(i=0; i<ITERATER_NUM; i++)//多次迭代 精确中心
{
Corrode(result.x, result.y, condition, &result); //从腐蚀中心向外腐蚀,得到新的腐蚀中心
}
if( Corrode(result.x, result.y, condition, &result) )//重新腐蚀成功
{
result_final->x = result.x;
result_final->y = result.y;
result_final->w = result.w;
result_final->h = result.h;
#if TRACE_NUM==1 //只有一个图像时才使用快速查找
/*为了快速对下一个图像进行找腐蚀中心,直接定义本次图像的腐蚀中心为下一个图像的扫描区域*/
area.X_Start = result.x - ((result.w)>>1);
area.X_End = result.x + ((result.w)>>1);
area.Y_Start = result.y - ((result.h)>>1);
area.Y_End = result.y + ((result.h)>>1);
#endif
return 1;
}
else
{
return 0;
}
}
此代码(来源于网络,将要识别的区域的像素遍历 需找匹配范围内的像素,找到匹配像素后,向它的四周进行蔓延匹配像素。
直到匹配到符合色块大小的区域,标记识别成功。
为了提高识别速度和降低cpu压力,并不是每个多个像素都要比较的。我们以(最小识别区域/3)为一个单位色块,
只用识别色块的中间一横和一竖上的像素点作为该色块的匹配标准,大大减少了cpu的压力。
在识别算法和RGB转HSL函数
只要调用Trace这一个函数就能实现颜色的识别,非常方便。
在使用前 需要在colorcfg.h中根据自己的图像大小在LCD中的位置配置宏定义
提供自己lcd的读点函数,读点格式是rgb565.
)只需修改以下部分,这是我测好的四种颜色的hsl数据,(因为环境不一样,可能需要自己重新测试,每次都能识别到即可)
TARGET_CONDITION_t condition[TRACE_NUM]=
{
{220,240, 50,200,5,200,40,40,200,200},//红0
{80,120, 50,200,5,200,40,40,200,200},//绿80
{140,160, 50,200,5,200,40,40,200,200},//蓝160
{0,40, 50,200,5,200,40,40,200,200},//黄40
};
//colorcfg.h
#ifndef colorcfg_h
#define colorcfg_h
#include "color.h"
#include "test.h"
/*配置色块查询的范围 图像在LCD的坐标*/
#define IMG_X 0 //图片x坐标
#define IMG_Y 0 //图片y坐标
#define IMG_W 240 //图片宽度
#define IMG_H 320 //图片高度
#define ALLOW_FAIL_PER 3 //容错率越大 颜色匹配越高,也越难识别 取值>1
#define ITERATER_NUM 5 //迭代次数 越多精度越准
#define COLOR_RANG 90 //设定颜色的偏移范围 越大越容易识别 太大容易误识别
#define TRACE_NUM 4 //设定追踪颜色的数目
extern u8 global_page;//当前颜色的
extern SEARCH_AREA_t area;//定义搜索区域
extern RESULT_t result[TRACE_NUM];//定义搜索结果
extern TARGET_CONDITION_t condition[TRACE_NUM];//定义目标参数
#define LCD_READPOINT( usX, usY ) LCD_ReadPoint(usX,usY)//定义读点函数
#endif
这里需要修改以下部分
#define IMG_W 240 //图片宽度
#define IMG_H 320 //图片高度
#define ALLOW_FAIL_PER 3 //容错率越大 颜色匹配越高,也越难识别 取值>1
#define ITERATER_NUM 5 //迭代次数 越多精度越准
#define COLOR_RANG 90 //设定颜色的偏移范围 越大越容易识别 太大容易误识别
#define TRACE_NUM 4 //设定追踪颜色的数目
u8 car_distinguish(void)
{
u8 CAR_FLAG=0;
u8 i=0;
LCD_ShowString(30,Y11,200,16,16,"Camera Starting...");
//初始化OV7670
while(OV7670_Init())
{
LCD_ShowString(30,Y12,240,16,16,"Camera ERR");
delay_ms(200);
LCD_Fill(30,Y12,239,170,WHITE);
delay_ms(200);
}
LCD_ShowString(30,Y12,200,16,16,"Camera OK");
//OV7670设置输出窗口
OV7670_Window_Set(12,176,240,320);
//DCMI配置
My_DCMI_Init();
//DCMI DMA配置
DCMI_DMA_Init((u32)&LCD->LCD_RAM,10,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Disable);
//启动传输
DCMI_Start();
while(CAR_FLAG==0)
{
DCMI_Stop();
for(i=0;i<TRACE_NUM;i++)
{
if(Trace(&condition[i], &result[i]))//执行颜色识别
{
/*颜色匹配 画矩形圈出色块 LED0熄灭 */
LCD_DrawRectangle( result[i].x-result[i].w/2, result[i].y-result[i].h/2, result[i].x-result[i].w/2+result[i].w, result[i].y-result[i].h/2+result[i].h);
draw_cross(result[i].x, result[i].y);
CAR_FLAG=i+1;
}
}
DCMI_Start();
delay_ms(10);
}
DCMI_Stop();
LCD_Fill(0,0,320,240,WHITE);
LCD_Scan_Dir(0);
return CAR_FLAG;
}
此函数是具体结合ov7670的调用识别函数,因为我的7670是无晶振,无fifo的(某宝15元+就可买到),而且通过dcmi接口,dma传输到lcd显示的,所以读数据就只能用lcd的读点函数了,
car_color=car_distinguish();
switch(car_color)
{
case 1:showhz32str(200,Y12,"吉",RED,WHITE);POINT_COLOR=RED;LCD_ShowString(240-4*16,Y11,200,16,16,"R.66666"); break;
case 2:showhz32str(200,Y12,"吉",GREEN,WHITE);POINT_COLOR=GREEN;LCD_ShowString(240-4*16,Y11,200,16,16,"G.88888"); break;
case 3:showhz32str(200,Y12,"吉",BLUE,WHITE);POINT_COLOR=BLUE;LCD_ShowString(240-4*16,Y11,200,16,16,"B.dzxh9"); break;
}
识别完后,拿去给lcd显示。
推荐以下参考资料
阿莫
基于stm32f407vet开发板的人脸识别
如果觉得对你有帮助的话,关注我,点个赞再走呗