改进的php验证码生成程序

上一篇文章说的验证码生成,最后得到的程序在使用中你会发现有个问题,就是生成的验证码有时会不好辨认。干扰线越多情况就越严重。如下图:

(5条干扰线的效果)

(10条干扰线的效果)

其中的第2个数字是1还是7,不太好辨认。我实验了一下生成的容易分辨的验证码大概在80% - 90%间。

如果去除干扰线效果就好很多,基本都能辨认出来。但是图片简单了容易让别人使用程序给识别出来。不是好办法。,再分析一下原因其实就是干扰点和干扰线实在画完数字后再画上的,这样就对原图片上的数字产生了遮挡。如果先生成干扰点和干扰线再画上图片的话效果应该会好很多。我猜想,这么实现呢?使用我上一篇的程序,调整一下语句的顺序让数字在干扰点线后生成,呵呵,这样显然不行,因为我是用产生的数字图片贴上背景图实现的,因此有个数字的背景问题,他的白色背景会覆盖原图。效果如下:

显然这结果不太好,有一种简单的方法就是放弃使用旋转而直接画出数字,那得出的效果就是规规矩矩的数字图片了,也使得使用程序辨认难度降低。也不太好。

其实要解决也不太难,只要将数字的白色背景加以扣除就行了。再使用带透明度的拷贝函数拷贝到背景图片上就可以了(就是imagecopymerge函数),于是就得到以下程序(完整的代码):


header("Pragma: no-cache");
header("Cache-Control: max-age=1, s-maxage=1, no-cache, must-revalidate");
session_start();
unset($_SESSION['validate']);

$randStr = array(rand(0, 9), rand(0, 9), rand(0, 9), rand(0, 9));  // 产生4个随机字符
$_SESSION['validate'] = $randStr[0].$randStr[1].$randStr[2].$randStr[3];

function RgbToHsv($R, $G, $B)
{
 $tmp = min($R, $G);
  $min = min($tmp, $B);
  $tmp = max($R, $G);
  $max = max($tmp, $B);
  $V = $max;
  $delta = $max - $min;

  if($max != 0)
   $S = $delta / $max; // s
  else
  {
   $S = 0;
    //$H = UNDEFINEDCOLOR;
    return;
  }
  if($R == $max)
   $H = ($G - $B) / $delta; // between yellow & magenta
  else if($G == $max)
    $H = 2 + ($B - $R) / $delta; // between cyan & yellow
  else
    $H = 4 + ($R - $G) / $delta; // between magenta & cyan

  $H *= 60; // degrees
  if($H < 0)
   $H += 360;
  return array($H, $S, $V);
}

function HsvToRgb($H, $S, $V)
{
 if($S == 0)
  {
   // achromatic (grey)
   $R = $G = $B = $V;
    return;
  }

  $H /= 60;  // sector 0 to 5
  $i = floor($H);
  $f = $H - $i;  // factorial part of h
  $p = $V * (1 - $S);
  $q = $V * (1 - $S * $f);
  $t = $V * (1 - $S * (1 - $f));

  switch($i)
  {
   case 0:
     $R = $V;
      $G = $t;
      $B = $p;
      break;
    case 1:
      $R = $q;
      $G = $V;
      $B = $p;
      break;
    case 2:
      $R = $p;
      $G = $V;
      $B = $t;
      break;
    case 3:
      $R = $p;
      $G = $q;
      $B = $V;
      break;
    case 4:
      $R = $t;
      $G = $p;
      $B = $V;
      break;
    default: // case 5:
      $R = $V;
      $G = $p;
      $B = $q;
      break;
 }
  return array($R, $G, $B);
}

$size = 20;
$width = 80;
$height = 25;
$degrees = array(rand(0, 45), rand(0, 45), rand(0, 45), rand(0, 45)); // 生成数字旋转角度

for($i = 0; $i < 4; ++$i)
{
 if(rand() % 2);
 else $degrees[$i] = -$degrees[$i];
}

$image = imagecreatetruecolor($size, $size);   // 数字图片画布
$validate = imagecreatetruecolor($width, $height);  // 最终验证码画布
$back = imagecolorallocate($image, 255, 255, 255);  // 背景色
$border = imagecolorallocate($image, 0, 0, 0);    // 边框
imagefilledrectangle($validate, 0, 0, $width, $height, $back); // 画出背景色

