赞
踩
闲暇之时偶然翻出了一个几年前用Java写的预约课程工具。看这创建时间,尘土已相当厚了。
* @version $$Id: Main, v 0.1 2018/5/9 12:49
在看到她的那一刻,瞬间勾起了有些遗憾的记忆。说起这位“老友”,当时还真帮了我的大忙。
话说是因为要在网站上预约课程。预约课程的流程与在电商平台购物有些相似,登录,根据日期查询当天是否有可预约的课程,如果有则从列表中找到合适的时间段,最后预订。经过几次这样的操作后便觉得实在麻烦。程序员最怕的就是这样的麻烦!如果别人也觉得上述情况麻烦,那Ta只能忍着继续在页面上点来点去,但作为一名讨厌繁琐的程序员可不会惯着它。随即,F12走起。综上,我这位“老友”便诞生了,而且之后还给我带来了意外惊喜。
这个预约课程工具本身很简单,只用了java.net包下与HTTP相关的类便很快搞定了登录和约课的功能,但这两个功能在操作时都需要将图片验证码中的字符一并提交给服务器。完成代码后,之前需要在页面上完成的输入,则改为在代码启动后的控制台进行输入。那图片验证码是怎么在控制台输出的?先来看看验证码图片的样子。
这是放大了一千多倍的验证码图片,就是这样的。说白了,上面的每个像素点也只是不同的数字而已,数值越小则颜色越深。那么上面这个验证码图片经过代码处理后,打印到控制台就是下面这样的。
- 0
- 0 0
- 0
- 0
- 0 0
- 0
- 0000 000 000000000 000
- 0000000 000000000 000
- 00 0 0000
- 0 00 00 00 0 00 00 0
- 0 00 0 00000 0 0 0 00
- 0 00 00 00 0000 0 00 00 0
- 0 0 00 00 00 000000
- 0 0000 000 00 00000000
- 0000 000 00 00 00
- 000 00 00 000 00
- 00 00 000 00 000
- 0
- 0
-
-

