模式識別之線條矩形識別---長方形畫布或紙張並提取圖像內容


基於知乎上的一個 答案。問題如下:
也就是在一張照片里,已知有個長方形的物體,但是經過了透視投影,已經不再是規則的長方形,那么如何提取這個圖形里的內容呢?這是個很常見的場景,比如在博物館里看到一幅很喜歡的畫,用手機找了下來,可是回家一看歪歪斜斜,腦補原畫內容又覺得不對,那么就需要算法輔助來從原圖里提取原來的內容了。不妨把應用的場景分為以下:

紙張四角的坐標(圖中紅點)已知的情況

也就是上面的左圖中4個紅點是可以准確獲取,比如手動標注,那么就簡單了:用OpenCV的Perspective Transform就可以。具體步驟如下:
1) 將標注好的四個點坐標存入一個叫corner的變量里,比如上面的例子中,原圖的分辨率是300x400,定義x和y的方向如下:
那么紙張的四角對應的坐標分別是:
左上:157.6, 71.5

右上:295.6, 118.4

 

 

 

右下:172.4, 311.3

 

 

 

左下:2.4, 202.4

 
把這四個坐標按如上順序放到一個叫corner的變量里。如果我們打算把這幅圖案恢復到一個300x400的圖像里,那么按照對應的順序把下面四個坐標放到一個叫canvas的變量里:
左上:0, 0

右上:300, 0

右下:300, 400

 

 

 

左下:0, 400

 
假設原圖已經用OpenCV讀取到一個叫image的變量里,那么提取紙張圖案的代碼如下:
1 M = cv2.getPerspectiveTransform(corners, canvas)
2 result = cv2.warpPerspective(image, M, (0, 0))
把左圖剪裁出來,去掉紅點后試了試,結果如下:
當然,其實這一步用Photoshop就可以了。。

紙張四角的坐標未知或難以准確標注的情況

這種場景可能是小屏幕應用,或是原始圖像就很小,比如我這里用的這個300x400例子,點坐標很難精確標注。這種情況下一個思路是,用邊緣檢測提取紙張四邊,然后求出四角坐標,再做Perspective Transform。
 
1) 圖像預處理
一般而言即使做普通的邊緣檢測也需要提前對圖像進行降噪避免誤測,比如最常見的辦法是先對圖像進行高斯濾波,然而這樣也會導致圖像變得模糊,當待檢測圖形邊緣不明顯,或是圖像本身分辨率不高的情況下(比如本文用的例子),會在降噪的同時把待檢測的邊緣強度也給犧牲了。具體到本文的例子,紙張是白色,背景是淺黃帶紋路,如果進行高斯濾波是顯然不行的,這時候一個替代方案是可以考慮使用Mean Shift,Mean Shift的優點就在於如果是像背景桌面的淺色紋理,圖像分割的過程中相當於將這些小的浮動過濾掉,並且保留相對明顯的紙張邊緣,結果如下:
原圖
處理后
Meanshift的代碼:
1 image = cv2.pyrMeanShiftFiltering(image, 25, 10)

因為主要目的是預處理降噪,windows size和color distance都不用太大,避免浪費計算時間還有過度降噪。降噪后可以看到桌面上的紋理都被抹去了,紙張邊緣附近干凈了很多。然而這還遠遠不夠,圖案本身,和圖像里的其他物體都有很多明顯的邊緣,而且都是直線邊緣。

2) 紙張邊緣檢測

雖然降噪了,可是圖像里還是有很多邊緣明顯的元素。怎么盡量只保留紙張的邊緣呢,這時候可以考慮用分割算法,把圖像分為紙張部分和其他部分,這樣分割的mask邊緣就和紙張邊緣應該是差不多重合的。在這里可以考慮用GrabCut,這樣對於簡單的情況,比如紙張或畫布和背景對比強烈的,直接把圖像邊緣的像素作為bounding box就可以實現自動分割。當自動分割不精確的情況下再引入手動輔助分割,具體到我這里用的例子,背景和畫面接近,所以需要手動輔助:
結果如下:
可以看到,分割后的結果雖然能基本區分紙張形狀了,可是邊緣並不准確,另外鍵盤和部分桌面沒能區分開來。這時可以繼續用GrabCut+手動標注得到只有紙張的分割。或者為了用戶友好的話,盡量少引入手動輔助,那么可以考慮先繼續到下一步檢測邊緣,再做后期處理。假設我們考慮后者,那么我們得到的是如下的mask:
這個mask並不精確,所以不能直接用於邊緣檢測,但是它大致標出了圖片里最明顯的邊緣位置所在,所以可以考慮下面的思路:保留降噪后位於mask邊緣附近的信息用於真正的邊緣檢測,而把其他部分都模糊處理,也就是說基於上面得到的mask做出下面的mask用於模糊處理:
基於這個mask得到的用於邊緣檢測的圖像如下:
用canny算子檢測出邊緣如下:

