使用svm算法实现验证码识别

 

//如果加载不出来代码高亮插件请刷新一遍


一.流程

  1. 下载验证码图片
  2. 图片预处理
  3. 图片字符切割
  4. 字符分类标记
  5. 生成特征和标记对应的训练数据集
  6. 训练特征生成数据识别模型
  7. 使用识别模型识别未知验证码

1.1下载验证码图片

这里用来被识别验证码是直接在网上找的。算不上难也不算简单,使用php生成,带有噪点和其他干扰元素。

<?php
$img = imagecreatetruecolor(60, 30);
$black = imagecolorallocate($img, 0x00, 0x00, 0x00);
$green = imagecolorallocate($img, 0x00, 0xFF, 0x00);
$white = imagecolorallocate($img, 0xFF, 0xFF, 0xFF);
imagefill($img,0,0,$white);
//生成随机的验证码
$code = '';
for($i = 0; $i < 4; $i++) {  //4位数的验证码
$code .= rand(0, 9);
}
imagestring($img, 5, 10, 10, $code, $black);
//加入噪点干扰
for($i=0;$i<50;$i++) {
imagesetpixel($img, rand(0, 100) , rand(0, 100) , $black);  //imagesetpixel — 画一个单一像素,语法: bool imagesetpixel ( resource $image , int $x , int $y , int $color )
//imagesetpixel($img, rand(0, 100) , rand(0, 100) , $green);
}
//输出验证码
header("content-type: image/png");
imagepng($img);  //以 PNG 格式将图像输出到浏览器或文件
imagedestroy($img);  //图像处理完成后,使用 imagedestroy() 指令销毁图像资源以释放内存,虽然该函数不是必须的,但使用它是一个好习惯。


?>

运行生成的验证码是这样的:

使用request模块下载验证码,并以数字递增保存到/image/目录下

def downloadImg(t,p):
  #下载验证码
  for i in range(t,p):
    data = get('http://192.168.1.106/code.php').content
    with open('./image/%s.png'%str(i),'wb') as f:
      f.write(data)

 

1.2 图片预处理

因为减少后面训练的复杂度同时增加识别率,所以要对图片进行预处理,让机器更好的识别出来。

首先需要循环读取/image/目录图片,将彩色图片二值化黑白图片。

def get_bin_table(threshold=140):
  #二值化处理
  table = []
  for i in range(256):
    if i < threshold:
      table.append(0)
    else:
      table.append(1)
  return table

二值化分割,为了节省时间,就没有做除噪点的步骤了,验证码噪点不是很多,不去除并无大碍。

for o in range(t,p):
    I = Image.open('./image/%s.png'%(str(o)))
    L = I.convert('L') #灰度处理
    table = get_bin_table() #二值化分割
    out = L.point(table,'1')

1.3 图片字符切割

图片二值化完以后就要对图片进行字符分割了。

svm并不能像cnn一样,直接都识别出来,要识别验证码,需先把验证码的四个字符分割出来然后再去以此识别字符,最后拼接识别的结果。

关于分割的算法,没有很通用的方式。要仔细研究验证码的特点量身定制。

每个字符的像素占9个像素,第一个字符出现在第十个像素,所以第二个是在第19个像素,以此类推。

def valueImage(t,p):
  #根据字符分割图片
  # o 为遍历未处理的图片名
  # i 为处理后图片的分割字符位置文件名
  name = 0
  x = 10 #左
  y = 13 #上
  w = 8 #右
  h = 10 #下
  for o in range(t,p):
    I = Image.open('./image/%s.png'%(str(o)))
    L = I.convert('L')
    L = L.convert('L') #灰度处理
    table = get_bin_table() #二值化分割
    out = L.point(table,'1')
    region = out.crop((x, y, x+w, y+h))
    region.save("./fximage/%s_1.png"%(o))
    for i in range(2,5):
      x = x+9
      table = get_bin_table()
      out = L.point(table,'1')
      region = out.crop((x,y,x+w,y+h))
      region.save('./fximage/%s_%s.png'%(o,i))
    x = 10

这个函数的作用是循环/image/文件夹中的图片做图片预处理,之后根据每个字符的占位做分割。生成的图片存入/fximage/目录下

 

