利用 KNN 算法进行验证码识别

文章:用KNN来进行验证码识别

前言

对那些及其简单的验证码,可以用KNN算法来破解,这里整理了一个思路,可以了解了解.那么KNN算法原理是什么呢?

KNN原理

这里写图片描述

kNN算法的核心思想是如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。 kNN方法在类别决策时,只与极少量的相邻样本有关。由于kNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,kNN方法较其他方法更为适合。

算法流程

  1. 准备数据,对数据进行预处理
  2. 选用合适的数据结构存储训练数据和测试元组
  3. 设定参数,如k
    4.维护一个大小为k的的按距离由大到小的优先级队列,用于存储最近邻训练元组。随机从训练元组中选取k个元组作为初始的最近邻元组,分别计算测试元组到这k个元组的距离,将训练元组标号和距离存入优先级队列
  4. 遍历训练元组集,计算当前训练元组与测试元组的距离,将所得距离L 与优先级队列中的最大距离Lmax
  5. 进行比较。若L>=Lmax,则舍弃该元组,遍历下一个元组。若L < Lmax,删除优先级队列中最大距离的元组,将当前训练元组存入优先级队列。
  6. 遍历完毕,计算优先级队列中k 个元组的多数类,并将其作为测试元组的类别。
  7. 测试元组集测试完毕后计算误差率,继续设定不同的k值重新进行训练,最后取误差率最小的k 值。

优点

1.简单,易于理解,易于实现,无需估计参数,无需训练
2.适合对稀有事件进行分类(例如当流失率很低时,比如低于0.5%,构造流失预测模型)
3.特别适合于多分类问题(multi-modal,对象具有多个类别标签),例如根据基因特征来判断其功能分类,kNN比SVM的表现要好

缺点

1.懒惰算法,对测试样本分类时的计算量大,内存开销大,评分慢
可解释性较差,无法给出决策树那样的规则。

下面开始着重讲如何利用KNN算法;来实现验证码的识别.

具体实现

其实就是简单地把字符进行旋转然后加上一些微弱的噪点形成的。我们要识别,就得逆行之.

具体思路

1)获得验证码
2) 首先二值化去掉噪点
3)然后把单个字符分割出来
4)最后旋转至标准方向,然后从这些处理好的图片中选出模板
5)最后每次新来一张验证码就按相同方式处理,然后和这些模板进行比较,选择判别距离最近的一个模板作为其判断结果(亦即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)
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

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

二值化

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
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

结果

这里写图片描述

分割

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
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

这里写图片描述

旋转

接下来进行旋转,哪找一个什么标准呢?据观察,这些字符旋转不超过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
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

这里写图片描述

模板选取

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

测试

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

% 具有差分最小值的图作为答案
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);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
验证码实际字符: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%
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

转载:https://blog.csdn.net/baihuaxiu123/article/details/55506801

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值