php识别验证码(二)

在 php 识别验证码(一)当中说了基本的识别验证码的方法,及其关键的代码,但是最后我们留下了一个问题,就是对于有旋转的验证码的识别率及其低下的问题,下面来解决这个问题。

标准化

解决类似于下面图片这种有旋转字符的验证码,我们首先要给出一种标准,让所有的分割后的图片都按照一个标准来摆放,最后对比的时候才能够统一

inImgTemp.png

如何标准化?

  • 我们对一个字符进行旋转,然后计算这个字符的宽度,当宽度最小时,我们认为它是一个标准字符,这样我们就能够得到比较好的结果了(通过观察这种验证码,发现一般只要左右旋转三十度即可)

  • 然后仅仅进行旋转还不够,为了能够和模板也就是特征值库进行对比,我们还需要,统一字符长度。也就是二值化之后的字符串长度,这时就需要画一张长宽固定的图片,把我们原来的图片按照比例拉伸压缩复制过去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
    /**
* 图像标准化,将旋转的图像标准化
* @author mohuishou<1@lailin.xyz>
* @param $img
* @return resource 标准的图像资源句柄
*/
public function imageStandard($img){
$min_w=999;
$oimg=$img;

$c=imagecolorallocate($img, 255, 255, 255);
for($i=-30;$i<30;$i++){
$simg=imagerotate($img,$i,$c);
// //计算字符宽度
$simg_hash_data=$this->getWidth($simg);
$w=count($simg_hash_data);
if($w<$min_w){
$oimg_hash_data=$simg_hash_data;
$min_w=$w;
}

}

$out_img_w=count($oimg_hash_data);
$out_img_h=count($oimg_hash_data[0]);

$out_img = imagecreatetruecolor($out_img_w,$out_img_h);//创建一幅真彩色图像
$bg=imagecolorallocate($out_img, 255, 255, 255);//背景色画为白色
imagefill($out_img, 0,0, $bg);

//一列一列的进行画图
foreach ($oimg_hash_data as $k=>$v){
foreach ($v as $key=> $val){
$color=255;
if($val) $color=0;
$c = imagecolorallocate($out_img, $color, $color, $color);
imagesetpixel($out_img, $k,$key, $c);
}
}

// imagepng($out_img,'./0.png');


$hash_img = imagecreatetruecolor(self::HASH_W, self::HASH_H);
imagecopyresized($hash_img, $out_img, 0, 0, 0, 0, self::HASH_W,self::HASH_H,$out_img_w,$out_img_h);


return $hash_img;

}

/**
* 获取图像的宽度
* @author mohuishou<1@lailin.xyz>
* @param $img 图像资源句柄
* @return int
*/
public function getWidth($img){
//根据资源句柄获取整个图像的高与宽
$img_w=imagesx($img);
$img_h=imagesy($img);

//图像二值化
for($i = 0; $i <$img_h; $i++) {
for ($j = 0; $j <$img_w; $j++) {
$rgb = imagecolorat($img,$j,$i);
if($rgb==0){
$data[$i][$j]=1;
}else{
$data[$i][$j]=0;
}
}
}

//去掉零行
$data=$this->removeZero($data);

//按列取图像获取宽度
for($i=0;$i<$img_w;$i++){
$column=array_column($data,$i);
if(implode("",$column)!=0){
$data1[]=$column;
}
}


//返回
return $data1;


}

看上方的代码发现会有一个去除零行的操作,也就是去除空白行,这一步操作的目的主要是为了出掉上下左右无用的空白部分,在我们进行模板对比的时候更加的精确

建立特征值库

之前提到了很多和模板或者是特征值库进行比较,那我们如何来建立这个库呢?下面说一下最简单的两种方法

  • 第一种,就是把你需要的数字可能还有 26 个字母,都先识别一遍然后存到一个文件当中,使用的时候包含这个文件进行对比就行了
  • 第二种,也差不多,可以写一个 study 文件,让上面的步骤稍微智能一点,直接输入之后,自动储存到数据库中

下面是我写的一个针对联通优选在沃的一个小的脚本,需要注意的是对于这种通过链接来识别的,一定要先将链接的图片保存下来,因为链接没获取一次图片就会改变一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
/**
* Created by mohuishou<1@lailin.xyz>.
* User: mohuishou<1@lailin.xyz>
* Date: 2016/5/1 0001
* Time: 20:44
*/
require_once 'Image.class.php';
require_once 'DB.class.php';
$db=new \ImageOCR\DB();
if(isset($_POST['send'])&&$_POST['send']=="send"){
$image=new \ImageOCR\Image("./img/inImgTemp.png");
$code=$_POST['code'];
$code_arr=str_split($code);

for($i=0;$i<$image::CHAR_NUM;$i++){
$hash_img_data=implode("",$image->splitImage($i));
$db->add($code_arr[$i],$hash_img_data);
}

echo "<script>location.href='./study.php';</script>";

}else{
$image=new \ImageOCR\Image("http://www.169ol.com/Mall/Code/getCode&1462104790492");
imagepng($image->_in_img,"./img/inImgTemp.png");
}

?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Study</title>
</head>
<body>
<form action="" method="post">
<img src="img/inImgTemp.png">
<input type="text" name="code">
<input name="send" type="submit" value="send" />
</form>
</body>
</html>

开源代码

觉得不错,给个 star 呗

感谢

常见验证码的弱点与验证码识别

结束语

目前的识别效率可以达到 50%以上,基本可以投入正常使用,但是还有很大的进步空间,正在做下一步的优化,针对识别结果对特征库进行自动优化,自动去除使用率较低的特征值,自动保存,识别成功的特征值等等