STM32 OV7725摄像头模块识别颜色物体(1)--HSL二值化和腐蚀中心算法,并用串口输出数据

目录

前言

一、摄像头采集数据流程

二、如何将图像显示到电脑上

 三、图像二值化

1、什么是RGB?

2、RGB565转RGB888

I、RGB565和RGB888的区别

II、代码

3、RGB转HSL

I、什么是HSL

 II、转换公式

 III、代码

3、输出一张摄像头二值化图片

I、原理

II、代码 

四、简单的物体识别

1、原理参考

 2、识别代码

3、显示代码

总结



前言

前阵子用STM32弄摄像头,断断续续有段时间,也在网上翻阅了不少资料,写篇博客记录一下学习过程。最后成功识别单个物体,图形和多个物体暂不支。

一、摄像头采集数据流程

(1) 利用 SIO_C、SIO_D 引脚通过 SCCB 协议向 OV7725 的寄存器写入初始化配置;

(2) 初始化完成后,OV7725 传感器会使用 VGA 时序输出图像数据,它的 VSYNC 会

首先输出帧有效信号(低电平跳变),当外部的控制器(如 STM32)检测到该信号

时,把 WEN 引脚设置为高电平,并且使用 WRST 引脚复位 FIFO 的写指针到 0 地

址;

(3) 随着 OV7725 继续按 VGA 时序输出图像数据,它在传输每行有效数据时, HREF

引脚都会持续输出高电平,由于 WEN 和 HREF 同时为高电平输入至与非门,使得

其连接到 FIFO WE 引脚的输出为低电平,允许向 FIFO 写入数据,所以在这期间,

OV7725 通过它的 PCLK 和 D[0:7]信号线把图像数据存储到 FIFO 中,由于前面复

位了写指针,所以图像数据是从 FIFO 的 0 地址开始记录的;

(4) 各行图像数据持续传输至 FIFO,受 HREF 控制的 WE 引脚确保了写入到 FIFO 中

的都是有效的图像数据,OV7725 输出完一帧数据时,VSYNC 会再次输出帧有效

信号,表示一帧图像已输出完成;

(5) 控制器检测到上述 VSYNC 信号后,可知 FIFO 中已存储好一帧图像数据,这时控

制 WEN 引脚为低电平,使得 FIFO 禁止写入,防止 OV7725 持续输出的下一帧数

据覆盖当前 FIFO 数据;

(6) 控制器使用RRST复位读指针到FIFO的0地址,然后通过FIFO的RCLK和DO[0:7]

引脚,从 0 地址开始把 FIFO 缓存的整帧图像数据读取出来。在这期间,OV7725

是持续输出它采集到的图像数据的,但由于禁止写入 FIFO,这些数据被丢弃了;

(7) 控制器使用 WRST 复位写指针到 FIFO 的 0 地址,然后等待新的 VSYNC 有效信号

到来,检测到后把 WEN 引脚设置为高电平,恢复 OV7725 向 FIFO 的写入权限,

OV7725 输出的新一帧图像数据会被写入到 FIFO 的 0 地址中,重复上述过程。

网上也有很多现成的文章,分享几个链接

摄像头原理:

stm32 OV7670摄像头模块的介绍以及应用(SCCB的使用)_闰土小蒋的博客-CSDN博客

正点原子开发板程序:

http://www.openedv.com/docs/modules/camera/ov7725-fifo.html

二、如何将图像显示到电脑上

使用串口发送数据到电脑,用山外调试助手显示,这里的波特率使用256000,因为调试助手的最大也只能是256000,图像配置,宽:320,高:240,RGB565大端(这个是个大坑)

一副图像的通信协议为:[0x01] [0xFE][…数据…][0xFE] [0x01]