3) 直線檢測

對檢測到的邊緣使用Hough變換檢測直線,我例子里用的是cv2.HoughLinesP,分辨率1像素和1°,可以根據圖像大小設置檢測的閾值和minLineLength去除大部分誤檢測。特別提一下的是如果使用OpenCV的Python binding,OpenCV 2和OpenCV 3的結果結構是不一樣的,如果進行代碼移植需要相應的修改。檢測到的結果如下:
可以看到,有些線幾乎重合在一起了,這是難以避免的,上圖中一共檢測到9條線,其中兩對(下、右邊緣)重合。可以通過距離判斷和直線相對角度來判斷並把重合線段合為一條:
剩下的都是沒有重合的線了。

4) 判斷紙張邊緣

那么如何選取紙張邊緣的四條線呢(即使圖像分割步驟非常好得分開了紙張和其他部分,這在有些情況下還是難以避免的,比如圖案里有和邊緣平行的線條),可以沿着提取線段的兩邊采樣像素的灰度:
在線段的兩個端點之間平均采樣左右兩邊像素的值,因為一般來說如果是紙張或者畫布,邊緣和背景的顏色在四邊上應該都是類似的。然而這樣做的話引入另外一個問題是需要區分線段的“左”和“右”,對於線段本身而言就是要區分前后。所以需要對畫面里所有的線段端點進行排序,而這個排序的基准就是相對畫布。
具體到本文的例子就是把圖像中心定義為所有線段的“左”邊,如上圖。而決定線段端點“前”和“后”可以用如下辦法:
先假設線段的前后端點,將兩個端點坐標分別減去中心點(紅點)的坐標,然后將得到的兩個向量a和b求叉積,如果叉積大於0則說明假設正確,如果<0則交換假設的前后端點。線段端點的順序確定后就可以進行采樣了,簡單起見可以分別采樣左右兩側的像素灰度值,如果希望更准確可以采樣RGB通道的值進行綜合比較,下面是7條線段對應的兩側像素灰度的中值分布:
可以看到其中有4個點距離非常近(紅色),說明他們的像素灰度分布也很接近,把這4條選出來,結果如下:

正是要的結果。

5) 計算四角的坐標

接下來計算四條線的交點,方法點這里。因為有4條線,會得到6個結果,因為在這種應用場景中,方形的物體在透視變換下不會出現凹角,所以直接舍棄離紙張中心最遠的兩個交點就得到了四個角的坐標,結果如下:

這樣就回到了一開始四角坐標已經得到的情況,直接進行透視變換就行了。

Camera Calibration?

寫了這么多,其實有一條至關重要的假設,甚至可以說是最關鍵的步驟之一我一直沒提,那就是Camera Calibration,如果有相機的情況下,meta data都知道,那么需要先坐Camera Calibration才能知道紙張或者畫布的原始尺寸。我這里試的例子當然是沒有的,也可以有,相應的算法OpenCV里也有現成的,不過即便如此還是非常麻煩,所以我的所有流程都是默認原始尺寸已經獲得了。再說了,就算沒有,變換回方形之后使用者憑感覺進行簡單軸縮放都比Camera Calibration方便得多。。

印象派

我用的例子算是略微有些極端的,因為背景和圖案非常接近,另一方面分辨率還巨低。在網上搜搜,我找了幅少年畫家的印象派作品來試試:

原圖

手動標注

GrabCut

檢測到的邊緣

結果

看上去還不錯~

 

http://www.cnblogs.com/frombeijingwithlove/p/4226489.html

http://www.tuicool.com/articles/6zq2aq sift


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM