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

需要识别的验证码

第一种:

code.png

第二种

1211862590-56a5bd94d822e.gif

分析

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

去除背景

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

二值化

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

/**
 * 二值化,排除背景色,雪花等干扰项
 * @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),就确认它为噪点并将之去除

/**
 * 去除噪点
 * @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

去除干扰线

通过去除噪点之后的图像,我们可以发现,大部分的干扰线,其实已经被去除掉了,所以我们可以将,去除噪点的方法当做一个滤镜,多过滤几次,干扰线基本上可以去除完毕(这里测试去除三次的效果是最好的)

分割验证码

这个验证码的每个数字其实都是等分的,所以我们可以采用等分的方法去分割它

/**
 * @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%,基本不能投入使用,下一篇文章针对这种验证码,怎么更好的识别,主要是标准化特征库的建立