[…数据…] 是图像的数据,一帧图像有多少数据,这里的数据长度就有多少。换句话说, […数据…]与图像的格式,图像的宽高有关。此处的图像数据,都是从上往下,从左往右存储 的。 只有下位机发送的数据与上位机配置的格式的长度相同时,才可正确识别图像格式,从 而正确显示图像。 下位机发送图像时,先发送帧头:0x01,0xFE,接着发送图像数据,最后发送帧尾: 0xFE,0x01 完成一副图像发送。

链接:https://pan.baidu.com/s/1eA3rhtNiocKxtH0rxvyGzg?pwd=0722 
提取码:0722

摄像头采集代码如下:

        OV7725_RRST(0);                //开始复位读指针 OV7725_RCK_L;OV7725_RCK_H;OV7725_RCK_L;OV7725_RRST(1);                //复位读指针结束 OV7725_RCK_H;while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。USART_SendData(UART4, 0x01);while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。USART_SendData(UART4, 0xfe);for(x_size=0;x_size<240;x_size++){for(y_size=0;y_size<320;y_size++){OV7725_RCK_L;color=GPIOE->IDR;    //读数据OV7725_RCK_H;colorH=(color>>8) &0xff;OV7725_RCK_L;color=GPIOE->IDR ;    //读数据OV7725_RCK_H; colorL=(color>>8) &0xff;while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。USART_SendData(UART4, colorH);while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。USART_SendData(UART4, colorL);}}while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。USART_SendData(UART4, 0xfe);while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。USART_SendData(UART4, 0x01);

 三、图像二值化

1、什么是RGB?

        RGB是从颜色发光的原理来设计定的,通俗点说它的颜色混合方式就好像有红、绿、蓝三盏灯,当它们的光相互叠合的时候,色彩相混,而亮度却等于两者亮度之总和,越混合亮度越高,即加法混合。

        红、绿、蓝三个颜色通道每种色各分为256阶亮度,在0时“灯”最弱——是关掉的,而在255时“灯”最亮。当三色灰度数值相同时,产生不同灰度值的灰色调,即三色灰度都为0时,是最暗的黑色调;三色灰度都为255时,是最亮的白色调。

        在电脑中,RGB的所谓“多少”就是指亮度,并使用整数来表示。通常情况下,RGB各有256级亮度,用数字表示为从0、1、2...直到255。注意虽然数字最高是255,但0也是数值之一,因此共256级。

2、RGB565转RGB888

I、RGB565和RGB888的区别

在计算机中图像基本是以RGB888格式显示的,24位图每个像素保存了32bit的数据,即RGB888+Alpha,Alpha就是半透明填充字节。对于真彩的图像而言,肉眼在16bit的时候已经难以分辨了,有些时候,可以将RGB888转换为RGB565来存储,减少了存储器的容量的同时,降低了数据量;在显示的时候,再次把RGB565转换为RGB888,实现数据宽度的匹配

RGB888->RGB565

只要提取相应单色高位即可(R5 G6 B5),但会导致低位的缺失,影响精度,而且无法恢复

RGB565->RGB888

填充相应单色低位即可

II、代码

typedef struct 					//RGB888
{unsigned char Red;			//红色,[0,255]unsigned char Green;        //绿色,[0,255]unsigned char Blue;         //蓝色,[0,255]
}COLOR_RGB;void RGB565_To_RGB888(u16 rgb,COLOR_RGB *color_rgb)
{color_rgb->Red 	    = (unsigned char)( ( rgb & 0xF800 ) >> 8 );color_rgb->Green    = (unsigned char)( ( rgb & 0x07E0 ) >> 3 );color_rgb->Blue 	= (unsigned char)( ( rgb & 0x001F ) << 3 );	
}

3、RGB转HSL

I、什么是HSL

 HSL的H(hue)分量,代表的是人眼所能感知的颜色范围,这些颜色分布在一个平面的色相环上,取值范围是0°到360°的圆心角,每个角度可以代表一种颜色。色相值的意义在于,我们可以在不改变光感的情况下,通过旋转色相环来改变颜色。在实际应用中,我们需要记住色相环上的六大主色,用作基本参照:360°/0°红、60°黄、120°绿、180°青、240°蓝、300°洋红,它们在色相环上按照60°圆心角的间隔排列

