2013-05-20

[PHP] aHash, pHash, dHash 實做

<?php
/**
 * 圖片特徵 Hash 計算
 *
 * @version     $Id: ImageHash.php 4429 2012-04-17 13:20:31Z jax $
 * @author      jax.hu
 *
 * <code>
 *  //Sample_1
 *  $hashA = ImageHash::pHash('001.jpg');
 *  $hashB = ImageHash::pHash('002.jpg');
 *  if(ImageHash::isSimilar($hashA, $hashB)){
 *
 *  }
 *
 * </code>
 */

class ImageHash {

    /**讀取圖片至指定大小*/
    public static function readImageTo($imagePath, $width, $height){
        if(!$imagePath || !file_exists($imagePath)){ return null; }

        if(class_exists('Imagick')){
            $image = new Imagick($imagePath);
            $image->thumbnailImage($width, $height);
            $img = imagecreatefromstring($image->getImageBlob());
            $image->destroy(); $image = null;

        }else{
            $createFunc = array(
                IMAGETYPE_GIF   =>'imageCreateFromGIF',
                IMAGETYPE_JPEG  =>'imageCreateFromJPEG',
                IMAGETYPE_PNG   =>'imageCreateFromPNG',
                IMAGETYPE_BMP   =>'imageCreateFromBMP',
                IMAGETYPE_WBMP  =>'imageCreateFromWBMP',
                IMAGETYPE_XBM   =>'imageCreateFromXBM',
            );

            $type = exif_imagetype($imagePath);
            if(!array_key_exists($type, $createFunc)){ return null; }

            $func = $createFunc[$type];
            if(!function_exists($func)){ return null; }

            $src = $func($imagePath);
            $img = imageCreateTrueColor($width, $height);
            imageCopyResized(
                $img, $src, 
                0,0,0,0, 
                $width, $height, imagesX($src),imagesY($src)
            );
            imagedestroy($src);
        }

        return $img;
    }



    /**取得灰階數值*/
    public static function getGray($img,$x,$y){
        $col = imagecolorsforindex($img, imagecolorat($img,$x,$y));
        return intval($col['red']*0.3 + $col['green']*0.59 + $col['blue']*0.11);
    }



    /**取得 DCT 常數*/
    private static $_dctConst = null;
    public static function getDctConst(){
        if(self::$_dctConst){ return self::$_dctConst;}

        self::$_dctConst = array();
        for ($dctP=0; $dctP<8; $dctP++) {
            for ($p=0;$p<32;$p++) {
                self::$_dctConst[$dctP][$p] = 
                    cos( ((2*$p + 1)/64) * $dctP * pi() );
            }
        }

        return self::$_dctConst;
    }



    /**圖片檔案 aHash
     * @param string $filePath 檔案位址路徑
     * @return string 圖片 hash 值,失敗則是 null
     * */
    public static function aHash($imagePath){
        $img = self::readImageTo($imagePath, 8, 8);
        if(!$img){ return null; }

        $graySum = 0;
        $grays = array();
        for ($y=0; $y<8; $y++){
            for ($x=0; $x<8; $x++){
                $gray = self::getGray($img,$x,$y);
                $grays[] = $gray;
                $graySum +=  $gray;
            }
        }
        imagedestroy($img);

        /*計算所有像素的灰階平均值*/
        $average = $graySum/64;

        /*計算 hash 值*/
        foreach ($grays as $i => $gray){
            $grays[$i] = ($gray>=$average) ? '1' : '0';
        }

        return join('',$grays);
    }




    /**圖片檔案 pHash
     * @param string $filePath 檔案位址路徑
     * @return string 圖片 hash 值,失敗則是 null
     * */
    public static function pHash($imagePath){
        $img = self::readImageTo($imagePath, 32, 32);
        if(!$img){ return null; }

        /*取得灰階數值 32*32*/
        $grays = array();
        for ($y=0; $y<32; $y++){
            for ($x=0; $x<32; $x++){
                $grays[$y][$x] = self::getGray($img,$x,$y);
            }
        }
        imagedestroy($img);


        /*計算 DCT 8*8*/
        $dctConst = self::getDctConst();
        $dctSum = 0;
        $dcts = array();
        for ($dctY=0; $dctY<8; $dctY++) {
            for ($dctX=0; $dctX<8; $dctX++) {

                $sum = 1;
                for ($y=0;$y<32;$y++) {
                    for ($x=0;$x<32;$x++) {
                        $sum += 
                            $dctConst[$dctY][$y] * 
                            $dctConst[$dctX][$x] * 
                            $grays[$y][$x];
                    }
                }

                /*apply coefficients*/
                $sum *= .25;
                if ($dctY == 0 || $dctX == 0) {
                    $sum *= 1/sqrt(2);
                }

                $dcts[] = $sum;
                $dctSum +=  $sum;
            }
        }

        /*計算所有像素的灰階平均值*/
        $average = $dctSum/64;

        /*計算 hash 值*/
        foreach ($dcts as $i => $dct){
            $dcts[$i] = ($dct>=$average) ? '1' : '0';
        }

        return join('',$dcts);
    }



    /**圖片檔案 dHash
     * @param string $filePath 檔案位址路徑
     * @return string 圖片 hash 值,失敗則是 null
     * */
    public static function dHash($imagePath){
        $img = self::readImageTo($imagePath, 9, 8);
        if(!$img){ return null; }

        $grays = array();
        for ($y=0; $y<8; $y++){
            for ($x=0; $x<9; $x++){
                $grays[$y][$x] = $gray = self::getGray($img,$x,$y);
            }
        }
        imagedestroy($img);

        $bitStr = array();
        for ($y=0; $y<8; $y++){
            for ($x=0; $x<8; $x++){
                $bitStr[] = ($grays[$y][$x] < $grays[$y][$x+1]) ? '1' : '0';
            }
        }

        return join('',$bitStr);
    }



   /**比較兩個 hash 值,是不是相似
    * @param string $aHash A圖片的 hash 值
    * @param string $bHash B圖片的 hash 值
    * @return bool 當圖片相似則回傳 true,否則是 false
    * */
   public static function isSimilar($hashStrA, $hashStrB){
       $aL = strlen($hashStrA); $bL = strlen($hashStrB);
       if ($aL !== $bL){ return false; }

       /*計算兩個 hash 值的漢明距離*/
       $distance = 0;
       for($i=0; $i<$aL; $i++){
           if ($hashStrA{$i} !== $hashStrB{$i}){ $distance++; }
       }

       return ($distance <= 10) ? true : false;
   }

}

3 回應:

Lucien 提到...

厲害的人...我是路過的,也是本行資訊人,您的文章很不錯,我會加RSS的

胡忠晞 提到...

感謝支持!

匿名 提到...

博主,您好,这些hash可以做在另一个图像中包含给定的图像吗