PHP验证码识别实例

PHP验证码识别实例,识别的过程包括对图像的二值化、降噪、补偿、切割、倾斜矫正、建库、匹配,最后会提供实例代码,能够直接运行识别。

简述

要识别的验证码相对比较简单,没有粘连字符,但是会有几种不同程度的字体加粗,以及大约0-30度的倾斜,还有字符的个数会在4-5个之间变化,相对来说还是使用Python进行验证码识别比较简单,如果有需要可以参考文章
强智教务系统验证码识别 OpenCV
强智教务系统验证码识别 Tensorflow CNN

二值化

图像都是由各个像素点组成,每个像素点可以量化成为rgb三种颜色值,根据验证码的颜色,调整三种颜色的阈值,将背景与字符过滤出来,背景置1,字符置0

// 二值化
    private static function binaryImage($image){
        $img = [];
        for($y = 0;$y < self::$width;$y++) {
            for($x =0;$x < self::$height;$x++) {
                if($y === 0 || $x === 0 || $y === self::$width - 1 || $x === self::$height - 1){
                    $img[$x][$y] = 1;
                    continue;
                }
                $rgb = imagecolorat($image, $y, $x);
                $rgb = imagecolorsforindex($image, $rgb);
                if($rgb['red'] < 255 && $rgb['green'] < 230 && $rgb['blue'] < 220) {
                    $img[$x][$y] = 0;
                } else {
                    $img[$x][$y] = 1;
                }
            }
        }
        return $img;
    }
1111111111111111111111111111011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111100000000011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111110000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111110000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111100000011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111100100111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111111111111111111111111111111111111111111111111111111100111111111111111111111111111111111111111111111111000111111111111111111111111111111111111111111100000011111111111111111111111111111111111111
1111111111111111000000000000001111111111111111111111111111110000000011111111100000001111111111111111111111000000000000111111111111111111111111111111111111110000000000011111111111111111111111111111111111000000000000001111111111111111111111111111111111
1111111111111111100000100000001111111111111111111111111111110000000111111111000000001111111111111111111110000000000000001111111111111111111111111111111111100000000000000111111111111111111111111111111110000000000000000111111111111111111111111111111111
1111111111111111100000000000001111111111111111111111111111110000000111111111000000001111111111111111111100000000000000000111111111111111111111111111111110000000000000000011111111111111111111111111111100000000000000000001111111111111111111111111111111
1111111111111111100000000011101111111111111111111111111111110000100111111111000000011111111111111111111000000111110000000111111111111111111111111111111110000000000000000001111111111111111111111111111000000000000000000001111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111100000100111111111000000011111111111111111111000011111111100000111111111111111111111111111111100000000000000000000111111111111111111111111111000000000010000000000111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111100000101111111111000000011111111111111111110000011111111110000011111111111111111111111111111000000001111110000000111111111111111111111111110000000111111110000000111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111100000001111111111000000011111111111111111110000011111111110000011111111111111111111111111111001000011111110000000011111111111111111111111110000000111111110000000011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111100000001111111110000000011111111111111111110000011111111111111111111111111111111111111111110000000011111111000000011111111111111111111111110000000111111111001111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000001111111110000000111111111111111111111000001111111111111111111111111111111111111111110000000111111111000000011111111111111111111111110000000000111111111111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000011111111110000000111111111111111111111000000011111111111111111111111111111111111111110100000111111111100000001111111111111111111111111000000000001111111111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000011111111110000000111111111111111111111000000000001111111111111111111111111111111111100000000001010111100000001111111111111111111111111000000000000000011111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000011111111100000000111111111111111111111100000000000001111111111111111111111111111111100000000000000000000000001111111111111111111111111000000000000000000011111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000011111111100000001111111111111111111111110000000001000001111111111111111111111111111100000000000010110000011001111111111111111111111111100000000000000000000111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000011111111100000001111111111111111111111111100000000000000011111111111111111111111111100000000000000010000110001111111111111111111111111110000000000000110000111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111110000000011111111100000001111111111111111111111111111100000000000011111111111111111111111111110000000000000000000000001111111111111111111111111111100000000000000000011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111110000000111111111000000001111111111111111111111111111111110000000001111111111111111111111111110000000111111111111111111111111111111111111111111111111110000000000000011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111110000000111111111000000001111111111111111111111111111111111110000001111111111111111111111111110000000111111111111111111111111111111111111111111111111111110000000000011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111100000001111111111000000001111111111111111111111110111111111111000001111111111111111111111111110000000111111111011111111111111111111111111111111111101111111111000000001111111111111111111111111111
1111111111111111111100000111111111111111111111111111111100000001111111110000000011111111111111111111110000111111111111100001111111111111111111111111110000000011111111000001011111111111111111111111100000000111111111001000011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111100000001111111100000000011111111111111111111110000011111111111100001111111111111111111111111111000000011111110000000011111111111111111111111100000000111111111001000011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111100000000111111000000000011111111111111111111110000011111111111000001111111111111111111111111111000000001111110000000011111111111111111111111100000000011111111000100011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111100000000001000000000000111111111111111111111111000001111111110000011111111111111111111111111111000000000000000000000111111111111111111111111110000000000000000000000111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111110000000000000000000000111111111111111111111111000000001111100000011111111111111111111111111111100000100000000000000111111111111111111111111111000000000000000000000111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111110000000000000011000100111111111111111111111111100000000000000000011111111111111111111111111111110000000000000000001111111111111111111111111111100000000000000000001111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000000000110000000111111111111111111111111110001000010000001111111111111111111111111111111111100000000000000011111111111111111111111111111110000000000000000011111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111100000000001110000000111111111111111111111111111000000000000011111111111111111111111111111111111110000000000001111111111111111111111111111111111000000000000001111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111000011111111111111111111111111111111111111111111100000111111111111111111111111111111111111111111111000011111111111111111111111111111111111111111110000111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111

