車牌、驗證碼識別的普通方法為:
1.
將圖片灰度化與二值化。
2.
去噪,然后切割成一個一個的字符。
3.提取每一個字符的特征,生成特征矢量或特征矩陣。
4.
分類與學習。將特征矢量或特征矩陣與樣本庫進行比對,挑選出相似的那類樣本,將這類樣本的值作為輸出結(jié)果。
下面借著代碼,描述一下上述過程。
1.
圖片的灰度化與二值化
這樣做的目的是將圖片的每一個象素變成
0
或者255
,以便以計算。同時,也可以去除部分噪音。
圖片的灰度化與二值化的前提是
bmp
圖片,如果不是,則需要首先轉(zhuǎn)換為
bmp
圖片。
1
protected
static
Color Gray(Color c)
2 { 3 int rgb = Convert.ToInt32(( double ) ((( 0.3 * c.R) + ( 0.59 * c.G)) + ( 0.11 * c.B))); 4 return Color.FromArgb(rgb, rgb, rgb); 5 } 6 |
通過將圖片灰度化,每一個象素就變成了一個
0-255
的灰度值。然后是將灰度值二值化為 0 或 255 。一般的處理方法是設(shè)定一個區(qū)間,比如, [a,b] ,將 [a,b] 之間的灰度全部變成 255 ,其它的變成 0 。這里我采用的是網(wǎng)上廣為流行的自適應(yīng)二值化算法。
1
public
static
void
Binarizate(Bitmap map)
2 { 3 int tv = ComputeThresholdValue(map); 4 int x = map.Width; 5 int y = map.Height; 6 for ( int i = 0 ; i < x; i ++ ) 7 { 8 for ( int j = 0 ; j < y; j ++ ) 9 { 10 if (map.GetPixel(i, j).R >= tv) 11 { 12 map.SetPixel(i, j, Color.FromArgb( 0xff , 0xff , 0xff )); 13 } 14 else 15 { 16 map.SetPixel(i, j, Color.FromArgb( 0 , 0 , 0 )); 17 } 18 } 19 } 20 } 21 22 private static int ComputeThresholdValue(Bitmap img) 23 { 24 int i; 25 int k; 26 double csum; 27 int thresholdValue = 1 ; 28 int [] ihist = new int [ 0x100 ]; 29 for (i = 0 ; i < 0x100 ; i ++ ) 30 { 31 ihist[i] = 0 ; 32 } 33 int gmin = 0xff ; 34 int gmax = 0 ; 35 for (i = 1 ; i < (img.Width - 1 ); i ++ ) 36 { 37 for ( int j = 1 ; j < (img.Height - 1 ); j ++ ) 38 { 39 int cn = img.GetPixel(i, j).R; 40 ihist[cn] ++ ; 41 if (cn > gmax) 42 { 43 gmax = cn; 44 } 45 if (cn < gmin) 46 { 47 gmin = cn; 48 } 49 } 50 } 51 double sum = csum = 0.0 ; 52 int n = 0 ; 53 for (k = 0 ; k <= 0xff ; k ++ ) 54 { 55 sum += k * ihist[k]; 56 n += ihist[k]; 57 } 58 if (n == 0 ) 59 { 60 return 60 ; 61 } 62 double fmax = - 1.0 ; 63 int n1 = 0 ; 64 for (k = 0 ; k < 0xff ; k ++ ) 65 { 66 n1 += ihist[k]; 67 if (n1 != 0 ) 68 { 69 int n2 = n - n1; 70 if (n2 == 0 ) 71 { 72 return thresholdValue; 73 } 74 csum += k * ihist[k]; 75 double m1 = csum / (( double ) n1); 76 double m2 = (sum - csum) / (( double ) n2); 77 double sb = ((n1 * n2) * (m1 - m2)) * (m1 - m2); 78 if (sb > fmax) 79 { 80 fmax = sb; 81 thresholdValue = k; 82 } 83 } 84 } 85 return thresholdValue; 86 } 87 |
灰度化與二值化之前的圖片:
灰度化與二值化之后的圖片:
注:對于車牌識別來說,這個算法還不錯。對于驗證碼識別,可能需要針對特定的網(wǎng)站設(shè)計特殊的二值化算法,以過濾雜色。
2. 去噪,然后切割成一個一個的字符
上面這張車牌切割是比較簡單的,從左到右掃描一下,碰見空大的,咔嚓一刀,就解決了。但有一些車牌,比如這張:
簡單的掃描就解決不了。因此需要一個比較通用的去噪和切割算法。 這里我采用的是比較樸素的方法: 將上面的圖片看成是一個平面。將圖片向水平方向投影,這樣有字的地方的投影值就高,沒字的地方投影得到的值就低。
然后,用一根掃描線 從下向上掃描。這個掃描線會與圖中曲線存在交點,這些交點會將山頭分割成一個又一個區(qū)域。車牌圖片一般是 7 個字符,因此,當掃描線將山頭分割成七個區(qū)域時停止。然后根據(jù)這七個區(qū)域向水平線的投影的坐標就可以將圖片中的七個字符分割出來。
但是,現(xiàn)實是復雜的。比如,“川”字,它的水平投影是三個山頭。按上面這種掃描方法會將它切開。因此,對于上面的切割,需要加上約束條件:每個山頭有一個中心線,山頭與山頭的中心線的距離必需在某一個值之上,否則,則需要將這兩個山頭進行合并。加上這個約束之后,便可以有效的切割了。
以上是水平投影。然后還需要做垂直投影與切割。這里的垂直投影與切割就一個山頭,因此好處理一些。
水平投影及切割代碼:
1
public
static
IList
<
Bitmap
>
Split(Bitmap map,
int
count)
2 { 3 if (count <= 0 ) 4 { 5 throw new ArgumentOutOfRangeException( " Count 必須大于0. " ); 6 } 7 IList < Bitmap > resultList = new List < Bitmap > (); 8 int x = map.Width; 9 int y = map.Height; 10 int splitBitmapMinWidth = 4 ; 11 int [] xNormal = new int [x]; 12 for ( int i = 0 ; i < x; i ++ ) 13 { 14 for ( int j = 0 ; j < y; j ++ ) 15 { 16 if (map.GetPixel(i, j).R == CharGrayValue) 17 { 18 xNormal[i] ++ ; 19 } 20 } 21 } 22 Pair pair = new Pair(); 23 for ( int i = 0 ; i < y; i ++ ) 24 { 25 IList < Pair > pairList = new List < Pair > (count + 1 ); 26 for ( int j = 0 ; j < x; j ++ ) 27 { 28 if (xNormal[j] >= i) 29 { 30 if ((j == (x - 1 )) && (pair.Status == PairStatus.Start)) 31 { 32 pair.End = j; 33 pair.Status = PairStatus.End; 34 if ((pair.End - pair.Start) >= splitBitmapMinWidth) 35 { 36 pairList.Add(pair); 37 } 38 pair = new Pair(); 39 } 40 else if (pair.Status == PairStatus.JustCreated) 41 { 42 pair.Start = j; 43 pair.Status = PairStatus.Start; 44 } 45 } 46 else if (pair.Status == PairStatus.Start) 47 { 48 pair.End = j; 49 pair.Status = PairStatus.End; 50 if ((pair.End - pair.Start) >= splitBitmapMinWidth) 51 { 52 pairList.Add(pair); 53 } 54 pair = new Pair(); 55 } 56 if (pairList.Count > count) 57 { 58 break ; 59 } 60 } 61 if (pairList.Count == count) 62 { 63 foreach (Pair p in pairList) 64 { 65 if (p.Width < (map.Width / 10 )) 66 { 67 int width = (map.Width / 10 ) - p.Width; 68 p.Start = Math.Max( 0 , p.Start - (width / 2 )); 69 p.End = Math.Min(( int ) (p.End + (width / 2 )), ( int ) (map.Width - 1 )); 70 } 71 } 72 foreach (Pair p in pairList) 73 { 74 int newMapWidth = (p.End - p.Start) + 1 ; 75 Bitmap newMap = new Bitmap(newMapWidth, y); 76 for ( int ni = p.Start; ni <= p.End; ni ++ ) 77 { 78 for ( int nj = 0 ; nj < y; nj ++ ) 79 { 80 newMap.SetPixel(ni - p.Start, nj, map.GetPixel(ni, nj)); 81 } 82 } 83 resultList.Add(newMap); 84 } 85 return resultList; 86 } 87 } 88 return resultList; |
代碼中的
Pair,
代表掃描線與曲線的一對交點:
1
private
class
Pair
2 { 3 public Pair(); 4 public int CharPixelCount { get ; set ; } 5 public int CharPixelXDensity { get ; } 6 public int End { get ; set ; } 7 public int Start { get ; set ; } 8 public BitmapConverter.PairStatus Status { get ; set ; } 9 public int Width { get ; } 10 } |
PairStatus 代表 Pair 的狀態(tài)。
1
private
enum
PairStatus
2 { 3 JustCreated, 4 Start, 5 End 6 } |
3.
提取每一個字符的特征,生成特征矢量或特征矩陣將切割出來的字符,分割成一個一個的小塊,比如 3 × 3 , 5 × 5 ,或 3 × 5 ,或 10 × 8 ,然后統(tǒng)計一下每小塊的值為 255 的像素數(shù)量,這樣得到一個矩陣 M ,或者將這個矩陣簡化為矢量 V
通過以上 3 步,就可以將一個車牌中的字符數(shù)值化為矢量了。
4. 分類與學習。將特征矢量或特征矩陣與樣本庫進行比對,挑選出相似的那類樣本,將這類樣本的值作為輸出結(jié)果。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
