这段时间实习工作和找工作,导致时间很忙。现在有时间,还是希望把自己最近的工作感触和所完成的项目经验分享给大家。也算是对自己的一个总结。学习漫长的生涯里,作为我这个算法工程师这个职位非常重要,也希望大家互相督促养成好的纪律习惯。今天我要分享的一个项目是公司里爬虫经常用到的项目,破解验证码。博主下面要做很多验证码的破解,喜欢的同学可以跟进我的博客,我做出来都会分享出来的。现在已经完成两个验证码的破解,下面首先给大家介绍数字加减验证码的破解。
一、数据展示:
二、场景和目的
在大数据时代,如何借助互联网获得最大数据是很多TOB公司追求的资源。通过这些数据我们能够获得很多有效的智能推荐。
然而在网上进行爬虫数据的过程中会出现验证码的输入,导致爬数据的中断,影响工作进度。如果专职聘请一位员工输入验证码会费钱费力。因此需要机器自动识别和破解出现验证码。例如知道了第一张图,我就能得到28。自动输入数字(这一部分我们不探究),完成数据的持续爬。
三、算法分析
大家可能像我一样开始就会有一些思虑,但是不知道采用那个更有效。针对这种情况,不同的应用场景是有不一样的解决方案的。针对这个问题我们的思虑有下面这些:
1、切割+识别:该方法是大家最常用的方法,他有优点是,算法基础完善,我们只需要完成验证码的切割,就可以进行下一步的深度学习模型训练。但是缺点是,深度学习需要大量的数据,我们还要对切割的数据进行标注,这个过程中需要很大的经理。大家像了解这种方法网上大把,我就不介绍了。
2、CNN和CTC的方法,这个方法有一个场景是上一个方法不能做到的就是可以对缠连的数据进行识别,其中CTC这个方法来源于语音识别里面的思想。该方法能够对缠连数据进行一个合理的分离,能够达到很好的效果。但是缺点是不适合大图的分离,应用也是有局限,不过对长条验证码有很好的效果。另外就是需要大量的标注。
四、本文算法
我今天分享的算法,理论会听起来很简单,不多这个想法不太容易想到,我采用的是结合传统opencv的方法。该方法的想起,还是结合了对数据的分析。前面我们能够看到验证码的形式是一样的。包括 形状位置大小等信息。从而也体现出某局的技术含量有点低,给了我一个走捷径的方法。
数据分析,经过自己的二值化处理和灰度读取的方法对数据进行分析,发现验证码中的字符的像素位置和高度,完全相同。因此,我采用了创建PKL文件,来对每个位置的字符进行保存,从而进行检索匹配。这种方法不用训练,准确率高,效果快。但是有一个缺点,值能应用到该场景或者同等场景的情况。
五、源码及分析
#!/usr/bin/env python3
# coding=utf-8
"""
@File: Patent-Crack.py
@Desc:
@Author: lv junling
@Date Created: 2018/10/22
"""
import os
import pickle
import cv2
import numpy as np
from PIL import Image
class PatentCrack(object):
def __init__(self, pkl_fn=None):
if pkl_fn is None:
print('[error]Must specify the pickle filename.')
return
self.pkl_fn = pkl_fn
if os.path.exists(pkl_fn):
self._load_pkl()
else:
self.gen_pkl_fn()
def gen_pkl_fn(self):
imgs_path = u'./data'
# 从下面这些数字截取第一个位置的数字
chi_1_imgs = ['1.jpeg', '2.jpeg', '3.jpeg', '4.jpeg', '5.jpeg',
'6.jpeg', '7.jpeg', '8.jpeg', '9.jpeg']
chi_2_imgs = ['10.jpeg', '11.jpeg', '12.jpeg', '13.jpeg', '14.jpeg', '15.jpeg',
'16.jpeg', '17.jpeg', '18.jpeg', '19.jpeg']
# 截取第三个位置,也就是加减的矩阵数据
op_imgs = ['1.jpeg', '7.jpeg']
chi_3_imgs = ['100.jpeg', '101.jpeg', '102.jpeg', '103.jpeg', '104.jpeg', '105.jpeg',
'106.jpeg', '107.jpeg', '108.jpeg', '109.jpeg']
chi_1_arr = np.zeros([10, 20, 11], dtype=np.bool)
# 把每个位置的字符保存成矩阵的形式
for idx, img_fn in enumerate(chi_1_imgs):
c1, _, _, _ = self._get_split_img(os.path.join(imgs_path, img_fn))
chi_1_arr[idx+1] = c1
chi_2_arr = np.zeros([10, 20, 9], dtype=np.bool)
for idx, img_fn in enumerate(chi_2_imgs):
_, c2, _, _ = self._get_split_img(os.path.join(imgs_path, img_fn))
chi_2_arr[idx] = c2
op_arr = np.zeros([3, 20, 12], dtype=np.bool)
for idx, img_fn in enumerate(op_imgs):
_, _, op, _ = self._get_split_img(os.path.join(imgs_path, img_fn))
op_arr[idx] = op
chi_3_arr = np.zeros([10, 20, 10], dtype=np.bool)
for idx, img_fn in enumerate(chi_3_imgs):
_, _, _, c3 = self._get_split_img(os.path.join(imgs_path, img_fn))
chi_3_arr[idx] = c3
# 把每个位置的矩阵和标签储存到pkl文件中
fout = open(self.pkl_fn, 'wb')
data = {'chi_1': chi_1_arr, 'chi_2': chi_2_arr, 'op': op_arr, 'chi_3': chi_3_arr}
pickle.dump(data, fout)
fout.close()
self._load_pkl()
def _load_pkl(self):
data = pickle.load(open(self.pkl_fn, 'rb'))
self.chi_1_arr = data['chi_1']
self.op_arr = data['op']
self.chi_2_arr = data['chi_2']
self.chi_3_arr = data['chi_3']
@staticmethod
def _get_split_img(img_fn):
img_arr = np.array(Image.open(img_fn).convert('L'))
img_arr[img_arr < 156] = 1
img_arr[img_arr >= 156] = 0
img_arr = img_arr.astype(np.bool)
chi_1_arr = img_arr[:, 6:17]
chi_2_arr = img_arr[:, 19:28]
op_arr = img_arr[:, 32:44]
chi_3_arr = img_arr[:, 45:55]
return chi_1_arr, chi_2_arr, op_arr, chi_3_arr
@staticmethod
def _cal_result(num1, num2, num3,op):
if op == 0:
return num1*10 + num2 + num3
elif op == 1:
return num1*10 + num2 - num3
elif op == 2:
return num1 * num2
else:
return int(num1 / num2)
# 获得结果的方法
def feed(self, img_fn):
chi_1_arr, chi_2_arr, op_arr, chi_3_arr = self._get_split_img(img_fn)
chi_1_arr = np.tile(chi_1_arr[np.newaxis, :], [10, 1, 1])
op_arr = np.tile(op_arr[np.newaxis, :], [3, 1, 1])
chi_2_arr = np.tile(chi_2_arr[np.newaxis, :], [10, 1, 1])
chi_1_sum = np.sum(
np.sum(np.bitwise_and(chi_1_arr, self.chi_1_arr), axis=2), axis=1)
chi_2_sum = np.sum(
np.sum(np.bitwise_and(chi_2_arr, self.chi_2_arr), axis=2), axis=1)
op_sum = np.sum(
np.sum(np.bitwise_and(op_arr, self.op_arr), axis=2), axis=1)
op_sum[1] += 1 # 区分减号和加号
chi_3_sum = np.sum(
np.sum(np.bitwise_and(chi_3_arr, self.chi_3_arr), axis=2), axis=1)
num1 = chi_1_sum.argmax()
num2 = chi_2_sum.argmax()
op = op_sum.argmax()
num3 = chi_3_sum.argmax()
result = self._cal_result(num1, num2,num3, op)
print (result)
def test():
crack = PatentCrack('Patent.pkl')
crack.feed(os.path.join('86-0.jpeg'))
# fn_list = [fn for fn in os.listdir(u'../04_data/企业证书/cnca')]
# fn_list.sort()
# for fn in fn_list[:50]:
# crack.feed(os.path.join(u'../04_data/企业证书/cnca', fn))
if __name__=='__main__':
test()
五、总结
上面就是我对理论和代码的分析,如果想获得完整的工程请到我的github上下载(https://github.com/machine-lv/Verification-code-cracking)