首页 PHP 正文
283

PHP识别简单的验证码

  • yiqingpeng
  • 2015-05-17
  • 0
  •  
闲暇之余,试着用PHP写了一个识别简单验证码的程序,本程序对无干扰的验证码识别比较有效。
class verifiCodeEyes{
    private $imgFilePath;
    private $tempDir;
    private $textOfImg;
    private $allowedImgExt = array('jpeg', 'png', 'jpg', 'bmp');
    private $imgExt;
    private $validRect;
    private $fazhi;//阀值
    private $imgData;
    private $charsArray;
    private $charsStringify;
    private $keysTable;
    private $text;
    const KEY_DB_PATH = 'key_db.bat';
    
    
    public function __construct($imgFilePath){
        if(!file_exists($imgFilePath)) $this->printError('图片文件不存在!');
        $ext = $this->getFileExt($imgFilePath);
        if(!$this->isAllowedImgExt($ext)) $this->printError('图片类型不合法!');
        if(!is_readable($imgFilePath)) $this->printError('图片文件没有读取权限!');
        $this->imgExt = $ext;
        $this->imgFilePath = $imgFilePath;
        $this->tempDir = sys_get_temp_dir();
        $this->imgData = array();
        $this->fazhi = array();//r,g,b值
        $this->charsArray = array();
        $this->charsStringify = array();
        $this->keysTable = array();
        $this->text = '';
        $this->loadKeyDb();
    }
    
    public function parseImgData(){
        //检查有没有设置阀值
        if(!isset($this->fazhi)) die('请设置阈值!');
        
        if(empty($this->validRect)){
            $imgSize = getimagesize($this->imgFilePath);
            $s_x=0;
            $s_y=0;
            $e_x=$imgSize[0];
            $e_y=$imgSize[1];
        }else{
            $s_x=$this->validReclc_0;
            $s_y=$this->validReclc_1;
            $e_x=$this->validReclc_2;
            $e_y=$this->validReclc_3;
        }
        
        //对图像进行二值化处理
        $imgRes = $this->getImageRes();
        if($imgRes===false) die('图片资源加载失败');
        $fz_red = $this->fazhi[0];
        $fz_green = $this->fazhi[1];
        $fz_blue = $this->fazhi[2];
        $row=0;
        $column=0;
        for($i=$s_y; $i<$e_y; $i++,$row++){
            for($j=$s_x,$column=0; $j<$e_x; $j++,$column++){
                //获得当前像素点颜色索引值
                $colorIndex = imagecolorat($imgRes, $j, $i);
                //通过颜色索引值获得颜色的RGB数组表示形式
                $colorRgb = imagecolorsforindex($imgRes, $colorIndex);
                if($colorRgb['red']>$fz_red && $colorRgb['green']>$fz_green && $colorRgb['blue']>$fz_blue){
                    $this->imgData[$row][$column] = 0;//背景区域设置为0
                }else{
                    $this->imgData[$row][$column] = 1;//前景设置为1
                }     
            }
        }
        //print_r($this->imgData);
        
        //去除上边、下边的背景
        $tmpData = array();
        foreach($this->imgData as $rowData){
            if(array_sum($rowData)>0){
                $tmpData[] = $rowData;
            } 
        }
        
        //分隔出单个字符
        //首先进行行列转置
        $revArray = array();
        foreach($tmpData as $r => $row){
            foreach($row as $c => $column){
                $revArray[$c][$r] = $column;
            }
        }
	
        //对转置后的数组进行字符分隔
        $charsHeap = array();
        foreach($revArray as $row){
            if(array_sum($row)==0){
                if(isset($t) && is_array($t) && !empty($t)){
                    $charsHeap[] = $t;
                    unset($t);
                }
                $t = array();
                continue;
            }
            $t[] = $row;
        }
	if(!empty($t)) $charsHeap[] = $t;
	unset($t);
        //对charsHeap中的每个字符数据进行行列转置,还原成正常序列
	$i=0;
        foreach($charsHeap as $charData){
            $temp = array();
            foreach($charData as $r=>$row){
                foreach($row as $c=>$column){
                    $temp[$c][$r] = $column; 
                }
            }
            foreach($temp as $r => $row){
                $temp[$r] = implode('', $row);
            }
            $this->charsArray[$i] =  $temp;
            $this->charsStringify[$i] = implode(',', $temp);
            unset($temp);
			$i ++;
        }
	
    }
    
    public function getText(){
        $this->parseImgData();//将图片上的数据结构化
	$this->text = '';
        foreach($this->charsStringify as $charData){
            $similar = 0.0;
            $char = '#';
            foreach($this->keysTable as $key => $strValue){
                $percent = 0.0;
                similar_text($strValue, $charData, $percent);
                if($percent>$similar) {$similar = $percent;$char = $key;}
            }
            $this->text .= $char;
        }
        return $this->text;
    }
    
    public function echoText(){
        echo $this->text;
    }
    
