拍照怎么搜题?(下)

 

那些拍照就能搜题的App是怎么实现的呢?老王带你一起去看看~...



/*

* 上一篇,我们讲到了如何对图像进行处理

* 接下来,我们聊聊如何对图像进行识别

* 没看过上一篇的同学,请微信关注:simplemain

*/

==== 图像的识别 ====
我们已经拿到一个个的字符的图案了,现在要做的,就是如何把这些图案转化成计算机可认知的二进制编码。因为这次只涉及到英文和标点,所以实际要做的,就是将图案转化为asc码。

识别的方法有非常多,接下来我给大家介绍一下我用的几种方法。为了使得识别率提升,一般还会有其他更好或者更有效的方法,如果这里没讲述到,请大家谅解~

对于每一个待识别的字符图案,都有他自己的特征(比如:i长的很瘦,O长的很圆,T横竖都有等等),识别的时候,就可以借助他们的特征,从而抽丝剥茧的把他们认出来。

问:那具体怎么做呢?

答:我们将多种字体的所有英文和标点生成标准图案,再将待识别的图案和他们进行比对,看这些待匹配的图案和哪个标准图案最像,那就能找出最有可能的asc码。

那就开始吧!

步骤一:生成模板图案

我们用多种常见字体,将所有英文和标点生成标准图案。这里简称模板图案。为什么要多种字体呢?因为对于同一个字符,在不同字体下,可能长的完全不一样,比如同样是gal,不同字体效果如下:



拿到这些标准模板图案以后,我们就有了如下图的这些位图:



但是有了这些位图还不够,我们还要做一些其他工作,来保证后面的匹配工作能正常进行。接下来我们就看看还要做什么工作。

步骤二:归一化 + 记录meta信息

我们将模板图案先去掉上下左右空白的背景,让前景图案顶格。接着再统一宽高,将图案压缩或拉伸为p*q大小的图案(比如都压缩或者拉伸到50像素*50像素),这样我们才能很好比对。说白了,就是大家都用统一的标准,否则你穿XL号的衣服,我穿S号的,我们的衣服就根本没有可比性。

好了,有了统一的比对标准,是不是一切就都好了呢?

大家一看这种问题,基本就可以条件反射的判定:回答是否定的(就跟读书那会儿做判断题一样^_^)。那问题出在哪儿呢?

有些图案做了归一化以后,就失真了,比如逗号[,]和引号[’],去掉周围的白边,最后就都长成如下图像了:



还有一些字符就会长的很像了,比如大写的Z和小写的z,归一化以后,就变成如下这样了,人看都恼火,让计算机怎么来判断,对吧。



所以,除了归一化,我们还要记录图案本身的信息,比如原始的长宽,原始的位图等等,这些信息在匹配的时候,可以提供额外的信息,来帮助算法判断有效性。

好了,做完上面两步,我们就得到了标准图案的归一化位图和meta信息。同时,我们对上一篇文章中做完垂直切割的待匹配图案也做归一化和meta信息记录的工作。有了这两样东西,我们就可以开展下面的匹配工作了。

步骤三:模板图案和待匹配图案的匹配

接下来,就用我们的匹配算法,对待匹配的图案和标准图案进行匹配了。图像的识别算法有很多很多,根据不同的应用场景,会有很多不同的选择,包括直接匹配的,也有通过数据挖掘来识别。每一种方法没有绝对的好坏,只有看是否更适合。

我曾经看过一篇文章,是讲验证码识别的。验证码为了防止机器识别,会对字符进行旋转、扭曲、干扰等处理。为了识别这些变态的字符,那篇文章列举了多达10几种的识别算法,当时看得我不断的惊叹:我擦……。如果以后有时间,我尝试看看能不能把那篇文章找出来分享给大家。

我们这里因为只涉及到英文和标点,所以用了以下几种方法:

1、字符像素匹配

2、投影区块匹配

3、九宫格匹配

4、重心匹配

5、宽高比匹配

每一个匹配算法都有自身的优缺点(稍后会讲到),因此他们都能得出一个谁跟谁最像,待匹配图案是谁的可能性最大的判断。虽然这个判断绝大多数情况下是对的,但是也有bad case的情况。所以,我们要综合来看所有的判断,将每一个算法的匹配值进行加权求和,得出最后的一个相似度的值,这个相似度有可能就是最终的一个结果。不过这个结果可能也是有错误的(bad case),所以如果继续往后做的时候,我们要不断去给我们的评价判断系统以反馈,丰富我们识别的样本,同时对识别算法和加权参数进行修正(这个会比较花时间,我这次就没做了^_^)

通过上面的算法,我们就将待匹配的图案和每一个标准图案进行算法比对的相似度值求出来,做个排序,取相似度最大的那个,基本就得到我们最终想要的那个。

