本章将要介绍一下如何识别简单的验证码。会涉及到一些图像的概念以及机器学习的知识。python
咱们本次识别的验证码来自csdn,长相以下: git
在学习以前,咱们先安装本章须要的三个库:图像库Pillow、机器学习库Scikit-Learn、科学计算库Numpy。经过pip命令就能够进行安装。github
pip install pillow scikit-learn numpy
复制代码
本章节的案例稍微复杂,见:USTBCrawlers/lesson8算法
这里主要有三个部分:下载器、分割器、与识别器。咱们能够先把代码clone下来,而后进入到lesson8这个目录下。数组
下面咱们先来对图像有个基本的介绍。图像是由一个一个像素点构成的,其内部结构是一个二维的矩阵,或者理解成一个二维数组。bash
例如,一个 M x N 的图像,能够表示成如下的格式: 网络
在python中,咱们可使用PIL(Pillow)对图像进行操做。以下,咱们打开了咱们的验证码,并调用convert("L")
方法把图片转为灰度图像。app
from PIL import Image
im = Image.open("csdn.png").convert("L")
im.show() # 显示图像
复制代码
然而咱们真正操做并非图像对象,而是一个矩阵,或者说是二维数组,咱们能够把图像转成numpy数组。 less
能够看到咱们的验证码是20*48的。机器学习
图像是由一个个的像素构成的,像素有灰度值,从0-255,一共256个灰度级。直方图的做用是观察每一个灰度级所占像素的多少。
能够调用Image.histogram()
获取Image对象的直方图。
好比说对于咱们的验证码图片,一共有20*48=960个像素点,其中灰度级为94的像素有754个,而灰度级为255的有129个。
再次观察一下咱们灰度化的验证码,能够看到验证码的字母是白色的,也就是灰度级为255。周围的背景是灰色的,灰度级为94。
在处理验证码的时候,背景不少时候并非同一个灰度级的,为了减小背景对数据的影响。通常都会讲验证码进行二值化。
所谓二值化,其实就是把灰度图像变成只由纯黑、或纯白两种像素组成的图像。
方法很简单,咱们能够设定一个阈值,灰度大于100的像素都变成纯白(255),而灰度小于100的像素都变成纯黑(0)。
由于验证码包含四个数字,因此须要把每一个字母分割开。笔者为读者准备的验证码是很好分割的类型,只须要对指定区域进行筛选便可。
这里的图片数字的宽度都是8,起点分别位于五、1四、2三、32。分割代码以下:
def split_and_save(path):
path = "../downloader/captchas/" + path
pix = np.array(Image.open(path).convert("L"))
# threshold image
pix = (pix > 100) * 255
col_ranges = [
[5, 5 + 8],
[14, 14 + 8],
[23, 23 + 8],
[32, 32 + 8]
]
# split and save
for col_range in col_ranges:
letter = pix[:, col_range[0]: col_range[1]]
im = Image.fromarray(np.uint8(letter))
save_path = "./letters/" + str(uuid.uuid4()) + ".png"
im.save(save_path)
复制代码
咱们每一个验证码字符的大小为:20*8。
对验证码分割后,咱们就会获得一堆字母的图片了。
可是这些图片都没有标注,下面咱们使用的机器学习算法是数据驱动的,因此须要一些已经标注好的验证码数据。我这里标注的方法比较简单,由于毕竟只有0-9十种字母,我就每种数据标注6个。直接经过文件名称进行标识。
K近邻算法的定义十分简单,在百度百科上有这样的解释:若是一个样本在特征空间中的k个最类似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。
也就是说,须要找到要识别的字母在训练样本中K个最近的字母,而后找出这K个字母中最多的是某个类的?要识别的图片也就是该类的。
上面那么描述可能稍微有点儿晦涩,那咱们举个例子。
这里以电影分类做为例子,电影题材可分为爱情片,动做片等。这里假定将电影分为爱情片和动做片两类,直观感觉的话,若是一部电影中接吻镜头不少,打斗镜头较少,显然是属于爱情片,反之为动做片。
这里咱们的数据有两个特征:一个是接吻镜头的数目,一个是打斗镜头的数目。下面咱们有一组已知的数据。
咱们这里的目标是利用已知的四个电影数据,预测未知电影的类型。
咱们把爱情电影定义为红色的叉子,动做电影定义为绿色的圆圈,未知电影为问号。这里能够画个图直观感觉一下。
假设咱们K取3的话,那么从图中能够很清晰的看到,离未知电影最近的三个电影分别是:爱情电影、爱情电影、动做电影。爱情电影占比大,因此咱们未知的电影是爱情片。
咱们将使用scikit-learn来实现KNN,因此不须要关注算法的实现(虽然实现也很简单),只要有数据和标签就行了。咱们来看看怎么实现上面的预测电影类型的功能。
这里X是咱们已知类型的四部电影,y是四部电影的标签。0表明爱情电影,1表明动做电影。而后调用scikit-learn中的KNeighborsClassifier
先经过fit
拟合数据,再调用predict
预测就行了。
能够看到咱们最后预测出未知电影的标签为0,也就是爱情电影,和想法一致。
了解了以上知识后,咱们能够编写验证码识别脚本了。
咱们这里首先编写加载数据的函数,加载以前标注好的验证码字母数据。咱们验证码字母数据是20*8的,也就是至关于有160个特征。
def load_dataset():
X = []
y = []
for i in range(60):
path = "./dataset/%d%d.png" % (i / 6, i % 6 + 1)
pix = np.array(Image.open(path).convert("L"))
X.append(pix.reshape(8*20))
y.append(i/6)
return np.array(X), np.array(y)
复制代码
而后对数据进行拟合:
X, y = load_dataset()
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X, y.astype('uint8'))
复制代码
最后先分割图片,再使用拟合好数据的knn进行预测。
def split_letters(path):
pix = np.array(Image.open(path).convert("L"))
# threshold image
pix = (pix > 100) * 255
col_ranges = [
[5, 5 + 8],
[14, 14 + 8],
[23, 23 + 8],
[32, 32 + 8]
]
letters = []
for col_range in col_ranges:
letter = pix[:, col_range[0]: col_range[1]]
letters.append(letter.reshape(8*20))
return letters
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python recognizer.py <image_filename>")
letters = split_letters(sys.argv[1])
print(knn.predict(letters))
复制代码
咱们运行一下,能够看到如下识别的结果,都识别出来了。
以上的验证码识别只是一个基本的操做流程。如今只要有足够多的数据,利用深度学习基本上全部的验证码都能识别出来。
深度学习因为须要读者有数学基础以及相关的背景知识,这里笔者就提供一些我本身写过的验证码相关的资料,若是感兴趣能够本身去学习。
若是读者感兴趣能够进行深刻学习。
笔者的验证码相关的一个项目:
笔者的验证码相关的博客: