EasyPR--开发详解(5)颜色定位与偏斜扭转
本篇文章介紹EasyPR里新的定位功能:顏色定位與偏斜扭正。希望這篇文檔可以幫助開發(fā)者與使用者更好的理解EasyPR的設(shè)計思想。
讓我們先看一下示例圖片,這幅圖片中的車牌通過顏色的定位法進(jìn)行定位并從偏斜的視角中扭正為正視角(請看右圖的左上角)。
?
圖1 新版本的定位效果??
?
下面內(nèi)容會對這兩個特性的實現(xiàn)過程展開具體的介紹。首先介紹顏色定位的原理,然后是偏斜扭正的實現(xiàn)細(xì)節(jié)。
由于本文較長,為方便讀者,以下是本文的目錄:
一.顏色定位
1.1起源
1.2方法
1.3不足與改善
二.偏斜扭正
2.1分析
2.2ROI截取
2.3擴大化旋轉(zhuǎn)
2.4偏斜判斷
2.5仿射變換
2.6總結(jié)
三.總結(jié)
?
一.?顏色定位
1.起源
在前面的介紹里,我們使用了Sobel查找垂直邊緣的方法,成功定位了許多車牌。但是,Sobel法最大的問題就在于面對垂直邊緣交錯的情況下,無法準(zhǔn)確地定位車牌。例如下圖。為了解決這個問題,可以考慮使用顏色信息進(jìn)行定位。
?
圖2 顏色定位與Sobel定位的比較
?
如果將顏色定位與Sobel定位加以結(jié)合的話,可以使車牌的定位準(zhǔn)確率從75%上升到94%。
?
2.方法
關(guān)于顏色定位首先我們想到的解決方案就是:利用RGB值來判斷。
這個想法聽起來很自然:如果我們想找出一幅圖像中的藍(lán)色部分,那么我們只需要檢查RGB分量(RGB分量由Red分量--紅色,Green分量--綠色,Blue分量--藍(lán)色共同組成)中的Blue分量就可以了。一般來說,Blue分量是個0到255的值。如果我們設(shè)定一個閾值,并且檢查每個像素的Blue分量是否大于它,那我們不就可以得知這些像素是不是藍(lán)色的了么?這個想法雖然很好,不過存在一個問題,我們該怎么來選擇這個閾值?這是第一個問題。
即便我們用一些方法決定了閾值以后,那么下面的一個問題就會讓人抓狂,顏色是組合的,即便藍(lán)色屬性在255(這樣已經(jīng)很‘藍(lán)’了吧),只要另外兩個分量配合(例如都為255),你最后得到的不是藍(lán)色,而是黑色。
這還只是區(qū)分藍(lán)色的問題,黃色更麻煩,它是由紅色和綠色組合而成的,這意味著你需要考慮兩個變量的配比問題。這些問題讓選擇RGB顏色作為判斷的難度大到難以接受的地步。因此必須另想辦法。
為了解決各種顏色相關(guān)的問題,人們發(fā)明了各種顏色模型。其中有一個模型,非常適合解決顏色判斷的問題。這個模型就是HSV模型。
圖3 HSV顏色模型
HSV模型是根據(jù)顏色的直觀特性創(chuàng)建的一種圓錐模型。與RGB顏色模型中的每個分量都代表一種顏色不同的是,HSV模型中每個分量并不代表一種顏色,而分別是:色調(diào)(H),飽和度(S),亮度(V)。
H分量是代表顏色特性的分量,用角度度量,取值范圍為0~360,從紅色開始按逆時針方向計算,紅色為0,綠色為120,藍(lán)色為240。S分量代表顏色的飽和信息,取值范圍為0.0~1.0,值越大,顏色越飽和。V分量代表明暗信息,取值范圍為0.0~1.0,值越大,色彩越明亮。
H分量是HSV模型中唯一跟顏色本質(zhì)相關(guān)的分量。只要固定了H的值,并且保持S和V分量不太小,那么表現(xiàn)的顏色就會基本固定。為了判斷藍(lán)色車牌顏色的范圍,可以固定了S和V兩個值為1以后,調(diào)整H的值,然后看顏色的變化范圍。通過一段摸索,可以發(fā)現(xiàn)當(dāng)H的取值范圍在200到280時,這些顏色都可以被認(rèn)為是藍(lán)色車牌的顏色范疇。于是我們可以用H分量是否在200與280之間來決定某個像素是否屬于藍(lán)色車牌。黃色車牌也是一樣的道理,通過觀察,可以發(fā)現(xiàn)當(dāng)H值在30到80時,顏色的值可以作為黃色車牌的顏色。
這里的顏色表來自于這個網(wǎng)站。
下圖顯示了藍(lán)色的H分量變化范圍。
?
圖4 藍(lán)色的H分量區(qū)間?
?
下圖顯示了黃色的H分量變化范圍。?
?
?圖5 黃色的H分量區(qū)間??
?
光判斷H分量的值是否就足夠了?
事實上是不足的。固定了H的值以后,如果移動V和S會帶來顏色的飽和度和亮度的變化。當(dāng)V和S都達(dá)到最高值,也就是1時,顏色是最純正的。降低S,顏色越發(fā)趨向于變白。降低V,顏色趨向于變黑,當(dāng)V為0時,顏色變?yōu)楹谏R虼?#xff0c;S和V的值也會影響最終顏色的效果。
我們可以設(shè)置一個閾值,假設(shè)S和V都大于閾值時,顏色才屬于H所表達(dá)的顏色。
在EasyPR里,這個值是0.35,也就是V屬于0.35到1且S屬于0.35到1的一個范圍,類似于一個矩形。對V和S的閾值判斷是有必要的,因為很多車牌周身的車身,都是H分量屬于200-280,而V分量或者S分量小于0.35的。通過S和V的判斷可以排除車牌周圍車身的干擾。
?
?? ? ? ?
圖6 V和S的區(qū)間?
?
明確了使用HSV模型以及用閾值進(jìn)行判斷以后,下面就是一個顏色定位的完整過程。
第一步,將圖像的顏色空間從RGB轉(zhuǎn)為HSV,在這里由于光照的影響,對于圖像使用直方圖均衡進(jìn)行預(yù)處理;
第二步,依次遍歷圖像的所有像素,當(dāng)H值落在200-280之間并且S值與V值也落在0.35-1.0之間,標(biāo)記為白色像素,否則為黑色像素;
第三步,對僅有白黑兩個顏色的二值圖參照原先車牌定位中的方法,使用閉操作,取輪廓等方法將車牌的外接矩形截取出來做進(jìn)一步的處理。
?
?圖7 藍(lán)色定位效果?
?
以上就完成了一個藍(lán)色車牌的定位過程。我們把對圖像中藍(lán)色車牌的尋找過程稱為一次與藍(lán)色模板的匹配過程。代碼中的函數(shù)稱之為colorMatch。一般說來,一幅圖像需要進(jìn)行一次藍(lán)色模板的匹配,還要進(jìn)行一次黃色模板的匹配,以此確保藍(lán)色和黃色的車牌都被定位出來。
黃色車牌的定位方法與其類似,僅僅只是H閾值范圍的不同。事實上,黃色定位的效果一般好的出奇,可以在非常復(fù)雜的環(huán)境下將車牌極為準(zhǔn)確的定位出來,這可能源于現(xiàn)實世界中黃色非常醒目的原因。
?
??圖8 黃色定位效果?
從實際效果來看,顏色定位的效果是很好的。在通用數(shù)據(jù)測試集里,大約70%的車牌都可以被定位出來(一些顏色定位不了的,我們可以用Sobel定位處理)。
在代碼中有些細(xì)節(jié)需要注意:
一. opencv為了保證HSV三個分量都落在0-255之間(確保一個char能裝的下),對H分量除以了2,也就是0-180的范圍,S和V分量乘以了255,將0-1的范圍擴展到0-255。我們在設(shè)置閾值的時候需要參照opencv的標(biāo)準(zhǔn),因此對參數(shù)要進(jìn)行一個轉(zhuǎn)換。
二. 是v和s取值的問題。對于暗的圖來說,取值過大容易漏,而對于亮的圖,取值過小則容易跟車身混淆。因此可以考慮最適應(yīng)的改變閾值。
三. 是模板問題。目前的做法是針對藍(lán)色和黃色的匹配使用了兩個模板,而不是統(tǒng)一的模板。統(tǒng)一模板的問題在于擔(dān)心藍(lán)色和黃色的干擾問題,例如黃色的車與藍(lán)色的牌的干擾,或者藍(lán)色的車和黃色牌的干擾,這里面最典型的例子就是一個帶有藍(lán)色車牌的黃色出租車,在很多城市里這已經(jīng)是“標(biāo)準(zhǔn)配置”。因此需要將藍(lán)色和黃色的匹配分別用不同的模板處理。
了解完這三個細(xì)節(jié)以后,下面就是代碼部分。
?View Code
3.不足
以上說明了顏色定位的設(shè)計思想與細(xì)節(jié)。那么顏色定位是不是就是萬能的?答案是否定的。在色彩充足,光照足夠的情況下,顏色定位的效果很好,但是在面對光線不足的情況,或者藍(lán)色車身的情況時,顏色定位的效果很糟糕。下圖是一輛藍(lán)色車輛,可以看出,車牌與車身內(nèi)容完全重疊,無法分割。
?
圖9 失效的顏色定位?
碰到失效的顏色定位情況時需要使用原先的Sobel定位法。
目前的新版本使用了顏色定位與Sobel定位結(jié)合的方式。首先進(jìn)行顏色定位,然后根據(jù)條件使用Sobel進(jìn)行再次定位,增加整個系統(tǒng)的適應(yīng)能力。
為了加強魯棒性,Sobel定位法可以用兩階段的查找。也就是在已經(jīng)被Sobel定位的圖塊中,再進(jìn)行一次Sobel定位。這樣可以增加準(zhǔn)確率,但會降低了速度。一個折衷的方案是讓用戶決定一個參數(shù)m_maxPlates的值,這個值決定了你在一幅圖里最多定位多少車牌。系統(tǒng)首先用顏色定位出候選車牌,然后通過SVM模型來判斷是否是車牌,最后統(tǒng)計數(shù)量。如果這個數(shù)量大于你設(shè)定的參數(shù),則認(rèn)為車牌已經(jīng)定位足夠了,不需要后一步處理,也就不會進(jìn)行兩階段的Sobel查找。相反,如果這個數(shù)量不足,則繼續(xù)進(jìn)行Sobel定位。
綜合定位的代碼位于CPlateDectec中的的成員函數(shù)plateDetectDeep中,以下是plateDetectDeep的整體流程。
?圖10 綜合定位全部流程?
有沒有顏色定位與Sobel定位都失效的情況?有的。這種情況下可能需要使用第三類定位技術(shù)--字符定位技術(shù)。這是EasyPR發(fā)展的一個方向,這里不展開討論。
?
二.?偏斜扭轉(zhuǎn)
解決了顏色的定位問題以后,下面的問題是:在定位以后,我們?nèi)绾伟哑边^來的車牌扭正呢?
?
?
圖11 偏斜扭轉(zhuǎn)效果?
這個過程叫做偏斜扭轉(zhuǎn)過程。其中一個關(guān)鍵函數(shù)就是opencv的仿射變換函數(shù)。但在具體實施時,有很多需要解決的問題。
?
1.分析
在任何新的功能開發(fā)之前,技術(shù)預(yù)研都是第一步。
在這篇文檔介紹了opencv的仿射變換功能。效果見下圖。
圖12 仿射變換效果?
?
仔細(xì)看下,貌似這個功能跟我們的需求很相似。我們的偏斜扭轉(zhuǎn)功能,說白了,就是把對圖像的觀察視角進(jìn)行了一個轉(zhuǎn)換。
不過這篇文章里的代碼基本來自于另一篇官方文檔。官方文檔里還有一個例子,可以矩形扭轉(zhuǎn)成平行四邊形。而我們的需求正是將平行四邊形的車牌扭正成矩形。這么說來,只要使用例子中對應(yīng)的反函數(shù),應(yīng)該就可以實現(xiàn)我們的需求。從這個角度來看,偏斜扭轉(zhuǎn)功可以實現(xiàn)。確定了可行性以后,下一步就是思考如何實現(xiàn)。
在原先的版本中,我們對定位出來的區(qū)域會進(jìn)行一次角度判斷,當(dāng)角度小于某個閾值(默認(rèn)30度)時就會進(jìn)行全圖旋轉(zhuǎn)。
這種方式有兩個問題:
一是我們的策略是對整幅圖像旋轉(zhuǎn)。對于opencv來說,每次旋轉(zhuǎn)操作都是一個矩形的乘法過程,對于非常大的圖像,這個過程是非常消耗計算資源的;
二是30度的閾值無法處理示例圖片。事實上,示例圖片的定位區(qū)域的角度是-50度左右,已經(jīng)大于我們的閾值了。為了處理這樣的圖片,我們需要把我們的閾值增大,例如增加到60度,那么這樣的結(jié)果是帶來候選區(qū)域的增多。
兩個因素結(jié)合,會大幅度增加處理時間。為了不讓處理速度下降,必須想辦法規(guī)避這些影響。
一個方法是不再使用全圖旋轉(zhuǎn),而是區(qū)域旋轉(zhuǎn)。其實我們在獲取定位區(qū)域后,我們并不需要定位區(qū)域以外的圖像。
倘若我們能劃出一塊小的區(qū)域包圍定位區(qū)域,然后我們僅對定位區(qū)域進(jìn)行旋轉(zhuǎn),那么計算量就會大幅度降低。而這點,在opencv里是可以實現(xiàn)的,我們對定位區(qū)域RotatedRect用boundingRect()方法獲取外接矩形,再使用Mat(Rect ...)方法截取這個區(qū)域圖塊,從而生成一個小的區(qū)域圖像。于是下面的所有旋轉(zhuǎn)等操作都可以基于這個區(qū)域圖像進(jìn)行。
在這些設(shè)計決定以后,下面就來思考整個功能的架構(gòu)。
我們要解決的問題包括三類,第一類是正的車牌,第二類是傾斜的車牌,第三類是偏斜的車牌。前兩類是前面說過的,第三類是本次新增的功能需求。第二類傾斜車牌與第三類車牌的區(qū)別見下圖。
圖13 兩類不同的旋轉(zhuǎn)?
?
通過上圖可以看出,正視角的旋轉(zhuǎn)圖片的觀察角度仍然是正方向的,只是由于路的不平或者攝像機的傾斜等原因,導(dǎo)致矩形有一定傾斜。這類圖塊的特點就是在RotataedRect內(nèi)部,車牌部分仍然是個矩形。偏斜視角的圖片的觀察角度是非正方向的,是從側(cè)面去看車牌。這類圖塊的特點是在RotataedRect內(nèi)部,車牌部分不再是個矩形,而是一個平行四邊形。這個特性決定了我們需要區(qū)別的對待這兩類圖片。
一個初步的處理思路就是下圖。
?
圖14 分析實現(xiàn)流程
簡單來說,整個處理流程包括下面四步:
1.感興趣區(qū)域的截取
2.角度判斷
3.偏斜判斷
4.仿射變換?
接下來按照這四個步驟依次介紹。
2.ROI截取
如果要使用區(qū)域旋轉(zhuǎn),首先我們必須從原圖中截取出一個包含定位區(qū)域的圖塊。
opencv提供了一個從圖像中截取感興趣區(qū)域ROI的方法,也就是Mat(Rect ...)。這個方法會在Rect所在的位置,截取原圖中一個圖塊,然后將其賦值到一個新的Mat圖像里。遺憾的是這個方法不支持RotataedRect,同時Rect與RotataedRect也沒有繼承關(guān)系。因此布不能直接調(diào)用這個方法。
我們可以使用RotataedRect的boudingRect()方法。這個方法會返回一個RotataedRect的最小外接矩形,而且這個矩形是一個Rect。因此將這個Rect傳遞給Mat(Rect...)方法就可以截取出原圖的ROI圖塊,并獲得對應(yīng)的ROI圖像。
需要注意的是,ROI圖塊和ROI圖像的區(qū)別,當(dāng)我們給定原圖以及一個Rect時,原圖中被Rect包圍的區(qū)域稱為ROI圖塊,此時圖塊里的坐標(biāo)仍然是原圖的坐標(biāo)。當(dāng)這個圖塊里的內(nèi)容被拷貝到一個新的Mat里時,我們稱這個新Mat為ROI圖像。ROI圖像里僅僅只包含原來圖塊里的內(nèi)容,跟原圖沒有任何關(guān)系。所以圖塊和圖像雖然顯示的內(nèi)容一樣,但坐標(biāo)系已經(jīng)發(fā)生了改變。在從ROI圖塊到ROI圖像以后,點的坐標(biāo)要計算一個偏移量。
下一步的工作中可以僅對這個ROI圖像進(jìn)行處理,包括對其旋轉(zhuǎn)或者變換等操作。
示例圖片中的截取出來的ROI圖像如下圖:
?
圖15 截取后的ROI圖像
在截取中可能會發(fā)生一個問題。如果直接使用boundingRect()函數(shù)的話,在運行過程中會經(jīng)常發(fā)生這樣的異常。OpenCV Error: Assertion failed (0 <= roi.x && 0 <= roi.width && roi.x + roi.width <= m.cols && 0 <= roi.y && 0 <= roi.height && roi.y + roi.height <= m.rows) incv::Mat::Mat,如下圖。
?
圖16 不安全的外接矩形函數(shù)會拋出異常
?
這個異常產(chǎn)生的原因在于,在opencv2.4.8中(不清楚opencv其他版本是否沒有這個問題),boundingRect()函數(shù)計算出的Rect的四個點的坐標(biāo)沒有做驗證。這意味著你計算一個RotataedRect的最小外接矩形Rect時,它可能會給你一個負(fù)坐標(biāo),或者是一個超過原圖片外界的坐標(biāo)。于是當(dāng)你把Rect作為參數(shù)傳遞給Mat(Rect ...)的話,它會提示你所要截取的Rect中的坐標(biāo)越界了!
解決方案是實現(xiàn)一個安全的計算最小外接矩形Rect的函數(shù),在boundingRect()結(jié)果之上,對角點坐標(biāo)進(jìn)行一次判斷,如果值為負(fù)數(shù),就置為0,如果值超過了原始Mat的rows或cols,就置為原始Mat的這些rows或cols。
這個安全函數(shù)名為calcSafeRect(...),下面是這個函數(shù)的代碼。
?View Code?
3.擴大化旋轉(zhuǎn)
好,當(dāng)我通過calcSafeRect(...)獲取了一個安全的Rect,然后通過Mat(Rect ...)函數(shù)截取了這個感興趣圖像ROI以后。下面的工作就是對這個新的ROI圖像進(jìn)行操作。
首先是判斷這個ROI圖像是否要旋轉(zhuǎn)。為了降低工作量,我們不對角度在-5度到5度區(qū)間的ROI進(jìn)行旋轉(zhuǎn)(注意這里講的角度針對的生成ROI的RotataedRect,ROI本身是水平的)。因為這么小的角度對于SVM判斷以及字符識別來說,都是沒有影響的。
對其他的角度我們需要對ROI進(jìn)行旋轉(zhuǎn)。當(dāng)我們對ROI進(jìn)行旋轉(zhuǎn)以后,接著把轉(zhuǎn)正后的RotataedRect部分從ROI中截取出來。
但很快我們就會碰到一個新問題。讓我們看一下下圖,為什么我們截取出來的車牌區(qū)域最左邊的“川”字和右邊的“2”字發(fā)生了形變?為了搞清這個原因,作者仔細(xì)地研究了旋轉(zhuǎn)與截取函數(shù),但很快發(fā)現(xiàn)了形變的根源在于旋轉(zhuǎn)后的ROI圖像。
仔細(xì)看一下旋轉(zhuǎn)后的ROI圖像,是否左右兩側(cè)不再完整,像是被截去了一部分?
?
圖17 旋轉(zhuǎn)后圖像被截斷?
?
要想理解這個問題,需要理解opencv的旋轉(zhuǎn)變換函數(shù)的特性。作為旋轉(zhuǎn)變換的核心函數(shù),affinTransform會要求你輸出一個旋轉(zhuǎn)矩陣給它。這很簡單,因為我們只需要給它一個旋轉(zhuǎn)中心點以及角度,它就能計算出我們想要的旋轉(zhuǎn)矩陣。旋轉(zhuǎn)矩陣的獲得是通過如下的函數(shù)得到的:
Mat rot_mat = getRotationMatrix2D(new_center, angle, 1);在獲取了旋轉(zhuǎn)矩陣rot_mat,那么接下來就需要調(diào)用函數(shù)warpAffine來開始旋轉(zhuǎn)操作。這個函數(shù)的參數(shù)包括一個目標(biāo)圖像、以及目標(biāo)圖像的Size。目標(biāo)圖像容易理解,大部分opencv的函數(shù)都會需要這個參數(shù)。我們只要新建一個Mat即可。那么目標(biāo)圖像的Size是什么?在一般的觀點中,假設(shè)我們需要旋轉(zhuǎn)一個圖像,我們給opencv一個原始圖像,以及我需要在某個旋轉(zhuǎn)點對它旋轉(zhuǎn)一個角度的需求,那么opencv返回一個圖像給我即可,這個圖像的Size或者說大小應(yīng)該是opencv返回給我的,為什么要我來告訴它呢?
你可以試著對一個正方形進(jìn)行旋轉(zhuǎn),仔細(xì)看看,這個正方形的外接矩形的大小會如何變化?當(dāng)旋轉(zhuǎn)角度還小時,一切都還好,當(dāng)角度變大時,明顯我們看到的外接矩形的大小也在擴增。在這里,外接矩形被稱為視框,也就是我需要旋轉(zhuǎn)的正方形所需要的最小區(qū)域。隨著旋轉(zhuǎn)角度的變大,視框明顯增大。
?
圖18 矩形旋轉(zhuǎn)后所需視框增大??
?
在圖像旋轉(zhuǎn)完以后,有三類點會獲得不同的處理,一種是有原圖像對應(yīng)點且在視框內(nèi)的,這些點被正常顯示;一類是在視框內(nèi)但找不到原圖像與之對應(yīng)的點,這些點被置0值(顯示為黑色);最后一類是有原圖像與之對應(yīng)的點,但不在視框內(nèi)的,這些點被悲慘的拋棄。
?
圖19 旋轉(zhuǎn)后三類不同點的命運?
這就是旋轉(zhuǎn)后不同三類點的命運,也就是新生成的圖像中一些點呈現(xiàn)黑色(被置0),一些點被截斷(被拋棄)的原因。如果把視框調(diào)整大點的話,就可以大幅度減少被截斷點的數(shù)量。所以,為了保證旋轉(zhuǎn)后的圖像不被截斷,因此我們需要計算一個合理的目標(biāo)圖像的Size,讓我們的感興趣區(qū)域得到完整的顯示。
下面的代碼使用了一個極為簡單的策略,它將原始圖像與目標(biāo)圖像都進(jìn)行了擴大化。首先新建一個尺寸為原始圖像1.5倍的新圖像,接著把原始圖像映射到新圖像上,于是我們得到了一個顯示區(qū)域(視框)擴大化后的原始圖像。顯示區(qū)域擴大以后,那些在原圖像中沒有值的像素被置了一個初值。
接著調(diào)用warpAffine函數(shù),使用新圖像的大小作為目標(biāo)圖像的大小。warpAffine函數(shù)會將新圖像旋轉(zhuǎn),并用目標(biāo)圖像尺寸的視框去顯示它。于是我們得到了一個所有感興趣區(qū)域都被完整顯示的旋轉(zhuǎn)后圖像。
這樣,我們再使用getRectSubPix()函數(shù)就可以獲得想要的車牌區(qū)域了。
圖20 擴大化旋轉(zhuǎn)后圖像不再被截斷
以下就是旋轉(zhuǎn)函數(shù)rotation的代碼。
?View Code?
4.偏斜判斷
當(dāng)我們對ROI進(jìn)行旋轉(zhuǎn)以后,下面一步工作就是把RotataedRect部分從ROI中截取出來,這里可以使用getRectSubPix方法,這個函數(shù)可以在被旋轉(zhuǎn)后的圖像中截取一個正的矩形圖塊出來,并賦值到一個新的Mat中,稱為車牌區(qū)域。
下步工作就是分析截取后的車牌區(qū)域。車牌區(qū)域里的車牌分為正角度和偏斜角度兩種。對于正的角度而言,可以看出車牌區(qū)域就是車牌,因此直接輸出即可。而對于偏斜角度而言,車牌是平行四邊形,與矩形的車牌區(qū)域不重合。
如何判斷一個圖像中的圖形是否是平行四邊形?
一種簡單的思路就是對圖像二值化,然后根據(jù)二值化圖像進(jìn)行判斷。圖像二值化的方法有很多種,假設(shè)我們這里使用一開始在車牌定位功能中使用的大津閾值二值化法的話,效果不會太好。因為大津閾值是自適應(yīng)閾值,在完整的圖像中二值出來的平行四邊形可能在小的局部圖像中就不再是。最好的辦法是使用在前面定位模塊生成后的原圖的二值圖像,我們通過同樣的操作就可以在原圖中截取一個跟車牌區(qū)域?qū)?yīng)的二值化圖像。
下圖就是一個二值化車牌區(qū)域獲得的過程。
圖21 二值化的車牌區(qū)域
?
接下來就是對二值化車牌區(qū)域進(jìn)行處理。為了判斷二值化圖像中白色的部分是平行四邊形。一種簡單的做法就是從圖像中選擇一些特定的行。計算在這個行中,第一個全為0的串的長度。從幾何意義上來看,這就是平行四邊形斜邊上某個點距離外接矩形的長度。
假設(shè)我們選擇的這些行位于二值化圖像高度的1/4,2/4,3/4處的話,如果是白色圖形是矩形的話,這些串的大小應(yīng)該是相等或者相差很小的,相反如果是平行四邊形的話,那么這些串的大小應(yīng)該不等,并且呈現(xiàn)一個遞增或遞減的關(guān)系。通過這種不同,我們就可以判斷車牌區(qū)域里的圖形,究竟是矩形還是平行四邊形。
偏斜判斷的另一個重要作用就是,計算平行四邊形傾斜的斜率,這個斜率值用來在下面的仿射變換中發(fā)揮作用。我們使用一個簡單的公式去計算這個斜率,那就是利用上面判斷過程中使用的串大小,假設(shè)二值化圖像高度的1/4,2/4,3/4處對應(yīng)的串的大小分別為len1,len2,len3,車牌區(qū)域的高度為Height。一個計算斜率slope的計算公式就是:(len3-len1)/Height*2。
Slope的直觀含義見下圖。
?
?
?圖22 slope的幾何含義
?
需要說明的,這個計算結(jié)果在平行四邊形是右斜時是負(fù)值,而在左斜時則是正值。于是可以根據(jù)slope的正負(fù)判斷平行四邊形是右斜或者左斜。在實踐中,會發(fā)生一些公式不能應(yīng)對的情況,例如像下圖這種情況,斜邊的部分區(qū)域發(fā)生了內(nèi)凹或者外凸現(xiàn)象。這種現(xiàn)象會導(dǎo)致len1,len2或者len3的計算有誤,因此slope也會不準(zhǔn)。
?
圖23 內(nèi)凹現(xiàn)象
為了實現(xiàn)一個魯棒性更好的計算方法,可以用(len2-len1)/Height*4與(len3-len1)/Height*2兩者之間更靠近tan(angle)的值作為solpe的值(在這里,angle代表的是原來RotataedRect的角度)。
多采取了一個slope備選的好處是可以避免單點的內(nèi)凹或者外凸,但這仍然不是最好的解決方案。在最后的討論中會介紹一個其他的實現(xiàn)思路。
完成偏斜判斷與斜率計算的函數(shù)是isdeflection,下面是它的代碼。
?View Code?
5.仿射變換
俗話說:行百里者半九十。前面已經(jīng)做了如此多的工作,應(yīng)該可以實現(xiàn)偏斜扭轉(zhuǎn)功能了吧?但在最后的道路中,仍然有問題等著我們。
我們已經(jīng)實現(xiàn)了旋轉(zhuǎn)功能,并且在旋轉(zhuǎn)后的區(qū)域中截取了車牌區(qū)域,然后判斷車牌區(qū)域中的圖形是一個平行四邊形。下面要做的工作就是把平行四邊形扭正成一個矩形。
圖24 從平行四邊形車牌到矩形車牌
?
首先第一個問題就是解決如何從平行四邊形變換成一個矩形的問題。opencv提供了一個函數(shù)warpAffine,就是仿射變換函數(shù)。注意,warpAffine不僅可以讓圖像旋轉(zhuǎn)(前面介紹過),也可以進(jìn)行仿射變換,真是一個多才多藝的函數(shù)。o
通過仿射變換函數(shù)可以把任意的矩形拉伸成其他的平行四邊形。opencv的官方文檔里給了一個示例,值得注意的是,這個示例演示的是把矩形變換為平行四邊形,跟我們想要的恰恰相反。但沒關(guān)系,我們先看一下它的使用方法。
?
圖25 opencv官網(wǎng)上對warpAffine使用的示例
?
warpAffine方法要求輸入的參數(shù)是原始圖像的左上點,右上點,左下點,以及輸出圖像的左上點,右上點,左下點。注意,必須保證這些點的對應(yīng)順序,否則仿射的效果跟你預(yù)想的不一樣。通過這個方法介紹,我們可以大概看出,opencv需要的是三個點對(共六個點)的坐標(biāo),然后建立一個映射關(guān)系,通過這個映射關(guān)系將原始圖像的所有點映射到目標(biāo)圖像上。
?
圖26 warpAffine需要的三個對應(yīng)坐標(biāo)點
?
再回來看一下我們的需求,我們的目標(biāo)是把車牌區(qū)域中的平行四邊形映射為一個矩形。讓我們做個假設(shè),如果我們選取了車牌區(qū)域中的平行四邊形車牌的三個關(guān)鍵點,然后再確定了我們希望將車牌扭正成的矩形的三個關(guān)鍵點的話,我們是否就可以實現(xiàn)從平行四邊形車牌到矩形車牌的扭正?
讓我們畫一幅圖像來看看這個變換的作用。有趣的是,把一個平行四邊形變換為矩形會對包圍平行四邊形車牌的區(qū)域帶來影響。
例如下圖中,藍(lán)色的實線代表扭轉(zhuǎn)前的平行四邊形車牌,虛線代表扭轉(zhuǎn)后的。黑色的實線代表矩形的車牌區(qū)域,虛線代表扭轉(zhuǎn)后的效果。可以看到,當(dāng)藍(lán)色車牌被扭轉(zhuǎn)為矩形的同時,黑色車牌區(qū)域則被扭轉(zhuǎn)為平行四邊形。
注意,當(dāng)車牌區(qū)域扭變?yōu)槠叫兴倪呅我院?#xff0c;需要顯示它的視框增大了。跟我們在旋轉(zhuǎn)圖像時碰到的情形一樣。
?
?圖27 平行四邊形的扭轉(zhuǎn)帶來的變化
?
讓我們先實際嘗試一下仿射變換吧。
根據(jù)仿射函數(shù)的需要,我們計算平行四邊形車牌的三個關(guān)鍵點坐標(biāo)。其中左上點的值(xdiff,0)中的xdiff就是根據(jù)車牌區(qū)域的高度height與平行四邊形的斜率slope計算得到的:
為了計算目標(biāo)矩形的三個關(guān)鍵點坐標(biāo),我們首先需要把扭轉(zhuǎn)后的原點坐標(biāo)調(diào)整到平行四邊形車牌區(qū)域左上角位置。見下圖。
?
圖28 原圖像的坐標(biāo)計算
依次推算關(guān)鍵點的三個坐標(biāo)。它們應(yīng)該是
plTri[0] = Point2f(0 + xiff, 0);plTri[1] = Point2f(width - 1, 0);plTri[2] = Point2f(0, height - 1);dstTri[0] = Point2f(xiff, 0);dstTri[1] = Point2f(width - 1, 0);dstTri[2] = Point2f(xiff, height - 1);??
根據(jù)上圖的坐標(biāo),我們開始進(jìn)行一次仿射變換的嘗試。
opencv的warpAffine函數(shù)不會改變變換后圖像的大小。而我們給它傳遞的目標(biāo)圖像的大小僅會決定視框的大小。不過這次我們不用擔(dān)心視框的大小,因為根據(jù)圖27看來,哪怕視框跟原始圖像一樣大,我們也足夠顯示扭正后的車牌。
看看仿射的效果。暈,好像效果不對,視框的大小是足夠了,但是圖像往右偏了一些,導(dǎo)致最右邊的字母沒有顯示全。
?
圖29 被偏移的車牌區(qū)域
?
這次的問題不再是目標(biāo)圖像的大小問題了,而是視框的偏移問題。仔細(xì)觀察一下我們的視框,倘若我們想把車牌全部顯示的話,視框往右偏移一段距離,是不是就可以解決這個問題呢?為保證新的視框中心能夠正好與車牌的中心重合,我們可以選擇偏移xidff/2長度。正如下圖所顯示的一樣。
?
?圖30 考慮偏移的坐標(biāo)計算
?
視框往右偏移的含義就是目標(biāo)圖像Mat的原點往右偏移。如果原點偏移的話,那么仿射后圖像的三個關(guān)鍵點的坐標(biāo)要重新計算,都需要減去xidff/2大小。
重新計算的映射點坐標(biāo)為下:
plTri[0] = Point2f(0 + xiff, 0);plTri[1] = Point2f(width - 1, 0);plTri[2] = Point2f(0, height - 1);dstTri[0] = Point2f(xiff/2, 0);dstTri[1] = Point2f(width - 1 - xiff + xiff/2, 0);dstTri[2] = Point2f(xiff/2, height - 1);?
再試一次。果然,視框被調(diào)整到我們希望的地方了,我們可以看到所有的車牌區(qū)域了。這次解決的是warpAffine函數(shù)帶來的視框偏移問題。
?
圖31 完整的車牌區(qū)域
?
關(guān)于坐標(biāo)調(diào)整的另一個理解就是當(dāng)中心點保持不變時,平行四邊形扭正為矩形時恰好是左上的點往左偏移了xdiff/2的距離,左下的點往右偏移了xdiff/2的距離,形成一種對稱的平移。可以使用ps或者inkspace類似的矢量制圖軟件看看“斜切”的效果,
如此一來,就完成了偏斜扭正的過程。需要注意的是,向左傾斜的車牌的視框偏移方向與向右傾斜的車牌是相反的。我們可以用slope的正負(fù)來判斷車牌是左斜還是右斜。
?
6.總結(jié)
通過以上過程,我們成功的將一個偏斜的車牌經(jīng)過旋轉(zhuǎn)變換等方法扭正過來。
讓我們回顧一下偏斜扭正過程。我們需要將一個偏斜的車牌扭正,為了達(dá)成這個目的我們首先需要對圖像進(jìn)行旋轉(zhuǎn)。因為旋轉(zhuǎn)是個計算量很大的函數(shù),所以我們需要考慮不再用全圖旋轉(zhuǎn),而是區(qū)域旋轉(zhuǎn)。在旋轉(zhuǎn)過程中,會發(fā)生圖像截斷問題,所以需要使用擴大化旋轉(zhuǎn)方法。旋轉(zhuǎn)以后,只有偏斜視角的車牌才需要扭正,正視角的車牌不需要,因此還需要一個偏斜判斷過程。如此一來,偏斜扭正的過程需要旋轉(zhuǎn),區(qū)域截取,擴大化,偏斜判斷等等過程的協(xié)助,這就是整個流程中有這么多步需要處理的原因。
下圖從另一個視角回顧了偏斜扭正的過程,主要說明了偏斜扭轉(zhuǎn)中的兩次“截取”過程。
圖32 偏斜扭正全過程
?
整個過程有一個統(tǒng)一的函數(shù)--deskew。下面是deskew的代碼。
?View Code?
最后是改善建議:
角度偏斜判斷時可以用白色區(qū)域的輪廓來確定平行四邊形的四個點,然后用這四個點來計算斜率。這樣算出來的斜率的可能魯棒性更好。
?
三.?總結(jié)
本篇文檔介紹了顏色定位與偏斜扭轉(zhuǎn)等功能。其中顏色定位屬于作者一直想做的定位方法,而偏斜扭轉(zhuǎn)則是作者以前認(rèn)為不可能解決的問題。這些問題現(xiàn)在都基本被攻克了,并在這篇文檔中闡述,希望這篇文檔可以幫助到讀者。
作者希望能在這片文檔中不僅傳遞知識,也傳授我在摸索過程中積累的經(jīng)驗。因為光知道怎么做并不能加深對車牌識別的認(rèn)識,只有經(jīng)歷過失敗,了解哪些思想嘗試過,碰到了哪些問題,是如何解決的,才能幫助讀者更好地認(rèn)識這個系統(tǒng)的內(nèi)涵。
?
最后,作者很感謝能夠閱讀到這里的讀者。如果看完覺得好的話,還請輕輕點一下贊,你們的鼓勵就是作者繼續(xù)行文的動力。
?
對EasyPR做下說明:EasyPR,一個開源的中文車牌識別系統(tǒng),代碼托管在github。其次,在前面的博客文章中,包含EasyPR至今的開發(fā)文檔與介紹。在后續(xù)的文章中,作者會介紹EasyPR中字符分割與識別等相關(guān)內(nèi)容,歡迎繼續(xù)閱讀。
?
版權(quán)說明:
?
本文中的所有文字,圖片,代碼的版權(quán)都是屬于作者和博客園共同所有。歡迎轉(zhuǎn)載,但是務(wù)必注明作者與出處。任何未經(jīng)允許的剽竊以及爬蟲抓取都屬于侵權(quán),作者和博客園保留所有權(quán)利。
?
參考文獻(xiàn):
1.http://blog.csdn.net/xiaowei_cqu/article/details/7616044
2.http://docs.opencv.org/doc/tutorials/imgproc/imgtrans/warp_affine/warp_affine.html
?
總結(jié)
以上是生活随笔為你收集整理的EasyPR--开发详解(5)颜色定位与偏斜扭转的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 李艳鹏:技术人如何修炼内功
- 下一篇: 时钟抖动(Jitter)和时钟偏斜(Sk