php识别验证码(一)

想做一个针对联通优选在沃自动签到的小程序,但是登陆需要验证码,找了一下现有的一些博客或者是开源的验证码识别的代码,没有一个比较满意的,so,自己写了一个。

需要识别的验证码

第一种:

code.png

第二种

1211862590-56a5bd94d822e.gif

分析

像上图的验证码一般来说的话,我们需要识别的是 4 个数字,但是验证码为了防止自动识别程序添加了许多的干扰项,例如背景色雪花干扰线等等

去除背景

我们通过分析该验证码图片可以知道,数字的 rgb 值一般处在 180~190 以下,而背景色和雪花的 rgb 值一般处在 200 以上,so,我们只要在处理图片的时候只取 190 以下的 rgb 值保存就 ok 了,这样就可以去除掉绝大部分的干扰项

二值化

把干扰信息去除之后,只留下二进制(也就是 0 和 1 表示)的点阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 二值化,排除背景色,雪花等干扰项
* @author mohuishou<1@lailin.xyz>
*/
public function imageHash(){
for($i = 0; $i < $this->_image_h; $i++) {
for ($j = 0; $j < $this->_image_w; $j++) {
$rgb = imagecolorat($this->_in_img,$j,$i);
$rgb_array = imagecolorsforindex($this->_in_img, $rgb);
if($rgb_array['red']<190&&$rgb_array['green']<190&&$rgb_array['blue']<190){

$data[$i][$j]=1;
}else{

$data[$i][$j]=0;
}
}

}

}

点阵化之后的截图

QQ截图20160503213904.png

去噪点

图像二值化之后可能还存在很多噪点,噪点的特点一般是孤立无援的,这时候我们只需要判断以这个 1 点为中心的点,周围的 1 点个数小于一个阈值(我把它设为 3),就确认它为噪点并将之去除

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
/**
* 去除噪点
* @author mohuishou<1@lailin.xyz>
* @param $hash_data
* @return mixed
*/
public function removeHotSpots($hash_data){
for($i = 0; $i < $this->_image_h; $i++) {
for ($j = 0; $j < $this->_image_w; $j++) {
if($hash_data[$i][$j]){
if($this->isHotSpots($i,$j,$hash_data)) $hash_data[$i][$j]=0;
}
}
}
return $hash_data;
}

/**
* 判断是否是噪点
* @author mohuishou<1@lailin.xyz>
* @param $i
* @param $j
* @param $hash_data
* @return bool ture:是噪点,false:不是
*/
public function isHotSpots($i,$j,$hash_data){
if($i == 0 || $j == 0 || $i == ($this->_image_h - 1) || $j == ($this->_image_w - 1)) return true;


//待检查点为中心的九个点
$points[0]=$hash_data[$i-1][$j-1];
$points[1]=$hash_data[$i-1][$j];
$points[2]=$hash_data[$i-1][$j+1];
$points[3]=$hash_data[$i][$j-1];
$points[4]=$hash_data[$i][$j];//待检查点
$points[5]=$hash_data[$i][$j+1];
$points[6]=$hash_data[$i+1][$j-1];
$points[7]=$hash_data[$i+1][$j];
$points[8]=$hash_data[$i+1][$j+1];

$count=0;

foreach ($points as $v){
if($v){
$count++;
}
}

return $count<4;
}

去除噪点之后

QQ截图20160503214315.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
/**
* @author mohuishou<1@lailin.xyz>
* @param $n
* @return array
*/
public function splitImage($n){
$data=[];
$a=$this->_image_w/self::CHAR_NUM;
for($i=$n*$a;$i<($n+1)*$a;$i++){
$column=array_column($this->_hash_data,$i);
if(implode("",$column)!=0){
$data[]=$column;
}
}

$out_img_w=count($data)+4;
$out_img_h=count($data[0])+4;

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

//一列一列的进行画图
foreach ($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+2,$key+2, $c);
}
}

return $out_img;
}

保存&对比识别

最后将分割好的图片二值化,通过一个数组保存下来,作为一个标准,然后后之后需要验证的验证码,通过和之前的数组求交集,比较最相近的数字得出结果即可

更多

其实到这一步之后已经能够识别一些验证码了,对于第二种验证码已经可以达到很好的识别效果,但是对于第一种验证码的识别率很低很低,大概只有5%-15%,基本不能投入使用,下一篇文章针对这种验证码,怎么更好的识别,主要是标准化特征库的建立