PHP简易版图片相似度比较(GD)

在项目当中实现已有图库的以图搜图功能时看到的代码,贴在这里以备后用。图片相似度比较和以图搜图还是有一点区别的,稍微注意区分一下。

下面是建议版的代码,顺便说一句,grafika 这个库也支持将两张图进行相似度比对,使用 compare 方法即可,官方文档:https://kosinix.github.io/grafika/editor/compare.html

下面是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
<?php
/**
* PHP实现的简易版图片相似度比较
* http://www.php.cn/php-notebook-167522.html
*/

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);
}
}