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’离这两条切线的距离,哪个最近就算哪一类。这样一来,每一个样本,就可以代表它附近的一个样本区域,不需要海量的样本,也能有效的计算不同形状间的相似性。
(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
{
publicstaticclass DaoConfig
{
privatestatic Boolean Inited =false;
publicstaticvoid Init()
{
if (!Inited)
{
Inited =true;
XmlConfigurationSource con =new XmlConfigurationSource(AppDomain.CurrentDomain.BaseDirectory +@"\ActiveRecord.config");
ActiveRecordStarter.Initialize
(con,
typeof(TrainPattern)
);
}
}
}
}
TrainPattern:// TrainPattern存在数据库里
[ActiveRecord("TrainPattern")]
publicclass 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; }
publicstatic TrainPattern[] FindAll()
{
String hql ="from TrainPattern ORDER BY Category DESC";
SimpleQuery<TrainPattern> query =new SimpleQuery<TrainPattern>(hql);
return query.Execute();
}
}
Classifier://主要调用封装在这里
publicclass Classifier
{
protectedstatic Orc.PatternRecognition.KnnClassifier<Int32> DefaultChineseCharClassifier;
protectedstatic Orc.PatternRecognition.KnnClassifier<Int32> DefaultEnglishAndNumberCharClassifier;
protectedstatic Orc.PatternRecognition.KnnClassifier<Int32> DefaultNumberCharClassifier;
publicstatic Int32 DefaultWidthSplitCount =3;
publicstatic Int32 DefaultHeightSplitCount =3;
publicstatic Int32 DefaultCharsCount =7; // 一张图片中包含的字符个数
publicstatic Int32 DefaultHeightTrimThresholdValue =4;
publicstatic ILog Log = LogManager.GetLogger("Vcr");
publicstaticvoid 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);
}
elseif (c >='a'&& c <='z')
DefaultEnglishAndNumberCharClassifier.AddTrainPattern(tpv);
elseif (c >='A'&& c <='Z')
DefaultEnglishAndNumberCharClassifier.AddTrainPattern(tpv);
else
DefaultChineseCharClassifier.AddTrainPattern(tpv);
}
}
}
protectedstatic 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;
}
protectedstatic SampleVector<Int32> CreateSampleVector(Bitmap bitmap)
{
ImageSpliter spliter =new ImageSpliter(bitmap);
spliter.WidthSplitCount = DefaultWidthSplitCount;
spliter.HeightSplitCount = DefaultHeightSplitCount;
spliter.Init();
returnnew SampleVector<Int32>(spliter.ValueList);
}
protectedstatic SampleVector<Int32> CreateYNormalSampleVector(Bitmap bitmap)
{
ImageSpliter spliter =new ImageSpliter(bitmap);
spliter.WidthSplitCount =1;
spliter.HeightSplitCount = DefaultHeightSplitCount;
spliter.Init();
returnnew SampleVector<Int32>(spliter.ValueList);
}
protectedstatic SampleVector<Int32> CreateXNormalSampleVector(Bitmap bitmap)
{
ImageSpliter spliter =new ImageSpliter(bitmap);
spliter.WidthSplitCount = DefaultWidthSplitCount;
spliter.HeightSplitCount =1;
spliter.Init();
returnnew SampleVector<Int32>(spliter.ValueList);
}
publicstatic String Classify(String imageFileName)
{
Log.Debug("识别文件:"+ imageFileName);
String result = String.Empty;
if (DefaultChineseCharClassifier ==null|| DefaultEnglishAndNumberCharClassifier ==null) thrownew 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, 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);
}
}*/
} |