Python用 KNN 进行验证码识别的实现方法


Posted in Python onFebruary 06, 2018

前言

之前做了一个校园交友的APP,其中一个逻辑是通过用户的教务系统来确认用户是一名在校大学生,基本的想法是通过用户的账号和密码,用爬虫的方法来确认信息,但是许多教务系统都有验证码,当时是通过本地服务器去下载验证码,然后分发给客户端,然后让用户自己填写验证码,与账号密码一并提交给服务器,然后服务器再去模拟登录教务系统以确认用户能否登录该教务系统。验证码无疑让我们想使得用户快速认证的想法破灭了,但是当时也没办法,最近看了一些机器学习的内容,觉得对于大多数学校的那些极简单的验证码应该是可以用KNN这种方法来破解的,于是整理了一下思绪,撸起袖子做起来!

分析

我们学校的验证码是这样的:Python用 KNN 进行验证码识别的实现方法,其实就是简单地把字符进行旋转然后加上一些微弱的噪点形成的。我们要识别,就得逆行之,具体思路就是,首先二值化去掉噪点,然后把单个字符分割出来,最后旋转至标准方向,然后从这些处理好的图片中选出模板,最后每次新来一张验证码就按相同方式处理,然后和这些模板进行比较,选择判别距离最近的一个模板作为其判断结果(亦即KNN的思想,本文取K=1)。接下来按步骤进行说明。

获得验证码

首先得有大量的验证码,我们通过爬虫来实现,代码如下

#-*- coding:UTF-8 -*-
import urllib,urllib2,cookielib,string,Image
def getchk(number):
 #创建cookie对象
 cookie = cookielib.LWPCookieJar()
 cookieSupport= urllib2.HTTPCookieProcessor(cookie)
 opener = urllib2.build_opener(cookieSupport, urllib2.HTTPHandler)
 urllib2.install_opener(opener)
 #首次与教务系统链接获得cookie#
 #伪装browser
 headers = {
 'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
 'Accept-Encoding':'gzip,deflate',
 'Accept-Language':'zh-CN,zh;q=0.8',
 'User-Agent':'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36'
 }
 req0 = urllib2.Request(
  url ='http://mis.teach.ustc.edu.cn',
  headers = headers  #请求头
 )
 # 捕捉http错误
 try :
 result0 = urllib2.urlopen(req0)
 except urllib2.HTTPError,e:
 print e.code
 #提取cookie
 getcookie = ['',]
 for item in cookie:
 getcookie.append(item.name)
 getcookie.append("=")
 getcookie.append(item.value)
 getcookie = "".join(getcookie)
 
 #修改headers
 headers["Origin"] = "http://mis.teach.ustc.edu.cn"
 headers["Referer"] = "http://mis.teach.ustc.edu.cn/userinit.do"
 headers["Content-Type"] = "application/x-www-form-urlencoded"
 headers["Cookie"] = getcookie
 for i in range(number):
 req = urllib2.Request(
  url ="http://mis.teach.ustc.edu.cn/randomImage.do?date='1469451446894'",
  headers = headers   #请求头
 )
 response = urllib2.urlopen(req)
 status = response.getcode()
 picData = response.read()
 if status == 200:
  localPic = open("./source/"+str(i)+".jpg", "wb")
  localPic.write(picData)
  localPic.close()
 else:
  print "failed to get Check Code "
if __name__ == '__main__':
 getchk(500)

这里下载了500张验证码到source目录下面。如图:

Python用 KNN 进行验证码识别的实现方法

二值化

matlab丰富的图像处理函数能给我们省下很多时间,,我们遍历source文件夹,对每一张验证码图片进行二值化处理,把处理过的图片存入bw目录下。代码如下