降噪 补偿

验证码经常会加入一些噪点,这些噪点一般都是单独的点,有时候会加入几个单像素点连成干扰线,降噪的时候就需要去掉噪点与干扰线,我采用了将每个像素点周围四个像素点的值取出,计算如果周围四个像素点有两个以上是背景,也就是1的话,那么就认为这个是噪点,将其设为背景,也就是1
当二值化的时候,不可避免的会将字符中一些小像素点过滤成了背景,此时就需要补偿这个字符,也是同样采用将周围四个字符进行统计,如果周围四个像素点有两个以上都是字符,也就是0,那么就认为这个像素点也是字符像素点,将其设为字符,也就是0

// 降噪 补偿
    private static function noiseReduce($img) {
        $xCount = count($img[0]);
        $yCount = count($img); 
        for ($i=1; $i < $yCount-1 ; $i++) { 
            for ($k=1; $k < $xCount-1; $k++) { 
                if($img[$i][$k] === 0){
                    $countOne = $img[$i][$k-1] + $img[$i][$k+1] + $img[$i+1][$k] + $img[$i-1][$k];
                    if($countOne > 2) $img[$i][$k] = 1;
                } 
                if($img[$i][$k] === 1){
                    $countZero = $img[$i][$k-1] + $img[$i][$k+1] + $img[$i+1][$k] + $img[$i-1][$k];
                    if($countZero < 2) $img[$i][$k] = 0;
                } 
            }
        }
        return $img;
    }
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111100000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111110000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111110000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111100000011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111111111111111111111111111111111111111111111111111111100111111111111111111111111111111111111111111111111000111111111111111111111111111111111111111111100000011111111111111111111111111111111111111
1111111111111111100000000000001111111111111111111111111111110000000111111111100000001111111111111111111111000000000000111111111111111111111111111111111111110000000000011111111111111111111111111111111111000000000000001111111111111111111111111111111111
1111111111111111100000000000001111111111111111111111111111110000000111111111000000001111111111111111111110000000000000001111111111111111111111111111111111100000000000000111111111111111111111111111111110000000000000000111111111111111111111111111111111
1111111111111111100000000000001111111111111111111111111111110000000111111111000000001111111111111111111100000000000000000111111111111111111111111111111110000000000000000011111111111111111111111111111100000000000000000001111111111111111111111111111111
1111111111111111100000000011111111111111111111111111111111110000000111111111000000011111111111111111111000000111110000000111111111111111111111111111111110000000000000000001111111111111111111111111111000000000000000000001111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111100000000111111111000000011111111111111111111000011111111100000111111111111111111111111111111100000000000000000000111111111111111111111111111000000000000000000000111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111100000001111111111000000011111111111111111110000011111111110000011111111111111111111111111111000000001111110000000111111111111111111111111110000000111111110000000111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111100000001111111111000000011111111111111111110000011111111110000011111111111111111111111111111000000011111110000000011111111111111111111111110000000111111110000000111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111100000001111111110000000011111111111111111110000011111111111111111111111111111111111111111110000000011111111000000011111111111111111111111110000000111111111001111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000001111111110000000111111111111111111111000001111111111111111111111111111111111111111110000000111111111000000011111111111111111111111110000000000111111111111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000011111111110000000111111111111111111111000000011111111111111111111111111111111111111110000000111111111100000001111111111111111111111111000000000001111111111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000011111111110000000111111111111111111111000000000001111111111111111111111111111111111100000000000000111100000001111111111111111111111111000000000000000011111111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000011111111100000000111111111111111111111100000000000001111111111111111111111111111111100000000000000000000000001111111111111111111111111000000000000000000011111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000011111111100000001111111111111111111111110000000000000001111111111111111111111111111100000000000000000000010001111111111111111111111111100000000000000000000111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000011111111100000001111111111111111111111111100000000000000011111111111111111111111111100000000000000000000000001111111111111111111111111110000000000000000000111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111110000000011111111100000001111111111111111111111111111100000000000011111111111111111111111111110000000000000000000000001111111111111111111111111111100000000000000000011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111110000000111111111000000001111111111111111111111111111111110000000001111111111111111111111111110000000111111111111111111111111111111111111111111111111110000000000000011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111110000000111111111000000001111111111111111111111111111111111110000001111111111111111111111111110000000111111111111111111111111111111111111111111111111111110000000000011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111100000001111111111000000001111111111111111111111111111111111111000001111111111111111111111111110000000111111111111111111111111111111111111111111111111111111111000000011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111100000001111111110000000011111111111111111111110000111111111111100001111111111111111111111111110000000011111111000000011111111111111111111111100000000111111111000000011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111100000001111111100000000011111111111111111111110000011111111111100001111111111111111111111111111000000011111110000000011111111111111111111111100000000111111111000000011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111100000000111111000000000011111111111111111111110000011111111111000001111111111111111111111111111000000001111110000000011111111111111111111111100000000011111111000000011111111111111111111111111111
1111111111111111111100000111111111111111111111111111111100000000000000000000000111111111111111111111111000001111111110000011111111111111111111111111111000000000000000000000111111111111111111111111110000000000000000000000111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111110000000000000000000000111111111111111111111111000000001111100000011111111111111111111111111111100000000000000000000111111111111111111111111111000000000000000000000111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111110000000000000010000000111111111111111111111111100000000000000000011111111111111111111111111111110000000000000000001111111111111111111111111111100000000000000000001111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111000000000000110000000111111111111111111111111110000000000000001111111111111111111111111111111111100000000000000011111111111111111111111111111110000000000000000011111111111111111111111111111111
1111111111111111111100000111111111111111111111111111111111100000000001110000000111111111111111111111111111000000000000011111111111111111111111111111111111110000000000001111111111111111111111111111111111000000000000001111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111000011111111111111111111111111111111111111111111100000111111111111111111111111111111111111111111111000011111111111111111111111111111111111111111110000111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111