后来出于好奇,我专门问了作业帮的朋友,想看看他们怎么做的。大体方法类似,同时他们还用了其他的一些手段,比如:卷积神经网络(CNN)等。后面有时间了,我再专门去研究研究。

好了,大体上的步骤,老王是不是都讲清楚了呢?接下来,就是具体讲讲这几个匹配算法,做完这几个算法,我们的识别工作就基本告一段落。来吧,跟着老王一起往下看。

方法一:字符像素匹配

这是最直观也最容易想到的方法。我们将待匹配图案和标准的每一个图案进行一个像素一个像素的比对,如果位图都是0或者都是1,则计数加一,最终得到计数数值k。用k除以像素个数w * h就得到了一个相似值 d = k / (w * h)。如果d越大,则相似度越高(为了描述起来容易些,后面我都用一些简单的位图来做示意):

模板图案:



待匹配图案:



我们的模板图案和待匹配图案都被归一化到10*10的尺寸,他们有99个点是重合的,有一个点不重合:(0,0)那个点。这样的话,他们的相似度就是d = 99 / 100 = 99%。

理想情况下,如果所有点都匹配的话,他们的就会有100点重合,那d = 100 / 100 = 100%。另外一个极端,就是一个点都不重合,那么d = 0 / 100 = 0%,对吧。所以,我们这个算法的值域就是[0,1]。对于99%的相似度,我们认为已经很高了,所以他俩匹配的可能性就非常高。

如此这样,我们将待匹配的图案和模板图案一一计算,得到d1 d2 ... dn这样一个结果序列,降序排列的第一个,就是这个算法认为最有可能的图案。

这个算法直观,实现简单,不过这个算法有一个问题:就是待匹配图案如果稍微有一些偏差或者干扰,对算法本身的影响就会非常大。比如,我们在图像处理的时候,某一行里多了一个干扰点,就有可能造成这一行的匹配度下降。如下图:



我们在图像处理的时候,最后一行多了一个噪点,然后做图像归一化以后,整个图像的最右边就往左边挤占了一个像素,这样和模板图案进行算法对比的时候,匹配值就变成了90,整个匹配度就变成d = 90 / 100 = 90%。

因此,这个算法对图像准确度要求很高,抗干扰能力弱。不过,在图像处理比较好的绝大多数情况下,他的匹配度还是不错的。

方法二:投影区块匹配

上一种方法就是对图像的信息太过敏感,稍微一点干扰就会造成影响。所以,我们有一个新的方法,将信息做汇聚,然后再比较信息的相似性。

我们将图像在x轴上做投影,累加位图为1的值。然后再将x轴等分成n份(比如n=5),分别将这等分的n份里所有的值累加,得到k1 k2 ... kn,然后将标准图案和待匹配图案的这n个k值进行求差。差值越小,则相似度越高。同理,在y轴也做同样的投影和求差值。

我们还是对这个10 * 10的位图进行分析,将这个位图分别往x、y轴上投影。将位图中为1的累加,因此得到在x轴上的10个数字和y轴上的10个数字,如下:

模板图案:



待匹配图案1:



待匹配图案2:



然后我们把x轴和y轴上的数字,分别分成连续的5组(每两个一组),组内的数字累加:

模板图案:

x轴:(8 8) (66) (6 6) (6 6) (8 8) -> 16 12 12 12 16

y轴:(10 10) (22) (10 10) (2 2) (10 10) -> 20 4 20 4 20

我们将上面的值命名为:

x0[1] x0[2] ... x0[5] = 16 12 12 12 16

y0[1] y0[2] ... y0[5] = 20 4 20 4 20

待匹配图案1:

x轴:(7 8) (66) (6 6) (6 6) (8 8) -> 15 12 12 12 16

y轴:(9 10) (22) (10 10) (2 2) (10 10) -> 19 4 20 4 20

我们将上面的值命名为:

x1[1] x1[2] ... x1[5] = 15 12 12 12 16

y1[1] y1[2] ... y1[5] = 19 4 20 4 20

待匹配图案2:

x轴:(7 8) (66) (6 6) (6 8) (8 1) -> 15 12 12 14 9

y轴:(8 9) (22) (9 9) (2 2) (9 10) -> 17 4 18 4 19

x2[1] x2[2] ... x2[5] = 15 12 12 14 9

y2[1] y2[2] ... y2[5] = 17 4 18 4 19

然后,我们将对应的值做以下操作:

待匹配图案1和标准图案对比:

a、dx1 = Σ((x1 - x0) / 20) ^ 2 = 0.0025

b、dy1 = Σ((y1 - y0) / 20) ^ 2 = 0.0025

c、d1  = 1 - (dx1 + dy1) / 2 = 99.75%

