本章包括以下內(nèi)容:
- 用形態(tài)學(xué)濾波器腐蝕和膨脹圖像;
- 用形態(tài)學(xué)濾波器開啟和閉合圖像;
- 在灰度圖像中應(yīng)用形態(tài)學(xué)運(yùn)算戏羽;
- 用分水嶺算法實(shí)現(xiàn)圖像分割;
- 用MSER 算法提取特征區(qū)域楼吃。
5.2 用形態(tài)學(xué)濾波器腐蝕和膨脹圖像
形態(tài)學(xué)濾波器通常作用于二值圖像始花。我們習(xí)慣用高像素值(白色)表示前景物體,用低像素值(黑色)表示背景物體孩锡。
在形態(tài)學(xué)術(shù)語中酷宵,下面的圖像稱為第4 章所建圖像的補(bǔ)碼。
OpenCV 用簡單的函數(shù)實(shí)現(xiàn)了腐蝕和膨脹運(yùn)算躬窜,它們分別是cv:erode 和cv:dilate浇垦,用法也很簡單:
// 讀取輸入圖像
cv::Mat image= cv::imread("binary.bmp");
// 腐蝕圖像
// 采用默認(rèn)的3×3 結(jié)構(gòu)元素
cv::Mat eroded; // 目標(biāo)圖像
cv::erode(image,eroded,cv::Mat());
// 膨脹圖像
cv::Mat dilated; // 目標(biāo)圖像
cv::dilate(image,dilated,cv::Mat());
這些函數(shù)生成的兩幅圖像如下所示。
腐蝕就是把當(dāng)前像素替換成所定義像素集合中的最小像素值;
膨脹是腐蝕的反運(yùn)算,它把當(dāng)前像素替換成所定義像素集合中的最大像素值颠蕴。
腐蝕時(shí),如果結(jié)構(gòu)元素放到某個(gè)像素位置時(shí)碰到了背景(即交集中有一個(gè)像素是黑色的),那么這個(gè)像素就
變?yōu)楸尘叭耘慌蛎洉r(shí)厕倍,如果結(jié)構(gòu)元素放到某個(gè)背景像素位置時(shí)碰到了前景物體,那么這個(gè)像素就被標(biāo)為白色贩疙。
正因如此讹弯,圖像腐蝕后物體尺寸會縮锌黾取(形狀被腐蝕),而圖像膨脹后物體會擴(kuò)大组民。
在腐蝕圖像中棒仍,有些面積較小的物體(可看作背景中的“噪聲”像素)會徹底消失。與之類似臭胜,膨脹后的物體會變大莫其,而物體中一些“空隙”會被填滿。
OpenCV 默認(rèn)使用3×3 正方形結(jié)構(gòu)元素耸三。在調(diào)用函數(shù)時(shí)乱陡,參考前面的例子將第三個(gè)參數(shù)指定為空矩陣(即cv::Mat()),就能得到默認(rèn)的結(jié)構(gòu)元素仪壮。你也可以通過提供一個(gè)矩陣來指定結(jié)構(gòu)元素的大泻┑摺(以及形狀),矩陣中的非零元素將構(gòu)成結(jié)構(gòu)元素积锅。下面的例子使用7×7 的結(jié)構(gòu)元素:
// 用更大的結(jié)構(gòu)元素腐蝕圖像
// 創(chuàng)建7×7 的mat 變量爽彤,其中全部元素都為1
cv::Mat element(7,7,CV_8U,cv::Scalar(1));
// 用這個(gè)結(jié)構(gòu)元素腐蝕圖像
cv::erode(image,eroded,element);
這次的結(jié)果更有破壞性,如下圖所示缚陷。
還有一種方法也能得到類似的結(jié)果适篙,就是在圖像上反復(fù)應(yīng)用同一個(gè)結(jié)構(gòu)元素。這兩個(gè)函數(shù)都有一個(gè)用于指定重復(fù)次數(shù)的可選參數(shù):
// 腐蝕圖像三次
cv::erode(image,eroded,cv::Mat(),cv::Point(-1,-1),3);
參數(shù)cv::Point(-1,-1)表示原點(diǎn)是矩陣的中心點(diǎn)(默認(rèn)值)蹬跃,也可以定義在結(jié)構(gòu)元素上的其他位置匙瘪。由此得到的圖像與使用7×7 結(jié)構(gòu)元素得到的圖像是一樣的。實(shí)際上蝶缀,對圖像腐蝕兩次相當(dāng)于對結(jié)構(gòu)元素自身膨脹后的圖像進(jìn)行腐蝕丹喻。這個(gè)規(guī)則也適用于膨脹。
最后翁都,鑒于前景/背景概念有很大的隨意性碍论,我們可得到以下的實(shí)驗(yàn)結(jié)論(這是腐蝕/膨脹運(yùn)算的基本性質(zhì))。用結(jié)構(gòu)元素腐蝕前景物體可看作對圖像背景部分的膨脹柄慰,也就是說:
- 腐蝕圖像相當(dāng)于對其反色圖像膨脹后再取反色鳍悠;
- 膨脹圖像相當(dāng)于對其反色圖像腐蝕后再取反色。
另外坐搔,OpenCV 的形態(tài)學(xué)函數(shù)支持就地處理藏研。這意味著輸入圖像和輸出圖像可以采用同一個(gè)變量。
cv::erode(image,image,cv::Mat());
5.3 用形態(tài)學(xué)濾波器開啟和閉合圖像
本節(jié)將講解開啟和閉合運(yùn)算概行。
應(yīng)用較高級別的形態(tài)學(xué)濾波器蠢挡,需要用cv::morphologyEx 函數(shù),例如下面的調(diào)用方法將適用于閉合運(yùn)算:
// 閉合圖像
cv::Mat element5(5, 5, CV_8U, cv::Scalar(1));
cv::Mat closed;
cv::morphologyEx(image, closed, // 輸入和輸出的圖像
cv::MORPH_CLOSE, // 運(yùn)算符
element5); // 結(jié)構(gòu)元素
注意,為了讓濾波器的效果更加明顯业踏,這里使用了5×5 的結(jié)構(gòu)元素禽炬。如果輸入上節(jié)的二值圖像,將得到如下所示的圖像勤家。
與之類似腹尖,應(yīng)用形態(tài)學(xué)開啟運(yùn)算后將得到如下圖像。
cv::Mat opened;
cv::morphologyEx(image, opened, cv::MORPH_OPEN, element5);
開啟和閉合濾波器的定義只與基本的腐蝕和膨脹運(yùn)算有關(guān):閉合的定義是對圖像先膨脹后腐蝕伐脖,開啟的定義是對圖像先腐蝕后膨脹热幔。
因此可以用以下方法對圖像做閉合運(yùn)算:
// 膨脹原圖像
cv::dilate(image, result, cv::Mat());
// 就地腐蝕膨脹后的圖像
cv::erode(result, result, cv::Mat());
調(diào)換這兩個(gè)函數(shù)的調(diào)用次序,就能得到開啟濾波器晓殊。
查看閉合濾波器的結(jié)果断凶,可看到白色的前景物體中的小空隙已經(jīng)被填滿。閉合濾波器也會把鄰近的物體連接起來巫俺∪纤福基本上,所有小到不能容納完整結(jié)構(gòu)元素的空隙或間隙都會被閉合濾波器消除介汹。
與閉合濾波器相反却嗡,開啟濾波器消除了背景中的幾個(gè)小物體。所有小到不能容納完整結(jié)構(gòu)元素的物體都會被移除嘹承。
這些濾波器常用于目標(biāo)檢測窗价。閉合濾波器可把錯(cuò)誤分裂成小碎片的物體連接起來,而開啟濾波器可以移除因圖像噪聲產(chǎn)生的斑點(diǎn)叹卷。因此最好按一定的順序調(diào)用這些濾波器撼港。如果優(yōu)先考慮過濾噪聲,可以先開啟后閉合骤竹,但這樣做的壞處是會消除掉部分物體碎片帝牡。
先使用開啟濾波器,再使用閉合濾波器蒙揣,會得到如下結(jié)果靶溜。
注意,對一幅圖像進(jìn)行多次同樣的開啟運(yùn)算是沒有作用的(閉合運(yùn)算也一樣)懒震。事實(shí)上罩息,因?yàn)榈谝淮问褂瞄_啟濾波器時(shí)已經(jīng)填充了空隙,再使用同一個(gè)濾波器將不會使圖像產(chǎn)生變化个扰。
5.4 在灰度圖像中應(yīng)用形態(tài)學(xué)運(yùn)算
本節(jié)將介紹兩種形態(tài)學(xué)運(yùn)算瓷炮,將它們應(yīng)用于灰度圖像上可以檢測圖像的特征。
形態(tài)學(xué)梯度運(yùn)算可以提取出圖像的邊緣递宅,具體方法為使用cv::morphologyEx 函數(shù)娘香,代碼如下所示:
// 用3×3 結(jié)構(gòu)元素得到梯度圖像
cv::Mat result;
cv::morphologyEx(image, result,
cv::MORPH_GRADIENT, cv::Mat());
得到圖像中物體的輪廓(為方便觀察冬筒,對圖像做了反色處理)。
另一種很實(shí)用的形態(tài)學(xué)運(yùn)算是頂帽(hat-top)變換茅主,它可以從圖像中提取出局部的小型前景物體。為了說明該運(yùn)算的效果土榴,我們用本書中一頁的照片做試驗(yàn)诀姚。由圖可知,頁面的光照并不均勻玷禽。通過使用cv::morphologyEx 函數(shù)并采用正確的參數(shù)赫段,可以調(diào)用黑帽變換提取出頁面上的文字(作為前景物體):
// 使用7×7 結(jié)構(gòu)元素做黑帽變換
cv::Mat element7(7, 7, CV_8U, cv::Scalar(1));
cv::morphologyEx(image, result, cv::MORPH_BLACKHAT, element7);
運(yùn)行結(jié)果如下圖所示(反色處理),它可以從圖像中提取出大部分文字矢赁。
理解形態(tài)學(xué)運(yùn)算在灰度圖像上的效果有一個(gè)好辦法糯笙,就是把圖像看作是一個(gè)拓?fù)涞孛玻煌幕叶燃墑e代表不同的高度(或海拔)撩银「椋基于這種觀點(diǎn),明亮的區(qū)域代表高山额获,黑暗的區(qū)域代表深谷够庙;邊緣相當(dāng)于黑暗和明亮像素之間的快速過渡,因此可以比作陡峭的懸崖抄邀。
腐蝕這種地形的最終結(jié)果是:每個(gè)像素被替換成特定鄰域內(nèi)的最小值耘眨,從而降低它的高度。結(jié)果是懸崖“縮小”境肾,山谷“擴(kuò)大”剔难。膨脹的效果剛好相反,即懸崖“擴(kuò)大”奥喻,山谷“縮小”偶宫。但不管哪種情況,平地(即強(qiáng)度值固定的區(qū)域)都會相對保持不變衫嵌。
根據(jù)這個(gè)結(jié)論读宙,可以得到一種檢測圖像邊緣(或懸崖)的簡單方法,即通過計(jì)算膨脹后的圖像與腐蝕后的圖像之間的的差距得到邊緣楔绞。因?yàn)檫@兩種轉(zhuǎn)換后圖像的差別主要在邊緣地帶结闸,所以相減后會突出邊緣。
在cv::morphologyEx 函數(shù)中輸入cv::MORPH_GRADIENT 參數(shù)酒朵,即可實(shí)現(xiàn)此功能桦锄。顯然,結(jié)構(gòu)元素越大蔫耽,檢測到的邊緣就越寬结耀。這種邊緣檢測運(yùn)算稱為Beucher 梯度留夜。
注意還有兩種簡單的方法能得到類似結(jié)果,即用膨脹后的圖像減去原始圖像图甜,或者用原始圖像減去腐蝕后的圖像碍粥,那樣得到的邊緣會更窄。
頂帽運(yùn)算也基于圖像比對黑毅,它使用了開啟和閉合運(yùn)算嚼摩。因?yàn)榛叶葓D像進(jìn)行形態(tài)學(xué)開啟運(yùn)算時(shí)會先對圖像進(jìn)行腐蝕,局部的尖銳部分會被消除矿瘦,其他部分則將保留下來枕面。因此,原始圖像和經(jīng)過開啟運(yùn)算的圖像的比對結(jié)果就是局部的尖銳部分缚去。這些尖銳部分就是我們需要提取的前景物體潮秘。
對于本書的照片來說,前景物體就是頁面上的文字易结。因?yàn)闀緸榘椎缀谧终碥瘢晕覀儾捎盟幕パa(bǔ)運(yùn)算,即黑帽算法衬衬。它將對圖像做閉合運(yùn)算买猖,然后從得到的結(jié)果中減去原始圖像。這里采用7×7 的結(jié)構(gòu)元素滋尉,它足夠大了玉控,能確保移除文字。
5.5 用分水嶺算法實(shí)現(xiàn)圖像分割
分水嶺變換是一種流行的圖像處理算法狮惜,用于快速將圖像分割成多個(gè)同質(zhì)區(qū)域高诺。
它基于這樣的思想:如果把圖像看作一個(gè)拓?fù)涞孛玻敲赐悈^(qū)域就相當(dāng)于陡峭邊緣內(nèi)相對平坦的盆地碾篡。分
水嶺算法通過逐步增高水位虱而,把地貌分割成多個(gè)部分。OpenCV 提出了該算法的改進(jìn)版本开泽,使用一系列預(yù)定義標(biāo)記來引導(dǎo)圖像分割的定義方式牡拇。
使用分水嶺分割法需要調(diào)用cv::watershed 函數(shù)。該函數(shù)的輸入對象是一個(gè)標(biāo)記圖像穆律,圖像的像素值為32 位有符號整數(shù)惠呼,每個(gè)非零像素代表一個(gè)標(biāo)簽。它的原理是對圖像中部分像素做標(biāo)記峦耘,表明它們的所屬區(qū)域是已知的剔蹋。分水嶺算法可根據(jù)這個(gè)初始標(biāo)簽確定其他像素所屬的區(qū)域。
本節(jié)將先建立一個(gè)標(biāo)記圖像作為灰度圖像辅髓,然后將其轉(zhuǎn)換成整型圖像泣崩。我們把這個(gè)步驟封裝進(jìn)WatershedSegmenter 類少梁,它包括指定標(biāo)記圖像和計(jì)算分水嶺的方法:
class WatershedSegmenter {
private:
cv::Mat markers;
public:
void setMarkers(const cv::Mat& markerImage) {
// 轉(zhuǎn)換成整數(shù)型圖像
markerImage.convertTo(markers, CV_32S);
}
cv::Mat process(const cv::Mat& image) {
// 應(yīng)用分水嶺
cv::watershed(image, markers);
return markers;
}
不同應(yīng)用程序獲得標(biāo)記的方式各不相同。例如矫付,可在預(yù)處理過程中識別出一些屬于某個(gè)感興趣物體的像素凯沪。然后,根據(jù)初始檢測結(jié)果买优,使用分水嶺算法劃出整個(gè)物體的邊緣著洼。
本節(jié)將利用本章一直使用的二值圖像,識別出對應(yīng)原始圖像中的動物而叼。因此,我們需要從二值圖像中識別出屬于前景(動物)的像素以及屬于背景(主要是草地)的像素豹悬。這里把前景像素標(biāo)記為255葵陵,把背景像素標(biāo)記為128(該數(shù)字是隨意選擇的,任何不等于255 的數(shù)字都可以)瞻佛。其他像素的標(biāo)簽是未知的脱篙,標(biāo)記為0。
現(xiàn)在伤柄,這個(gè)二值圖像包含了屬于圖像不同部分的白色像素绊困,因此要對圖像做深度腐蝕運(yùn)算,只保留明顯屬于前景物體的像素:
// 消除噪聲和細(xì)小物體
cv::Mat fg;
cv::erode(image, fg, cv::Mat(), cv::Point(-1, -1), 4);
得到的圖像如下所示适刀。
注意秤朗,仍然有少量屬于背景(森林)的像素保留了下來,不用管它們笔喉,可將它們看作感興趣物體取视。與之類似,我們可以通過對原二值圖像做一次大幅度的膨脹運(yùn)算來選中一些背景像素:
// 標(biāo)識不含物體的圖像像素
cv::Mat bg;
cv::dilate(binary,bg,cv::Mat(),cv::Point(-1,-1),4);
cv::threshold(bg,bg,1,128,cv::THRESH_BINARY_INV);
得到的黑色像素對應(yīng)背景像素常挚。因此在膨脹后作谭,要立即通過閾值化運(yùn)算把它們賦值為128。得到的圖像如下所示奄毡。
合并這兩幅圖像折欠,得到標(biāo)記圖像,代碼為:
// 創(chuàng)建標(biāo)記圖像
cv::Mat markers(binary.size(),CV_8U,cv::Scalar(0));
markers= fg+bg;
下面的圖像將被輸入分水嶺算法吼过。
毫無疑問锐秦,在這個(gè)輸入圖像中,白色區(qū)域?qū)儆谇熬拔矬w那先,灰色區(qū)域?qū)儆诒尘芭┾谏珔^(qū)域帶有未知標(biāo)簽。分水嶺算法的作用就是明確地劃分前景和背景售淡,并對黑色區(qū)域的像素做出標(biāo)記(屬于前景還是背景)斤葱】犊澹可用下面的方法來分割圖像:
// 創(chuàng)建分水嶺分割類的對象
WatershedSegmenter segmenter;
// 設(shè)置標(biāo)記圖像,然后執(zhí)行分割過程
segmenter.setMarkers(markers);
segmenter.process(image);
上面的代碼會修改標(biāo)記圖像揍堕,每個(gè)值為0 的像素都會被賦予一個(gè)輸入標(biāo)簽料身,而邊緣處的像素被賦值為-1,得到的標(biāo)簽圖像如圖所示衩茸。
邊緣圖像如圖所示芹血。
跟前面幾節(jié)一樣,我們在描述分水嶺算法時(shí)用拓?fù)涞貓D來做類比楞慈。用分水嶺算法分割圖像的原理是從高度0 開始逐步用洪水淹沒圖像幔烛。當(dāng)“水”的高度逐步增加時(shí)(到1、2囊蓝、3 等)饿悬,會形成聚水的盆地。隨著盆地面積逐步變大聚霜,兩個(gè)盆地的水最終會匯合到一起狡恬。這時(shí)就要?jiǎng)?chuàng)建一個(gè)分水嶺,用來分割這兩個(gè)盆地蝎宇。當(dāng)水位達(dá)到最大高度時(shí)弟劲,創(chuàng)建的盆地和分水嶺就組成了分水嶺分割圖。
可以想象姥芥,在水淹過程的開始階段會創(chuàng)建很多細(xì)小的獨(dú)立盆地兔乞。當(dāng)所有盆地匯合時(shí),就會創(chuàng)建很多分水嶺線條凉唐,導(dǎo)致圖像被過度分割报嵌。要解決這個(gè)問題,就要對這個(gè)算法進(jìn)行修改熊榛,使水淹過程從一組預(yù)先定義好的標(biāo)記像素開始锚国。每個(gè)用標(biāo)記創(chuàng)建的盆地,都按照初始標(biāo)記的值加上標(biāo)簽玄坦。如果兩個(gè)標(biāo)簽相同的盆地匯合血筑,就不創(chuàng)建分水嶺,以避免過度分割煎楣。調(diào)用cv::watershed 函數(shù)時(shí)就執(zhí)行了這些過程豺总。輸入的標(biāo)記圖像會被修改,用以生成最終的分水嶺分割圖择懂。輸入的標(biāo)記圖像可以含有任意數(shù)值的標(biāo)簽喻喳,未知標(biāo)簽的像素值為0。標(biāo)記圖像的類型選用32 位有符號整數(shù)困曙,以便定義超過255 個(gè)的標(biāo)簽表伦。另外谦去,可以把分水嶺的對應(yīng)像素設(shè)為特殊值-1。
為了方便顯示結(jié)果蹦哼,我們采用兩種特殊方法鳄哭。第一種方法返回由標(biāo)簽組成的圖像(包含值為0 的分水嶺)。該方法通過閾值化很容易實(shí)現(xiàn)纲熏,代碼如下所示:
// 以圖像的形式返回結(jié)果
cv::Mat getSegmentation() {
cv::Mat tmp;
// 所有標(biāo)簽值大于255 的區(qū)段都賦值為255
markers.convertTo(tmp, CV_8U);
return tmp;
}
與之類似妆丘,第二種方法返回一幅圖像,圖像中分水嶺線條賦值為0局劲,其他部分賦值為255勺拣。這次用cv::convertTo 方法來獲得結(jié)果,代碼如下所示:
// 以圖像的形式返回分水嶺
cv::Mat getWatersheds() {
cv::Mat tmp;
// 在變換前鱼填,把每個(gè)像素p 轉(zhuǎn)換為255p+255
markers.convertTo(tmp, CV_8U, 255, 255);
return tmp;
}
在變換前對圖像做線性轉(zhuǎn)換宣脉,使值為-1 的像素變?yōu)?(因?yàn)?1*255+255=0)。
值大于255 的像素賦值為255剔氏。這是因?yàn)閷⒂蟹栒麛?shù)轉(zhuǎn)換成無符號字符型時(shí),應(yīng)用了飽和度運(yùn)算竹祷。
5.6 用MSER 算法提取特征區(qū)域
最大穩(wěn)定外部區(qū)域(MSER)算法也用相同的水淹類比谈跛,以便從圖像中提取有意義的區(qū)域。創(chuàng)建這些區(qū)域時(shí)也使用逐步提高水位的方法塑陵,但是這次我們關(guān)注的是在水淹過程中的某段時(shí)間內(nèi)感憾,保持相對穩(wěn)定的盆地×罨ǎ可以發(fā)現(xiàn)阻桅,這些區(qū)域?qū)?yīng)著圖像中某些物體的特殊部分。
計(jì)算圖像MSER 的基礎(chǔ)類是cv::MSER兼都。cv::MSER 類的實(shí)例可以通過create 方法創(chuàng)建嫂沉。我們在初始化時(shí)指定被檢測區(qū)域的最小和最大尺寸,以便限制被檢測特征的數(shù)量扮碧,調(diào)用方式如下:
// 基本的MSER 檢測器
cv::Ptr<cv::MSER> ptrMSER = cv::MSER::create(
5, // 局部檢測時(shí)使用的增量值
200, // 允許的最小面積
2000); // 允許的最大面積
現(xiàn)在可以通過調(diào)用detectRegions 方法來獲得MSER趟章,指定輸入圖像和一個(gè)相關(guān)的輸出數(shù)據(jù)結(jié)構(gòu),代碼如下所示:
// 點(diǎn)集的容器
std::vector<std::vector<cv::Point> > points;
// 矩形的容器
std::vector<cv::Rect> rects;
// 檢測MSER 特征
ptrMSER->detectRegions(image, points, rects);
檢測結(jié)果放在兩個(gè)容器中慎王。第一個(gè)是區(qū)域的容器蚓土,每個(gè)區(qū)域用組成它的像素點(diǎn)表示;第二個(gè)是矩形的容器赖淤,每個(gè)矩形包圍一個(gè)區(qū)域蜀漆。為了呈現(xiàn)結(jié)果,創(chuàng)建一個(gè)空白圖像咱旱,在圖像上用不同的顏色顯示檢測到的區(qū)域(顏色是隨機(jī)選擇的)确丢。用以下代碼實(shí)現(xiàn):
// 創(chuàng)建白色圖像
cv::Mat output(image.size(), CV_8UC3);
output = cv::Scalar(255, 255, 255);
// OpenCV 隨機(jī)數(shù)生成器
cv::RNG rng;
// 針對每個(gè)檢測到的特征區(qū)域绷耍,在彩色區(qū)域顯示MSER
// 反向排序,先顯示較大的MSER
for (std::vector<std::vector<cv::Point> >::reverse_iterator
it = points.rbegin();
it != points.rend(); ++it) {
// 生成隨機(jī)顏色
cv::Vec3b c(rng.uniform(0, 254),
rng.uniform(0, 254), rng.uniform(0, 254));
// 針對MSER 集合中的每個(gè)點(diǎn)
for (std::vector<cv::Point>::iterator itPts = it->begin();
itPts != it->end(); ++itPts) {
// 不重寫MSER 的像素
if (output.at<cv::Vec3b>(*itPts)[0] == 255) {
output.at<cv::Vec3b>(*itPts) = c;
}
}
}
注意蠕嫁,MSER 會形成層疊區(qū)域锨天。為了顯示全部區(qū)域,如果一個(gè)較大區(qū)域內(nèi)包含了較小的區(qū)域剃毒,就不能覆蓋它病袄。可以從下圖中檢測出MSER赘阀。
結(jié)果如下圖所示益缠。
圖中沒有顯示全部區(qū)域,但是可以看出基公,通過這種方法能從圖片中提取到一些有意義的區(qū)域(例如建筑物的窗戶)幅慌。
MSER 的原理與分水嶺算法相同,即高度為0~255轰豆,逐漸淹沒圖像胰伍。隨著水位的升高,顏色較黑并且邊界陡峭的區(qū)域會形成盆地 酸休,并且在一段時(shí)間內(nèi)有相對穩(wěn)定的形狀(用水位表示顏色骂租,水位高低代表了像素值的強(qiáng)度)。這些穩(wěn)定的盆地就是MSER斑司。
檢測它們的方法是渗饮,觀察每個(gè)水位連通的區(qū)域(即盆地)并測量它們的穩(wěn)定性。測量穩(wěn)定性的方法是:計(jì)算區(qū)域的當(dāng)前面積以及該區(qū)域原先的面積(比當(dāng)前水位低一個(gè)特定值的時(shí)候)宿刮,并比較這兩個(gè)面積互站。如果相對變化達(dá)到局部最小值,就認(rèn)為這個(gè)區(qū)域是MSER僵缺。
增量值將作為cv::MSER 類構(gòu)造函數(shù)的第一個(gè)參數(shù)胡桃,用以測量相對穩(wěn)定性,默認(rèn)值為5磕潮。另外要注意标捺,區(qū)域面積必須在預(yù)定義的范圍內(nèi)。構(gòu)造函數(shù)中后面兩個(gè)參數(shù)就是允許的最小和最大區(qū)域尺寸揉抵。另外必須確保MSER 是穩(wěn)定的(第四個(gè)參數(shù))亡容,即形狀的相對變化必須足夠小。
MSER 檢測器首先輸出一個(gè)包含像素集的容器冤今,每個(gè)像素集構(gòu)成一個(gè)區(qū)域闺兢。因?yàn)槲覀冃枰页稣麄€(gè)區(qū)域的位置,而不是里面的單個(gè)像素,所以通常用包含了被檢測區(qū)域的幾何形狀表示一個(gè)MSER屋谭。
檢測過程中輸出的第二項(xiàng)是一系列矩形脚囊,畫出所有矩形就能表示檢測的結(jié)果。但是這樣會畫出許多矩形桐磁,使結(jié)果很不直觀(區(qū)域之間還會互相包含悔耘,結(jié)果更加混亂)。這個(gè)例子主要想檢測出大樓中的窗戶我擂,因此要提取出所有包含垂直矩形的區(qū)域衬以。實(shí)現(xiàn)方法是將每個(gè)矩形的面積與檢測到的對應(yīng)區(qū)域進(jìn)行比較,如果兩者一致(這里用的判斷標(biāo)準(zhǔn)是兩者比例超過0.6)校摩,那么它就是一個(gè)MSER看峻。測試代碼如下所示:
// 提取并顯示矩形的MSER
std::vector<cv::Rect>::iterator itr = rects.begin();
std::vector<std::vector<cv::Point> >::iterator itp = points.begin();
for (; itr != rects.end(); ++itr, ++itp) {
// 檢查兩者比例
if (static_cast<double>(itp->size()) / itr->area() > 0.6)
cv::rectangle(image, *itr, cv::Scalar(255), 2);
}
提取到的MSER 如下圖所示。