切割

由于此验证码并没有粘连,对于字符的切割相对而言比较简单,纵向统计出切割字符的起始与终止位置,切割后分别置入数组中,然后将横向的空白去除,同样也是统计字符有0值的起始行与终止行,再做切割,仅保留字符。

// 裁剪
    private static function cutImg($img){
        $xCount = count($img[0]);
        $yCount = count($img);
        $xFilter = [];
        for($x = 0;$x < $xCount;$x++) {
            $filter = true;
            for($y = 0;$y < $yCount;$y++)  $filter = $filter && ($img[$y][$x] === 1);
            if($filter) $xFilter[] = $x;
        }
        $xImage = array_values(array_diff(range(0, $xCount-1), $xFilter));
        $wordImage = [];
        $preX = $xImage[0] - 1;
        $wordCount = 0;
        foreach($xImage as $xKey => $x) {
            if($x != ($preX + 1))  $wordCount++;
            $preX = $x;
            for($y = 0;$y < $yCount;$y++) $wordImage[$wordCount][$y][] = $img[$y][$x];
        }
        $cutImg = [];
        foreach($wordImage as $i => $image) {
            $xCount = count($image[0]);
            $yCount = count($image);
            $start = 0;
            for ($j=0; $j < $yCount; ++$j) { 
                $stopFlag = false;
                for ($k=0; $k < $xCount; ++$k) { 
                    if ($image[$j][$k] === 0) {
                        $start = $j;
                        $stopFlag = true;
                        break;
                    }
                }
                if($stopFlag) break;
            }
            $stop = $yCount-1;
            for ($j=$yCount-1; $j >= 0; --$j) { 
                $stopFlag = false;
                for ($k=0; $k < $xCount; ++$k) { 
                    if ($image[$j][$k] === 0) {
                        $stop = $j;
                        $stopFlag = true;
                        break;
                    }
                }
                if($stopFlag) break;
            }
            for ($k=$start; $k <= $stop ; ++$k) { 
                $cutImg[$i][] = $image[$k];
            }
            // self::showImg($cutImg[$i]);
            $cutImg[$i] = self::adjustImg($cutImg[$i]);
            // self::showImg($cutImg[$i]);
        }
        return $cutImg;
    }