怎么样,经过处理后打印出来还是很好辨识的吧。将肉眼辨识出来的验证码字符从控制台输入给程序后便可直接预定指定时间的课程,而登录,查询,浏览,选择这些在页面上预约操作所必不可少的步骤则被全部省略掉了。如果在自己指定的时间没有可供预定的课程,则程序给出相应提示,如果预约成功,则返回预约成功课程的时间地点等信息。在测试代码的过程中,还给了我前面提到的意外惊喜————程序可以预约在网站上查询不到的课程。这个意外收获直接让我提前完成了所有课程,简直有点降维打击的感觉,如同菜还没上桌,你已经在厨房吃饱了。不过作为程序员要注意,这虽然算不上是一个bug,但肯定算是一个缺陷,即前台与后台的没有统一。
从这位“老友”第一次为我成功预定课程后,我就一直想着怎么再把她捯饬的漂亮些————把人肉识别图片验证码变成自动识别,这样就又可以省去敲四下键盘的巨大工作量了。为此,当时还特意买了一本《模式识别》。之所以买这本书而不是关于机器学习的书,是因为打算用数学模型将每个字符描述出来,而且第一感觉是描述字符的数学模型无法通过机器学习得到,得自己通过对字符的规律进行总结后给出(其实《模式识别》中的内容与机器学习的内容有很多相似之处)。不过后来随着课程的完毕,自动识别图片验证码的想法也就被搁置了,直到今天她又重见天日。如今某家要誓补当年之憾。
前段时间抽空用Java弥补了下这个当年的遗憾,总的来说识别效果还不错。具体的识别方法也是在以下将要介绍的实现过程中几经调整。
之所以人可以识别图片中的那些字符,是因为我们认识图片中的每一个字符,但如果让一个未识字的幼儿来指出这些字符是什么,那么他肯定是做不到的。如果想让他辨识字符,那就要先告诉他这些字符是什么。这时,计算机就是这个尚未识字的幼儿。首先要做的就是让计算机认识并记住它要识别的每个字符。这些字符包括0~9,a~z,A~Z。可以用数学模型或记录有效像素两种方法来让计算机认识它将要识别的字符。用数学模型给出每个字符的描述,也就是说要写出六十多个方程组,这工作量实在太可观了。虽然数学模型可以节省空间,并在识别效果不准时可对其进行参数调整,不过这种方法会出现与机器学习中的过拟合类似的问题,也就是对一个字符的某个样子识别准确了,但同一个字符的其他样子的识别能力就降低了,还可能把其他字符识别成这个字符,属于按下葫芦起了瓢,还是算了吧。那就只能用记录描述字符的有效像素的方法来让计算机认识每个字符了。这个方法的缺点是要记录每个字符的所有可能的样子,如果有一个样子没记录,那么就识别不出来或是识别成其他字符。不过也有优点,那就是简单,只要发现了未识别的字符,就将该字符的有效像素描述记录下来,并给与标记即可。总结就是,数学模型复杂但灵活,像素描述简单但死板。事儿就是这么个事儿,情况就是这么个情况。
用记录字符有效像素(文中提到的有效像素指的是表示出字符轮廓的像素)这种方法则需要在识别验证码前保存同一个字符的所有可能的样子,我把这称为有效像素描述。以下就是以文本形式保存的字符像素描述的内容。
- 0 00 000 00 000 00 0 000 00 000
- 0 0000 00 00 0000 00 0 0000 00 0 0000 0
- 00 0000 000 000 0000 00 00 0000 00 00 0 00 0
- 00 0000 00 00 0000 00 00 0000 00 00 0000 00
- 00 00 0 00 00 0 0 00 00 00 0 00 00 00 00 00
- 00000 00 00 00000 00 0 00 0 00 00 00 0 00 00
- 0000 0000 0000 0000 0000 00 00 0000 00 00
- 0000 0000 0000 000 0000 0000 0000 000
- 0000 000 0000 000 0000 000 0000 000
- 000 000 000 000 000 000 000 000
- 00 000 00 00 00 000 00 000
这是大写的“W”在图片验证码中可能的样子,当然这四个有效像素描述分别位于文件名中包含大写W的四个文件中,像下图这样。
用这样的方法就需要在发现未能识别的字符时,将这个字符的有效像素描述保存为上图中的文件。缺点是,随着文件的增多,识别速度会变慢,而且会造成误识别为其他字符的情况增多。所以上述文件并不是越多越好。
有了上面这些文件,就距离让计算机识别出图片中的验证码进了一步。此时计算机已经知道了每个字符在验证码图片中的样子了。接下来就可以开始进行比较了。在比较有效像素的过程中,同时计算与字符集中每个字符的相似度,并返回相似度最高的那个字符对应的文件名,从而也就识别出了图片中的字符。
上面的文本文件中保存的是单个字符的有效像素描述,但验证码图片上的那些字符可是连接一起的。所以在与字符描述集中的字符进行比较前先得将图片中的字符切分成一个一个的,然后再进行比较。这也是图片验证码最常用的识别手段。现在,真正的重点来了————从图片中切分出可以识别的字符。
从前面的图片可以看出,验证码图片上的字符间的像素值显然是高于用于显示字符轮廓像素的值的。因此,这自然的就成为了字符的切分标准。仿佛又接近了目标一步。然而,现实总是残酷的,如果能仅凭这个标准就可将所有图片都准确的切分成四个可供识别的部分那可就太美好了。来看下这张图片。
K和W还好说,可是9和 j 的下方存在2个有效像素在同一X轴上,所以用有效像素个数为0的标准来拆分上面这个图时,只能切分出3个字符,即K,W,和9j,9和j是连接在一起的。如果这个切分标准放宽些,比如当X轴上有一个,两个或三个有效字符也允许切分,那确实可以将9和 j 切分开,但对于其他图片就有可能出现误切,比如下面这个两个图。
如果以小于等于两个或三个有效像素的标准来切分上面两个图的话,则前一个图中的L和后一个图中的 m 将被拆分成两个以上且无法识别或是被识别成其他字符的部分。因此,要是切分后得到的字符少于或多余4个的话,就不宜再用有效像素法进行处理了,必须换个思路。
最初所采用的切分方法是:当切分出的字符少于4个时,就找出其中最宽的那个,然后放宽切分条件再次进行切分。这种方法有一定的效果,但问题是最宽的那部分并非一定是由多个字符组成的。比如,三个小写 j 也没有一个大写的W宽,还是会出现误切分,那就换一种办法。从所有切分出的部分中找出在X轴上有效像素最少的那段,然后以这个X轴为界再次进行切分。只能说也有一定效果,但当识别出了一个图片后,对其他图片的识别能力便减弱了,这显然不行。随后,试着用这两种方法分别再次处理无法切分出4个字符的图片。因为这里要得到两种方法怎么交替结合运行才能给出最高相似度,看到“最”这个字,一下想到了用动态规划算法试试。之后结合动态规划,将上述两种方法结合了起来,效果还是不理想,而且代码也越来越复杂。不行,还得换思路。
事实证明,当没有好的思路的时候,先暂时放下,干点别的事,也许灵感大爷自己就找上门来了。这不,骑车出去浪一圈,打会儿篮球,思路就有了。不是有个KMP算法吗,可以借鉴一下。既然更换切分标准会误伤友军,那干脆就不切分了,反正现在已经有了一堆字符描述文件,直接比对不就可以了。
具体做法就是从候选字符集中取一个字符描述,然后根据这个字符描述的宽度,从图片右侧开始比对与该宽度相等的范围内所有像素,比对完之后计算出一个相似度,然后再取下一个字符描述重复这个过程,直到比对完全部字符集,随后便得到了最高相似度的那个字符描述,接着缩小图片范围,重复上述全部过程,直到图片范围缩小到0。核心代码如下。
- private static final float SIMILARITY_THRESHOLD = 0.9f;
-
- private void selectMaxSimilarityChar(CharRange charRange) {
- //从图片右侧开始依次比对字符集所有字符,取相似度最高者
- if (!charRange.alreadyMatched) {
- MatchedChar matchedChar = matchEqualsCharWidth(charRange);
-
- if (matchedChar != null && matchedChars.size() < CHAR_COUNT) {
- matchedChars.add(matchedChar);
- } else {
- charRange.alreadyMatched = true;
- stack.add(0, charRange);
- }
- } else {
- Map<CharRange, MatchedChar> recognizeResult = matchFromEndToStart(charRange, new CharRange(charRange.startX, charRange.endX));
- MatchedChar bestMatchedChar = new MatchedChar(null, 0, null);
- for (MatchedChar matchedChar : recognizeResult.values()) {
- if (matchedChar.similarity > bestMatchedChar.similarity && matchedChar.similarity > SIMILARITY_THRESHOLD) {
- bestMatchedChar = matchedChar;
- }
- }
- if (bestMatchedChar.aChar != null) {
- matchedChars.add(bestMatchedChar);
-
- charRange.endX = bestMatchedChar.charRange.startX - 1;
- charRange.alreadyMatched = false;
-
- stack.add(0, charRange);
- } else {
- if (matchedChars.size() == CHAR_COUNT) {
- return;
- }
- recognizeResult = matchFromStartToEnd(charRange);
-
- bestMatchedChar = new MatchedChar(null, 0, null);
- for (MatchedChar matchedChar : recognizeResult.values()) {
- if (matchedChar.similarity > bestMatchedChar.similarity && matchedChar.similarity > SIMILARITY_THRESHOLD) {
- bestMatchedChar = matchedChar;
- }
- }
- if (bestMatchedChar.aChar != null) {
- matchedChars.add(bestMatchedChar);
-
- charRange.startX = bestMatchedChar.charRange.endX + 1;
- charRange.alreadyMatched = false;
- stack.add(0, charRange);
- } else {
- log.out("未能给" + charRange + "匹配到任何字符");
- }
- }
- }
- }