HSL的S(saturation)分量,指的是色彩的饱和度,它用0%至100%的值描述了相同色相、明度下色彩纯度的变化。数值越大,颜色中的灰色越少,颜色越鲜艳,呈现一种从灰度到纯色的变化。

HSL的L(lightness)分量,指的是色彩的明度,作用是控制色彩的明暗变化。它同样使用了0%至100%的取值范围。数值越小,色彩越暗,越接近于黑色;数值越大,色彩越亮,越接近于白色。

 II、转换公式

 III、代码

typedef struct					//HLS颜色
{unsigned char Hue;			//色度,[0,240]				unsigned char Lightness;	//亮度,[0,240]	     unsigned char Saturation;	//饱和度,[0,240]	     
}COLOR_HLS;#define maxOf3Values( v1, v2, v3 )			( (v1>v2) ? ( (v1>v3) ? (v1) : (v3) ) : ( (v2>v3) ? (v2) : (v3) ) ) //取rgb中的最大值
#define minOf3Values( v1, v2, v3 )			( (v1<v2) ? ( (v1<v3) ? (v1) : (v3) ) : ( (v2<v3) ? (v2) : (v3) ) ) //取rgb中的最小值void RGB888_TO_HSL(COLOR_RGB* color_rgb, COLOR_HLS* color_hls)
{unsigned char r, g, b;unsigned char h, l, s;unsigned char max, min, dif;r = color_rgb->Red;g = color_rgb->Green;b = color_rgb->Blue;max = maxOf3Values( r, g, b );  //取rgb中的最大值min = minOf3Values( r, g, b );  //取rgb中的最小值dif = max - min;                //计算最大值和最小值的差值//计算l,亮度l = ( max + min ) * 240 / 255 / 2;//计算h,色度if( max == min )//无定义 RGB一样  黑灰白{s = 0;//饱和度0h = 0;//色度0}else{/*计算色度h*/if( max == r )      //如果R值最大{if( g >= b )    //h介于0到40{h = 40 * ( g - b ) / dif;}else if( g < b )//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 )      /* 0<l<=1/2 */{s = dif * 240 / ( max + min );                  }else                    /* l>1/2 */{s = dif * 240 / ( 480 - ( max + min ) );        }		 }   color_hls->Hue = h;				//色度color_hls->Lightness = l;		//亮度color_hls->Saturation = s;		//饱和度
}

3、输出一张摄像头二值化图片

I、原理

摄像头采集到的每个像素点,先由RGB格式转换成HSL格式,再与定义阈值进行对比,判断颜色是否和定义颜色匹配

II、代码 

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 short WIDTH_MIN;	//目标最小宽度unsigned short HEIGHT_MIN;	//目标最小高度unsigned short WIDTH_MAX;   //目标最大宽度unsigned short HEIGHT_MAX;	//目标最大高度
}TARGET_CONDITION;int ColorMatch(const COLOR_HLS* color_hls, const TARGET_CONDITION* 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 )  //设定的最大颜色小于最小颜色 说明有向下溢出 可能需要和高位颜色匹配            {/*0——有效——最大值——无效——最小值——有效——240*/if( color_hls->Hue <= condition->H_MAX )     //小于最大值return 1;if( color_hls->Hue >= condition->H_MIN )     //大于最小值return 1;} }return 0;
}

判断颜色是否和定义颜色匹配。

输入变量,color_hls:COLOR_HLS结构体,存储HLS格式颜色数据; 

                 condition :TARGET_CONDITION结构体,存放希望的颜色数据阈值。

返回数据,1:像素点颜色在目标范围内;0:像素点颜色不在目标范围内。

