来做个简易的字符识别 ,既然是简易的 那么我们就不能用任何的第三方库 。啥谷歌的 tesseract-ocr, opencv 之类的 那些玩意是叼 至少图像处理 机器视觉这类课题对我这种高中没毕业的人来说是一座高山 对于大多数程序员都应该算难度不小吧。 但是我们这里 这么简陋的功能 还用那些玩意 作为一个程序员的自我修养 你还玩个球。管他代码写得咋个low 效率咋个低 被高手嗤之以鼻也好 其实那些高手也就那样 把你的代码走起来  ,这是一件很好玩的事情。 以前一直觉着这玩意挺神奇 什么OCR optical character Recognition  高大上,这三个单词一直记不住 。好了正题:

二值化和对象分割

拿到图像 首先二值化 就是用一种无脑的方式把浅色的背景去掉变成纯白色,书上都是说二值化 这样说感觉是要叼一些 专业一些 那么我也这样说了。图像上的像素数据都是一堆无意义的离散的数据。那么第一步就是要把这些离散的像素数据组织成有逻辑的 数据 也就是对象分割了,一块整的图片 把他分割成一个个的字符 小图片。 网上看到别人用投影直方图的方式 这样做可以很容易 分割一行排的字符。 但是我原来还想做一个简易的“数细胞”的算法  干脆就一并实现了吧 正好这里也可以用得上 ,数细胞明白否 就是一副白纸上 一坨 一坨的 每一坨的形状都不一样 我们要用程序判断它总共有多少坨 只要是连在一起 哪怕是一根细线连着的 都算一坨。 当然也可以分割开 涉及到形态学 啥的 这里面太深奥了 暂时我还没准备深入研究 。  基于他的原理你们也知道了 不能判断小写字母i 这样的 因为一点加一竖 的方式 。这也是为啥那些成熟的OCR软件里都容易把扫描文本里比较粗糙有毛边的i 识别成 1 加 ' 。 好 我们就用这种方式 只是为了演示原理 我们这里也只准备进行数字识别, 正好数字0~9 每一个字符也都是连着的。

我们还是用我原来的巡路用过的算法 扩散大法 ,书面叫广度搜索 本来在原来是用来进行路径联通测试的,说明这玩意的用处还挺多的 威力无穷啊。 就这样随便从黑坨里取一个像素 作为种子  就像一滴水一样 让他去扩散 污染整个池塘。什么时候返回 也很简单 当触角不能再延伸了 自然就返回了。 污染后把整个池塘删除 放到 逻辑数据集里去  ,然后又从所有黑色像素里取一个种子像素 如此往复就把这一堆离散的像素点变得有意义了,我们一个个的字符也分割出来了 并且还有个好处 单个字符的每个像素点我们都知晓 进而可以计算字符的像素面积 ,这就可以把小的噪点过滤掉 然后 还可以定位每个字符的位置 宽 高。上面的做法效率是很低的 尤其字符面积过大 ,其实正统的做法应该是使用边缘查找,边缘查找的原理: 假设从上下左右有四堵墙往中间推 把遇到的所有第一个黑色像素 确定为边缘。 然后找一个像素 八方向查找 依次连城一个路径 直到找到起始点 则连成一个完整的闭塞区域,当然这个东西也不是那么简单的 比如遇到238这样的 ,任何东西运行都要有严密而行得通的理论支持。

对象分割的部分核心代码:

 public Bitmap objSegmentation()
{
if (stu > Status.readyToTransform)
return sourceImg;
else if (stu == Status.waitSourceImg)
return null; if (sourceImg == null)
return null; bool Over = false;
while (Over == false)
{
//取得一个种子像素
node pxs = null;
foreach (var item in blackPixs)
{
if (item.accessed == false)
{
pxs = item;
break;
}
} //根据种子像素找出被污染的区域 并把对应的位置设置为已访问
//设置第一个节点
startPoint = new Point(pxs.x, pxs.y);
zouguo = new Dictionary<int, List<node>>();
int qibu = ;
List<node> stepOne = new List<node>();
stepOne.Add(new node() { parent = startPoint, current = startPoint });
zouguo.Add(qibu, stepOne);
qibu++; //进行广度搜索 直到搜索完一片区域为止
bool isgogogo = false;
do
{
isgogogo = besideOf(qibu - );
qibu++;
//if (qibu > 10)
// break;
} while (isgogogo); //遍历当前被腐蚀的那一片区域
//并把所有节点添加到一个线性数组里去 int top = height - ;
int bottom = ;
int left = cols - ;
int right = ; RegionOfObj bedestory = new RegionOfObj();
bedestory.pixs = new List<Point>();
foreach (var item in zouguo.Values)
{
foreach (var item2 in item)
{
bedestory.pixs.Add(item2.current);
//找出黑色像素里已经被腐蚀过的 把标示设置为已访问
for (int i = ; i < blackPixs.Count; i++)
{
if (item2.current.X == blackPixs[i].x && item2.current.Y == blackPixs[i].y)
{
blackPixs[i].accessed = true;
if (blackPixs[i].x > right)
right = blackPixs[i].x;
if (blackPixs[i].x < left)
left = blackPixs[i].x;
if (blackPixs[i].y < top)
top = blackPixs[i].y;
if (blackPixs[i].y > bottom)
bottom = blackPixs[i].y;
}
}
}
} Rectangle rec = new Rectangle(left, top, right - left + , bottom - top + ); bedestory.rect = rec;
//往最终呈现数据里加入结果
groupedObj.Add(bedestory); //直到黑色像素所有的区域都被访问 就退出
Over = true;
foreach (var item in blackPixs)
{
if (item.accessed == false)
{
Over = false;
break;
}
}
//break;
} stu = Status.readyToRecognition;
return sourceImg;
}