1111111111111000001111111
1111111100000000000001111
1111111000000000000000011
1111110000000000000000011
1111100000000000000000001
1111000000001111000000001
1110000000011111100000000
1110000000111111110000000
1111111111111111110000000
1111111111111111110000000
1111111111111111100000001
1111111100000000000000001
1111100000000000000000001
1110000000000000000000001
1100000000000000000000001
1000000000000111100000011
1000000001111111000000011
1000000011111111000000011
0000000111111111000000011
0000000111111110000000111
0000000111111100000000111
0000000011111000000000111
1000000001100000000000111
1000000000000000000000111
1000000000000000000000111
1100000000000010000000111
1111000000001110000000111
1111100001111111111111111

倾斜矫正

对于倾斜矫正我尝试了两种方案,一个是使用线性回归,另一个是使用投影法。

线性回归

使用线性回归,取得每一行上字符像素点的中点的坐标,使用最小二乘法拟合曲线,得到一个斜率,也就相当于得到了这个字符的倾斜角度,然后根据斜率来矫正这个字符的倾斜度,这个方式对于n这样的字符效果比较不错,但是对于j这样的字符效果就比较差。

$img = [
    [1,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1],
    [1,1,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1],
    [1,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0],
    [1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0],
    [1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0],
    [1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0],
    [1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0],
    [1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0],
    [1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0],
    [1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0],
    [1,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0],
    [1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1],
    [1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1],
    [1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1],
    [1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1],
    [1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1],
    [1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1],
    [1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1],
    [1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1],
    [1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1],
    [1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1],
    [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1],
    [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1],
    [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1],
    [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1],
];
ImgIdenfy::showImg($img);
$mixX = 0.0;
$mixY = 0.0;
$mixXX = 0.0;
$mixXY = 0.0;
$yCount = count($img);
$xCount = count($img[0]);
foreach($img as $i => $line) {
    $x = 0;
    $xValidCount = 0;
    foreach($line as $k => $unit) {
        if($unit === 0) {
            $x += $k;
            ++$xValidCount;
        }
    }
    if($xValidCount) {
        $pointX = $x/$xValidCount;
        $pointY = $yCount - $i;
        $mixX += $pointX;
        $mixY += $pointY;
        $mixXX += ($pointX*$pointX);
        $mixXY += ($pointX*$pointY);
    }
}
$linearK = -($mixXY - $mixX*$mixY/$yCount) / ($mixXX - $mixX*$mixX/$yCount);
// if($linearK > -1 && $linearK < 1) return $img;
$whirlImg = [];
foreach($img as $i => $line) {
    $pointY = $i;
    if(!isset($whirlImg[$pointY])) $whirlImg[$pointY]=[];
    foreach($line as $pointX => $unit) {
        if(!isset($whirlImg[$pointY][$pointX])) $whirlImg[$pointY][$pointX]=1;
        // $newY = (int)($pointY*sqrt(1+$linearK*$linearK)/$linearK);
        $newY = (int)($pointY);
        $newX = (int)($pointX-$pointY/$linearK);
        if($newX >= 0 && $newX < $xCount && $newY >= 0 && $newY < $yCount) $whirlImg[$newY][$newX] = $unit;
    }
}