mydir='./source/';
bw = './bw/';
if mydir(end)~='\'
 mydir=[mydir,'\'];
end
DIRS=dir([mydir,'*.jpg']); %扩展名
n=length(DIRS);
for i=1:n
 if ~DIRS(i).isdir
 img = imread(strcat(mydir,DIRS(i).name ));
 img = rgb2gray(img);%灰度化
 img = im2bw(img);%0-1二值化
 name = strcat(bw,DIRS(i).name)
 imwrite(img,name);
 end
end

处理结果如图:

Python用 KNN 进行验证码识别的实现方法

分割

mydir='./bw/';
letter = './letter/';
if mydir(end)~='\'
 mydir=[mydir,'\'];
end
DIRS=dir([mydir,'*.jpg']); %扩展名
n=length(DIRS);
for i=1:n
 if ~DIRS(i).isdir
 img = imread(strcat(mydir,DIRS(i).name ));
 img = im2bw(img);%二值化
 img = 1-img;%颜色反转让字符成为联通域,方便去除噪点
 for ii = 0:3
  region = [ii*20+1,1,19,20];%把一张验证码分成四个20*20大小的字符图片
  subimg = imcrop(img,region);
  imlabel = bwlabel(subimg);
%  imshow(imlabel);
 
  if max(max(imlabel))>1 % 说明有噪点,要去除
%   max(max(imlabel))
 
%   imshow(subimg);
 
  stats = regionprops(imlabel,'Area');
  area = cat(1,stats.Area);
  maxindex = find(area == max(area));
  area(maxindex) = 0;  
  secondindex = find(area == max(area)); 
  imindex = ismember(imlabel,secondindex);
  subimg(imindex==1)=0;%去掉第二大连通域,噪点不可能比字符大,所以第二大的就是噪点
  end
  name = strcat(letter,DIRS(i).name(1:length(DIRS(i).name)-4),'_',num2str(ii),'.jpg')
  imwrite(subimg,name);
 end
 end
end

处理结果如图:

Python用 KNN 进行验证码识别的实现方法

旋转

接下来进行旋转,哪找一个什么标准呢?据观察,这些字符旋转不超过60度,那么在正负60度之间,统一旋转至字符宽度最小就行了。代码如下

if mydir(end)~='\'
 mydir=[mydir,'\'];
end
DIRS=dir([mydir,'*.jpg']); %扩展名
n=length(DIRS);
for i=1:n
 if ~DIRS(i).isdir
 img = imread(strcat(mydir,DIRS(i).name ));
 img = im2bw(img);
 minwidth = 20;
 for angle = -60:60
  imgr=imrotate(img,angle,'bilinear','crop');%crop 避免图像大小变化
  imlabel = bwlabel(imgr);
  stats = regionprops(imlabel,'Area');
  area = cat(1,stats.Area);
  maxindex = find(area == max(area));
  imindex = ismember(imlabel,maxindex);%最大连通域为1
  [y,x] = find(imindex==1);
  width = max(x)-min(x)+1;
  if width<minwidth
  minwidth = width;
  imgrr = imgr;
  end
 end
 name = strcat(rotate,DIRS(i).name)
 imwrite(imgrr,name);
 end
end

处理结果如图,一共2000个字符的图片存在rotate文件夹中

Python用 KNN 进行验证码识别的实现方法

模板选取

现在从rotate文件夹中选取一套模板,涵盖每一个字符,一个字符可以选取多个图片,因为即使有前面的诸多处理也不能保证一个字符的最终呈现形式只有一种,多选几个才能保证覆盖率。把选出来的模板图片存入samples文件夹下,这个过程很耗时耗力。可以找同学帮忙~,如图

Python用 KNN 进行验证码识别的实现方法

测试

测试代码如下:首先对测试验证码进行上述操作,然后和选出来的模板进行比较,采用差分值最小的模板作为测试样本的字符选择,代码如下

% 具有差分最小值的图作为答案 

mydir='./test/';
samples = './samples/';
if mydir(end)~='\'
 mydir=[mydir,'\'];
end
if samples(end)~='\'
 samples=[samples,'\'];
end
DIRS=dir([mydir,'*.jpg']); %扩展?
DIRS1=dir([samples,'*.jpg']); %扩展名
n=length(DIRS);%验证码总图数
singleerror = 0;%单个错误
uniterror = 0;%一张验证码错误个数
for i=1:n
 if ~DIRS(i).isdir
 realcodes = DIRS(i).name(1:4);
 fprintf('验证码实际字符:%s\n',realcodes);
 img = imread(strcat(mydir,DIRS(i).name ));
 img = rgb2gray(img);
 img = im2bw(img);
 img = 1-img;%颜色反转让字符成为联通域
 subimgs = [];
 for ii = 0:3
  region = [ii*20+1,1,19,20];%奇怪,为什么这样才能均分?
  subimg = imcrop(img,region);
  imlabel = bwlabel(subimg);
  if max(max(imlabel))>1 % 说明有杂点
  stats = regionprops(imlabel,'Area');
  area = cat(1,stats.Area);
  maxindex = find(area == max(area));
  area(maxindex) = 0;  
  secondindex = find(area == max(area)); 
  imindex = ismember(imlabel,secondindex);
  subimg(imindex==1)=0;%去掉第二大连通域
  end
  subimgs = [subimgs;subimg];
 end
 codes = [];
 for ii = 0:3
  region = [ii*20+1,1,19,20];
  subimg = imcrop(img,region);
  minwidth = 20;
  for angle = -60:60
  imgr=imrotate(subimg,angle,'bilinear','crop');%crop 避免图像大小变化
  imlabel = bwlabel(imgr);
  stats = regionprops(imlabel,'Area');
  area = cat(1,stats.Area);
  maxindex = find(area == max(area));
  imindex = ismember(imlabel,maxindex);%最大连通域为1
  [y,x] = find(imindex==1);
  width = max(x)-min(x)+1;
  if width<minwidth
   minwidth = width;
   imgrr = imgr;
  end
  end
  mindiffv = 1000000;
  for jj = 1:length(DIRS1)
  imgsample = imread(strcat(samples,DIRS1(jj).name ));
  imgsample = im2bw(imgsample);
  diffv = abs(imgsample-imgrr);
  alldiffv = sum(sum(diffv));
  if alldiffv<mindiffv
   mindiffv = alldiffv;
   code = DIRS1(jj).name;
   code = code(1);
  end
  end
  codes = [codes,code];
 end
 fprintf('验证码测试字符:%s\n',codes);
 num = codes-realcodes;
 num = length(find(num~=0));
 singleerror = singleerror + num;
 if num>0
  uniterror = uniterror +1;
 end
 fprintf('错误个数:%d\n',num);
 end
end
fprintf('\n-----结果统计如下-----\n\n');
fprintf('测试验证码的字符数量:%d\n',n*4);
fprintf('测试验证码的字符错误数量:%d\n',singleerror);
fprintf('单个字符识别正确率:%.2f%%\n',(1-singleerror/(n*4))*100);
fprintf('测试验证码图的数量:%d\n',n);
fprintf('测试验证码图的错误数量:%d\n',uniterror);
fprintf('填对验证码的概率:%.2f%%\n',(1-uniterror/n)*100);

结果:

验证码实际字符:2B4E
验证码测试字符:2B4F
错误个数:1
验证码实际字符:4572
验证码测试字符:4572
错误个数:0
验证码实际字符:52CY
验证码测试字符:52LY
错误个数:1
验证码实际字符:83QG
验证码测试字符:85QG
错误个数:1
验证码实际字符:9992
验证码测试字符:9992
错误个数:0
验证码实际字符:A7Y7
验证码测试字符:A7Y7
错误个数:0
验证码实际字符:D993
验证码测试字符:D995
错误个数:1
验证码实际字符:F549
验证码测试字符:F5A9
错误个数:1
验证码实际字符:FMC6
验证码测试字符:FMLF
错误个数:2
验证码实际字符:R4N4
验证码测试字符:R4N4
错误个数:0 

-----结果统计如下----- 

测试验证码的字符数量:40
测试验证码的字符错误数量:7
单个字符识别正确率:82.50%
测试验证码图的数量:10
测试验证码图的错误数量:6
填对验证码的概率:40.00%

可见单个字符准确率是比较高的的了,但是综合准确率还是不行,观察结果至,错误的字符就是那些易混淆字符,比如E和F,C和L,5和3,4和A等,所以我们能做的事就是增加模板中的样本数量,以期尽量减少混淆。

增加了几十个样本过后再次试验,结果:

验证码实际字符:2B4E
验证码测试字符:2B4F
错误个数:1
验证码实际字符:4572
验证码测试字符:4572
错误个数:0
验证码实际字符:52CY
验证码测试字符:52LY
错误个数:1
验证码实际字符:83QG
验证码测试字符:83QG
错误个数:0
验证码实际字符:9992
验证码测试字符:9992
错误个数:0
验证码实际字符:A7Y7
验证码测试字符:A7Y7
错误个数:0
验证码实际字符:D993
验证码测试字符:D993
错误个数:0
验证码实际字符:F549
验证码测试字符:F5A9
错误个数:1
验证码实际字符:FMC6
验证码测试字符:FMLF
错误个数:2
验证码实际字符:R4N4
验证码测试字符:R4N4
错误个数:0 

-----结果统计如下----- 

测试验证码的字符数量:40
测试验证码的字符错误数量:5
单个字符识别正确率:87.50%
测试验证码图的数量:10
测试验证码图的错误数量:4
填对验证码的概率:60.00%

可见无论是单个字符识别正确率还是整个验证码正确的概率都有了提升。能够预见:随着模板数量的增多,正确率会不断地提高。

总结

这种方法的可扩展性很弱,而且只适用于简单的验证码,12306那种根本就别提了。

Python 相关文章推荐
Using Django with GAE Python 后台抓取多个网站的页面全文
Feb 17 Python
Python基础教程之tcp socket编程详解及简单实例
Feb 23 Python
对python中return和print的一些理解
Aug 18 Python
django将图片上传数据库后在前端显式的方法
May 25 Python
python脚本实现验证码识别
Jun 07 Python
python 以16进制打印输出的方法
Jul 09 Python
Python读取数据集并消除数据中的空行方法
Jul 12 Python
浅谈python requests 的put, post 请求参数的问题
Jan 02 Python
pytorch 模拟关系拟合——回归实例
Jan 14 Python
pytorch的batch normalize使用详解
Jan 15 Python
Python模块/包/库安装的六种方法及区别
Feb 24 Python
Python实现日志实时监测的示例详解
Apr 06 Python
Python实现的径向基(RBF)神经网络示例
Feb 06 #Python
python实现淘宝秒杀聚划算抢购自动提醒源码
Jun 23 #Python
初探TensorFLow从文件读取图片的四种方式
Feb 06 #Python
用十张图详解TensorFlow数据读取机制(附代码)
Feb 06 #Python
Python实现matplotlib显示中文的方法详解
Feb 06 #Python
Python实现自动上京东抢手机
Feb 06 #Python
Python获取指定文件夹下的文件名的方法
Feb 06 #Python
You might like
德生PL990,目前市面上唯一一款便携式插卡蓝牙全波段高性能收音机
2021/03/02 无线电
php 判断网页是否是utf8编码的方法
2014/06/06 PHP
ThinkPHP入库出现两次反斜线转义及数据库类转义的解决方法
2014/11/04 PHP
php获取数组元素中头一个数组元素值的实现方法
2014/12/20 PHP
php计算到指定日期还有多少天的方法
2015/04/14 PHP
基于jquery实现的省市区级联无ajax
2013/09/24 Javascript
js中settimeout方法加参数
2014/02/28 Javascript
JavaScript中的prototype和constructor简明总结
2014/04/05 Javascript
JS+CSS实现弹出全屏灰黑色透明遮罩效果的方法
2014/12/20 Javascript
Javascript核心读书有感之语言核心
2015/02/01 Javascript
Js为表单动态添加节点内容的方法
2015/02/10 Javascript
JavaScript实现单击下拉框选择直接跳转页面的方法
2015/07/02 Javascript
微信JSSDK上传图片
2015/08/23 Javascript
BootStrap使用popover插件实现鼠标经过显示并保持显示框
2016/06/23 Javascript
原生js实现选项卡功能
2017/03/08 Javascript
jQuery中clone()函数实现表单中增加和减少输入项
2017/05/13 jQuery
基于nodejs+express4.X实现文件下载的实例代码
2017/07/13 NodeJs
react.js组件实现拖拽复制和可排序的示例代码
2018/08/20 Javascript
JSON stringify方法原理及实例解析
2020/10/23 Javascript
VUE-ElementUI 自定义Loading图操作
2020/11/11 Javascript
[01:07:11]Secret vs Newbee 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
python持久性管理pickle模块详细介绍
2015/02/18 Python
单链表反转python实现代码示例
2018/02/08 Python
python 借助numpy保存数据为csv格式的实现方法
2018/07/04 Python
浅谈Python中文件夹和python package包的区别
2020/06/01 Python
python numpy实现rolling滚动案例
2020/06/08 Python
python实现图书馆抢座(自动预约)功能的示例代码
2020/09/29 Python
HTML5之HTML元素扩展(上)—新增加的元素及使用概述
2013/01/31 HTML / CSS
Monnier Frères美国官网:法国知名奢侈品网站
2016/11/22 全球购物
在DELPHI中调用存储过程和使用内嵌SQL哪种方式更好
2016/11/22 面试题
物流管理专业毕业生自荐信
2014/03/04 职场文书
大学活动总结范文
2014/04/29 职场文书
中秋节国旗下演讲稿
2014/09/13 职场文书
法人委托书范本
2014/09/15 职场文书
保密工作整改报告
2014/11/06 职场文书
校友会致辞
2015/07/30 职场文书