// 数字颜色
for($i = 0; $i < 4; ++$i)
{
 // 考虑为使字符容易看清使用颜色较暗的颜色
 $temp = RgbToHsv(rand(0, 255), rand(0, 255), rand(0, 255));
 
 if($temp[2] > 60)
  $temp [2] = 60;

 $temp = HsvToRgb($temp[0], $temp[1], $temp[2]);
 $textcolor[$i] = imagecolorallocate($image, $temp[0], $temp[1], $temp[2]);
}

for($i = 0; $i < 200; ++$i) //加入干扰象素
{
 $randpixelcolor = ImageColorallocate($validate, rand(0, 255), rand(0, 255), rand(0, 255));
 imagesetpixel($validate, rand(1, 87), rand(1, 27), $randpixelcolor);
}

// 干扰线使用颜色较明亮的颜色
$temp = RgbToHsv(rand(0, 255), rand(0, 255), rand(0, 255));

if($temp[2] < 200)
 $temp [2] = 255;
 
$temp = HsvToRgb($temp[0], $temp[1], $temp[2]);
$randlinecolor = imagecolorallocate($image, $temp[0], $temp[1], $temp[2]);

// 画5条干扰线
for ($i = 0;$i < 5; $i ++)
 imageline($validate, rand(1, 79), rand(1, 24), rand(1, 79), rand(1, 24), $randpixelcolor);

imagefilledrectangle($image, 0, 0, $size, $size, $back); // 画出背景色 
imagestring($image, 5, 6, 2, $randStr[0], $textcolor[0]);  // 画出数字
$image = imagerotate($image, $degrees[0], $back);
imagecolortransparent($image, $back);
imagecopymerge($validate, $image, 1, 4, 4, 5, imagesx($image) - 10, imagesy($image) - 10, 100);

$image = imagecreatetruecolor($size, $size); // 刷新画板
imagefilledrectangle($image, 0, 0, $size, $size, $back);  // 画出背景色 
imagestring($image, 5, 6, 2, $randStr[1], $textcolor[1]);  // 画出数字
$image = imagerotate($image, $degrees[1], $back);
imagecolortransparent($image, $back);
imagecopymerge($validate, $image, 21, 4, 4, 5, imagesx($image) - 10, imagesy($image) - 10, 100);

$image = imagecreatetruecolor($size, $size); // 刷新画板
imagefilledrectangle($image, 0, 0, $size - 1, $size - 1, $back);  // 画出背景色 
imagestring($image, 5, 6, 2, $randStr[2], $textcolor[2]);  // 画出数字
$image = imagerotate($image, $degrees[2], $back);
imagecolortransparent($image, $back);
imagecopymerge($validate, $image, 41, 4, 4, 5, imagesx($image) - 10, imagesy($image) - 10, 100);

$image = imagecreatetruecolor($size, $size); // 刷新画板
imagefilledrectangle($image, 0, 0, $size - 1, $size - 1, $back);  // 画出背景色 
imagestring($image, 5, 6, 2, $randStr[3], $textcolor[3]);  // 画出数字
$image = imagerotate($image, $degrees[3], $back);
imagecolortransparent($image, $back);
imagecopymerge($validate, $image, 61, 4, 4, 5, imagesx($image) - 10, imagesy($image) - 10, 100);

imagerectangle($validate, 0, 0, $width - 1, $height - 1, $border);  // 画出边框

header('Content-type: image/png');
imagepng($validate);
imagedestroy($validate);
imagedestroy($image);

?>

这个程序得到的效果还是比较理想的。你还可以多加几条干扰线也没问题,经实验生成的容易识别的验证码基本上能达到100%

运行后的效果如下:

(5条干扰线的效果)

(10条干扰线的效果)

你会发现以上两幅图很容易就能辨认出是7115和3179。

 

如果使用较粗的字体干扰素和干扰线可以使用任意颜色,而不用处理也能得到较好的效果,在不引起歧义的情况下(比如除去某些些数字)可以考虑加大数字的旋转角度,如果要使程序更难辩认可以不使用旋转,考虑其他算法。

 

另外ImageColorAt和imagesetpixel函数结合使用,还可以对点进行操作,比如将单色数字变花色数字,甚至可以进行正弦曲线Wave扭曲图片等操作。

 

还有要说明的就是使用机器识别这方法产生的验证码不是不可能的。而生成验证码的程序应该是满足使用最简单的办法达到比较好的效果就是是最佳选择,我并不要求生成的识别码,机器100%不能识别(包括recaptcha这玩意据说有俄罗斯的人用机器识别出来了,但识别率很低)。其实只要做到10000次里机器只能正确识别一次,这样就可以了,因为这东西只是防止机器发大量的垃圾信息使用,并不能防止人工操作。也就是说我自要把机器识别的效率降到人工效率就达到目的了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章