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 相关文章推荐
Python脚本实现虾米网签到功能
Apr 12 Python
python django事务transaction源码分析详解
Mar 17 Python
Python处理XML格式数据的方法详解
Mar 21 Python
简单易懂的python环境安装教程
Jul 13 Python
详解Python开发中如何使用Hook技巧
Nov 01 Python
Pycharm连接远程服务器并实现远程调试的实现
Aug 02 Python
pytorch numpy list类型之间的相互转换实例
Aug 18 Python
selenium+PhantomJS爬取豆瓣读书
Aug 26 Python
python线程安全及多进程多线程实现方法详解
Sep 27 Python
详解Python time库的使用
Oct 10 Python
Python 实现自动完成A4标签排版打印功能
Apr 09 Python
python 利用zmail库发送邮件
Sep 11 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
PHP中大于2038年时间戳的问题处理方案
2015/03/03 PHP
php时间计算相关问题小结
2016/05/09 PHP
PHP Swoole异步读取、写入文件操作示例
2019/10/24 PHP
Aptana调试javascript图解教程
2009/11/30 Javascript
javascript中的prototype属性实例分析说明
2010/08/09 Javascript
JS操作JSON要领详细总结
2013/08/25 Javascript
jQuery选择器源码解读(一):Sizzle方法
2015/03/31 Javascript
如何改进javascript代码的性能
2015/04/02 Javascript
Highcharts使用简例及异步动态读取数据
2015/12/30 Javascript
JavaScript中点击事件的写法
2016/06/28 Javascript
浅谈JS中的bind方法与函数柯里化
2016/08/10 Javascript
解析AngularJS中get请求URL出现的跨域问题
2016/12/01 Javascript
微信小程序request请求后台接口php的实例详解
2017/09/20 Javascript
vue 挂载路由到头部导航的方法
2017/11/13 Javascript
基于iScroll实现内容滚动效果
2018/03/21 Javascript
JavaScript继承定义与用法实践分析
2018/05/28 Javascript
打通前后端构建一个Vue+Express的开发环境
2018/07/17 Javascript
基于mpvue的简单弹窗组件mptoast使用详解
2019/08/02 Javascript
node.js中对Event Loop事件循环的理解与应用实例分析
2020/02/14 Javascript
原生js实现日历效果
2020/03/02 Javascript
webpack+vue-cil 中proxyTable配置接口地址代理操作
2020/07/18 Javascript
CentOS中使用virtualenv搭建python3环境
2015/06/08 Python
python TCP Socket的粘包和分包的处理详解
2018/02/09 Python
在Pycharm中将pyinstaller加入External Tools的方法
2019/01/16 Python
Python @property及getter setter原理详解
2020/03/31 Python
使用python实现名片管理系统
2020/06/18 Python
如何使用pycharm连接Databricks的步骤详解
2020/09/23 Python
详解修改Anaconda中的Jupyter Notebook默认工作路径的三种方式
2021/01/24 Python
程序设计HTML5 Canvas API
2013/04/08 HTML / CSS
MIRTA官网:手工包,100%意大利制造
2020/02/11 全球购物
数控专业毕业生求职信范文
2013/09/21 职场文书
宿舍使用违章电器检讨书
2014/01/12 职场文书
亲戚结婚的请假条
2014/02/11 职场文书
小升初自荐信范文
2015/03/05 职场文书
质检员工作总结2015
2015/04/25 职场文书
Windows Server 2016服务器用户管理及远程授权图文教程
2022/08/14 Servers