[转载]Kinect for Windows SDK开发初体验(三)骨骼追踪 - 马宁 - 博客园

mikel阅读(845)

来源: [转载]Kinect for Windows SDK开发初体验(三)骨骼追踪 – 马宁 – 博客园

作者:马宁

我们的Kinect SDK开发开始渐入佳境了,Skeleton Tracking(骨骼追踪)是Kinect的核心技术,正因为有了这项技术,很多有趣的功能才得以实现。

首先,我们来看一下骨骼追踪的具体实现。Kinect最多可以追踪20个骨骼点,而且目前只能追踪人体,其他的物体或者动物就无能为力了。下图介绍了Kinect骨骼点的分布情况:

7

初始化代码

接 下来,我们来看一下骨骼追踪的代码是如何编写的。首先,我们要创建一个新的Visual C#的工程,叫做“SkeletonTracking”,添加Kinect程序集和Coding4Fun程序集的工作,可以参考上一篇“Kinect for Windows SDK开发初体验(二)操作Camera”的内容,在这里不再重复。

首先我们还是创建Runtime对象,在初始化时,指定UseSkeletalTracking的RuntimeOptions,然后在SkeletonFrameReady事件中添加处理函数。

Runtime nui; 

private void Window_Loaded(object sender, RoutedEventArgs e)

{

nui = new Runtime();

nui.Initialize(RuntimeOptions.UseSkeletalTracking); 

nui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);

}

接下来,窗体关闭时的事件处理函数:

private void Window_Closed(object sender, EventArgs e)

{

nui.Uninitialize();

}

如果这个时候,我们在空的nui_SkeletonFrameReady事件处理函数中,添加一个断点:

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) 

{

}

正确连接Kinect设备,并且站在Kinect前,让Kinect能够正确识别人体时,SkeletonFrameReady事件将被触发。

我们可以通过下图看到返回的事件处理参数,其中比较重要的是SkeletonFrame和Skeletons两个对象。

image[11]

添加代码

接下来,我们要准备WPF的界面,在界面上添加五个小球,来跟踪头部、双手和膝盖的位置。在MainPage.xaml中,添加下列代码:

<Canvas Name="MainCanvas">
<Ellipse Canvas.Left="0" Canvas.Top="0" Height="50" Name="headEllipse" Stroke="Black" Width="50" Fill="Orange" />
<Ellipse Canvas.Left="50" Canvas.Top="0" Height="50" Name="rightEllipse" Stroke="Black" Width="50" Fill="SlateGray" />
<Ellipse Canvas.Left="100" Canvas.Top="0" Fill="SpringGreen" Height="50" Name="leftEllipse" Stroke="Black" Width="50" />
<Ellipse Canvas.Left="150" Canvas.Top="0" Height="50" Name="KneeRightEllipse" Stroke="Black" Width="50" Fill="Salmon" />
<Ellipse Canvas.Left="200" Canvas.Top="0" Fill="Navy" Height="50" Name="KneeLeftEllipse" Stroke="Black" Width="50" />
</Canvas>

然后,我们在SkeletonFrameReady事件处理函数中添加捕捉SkeletonData的方法:

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) 

{

SkeletonFrame allSkeletons = e.SkeletonFrame;

//get the first tracked skeleton

SkeletonData skeleton = (from s in allSkeletons.Skeletons

where s.TrackingState == SkeletonTrackingState.Tracked

select s).FirstOrDefault();

}

我们使用了LINQ来获取TrackingState等于Tracked的SkeletonData数据。在SkeletonData对象的 Joints属性集合中保存了所有骨骼点的信息。每个骨骼点的信息都是一个Joint对象,其中的Position的X、Y、Z表示了三维位置。其中X和 Y的范围都是-1到1,而Z是Kinect到识别物体的距离。

我们可以用下面的代码,将Joint的位置缩放到合适的比例:

Joint j = skeleton.Joints[JointID.HandRight].ScaleTo(640, 480, .5f, .5f);

最后两个参数为原始大小的最大值和最小值,上面的语句相当于将-0.5到0.5的范围扩大为0到640的范围。

我们封装了一个函数,将获取到的SkeletonData数据,转换为屏幕上的某一个圆圈:

private void SetEllipsePosition(FrameworkElement ellipse, Joint joint) 

{ 

var scaledJoint = joint.ScaleTo(640, 480, .5f, .5f);

Canvas.SetLeft(ellipse, scaledJoint.Position.X);

Canvas.SetTop(ellipse, scaledJoint.Position.Y); 

}

最后,我们SkeletonFrameReady事件的处理方法会是这样的:

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) 

{

SkeletonFrame allSkeletons = e.SkeletonFrame;

//get the first tracked skeleton

SkeletonData skeleton = (from s in allSkeletons.Skeletons

where s.TrackingState == SkeletonTrackingState.Tracked

select s).FirstOrDefault();

SetEllipsePosition(headEllipse, skeleton.Joints[JointID.Head]); 

SetEllipsePosition(leftEllipse, skeleton.Joints[JointID.HandLeft]); 

SetEllipsePosition(rightEllipse, skeleton.Joints[JointID.HandRight]);

SetEllipsePosition(KneeLeftEllipse, skeleton.Joints[JointID.KneeLeft]);

SetEllipsePosition(KneeRightEllipse, skeleton.Joints[JointID.KneeRight]);

}

最后,程序运行的效果如下,貌似膝盖的识别还是有些问题:

30

程序运行时,我们会发现小球运动时会有跳动的问题,为了减少这种情况,我们要设置SkeletonEngine引擎的 TransformSmooth属性为true,并指定TransformSmoothParameters参数,根据应用的具体情况,该参数也应该被适 当微调。

添加代码后的Load函数,代码如下:

private void Window_Loaded(object sender, RoutedEventArgs e)

{

nui = new Runtime();

nui.Initialize(RuntimeOptions.UseSkeletalTracking); 

//Must set to true and set after call to Initialize

nui.SkeletonEngine.TransformSmooth = true;

//Use to transform and reduce jitter

var parameters = new TransformSmoothParameters

{

Smoothing = 0.75f,

Correction = 0.0f,

Prediction = 0.0f,

JitterRadius = 0.05f,

MaxDeviationRadius = 0.04f

};

nui.SkeletonEngine.SmoothParameters = parameters;

nui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);

}

写到最后

到这里, Kinect最精华的部分“骨骼追踪”已经介绍给大家了,大家可以去写一些有趣的应用了。接下来,我们会介绍另外一个Kinect的核心功能——Depth Data,景深数据。

 

OpenXLive杯Windows Phone游戏开发大赛

clip_image019

OpenXLive杯Windows Phone游戏开发大赛,是由OpenXLive联合国内知名的开发者社区:DevDiv、智机网、WPMind、Silverlight银光中国和XNA游戏世界,一起举办的针对Windows Phone游戏开发的比赛。

http://www.openxlive.net/posts/news/40

