目錄
前言
一、攝像頭采集數(shù)據(jù)流程
二、如何將圖像顯示到電腦上
?三、圖像二值化
1、什么是RGB?
2、RGB565轉(zhuǎn)RGB888
I、RGB565和RGB888的區(qū)別
II、代碼
3、RGB轉(zhuǎn)HSL
I、什么是HSL
?II、轉(zhuǎn)換公式
?III、代碼
3、輸出一張攝像頭二值化圖片
I、原理
II、代碼?
四、簡單的物體識別
1、原理參考
?2、識別代碼
3、顯示代碼
總結(jié)
前言
前陣子用STM32弄攝像頭,斷斷續(xù)續(xù)有段時間,也在網(wǎng)上翻閱了不少資料,寫篇博客記錄一下學習過程。最后成功識別單個物體,圖形和多個物體暫不支。
一、攝像頭采集數(shù)據(jù)流程
(1) 利用 SIO_C、SIO_D 引腳通過 SCCB 協(xié)議向 OV7725 的寄存器寫入初始化配置;
(2) 初始化完成后,OV7725 傳感器會使用 VGA 時序輸出圖像數(shù)據(jù),它的 VSYNC 會
首先輸出幀有效信號(低電平跳變),當外部的控制器(如 STM32)檢測到該信號
時,把 WEN 引腳設置為高電平,并且使用 WRST 引腳復位 FIFO 的寫指針到 0 地
址;
(3) 隨著 OV7725 繼續(xù)按 VGA 時序輸出圖像數(shù)據(jù),它在傳輸每行有效數(shù)據(jù)時, HREF
引腳都會持續(xù)輸出高電平,由于 WEN 和 HREF 同時為高電平輸入至與非門,使得
其連接到 FIFO WE 引腳的輸出為低電平,允許向 FIFO 寫入數(shù)據(jù),所以在這期間,
OV7725 通過它的 PCLK 和 D[0:7]信號線把圖像數(shù)據(jù)存儲到 FIFO 中,由于前面復
位了寫指針,所以圖像數(shù)據(jù)是從 FIFO 的 0 地址開始記錄的;
(4) 各行圖像數(shù)據(jù)持續(xù)傳輸至 FIFO,受 HREF 控制的 WE 引腳確保了寫入到 FIFO 中
的都是有效的圖像數(shù)據(jù),OV7725 輸出完一幀數(shù)據(jù)時,VSYNC 會再次輸出幀有效
信號,表示一幀圖像已輸出完成;
(5) 控制器檢測到上述 VSYNC 信號后,可知 FIFO 中已存儲好一幀圖像數(shù)據(jù),這時控
制 WEN 引腳為低電平,使得 FIFO 禁止寫入,防止 OV7725 持續(xù)輸出的下一幀數(shù)
據(jù)覆蓋當前 FIFO 數(shù)據(jù);
(6) 控制器使用RRST復位讀指針到FIFO的0地址,然后通過FIFO的RCLK和DO[0:7]
引腳,從 0 地址開始把 FIFO 緩存的整幀圖像數(shù)據(jù)讀取出來。在這期間,OV7725
是持續(xù)輸出它采集到的圖像數(shù)據(jù)的,但由于禁止寫入 FIFO,這些數(shù)據(jù)被丟棄了;
(7) 控制器使用 WRST 復位寫指針到 FIFO 的 0 地址,然后等待新的 VSYNC 有效信號
到來,檢測到后把 WEN 引腳設置為高電平,恢復 OV7725 向 FIFO 的寫入權(quán)限,
OV7725 輸出的新一幀圖像數(shù)據(jù)會被寫入到 FIFO 的 0 地址中,重復上述過程。
網(wǎng)上也有很多現(xiàn)成的文章,分享幾個鏈接
攝像頭原理:
stm32 OV7670攝像頭模塊的介紹以及應用(SCCB的使用)_閏土小蔣的博客-CSDN博客
正點原子開發(fā)板程序:
http://www.openedv.com/docs/modules/camera/ov7725-fifo.html
二、如何將圖像顯示到電腦上
使用串口發(fā)送數(shù)據(jù)到電腦,用山外調(diào)試助手顯示,這里的波特率使用256000,因為調(diào)試助手的最大也只能是256000,圖像配置,寬:320,高:240,RGB565大端(這個是個大坑)
一副圖像的通信協(xié)議為:[0x01] [0xFE][…數(shù)據(jù)…][0xFE] [0x01]
[…數(shù)據(jù)…] 是圖像的數(shù)據(jù),一幀圖像有多少數(shù)據(jù),這里的數(shù)據(jù)長度就有多少。換句話說, […數(shù)據(jù)…]與圖像的格式,圖像的寬高有關(guān)。此處的圖像數(shù)據(jù),都是從上往下,從左往右存儲 的。 只有下位機發(fā)送的數(shù)據(jù)與上位機配置的格式的長度相同時,才可正確識別圖像格式,從 而正確顯示圖像。 下位機發(fā)送圖像時,先發(fā)送幀頭:0x01,0xFE,接著發(fā)送圖像數(shù)據(jù),最后發(fā)送幀尾: 0xFE,0x01 完成一副圖像發(fā)送。
鏈接:https://pan.baidu.com/s/1eA3rhtNiocKxtH0rxvyGzg?pwd=0722?
提取碼:0722
攝像頭采集代碼如下:
?? ??? ?OV7725_RRST(0);?? ??? ??? ??? ?//開始復位讀指針?
?? ??? ?OV7725_RCK_L;
?? ??? ?OV7725_RCK_H;
?? ??? ?OV7725_RCK_L;
?? ??? ?OV7725_RRST(1);?? ??? ??? ??? ?//復位讀指針結(jié)束?
?? ??? ?OV7725_RCK_H;
? ?
? ? ? ? while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET); ? ? ?//判斷是否發(fā)送完成。
? ? ? ? USART_SendData(UART4, 0x01);
? ? ? ? while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET); ? ? ?//判斷是否發(fā)送完成。
? ? ? ? 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;?? ?//讀數(shù)據(jù)
?? ??? ??? ??? ?OV7725_RCK_H;
?? ??? ??? ??? ?colorH=(color>>8) &0xff;
?? ??? ??? ??? ?OV7725_RCK_L;
?? ??? ??? ??? ?color=GPIOE->IDR ;?? ?//讀數(shù)據(jù)
?? ??? ??? ??? ?OV7725_RCK_H;?
?? ??? ??? ??? ?colorL=(color>>8) &0xff;
?? ??? ??? ??? ?
? ? ? ? ? ? ? ? while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET); ? ? ?//判斷是否發(fā)送完成。
? ? ? ? ? ? ? ? USART_SendData(UART4, colorH);
? ? ? ? ? ? ? ? while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET); ? ? ?//判斷是否發(fā)送完成。
? ? ? ? ? ? ? ? USART_SendData(UART4, colorL);
?? ??? ??? ?}
?? ??? ?}
? ? ? ? while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET); ? ? ?//判斷是否發(fā)送完成。
? ? ? ? USART_SendData(UART4, 0xfe);
? ? ? ? while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET); ? ? ?//判斷是否發(fā)送完成。
? ? ? ? USART_SendData(UART4, 0x01);
?三、圖像二值化
1、什么是RGB?
????????RGB是從顏色發(fā)光的原理來設計定的,通俗點說它的顏色混合方式就好像有紅、綠、藍三盞燈,當它們的光相互疊合的時候,色彩相混,而亮度卻等于兩者亮度之總和,越混合亮度越高,即加法混合。
????????紅、綠、藍三個顏色通道每種色各分為256階亮度,在0時“燈”最弱——是關(guān)掉的,而在255時“燈”最亮。當三色灰度數(shù)值相同時,產(chǎn)生不同灰度值的灰色調(diào),即三色灰度都為0時,是最暗的黑色調(diào);三色灰度都為255時,是最亮的白色調(diào)。
????????在電腦中,RGB的所謂“多少”就是指亮度,并使用整數(shù)來表示。通常情況下,RGB各有256級亮度,用數(shù)字表示為從0、1、2...直到255。注意雖然數(shù)字最高是255,但0也是數(shù)值之一,因此共256級。
2、RGB565轉(zhuǎn)RGB888
I、RGB565和RGB888的區(qū)別
在計算機中圖像基本是以RGB888格式顯示的,24位圖每個像素保存了32bit的數(shù)據(jù),即RGB888+Alpha,Alpha就是半透明填充字節(jié)。對于真彩的圖像而言,肉眼在16bit的時候已經(jīng)難以分辨了,有些時候,可以將RGB888轉(zhuǎn)換為RGB565來存儲,減少了存儲器的容量的同時,降低了數(shù)據(jù)量;在顯示的時候,再次把RGB565轉(zhuǎn)換為RGB888,實現(xiàn)數(shù)據(jù)寬度的匹配
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轉(zhuǎn)HSL
I、什么是HSL
?HSL的H(hue)分量,代表的是人眼所能感知的顏色范圍,這些顏色分布在一個平面的色相環(huán)上,取值范圍是0°到360°的圓心角,每個角度可以代表一種顏色。色相值的意義在于,我們可以在不改變光感的情況下,通過旋轉(zhuǎn)色相環(huán)來改變顏色。在實際應用中,我們需要記住色相環(huán)上的六大主色,用作基本參照:360°/0°紅、60°黃、120°綠、180°青、240°藍、300°洋紅,它們在色相環(huán)上按照60°圓心角的間隔排列
HSL的S(saturation)分量,指的是色彩的飽和度,它用0%至100%的值描述了相同色相、明度下色彩純度的變化。數(shù)值越大,顏色中的灰色越少,顏色越鮮艷,呈現(xiàn)一種從灰度到純色的變化。
HSL的L(lightness)分量,指的是色彩的明度,作用是控制色彩的明暗變化。它同樣使用了0%至100%的取值范圍。數(shù)值越小,色彩越暗,越接近于黑色;數(shù)值越大,色彩越亮,越接近于白色。
?II、轉(zhuǎn)換公式
?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;//飽和度0
h = 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格式轉(zhuǎn)換成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 ) //顏色在范圍內(nèi)
{
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結(jié)構(gòu)體,存儲HLS格式顏色數(shù)據(jù);?
? ? ? ? ? ? ? ? ?condition :TARGET_CONDITION結(jié)構(gòu)體,存放希望的顏色數(shù)據(jù)閾值。
返回數(shù)據(jù),1:像素點顏色在目標范圍內(nèi);0:像素點顏色不在目標范圍內(nèi)。
u8 Bmp_Minimize_SDRAM[240][40]; /*用于存放二值化后像素點數(shù)據(jù),Bmp_Minimize_SDRAM[X][Y]*/
TARGET_CONDITION condition0={
40, //目標最小色度,H_MIN
80, //目標最大色度,H_MAX
0, //目標最小飽和度,S_MIN
240, //目標最大飽和度,S_MAX
0, //目標最小亮度,L_MIN
240, //目標最大亮度,L_MAX
40, //目標最小寬度,WIDTH_MIN
40, //目標最小高度,HEIGHT_MIN
240, //目標最大寬度,WIDTH_MAX
320 //目標最大高度,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; //讀數(shù)據(jù)
OV7725_RCK_H;
colorH=(color>>8) &0xff;
OV7725_RCK_L;
color=GPIOE->IDR ; //讀數(shù)據(jù)
OV7725_RCK_H;
colorL=(color>>8) &0xff;
color=(colorH<<8)|colorL;
RGB565_TO_HSL(color,&hls_value); /*RGB565轉(zhuǎn)換為HLS*/
if(y_size%8==0)
{
Bmp_Minimize_SDRAM[x_size][y_size/8]=color_buffer; /*二值化后的數(shù)據(jù),存入緩存*/
}
color_buffer<<=1;
color_buffer|=ColorMatch(&hls_value,&condition0);
}
}
攝像頭采集數(shù)據(jù)二值化,由于STM32F1內(nèi)存有限,存放不了一幀320*240的彩色圖像,故將處理好后的數(shù)據(jù)存入數(shù)組
while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET); //判斷是否發(fā)送完成。
USART_SendData(UART4, 0x01);
while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET); //判斷是否發(fā)送完成。
USART_SendData(UART4, 0xfe);
for(y_size=0;y_size<240;y_size++) /*數(shù)組數(shù)據(jù)串口打印到電腦*/
{
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); //判斷是否發(fā)送完成。
USART_SendData(UART4, 0xff);
while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET); //判斷是否發(fā)送完成。
USART_SendData(UART4, 0xff);
}
else
{
while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET); //判斷是否發(fā)送完成。
USART_SendData(UART4, 0x00);
while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET); //判斷是否發(fā)送完成。
USART_SendData(UART4, 0x00);
}
}
}
}
while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET); //判斷是否發(fā)送完成。
USART_SendData(UART4, 0xfe);
while(USART_GetFlagStatus(UART4, USART_FLAG_TC)==RESET); //判斷是否發(fā)送完成。
USART_SendData(UART4, 0x01);
將存放在緩存中的二值化數(shù)據(jù),通過簡單位操作輸出到調(diào)試助手
四、簡單的物體識別
1、原理參考
首先遍歷尋找腐蝕中心,然后在之前腐蝕中心點處進行迭代向外尋找新的腐蝕中心。腐蝕算法從該點開始分別向上下左右四個方向進行讀點,若點的顏色符合條件則往外讀,等四個方向都結(jié)束后得到四個邊緣點的坐標,記左邊緣點的x軸坐標為left,右邊緣點的x軸坐標為right,上邊緣點的y軸坐標為up,下邊緣點的y軸坐標為bottom,那么坐標( (right-left)/2 , (up-bottom)/2 ) 即為新的腐蝕中心。當確定4個點通過,澤圍繞4個點畫框,將色塊識別區(qū)域框起來。
2、腐蝕中心算法(如下圖詳解)
一個40/3=13 1313大小的色塊為單位進行識別
每次只讀取這色塊的以y的2/1 為點(也就是這個1313色塊y軸為中心點) x軸向右開始查詢識別顏色 如識別失敗個數(shù)大于6次 則認為這一個1313的色塊無效 跳出循環(huán) 繼續(xù)查詢下一個1313的色塊。
如果失敗次數(shù)少于6次則改變查詢方向 查詢這色塊的以X/2 為點 對y軸進行查詢方法同上 ,如果y軸有6次失敗則,退出循環(huán)不認同這個色塊合格(因為太小了,失敗次數(shù)又多),但如果少于六次失敗,則認為這個色塊識別成功 并記錄下這個色塊的中心點,這就是腐蝕中心 /。
?
?
?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 //迭代次數(shù)
/**
* @brief 尋找腐蝕中心
* @param x :腐蝕中心x坐標
* @param y :腐蝕中心y坐標
* @param condition :TARGET_CONDITION結(jié)構(gòu)體,存放希望的顏色數(shù)據(jù)閾值
* @param area :SEARCH_AREA結(jié)構(gòu)體,查找腐蝕中心的區(qū)域
* @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; //失敗次數(shù)初始化
for(k=0; k<SpaceX+SpaceY; k++)
{
if(k<SpaceX) //查詢色塊中間一橫的顏色
{
if(ReadColor(j+k, i+SpaceY/2)==0)
{
FailCount++; //顏色不匹配 失敗計數(shù)+1
}
}
else //查詢色塊中間一豎的顏色
{
if(ReadColor( j+SpaceX/2, i+k-SpaceX)==0)
{
FailCount++; //顏色不匹配 失敗計數(shù)+1
}
}
if(FailCount>( (SpaceX+SpaceY) / ALLOW_FAIL_PER )) //失敗計數(shù)大于 色塊需要查詢的總點數(shù)/容錯率
break; //失敗次數(shù)太多 退出
}
if(k == SpaceX+SpaceY) //k堅持到查詢完畢,說明基本匹配
{
/*記錄該色塊的中心點為腐蝕中心*/
*x = j + SpaceX / 2;
*y = i + SpaceY / 2;
return 1; //記錄到第一個腐蝕中心后退出函數(shù)
}
}
}
return 0;
}
/**
* @brief 從腐蝕中心向外腐蝕,得到新的腐蝕中心
* @param oldX :先前的腐蝕中心x坐標
* @param oldX :先前的腐蝕中心y坐標
* @param condition :TARGET_CONDITION結(jié)構(gòu)體,存放希望的顏色數(shù)據(jù)閾值
* @param result :RESULT結(jié)構(gòu)體,存放檢測結(jié)果
* @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最左側(cè)
{
if(!ReadColor(i, oldY))
FailCount++; //不匹配計數(shù)自加1
if( FailCount> ((condition->WIDTH_MIN)/ALLOW_FAIL_PER) | i<=0)//當識別失敗點大于最小寬度/2是跳出
break;
}
Xmin=i; //更新X軸最小坐標值
FailCount=0; //清空錯誤次數(shù)
for(i=oldX; i<IMG_X+IMG_W; i++) //從中心點查到x最右側(cè)
{
if(!ReadColor(i, oldY))
FailCount++; //不匹配計數(shù)自加1
if( FailCount> ((condition->WIDTH_MIN)/ALLOW_FAIL_PER) | i>=240)
break;
}
Xmax=i; //更新X軸最大坐標值
FailCount=0; //清空錯誤次數(shù)
for(i=oldY; i>IMG_Y; i--) //從中心點查到y(tǒng)最上側(cè)
{
if(!ReadColor(oldX, i))
FailCount++; //不匹配計數(shù)自加1
if( FailCount> ((condition->HEIGHT_MIN)/ALLOW_FAIL_PER) | i<=0)
break;
}
Ymin=i; //更新Y軸最小坐標值
FailCount=0; //清空錯誤次數(shù)
for(i=oldY; i<IMG_Y+IMG_H; i++) //從中心點查到y(tǒng)最下側(cè)
{
if(!ReadColor(oldX, i))
FailCount++;
if( FailCount> ((condition->HEIGHT_MIN)/ALLOW_FAIL_PER)| i>=320)
break;
}
Ymax=i; //更新Y軸最大坐標值
FailCount=0; //清空錯誤次數(shù)
//獲得腐蝕區(qū)域的中點和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; //如果腐蝕后的區(qū)域沒有超過最大限定區(qū)域且沒有小于最小限定區(qū)域 有效?。?
}
return 0;
}
/**
* @brief 用戶將識別條件寫入Condition指向的結(jié)構(gòu)體中,該函數(shù)將返回目標的x,y坐標和長寬
* @param condition :TARGET_CONDITION結(jié)構(gòu)體,存放希望的顏色數(shù)據(jù)閾值
* @param result :RESULT結(jié)構(gòu)體,存放檢測結(jié)果
* @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};//搜索區(qū)域
RESULT result; //RESULT識別結(jié)果
if(Flag==0) //如果首次使用或上一次腐蝕失敗
{
if(SearchCenter(&x0, &y0, condition, &area)) //搜索腐蝕中心并返回給x0,y0,如果成功搜索到,那么flag置1
{
Flag = 1;
}
else //如果還沒腐蝕成功,那么把腐蝕區(qū)域再次擴大到整個圖像范圍內(nèi)進行腐蝕
{
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,說明上一次有腐蝕中心結(jié)果,所以直接使用上一次結(jié)果腐蝕即可,而不需要再次遍歷圖像搜索腐蝕中心
result.y = y0; //上一次的腐蝕中心賦值給這次的oldx,oldy
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;
//更新/返回結(jié)果值
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);
}
在二值化數(shù)據(jù)中畫矩形
if(Trace(&condition0, &result))
{
for(y_size=0;y_size<240;y_size++) /*檢測到物體,清空二值化數(shù)據(jù)*/
{
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);
}
識別物體滿足顏色和大小的判斷條件,更新二值化數(shù)組,用矩形框出物體,并標出中心點?
總結(jié)
第一次寫博客,有諸多不足,望多包涵,后續(xù)會更新多物體識別和圖形識別。
END...
參考文章
stm32 OV7670/攝像頭模塊顏色區(qū)域定位(腐蝕中心算法)_攝像頭模塊顏色識別_閏土小蔣的博客-CSDN博客
識別車牌-識別顏色-基于stm32f4 ov7670(無晶振,無fifo,ov7725,ov2640類似可用)_stm32f4 ov7670顏色識別_qq斯國一的博客-CSDN博客
STM32+ov7725圖像識別(HSL原理)_stm32圖像識別_大桶礦泉水的博客-CSDN博客文章來源:http://www.zghlxwxcb.cn/news/detail-675524.html
《STM32》EasyTrace物體追蹤 源代碼個人注釋+完整例程 - 知乎文章來源地址http://www.zghlxwxcb.cn/news/detail-675524.html
到了這里,關(guān)于STM32 OV7725攝像頭模塊識別顏色物體(1)--HSL二值化和腐蝕中心算法,并用串口輸出數(shù)據(jù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!