步了学长的老路,准备搞一下旧版正方的图片验证码(。
当然这篇重点是堆一下KNN,真要想做识别的话搞这个基础算法没前途的而且也没准确率,卷积神经网络使我快乐(……
附学长文章链接:实战“正方教务管理系统”验证码识别
整体思路是手动标记内容->二值化->分割图片并按照图像内容分类->测试数据
这样子。
用了真实数据集甚至图像还没去噪,领会精神就好(。
前期准备
随便挑一个旧版正方教务系统,拿到生成验证码图像的网页。这里随便找了个没做频繁访问处理的学校的页面:
121.22.25.47/CheckCode.aspx
抓包写headers的过程不再赘述。构造爬虫脚本:
#coding = utf-8
'''
●getPIC v1.0
作者:Yemu
创建日期:19.5.21
最近修改时间:19.5.21
程序目的:爬取教务系统验证码图片
程序备注:
1.用于爬取KNN算法中所需的原始数据集
2.因为可复用性为零所以直接莽了,没封装函数啥的,问题不大
关于作者栏的署名:
复用性极低的辣鸡程序=Yemu
带有探索和尝试性质=Kadoya
不知道写哪个id好的时候=ylylhl
'''
import requests
import os
path='./origin/'
try:
os.makedirs(path)
except:
print("dir has been exist.")
url="http://121.22.25.47/CheckCode.aspx"
cookie="ASP.NET_SessionId=sgoze555qu3hqef42kchnm55"
for n in range(0,100):
try:
headers={
"Host": "121.22.25.47",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cache-Control": "max-age=0",
"Proxy-Connection": "keep-alive",
"Cookie": cookie,
"Referer": "http://121.22.25.47/",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"
}
response = requests.get(url,headers=headers)
filename=path+str(n)+'.gif'
with open(filename,'wb') as f:
f.write(response.content)
except Exception as e:
print('error:',e)
continue
print('done')
如下图,然后就到了令人去世的手动改名时间(。
二值化&分割图片脚本如下,分割区域直接搬了学长的数据(靠)
可以用opencv或者别的什么降噪,也可以做旋转处理,然懒(
或者直接指定rgb手动二值化,同样可以达到降噪的效果。
#coding = utf-8
'''
●splitPIC v1.0
作者:Yemu
创建日期:19.5.21
最近修改时间:19.5.21
程序目的:二值化+分割图片为单个字符+获取特征值并写入文件(=分类好的数据集
程序备注:
1.get_feature函数是抄来的(安详
参考链接:https://www.cnblogs.com/beer/p/5672678.html
2.懒得做去噪处理了,做了的话KNN正确率会更高一点
'''
import os
from PIL import Image
def get_feature(img): #获取指定图片特征值,即数每行每列黑色像素点
width, height = img.size
pixel_cnt_list = []
for y in range(height):
pix_cnt_x = 0
for x in range(width):
if img.getpixel((x, y)) == 0: #黑色点
pix_cnt_x += 1
pixel_cnt_list.append(pix_cnt_x)
for x in range(width):
pix_cnt_y = 0
for y in range(height):
if img.getpixel((x, y)) == 0: #黑色点
pix_cnt_y += 1
pixel_cnt_list.append(pix_cnt_y)
return pixel_cnt_list
if __name__ == '__main__':
path='./origin/'
filelist=[path+i for i in os.listdir(path)]
d={}
for i in filelist:
img=Image.open(i)
img=img.convert('L').convert('1')
boxs = [(5, 1, 17, 21), (17, 1, 29, 21), (29, 1, 41, 21), (41, 1, 53, 21)]
for x in range(len(boxs)):
roi = img.crop(boxs[x])
d.setdefault(i[len(path):][x], [])
d[i[len(path):][x]].append(roi)
f=open('data.txt','a')
for i in d.keys():
#new_path='./data2/'+i+'/'
#os.makedirs(new_path)
for j in range(len(d[i])):
#filename=new_path+str(j)+'.png'
#d[i][j].save(filename)
s=i+' '+' '.join(str(i) for i in get_feature(d[i][j]))+'\n'
#格式:对应字符+图像特征值
f.write(s)
这时生成的数据集如下图,前期准备已经完成。
kNN算法
简单介绍
kNN算法的核心思想是如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别。不得不说,百科上这张图挺好的,借用一下:
kNN实现起来也很简单 比k-means还要简单,只需计算测试向量距每个点的欧氏距离即可。相对的缺点也很明显,(因为每个点都要算一遍所以)数据集大的时候算起来真的很慢(。
虽然很相似,但对于k-means和kNN的区别说明,推荐阅读这篇文章:Kmeans算法与KNN算法的区别
python实现
#coding = utf-8
'''
●kNN v1.0
作者:Kadoya
创建日期:19.5.21
最近修改时间:19.5.21
程序目的:k-最近邻
主要算法说明:
1.分别计算测试数据距离每个已分类数据的欧几里得距离
2.选出k个距离最小的对象,这些对象中出现次数最多的类即为该测试数据的所属类
程序备注:
1.我爱摸鱼摸鱼爱我(?
2.魔改了一下,建议的kNN输入数据包括如下内容:
训练集D、测试对象z(属性值构成的向量)、类别标签集合L、数量k
也就是knn(D,z,L,k):return c,c属于L。
不过我这里的kNN是knn(D,z,k),L已经包含在D里面了,准确地说是D[i][0]。
更新历史:
5.21 v1.0
①基本功能完成,小bug不计其数,智障操作不计其数
Attribute Information:
1. 类别标签
2-33. 属性值
'''
from splitPIC import get_feature
from PIL import Image
from collections import Counter
#import os
def stardust(data):#基本处理 参数:原始数据
lost_star=[]
for i in data:
lost_star.append(i.strip('\n').split(' '))
lost_star=[[i[0]]+list(map(float,i[1:])) for i in lost_star[:]]#str转为float
return lost_star
def knn(test_data,train_data,k):#参数:测试数据,已分类好的数据集,k值
d={}
for s in range(len(train_data)):#遍历数据集
t=sum([pow(test_data[i]-train_data[s][i+1],2) for i in range(len(train_data[s])-1)])#计算欧几里得距离
d[s]=t #key:value=图像所在行数:测试图距该图距离
s=sorted(d.items(),key = lambda x:x[1])[:k]#从小到大排序
last=[train_data[i[0]][0] for i in s]#挑选前k个最近的点
p=Counter(last).most_common(1)#出现最多次的类即为测试数据的归属类
return p[0][0]
if __name__ == '__main__':
path = r'data.txt'
with open(path) as f:
data = f.readlines()
dataSet=stardust(data)
img=Image.open('testnew.gif')
result=''
img=img.convert('L').convert('1')
boxs = [(5, 1, 17, 21), (17, 1, 29, 21), (29, 1, 41, 21), (41, 1, 53, 21)]
for x in range(len(boxs)):
roi = img.crop(boxs[x])
result+=knn(get_feature(roi),dataSet,10)
print(result)
## #测试正确率
## path='./origin/'
## filelist=[path+i for i in os.listdir(path)]
## error=0
## for i in filelist:
## img=Image.open(i)
## result=''
## img=img.convert('L').convert('1')
## boxs = [(5, 1, 17, 21), (17, 1, 29, 21), (29, 1, 41, 21), (41, 1, 53, 21)]
## for x in range(len(boxs)):
## roi = img.crop(boxs[x])
## result+=knn(get_feature(roi),dataSet,10)
## print('========')
## print(result)
## if result!=i[len(path):-4]:
## error+=1
## print('wrong, correct answer is',i[len(path):-4])
## print('========')
## print(error/len(filelist))
正确率在50左右,不出意料的低(。手动粗糙地指定rgb二值化了一下升到了60%,将训练集增加到200个后正确率提升至80%,但是kNN的话80+应该是极限了,也没有继续下去的必要。
姑且就这样了,没什么特殊需要注意的点……窃以为书上的十个算法都比较适合新手入门练习。虽说重复造轮子没有意义,造古老的轮子更没有意义,但个人时间充裕的话这种行为对造轮子的个体还是有所裨益的。写过kmeans的话就不用写这个了根本什么都没有学到