[转载]使用Tesseract (OCR)实现简单的验证码识别(C#)+窗体淡入淡出效果 - 你的创可贴 - 博客园

mikel阅读(964)

来源: [转载]使用Tesseract (OCR)实现简单的验证码识别(C#)+窗体淡入淡出效果 – 你的创可贴 – 博客园

来园子也有一段时间了,一直没时间写点东西,说实话刚开始也不知道写什么,一直以来对验证码识别比较感兴趣,曾经想着自己处理图形实现识别验证码, 不过感觉对我来说太难了,偶然中再网上发现了Tesseract,于是就想借助Tesseract 来实现简单验证码的识别,正好今天周末有时间写了这点东西。

1、Tesseract介绍:

Tesseract的OCR引擎最先由HP实验室于 1985年开始研发,至1995年时已经成为OCR业内最准确的三款识别引擎之一。然而,HP不久便决定放弃OCR业务,Tesseract也从此尘封。 数年以后,HP意识到,与其将Tesseract束之高阁,不如贡献给开源软件业,让其重焕新生--2005年,Tesseract由美国内华达州信息技 术研究所获得,并求诸于Google对Tesseract进行改进、消除Bug、优化工作。

2、下载Tesseract:

http://code.google.com/p/tesseract-ocr/downloads/list,可以到这里下载一些识别必须的文件。

3、编写代码:

Tesseract可以在命令行中运行,但觉得不太好用,于是就通过代码调用DOS命令实现图像识别

using System.Diagnostics;
Process p = new Process();
p.StartInfo.FileName = "cmd.exe ";
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;

p.Start();
string cmd = "tesseract.exe " + PicPath + " " + filename + " -l chi_sim";//主要这句代码在起作用
p.StandardInput.WriteLine(cmd);
p.Close();

图片识别以后会把识别的内容保存在一个txt文件中,我们可以通过代码将保存在txt文件中的内容读出来,直观的显示出来

读取txt文件

public static string LoadDataFromTXT(string filePath)
{
string[] line = System.IO.File.ReadAllLines(filePath);
return line[0];
}

运行以后就可以看到这样的效果             

 

经过多次实验发现对于这种简单的数字验证码,识别正确率还是非常高的,但是对于我们中华民族博大精深的汉字,它表现的是否也如此优秀呢?那就让我们来做一下实验。。

在网上随便截一张带有汉字的图片:

 

让我们来看看汉字的识别效果吧:

大家看了一定很失望吧,对于汉字不是太给力,得到的结果乱七八糟,根本不能用,还要有很大的改进之处啊。。。不过它还是为我们识别一般验证码提供了方便。。

 

到这也基本讲完了,顺便讲一下如何实现窗体淡入淡出效果

using System.Runtime.InteropServices;
public class Win32
{
public const Int32 AW_HOR_POSITIVE = 0x00000001; // 从左到右打开窗口
public const Int32 AW_HOR_NEGATIVE = 0x00000002; // 从右到左打开窗口
public const Int32 AW_VER_POSITIVE = 0x00000004; // 从上到下打开窗口
public const Int32 AW_VER_NEGATIVE = 0x00000008; // 从下到上打开窗口
public const Int32 AW_CENTER = 0x00000010;
public const Int32 AW_HIDE = 0x00010000; // 在窗体卸载时若想使用本函数就得加上此常量
public const Int32 AW_ACTIVATE = 0x00020000; //在窗体通过本函数打开后,默认情况下会失去焦点,除非加上本常量
public const Int32 AW_SLIDE = 0x00040000;
public const Int32 AW_BLEND = 0x00080000; // 淡入淡出效果
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool AnimateWindow(
IntPtr hwnd, // handle to window
int dwTime, // duration of animation
int dwFlags // animation type
);
}
Win32.AnimateWindow(this.Handle, 1000, Win32.AW_BLEND);//淡入
Win32.AnimateWindow(this.Handle, 1000, Win32.AW_SLIDE | Win32.AW_HIDE | Win32.AW_BLEND);//淡出

 

效果呢就是这样:

至此全部结束,第一次写东西也不知道怎么写,也没什么技术含量,不过还是希望和大家一起学习,一起进步,有什么问题、指教请留言。。。

delphi验证码识别之如何识别高级验证码

mikel阅读(1139)

一、验证码的基本知识

1. 验证码的主要目的是强制人机交互来抵御机器自动化攻击的。

2. 大部分的验证码设计者并不得要领,不了解图像处理,机器视觉,模式识别,人工智能的基本概念。
3. 利用验证码,可以发财,当然要犯罪:比如招商银行密码只有6位,验证码形同虚设,计算机很快就能破解一个有钱的账户,很多帐户是可以网上交易的。

4. 也有设计的比较好的,比如Yahoo,Google,Microsoft等。而国内Tencent的中文验证码虽然难,但算不上好。

二、人工智能,模式识别,机器视觉,图像处理的基本知识

1)主要流程:

比如我们要从一副图片中,识别出验证码;比如我们要从一副图片中,检测并识别出一张人脸。 大概有哪些步骤呢?

1.图像采集:验证码呢,就直接通过HTTP抓HTML,然后分析出图片的url,然后下载保存就可以了。 如果是人脸检测识别,一般要通过视屏采集设备,采集回来,通过A/D转操作,存为数字图片或者视频频。

2.预处理:检测是正确的图像格式,转换到合适的格式,压缩,剪切出ROI,去除噪音,灰度化,转换色彩空间这些。

3.检测:车牌检测识别系统要先找到车牌的大概位置,人脸检测系统要找出图片中所有的人脸(包括疑似人脸);验证码识别呢,主要是找出文字所在的主要区域。

4.前处理:人脸检测和识别,会对人脸在识别前作一些校正,比如面内面外的旋转,扭曲等。我这里的验证码识别,“一般”要做文字的切割

5.训练:通过各种模式识别,机器学习算法,来挑选和训练合适数量的训练集。不是训练的样本越多越好。过学习,泛化能力差的问题可能在这里出现。这一步不是必须的,有些识别算法是不需要训练的。

6.识别:输入待识别的处理后的图片,转换成分类器需要的输入格式,然后通过输出的类和置信度,来判断大概可能是哪个字母。识别本质上就是分类。

2)关键概念:

图像处理:一般指针对数字图像的某种数学处理。比如投影,钝化,锐化,细化,边缘检测,二值化,压缩,各种数据变换等等。

1.二值化:一般图片都是彩色的,按照逼真程度,可能很多级别。为了降低计算复杂度,方便后续的处理,如果在不损失关键信息的情况下,能将图片处理成黑白两种颜色,那就最好不过了。

2.细化:找出图像的骨架,图像线条可能是很宽的,通过细化将宽度将为1,某些地方可能大于1。不同的细化算法,可能有不同的差异,比如是否更靠近线条中间,比如是否保持联通行等。

3. 边缘检测:主要是理解边缘的概念。边缘实际上是图像中图像像素属性变化剧烈的地方。可能通过一个固定的门限值来判断,也可能是自适应的。门限可能是图像全 局的,也可能是局部的。不能说那个就一定好,不过大部分时候,自适应的局部的门限可能要好点。被分析的,可能是颜色,也可能是灰度图像的灰度。

机器视觉:利用计算机来模式实现人的视觉。 比如物体检测,定位,识别。按照对图像理解的层次的差别,分高阶和低阶的理解。

模式识别:对事物或者现象的某种表示方式(数值,文字,我们这里主要想说的是数值),通过一些处理和分析,来描述,归类,理解,解释这些事物,现象及其某种抽象。

人工智能:这种概念比较宽,上面这些都属于人工智能这个大的方向。简单点不要过分学院派的理解就是,把人类的很“智能”的东西给模拟出来协助生物的人来处理问题,特别是在计算机里面。

三、常见的验证码的破解分析

以http://libcaca.zoy.org/wiki/PWNtcha这里PWNtcha项目中的资料为例分析,各种验证码的破解。(方法很多,仅仅从我个人乍看之下觉得可行的方法来分析)