1.4 图片分类标记

机器一开始是不认识数字的,用切割完成的图片进行分类标记来告诉机器什么是1什么是2

具体流程:

  1. 为0~9每个数字建立一个目录,目录名称为相应数字(相当于标签),手动将分割好的1-9拖入相对应的目录中

  2. 每个目录放一百张左右相应标记的图片

没错,这是个体力活

 

1.5 生成特征和对应的训练数据集

不同的数字图片的本质就是将黑色按照一定规则填充在相应的像素点上。所以我们只需要统计每行每列的黑色像素点来得到特征。

 

def get_feature(img):
  #遍历分割后的图片生成向量特征
  width, height = img.size
  pixel_cnt_list = []
  height = 10
  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

def Vecfeature(t):
  #将向量特征处理为svm格式
  for o in range(t,t+1):
    for i in range(1,5):
      I = Image.open('./fximage/%s_%s.png'%(str(o),str(i)))
      I = get_feature(I)
      print str(randint(1,9)),
      for py in range(len(I)):
        if py == 0:
          I[py] = str(1)+':'+str(I[py])
          print(I[py]),
        else:
          I[py] = str(py+1)+':'+str(I[py])
          print(I[py]),

      print ''
Vecfeature(2)

着两个函数的作用是遍历/fximage/的图片然后来用get_feature函数统计黑点,再用svm的格式表现出来

运行结果:

0 1:2 2:3 3:4 4:4 5:4 6:4 7:8 8:2 9:2 10:2 11:2 12:3 13:3 14:3 15:3 16:10 17:10 18:1 
0 1:4 2:4 3:3 4:2 5:5 6:5 7:4 8:4 9:4 10:4 11:6 12:8 13:5 14:3 15:3 16:6 17:6 18:2 
0 1:2 2:3 3:4 4:2 5:2 6:2 7:2 8:2 9:2 10:6 11:0 12:2 13:3 14:10 15:10 16:1 17:1 18:0 
0 1:8 2:2 3:2 4:2 5:2 6:2 7:2 8:2 9:2 10:2 11:3 12:4 13:3 14:3 15:3 16:3 17:4 18:3

生成的特征集格式解析:

  1. 第一列为标记列,为人工标记值。后续会有0~9
  2. 第二列是特征值,前面是索引后面是只
  3. 训练多少次生成多少记录

这也是个体力活,因为要一次生成0~9的特征集

1. 6 训练特征集生成数据识别模型

到这一步就很简单了,直接调svm库,按照文档上的格式写就OK了

#!/use/bin/python
# -*- coding:utf-8 -*-
from svmutil import *

def train_svm_model():
  y, x = svm_read_problem('C:/Users/4dmin55/Desktop/aicode/newtest.txt') #特征集
  model = svm_train(y, x)
  svm_save_model('C:/Users/4dmin55/Desktop/rest_1.txt', model) #训练模型

if __name__ == '__main__':
  train_svm_model()

1.7 训练特征集生成数据识别模型

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from svmutil import *

def svm_model_test():
    """
    使用测试集测试模型
    :return:
    """
    yt, xt = svm_read_problem('newtest.txt')
    model = svm_load_model('rest_1.txt')
    p_label, p_acc, p_val = svm_predict(yt, xt, model)       # p_label即为识别的结果

    for item in range(len(p_label)):
        if item == 0:
            print '验证码为:',
        print '%d ' % p_label[item],

if __name__ == '__main__':
    svm_model_test()

用训练好的特征集来识别未知的验证码。

 

1.8 参考链接

http://www.cnblogs.com/Finley/p/5329417.html

https://www.csie.ntu.edu.tw/~cjlin/libsvm/

https://www.cnblogs.com/beer/p/5672678.html

1.9 总结

这个验证码算是上古时期的验证码了,以前日南方的时候见到过这种的验证码。其实难一点的验证码也能识别的。

可以这样说,只要是图形验证码验证的,基本都可以用机器学习来识别。只不过在于越难的步骤越繁琐罢了。

(其实这种验证码拿cnn来做应该更快捷)

发表评论

电子邮件地址不会被公开。 必填项已用*标注

Are you human? Click the Pineapple...