u8 Bmp_Minimize_SDRAM[240][40];  /*用于存放二值化后像素点数据,Bmp_Minimize_SDRAM[X][Y]*/TARGET_CONDITION condition0={40,		  //目标最小色度,H_MIN80,       //目标最大色度,H_MAX0,        //目标最小饱和度,S_MIN240,       //目标最大饱和度,S_MAX0,        //目标最小亮度,L_MIN240,       //目标最大亮度,L_MAX40,        //目标最小宽度,WIDTH_MIN40,        //目标最小高度,HEIGHT_MIN240,       //目标最大宽度,WIDTH_MAX320        //目标最大高度,HEIGHT_MAX
};for(x_size=0;x_size<240;x_size++)   //此种方式可以兼容任何彩屏,但是速度很慢
{for(y_size=0;y_size<320;y_size++){OV7725_RCK_L;color=GPIOE->IDR;	//读数据OV7725_RCK_H;colorH=(color>>8) &0xff;OV7725_RCK_L;color=GPIOE->IDR ;	//读数据OV7725_RCK_H; colorL=(color>>8) &0xff;color=(colorH<<8)|colorL;RGB565_TO_HSL(color,&hls_value);        /*RGB565转换为HLS*/if(y_size%8==0){Bmp_Minimize_SDRAM[x_size][y_size/8]=color_buffer;      /*二值化后的数据,存入缓存*/}color_buffer<<=1;color_buffer|=ColorMatch(&hls_value,&condition0);}
}

摄像头采集数据二值化,由于STM32F1内存有限,存放不了一帧320*240的彩色图像,故将处理好后的数据存入数组

while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。
USART_SendData(UART4, 0x01);
while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。
USART_SendData(UART4, 0xfe);
for(y_size=0;y_size<240;y_size++)     /*数组数据串口打印到电脑*/
{for(x_size=0;x_size<40;x_size++){color=Bmp_Minimize_SDRAM[y_size][x_size+1];for(color_buffer=0;color_buffer<8;color_buffer++){if((color<<color_buffer) &0x80){while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。USART_SendData(UART4, 0xff);while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。USART_SendData(UART4, 0xff);}else{while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。USART_SendData(UART4, 0x00);while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。USART_SendData(UART4, 0x00);                        }}}
}
while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。
USART_SendData(UART4, 0xfe);
while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET);      //判断是否发送完成。
USART_SendData(UART4, 0x01);

将存放在缓存中的二值化数据,通过简单位操作输出到调试助手

四、简单的物体识别

1、原理参考

首先遍历寻找腐蚀中心,然后在之前腐蚀中心点处进行迭代向外寻找新的腐蚀中心。腐蚀算法从该点开始分别向上下左右四个方向进行读点,若点的颜色符合条件则往外读,等四个方向都结束后得到四个边缘点的坐标,记左边缘点的x轴坐标为left,右边缘点的x轴坐标为right,上边缘点的y轴坐标为up,下边缘点的y轴坐标为bottom,那么坐标( (right-left)/2 , (up-bottom)/2 ) 即为新的腐蚀中心。当确定4个点通过,泽围绕4个点画框,将色块识别区域框起来。

2、腐蚀中心算法(如下图详解)
一个40/3=13 1313大小的色块为单位进行识别
每次只读取这色块的以y的2/1 为点(也就是这个1313色块y轴为中心点) x轴向右开始查询识别颜色 如识别失败个数大于6次 则认为这一个1313的色块无效 跳出循环 继续查询下一个1313的色块。

如果失败次数少于6次则改变查询方向 查询这色块的以X/2 为点 对y轴进行查询方法同上 ,如果y轴有6次失败则,退出循环不认同这个色块合格(因为太小了,失败次数又多),但如果少于六次失败,则认为这个色块识别成功 并记录下这个色块的中心点,这就是腐蚀中心 /。

 

 

 2、识别代码