1)Authimage


使用的反破解技巧:

1.不连续的点组成字符
2.有一定程度的倾斜

设计不好的地方:

1.通过纵横的直方图投影,可以找到字幕区域
2.通过Hough变换,适当的参数,可以找到近似的横线,可以做倾斜矫正
3.字符串的倾斜式面内的,没有太多的破解难度
4.字母宽度一定,大小一定

2)Clubic


使用的反破解技巧:

1.字符是手写体

设计不好的地方:

1.检测切割阶段没有任何技术含量,属于设计的比较丑的
2.只有数字,而且手写体变化不大
3.表面看起来对识别阶段有难度,仔细分析,发现几乎不用任何高级的训练识别算法,就固定的招某些像素点是否有色彩就够了

3)linuxfr.org


使用的反破解技巧:

1.背景颜色块
2.前景的横线或矩形

设计不好的地方:

1.背景色是单一色块,有形状,通过Region-Growth区域增长来很容易把背景给去掉
2.前景色是标准的线条,色彩单一
3.字母无粘连
4.都是印刷体

4)Ourcolony


使用的反破解技巧:

1.设计的太低级,不屑于去评价

设计不好的地方:

1.这种验证码,设计的最丑,但还是能把菜鸟搞定,毕竟学计算机的少,搞这个破解的更少,正所谓隔行如隔山

5)LiveJournal


使用的反破解技巧:

1.这个设计略微好点,使用个随机噪音,而且作为前景
2.字母位置粗细都有变化

设计不好的地方:

1.字母没有粘连
2.噪音类型单一
3.通过在X轴的直方图投影,能准确分割字幕
4.然后在Y周作直方图投影,能准确定位高度
5.识别阶段,都是印刷体,简单地很

四、网上的一些高级验证码

1)ICQ


2)IMDb


3)MS MVPS

4)MVN Forum

这些类型是被很多人认为比较难得类型,分析一下可以发现,字符检测,定位和分割都不是难。 唯一影响识别率的是IMDBb和MVPS这两类,字体变形略大。

总体来说,这些类型的破解也不难,很容易做到50%以上的识别率。

五、高级验证码的破解分析

时间关系,我简单介绍如何利用图像处理和模式识别技术,自动识别比较高级的验证码。
(以风头正劲的Google为例)


1)至少从目前的AI的发展程度看,没有简单的做法能自动处理各种不同的验证码,即使能力很强,那么系统自然也十分复杂强大。所以,要想在很简单的算法实现比较高级的验证码破解,必须分析不同验证码算法的特点:

作 为一般的图像处理和计算机视觉,会考虑色彩,纹理,形状等直接的特征,同时也考虑直方图,灰度等统计特征,还考虑FFT,Wavelet等各种变换后的特 征。但最终目标都是Dimension Reduction(降维)然后利于识别,不仅仅是速度的考虑。从图像的角度看,很多系统都考虑转换为灰度级甚者黑白图片。
Google的图片可以看出,颜色变化是虚晃一枪,不存在任何处理难度。难度是字体变形和字符粘连。
如果能成功的分割字符,那么后期识别无论是用SVM等分类算法,还是分析笔顺比划走向来硬识别,都相对好做。
2)图像处理和粘连分割

代码中的part1目录主要完成图像预处理和粘连字符分割
001:将图像从jpg等格式转换为位图便于处理
002:采用Fix/Adaptive的Threshold门限算法,将图片Bin-Value二值化。
(可用003算法)
003:采用OSTU分水岭算法,将图片Bin-Value二值化。
(更通用,大部分时候效果更好)
005:获取ROI感兴趣的区域。
006:Edge Trace边缘跟踪。
007:Edge Detection边界检测。
008:Thin细化去骨架。
009:做了一些Tidy整理。
(这个一般要根据特定的Captcha算法调整)
010:做切割,注意图片中红色的交叉点。
011:将边缘检测和骨干交叉点监测的图像合并。
(合并过程可以做分析: 比如X坐标偏移门限分析,交叉点区域纹理分析,线条走势分析,等等各种方法,找出更可能的切分点和分离后部件的组合管理。)


代码:(代码质量不高,从其他项目拷贝过来,简单修改的。)

查看代码(./pstzine_09_01.txt)

注: 在这里,我们可以看到,基本的部件(字母是分割开了,但可以造成统一字母的被切割成多个Component。 一种做法是:利用先验知识,做分割; 另外一种做法是,和第二部分的识别结合起来。 比如按照从左至右,尝试增加component来识别,如果不能识别而且component的总宽度,总面积还比较小,继续增加。 当然不排除拒识的可能性。 )

3)字符部件组合和识别。

part2的代码展示了切割后的字母组合,和基于svm的字 符识别的训练和识别过程。Detection.cpp中展示了ImageSpam检测过程中的一些字符分割和组合,layout的分析和利用的简单技术。 而Google的验证码的识别,完全可以不用到,仅做参考。

SVM及使用:

本质上,SVM是一个分类器,原始的 SVM是一个两类分类的分类器。可以通过1:1或者1:n的方式来组合成一个多类分类的分类器。 天生通过核函数的使用支持高维数据的分类。从几何意义上讲,就是找到最能表示类别特征的那些向量(支持向量SV),然后找到一条线,能最大化分类的 Margin。

libSVM是一个不错的实现。

训练间断和识别阶段的数据整理和归一化是一样的。 这里的简单做法是:

首先:

#define SVM_MAX +0.999
#define SVM_MIN +0.001

其次:

扫描黑白待识别字幕图片的每个像素,如果为0(黑色,是字母上的像素),那么svm中该位置就SVM_MAX,反之则反。

最后:

训练阶段,在svm的input的前面,为该类打上标记,即是那一个字母。
识别阶段,当然这个类别标记是SVM分类出来。

注意:

如果是SVM菜鸟,最好找一个在SVM外边做了包装的工具,比如样本选择,交叉验证,核函数选择这些,让程序自动选择和分析。

代码:通过ReginGrowth来提取单个单个的字符,然后开始识别。

查看代码(./pstzine_09_02.txt)

六、对验证码设计的一些建议

1.在噪音等类型的使用上,尽力让字符和用来混淆的前景和背景不容易区分。尽力让坏人(噪音)长得和好人(字母)一样。

2. 特别好的验证码的设计,要尽力发挥人类擅长而AI算法不擅长的。 比如粘连字符的分割和手写体(通过印刷体做特别的变形也可以)。 而不要一味的去加一些看起来比较复杂的噪音或者其他的花哨的东西。即使你做的足够复杂,但如果人也难识别,显然别人认为你是没事找抽型的。

3. 从专业的机器视觉的角度说,验证码的设计,一定要让破解者在识别阶段,反复在低阶视觉和高阶视觉之间多反复几次才能识别出来。 这样可以大大降低破解难度和破解的准确率。

作者:水木

[转载]用OCR技术识别验证码---tesseract - 网际大鱼 - 博客园

mikel阅读(970)

来源: [转载]用OCR技术识别验证码—tesseract – 网际大鱼 – 博客园

世界排名第三的。http://sourceforge.net/projects/tesseract-ocr

编译:
察看dsw文件,发现是VC6的工程,用VC6打开,如果用VS2003或者vs2005编译,会有很多编译错误。开始编译,有很多错误,然后Clean掉,Rebuild ALL,出现681 warning后成功编译。

