来做个简易的字符识别 ,既然是简易的 那么我们就不能用任何的第三方库 。啥谷歌的 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 点安装即可。 然后就可以进行识别了 。

运行结果:

最新文章

  1. EditText 基本用法
  2. 浅谈WebService的版本兼容性设计
  3. iOS 开发技术牛人博客
  4. 容器--HashMap
  5. tar等
  6. SSH整合(1)异常
  7. cocos进阶教程(3)Cocos2d-x多场景切换生命周期
  8. HTML 中 META的作用
  9. keynotes egestas,PPT 渐变背景下载-imsoft.cnblogs
  10. OAuth 2 的简单理解
  11. Spark菜鸟学习营Day4 单元测试程序的编写
  12. FMDB 的基本操作
  13. Java - extends
  14. $(window).scroll在页面没有滚动条时无法触发事件的bug解决方法
  15. 使用自建Git服务器管理私有项目 Centos 7.3 + Git 2.11.0 + gitosis (实测 笔记)
  16. 报文分析3、ICMP协议的头结构
  17. inception_v2版本《Rethinking the Inception Architecture for Computer Vision》(转载)
  18. 带通滤波 matlab
  19. css3渐变特性
  20. Lua 可变参数之arg与select

热门文章

  1. webp图片实践之路
  2. 使用 JavaScriptService 在.NET Core 里实现DES加密算法
  3. Jquery 搭配 css 使用,简单有效
  4. Bootstrap 模态框(Modal)插件
  5. Android-armebi-v7a、arm64-v8a、armebi的坑
  6. ASP.NET Core应用针对静态文件请求的处理[5]: DefaultFilesMiddleware中间件如何显示默认页面
  7. Mysql存储引擎比较
  8. obj.style.z-index的正确写法
  9. 在知乎上看到 Web Socket这篇文章讲得确实挺好,从头看到尾都非常形象生动,一口气看完,没有半点模糊,非常不错
  10. 微信小程序前端源码逻辑和工作流