[转载]车牌识别及验证码识别的一般思路 - xiaotie - 博客园

来源: [转载]车牌识别及验证码识别的一般思路 – xiaotie – 博客园
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);
}
}*/
}

作者:xiaotie , 集异璧实验室(GEBLAB)

出处:http://www.cnblogs.com/xiaotie/

若标题中有“转载”字样,则本文版权归原作者所有。若无转载字样,本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