PHP识别图片格式手机号码

最近公司要求做爬虫,在获取手机号码时遇到了图片格式的手机号码。手机号码显示为图片目的不言而喻,就是为了防爬虫。想到两年前试过用PHP识别验证码,这图片格式的手机号码也比较简单,破解起来应该也不难。

如图,原网站上为BMP格式。于是我转换成PNG便于低版本PHP处理(o(╥﹏╥)o公司项目还在用PHP5.4)。这个图片格式手机号码比较规范,没有任何噪点,字体大小颜色都一致这就省事多了。

二值化

RGB色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。

首先获取图片大小,可以通过 getimagesize() 函数得到;

然后获取图片每一个像素的RGB信息;

上述代码如下:

$im = imagecreatefrompng($imagePaths);
$rgbArray = array();
$res = $im;
$size = getimagesize($imagePath);
$wid = $size['0'];
$hid = $size['1'];
for ($i = 0; $i < $hid; ++ $i) {
    for ($j = 0; $j < $wid; ++ $j) {
        $rgb = imagecolorat($res, $j, $i);
        $rgbArray[$i][$j] = imagecolorsforindex($res, $rgb);
    }
}

通过PS取色工具分析图片背景规律,当然这个图片比较简单,肉眼能看出是白色并几乎没有任何杂色

于是可以通过循环和判断把每个像素是背景色的用0表示,是数字像素的用1表示。将数据保存到二维数组里。当然为了方便理解先把是背景的像素用□显示,数字像素用■显示,这样就清晰多了;

代码如下:

$str = [];
for ($i = 0; $i < $hid; $i ++) {
    for ($j = 0; $j < $wid; $j ++) {
        if ($i >= 0 && $i < 10) {
            if ($j > 0 && $j < 88) {
                if ($rgbArray[$i][$j]['red'] >= 230 && $rgbArray[$i][$j]['green'] >= 230 && $rgbArray[$i][$j]['blue'] >= 230) {
                    $str[] = '0';
                    echo '□';
                } else {
                    $str[] = '1';
                    echo '■';
                }
            }
        }
    }
    echo "<br>";
}

切割数组

通过观察我们可以知道,每个数字宽5个像素,高8个像素,于是我们就可以把每个数字分割下来提取特征值。

$temp = array_chunk($str, 85);
$p_1 = $p_2 = $p_3 = $p_4 = $p_5 = $p_6 = $p_7 = $p_8 = $p_9 = $p_10 = $p_11 = "";
for ($i = 0; $i < 8; $i ++) {
    for ($j = 0; $j < 85; $j ++) {
        if ($j <= 4) {
            $p_1 .= $temp[$i][$j];
        }
        if ($j >= 8 && $j <= 12) {
            $p_2 .= $temp[$i][$j];
        }
        if ($j >= 16 && $j <= 20) {
            $p_3 .= $temp[$i][$j];
        }
        if ($j >= 24 && $j <= 28) {
            $p_4 .= $temp[$i][$j];
        }
        if ($j >= 32 && $j <= 36) {
            $p_5 .= $temp[$i][$j];
        }
        if ($j >= 40 && $j <= 44) {
            $p_6 .= $temp[$i][$j];
        }
        if ($j >= 48 && $j <= 52) {
            $p_7 .= $temp[$i][$j];
        }
        if ($j >= 56 && $j <= 60) {
            $p_8 .= $temp[$i][$j];
        }
        if ($j >= 64 && $j <= 68) {
            $p_9 .= $temp[$i][$j];
        }
        if ($j >= 72 && $j <= 76) {
            $p_10 .= $temp[$i][$j];
        }
        if ($j >= 80 && $j <= 84) {
            $p_11 .= $temp[$i][$j];
        }
    }
}
$arr = array($p_1 , $p_2 , $p_3 , $p_4 , $p_5 , $p_6 , $p_7 , $p_8 , $p_9 , $p_10 , $p_11);

此时$arr输出的结果为

array (size=11)
  0 => string '0000000000100000110000001000000010010001' (length=40)
  1 => string '0000000001000000100000010000001000000100' (length=40)
  2 => string '0000000000010000001000011000000000001000' (length=40)
  3 => string '0000000001100001010000101000100100000010' (length=40)
  4 => string '0000000001010000001000001000001100000100' (length=40)
  5 => string '0000000000100000000001111000100000000000' (length=40)
  6 => string '0000000001100001010000101000100100000010' (length=40)
  7 => string '0000000001010000001001000000011100000100' (length=40)
  8 => string '0000000001010000001000111000100100010010' (length=40)
  9 => string '0000000001010000001001000000010100000010' (length=40)
  10 => string '0000000000010000001000111000100100010010' (length=40)

这就是该图片每个数字的特征值,我们需要把图片中0~9的数字特征值都提取出来,所以你需要获取足够多的图片(数字包含0~9)重复上面的步骤以取得0~9的特征值。

匹配特征值

将字符串与每一个数字对应的特征值比较,求其相似度,取最高的相似度对应的数字,或者相似度达到95%以上就可以断定是某个数字。

//特征值
$number = array(
    0=>'0111010001100011000110001100011000101110',
    1=>'0010001100001000010000100001000010001110',
    2=>'0111010001100010001000100010001000011111',
    3=>'0111010001000010011000001000011000101110',
    4=>'0001000110010100101010010011110001000011',
    5=>'1111110000100001111000001000011000101110',
    6=>'0111010010100001111010001100011000101110',
    7=>'1111110010000100010000100001000010000100',
    8=>'0111010001100010111010001100011000101110',
    9=>'0111010001100011000101111000010100101111',
);

foreach($data as $numKey => $numString)
{
    $max=0.0;
    $num = 0;
    foreach($number as $key => $value)
    {
        $percent=0.0;
        similar_text($value, $numString,$percent);
        if(intval($percent) > $max)
        {
            $max = $percent;
            $num = $key;
            if(intval($percent) > 95)
                break;
        }
    }
    $result.=$num;
}
return $result;

得出的结果

因为这种图片比较简单,所以使用这种方法基本能100%正确识别。