- private MatchedChar calculateSimilarity(CharRange charRange) {
- MatchedChar bestMatchedChar = new MatchedChar(null, 0f, charRange);
-
- for (Map.Entry<boolean[][], Character> candidateChar : CharSetLoader.getCharDict().entrySet()) {
- int candidateCharWidth = candidateChar.getKey()[0].length, recognizeCharWidth = charRange.endX - charRange.startX + 1;
- //过滤掉宽度不一致的字符
- if (recognizeCharWidth != candidateCharWidth) {
- continue;
- }
- //以候选字符的矩阵进行比较,可以得到很高的相似度,但对于有重叠情况的字符有误判,如 q 和 o
- MatchedChar mc = calculateSimilarity(charRange, candidateChar);
- if (mc.similarity > bestMatchedChar.similarity) {
- bestMatchedChar = mc;
- }
- }
- return bestMatchedChar;
- }
-
- private MatchedChar calculateSimilarity(CharRange charRange, Map.Entry<boolean[][], Character> candidateChar) {
- float similarity = getBingoByCandidateChar(charRange, candidateChar) / (candidateChar.getKey().length * candidateChar.getKey()[0].length);
- return new MatchedChar(candidateChar.getValue(), similarity, charRange);
- }

这个方法的效果要优于之前的两个方法,不过也存在误判,这取决于字符描述集中不同字符的相似程度,这也就是之前所说的,字符描述集合中的文件不易太多的原因,就像布隆过滤器一样,随着数据的增多,误判率也会随之增长。
- 未识别文件:[{文件名:DBYc | 识别结果:DEYc} {文件名:ewfr | 识别结果:Efr} {文件名:jwwk | 识别结果:JWk} {文件名:l4am | 识别结果:AM} {文件名:phcw | 识别结果:HCw} {文件名:rmlf | 识别结果:rmf} ]
- 共计 83.0 文件,识别正确数量:76.0 正确率:0.91566265
程序识别的正确率还是比较邻人满意的。
因为上述识别代码只是针对那一种图片而写的,为了验证识别程序的通用性,于是在网上找了一些与之前进行识别的不一样的验证码图片,比如下面这些。
当然,一开始是没能成功识别的,问题出在对字符像素和背景像素的区分上,之后便重写了字符像素和背景像素的识别代码。终于,经过不断的调整,得到了一个更为通用的图片验证码识别程序。
- 0000000000 000 00 0 000 0000 00
- 00 0 00 00 000 000 000
- 00 0 0 0 00 000 00
- 00 0 00 0 00 0 00 0 00
- 00 0 00 00 0 000 0 00 0 00
- 000000 00 00 000 00 0 00 0 00
- 00 0 0000 00 0 0 00 0 00
- 00 0 00 0 00 00 0 00 0 00
- 00 00 00 00 00 0 000 00
- 00 0 0 0 0 00 0 000 00
- 00 0 00 0 00 00 0 0 00
- 0000000000 00000 0 0 0000 00000 0 00000
-
-
- 0000 0000000000 0000
- 000000 0000000000 0000000
- 000 00 000 000 00
- 00 00 00 00 00
- 00 00 000 00 0 00 000000
- 00 000 00000 00 00 00000000000
- 00000000 000 00 00 00000000000
- 000 00 00 0 0000000000000
- 00 0 00 00 00000000000
- 00 0 00 00 00000000000
- 0 00 0 000000000000000
- 00 00 0 00000000000 00
- 000 000 00000000000 00
- 0000000000000000000000000000 00000000
- 000000000000 00000000000 000000000
- 00000000000
- 00000
-
-
- 0000 00000 0000 000
- 0 00 00 0 0 00
- 0 00 00 0 00 0
- 0 00 00 00 00 00
- 0 0 0 00
- 00 0 00 0 0 00
- 00000 000 0000 000
-