extern u8 Bmp_Minimize_SDRAM[240][40];static uint8_t ReadColor( uint16_t usX, uint16_t usY)
{uint8_t color_value;usY=usY/8;color_value=Bmp_Minimize_SDRAM[usX][usY];usY=usY%8;return (color_value>>usY)&0x01;
}#define IMG_X 0				//图片x坐标
#define IMG_Y 0             //图片y坐标
#define IMG_W 240           //图片宽度
#define IMG_H 320           //图片高度#define ALLOW_FAIL_PER 10    //容错率
#define ITERATER_NUM   8   //迭代次数/*** @brief  寻找腐蚀中心* @param  x :腐蚀中心x坐标* @param  y :腐蚀中心y坐标* @param  condition :TARGET_CONDITION结构体,存放希望的颜色数据阈值* @param  area :SEARCH_AREA结构体,查找腐蚀中心的区域* @retval 1:找到了腐蚀中心,x、y为腐蚀中心的坐标;0:没有找到腐蚀中心。*/
static int SearchCenter(unsigned short* x, unsigned short* y, const TARGET_CONDITION* condition, SEARCH_AREA* area )
{unsigned short i, j, k;unsigned short FailCount=0;unsigned short SpaceX, SpaceY;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)    //查询色块中间一横的颜色{   if(ReadColor(j+k, i+SpaceY/2)==0){FailCount++;     //颜色不匹配 失败计数+1 }}else            //查询色块中间一竖的颜色     {if(ReadColor( j+SpaceX/2, i+k-SpaceX)==0){FailCount++;    //颜色不匹配 失败计数+1}}if(FailCount>( (SpaceX+SpaceY) / ALLOW_FAIL_PER ))     //失败计数大于 色块需要查询的总点数/容错率break;  //失败次数太多 退出}if(k == SpaceX+SpaceY)  //k坚持到查询完毕,说明基本匹配{/*记录该色块的中心点为腐蚀中心*/*x = j + SpaceX / 2;*y = i + SpaceY / 2;return 1;   //记录到第一个腐蚀中心后退出函数}}	}return 0;	
}/*** @brief  从腐蚀中心向外腐蚀,得到新的腐蚀中心* @param  oldX :先前的腐蚀中心x坐标* @param  oldX :先前的腐蚀中心y坐标* @param  condition :TARGET_CONDITION结构体,存放希望的颜色数据阈值* @param  result :RESULT结构体,存放检测结果* @retval 1:检测成功;0:检测失败。*/
static int Corrode(unsigned short oldX, unsigned short oldY, const TARGET_CONDITION* condition, RESULT* result )
{unsigned short Xmin, Xmax, Ymin, Ymax;unsigned short i;unsigned short FailCount=0;for(i=oldX; i>IMG_X; i--)           //从中心点查到x最左侧{if(!ReadColor(i, oldY))FailCount++;    //不匹配计数自加1if( FailCount> ((condition->WIDTH_MIN)/ALLOW_FAIL_PER) | i<=0)//当识别失败点大于最小宽度/2是跳出  break;}Xmin=i;     //更新X轴最小坐标值FailCount=0;    //清空错误次数for(i=oldX; i<IMG_X+IMG_W; i++)     //从中心点查到x最右侧{if(!ReadColor(i, oldY))FailCount++;    //不匹配计数自加1if( FailCount> ((condition->WIDTH_MIN)/ALLOW_FAIL_PER) | i>=240)break;}Xmax=i;     //更新X轴最大坐标值FailCount=0;    //清空错误次数for(i=oldY; i>IMG_Y; i--)           //从中心点查到y最上侧{if(!ReadColor(oldX, i)) FailCount++;    //不匹配计数自加1if( FailCount> ((condition->HEIGHT_MIN)/ALLOW_FAIL_PER) | i<=0)break;}Ymin=i;     //更新Y轴最小坐标值FailCount=0;    //清空错误次数for(i=oldY; i<IMG_Y+IMG_H; i++)     //从中心点查到y最下侧{if(!ReadColor(oldX, i))FailCount++;if( FailCount> ((condition->HEIGHT_MIN)/ALLOW_FAIL_PER)| i>=320)break;}Ymax=i;     //更新Y轴最大坐标值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;   //如果腐蚀后的区域没有超过最大限定区域且没有小于最小限定区域 有效!! }return 0;
}/*** @brief  用户将识别条件写入Condition指向的结构体中,该函数将返回目标的x,y坐标和长宽* @param  condition :TARGET_CONDITION结构体,存放希望的颜色数据阈值* @param  result :RESULT结构体,存放检测结果* @retval 1:检测成功;0:检测失败。*/
int Trace(const TARGET_CONDITION* condition, RESULT* result_final)
{unsigned char i;static unsigned short x0, y0;static unsigned char Flag=0;static SEARCH_AREA area = {IMG_X, IMG_X+IMG_W, IMG_Y, IMG_Y+IMG_H};//搜索区域RESULT result;      //RESULT识别结果if(Flag==0)     //如果首次使用或上一次腐蚀失败{if(SearchCenter(&x0, &y0, condition, &area))    //搜索腐蚀中心并返回给x0,y0,如果成功搜索到,那么flag置1{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置1{Flag = 1;return 1;}else{Flag = 0;return 0;               }}      }//找到腐蚀中心 得到中点result.x = x0;      //如果flag!=0,说明上一次有腐蚀中心结果,所以直接使用上一次结果腐蚀即可,而不需要再次遍历图像搜索腐蚀中心result.y = y0;      //上一次的腐蚀中心赋值给这次的oldx,oldyfor(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,返回失败值0{Flag=0;return 0;}
}

