自己来实现一个简易的OCR
来做个简易的字符识别 ,既然是简易的 那么我们就不能用任何的第三方库 。啥谷歌的 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的更多相关文章
- .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”
FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为我们构建了一个物理文件系统和程序集内嵌文 ...
- 基于 getter 和 setter 撸一个简易的MVVM
Angular 和 Vue 在对Angular的学习中,了解到AngularJS 的两个主要缺点: 对于每一次界面时间,Ajax 或者 timeout,都会进行一个脏检查,而每一次脏检查又会在内部循环 ...
- 探秘Tomcat——一个简易的Servlet容器
即便再简陋的服务器也是服务器,今天就来循着书本的第二章来看看如何实现一个servlet容器. 背景知识 既然说到servlet容器这个名词,我们首先要了解它到底是什么. servlet 相比你或多或少 ...
- 使用Windows Form 制作一个简易资源管理器
自制一个简易资源管理器----TreeView控件 第一步.新建project,进行基本设置:(Set as StartUp Project:View/Toolbox/TreeView) 第二步.开始 ...
- [后端人员耍前端系列]AngularJs篇:使用AngularJs打造一个简易权限系统
一.引言 上一篇博文已经向大家介绍了AngularJS核心的一些知识点,在这篇博文将介绍如何把AngularJs应用到实际项目中.本篇博文将使用AngularJS来打造一个简易的权限管理系统.下面不多 ...
- ENode 2.0 - 第一个真实案例剖析-一个简易论坛(Forum)
前言 经过不断的坚持和努力,ENode 2.0的第一个真实案例终于出来了.这个案例是一个简易的论坛,开发这个论坛的初衷是为了验证用ENode框架来开发一个真实项目的可行性.目前这个论坛在UI上是使用了 ...
- 使用MVVM框架avalon.js实现一个简易日历
最近在做公司内部的运营管理系统,因为与日历密切相关,同时无需触发条件直接显示在页面上,所以针对这样的功能场景,我就用avalon快速实现了一个简易日历,毕竟也是第一次造日历这种轮子,所以这里记录下我当 ...
- 做了一个简易的git 代码自动部署脚本
做了一个简易的git 代码自动部署脚本 http://my.oschina.net/caomenglong/blog/472665 发表于2个月前(2015-06-30 21:08) 阅读(200 ...
- C 基于UDP实现一个简易的聊天室
引言 本文是围绕Linux udp api 构建一个简易的多人聊天室.重点看思路,帮助我们加深 对udp开发中一些api了解.相对而言udp socket开发相比tcp socket开发注意的细节要少 ...
随机推荐
- Unity 序列化
Script Serialization http://docs.unity3d.com/Manual/script-Serialization.html 自定义序列化及例子: http://docs ...
- 【初学python】使用python连接mysql数据查询结果并显示
因为测试工作经常需要与后台数据库进行数据比较和统计,所以采用python编写连接数据库脚本方便测试,提高工作效率,脚本如下(python连接mysql需要引入第三方库MySQLdb,百度下载安装) # ...
- 使用 JavaScript 和 canvas 做精确的像素碰撞检测
原文地址:Pixel accurate collision detection with Javascript and Canvas 译者:nzbin 我正在开发一个需要再次使用碰撞检测的游戏.我通常 ...
- org.jboss.deployment.DeploymentException: Trying to install an already registered mbean: jboss.jca:service=LocalTxCM,name=egmasDS
17:34:37,235 INFO [Http11Protocol] Starting Coyote HTTP/1.1 on http-0.0.0.0-8080 17:34:37,281 INFO [ ...
- 使用Git Bash远程添加分支和简单部署你的静态页面
新建一个分支:git branch mybranch(mybranch你的分支名字) 切换到你的新分支: git checkout mybranch 将新分支发布在github上: git push ...
- SharePonit 2010 更改另存为列表模板的语言类型
从朋友处得来一个列表模板:AccessApplicationSharePoint.stp 将其通过:网站操作----网站设置----列表模板,上传进去.然后去创建列表,发现找不到此模板. 根据多年老司 ...
- BPM配置故事之案例12-触发另外流程
还记得阿海么,对就是之前的那个采购员,他又有了些意见. 阿海:小明,你看现在的流程让大家都这么方便,能不能帮个忙让我也轻松点啊-- 小明:--你有什么麻烦,现在不是已经各个部门自己提交申请了嘛? 阿海 ...
- Windows下Redis缓存服务器的使用 .NET StackExchange.Redis Redis Desktop Manager
Redis缓存服务器是一款key/value数据库,读110000次/s,写81000次/s,因为是内存操作所以速度飞快,常见用法是存用户token.短信验证码等 官网显示Redis本身并没有Wind ...
- 转: 解决Github访问超慢问题
转自:http://zengrong.net/post/2092.htm 解决Github访问超慢问题 Github is so slowly. 这段时间访问 github 都非常慢,google了一 ...
- nuget常用命令
nuget命令的用法: 一.安装 1.安装指定版本类库install-package <程序包名> -version <版本号> 2.安装到指定的项目install-packa ...