(其中i=1到5, 20是每一组点的个数)

待匹配图案2和标准图案对比:

a、dx2 = Σ((x2 - x0) / 20) ^ 2 = 0.1325

b、dy2 = Σ((y2 - y0) / 20) ^ 2 = 0.035

c、d2  = 1 - (dx2 + dy2) / 2 = 91.6%

从绝对差值来看,这种方案要比前一种对于图像的敏感度要小,特别是如果每组的个数更多的时候,这种敏感度就越弱。

这个算法主要是在做图像有效信息分布的比对,信息相对要粗放一些,抗干扰度要好一些。不过但是带来的问题,就是信息是统计信息,比较粗,有些图案比对会不准确,比如:a和o等

方法三:九宫格匹配

我们将图案划分成3*3的格子,然后在每个格子里统计位图值为1的个数,得到k1 k2 ... k9一共9个值。然后再将标准图案和待匹配图案对k值求差,差值越小,则相似度越高。

这个算法类似上一种,不同的是,他将x轴和y轴的信息做了汇聚。鉴于计算方法类似,我们这里就不再赘述。

方法四:重心匹配

还记得我们之前说的一个case吗?逗号[,]和引号[’]其实除了位置不一样以外,其实长的非常像。我最先做匹配的时候,匹配出来的结果就是要么都是逗号,要么都是引号。类似的还有横线[-]和下划线[_]等。那我们要做的,其实就可以计算位图中1值的重心在x方向和y方向的分布,一比对就可以轻松的判别了。比如逗号的重心在y轴上的分布是更下面一些,而引号则是更上面一些。

具体重心的计算方式有很多,最简单的就是:

x轴:将每个1点的x坐标分别相加,然后除以1点的个数,再除以宽度做归一化;

y轴:将每个1点的y坐标分别相加,然后除以1点的个数,再除以高度做归一化。

for (int x = 0; x < width; x++)

{

for (int y = 0; y < height; y++)

{

if (data[x][y] == 1)

{

yValue += y;

count++;

}

}

}

double avg = count > 0 ? yValue / count : 0;

avg = avg / height;

方法五:宽高比匹配

为什么要做这样一个匹配呢?我们先看一个case:

1 1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1 1

请问,这个10*10的图案,是什么字符?

这可能是一个句号[.],也可能是一个竖线[|],还有可能是某些字体的逗号[,]。因为在做归一化的时候,都变成了一个全是1的图案。而逗号和句号的重心有可能还差不多。这个时候,我们就可以用原始信息中的宽高比来做比对。比如,句号[.]的宽高比接近1:1,竖线[|]的宽高比可能是1:20,逗号[,]的宽高比可能是1:2等等。有了这个判断,就可以将相似的这些图案区分开来了。

通过以上几种算法,我们就会拿到每个算法计算出来的每个待识别图案和标准图案的相似值。对于每个待识别图案,我们将每个算法的相似值做一个加权合并,就得到了最终的相似度值,从而来确定这个待识别的图案和哪个标准图案最相似,找出最有可能的asc码值。

好了,最后我们再来看看文章开始的时候,给大家展示的效果:



最后识别出来以后,没有做任何的单词校正工作:

===================================

Three months after [he government stopped issulng (&%) or renewing permits for In[ernet cafes because of security (%#) concerns, some cafe owners are having flnanclal (ff%%) concerns of their own.

===================================

大家肉眼看,识别率还是不错的。不过有一些字符比如(I、l、i)、(t、[)等这些字符组内的图案特别容易识别错误。为了解决这些问题,可以加入单词的校正算法。常用的LCS(最长公共子序列)或者BKTree等等。因为需要做全字典匹配,所以,BKTree的效率会更好一些。(这些基础算法就暂时不在这里细讲,有兴趣的同学可以查阅baidu)

有了纠错算法,基本能很好的将图案识别出来。
==== 总结 ====
以上只是一个基本的拍照识图的算法,除了上述说的这些方法,其实在实际应用中,还会对数据做反馈训练。比如有些字符会引发错误,我们会将这些图案反馈给系统,下次遇到类似的图案的时候,系统就会匹配到更精准的信息。同时,对于参数的调整,也可以引入反馈和修正。

而如果要加入中文的话,整个难度和复杂度还会提升不少,使用的方法可能都还有不一样。鉴于这次调研的时间,老王同志就没有再做深入的研究了。再等一段时间,老王可能会把这个做的更深入细致。这次就先到这里吧~ ^o^

最后还是老规矩,上美图:



void next()
{
大家可以关注老王的微信公众号:simplemain

老王最近写的技术干货都在里面,也可以提问题一起探讨~
不愿意打字的盆友们,可以长按一下二维码关注哈~



}


    关注 SimpleMain


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册