代码有点多,都有写注释哈,就不一一讲解了

3、显示代码

void GUI_DrawPoint(u16 x,u16 y)
{u8 color_value=0x80;color_value>>=y%8;Bmp_Minimize_SDRAM[x][y/8]|=color_value;
}void GUI_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2)
{u16 t; int xerr=0,yerr=0,delta_x,delta_y,distance; int incx,incy,uRow,uCol; delta_x=x2-x1; //计算坐标增量 delta_y=y2-y1; uRow=x1; uCol=y1; if(delta_x>0)incx=1; //设置单步方向 else if(delta_x==0)incx=0;//垂直线 else {incx=-1;delta_x=-delta_x;} if(delta_y>0)incy=1; else if(delta_y==0)incy=0;//水平线 else{incy=-1;delta_y=-delta_y;} if( delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴 else distance=delta_y; for(t=0;t<=distance+1;t++ )//画线输出 {  GUI_DrawPoint(uRow,uCol);//画点 xerr+=delta_x ; yerr+=delta_y ; if(xerr>distance) { xerr-=distance; uRow+=incx; } if(yerr>distance) { yerr-=distance; uCol+=incy; } }  
} void GUI_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2)
{GUI_DrawLine(x1,y1,x2,y1);GUI_DrawLine(x1,y1,x1,y2);GUI_DrawLine(x1,y2,x2,y2);GUI_DrawLine(x2,y1,x2,y2);
}

在二值化数据中画矩形

if(Trace(&condition0, &result))
{for(y_size=0;y_size<240;y_size++)     /*检测到物体,清空二值化数据*/{for(x_size=0;x_size<40;x_size++){Bmp_Minimize_SDRAM[y_size][x_size]=0;}}/*画矩形,和中心点*/GUI_DrawRectangle ( result.x-result.w/2, result.y-result.h/2, result.x+result.w/2, result.y+result.h/2);GUI_DrawLine(result.x-10,result.y,result.x+10,result.y);GUI_DrawLine(result.x,result.y-10,result.x,result.y+10);
}

识别物体满足颜色和大小的判断条件,更新二值化数组,用矩形框出物体,并标出中心点 


总结

第一次写博客,有诸多不足,望多包涵,后续会更新多物体识别和图形识别。

END...

参考文章

stm32 OV7670/摄像头模块颜色区域定位(腐蚀中心算法)_摄像头模块颜色识别_闰土小蒋的博客-CSDN博客

识别车牌-识别颜色-基于stm32f4 ov7670(无晶振,无fifo,ov7725,ov2640类似可用)_stm32f4 ov7670颜色识别_qq斯国一的博客-CSDN博客