$finishedImg = [];
for ($i=0; $i < $xCount; ++$i) { 
    for($k=0; $k < $yCount; ++$k) {
        if($whirlImg[$k][$i] !== 1){
            for($y = 0;$y < $yCount;++$y) $finishedImg[$y][] = $whirlImg[$y][$i];
            break;
        }
    }
}
ImgIdenfy::showImg($finishedImg);
111110000111100000000011
111110000111000000000001
111100001110000000000000
111100000000000111100000
111100000000111111110000
111100000001111111110000
111100000011111111110000
111000000111111111110000
111000000111111111110000
111000001111111111110000
111000001111111111100000
111000011111111111100000
110000011111111111100001
110000011111111111000001
110000111111111111000001
110000111111111111000001
110000111111111111000001
100000111111111111000011
100000111111111111000011
100000111111111110000011
100001111111111110000011
100001111111111110000011
000011111111111110000111
000011111111111110000111
000011111111111100000111
000011111111111100000111

10000111100000000011
10000111000000000001
00001110000000000000
00000000000111100000
00000000111111110000
10000000111111111000
10000001111111111000
00000011111111111000
00000011111111111000
00000111111111111000
10000011111111111000
10000111111111111000
00000111111111111000
00000111111111110000
00001111111111110000
10000111111111111000
10000111111111111000
00000111111111111000
00000111111111111000
00000111111111110000
10000111111111111000
10000111111111111000
00001111111111111000
00001111111111111000
00001111111111110000
10000111111111111000

投影法

由于直接线性拟合的方式对于一些字符的效果比较差,于是采用投影法的方式,字符如果进行旋转,那么他的宽度势必会增加,于是可以在一定范围内尝试旋转字符,取得旋转过程中宽度最小时的字符,就是矫正后的字符。由于直接将竖直的字符根据斜率旋转的话,因为tan90°不存在,不好界定逆时针旋转的范围,于是首先将字符数组进行转置,然后就可以在斜率-0.5-0.5的范围内顺时针旋转,然后再将其转置回即可,我在实现的过程中有比较多的重复运算,这个主要是需要数学推算,而我是一步步实现的计算,还有就是旋转的过程中如果字符宽度由小到大变化的时候就可以逆向运算或者停止运算了,就像一个梯度下降的方式,此外我并没有使用矩阵方式的运算,如果使用矩阵的话实现会比较简单,PHP中有PHP-ML这样的机器学习库,其中就有矩阵运算方面的方法,当然也可以直接使用PHP-ML进行神经网络的训练。