- 00000000000 000000
- 0000000000000 00000000
- 0000000000 0000000000000 0000000000
- 000000000000 000000000000 000000000000
- 000000000000 000000000000 000000000 00000 000
- 000000000000 0000000 0000 0000000000 0000 00
- 000000 00000 0000 0000 0000000000000 00000
- 00000 00000 0000 00000 0000000 000000 00000
- 00000 000000 0000 0000000 000000 0000
- 0000000000000 0000 0000000 000000 0000 000
- 00000000000000 0000 00000 000000 0000 00000000
- 000000000000 00000 000000000000 0000 00000000
- 000000000000 000000 000000000000 0000 0000000
- 00000 00000 000000 0000000000 0000 0000
- 00000000000 000000 00000000000 000000 0000
- 0000000 00000 000000 000000000000 000000 0000
- 0000000 000000 00000 000000 000000 000000000000
- 000000000 0000000 000 000000 0000000000 000000000
- 0000000 0000000 000000 000000000 000
- 00000 0000000 000000 0000000 0
- 00000 0000000 000000
- 00000000 00
- 0000000
- 000000

- 000
- 00000
- 0000000
- 000 000
- 0 000 00
- 00000 000 0000 000
- 000000 00 00 0000000 00000
- 0000 00 000 00 000 000000000 00000
- 00000 00 00 00 00 000 000 0000 000 000 00000
- 0 0000 000 00 000 000 0 00 00000 000000 00 000 0000000000
- 0 0000 000 00 00 0000000 00 00000 00 000 00 0000000000 000
- 0 000000 000 00 000000000 000 000 00 00 000 0000000 000
- 00 00 00000 000 00 00 0000 00 00 000 00 000 0000 000000 00
- 00 000 000 00000 000 00 00 000 000 00 00000 00000000 000
- 00 00000 000 00000 0000 00000 0000000 00 000 00 0000 000 00 00
- 00 000000 00000 000000 00000 00000000 00 00 000 0000 000 00 00
- 00 00 0000 00000 00 00 0000 000 000000 00 000 00000 0000000 000
- 000 00 0000 000000 000 000 0000 00000 000 00 00000 000000 00
- 000 000 000 00000 00000 000000000000 0000 000 00 0000 0000 000
- 0000000 000 00000 0000 0000000000000 0000 00 000 00000 0000
- 0000000000000 000000 000 000 000 0000 00 000 00000 000000
- 00 0000000000000 0000 0000 000 00 0000 000 000 00000 00000
- 000 00 0000 00000000000000 000000000 00000 000 00 00 00 00
- 000 000 0000000000000000 00 00000 00 00 0000000 00 000 00
- 00 000 000000 000000000000 00 00 000 000 00000 000 00 000
- 00 00 00000 0000000000000000000 000 000 00 00
- 000000 000 0000000000000000000 000000 000 00
- 000000 00000000000000000000000000000000000000000
- 0000 00000000000000000000000000000
- 00000
- 000