发布:
bin.dbg下面是编译好的文件,然后把tessdata文件夹拷贝到bin.dbg下,需要测试的图像文件拷贝到此目录。文件必 须是未压缩的tif位图格式(bit-map)。而且只能有一个bit的颜色。也就是非黑既白,而不仅仅是灰度。在photoshop里面可以简单地转 换。彩色的要先转成灰度(grey scale)才能转成位图(bitmap)
测试:
执行例子图像文件tesseract.exe phototest.tif abc batch
输出结果在abc.txt,识别率竟然是100%。当然你自己做的图片就不一定有这么高。

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

mikel阅读(982)

来源: [转载]车牌识别及验证码识别的一般思路 – 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/

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

[转载]图像识别练习(字符验证码、车牌号、身份证号) - 野比喵 - 博客园

mikel阅读(767)

来源: [转载]图像识别练习(字符验证码、车牌号、身份证号) – 野比喵 – 博客园

欢迎大家和我讨论相关问题。联系方式:1429013154

代码在此(注意此版并非最终版)

光学字符识别(OCR)是非常有用的技术。在验证码识别、车牌号识别、文字识别方面,基于字符的识别技术算是比较容易上手的了(相比图文识别)。

闲来看到有朋友研究验证码识别,一时手痒,野比自己动手来做做验证码识别。当然,肯定只是简单的验证码。

名为验证码,实际上并不限于,还可以识别车牌号、身份证号、门牌号等各种乱七八糟的内容。

识别的流程很明确:

1、预处理图像

2、做y轴的投影

3、分析直方图分区

4、根据分区拆分图像为多个字符(很关键,拆得越好,后续识别率越高)

5、丢弃空白或无效字符

6、自动旋转字符(如果有倾斜),识别字符

如果样本中个图像有粘连,则可能造成分区不准确。这种情况下,需要进行旋转,但是怎样自动旋转,是个难题。

目前已可拆分出字符,下一步准备研究如何识别。(如果单个字符比较规范,可以利用现成的OCR控件)

这里有一些例子。

普通的验证码(毫无难度)

带干扰的验证码

较高强度干扰(目前使用的分区算法不能解决,需要更好的算法,比如动态阈值)

CSDN的验证码(毫无压力)

身份证号码

车牌号

补充个QQ验证码,用单一阈值方法,识别很困难,需要结合字符宽度进行判断

这是单一阈值分区的结果(没有限制宽度),可以看到效果很差。

继续研究如何优化分区算法,如何识别单个文字(可以考虑多重识别+样本训练)。

 

附上太平洋网站验证码。

有些粘连,但是可以通过固定字符宽度解决(宽度基本一致)

参考这张图(获得整个宽度,然后除以字符数得到每个宽度,分别提取)

二值化我用的Otsu算法,参考文献:”A threshold selection method from gray-level histograms”, IEEE Trans. Systems, Man and Cybernetics 9(1), pp. 62–66, 1979

关于验证码,这篇论文很不错,建议参考:”Text-based CAPTCHA Strengths and Weaknesses”, ACM Computer and Communication security 2011 (CSS’2011)

© 野比 2012

改进的去污算法

 

污损车牌号拆分字符

[转载]啥都不懂也能识别验证码 - 杨中科 - 博客园

mikel阅读(1150)

来源: [转载]啥都不懂也能识别验证码 – 杨中科 – 博客园

相信大家在开发一些程序会有识别图片上文字(即所谓的OCR)的需求,比如识别车牌、识别图片格式的商品价格、识别图片格式的邮箱地址等等,当然需求最多 的还是识别验证码。如果要完成这些OCR的工作,需要你掌握图像处理、图像识别的知识,需要用到图形形态学、傅里叶变换、矩阵变换、贝叶斯决策等很多复杂 的理论,这让绝大部分人都会望而却步。

    Tesseract这个开源项目的出现让我们普通人也可以涉足OCR的开发。Tesseract可以从 图片中识别出文字内容,但不要以为Tesseract可以智能的识别出各种奇形怪状、复杂的图片文字,Tesseract默认只能识别非常标准字体、清晰 无干扰的图片文字,刚接触Tesseract的人很多都会发出这样的评价“Tesseract吹的挺厉害,但是识别率很低呀,不好用”。其实我们要识别的 内容千奇百怪,Tesseract是需要去训练才能比较高准确率的识别的,我们需要把一批样本图片让Tesseract去尝试识别,然后对他识别出的错误 结果进行校正,告诉他“这个图片你识别错了,应该识别为某某某”,这样Tesseract慢慢的就“学会了”怎么样进行识别。也就是如下一个训练过程:

看到上图有一个“预处理”,这是什么意思呢?我们知道,很多验证码都是加了一些干扰处理的,比如说有的验证码加了噪音点、有的验证码加了干扰线、有的验证码加了干扰背景、有的验证码做了文字扭曲。如下图:

这些图片如果直接交给Tesseract去处理,识别的难度会非常大。开发人员应该在把图片交给Tesseract之前对图片进行比较的预处理操作,比如 去掉干扰线、去掉背景噪点、字符矫正等等,有些复杂的预处理操作可能会涉及到图形形态学中比较深入的理论,这不是一篇文章能够介绍的,下面只列出比较简单 的图片预处理的基本知识,深入学习请参考图形学相关资料。

1、.Net中图片对象类是Image类,使用Image.FromFile(file)来加载一张图片,一般的图片都是位图,Bitmap类是 Image类的子类,所以我们一般把Image. FromFile()返回值转换为Bitmap类型使用Bitmap bitmap = (Bitmap)Image.FromFile(file)

2、Bitmap. Save()用来把内存中的图片对象保存到输出中去。第二个参数为图片格式。

3、由于Bitmap关联到GDI的非托管资源,实现了IDisposable接口,所以需要使用using进行对象的资源管理,以避免程序内存泄露的问题。关于using、IDisposable这些C#/.Net基础知识这里不再介绍。

4、如果要进行高效的图片操作,需要配合指针对Bitmap进行操作,当然为了避免对C#指针操作不熟悉的读者,这篇文章中我将会使用效率略低但是 比较易懂的GetPixel、 SetPixel方法来进行图片操作。GetPixel、 SetPixel是Bitmap提供的两个方法,分别可以用来对图片进行指定坐标像素点颜色的读取和设置指定坐标像素点的颜色。

 

接下来开始讲解Tesseract的使用:

一、     首先我们要采集多张有代表性的验证码样本图片,因为比较复杂的验证码的训练过程会比较长,而这次的验证码识别免费公开课时间有限,因此我挑选了相对比较简 单的验证码进行识别。复杂验证码的识别过程也是大同小异的。我测试用的100张验证码图片在文章最后的“公开课软件、图片库和代码.zip”压缩包中。

二、     这些图片有一些明显的噪音背景和干扰线,但是噪音背景和干扰线的颜色就是那几个,因此我使用拾色器拾取了这些点的颜色,使用如下的代码把那些颜色替换为白色,并且保存为tif格式的图片:

string[] files = Directory.GetFiles(@”D:\KuaiPan\如鹏网\班级资料\公开课\2013年4月份验证码识别\haijia”,”*.gif”);