// 旋转
    private static function whirl($img, $yCount, $xCount, $linearK){
        $whirlImg = [];
        foreach($img as $i => $line) {
            $pointY = $yCount - $i - 1;
            if(!isset($whirlImg[$pointY])) $whirlImg[$pointY]=[];
            foreach($line as $pointX => $unit) {
                if(!isset($whirlImg[$pointY][$pointX])) $whirlImg[$pointY][$pointX]=1;
                $newY = (int)($pointY - $pointX*$linearK);
                $newX = (int)($pointX);
                if($unit === 0 && ($newY < 0 || $newY >= $yCount)) return [$yCount+1, $img];
                if($newX >= 0 && $newX < $xCount && $newY >= 0 && $newY < $yCount) $whirlImg[$newY][$newX] = $unit;
            }
        }
        $cutImg = [];
        $height = $yCount;
        foreach ($whirlImg as $j => $line) {
            foreach ($line as $k => $v) {
                if($v !== 1) {
                    --$height;
                    break;
                }
            }
        }
        return [$yCount - $height, $whirlImg];
    }

    // 倾斜调整
    private static function adjustImg($img){
        $reverseImg = [];
        $yCount = count($img);
        $xCount = count($img[0]);
        for ($i=0; $i < $yCount; ++$i) { 
            $pointY = $yCount - $i - 1;
            for($k=0; $k < $xCount; ++$k) {
                $reverseImg[$k][$i] = $img[$pointY][$k];
            }
        }
        list($yCount,$xCount) = [$xCount,$yCount];
        $min = $yCount;
        $minImg = $reverseImg;
        for ($k= -0.5 ; $k <= 0.5; $k = $k + 0.05) { 
            list($tempMin, $tempMinImg) = self::whirl($reverseImg, $yCount, $xCount, $k);
            if($tempMin < $min) {
                $min = $tempMin;
                $minImg = $tempMinImg;
            }
        }
        $removedImg = [];
        foreach ($minImg as $j => $line) {
            foreach ($line as $k => $v) {
                if($v !== 1) {
                    $removedImg[] = $line;
                    break;
                }
            }
        }
        $reverseImg = [];
        $xCount = count($removedImg[0]);
        $yCount = count($removedImg);
        $reverseImg = [];
        for ($i=0; $i < $xCount; ++$i) { 
            for($k=0; $k < $yCount; ++$k) {
                $pointX = $xCount - $i - 1;
                $reverseImg[$i][$k] = $removedImg[$k][$pointX];
            }
        }
        return $reverseImg;
    }
1111111111111000001111111
1111111100000000000001111
1111111000000000000000011
1111110000000000000000011
1111100000000000000000001
1111000000001111000000001
1110000000011111100000000
1110000000111111110000000
1111111111111111110000000
1111111111111111110000000
1111111111111111100000001
1111111100000000000000001
1111100000000000000000001
1110000000000000000000001
1100000000000000000000001
1000000000000111100000011
1000000001111111000000011
1000000011111111000000011
0000000111111111000000011
0000000111111110000000111
0000000111111100000000111
0000000011111000000000111
1000000001100000000000111
1000000000000000000000111
1000000000000000000000111
1100000000000010000000111
1111000000001110000000111
1111100001111111111111111

111111111110000011111111
111111000000000000011111
111110000000000000000111
111100000000000000000111
111000000000000000000011
110000000011110000000011
100000000111111000000001
100000001111111100000001
111111111111111110000000
111111111111111110000000
111111111111111100000001
111111100000000000000001
111100000000000000000001
110000000000000000000001
100000000000000000000001
000000000000111100000011
000000001111111000000011
000000011111111000000011
000000011111111100000001
000000011111111000000011
000000011111110000000011
000000001111100000000011
100000000110000000000011
100000000000000000000011
100000000000000000000011
110000000000001000000011
111100000000111000000011
111110000111111111111111