该说不说的,那个空心的验证码实在是太损了!
之后又顺便找了个签名网站,生成了偶像的Flag和姓名,测试效果也不错。
- -----------------------------------------------------------------------------------------------------------------
- ---------##-----------------------#----------------##------------#-----------------------------------------------
- --------###-----##---------------###---------------##-----------###----------------##----------------------------
- --------###----###--------------####--------------###-----------##-----------------##----------------------------
- --------###----###--##---------####---------------###-----------##-----------------##----------------------------
- --------###---###--###---------####--------------####-----------##----------------##-----------------------------
- --------###--####--##---------##-#-----------#---###------------##----------------##-----------------------------
- --------######-##-------------###------##---###--###------------##----##---------###-#----##----#----------------
- --------#####--##--------###--###-----###---###-###---------#--##----##-----###--##-##---###---####--###---------
- --------#------#---##---####-#####---####--###--##----------##-##---####---###--###-#---####--#####-######-------
- --------#-----##--###--###---##-##--#####--###--##----------##-##-######--###---##-##--##-##-###-##########------
- -------##-----##-####-###--#######################-##--------#-#--##########--##########-######--###-###-###-----
- -------##-----##---######-##-###-######-##--###--####--------###--###-###-##-##-###-###--###-######--##---##-----
- --------#-----#--------####--##----###-----------------------###--##------####--##-------##---##-----#-----------
- -----------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------- -----------##----###---------------------------------------------#-----------------#####------------------------- ----------###---####--------------------------------------------##-------------###########----------------------- ----------###---###----#---------------------------------------###------------##############--------------------- ----------##----##----##---------------------------------------###-----------###---##----###--------------------- ----------##---###----##---------------------------------------##------------##----##-----##--------------------- ---------###--###---------------------------------------------##-------------###--###-----##--------------------- ---------#######------##---#----------##---------------###----##---------------#--##------##----##---##---#------ ---------#######-----###--###-###----####-------------#####-#######---------------##------##--#####-###-####----- ---------##--####----##--#-######---####-------------##--######-------------------##-----###-##--########-##----- --------###---####---##---####-##--##---------------##---##--###-----------------###----###--##---######--##----- --------###----###--##----###--##-##---#------------##---##--####----------------##########-##---##--##---#------ --------##-----####-##----###--##-#######-----------##---##-##--##---------------########---##---##--##--##------ --------##------######--#-##---######--###----------##--##--##--##---------------##---------##--##--###--##------ --------##-------#######-###---###----##------------#####---##--##--------------###----------####---#####-------- ------------------------------------####--------------------##--#---------------##------------------##----------- -----------------------------------##-##--------------------##-##---------------##------------------##----------- -----------------------------------#-##---------------------####----------------###-----------------##----------- ----------------------------------####---------------------####----------------###------------------##----------- ----------------------------------###-----------------------#---------------------------------------#------------ ----------------------------------------------------------------------------------------------------#------------ -----------------------------------------------------------------------------------------------------------------
当然,以上输出都是缩小了4倍的结果,如果原图输出是不太利于展示的,比如下面这样。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。