for (int i = 0; i < files.Length; i++) { string file = files[i]; using(Bitmap bitmap = (Bitmap)Image.FromFile(file)) using (Bitmap newBitmap = Process(bitmap)) { newBitmap.Save(@"F:\aa\"+i+".tif",ImageFormat.Tiff); } } private static Bitmap Process(Bitmap bitmap) { Bitmap newBitmap = new Bitmap(bitmap.Width, bitmap.Height); for (int x = 0; x < bitmap.Width; x++) { for (int y = 0; y < bitmap.Height; y++) { //去掉边框 if (x == 0 || y == 0 || x == bitmap.Width - 1 || y == bitmap.Height - 1) { newBitmap.SetPixel(x, y, Color.White); } else { Color color = bitmap.GetPixel(x, y); //如果点的颜色是背景干扰色,则变为白色 if (color.Equals(Color.FromArgb(204, 204, 51)) || color.Equals(Color.FromArgb(153, 204, 51)) || color.Equals(Color.FromArgb(204, 255, 102)) || color.Equals(Color.FromArgb(204, 204, 204)) || color.Equals(Color.FromArgb(204, 255, 51))) { newBitmap.SetPixel(x, y, Color.White); } else { newBitmap.SetPixel(x, y, color); } } } } return newBitmap; } [/code] F:\aa\文件夹下就会有100张图片转换后的图片,转换后效果如下: 可以看到背景颜色和干扰线全部被去掉了。 三、 接下来运行jTessBoxEditor(jTessBoxEditor是使用java编写的,因此先需要安装配置java运行环境,对java运行环境安装配置不熟悉的朋友请自行寻找资料),双击jTessBoxEditor.jar即可启动运行。将第二步处理后的tiff使用主菜单 “Tool→Merge Tiff”图片合并为一张图片,比如保存到F:\aa\下haijia.tif文件中。 四、 下载安装tesseract-ocr-setup-3.01-1.exe(我使用3.02有问题,不知道是我的问题还是我不会用,总之还是推荐大家这里先使用3.01版本),这个setup版本会自动把安装目录添加到Path环境变量中,推荐使用。如果下载portable版本则需要自己编辑环境把tesseract解压路径添加到Path环境变量中。 五、 在下一步操作之前需要先给训练结果取一个名字,比如我这里就取haijia这个名字。如果你取了别的名字,只要把后面所有操作中的“haijia”改成你取的名字即可。 六、 启动windows命令行窗口,并且进入haijia.tif文件所在的目录,然后执行tesseract.exe haijia.tif haijia batch.nochop makebox。其中haijia.tif就是第三步生成的合并tiff文件的文件名,haijia则是咱们取的训练名。这样就会生成初始识别结果的haijia.box文件 七、 保证haijia.box文件和haijia.tif文件文件名完全一样并且放在同一个文件夹下。使用jTessBoxEditor打开haijia.tif文件,逐个校正文字,后保存。注意在jTessBoxEditor中每次修改完了字符都要回车,注意及时保存。如果发现多识别了、两个字母被识别为一个字母、一个字母被识别为两个字母等错误,需要使用Merge、split、delete之类的功能进行微调,还要通过修改X、Y、W、H修改自动识别的区域 。 八、 所有的自动识别结果矫正完毕后命令行执行tesseract.exe haijia.tif haijia nobatch box.train 九、 命令行执行unicharset_extractor.exe haijia.box 十、 在目录下新建一个名字为“font_properties”的文件,并且输入文本(其中haijia就是训练的名字,保存的时候使用EditPlus等高级文本编辑器去掉BOM头或者保存为ANSI格式): haijia 1 0 0 1 0 十一、 命令行执行cntraining.exe haijia.tr 十二、 命令行执行 mftraining.exe -F font_properties -U unicharset haijia.tr 十三、 第12步完成后,目录下应该生成若干个文件了,把unicharset, inttemp, normproto, pfftable这四个文件加上训练名字前缀“haijia.”。 十四、 命令行执行“combine_tessdata haijia.” 来合并生成的haijia.traineddata训练文件。执行完这步后,文件夹下就应该生成一个haijia.traineddata文件了,这个文件就是识别用的训练数据文件,只需要这一个haijia.traineddata文件即可,之前用的tif图片文件以及其他的中间文件都不需要了。 十五、 接下来调用Tesseract的API就可以在程序中进行验证码识别了。Tesseract支持很多语言,C/C++、Java、.Net、PHP、Python都有相应的API封装,只要寻找你使用语言的API库即可。下面还是以.Net为例。 a) 使用tesseractdotnet_v301_r590.zip中的tesseract.dll添加到相应的引用。注意tesseractdotnet默认只支持.Net 2.0,所以需要把项目的Target修改为.Net 2.0,如果你需要在2.0以上的版本中使用,则需要自己下载tesseractdotnet的源代码进行编译。 b) 采用第二步同样的方法对待识别的图片进行预处理,然后执行下面的代码对预处理过后的图片对象进行识别: [code] using (Bitmap bitmap = (Bitmap)Image.FromFile(file)) using (Bitmap newBitmap = Process(bitmap))//图片预处理 { TesseractProcessor processor = new TesseractProcessor(); processor.SetPageSegMode(ePageSegMode.PSM_SINGLE_LINE); //F:\aa\是haijia.traineddata文件所在的文件夹。注意路径必须以\结尾,并且路径要用\分割,而不是/分割,否则会报错AccessViolation异常 processor.Init(@"F:\aa \", "haijia", (int)eOcrEngineMode.OEM_DEFAULT); string result = processor.Recognize(newBitmap);//返回值就是识别结果 MessageBox.Show(result); } [/code] 酷!帅!

[转载]分享12306全自动验证码识别提交,春运抢票准备时 - guozili@163.com - 博客园

mikel阅读(886)

来源: [转载]分享12306全自动验证码识别提交,春运抢票准备时 – guozili@163.com – 博客园

  本插件用于自动验证码OCR,自动提交,彻底实现无人值守抢票,结合早些文章提到的动态切站+动态DNS+验证码OCR,将完美超越现有的其他插件,此代码还在原型阶段,后续将完善和提供更多支持。
插件说明
  • 下载:http://pan.baidu.com/s/1pJ1CgLd
  • 目前只支持Chrome较高版本
  • 可以结合以前文章”分享12306秒票心得及杀手锏“
  • 参考文章1:百度”360抢票王验证码自动识别真的那么牛吗“
  • 参考文章2:百度”chome manifest.json embed“
主要功能列表
  • 支持动态验证码识别,自动提交,适合无人值收刷票
  • 支持自动OCR不奏效时,还原成手动方式输入
  • 支持设置刷票时间,默认为1000毫秒
  • 支持动态切站,尽可能的尝试使用不同参数的查询,避免DNS缓存
  • 支持保持在线状态,防被踢出
首先下载Chrome插件,源码结构如下
然后安装Chrome插件
最后测试:登录刷票,勾选“自动提交”,弹窗后将自动OCR填写验证码,自动提交
   ——————————————————————————————————————————————
  • 加入“设置刷新时间”,加入“还原手动输入&禁用自动识别”

 

  ——————————————————————————————————————————————
  较高版本的Chrome可能会出现不能OCR,请按如下:
  

验证码识别

mikel阅读(1778)

距离上次写博客又很长时间了,这个验证码识别模块其实自己早写出来就是懒的写博客,现在离职了有时间把它拿出来。 总体说来这个验证码还是有一定难度的:字母数量不固定、位置不固定、带倾斜角度、带粘连、有噪点和干扰线。所以识别率还是比较低的,有个十分之一吧,但是 识别出来就可以了,反正是软件识别,又不是人识别。这个验证码识别模块是专门针对此类验证码优化的,所以如果想识别其他验证码估计稍改一下源码就行。

源代码

首先看界面:

界面有很多按钮,我按照顺序说一下,其实就是识别的顺序

1、获取验证码(动态从某网址获取)

2、去背景

3、去干扰

4、二值化

5、分割

6、识别

7、如果识别错误,同时某字母有识别的价值,就对应分割字母的顺序找到下面对应的框填上正确的字母,然后点击学习。下次再出现类似字母就可以正确识别了。

比如这次识别h错误,那就可以在对应的框中填写h然后点击学习。但是这个 h字母左上角有个黑色干扰块,没有学习的价值。

 

下面贴上一些源代码,大家也可以点击上面的链接下载源代码查看,注意不能删除Debug下的Sample文件夹。代码里面都有注释:

1、去背景代码:

原理:把图片中颜色数量最多的一部分看成背景并替换为白色,相当于去除背景。

复制代码
 1         /// <summary>
 2         /// 去背景
 3         /// 把图片中最多的一部分颜色视为背景色 选出来后替换为白色
 4         /// </summary>
 5         /// <param name="sender"></param>
 6         /// <param name="e"></param>
 7         private void btnDropBG_Click(object sender, EventArgs e)
 8         {
 9             if (picbox.Image == null)
10             {
11                 return;
12             }
13             Bitmap img = new Bitmap(picbox.Image);
14             //key 颜色  value颜色对应的数量
15             Dictionary<Color, int> colorDic = new Dictionary<Color, int>();
16             //获取图片中每个颜色的数量
17             for (var x = 0; x < img.Width; x++)
18             {
19                 for (var y = 0; y < img.Height; y++)
20                 {
21                     //删除边框
22                     if (y == 0 || y == img.Height)
23                     {
24                         img.SetPixel(x, y, Color.White);
25                     }
26 
27                     var color = img.GetPixel(x, y);
28                     var colorRGB = color.ToArgb();
29 
30                     if (colorDic.ContainsKey(color))
31                     {
32                         colorDic[color] = colorDic[color] + 1;
33                     }
34                     else
35                     {
36                         colorDic[color] = 1;
37                     }
38                 }
39             }
40             //图片中最多的颜色
41             Color maxColor = colorDic.OrderByDescending(o => o.Value).FirstOrDefault().Key;
42             //图片中最少的颜色
43             Color minColor = colorDic.OrderBy(o => o.Value).FirstOrDefault().Key;
44 
45             Dictionary<int[], double> maxColorDifDic = new Dictionary<int[], double>();
46             //查找 maxColor 最接近颜色
47             for (var x = 0; x < img.Width; x++)
48             {
49                 for (var y = 0; y < img.Height; y++)
50                 {
51                     maxColorDifDic.Add(new int[] { x, y }, GetColorDif(img.GetPixel(x, y), maxColor));
52                 }
53             }
54             //去掉和maxColor接近的颜色 即 替换成白色
55             var maxColorDifList = maxColorDifDic.OrderBy(o => o.Value).Where(o => o.Value < bjfz).ToArray();
56             foreach (var kv in maxColorDifList)
57             {
58                 img.SetPixel(kv.Key[0], kv.Key[1], Color.White);
59             }
60             picbox.Image = img;
61             pbNormal.Image = picbox.Image;
62         }
复制代码

 

2、去干扰代码、

原理:如果一个像素和周围上下左右四个像素点中的两面或三面颜色差别都很大,则认为这个点是噪点或干扰线。看到网上资料中有用中值滤波算法的,但是效果不好。

复制代码
  1         /// <summary>
  2         /// 去干扰
  3         /// </summary>
  4         /// <param name="sender"></param>
  5         /// <param name="e"></param>
  6         private void btnDropDisturb_Click(object sender, EventArgs e)
  7         {
  8             if (picbox.Image == null)
  9             {
 10                 return;
 11             }
 12             Bitmap img = new Bitmap(picbox.Image);
 13             byte[] p = new byte[9]; //最小处理窗口3*3
 14             byte s;
 15             //去干扰线
 16             for (var x = 0; x < img.Width; x++)
 17             {
 18                 for (var y = 0; y < img.Height; y++)
 19                 {
 20                     Color currentColor = img.GetPixel(x, y);
 21                     int color = currentColor.ToArgb();
 22                    
 23                     if (x > 0 && y > 0 && x < img.Width - 1 && y < img.Height - 1)
 24                     {
 25                         #region 中值滤波效果不好
 26                         ////取9个点的值
 27                         //p[0] = img.GetPixel(x - 1, y - 1).R;
 28                         //p[1] = img.GetPixel(x, y - 1).R;
 29                         //p[2] = img.GetPixel(x + 1, y - 1).R;
 30                         //p[3] = img.GetPixel(x - 1, y).R;
 31                         //p[4] = img.GetPixel(x, y).R;
 32                         //p[5] = img.GetPixel(x + 1, y).R;
 33                         //p[6] = img.GetPixel(x - 1, y + 1).R;
 34                         //p[7] = img.GetPixel(x, y + 1).R;
 35                         //p[8] = img.GetPixel(x + 1, y + 1).R;
 36                         ////计算中值
 37                         //for (int j = 0; j < 5; j++)
 38                         //{
 39                         //    for (int i = j + 1; i < 9; i++)
 40                         //    {
 41                         //        if (p[j] > p[i])
 42                         //        {
 43                         //            s = p[j];
 44                         //            p[j] = p[i];
 45                         //            p[i] = s;
 46                         //        }
 47                         //    }
 48                         //}
 49                         ////      if (img.GetPixel(x, y).R < dgGrayValue)
 50                         //img.SetPixel(x, y, Color.FromArgb(p[4], p[4], p[4]));    //给有效值付中值
 51                         #endregion
 52 
 53                         //上 x y+1
 54                         double upDif = GetColorDif(currentColor, img.GetPixel(x, y + 1));
 55                         //下 x y-1
 56                         double downDif = GetColorDif(currentColor, img.GetPixel(x, y - 1));
 57                         //左 x-1 y
 58                         double leftDif = GetColorDif(currentColor, img.GetPixel(x - 1, y));
 59                         //右 x+1 y
 60                         double rightDif = GetColorDif(currentColor, img.GetPixel(x + 1, y));
 61                         //左上
 62                         double upLeftDif = GetColorDif(currentColor, img.GetPixel(x - 1, y + 1));
 63                         //右上
 64                         double upRightDif = GetColorDif(currentColor, img.GetPixel(x + 1, y + 1));
 65                         //左下
 66                         double downLeftDif = GetColorDif(currentColor, img.GetPixel(x - 1, y - 1));
 67                         //右下
 68                         double downRightDif = GetColorDif(currentColor, img.GetPixel(x + 1, y - 1));
 69 
 70                         ////四面色差较大
 71                         //if (upDif > threshold && downDif > threshold && leftDif > threshold && rightDif > threshold)
 72                         //{
 73                         //    img.SetPixel(x, y, Color.White);
 74                         //}
 75                         //三面色差较大
 76                         if ((upDif > threshold && downDif > threshold && leftDif > threshold)
 77                             || (downDif > threshold && leftDif > threshold && rightDif > threshold)
 78                             || (upDif > threshold && leftDif > threshold && rightDif > threshold)
 79                             || (upDif > threshold && downDif > threshold && rightDif > threshold))
 80                         {
 81                             img.SetPixel(x, y, Color.White);
 82                         }
 83 
 84                         List<int[]> xLine = new List<int[]>();
 85                         //去横向干扰线  原理 如果这个点上下有很多白色像素则认为是干扰
 86                         for (var x1 = x + 1; x1 < x + 10; x1++)
 87                         {
 88                             if (x1 >= img.Width)
 89                             {
 90                                 break;
 91                             }
 92 
 93                             if (img.GetPixel(x1, y + 1).ToArgb() == Color.White.ToArgb()
 94                                 && img.GetPixel(x1, y - 1).ToArgb() == Color.White.ToArgb())
 95                             {
 96                                 xLine.Add(new int[] { x1, y });
 97                             }
 98                         }
 99                         if (xLine.Count() >= 4)
100                         {
101                             foreach (var xpoint in xLine)
102                             {
103                                 img.SetPixel(xpoint[0], xpoint[1], Color.White);
104                             }
105                         }
106                         
107                         //去竖向干扰线
108 
109                     }
110                 }
111             }
112             picbox.Image = img;
113             pbNormal.Image = picbox.Image;
114         }
复制代码

 

 

3、二值化代码

原理:这个简单,就是亮度大于某阀值设为白色否则设为黑色。

复制代码
 1 /// <summary>
 2         /// 二值化 遍历每个点 点的亮度小于阀值 则认为是黑色 否则是白色
 3         /// </summary>
 4         private void EZH()
 5         {
 6             if (picbox.Image != null)
 7             {
 8                 var img = new Bitmap(picbox.Image);
 9                 for (var x = 0; x < img.Width; x++)
10                 {
11                     for (var y = 0; y < img.Height; y++)
12                     {
13                         Color color = img.GetPixel(x, y);
14                         if (color.GetBrightness() < ezFZ)
15                         {
16                             img.SetPixel(x, y, Color.Black);
17                         }
18                         else
19                         {
20                             img.SetPixel(x, y, Color.White);
21                         }
22                     }
23                 }
24                 picbox.Image = img;
25                 pbNormal.Image = picbox.Image;
26             }
27         }
复制代码

 

 

4、分割、识别的代码太多,大家还是自己看源码吧,这里就说一下原理:

分割就是把每个字母都分割出来和Sample文件夹下的图片对比,找出黑色区域重叠度最高的那个就是识别的过程。那Sample文件夹下的图片 从哪来的呢?还记得上面说的学习吗?对,就是手动分割后如果字母比较正规,没有干扰点或线后就可以纳为学习字符,点击学习后它就自动保存到对应的字符目录 中作为对比图片。呵呵,有点人工智能的赶脚。

再补充一些吧,分割其实也是比较麻烦的地方,主要的困难是干扰线、噪点、 粘连影响了分割的准确度,尤其是粘连。所以代码中就把黑色像素和白色像素的边界当做分割点,当然还要上下左右检测,如果黑色像素太少,就继续移动找边界。 还有就是如果结束点离起始点距离明显是两个字符的时候就认为它是粘连,从中间分开,这个做法不是很好,但期望推荐更好的办法。

所以知道怎么识别验证码才能制作出难以识别的验证码,光靠噪点和干扰线是不行的,你看看谷歌的验证码只用了粘连和变形就击败了那些号称破解验证码90%的专业识别软件。

最后再说一下这个验证码识别全凭自己的琢磨和借鉴网上资料,没有太多复杂的算法,所以对大多程序员来说应该不算难。还是希望大家也多思考,多提供宝贵意见。

[转载]详解c#迭代器 - YamatAmain - 博客园

mikel阅读(877)

来源: [转载]详解c#迭代器 – YamatAmain – 博客园

迭代器模式是设计模式中行为模式 (behavioral pattern)的一个例子,他是一种简化对象间通讯的模式,也是一种非常容易理解和使用的模式。简单来说,迭代器模式使得你能够获取到序列中的所有元素 而不用关心是其类型是array,list,linked list或者是其他什么序列结构。这一点使得能够非常高效的构建数据处理通道(data pipeline)–即数据能够进入处理通道,进行一系列的变换,或者过滤,然后得到结果。事实上,这正是LINQ的核心模式。

在.NET中,迭代器模式被IEnumerator和IEnumerable及其对应的泛型接口所封装。如果一个类实现了IEnumerable接 口,那么就能够被迭代;调用GetEnumerator方法将返回IEnumerator接口的实现,它就是迭代器本身。迭代器类似数据库中的游标,他是 数据序列中的一个位置记录。迭代器只能向前移动,同一数据序列中可以有多个迭代器同时对数据进行操作。

C#1中已经内建了对迭代器的支持,那就是foreach语句。使得能够进行比for循环语句更直接和简单的对集合的迭代,编译器会将 foreach编译来调用GetEnumerator和MoveNext方法以及Current属性,如果对象实现了IDisposable接口,在迭代 完成之后会释放迭代器。但是在C#1中,实现一个迭代器是相对来说有点繁琐的操作。C#2使得这一工作变得大为简单,节省了实现迭代器的不少工作。

接下来,我们来看如何实现一个迭代器以及C#2对于迭代器实现的简化,然后再列举几个迭代器在现实生活中的例子。

1. C#1:手动实现迭代器的繁琐

假设我们需要实现一个基于环形缓冲的新的集合类型。我们将实现IEnumerable接口,使得用户能够很容易的利用该集合中的所有元素。我们的忽 略其他细节,将注意力仅仅集中在如何实现迭代器上。集合将值存储在数组中,集合能够设置迭代的起始点,例如,假设集合有5个元素,你能够将起始点设为2, 那么迭代输出为2,3,4,0,最后是1. 为了能够简单展示,我们提供了一个设置值和起始点的构造函数。使得我们能够以下面这种方式遍历集合:

object[] values = { "a", "b", "c", "d", "e" };
IterationSample collection = new IterationSample(values, 3);
foreach (object x in collection)
{
    Console.WriteLine(x);
}

由于我们将起始点设置为3,所以集合输出的结果是d,e,a,b及c,现在,我们来看如何实现 IterationSample 类的迭代器:

class IterationSample : IEnumerable
{
    Object[] values;
    Int32 startingPoint;
    public IterationSample(Object[] values, Int32 startingPoint)
    {
        this.values = values;
        this.startingPoint = startingPoint;
    }
    public IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

我们还没有实现GetEnumerator方法,但是如何写GetEnumerator部分的逻辑呢,第一就是要将游标的当前状态存在某一个地方。 一方面 是迭代器模式并不是一次返回所有的数据,而是客户端一次只请求一个数据。这就意味着我们要记录客户当前请求到了集合中的那一个记录。C#2编译器对于迭代 器的状态保存为我们做了很多工作。 现在来看看,要保存哪些状态以及状态存在哪个地方,设想我们试图将状态保存在IterationSample集合中,使得它实现IEnumerator和 IEnumerable方法。咋一看,看起来可能,毕竟数据在正确的地方,包括起始位置。我们的GetEnumerator方法仅仅返回this。但是这 种方法有一个很重要的问题,如果GetEnumerator方法调用多次,那么多个独立的迭代器就会返回。例如,我们可以使用两个嵌套的foreach语 句,来获取所有可能的值对。这两个迭代需要彼此独立。这意味着我们需要每次调用GetEnumerator时返回的两个迭代器对象必须保持独立。我们仍旧 可以直接在IterationSample类中通过相应函数实现。但是我们的类拥有了多个职责,这位背了单一职责原则。因此,我们来创建另外一个类来实现 迭代器本身。我们使用C#中的内部类来实现这一逻辑。代码如下:

class IterationSampleEnumerator : IEnumerator
{
    IterationSample parent;//迭代的对象  #1
    Int32 position;//当前游标的位置 #2
    internal IterationSampleEnumerator(IterationSample parent)
    {
        this.parent = parent;
        position = -1;// 数组元素下标从0开始,初始时默认当前游标设置为 -1,即在第一个元素之前, #3
    }

public bool MoveNext()
{
    if (position != parent.values.Length) //判断当前位置是否为最后一个,如果不是游标自增 #4
    {
        position++;
    }
    return position &lt; parent.values.Length;
}

public object Current
{
    get
    {
        if (position == -1 || position == parent.values.Length)//第一个之前和最后一个自后的访问非法 #5
        {
            throw new InvalidOperationException();
        }
        Int32 index = position + parent.startingPoint;//考虑自定义开始位置的情况  #6
        index = index % parent.values.Length;
        return parent.values[index];
    }
}

public void Reset()
{
    position = -1;//将游标重置为-1  #7
}

}
要实现一个简单的迭代器需要手动写这么多的代码

  • 需要记录迭代的原始集合#1
  • 记录当前游标位置#2
  • 返回元素时,根据 当前游标和数组定义的起始位置设置定迭代器在数组中的位置#6
  • 初始化时,将当前位置设定在第一个元素之前#3
  • 当第一次调用迭代器时首先需要调用 MoveNext,然后再调用Current属性。在游标自增时对当前位置进行条件判断#4
  • 使得即使当第一次调用MoveNext时没有可返回的元素也 不至于出错#5
  • 重置迭代器时,我们将当前游标的位置还原到第一个元素之前#7

除了结合当前游标位置和自定义的起始位置返回正确的值这点容易出错外,上面的代码非常直观。现在,只需要在IterationSample类的GetEnumerator方法中返回我们当才编写的迭代类即可:

public IEnumerator GetEnumerator()
{
    return new IterationSampleEnumerator(this);
}

2. C#2:通过yield语句简化迭代

C#2使得迭代变得更加简单–减少了很多代码量也使得代码更加的优雅。下面的代码展示了再C#2中实现GetEnumerator方法的完整代码:

public IEnumerator GetEnumerator()
{
    for (int index = 0; index &lt; this.values.Length; index++)
    {
        yield return values[(index + startingPoint) % values.Length];
    }
}

简单几行代码就能够完全实现IterationSampleIterator类所需要的功能。方法看起来很普通,除了使用了yield return。这条语句告诉编译器这不是一个普通的方法,而是一个需要执行的迭代块(yield block),他返回一个IEnumerator对象,你能够使用迭代块来执行迭代方法并返回一个IEnumerable需要实现的类 型,IEnumerator或者对应的泛型。如果实现的是非泛型版本的接口,迭代块返的yield type是Object类型,否则返回的是相应的泛型类型。例如,如果方法实现IEnumerable接口,那么yield返回的类型就 是String类型。 在迭代块中除了yield return外,不允许出现普通的return语句。块中的所有yield return 语句必须返回和块的最后返回类型兼容的类型。举个例子,如果方法定义需要返回IEnumeratble类型的话,不能yield return 1 。 需要强调的一点是,对于迭代块,虽然我们写的方法看起来像是在顺序执行,实际上我们是让编译器来为我们创建了一个状态机。这就是在C#1中我们书写的那部 分代码—调用者每次调用只需要返回一个值,因此我们需要记住最后一次返回值时,在集合中位置。 当编译器遇到迭代块是,它创建了一个实现了状态机的内部类。这个类记住了我们迭代器的准确当前位置以及本地变量,包括参数。这个类有点类似与我们之前手写 的那段代码,他将所有需要记录的状态保存为实例变量。下面来看看,为了实现一个迭代器,这个状态机需要按顺序执行的操作:

它需要一些初始的状态;

  • 它需要一些初始的状态;
  • 当MoveNext被调用时,他需要执行GetEnumerator方法中的代码来准备下一个待返回的数据;
  • 当调用Current属性是,需要返回yielded的值;
  • 需要知道什么时候迭代结束是,MoveNext会返回false。

2.2 迭代器的执行流程

如下的代码,展示了迭代器的执行流程,代码输出(0,1,2,-1)然后终止。

class Program {

  static readonly String Padding = new String(' ', 30);
  static IEnumerable<int32> CreateEnumerable()
  {
      Console.WriteLine("{0} CreateEnumerable()方法开始", Padding);
      for (int i = 0; i &lt; 3; i++)
      {
          Console.WriteLine("{0}开始 yield {1}", i);
          yield return i;
          Console.WriteLine("{0}yield 结束", Padding);
      }
      Console.WriteLine("{0} Yielding最后一个值", Padding);
      yield return -1;
      Console.WriteLine("{0} CreateEnumerable()方法结束", Padding);
  }

  static void Main(string[] args)
  {
      IEnumerable<int32> iterable = CreateEnumerable();
      IEnumerator<int32> iterator = iterable.GetEnumerator();
      Console.WriteLine("开始迭代");
      while (true)
      {
          Console.WriteLine("调用MoveNext方法……");
          Boolean result = iterator.MoveNext();
          Console.WriteLine("MoveNext方法返回的{0}", result);
          if (!result)
          {
              break;
          }
          Console.WriteLine("获取当前值……");
          Console.WriteLine("获取到的当前值为{0}", iterator.Current);
      }
      Console.ReadKey();
  }
}

从输出结果中可以看出一下几点:

  • 直到第一次调用MoveNext,CreateEnumerable中的方法才被调用。
  • 在调用MoveNext的时候,已经做好了所有操作,返回Current属性并没有执行任何代码。
  • 代码在yield return之后就停止执行,等待下一次调用MoveNext方法的时候继续执行。
  • 在方法中可以有多个yield return语句。
  • 在最后一个yield return执行完成后,代码并没有终止。调用MoveNext返回false使得方法结束。
    第一点尤为重要:这意味着,不能在迭代块中写任何在方法调用时需要立即执行的代码–比如说参数验证。如果将参数验证放在迭代块中,那么他将不能够很好的 起作用,这是经常会导致的错误的地方,而且这种错误不容易发现。下面来看如何停止迭代,以及finally语句块的特殊执行方式。

2.3 迭代器的特殊执行流程

在普通的方法中,return语句通常有两种作用,一是返回调用者执行的结果。二是终止方法的执行,在终止之前执行finally语句中的方法。在 上面的例子中,我们看到了yield return语句只是短暂的退出了方法,在MoveNext再次调用的时候继续执行。在这里我们没有写finally语句块。如何真正的退出方法,退出方 法时finnally语句块如何执行,下面来看看一个比较简单的结构:yield break语句块。 使用 yield break 结束一个迭代

static IEnumerable<int32> CountWithTimeLimit(DateTime limit)
{
    try
    {
        for (int i = 1; i &lt;= 100; i++)
        {
            if (DateTime.Now >= limit)
            {
                yield break;
            }
            yield return i;
        }
    }
    finally
    {
        Console.WriteLine("停止迭代!"); Console.ReadKey();
    }
}
static void Main(string[] args)
{
    DateTime stop = DateTime.Now.AddSeconds(2);
    foreach (Int32 i in CountWithTimeLimit(stop))
    {
        Console.WriteLine("返回 {0}", i);
        Thread.Sleep(300);
    }
}

打个广告,ASP.NET 高级技术群 89336052

鸟文名:YamatAmain
地 址:http://www.cnblogs.com/YamatAmain/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。