前两天用到的摄像头模块ov7670 ,想在摄像头做一个色块识别,查阅了部分资料和教程,发现有用的文章挺多,于是下载了几个demo 学习了一下,感谢一些博主的分享,今天整理一下分享给大家。
实现原理
将摄像头的数据读出写入tft屏,读取tft屏幕上的像素点的颜色进行识别。由于RGB格式的颜色数据的效果不好,所以将其转换为HSL格式数据。首先遍历寻找腐蚀中心,然后在之前腐蚀中心点处进行迭代向外寻找新的腐蚀中心。腐蚀算法从该点开始分别向上下左右四个方向进行读点,若点的颜色符合条件则往外读,等四个方向都结束后得到四个边缘点的坐标,记左边缘点的x轴坐标为left,右边缘点的x轴坐标为right,上边缘点的y轴坐标为up,下边缘点的y轴坐标为bottom,那么坐标( (right-left)/2 , (up-bottom)/2 ) 即为新的腐蚀中心。当确定4个点通过,泽围绕4个点画框,将色块识别区域框起来。
1、实现基本的摄像头画面传输并显示在显示屏上(ov7670摄像头模块的使用之前已经分享过,)
2、腐蚀中心算法(如下图详解)
一个40/3=13 13
13大小的色块为单位进行识别
每次只读取这色块的以y的2/1 为点(也就是这个13
13色块y轴为中心点) x轴向右开始查询识别颜色 如识别失败个数大于6次 则认为这一个13
13的色块无效 跳出循环 继续查询下一个13
13的色块。
如果失败次数少于6次则改变查询方向 查询这色块的以X/2 为点 对y轴进行查询方法同上 ,如果y轴有6次失败则,退出循环不认同这个色块合格(因为太小了,失败次数又多),但如果少于六次失败,则认为这个色块识别成功 并记录下这个色块的中心点,
这就是腐蚀中心 /。
unsigned char Hue; //色度 ,[0,240]
unsigned char Lightness; //亮度,[0,240]
unsigned char Saturation; //饱和度,[0,240]
unsigned char Red; // [0,255]
unsigned char Green; // [0,255]
unsigned char Blue; // [0,255]
下面就是源码层,因为参考的是部分博主的源码移植过来的,这里说明一下。(color 兼容多型号的摄像头)
color.c 函数
#include "color.h"
#include "system.h"
#include "tftlcd.h"
#include <stdio.h>
RESULT result;
TARGET_CONDITION condition={
60, //目标最小色度,H_MIN
110, //目标最大色度,H_MAX
50, //目标最小饱和度,S_MIN
240, //目标最大饱和度,S_MAX
60, //目标最小亮度,L_MIN
190, //目标最大亮度,L_MAX
40, //目标最小宽度,WIDTH_MIN
40, //目标最小高度,HEIGHT_MIN
320, //目标最大宽度,WIDTH_MAX
240 //目标最大高度,HEIGHT_MAX蟾叨龋琀EIGHT_MAX
};
typedef struct //HLS颜色
{
unsigned char Hue; //色度 ,[0,240]
unsigned char Lightness; //亮度,[0,240]
unsigned char Saturation; //饱和度,[0,240]
}COLOR_HLS;
typedef struct //RGB
{
unsigned char Red; // [0,255]
unsigned char Green; // [0,255]
unsigned char Blue; // [0,255]
}COLOR_RGB;
/**************************************/
//读取某点的颜色
static void ReadColor( uint16_t usX, uint16_t usY, COLOR_RGB* color_rgb )
{
unsigned short rgb;
rgb = LCD_ReadPoint( usX, usY ); //获取颜色数据
//转换成值域为[0,255]的三原色值
color_rgb->Red = (unsigned char)( ( rgb & 0xF800 ) >> 8 );
color_rgb->Green = (unsigned char)( ( rgb & 0x07E0 ) >> 3 );
color_rgb->Blue = (unsigned char)( ( rgb & 0x001F ) << 3 );
}
/*************************************/
//RGB转换为HLS
//H:色度
//L:亮度
//S:饱和度
static void RGB2HSL( const COLOR_RGB* color_rgb, COLOR_HLS* 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 )//无定义
{
s = 0;
h = 0;
}
else
{
//计算色度
if( max == 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;
}
//计算饱和度
if( l == 0 )
{
s = 0;
}
else if( l <= 120 )
{
s = dif * 240 / ( max + min );
}
else
{
s = dif * 240 / ( 480 - ( max + min ) );
}
}
color_hls->Hue = h; //色度
color_hls->Lightness = l; //亮度
color_hls->Saturation = s; //饱和度
}
/************************************************/
// 颜色匹配
//color_hls :COLOR_HLS结构体,存储HLS格式颜色数据
// condition :TARGET_CONDITION结构体,存放希望的颜色数据阈值
// 1:像素点颜色在目标范围内;0:像素点颜色不在目标范围内。
static int ColorMatch(const COLOR_HLS* color_hls, const TARGET_CONDITION* condition )
{
if(
color_hls->Hue > condition->H_MIN &&
color_hls->Hue < condition->H_MAX &&
color_hls->Lightness > condition->L_MIN &&
color_hls->Lightness < condition->L_MAX &&
color_hls->Saturation > condition->S_MIN &&
color_hls->Saturation < condition->S_MAX
)
return 1;
else
return 0;
}
/****************************************************/
// 寻找腐蚀中心
// x :腐蚀中心x坐标
// y :腐蚀中心y坐标
// condition :TARGET_CONDITION结构体,存放希望的颜色数据阈值
// area :SEARCH_AREA结构体,查找腐蚀中心的区域
// 1:找到了腐蚀中心,x、y为腐蚀中心的坐标;0:没有找到腐蚀中心。
static int SearchCenter(unsigned int* x, unsigned int* y, const TARGET_CONDITION* condition, SEARCH_AREA* area )
{
unsigned int i, j, k;
unsigned int FailCount=0;
unsigned int SpaceX, SpaceY;
COLOR_RGB rgb;
COLOR_HLS hls;
SpaceX = condition->WIDTH_MIN / 3;
SpaceY = condition->HEIGHT_MIN / 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++;
if(FailCount>( (SpaceX+SpaceY) >> ALLOW_FAIL_PER ))
break;
}
if(k == SpaceX+SpaceY)
{
*x = j + SpaceX / 2;
*y = i + SpaceY / 2;
return 1;
}
}
}
return 0;
}
/***************************************************/
// 从腐蚀中心向外腐蚀,得到新的腐蚀中心
// oldX :先前的腐蚀中心x坐标
// oldX :先前的腐蚀中心y坐标
// condition :TARGET_CONDITION结构体,存放希望的颜色数据阈值
// result :RESULT结构体,存放检测结果
// 1:检测成功;0:检测失败。
static int Corrode(unsigned int oldX, unsigned int oldY, const TARGET_CONDITION* condition, RESULT* result )
{
unsigned int Xmin, Xmax, Ymin, Ymax;
unsigned int i;
unsigned int FailCount=0;
COLOR_RGB rgb;
COLOR_HLS hls;
for(i=oldX; i>IMG_X; i--)
{
ReadColor(i, oldY, &rgb);
RGB2HSL(&rgb, &hls);
if(!ColorMatch(&hls, condition))
FailCount++;
if(FailCount>(((condition->WIDTH_MIN+condition->WIDTH_MAX)>>2)>>ALLOW_FAIL_PER))
break;
}
Xmin=i;
FailCount=0;
for(i=oldX; i<IMG_X+IMG_W; i++)
{
ReadColor(i, oldY, &rgb);
RGB2HSL(&rgb, &hls);
if(!ColorMatch(&hls, condition))
FailCount++;
if(FailCount>(((condition->WIDTH_MIN+condition->WIDTH_MAX)>>2)>>ALLOW_FAIL_PER))
break;
}
Xmax=i;
FailCount=0;
for(i=oldY; i>IMG_Y; i--)
{
ReadColor(oldX, i, &rgb);
RGB2HSL(&rgb, &hls);
if(!ColorMatch(&hls, condition))
FailCount++;
if(FailCount>(((condition->HEIGHT_MIN+condition->HEIGHT_MAX)>>2)>>ALLOW_FAIL_PER))
break;
}
Ymin=i;
FailCount=0;
for(i=oldY; i<IMG_Y+IMG_H; i++)
{
ReadColor(oldX, i, &rgb);
RGB2HSL(&rgb, &hls);
if(!ColorMatch(&hls, condition))
FailCount++;
if(FailCount>(((condition->HEIGHT_MIN+condition->HEIGHT_MAX)>>2)>>ALLOW_FAIL_PER))
break;
}
Ymax=i;
FailCount=0;
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;
}
int Trace(const TARGET_CONDITION* condition, RESULT* result_final)
{
unsigned int i;
static unsigned int x0, y0, Flag = 0;
static SEARCH_AREA area = {IMG_X, IMG_X+IMG_W, IMG_Y, IMG_Y+IMG_H};
RESULT result;
if(Flag == 0)
{
if(SearchCenter(&x0, &y0, condition, &area))
{
Flag = 1;
}
else
{
area.X_Start = IMG_X;
area.X_End = IMG_X+IMG_W;
area.Y_Start = IMG_Y;
area.Y_End = IMG_Y+IMG_H;
if(SearchCenter(&x0, &y0, condition, &area))
{
Flag = 0;
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) )
{
x0 = result.x;
y0 = result.y;
result_final->x = result.x;
result_final->y = result.y;
result_final->w = result.w;
result_final->h = result.h;
Flag = 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);
return 1;
}
else
{
Flag = 0;
return 0;
}
}
color.h
#ifndef __COLOR_H
#define __COLOR_H
#include "system.h"
#include "stm32f10x.h"
#define IMG_X 0 //图片x坐标
#define IMG_Y 0 //图片y坐标
#define IMG_W 240 //图片宽度
#define IMG_H 320 //图片高度
#define ALLOW_FAIL_PER 3 //容错率
#define ITERATER_NUM 8 //迭代次数
#define minOf3Values( v1, v2, v3 ) ( (v1<v2) ? ( (v1<v3) ? (v1) : (v3) ) \
: ( (v2<v3) ? (v2) : (v3) ) )
#define maxOf3Values( v1, v2, v3 ) ( (v1>v2) ? ( (v1>v3) ? (v1) : (v3) ) \
: ( (v2>v3) ? (v2) : (v3) ) )
typedef struct //判定为目标的条件
{
unsigned char H_MIN; //目标最小色度
unsigned char H_MAX; //目标最大色度
unsigned char S_MIN; //目标最小饱和度
unsigned char S_MAX; //目标最大饱和度
unsigned char L_MIN; //目标最小亮度
unsigned char L_MAX; //目标最大亮度
unsigned int WIDTH_MIN; //目标最小宽度
unsigned int HEIGHT_MIN; //目标最小高度
unsigned int WIDTH_MAX; //目标最大宽度
unsigned int HEIGHT_MAX; //目标最大高度
}TARGET_CONDITION;
typedef struct //搜寻区域
{
unsigned int X_Start;
unsigned int X_End;
unsigned int Y_Start;
unsigned int Y_End;
}SEARCH_AREA;
typedef struct //结果
{
unsigned int x; //目标x坐标
unsigned int y; //目标y坐标
unsigned int w; //目标宽度
unsigned int h; //目标高度
}RESULT;
extern RESULT result;
extern TARGET_CONDITION condition;
int Trace(const TARGET_CONDITION* condition, RESULT* result_final);
#endif
主函数模块的引入这里是更新屏幕的时候引入的
//定位颜色识别区域(画框)
if(Trace(&condition, &result))
{
LCD_DrawRectangle( result.x-result.w/2, result.y-result.h/2, result.x-result.w/2+result.w, result.y-result.h/2+result.h);
LCD_DrawRectangle( result.x-2, result.y-2,result.x+2, result.y+2);
}
更新LCD显示
void camera_refresh(void)
{
u32 j;
u16 color;
if(ov_sta)//有帧中断更新?
{
LCD_Display_Dir(1);
LCD_Set_Window(0,0,320-1,240-1);//将显示区域设置到屏幕中央
OV7670_RRST=0; //开始复位读指针
OV7670_RCK_L;
OV7670_RCK_H;
OV7670_RCK_L;
OV7670_RRST=1; //复位读指针结束
OV7670_RCK_H;
for(j=0;j<76800;j++) //此种方式需清楚TFT内部显示方向控制寄存器值 速度较快
{
OV7670_RCK_L;
color=GPIOC->IDR&0XFF; //读数据
OV7670_RCK_H;
color<<=8;
OV7670_RCK_L;
color|=GPIOC->IDR&0XFF; //读数据
OV7670_RCK_H;
LCD_WriteData_Color(color);
}
ov_sta=0; //清零帧中断标记
ov_frame++;
LCD_Display_Dir(1); //设置扫描方向
//定位颜色识别区域(画框)
if(Trace(&condition, &result))
{
LCD_DrawRectangle( result.x-result.w/2, result.y-result.h/2, result.x-result.w/2+result.w, result.y-result.h/2+result.h);
LCD_DrawRectangle( result.x-2, result.y-2,result.x+2, result.y+2);
}
}
}
之前分享的原子哥的demo 免费的,大家可以直接下载看看,
stm32 原子哥战舰V3 +ov7670摄像头,形状识别颜色识别源码+ov7670摄像头资料【免费】
用来学习和参考即可。