模板匹配

然后就是进行识别了 网上随便一找 都知道是用 模板匹配的方式,翻了两本书 也都是说的用这种方式。要说的话这确实没啥技术含量 挺简单的,就是简单的像素比对 差异化的像素占总像素比过大则认为不匹配 。 我们也不是无脑的拿固定大小的模板图片去比对 既然我们字符都分割定位了 宽高都知道,首先 我们的模板字符是比较大 比较清晰的 然后缩放到分割字符的大小 然后才进行像素比对。

模板匹配部分核心代码:

 public string recognition()
{
if (stu == Status.waitSourceImg)
return "";
else if (stu > Status.readyToRecognition)
return recognition_result;
else if (stu == Status.readyToTransform)
objSegmentation(); //如果没有模板文件 则生成他
if (File.Exists("0.png") == false || File.Exists("1.png") == false || File.Exists("2.png") == false ||
File.Exists("3.png") == false || File.Exists("4.png") == false || File.Exists("5.png") == false ||
File.Exists("6.png") == false || File.Exists("7.png") == false || File.Exists("8.png") == false ||
File.Exists("9.png") == false)
createTempleFile(); //载入模板
Image[] templateImg = new Image[]{
Image.FromFile("0.png"),Image.FromFile("1.png"),Image.FromFile("2.png"),Image.FromFile("3.png"),Image.FromFile("4.png"),
Image.FromFile("5.png"),Image.FromFile("6.png"),Image.FromFile("7.png"),Image.FromFile("8.png"),Image.FromFile("9.png")}; GraphicsUnit uu = GraphicsUnit.Pixel;
string result = "";
for (int i = ; i < groupedObj.Count; i++)//遍历所有对象
{
float mach = 0.000f;
string chr_tmp = " ";
for (int j = ; j < templateImg.Length; j++)//0-9每个字符进行比对
{
//处理等比例缩放 算了也不用等比例了。
Bitmap scaleImg = new Bitmap(groupedObj[i].rect.Width, groupedObj[i].rect.Height);
Graphics gph = Graphics.FromImage(scaleImg);
gph.Clear(Color.White);
gph.DrawImage(templateImg[j], scaleImg.GetBounds(ref uu), templateImg[j].GetBounds(ref uu), GraphicsUnit.Pixel); float mach_tmp = ;
for (int k = ; k < scaleImg.Height; k++)
{
for (int l = ; l < scaleImg.Width; l++)
{
Color tmp_cor = scaleImg.GetPixel(l, k);
Color trg_cor = sourceImg.GetPixel(groupedObj[i].rect.Location.X + l, groupedObj[i].rect.Location.Y + k);
if (tmp_cor.R == trg_cor.R && tmp_cor.G == trg_cor.G && tmp_cor.B == trg_cor.B)//如果像素匹配上
mach_tmp += ;
}
}
if ((mach_tmp / (float)(groupedObj[i].rect.Width * groupedObj[i].rect.Height)) > mach)
{
mach = (mach_tmp / (float)(groupedObj[i].rect.Width * groupedObj[i].rect.Height));
chr_tmp = j.ToString();
}
}
if (mach < 0.6f)
result += "?";
else
result += chr_tmp;
}
recognition_result = result;
stu = Status.complete;
return result;
}

本来准备把模板跟目标区域进行等比例缩放的,后来仔细一想算了这不是多事吗 并且这样还有一个好处 ,就是高度进行压缩了的字符也可以识别出来。 搞完了 看得出来 我们这个只算是最初级最初级的 只能够去识别那种解放前水平的验证码。现在的验证码也不是那么好识别的 做验证码的人只要大概了解识别原理 都可以给识别的人制造成倍的难度 ,对于现在的有些验证码 即使是高手 做自动识别都不是那么容易的。

不要问我这可不可以用来识别身份证号 之类的 。我可以负责的告诉你 肯定是可以的 。身份证号识别那个本身难度就是比较低的。 首先身份证号 的位置 在整个身份证版面中 都是固定的 把那一块截取出来 进行处理就可以了  ,然后 身份证号所使用的字体叫 "OCR-B 10 BT" 我也不知道啥意思 意思是专利于进行OCR识别的字体?OCR-B: An isO recognized machine-readable typeface that is designed to be more legible to humans than OCR-A 这种字体电脑上是没有的 需要进行安装下 打开OCR-B 10 BT.ttf 点安装即可。 然后就可以进行识别了 。

运行结果:

自己来实现一个简易的OCR的更多相关文章

  1. .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”

    FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为我们构建了一个物理文件系统和程序集内嵌文 ...

  2. 基于 getter 和 setter 撸一个简易的MVVM

    Angular 和 Vue 在对Angular的学习中,了解到AngularJS 的两个主要缺点: 对于每一次界面时间,Ajax 或者 timeout,都会进行一个脏检查,而每一次脏检查又会在内部循环 ...

  3. 探秘Tomcat——一个简易的Servlet容器

    即便再简陋的服务器也是服务器,今天就来循着书本的第二章来看看如何实现一个servlet容器. 背景知识 既然说到servlet容器这个名词,我们首先要了解它到底是什么. servlet 相比你或多或少 ...

  4. 使用Windows Form 制作一个简易资源管理器

    自制一个简易资源管理器----TreeView控件 第一步.新建project,进行基本设置:(Set as StartUp Project:View/Toolbox/TreeView) 第二步.开始 ...

  5. [后端人员耍前端系列]AngularJs篇:使用AngularJs打造一个简易权限系统

    一.引言 上一篇博文已经向大家介绍了AngularJS核心的一些知识点,在这篇博文将介绍如何把AngularJs应用到实际项目中.本篇博文将使用AngularJS来打造一个简易的权限管理系统.下面不多 ...

  6. ENode 2.0 - 第一个真实案例剖析-一个简易论坛(Forum)

    前言 经过不断的坚持和努力,ENode 2.0的第一个真实案例终于出来了.这个案例是一个简易的论坛,开发这个论坛的初衷是为了验证用ENode框架来开发一个真实项目的可行性.目前这个论坛在UI上是使用了 ...

  7. 使用MVVM框架avalon.js实现一个简易日历

    最近在做公司内部的运营管理系统,因为与日历密切相关,同时无需触发条件直接显示在页面上,所以针对这样的功能场景,我就用avalon快速实现了一个简易日历,毕竟也是第一次造日历这种轮子,所以这里记录下我当 ...

  8. 做了一个简易的git 代码自动部署脚本

    做了一个简易的git 代码自动部署脚本 http://my.oschina.net/caomenglong/blog/472665 发表于2个月前(2015-06-30 21:08)   阅读(200 ...

  9. C 基于UDP实现一个简易的聊天室

    引言 本文是围绕Linux udp api 构建一个简易的多人聊天室.重点看思路,帮助我们加深 对udp开发中一些api了解.相对而言udp socket开发相比tcp socket开发注意的细节要少 ...

随机推荐

  1. Dapper.Contrib:GetAsync<T> only supports an entity with a [Key] or an [ExplicitKey] property

    异常处理:http://www.cnblogs.com/dunitian/p/4523006.html#dapper 原来Model是这样滴 修改后是这样滴 注意点:Model里面的Table和Key ...

  2. Node.js:Buffer浅谈

    Javascript在客户端对于unicode编码的数据操作支持非常友好,但是对二进制数据的处理就不尽人意.Node.js为了能够处理二进制数据或非unicode编码的数据,便设计了Buffer类,该 ...

  3. 用scikit-learn学习谱聚类

    在谱聚类(spectral clustering)原理总结中,我们对谱聚类的原理做了总结.这里我们就对scikit-learn中谱聚类的使用做一个总结. 1. scikit-learn谱聚类概述 在s ...

  4. Hawk 7. 常见问题

    本页面您可以通过关键字搜索来获取信息. 理性使用爬虫 爬虫是一种灰色的应用,虽然作为Hawk的设计者,但我依然不得不这么说. 各大网站都在收集和整理数据上花费了大量的精力,因此抓取的数据应当仅仅作为科 ...

  5. const extern static 终极指南

    const extern static 终极指南 不管是从事哪种语言的开发工作,const extern static 这三个关键字的用法和原理都是我们必须明白的.本文将对此做出非常详细的讲解. co ...

  6. Spring cache简单使用guava cache

    Spring cache简单使用 前言 spring有一套和各种缓存的集成方式.类似于sl4j,你可以选择log框架实现,也一样可以实现缓存实现,比如ehcache,guava cache. [TOC ...

  7. 设计模式C#合集--抽象工厂模式

    抽象工厂,名字就告诉你是抽象的了.上代码. public interface BMW { public void Drive(); } public class BMW730 : BMW { publ ...

  8. Android—关于自定义对话框的工具类

    开发中有很多地方会用到自定义对话框,为了避免不必要的城府代码,在此总结出一个工具类. 弹出对话框的地方很多,但是都大同小异,不同无非就是提示内容或者图片不同,下面这个类是将提示内容和图片放到了自定义函 ...

  9. centos安装nodejs

    1.下载安装nodejs wget http://nodejs.org/dist/v0.10.25/node-v0.10.25.tar.gz compat--c++ tar -xf node-v0.1 ...

  10. FineReport:任意时刻只允许在一个客户端登陆账号的插件

    在使用FineReport报表系统中,处于账户安全考虑,有些企业希望同一账号在任意时刻智能在统一客户端登录.那么当A用户在C1客户端登陆后,该账号又在另外一个C2客户端登陆,服务器如何取判断呢? 开发 ...