PHP实现图片相似度比较的感知哈希算法示例
针对现有图片搜索 API 不符合轻量化需求的场景,我们可以基于感知哈希(Perceptual Hash)算法,利用 PHP 原生 GD 函数手写一个简单的图片相似度比较模块。
算法原理与准备
该方案的核心逻辑是将复杂的图像内容压缩为一个短小的二进制字符串(即“图像指纹”)。如果两张图片相似,它们的指纹也应该非常接近。具体操作遵循以下步骤:
- 缩放:将图片缩小至固定尺寸(如
8x8或32x32),忽略长宽比,保留整体轮廓与色彩分布。 - 灰度化:将缩小后的彩色图片转为灰度值,仅保留亮度信息。
- 计算均值:计算所有像素灰度值的平均数。
- 生成 Hash:将每个像素与平均值对比,高于平均值为
1,低于为0,拼接成哈希字符串。 - 比较距离:通过计算两个哈希字符串不同位的数量(汉明距离)来判断相似度。
第1步:定义配置参数
首先创建 ImageHash 类,并设置可调的全局参数:
$rate:取样倍率。基础尺寸为 8,最终缩放尺寸为8 * $rate正方形。$similarity:相似度阈值(0-100)。汉明距离允许的误差范围百分比。$_createFunc:支持的文件类型映射表,用于动态调用对应的图像加载函数。
class ImageHash {
public static $rate = 2; // 默认生成 16x16 的哈希特征
public static $similarity = 80;
private static $_createFunc = array(
IMAGETYPE_GIF => 'imageCreateFromGIF',
IMAGETYPE_JPEG => 'imageCreateFromJPEG',
IMAGETYPE_PNG => 'imageCreateFromPNG',
IMAGETYPE_BMP => 'imageCreateFromBMP',
IMAGETYPE_WBMP => 'imageCreateFromWBMP',
IMAGETYPE_XBM => 'imageCreateFromXBM',
);
}
第2步:实现图片资源加载
编写静态方法 createImage,负责校验文件存在性、检测格式并返回 GD 资源句柄。
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);
}
第3步:核心哈希计算逻辑
hashImage 方法接收图片资源,执行缩放、灰度提取和指纹生成。注意在结束后销毁临时 GD 图像对象以防止内存泄漏。
public static function hashImage($src) {
if (!$src) { return false; }
// 1. 缩略处理
$delta = 8 * self::$rate;
$img = imageCreateTrueColor($delta, $delta);
imageCopyResized($img, $src, 0, 0, 0, 0, $delta, $delta, imagesX($src), imagesY($src));
// 2. 计算灰阶数组
$grayArray = array();
for ($y=0; $y<$delta; $y++) {
for ($x=0; $x<$delta; $x++) {
$rgb = imagecolorat($img, $x, $y);
$col = imagecolorsforindex($img, $rgb);
// RGB 转灰度
$gray = intval(($col['red'] + $col['green'] + $col['blue']) / 3) & 0xFF;
$grayArray[] = $gray;
}
}
imagedestroy($img);
// 3. 计算平均灰度
$average = array_sum($grayArray) / count($grayArray);
// 4. 生成哈希字符串
$hashStr = '';
foreach ($grayArray as $gray) {
$hashStr .= ($gray >= $average) ? '1' : '0';
}
return $hashStr;
}
第4步:封装文件操作接口
提供便捷的静态方法直接操作文件路径。
public static function hashImageFile($filePath) {
$src = self::createImage($filePath);
if (!$src) return false;
$hashStr = self::hashImage($src);
imagedestroy($src);
return $hashStr;
}
public static function isImageFileSimilar($pathA, $pathB) {
$hashA = self::hashImageFile($pathA);
$hashB = self::hashImageFile($pathB);
return self::isHashSimilar($hashA, $hashB);
}
第5步:相似度比对(汉明距离)
比对两段二进制哈希字符串,统计不同字符的数量。若差值小于允许阈值,则判定为相似。
public static function isHashSimilar($aHash, $bHash) {
$len = strlen($aHash);
if ($len !== strlen($bHash)) { return false; }
// 计算允许的最大差异位数量
$maxDiff = $len * (100 - self::$similarity) / 100;
$distance = 0;
for ($i = 0; $i < $len; $i++) {
if ($aHash{$i} !== $bHash{$i}) {
$distance++;
}
}
return ($distance <= $maxDiff);
}
使用示例
以下代码演示了如何实例化该类并进行两张图片的相似度验证:
$aPath = 'test/a.jpg';
$bPath = 'test/b.jpg';
$cPath = 'test/c_different.jpg';
// 方式一:分别获取 Hash 后比对
$hashA = ImageHash::hashImageFile($aPath);
$hashB = ImageHash::hashImageFile($bPath);
echo 'A与B相似: ', ImageHash::isHashSimilar($hashA, $hashB) ? 'Yes' : 'No', PHP_EOL;
// 方式二:直接比对文件
echo 'A与C相似: ', ImageHash::isImageFileSimilar($aPath, $cPath) ? 'Yes' : 'No', PHP_EOL;
注意事项与局限性
- 抗干扰能力:该算法对光照变化和几何变换(如旋转、平移、轻微缩放)具有较强的鲁棒性,因为它依赖于直方图统计而非绝对像素位置。
- 不适用的场景:对于经过裁剪、严重模糊或色彩大幅修改的图片,效果可能不佳。
- 性能考量:对于海量图片比对,建议将生成的 Hash 存入数据库,并在应用层进行索引过滤。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









