侯体宗的博客
  • 首页
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

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

Python  /  管理员 发布于 7年前   163

前言

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

分析

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

获得验证码

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

#-*- coding:UTF-8 -*-import urllib,urllib2,cookielib,string,Imagedef 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目录下面。如图:

二值化

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

mydir='./source/';bw = './bw/';if mydir(end)~='\' mydir=[mydir,'\'];endDIRS=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); endend

处理结果如图:

分割

mydir='./bw/';letter = './letter/';if mydir(end)~='\' mydir=[mydir,'\'];endDIRS=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 endend

处理结果如图:

旋转

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

if mydir(end)~='\' mydir=[mydir,'\'];endDIRS=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); endend

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

模板选取

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

测试

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

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

mydir='./test/';samples = './samples/';if mydir(end)~='\' mydir=[mydir,'\'];endif samples(end)~='\' samples=[samples,'\'];endDIRS=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); endendfprintf('\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 12306抢火车票脚本
    下一条:
    Python实现的径向基(RBF)神经网络示例
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在python语言中Flask框架的学习及简单功能示例(0个评论)
    • 在Python语言中实现GUI全屏倒计时代码示例(0个评论)
    • Python + zipfile库实现zip文件解压自动化脚本示例(0个评论)
    • python爬虫BeautifulSoup快速抓取网站图片(1个评论)
    • vscode 配置 python3开发环境的方法(0个评论)
    • 近期文章
    • 在windows10中升级go版本至1.24后LiteIDE的Ctrl+左击无法跳转问题解决方案(0个评论)
    • 智能合约Solidity学习CryptoZombie第四课:僵尸作战系统(0个评论)
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(95个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2018-04
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2022-01
    • 2023-07
    • 2023-10
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客