建库

将验证码矫正过后,就需要建立特征匹配库了,这里我直接使用了将二值化的数组转化为字符串全部作为特征写入一个特征匹配数组,再手动打码,若是识别出的字符与我手动打码的字符不符,就将其加入特征匹配数组,然后将字符数组序列化存储到文件中,然后将这个序列化后的字符串进行压缩,存储到文件中,我提取的特征数组有150个字符特征码,占用约8KB,注意我这是将PHP作为脚本使用的,配置好环境变量写入空数据后再使用php Build.php即可开始提取特征码。

// 写入空序列化数组
// $info = serialize([]);
// $library = fopen("library", "w+");
// fwrite($library,gzcompress($info));
// fclose($library);

$library = fopen("library", "r+");
$info = fread($library,filesize("library"));
if(!$info) $charMap = [];
else $charMap = unserialize(gzuncompress($info));
while (1) {
    $img = imagecreatefromjpeg("http://grdms.sdust.edu.cn:8081/security/jcaptcha.jpg"); //获取图片
    imagejpeg($img,"v.jpg"); // 写入硬盘
    list($result, $imgStringArr) = ImgIdenfy::build($img, $charMap, 250, 100);
    echo($result."\n");
    $input = fgets(STDIN);
    if(isset($input[0]) && $input[0] === "$") break;
    $n = strlen($input) - 2;
    for ($i=0; $i < $n; $i++) {
        if(!isset($result[$i]) || $input[$i] !== $result[$i]) $charMap[$input[$i].mt_rand(1, 10000)] = $imgStringArr[$i];
    }
    echo count($charMap)."\n";
    ftruncate($library,0);
    rewind($library);
    fwrite($library,gzcompress(serialize($charMap)));
}
fclose($library);

匹配

由于是直接将全部的特征信息存入文件,直接使用循环对比字符串的值即可,为了提高准确率,我将两个对比字符串的第一个0进行对齐,然后再进行遍历,取得相同字符的数量,此外由于对比的字符串的长度不同,将字符串的长度信息乘以一定权值也作为一部分信息计入相似度中,当然PHP中提供了similar_text函数进行字符串相似度对比,使用此函数的话识别率会提升,但是由于字符串长度过长,对比匹配的时间比较慢,权衡时间消耗与正确率还是选择了自行匹配的方式。

// 对比
    private static function comparedText($s1,$s2){
        $s1N = strlen($s1);
        $s2N = strlen($s2);
        $i = -1;
        $k = -1;
        $percent = -abs($s1N - $s2N) * 0.1;
        while(++$i<$s1N && $s1[$i]) {}
        while(++$k<$s2N && $s2[$k]) {}
        while ($i<$s1N && $k<$s2N) ($s1[$i++] === $s2[$k++]) ? $percent++ : "";
        return $percent;
        // $percent = 0;
        // $N = $s1N < $s2N ? $s1N : $s2N;
        // for ($i=0; $i < $N; ++$i) { 
        //     ($s1[$i] === $s2[$i]) ? $percent++ : "";
        // }
        // return $percent;
    }

    // 匹配
    private static function matchCode($imgGroup,$charMap){
        $record = "";
        $imgStringArr = [];
        foreach ($imgGroup as $img) {
            $maxMatch = 0;
            $tempRecord = "";
            $s = ImgIdenfy::getString($img);
            foreach ($charMap as $key => $value) {
                // similar_text(ImgIdenfy::getString($img),$value,$percent);
                $percent = self::comparedText($s , $value);
                if($percent > $maxMatch){
                    $maxMatch = $percent;
                    $tempRecord = $key[0];
                }
            }
            $record = $record.$tempRecord;
            $imgStringArr[] = $s;
        }
        return [$record, $imgStringArr];
    }

实例代码

如果觉得不错,点个star吧 😃 https://github.com/WindrunnerMax/Example