STM32+ov7725图像识别(HSL原理)_stm32图像识别_大桶矿泉水的博客-CSDN博客

《STM32》EasyTrace物体追踪 源代码个人注释+完整例程 - 知乎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/247885.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ElasticSearch搜索引擎入门到精通

ES 是基于 Lucene 的全文检索引擎,它会对数据进行分词后保存索引,擅长管理大量的数据,相对于 MySQL 来说不擅长经常更新数据及关联查询。这篇文章就是为了进一步了解一下它,到底是如何做到这么高效的查询的。 在学习其他数据库的时候我们知道索引是一个数据库系统极其重要…

数字图像处理(实践篇)三十六 OpenCV-Python 使用ORB和BFmatcher对两个输入图像的关键点进行匹配实践

目录 一 涉及的函数 二 实践 ORB(Oriented FAST and Rotated BRIEF)是一种特征点检测和描述算法,它结合了FAST关键点检测和BRIEF描述子。ORB算法具有以下优势: ①实时性:能够在实时应用中进行快速的特征点检测和描述。 ②

[C++]使用纯opencv部署yolov8旋转框目标检测

【官方框架地址】 https://github.com/ultralytics/ultralytics 【算法介绍】 YOLOv8是一种先进的对象检测算法&#xff0c;它通过单个神经网络实现了快速的物体检测。其中&#xff0c;旋转框检测是YOLOv8的一项重要特性&#xff0c;它可以有效地检测出不同方向和角度的物体。…

git用法总结

以gitee为例&#xff0c;GitHub也可参考本文 创建远程仓库 在自己的gitee主页 创建本地仓库 在文件夹下&#xff0c;右键→git bash here git init添加gitignore vi .gitignoregitignore里的内容根据自己实际情况设置&#xff0c;这里举个例子 # #开头的是注释 # Prer…

Oracle篇—分区索引的重建和管理(第三篇,总共五篇)

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…

写静态页面——魅族导航_前端页面练习

0、效果&#xff1a; 1、html代码&#xff1a;&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><…

Spring Boot通过配置文件支持数据库自定义表名

直接上干货&#xff1a; 例如一个叫xxx的项目&#xff0c;yml文件里加上这段 xxxproject:db:xxxTable: xxx_dbname #自定义的数据库表名创一个Configuration类放表名和Mapper // XxxProjectAutoConfiguration.javaConfiguration MapperScan(basePackages "cn.com.xxxp…

【vue】defineModel在vue3.4中的最新用法和详解

在2023年12月28日&#xff0c;尤大发布了vue3.4版本&#xff0c;这个版本主要对一些实验性特性的改进&#xff08;比如defineModel&#xff09;&#xff0c;大量重写了模板编译器并重构了响应式系统&#xff0c;可以说是大大提升了运行速度和效率。 之前在vue3.3中defineModel…

分布式ID是什么,以美团Leaf为例改造融入自己项目【第十一期】

前言 在日常开发中&#xff0c;主键id应用是非常广泛的&#xff0c;但是当涉及到分布式系统的时候&#xff0c;往往需要使用到分布式id&#xff0c;每一个服务里面一套生成规则的不易管理&#xff0c;容易引发冲突。我的IM聊天系统中使用分布式id来生成消息唯一键,为后面幂等做…

Flink CEP实现10秒内连续登录失败用户分析

1、什么是CEP&#xff1f; Flink CEP即 Flink Complex Event Processing&#xff0c;是基于DataStream流式数据提供的一套复杂事件处理编程模型。你可以把他理解为基于无界流的一套正则匹配模型&#xff0c;即对于无界流中的各种数据(称为事件)&#xff0c;提供一种组合匹配的…

网络防御安全:2-6天笔记

第二章&#xff1a;防火墙 一、什么是防火墙 防火墙的主要职责在于&#xff1a;控制和防护。 防火墙可以根据安全策略来抓取流量之后做出对应的动作。 二、防火墙的发展 区域&#xff1a; Trust 区域&#xff0c;该区域内网络的受信任程度高&#xff0c;通常用来定义内部…

