文章目錄
- 前言
- 一、攝像頭圖像處理
- 1、攝像頭圖像采集
- 2、圖像二值化與大津算法
- 二、左右邊界,中線掃描
前言
參加了第十六,十七和第十八屆全國大學生智能車競賽,對攝像頭的學習有部分心得,分享給大家,三屆車賽,車賽生涯也算是到了盡頭。打算從基礎的算法開始,給各位一些個人看法,也是對車賽的一次總結。
一、攝像頭圖像處理
閑話:其實攝像頭的算法有很多種,弄了兩年攝像頭,也只是學會了其中很小的一部分,但最終,作用都是大同小異的,也不必太過于追求算法上的完美。只需要達到能穩定提取特征,識別元素其實就夠用了。(個人用的是普通大津+二值化+八領域做邊界提取)
1、攝像頭圖像采集
打開攝像頭相關例程,可以發現其實最終攝像頭所采集的數據都存入了一個二維數組中,工作方式也很簡單:圖像采集,將圖像采集標志置一。手動清零,就可以達到重復采集的目的。(沒有相關例程的可以去找客服要,記得B站上也有逐飛的攝像頭攝像頭圖像采集視頻講解)
2、圖像二值化與大津算法
智能車使用的攝像頭所采集的圖像一般都是灰度圖像,將圖像分割、數字化成一個個的數(一個個的像素點),0~255,像素點顏色越白數字越大。我們可以看出圖像信息很豐富,圖像處理方法自然也就多種多樣了,首先我們可以將圖像二值化。
二值化就是將圖像上的灰度點分別設置為0,255(0xFF)(有點像二級分化)。圖像直觀上就會變為黑白圖像。我們該按照何種規則分黑和白呢?這時候就需要我們找出黑、白的分界值(也就是閾值)(大于閾值即為白,反之為黑),然后可以遍歷圖像數組中每一個像素點,大于閾值設為0XFF(白),反之設為0(黑)。(二值化有一個進階的思想:圖像每一個點都需要二值化嘛,我們能不能只將要使用的點二值化呢,這樣速度是不是就快了,這個其實開始沒有必要弄,有一張完整的二值化圖也方便我們理解)
注:1,數組均從零開始。
2,二值化不能作用于攝像頭采集原圖上,應該重新定義一個同大小的數組,專門存放二值化后的圖像信息。(防止在進行圖像處理時,攝像頭重新采集數據,覆蓋之前數據)
?/*
mt9v03x_image_dvp : 原圖像 存放攝像頭灰度原圖
mt9v03x_image_baz :二值化圖像 用于存放圖像右邊界
WHTIE 宏定義 替換作用 與0xff相同
*/
#define WHTIE 0xff //255
#define BLACK 0x00//使用宏 通過單詞代替抽象的數字 for(uint8_t i=0;i<MT9V03X_DVP_H;i++) //MT9V03X_DVP_H:圖像高{for(uint8_t j=0;j<MT9V03X_DVP_W;j++) //MT9V03X_DVP_W:圖像寬{if(mt9v03x_image_dvp[i][j]>=threshold ) //threshold:圖像閾值{mt9v03x_image_baz[i][j]=WHITE;}else{mt9v03x_image_baz[i][j]=BLACK;}}}?
后面就是確定圖像的閾值了,因為真實環境亮度是變化的,單純的給定閾值二值化就顯得不夠穩定,在圖像的基礎上動態的計算閾值適應性更強。我選擇的是大津算法,也是常用且基礎的一種了,缺點就是運算時間有點稍長。
大津算法的相關我就不說了,網上有很多相關介紹。(其實我理解的也不太透徹, 手動狗頭保命)
二、左右邊界,中線掃描
在二值化之后,我們得到了一個由黑和白組成的二維數組,而我們的目的是讓小車時刻處于賽道中間位置,也就是圖像中間位置。我們想要小車不出賽道,如果能讓賽道的中線始終貼合圖像數組的中間(此處圖像數組的中間我們可以想象為圖像的寬/2),那我們的小車是不是就不會出賽道了。圖像數組的中間就像PID中的理論值,實際賽道的中線就像實際值。這樣我們是不是就可以將圖像與PID聯系起來。
說回邊界掃描,想直接找賽道中線還是比較難的,主要是沒有明顯特征,我們不妨先尋找左右邊界(左右邊界黑白相夾),左右相加除二求得中線。這樣就得到比較真實的中線坐標,再對比理想中線坐標(圖像中間,也就是圖像寬/2),從而求出中線偏差,用于PID控制。思路大抵都是這樣,但找的方法就很多種多樣了。
基礎的方法就是從中間往兩邊找,缺點是遍歷了整幅圖像,消耗時間稍長,而進階的就有對普通兩邊找線方法的優化,(如:雙最長白列法)。再進一步稍微復雜一點的有八鄰域,迷宮法等等。
我們先來看看普通的兩邊找線法:
基本思路就是從最下行往上 (利于對之后對中線的處理),每一行從中間往兩邊,分別尋找左右邊界(特征:黑白跳變),存入左右邊界數組。
同時存下中線坐標,用于下一行中線的掃描。(這樣減小運算量的同時還可以加大中線掃描時的連續性)左右邊界相加除2求出該行中線坐標,存入中線數組。
注:
/* last_mid: 邊界掃描起始坐標 每行邊界從此開始 起始行為圖像寬/2 后為前一行圖像中線坐標MT9V03X_DVP_W: 圖像寬MT9V03X_DVP_H: 圖像高mt9v03x_image_baz[][]: 二值化圖像數組left_flag[] : 左邊界存在數組 左邊界存在 標志置1left_flag[] : 右邊界存在數組 右邊界存在 標志置1left_border[]: 左邊界數組 存放左邊界坐標right_border[]: 右邊界數組 存放右邊界坐標Mid_border[]: 中線左邊數組 存放中線坐標
*/last_mid = MT9V03X_DVP_W / 2;
for (int i = MT9V03X_DVP_H - 1; i >= 0; i--)//從下往上掃描
{left_flag[i] = 1;right_flag[i] = 1;for (int j = last_mid; j > 1; j--)//中間往左邊掃描{if (mt9v03x_image_baz[i][j] == 0xff && mt9v03x_image_baz[i][j-1] == 0x00 && mt9v03x_image_baz[i][j-2]==0x00)//黑黑白認為找到左邊界{left_border[i] = j; //將左邊界存入左邊界數組left_flag[i] = 1; //左邊界找到,標志置0break; //跳出循環}}if (left_flag[i]==0) left_border[i]=0; //補線標志未置一,此行左丟線,取圖像左邊界for (int j = last_mid; j < MT9V03X_DVP_W-2; j++) //往右掃描{if (mt9v03x_image_baz[i][j]==0xff && mt9v03x_image_baz[i][j+1]==0x00 && mt9v03x_image_baz[i][j+2]==0x00)//白黑黑認為找到右邊界{right_border[i] = j; //將右邊界存入右邊界數組right_flag[i] = 1; //右邊界找到,標志置0break; //跳出循環}}if (right_flag[i]==0) right_border[i] = MT9V03X_DVP_W-1; //補線標志未置一,此行右丟線,取圖像右邊界Mid_border[i] = (left_border[i] + right_border[i]) / 2; //中線坐標mt9v03x_image_baz[i][left_border[i]-2] = BLACK; //左邊界涂黑mt9v03x_image_baz[i][Mid_border[i]] = BLACK; //中線涂黑mt9v03x_image_baz[i][right_border[i]+2] = BLACK; //右邊界涂黑 /* 注意 左右邊界-2 +2 很容易存在數組越界 導致程序卡住 如 左邊界點 left_border[i]=0; [right_border[i]=MT9V03X_DVP_W-1自己使用時可以加個限制如mt9v03x_image_baz[i][(left_border[i]<2?2:left_border[i])-2] = BLACK; //左邊界涂黑*/last_mid = Mid_border[i]; //中線查找開始點,方便中線尋找
}
可以在圖像上把邊界顯示出來,方便后面觀察圖像,像這樣:
后續給各位說說我對八領域的理解,歡迎大家關注!!!