    public function train(){//此方法是用来训练字符库的
        $file = fopen(self::KEY_DB_PATH, 'ab');
        foreach($this->charsStringify as $keyStr){
            fwrite($file, '#='.$keyStr.PHP_EOL);
        }
        fclose($file);
    }
    
    private function loadKeyDb(){//加载字符库
        if(!file_exists(self::KEY_DB_PATH)) return false;
        $file = fopen(self::KEY_DB_PATH,'r');
        while(!feof($file)){
            $line = fgets($file);
            $t = explode('=', $line);
            $this->keysTable[$lc_0] = $lc_1;
        }
        //print_r($this->keysTable);
        fclose($file);
    }
    
    private function getImageRes(){
        switch($this->imgExt){
            case 'jpeg':
            case 'jpg':
                $res = imagecreatefromjpeg($this->imgFilePath);break;
            case 'png':
                $res = imagecreatefrompng($this->imgFilePath);break;
            case 'bmp':
                $res = imagecreatefromwbmp($this->imgFilePath);break;
            case 'gif':
                $res = imagecreatefromgif($this->imgFilePath);break;
            default:
                return false;
        }
        //imagefilter ($res, IMG_FILTER_GRAYSCALE);
        //imagefilter ($res, IMG_FILTER_EDGEDETECT);
        //将图像进行灰度计算,再进行黑白处理,以方便图像二值化
        if (imageistruecolor($res)){
            imagetruecolortopalette($res, false, 256);//如果是真彩色图象,将真彩色图像转换为调色板图像
        }
        for ($i = 0; $i < imagecolorstotal($res);/*获得调色板中颜色的数目*/ $i++){
            $rgb = imagecolorsforindex($res, $i);//获得颜色i点的颜色值    
            $gray = round(0.229 * $rgb['red'] + 0.587 * $rgb['green'] + 0.114 * $rgb['blue']);//获得颜色灰度值 
            //imagecolorset($res, $i, $gray, $gray, $gray);//设置i点颜色值,图像将变成灰度图,灰度图可以方便判断背景和前景
            if($gray<128){//如果灰度值小于128,我们就认为是有效图像,将图像该点像素颜色设置为黑色
                imagecolorset($res, $i, 0, 0, 0);
            }else{
                imagecolorset($res, $i, 255, 255, 255);//将像素点设置为白色
            } 
        }
        return $res;
    }
    
    private function isAllowedImgExt($ext){
        if(!in_array($ext, $this->allowedImgExt)) return false;
        return true;
    }
    
    public function setTempDir($path){
        if(!file_exists($path)) $this->printError('指定的临时目录不存在');
        if(!is_dir($path)) $this->printError('指定的目录不合法');
        if(!is_writable($path)) $this->printError('指定的目录没有写的权限!');
        $this->tempDir = $path;
    }
    
    public function setValidRect($rect){
        if(!is_array($rect)){
            $this->validRect = explode(',', $rect);
        }else{
            $this->validRect = $rect;
        }
    }
    
    public function setFazhi($fazhi){
        if(!is_array($fazhi)){
            $this->fazhi = explode(',', $fazhi);
        }else{
            $this->fazhi = $fazhi;
        }
    }
    
    public function getFileExt($file){
        $lastDotPos = strrpos($file, '.');
        if($lastDotPos===false){
            return 'unknown';
        }
        
        return substr($file, ++$lastDotPos);
    }
    
    public static function downloadImage($imgUrl, $imgType, $savePath=''){
        if(!self::checkDir($savePath)) self::printError('指定的目录不存在或者不可写!');
        $tmpFileName = date('YmdHis').'.'.ltrim($imgType,'.');
        $imgContent = file_get_contents($imgUrl);
        $filePath = rtrim($savePath.'/').'/'.$tmpFileName;
        $int = file_put_contents($filePath, $imgContent);
        if($int>0) return $filePath;
        return false; 
    }
    
    public static function checkDir($path){
        if(!file_exists($path)) return false;
        if(!is_dir($path)) return false;
        if(!is_writable($path)) return false;
        return true;
    }
    
    public function printError($msg){
        echo $msg;
        exit;
    }
    
    public function __destruct(){
        unset($this->imgData);
        unset($this->charsArray);
        unset($this->charsStringify);
        unset($this->keysTable);
    }
    
    public function printImg($data){
        $rowNum = count($data);
        foreach($data as $r=>$row){
            $r = substr(str_repeat(' ', $rowNum).$r,-1*strlen($rowNum-1));
            if(is_array($row)){
                echo $r.'=> '.implode('',$row)."\r\n";
            }else{
                echo $r.'=> '.$row."\r\n";
            }
            ob_flush();//释放缓冲区
	    flush();//输出到浏览器
        }
    }
    
}

$vc = new verifiCodeEyes('20150516161159.png');
$vc->setValidRect(array(1,0,75,18));
$vc->setFazhi(array(250,250,250));
$vc->parseImgData();
echo $vc->getText();

实际效果:
原始图片:

灰度处理之前的识别,可以看到杂点较多:

灰度处理之后的识别结果,明显清晰了许多:

正在加载评论...