文章:用KNN来进行验证码识别
#前言
对那些及其简单的验证码,可以用KNN算法来破解,这里整理了一个思路,可以了解了解.那么KNN算法原理是什么呢?
kNN算法的核心思想是如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。 kNN方法在类别决策时,只与极少量的相邻样本有关。由于kNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,kNN方法较其他方法更为适合。
#算法流程
- 准备数据,对数据进行预处理
#优点
1.简单,易于理解,易于实现,无需估计参数,无需训练
2.适合对稀有事件进行分类(例如当流失率很低时,比如低于0.5%,构造流失预测模型)
3.特别适合于多分类问题(multi-modal,对象具有多个类别标签),例如根据基因特征来判断其功能分类,kNN比SVM的表现要好
#缺点
1.懒惰算法,对测试样本分类时的计算量大,内存开销大,评分慢
可解释性较差,无法给出决策树那样的规则。
下面开始着重讲如何利用KNN算法;来实现验证码的识别.
#具体实现
其实就是简单地把字符进行旋转然后加上一些微弱的噪点形成的。我们要识别,就得逆行之.
#具体思路
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)
#二值化
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
#分割
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
#旋转
接下来进行旋转,哪找一个什么标准呢?据观察,这些字符旋转不超过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
#模板选取
现在从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 width0
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%