本文源自我之前花了2天時間做的一個簡單的車牌識別系統。那個項目,時間太緊,樣本也有限,達不到對方要求的95%識別率(主要對於車牌來說,D,0,O,I,1等等太相似了。然後,漢字的識別難度也不小),因此未被對方接受。在此放出,同時描述一下思路及算法。
全文分兩部分,第一部分講車牌識別及普通驗證碼這一類識別的普通方法,第二部分講對類似QQ驗證碼,Gmail驗證碼這一類變態驗證碼的識別方法和思路。
一、車牌/驗證碼識別的普通方法
車牌、驗證碼識別的普通方法爲:
(1) 將圖片灰度化與二值化
(2) 去噪,然後切割成一個一個的字符
(3) 提取每一個字符的特徵,生成特徵矢量或特徵矩陣
(4) 分類與學習。將特徵矢量或特徵矩陣與樣本庫進行比對,挑選出相似的那類樣本,將這類樣本的值作爲輸出結果。
下面藉着代碼,描述一下上述過程。因爲更新SVN Server,我以前以bdb儲存的代碼訪問不了,因此部分代碼是用Reflector反編譯過來的,望見諒。
(1) 圖片的灰度化與二值化
這樣做的目的是將圖片的每一個象素變成0或者255,以便以計算。同時,也可以去除部分噪音。
圖片的灰度化與二值化的前提是bmp圖片,如果不是,則需要首先轉換爲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。一般的處理方法是設定一個區間,比如,[a,b],將[a,b]之間的灰度全部變成255,其它的變成0。這裏我採用的是網上廣爲流行的自適應二值化算法。
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
88
灰度化與二值化之前的圖片:
灰度化與二值化之後的圖片:
注:對於車牌識別來說,這個算法還不錯。對於驗證碼識別,可能需要針對特定的網站設計特殊的二值化算法,以過濾雜色。
(2) 去噪,然後切割成一個一個的字符
上面這張車牌切割是比較簡單的,從左到右掃描一下,碰見空大的,咔嚓一刀,就解決了。但有一些車牌,比如這張:
簡單的掃描就解決不了。因此需要一個比較通用的去噪和切割算法。這裏我採用的是比較樸素的方法:
將上面的圖片看成是一個平面。將圖片向水平方向投影,這樣有字的地方的投影值就高,沒字的地方投影得到的值就低。這樣會得到一根曲線,像一個又一個山頭。下面是我手畫示意圖:
然後,用一根掃描線(上圖中的S)從下向上掃描。這個掃描線會與圖中曲線存在交點,這些交點會將山頭分割成一個又一個區域。車牌圖片一般是7個字符,因此,當掃描線將山頭分割成七個區域時停止。然後根據這七個區域向水平線的投影的座標就可以將圖片中的七個字符分割出來。
但是,現實是複雜的。比如,“川”字,它的水平投影是三個山頭。按上面這種掃描方法會將它切開。因此,對於上面的切割,需要加上約束條件:每個山頭有一箇中心線,山頭與山頭的中心線的距離必需在某一個值之上,否則,則需要將這兩個山頭進行合併。加上這個約束之後,便可以有效的切割了。
以上是水平投影。然後還需要做垂直投影與切割。這裏的垂直投影與切割就一個山頭,因此好處理一些。
切割結果如下:
水平投影及切割代碼:
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;
89 }
90
代碼中的 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 }
11
PairStatus代表Pair的狀態。具體哪個狀態是什麼意義,我已經忘了。
1 private enum PairStatus
2 {
3 JustCreated,
4 Start,
5 End
6 }
7
以上這一段代碼寫的很辛苦,因爲要處理很多特殊情況。那個PairStatus 也是爲處理特殊情況引進的。
垂直投影與切割的代碼簡單一些,不貼了,見附後的dll的BitmapConverter.TrimHeight方法。
以上用到的是樸素的去噪與切割方法。有些圖片,尤其是驗證碼圖片,需要特別的去噪處理。具體操作方法就是,打開CxImage(http://www.codeproject.com/KB/graphics/cximage.aspx),或者Paint.Net,用上面的那些圖片處理方法,看看能否有效去噪。記住自己的操作步驟,然後翻他們的源代碼,將其中的算法提取出來。還有什麼細化啊,濾波啊,這些處理可以提高圖片的質量。具體可參考ITK的代碼或圖像處理書籍。
(3) 提取每一個字符的特徵,生成特徵矢量或特徵矩陣
將切割出來的字符,分割成一個一個的小塊,比如3×3,5×5,或3×5,或10×8,然後統計一下每小塊的值爲255的像素數量,這樣得到一個矩陣M,或者將這個矩陣簡化爲矢量V。
通過以上3步,就可以將一個車牌中的字符數值化爲矢量了。
(1)-(3)步具體的代碼流程如下:
1
2 BitmapConverter.ToGrayBmp(bitmap); // 圖片灰度化
3 BitmapConverter.Binarizate(bitmap); // 圖片二值化
4 IList<Bitmap> mapList = BitmapConverter.Split(bitmap, DefaultCharsCount); // 水平投影然後切割
5 Bitmap map0 = BitmapConverter.TrimHeight(mapList[0], DefaultHeightTrimThresholdValue); // 垂直投影然後切割
6 ImageSpliter spliter = new ImageSpliter(map0);
7 spliter.WidthSplitCount = DefaultWidthSplitCount;
8 spliter.HeightSplitCount = DefaultHeightSplitCount;
9 spliter.Init();
10
然後,通過spliter.ValueList就可以獲得 Bitmap map0 的矢量表示。
(4) 分類
分類的原理很簡單。用(Vij,Ci)表示一個樣本。其中,Vij是樣本圖片經過上面過程數值化後的矢量。Ci是人肉眼識別這張圖片,給出的結果。Vij表明,有多個樣本,它們的數值化後的矢量不同,但是它們的結果都是Ci。假設待識別的圖片矢量化後,得到的矢量是V’。
直觀上,我們會有這樣一個思路,就是這張待識別的圖片,最像樣本庫中的某張圖片,那麼我們就將它當作那張圖片,將它識別爲樣本庫中那張圖片事先指定的字符。
在我們眼睛裏,判斷一張圖片和另一張圖片是否相似很簡單,但對於電腦來說,就很難判斷了。我們前面已經將圖片數值化爲一個個維度一樣的矢量,電腦是怎樣判斷一個矢量與另一個矢量相似的呢?
這裏需要計算一個矢量與另一個矢量間的距離。這個距離越短,則認爲這兩個矢量越相似。
我用 SampleVector<T> 來代表矢量:
1 public class SampleVector<T>
2 {
3 protected T[] Vector { get; set; }
4 public Int32 Dimension { get { return Vector.Length; } }
5 ……
6 }
7
T代表數據類型,可以爲Int32,也可以爲Double等更精確的類型。
測量距離的公共接口爲:IMetric
1 public interface IMetric<TElement,TReturn>
2 {
3 TReturn Compute(SampleVector<TElement> v1, SampleVector<TElement> v2);
4 }
5
常用的是MinkowskiMetric。
1 /// <summary>
2 /// Minkowski 測度。
3 /// </summary>
4 public class MinkowskiMetric<TElement> : IMetric<TElement, Double>
5 {
6 public Int32 Scale { get; private set; }
7 public MinkowskiMetric(Int32 scale)
8 { Scale = scale; }
9
10 public Double Compute(SampleVector<TElement> v1, SampleVector<TElement> v2)
11 {
12 if (v1 == null || v2 == null) throw new ArgumentNullException();
13 if (v1.Dimension != v2.Dimension) throw new ArgumentException("v1 和 v2 的維度不等.");
14 Double result = 0;
15 for (int i = 0; i < v1.Dimension; i++)
16 {
17 result += Math.Pow(Math.Abs(Convert.ToDouble(v1[i]) - Convert.ToDouble(v2[i])), Scale);
18 }
19 return Math.Pow(result, 1.0 / Scale);
20 }
21 }
22
23 MetricFactory 負責生產各種維度的MinkowskiMetric:
24
25 public class MetricFactory
26 {
27 public static IMetric<TElement, Double> CreateMinkowskiMetric<TElement>(Int32 scale)
28 {
29 return new MinkowskiMetric<TElement>(scale);
30 }
31
32 public static IMetric<TElement, Double> CreateEuclideanMetric<TElement>()
33 {
34 return CreateMinkowskiMetric<TElement>(2);
35 }
36 }
37
MinkowskiMetric是普遍使用的測度。但不一定是最有效的量。因爲它對於矢量V中的每一個點都一視同仁。而在圖像識別中,每一個點的重要性卻並不一樣,例如,Q和O的識別,特徵在下半部分,下半部分的權重應該大於上半部分。對於這些易混淆的字符,需要設計特殊的測量方法。在車牌識別中,其它易混淆的有D和0,0和O,I和1。Minkowski Metric識別這些字符,效果很差。因此,當碰到這些字符時,需要進行特別的處理。由於當時時間緊,我就只用了Minkowski Metric。
我的代碼中,只實現了哪個最近,就選哪個。更好的方案是用K近鄰分類器或神經網絡分類器。K近鄰的原理是,找出和待識別的圖片(矢量)距離最近的K個樣本,然後讓這K個樣本使用某種規則計算(投票),這個新圖片屬於哪個類別(C);神經網絡則將測量的過程和投票判決的過程參數化,使它可以隨着樣本的增加而改變,是這樣的一種學習機。有興趣的可以去看《模式分類》一書的第三章和第四章。
二、 變態字符的識別
有些字符變形很嚴重,有的字符連在一起互相交叉,有的字符被掩蓋在一堆噪音海之中。對這類字符的識別需要用上特殊的手段。
下面介紹幾種幾個經典的處理方法,這些方法都是被證實對某些問題很有效的方法:
(1) 切線距離 (Tangent Distance):可用於處理字符的各種變形,OCR的核心技術之一。
(2) 霍夫變換(Hough Transform):對噪音極其不敏感,常用於從圖片中提取各種形狀。圖像識別中最基本的方法之一。
(3) 形狀上下文(Shape Context):將特徵高維化,對形變不很敏感,對噪音也不很敏感。新世紀出現的新方法。
因爲這幾種方法我均未編碼實現過,因此只簡單介紹下原理及主要應用場景。
(1) 切線距離
前面介紹了MinkowskiMetric。這裏我們看看下面這張圖:一個正寫的1與一個歪着的1.
用MinkowskiMetric計算的話,兩者的MinkowskiMetric很大。
然而,在圖像識別中,形狀形變是常事。理論上,爲了更好地識別,我們需要對每一種形變都採足夠的樣,這樣一來,會發現樣本數幾乎無窮無盡,計算量越來越大。
怎麼辦呢?那就是通過計算切線距離,來代替直接距離。切線距離比較抽象,我們將問題簡化爲二維空間,以便以理解。
上圖有兩條曲線。分別是兩個字符經過某一形變後所產生的軌跡。V1和V2是2個樣本。V’是待識別圖片。如果用樣本之間的直接距離,比較哪個樣本離V’最近,就將V’當作哪一類,這樣的話,就要把V’分給V1了。理論上,如果我們無限取樣的話,下面那一條曲線上的某個樣本離V’最近,V’應該歸類爲V2。不過,無限取樣不現實,於是就引出了切線距離:在樣本V1,V2處做切線,然後計算V’離這兩條切線的距離,哪個最近就算哪一類。這樣一來,每一個樣本,就可以代表它附近的一個樣本區域,不需要海量的樣本,也能有效的計算不同形狀間的相似性。
深入瞭解切線距離,可參考這篇文章。Transformation invariance in pattern recognition – tangent distance and tangent propagation (http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.32.9482)這篇文章。
(2) 霍夫變換
霍夫變換出自1962年的一篇專利。它的原理非常簡單:就是座標變換的問題。
如,上圖中左圖中的直線,對應着有圖中k-b座標系中的一個點。通過座標變換,可以將直線的識別轉換爲點的識別。點的識別就比直線識別簡單的多。爲了避免無限大無限小問題,常用的是如下變換公式:
下面這張圖是wikipedia上一張霍夫變換的示意圖。左圖中的兩條直線變換後正對應着右圖中的兩個亮點。
通過霍夫變換原理可以看出,它的抗干擾性極強極強:如果直線不是連續的,是斷斷續續的,變換之後仍然是一個點,只是這個點的強度要低一些。如果一個直線被一個矩形遮蓋住了,同樣不影響識別。因爲這個特徵,它的應用性非常廣泛。
對於直線,圓這樣容易被參數化的圖像,霍夫變換是最擅長處理的。對於一般的曲線,可通過廣義霍夫變換進行處理。感興趣的可以google之,全是數學公式,看的人頭疼。
(3) 形狀上下文
圖像中的像素點不是孤立的,每個像素點,處於一個形狀背景之下,因此,在提取特徵時,需要將像素點的背景也作爲該像素點的特徵提取出來,數值化。
形狀上下文(Shape Context,形狀背景)就是這樣一種方法:假定要提取像素點O的特徵,採用上圖(c)中的座標系,以O點作爲座標系的圓心。這個座標系將O點的上下左右切割成了12×5=60小塊,然後統計這60小塊之內的像素的特徵,將其數值化爲12×5的矩陣,上圖中的(d),(e),(f)便分別是三個像素點的Shape Context數值化後的結果。如此一來,提取的每一個點的特徵便包括了形狀特徵,加以計算,威力甚大。來看看Shape Context的威力:
上圖中的驗證碼,對Shape Context來說只是小Case。
看看這幾張圖。嘿嘿,硬是給識別出來了。
Shape Context是新出現的方法,其威力到底有多大目前還未見底。這篇文章是Shape context的必讀文章:Shape Matching and Object Recognitiom using shape contexts(http://www.cs.berkeley.edu/~malik/papers/BMP-shape.pdf)。最後那兩張驗證碼識別圖出自Greg Mori,Jitendra Malik的《Recognizing Objects in Adversarial Clutter:Breaking a Visual CAPTCHA》一文。
===========================================================
附件:第一部分的代碼(vcr.zip). 3個dll文件,反編譯看的很清晰。源代碼反而沒dll好看,我就不放了。其中,Orc.Generics.dll是幾個泛型類,Orc.ImageProcess.Common.dll 對圖像進行處理和分割,Orc.PatternRecognition.dll 是識別部分。
這三個dll可以直接用在車牌識別上。用於車牌識別,對易混淆的那幾個字符識別率較差,需要補充幾個分類器,現有分類器識別結果爲D ,O,0,I,1等時,用新分類器識別。用於識別驗證碼需要改一改。
有個asp.net的調用例子可實現在線上傳圖片識別,因爲其中包含多張車牌信息,不方便放出來。我貼部分代碼出來:
Global.asax:
void Application_Start(object sender, EventArgs e)
{
log4net.Config.XmlConfigurator.Configure();
Orc.Spider.Vcr.DaoConfig.Init();
Classifier.Update(Server);
}
DaoConfig:
using System;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework;
using Castle.ActiveRecord.Framework.Config;
namespace Orc.Spider.Vcr
{
public static class DaoConfig
{
private static Boolean Inited = false;
public static void Init()
{
if (!Inited)
{
Inited = true;
XmlConfigurationSource con = new XmlConfigurationSource(AppDomain.CurrentDomain.BaseDirectory + @"\ActiveRecord.config");
ActiveRecordStarter.Initialize
(con,
typeof(TrainPattern)
);
}
}
}
}
TrainPattern:// TrainPattern存在數據庫裏
[ActiveRecord("TrainPattern")]
public class TrainPattern : ActiveRecordBase<TrainPattern>
{
[PrimaryKey(PrimaryKeyType.Native, "Id")]
public Int32 Id { get; set; }
[Property("FileName")]
public String FileName { get; set; }
[Property("Category")]
public String Category { get; set; }
public static TrainPattern[] FindAll()
{
String hql = "from TrainPattern ORDER BY Category DESC";
SimpleQuery<TrainPattern> query = new SimpleQuery<TrainPattern>(hql);
return query.Execute();
}
}
Classifier://主要調用封裝在這裏
public class Classifier
{
protected static Orc.PatternRecognition.KnnClassifier<Int32> DefaultChineseCharClassifier;
protected static Orc.PatternRecognition.KnnClassifier<Int32> DefaultEnglishAndNumberCharClassifier;
protected static Orc.PatternRecognition.KnnClassifier<Int32> DefaultNumberCharClassifier;
public static Int32 DefaultWidthSplitCount = 3;
public static Int32 DefaultHeightSplitCount = 3;
public static Int32 DefaultCharsCount = 7; // 一張圖片中包含的字符個數
public static Int32 DefaultHeightTrimThresholdValue = 4;
public static ILog Log = LogManager.GetLogger("Vcr");
public static void Update(HttpServerUtility server)
{
TrainPattern[] TPList = TrainPattern.FindAll();
if (TPList == null) return;
DefaultChineseCharClassifier = new KnnClassifier<Int32>(DefaultWidthSplitCount * DefaultHeightSplitCount);
DefaultEnglishAndNumberCharClassifier = new KnnClassifier<Int32>(DefaultWidthSplitCount * DefaultHeightSplitCount);
DefaultNumberCharClassifier = new KnnClassifier<Int32>(DefaultWidthSplitCount * DefaultHeightSplitCount);
foreach (TrainPattern tp in TPList)
{
String path = server.MapPath(".") + "/VcrImage/" + tp.FileName;
using (Bitmap bitmap = new Bitmap(path))
{
TrainPattern<Int32> tpv = CreateTainPatternVector(bitmap, tp.Category.Substring(0, 1));
Char c = tpv.Category[0];
if (c >= '0' && c <= '9')
{
DefaultEnglishAndNumberCharClassifier.AddTrainPattern(tpv);
DefaultNumberCharClassifier.AddTrainPattern(tpv);
}
else if (c >= 'a' && c <= 'z')
DefaultEnglishAndNumberCharClassifier.AddTrainPattern(tpv);
else if (c >= 'A' && c <= 'Z')
DefaultEnglishAndNumberCharClassifier.AddTrainPattern(tpv);
else
DefaultChineseCharClassifier.AddTrainPattern(tpv);
}
}
}
protected static TrainPattern<Int32> CreateTainPatternVector(Bitmap bitmap, String categoryChars)
{
TrainPattern<int> tpv = new TrainPattern<int>( CreateSampleVector(bitmap), categoryChars);
tpv.XNormalSample = CreateXNormalSampleVector(bitmap);
tpv.YNormalSample = CreateYNormalSampleVector(bitmap);
return tpv;
}
protected static SampleVector<Int32> CreateSampleVector(Bitmap bitmap)
{
ImageSpliter spliter = new ImageSpliter(bitmap);
spliter.WidthSplitCount = DefaultWidthSplitCount;
spliter.HeightSplitCount = DefaultHeightSplitCount;
spliter.Init();
return new SampleVector<Int32>(spliter.ValueList);
}
protected static SampleVector<Int32> CreateYNormalSampleVector(Bitmap bitmap)
{
ImageSpliter spliter = new ImageSpliter(bitmap);
spliter.WidthSplitCount = 1;
spliter.HeightSplitCount = DefaultHeightSplitCount;
spliter.Init();
return new SampleVector<Int32>(spliter.ValueList);
}
protected static SampleVector<Int32> CreateXNormalSampleVector(Bitmap bitmap)
{
ImageSpliter spliter = new ImageSpliter(bitmap);
spliter.WidthSplitCount = DefaultWidthSplitCount;
spliter.HeightSplitCount = 1;
spliter.Init();
return new SampleVector<Int32>(spliter.ValueList);
}
public static String Classify(String imageFileName)
{
Log.Debug("識別文件:" + imageFileName);
String result = String.Empty;
if (DefaultChineseCharClassifier == null || DefaultEnglishAndNumberCharClassifier == null) throw new Exception("識別器未初始化.");
using (Bitmap bitmap = new Bitmap(imageFileName))
{
BitmapConverter.ToGrayBmp(bitmap);
BitmapConverter.Binarizate(bitmap);
IList<Bitmap> mapList = BitmapConverter.Split(bitmap, DefaultCharsCount);
if (mapList.Count == DefaultCharsCount)
{
Bitmap map0 = BitmapConverter.TrimHeight(mapList[0], DefaultHeightTrimThresholdValue);
TrainPattern<Int32> tp0 = CreateTainPatternVector(map0, " ");
String sv0Result = DefaultChineseCharClassifier.Classify(tp0);
Console.WriteLine("識別樣本: " + tp0.Sample.ToString());
result += sv0Result;
for (int i = 1; i < mapList.Count; i++)
{
Bitmap mapi = BitmapConverter.TrimHeight(mapList[i], DefaultHeightTrimThresholdValue);
TrainPattern<Int32> tpi = CreateTainPatternVector(mapi, " ");
Console.WriteLine("識別樣本: " + tpi.Sample.ToString());
if (i < mapList.Count - 3)
result += DefaultEnglishAndNumberCharClassifier.Classify(tpi);
else
result += DefaultNumberCharClassifier.Classify(tpi);
}
}
return result;
}
}
/*
public static IList<Tuple<Double,String>> ComputeDistance(String imageFileName)
{
if (DefaultChineseCharClassifier == null) throw new Exception("識別器未初始化.");
using (Bitmap bitmap = new Bitmap(imageFileName))
{
ImageSpliter spliter = new ImageSpliter(bitmap);
spliter.WidthSplitCount = DefaultWidthSplitCount;
spliter.HeightSplitCount = DefaultHeightSplitCount;
spliter.Init();
SampleVector<Int32> sv = new SampleVector<Int32>(spliter.ValueList);
return DefaultChineseCharClassifier.ComputeDistance(sv);
}
}*/
}