2014年6月2日 星期一

[PHP] 簡易的圖片相似度比較

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<?php
/**
 * 圖片相似度比較
 *
 * @version     $Id: ImageHash.php 4429 2012-04-17 13:20:31Z jax $
 * @author      jax.hu
 *
 * <code>
 *  //Sample_1
 *  $aHash = ImageHash::hashImageFile('wsz.11.jpg');
 *  $bHash = ImageHash::hashImageFile('wsz.12.jpg');
 *  var_dump(ImageHash::isHashSimilar($aHash, $bHash));
 *
 *  //Sample_2
 *  var_dump(ImageHash::isImageFileSimilar('wsz.11.jpg', 'wsz.12.jpg'));
 * </code>
 */
 
class ImageHash {
 
    /**取樣倍率 1~10
     * @access public
     * @staticvar int
     * */
    public static $rate = 2;
 
    /**相似度允許值 0~64
     * @access public
     * @staticvar int
     * */
    public static $similarity = 80;
 
    /**圖片類型對應的開啟函數
     * @access private
     * @staticvar string
     * */
    private static $_createFunc = array(
        IMAGETYPE_GIF   =>'imageCreateFromGIF',
        IMAGETYPE_JPEG  =>'imageCreateFromJPEG',
        IMAGETYPE_PNG   =>'imageCreateFromPNG',
        IMAGETYPE_BMP   =>'imageCreateFromBMP',
        IMAGETYPE_WBMP  =>'imageCreateFromWBMP',
        IMAGETYPE_XBM   =>'imageCreateFromXBM',
    );
 
 
    /**從檔案建立圖片
     * @param string $filePath 檔案位址路徑
     * @return resource 當成功開啟圖片則回傳圖片 resource ID,失敗則是 false
     * */
    public static function createImage($filePath){
        if(!file_exists($filePath)){ return false; }
 
        /*判斷檔案類型是否可以開啟*/
        $type = exif_imagetype($filePath);
        if(!array_key_exists($type,self::$_createFunc)){ return false; }
 
        $func = self::$_createFunc[$type];
        if(!function_exists($func)){ return false; }
 
        return $func($filePath);
    }
 
 
    /**hash 圖片
     * @param resource $src 圖片 resource ID
     * @return string 圖片 hash 值,失敗則是 false
     * */
    public static function hashImage($src){
        if(!$src){ return false; }
 
        /*缩小圖片尺寸*/
        $delta = 8 * self::$rate;
        $img = imageCreateTrueColor($delta,$delta);
        imageCopyResized($img,$src, 0,0,0,0, $delta,$delta,imagesX($src),imagesY($src));
 
        /*計算圖片灰階值*/
        $grayArray = array();
        for ($y=0; $y<$delta; $y++){
            for ($x=0; $x<$delta; $x++){
                $rgb = imagecolorat($img,$x,$y);
                $col = imagecolorsforindex($img, $rgb);
                $gray = intval(($col['red']+$col['green']+$col['blue'])/3)& 0xFF;
 
                $grayArray[] = $gray;
            }
        }
        imagedestroy($img);
 
        /*計算所有像素的灰階平均值*/
        $average = array_sum($grayArray)/count($grayArray);
 
        /*計算 hash 值*/
        $hashStr = '';
        foreach ($grayArray as $gray){
            $hashStr .= ($gray>=$average) ? '1' : '0';
        }
 
        return $hashStr;
    }
 
 
    /**hash 圖片檔案
     * @param string $filePath 檔案位址路徑
     * @return string 圖片 hash 值,失敗則是 false
     * */
    public static function hashImageFile($filePath){
        $src = self::createImage($filePath);
        $hashStr = self::hashImage($src);
        imagedestroy($src);
 
        return $hashStr;
    }
 
 
    /**比較兩個 hash 值,是不是相似
     * @param string $aHash A圖片的 hash 值
     * @param string $bHash B圖片的 hash 值
     * @return bool 當圖片相似則回傳 true,否則是 false
     * */
    public static function isHashSimilar($aHash, $bHash){
        $aL = strlen($aHash); $bL = strlen($bHash);
        if ($aL !== $bL){ return false; }
 
        /*計算容許落差的數量*/
        $allowGap = $aL*(100-self::$similarity)/100;
 
        /*計算兩個 hash 值的漢明距離*/
        $distance = 0;
        for($i=0; $i<$aL; $i++){
            if ($aHash{$i} !== $bHash{$i}){ $distance++; }
        }
 
        return ($distance<=$allowGap) ? true : false;
    }
 
 
    /**比較兩個圖片檔案,是不是相似
     * @param string $aHash A圖片的路徑
     * @param string $bHash B圖片的路徑
     * @return bool 當圖片相似則回傳 true,否則是 false
     * */
    public static function isImageFileSimilar($aPath, $bPath){
        $aHash = ImageHash::hashImageFile($aPath);
        $bHash = ImageHash::hashImageFile($bPath);
        return ImageHash::isHashSimilar($aHash, $bHash);
    }
 
}