单片机介绍

本文为博主 日月同辉&#xff0c;与我共生&#xff0c;csdn原创首发。希望看完后能对你有所帮助&#xff0c;不足之处请指正&#xff01;一起交流学习&#xff0c;共同进步&#xff01; > 发布人&#xff1a;日月同辉,与我共生_单片机-CSDN博客 > 欢迎你为独创博主日月同…

electron-builder vue 打包后element-ui字体图标不显示问题

当使用electron打包完成的时候&#xff0c;启动项目发现使用的element-ui字体图标没显示都变成了小方块&#xff0c;并出现报错&#xff0c;请看下图&#xff1a; 解决方法&#xff1a; 在vue.config.js中设置 customFileProtocol字段&#xff1a;pluginOptions: {electronBui…

两个近期的计算机领域国际学术会议(软件工程、计算机安全):欢迎投稿

近期&#xff0c;受邀担任两个国际学术会议的Special session共同主席及程序委员会成员&#xff08;TPC member&#xff09;&#xff0c;欢迎广大学界同行踊跃投稿&#xff0c;分享最新研究成果。期待这个夏天能够在夏威夷檀香山或者加利福尼亚圣荷西与各位学者深入交流。 SERA…

防御保护常用知识

防火墙的主要职责在于&#xff1a;控制和防护 --- 安全策略 --- 防火墙可以根据安全策略来抓取流量之 后做出对应的动作 防火墙分类主要有四类&#xff1a; 防火墙吞吐量 --- 防火墙同一时间能处理的数据量多少 防火墙的发展主要经过以下阶段&#xff1b; 传统防火墙&#xf…

格子表单GRID-FORM | 嵌套子表单与自定义脚本交互

格子表单/GRID-FORM已在Github 开源&#xff0c;如能帮到您麻烦给个星&#x1f91d; GRID-FORM 系列文章 基于 VUE3 可视化低代码表单设计器嵌套表单与自定义脚本交互 新版本功能 &#x1f389; 不觉间&#xff0c;GRID-FORM 已经开源一年&#xff08;2023年1月29日首次提交…

大数据StarRocks(八):资源隔离实战

前言 自 2.2 版本起&#xff0c;StarRocks 支持资源组管理&#xff0c;集群可以通过设置资源组&#xff08;Resource Group&#xff09;的方式限制查询对资源的消耗&#xff0c;实现多租户之间的资源隔离与合理利用。在 2.3 版本中&#xff0c;StarRocks 支持限制大查询&#…

HAL STM32+EC11编码器实现增减调节及单击、双击、长按功能

HAL STM32EC11编码器实现增减调节及单击、双击、长按功能 &#x1f4fa;实现效果演示&#xff1a; &#x1f4d8;内容提要 &#x1f4dd;本文主要实现&#xff0c;通过STM32 HAL库开发&#xff0c;实现的EC11编码器功能&#xff0c;按键结合状态机思想实现的拓展单击、双击、…

Linux之快速入门(CentOS 7)

文章目录 一、Linux目录结构二、常用命令2.1 切换用户2.2查看ip地址2.3 cd2.4 目录查看2.5 查看文件内容2.6 创建目录及文件2.7 复制和移动2.8 其他2.9 tar3.0 which3.1 whereis3.2 find&#xff08;这个命令尽量在少量用户使用此软件时运行&#xff0c;因为此命令是真的读磁盘…

计数排序(六)——计数排序及排序总结

目录 一.前言 二.归并小补充 三.计数排序 操作步骤&#xff1a; 代码部分&#xff1a; 四.稳定性的概念&#xff1a; 五.排序大总结&#xff1a; ​六.结语 一.前言 我们已经进入排序的尾篇了&#xff0c;本篇主要讲述计数排序以及汇总各类排序的特点。码字不易&#x…