改進的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次裏機器只能正確識別一次,這樣就可以了,因爲這東西只是防止機器發大量的垃圾信息使用,並不能防止人工操作。也就是說我自要把機器識別的效率降到人工效率就達到目的了。

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