PHP如何通过RGB比对实现上传图与目录图片相似匹配
PHP实现相似图片匹配功能
你当前使用的MD5校验方案仅能识别文件字节完全一致的图片,无法匹配经过压缩、尺寸调整、轻微编辑的同内容图片。下面给出基于RGB值降维比对(平均哈希算法)的实现方案,遍历目录比对的性能远高于逐像素全量RGB比对,可直接替换原有逻辑。
注意:运行前请确认PHP环境已开启GD扩展,绝大多数PHP运行环境默认已开启该扩展。
实现逻辑
- 将待比对的两张图片统一缩放到8*8的固定小尺寸,消除分辨率、尺寸差异的干扰
- 将缩放后的图片转为灰度图,基于每个像素的RGB值计算对应的灰度值
- 计算64个像素的平均灰度值,每个像素灰度大于平均值记为1、小于记为0,生成64位长度的图片特征指纹
- 计算两个指纹的汉明距离(即取值不同的位的数量),距离越小代表图片相似度越高,通常距离小于5即可判定为内容高度相似
完整实现代码
PHP后端逻辑
<?php /** * 计算图片的特征指纹 * @param string $imgPath 图片路径 * @return string 64位二进制指纹 */ function getImageFingerprint(string $imgPath): string { // 获取图片基础信息 $imgInfo = getimagesize($imgPath); if (!$imgInfo) return ''; list($width, $height, $type) = $imgInfo; // 根据图片格式创建图像资源 switch ($type) { case IMAGETYPE_JPEG: $imgRes = imagecreatefromjpeg($imgPath); break; case IMAGETYPE_PNG: $imgRes = imagecreatefrompng($imgPath); break; case IMAGETYPE_GIF: $imgRes = imagecreatefromgif($imgPath); break; default: return ''; } // 缩放到8*8尺寸 $sampleSize = 8; $sampleRes = imagecreatetruecolor($sampleSize, $sampleSize); imagecopyresampled($sampleRes, $imgRes, 0, 0, 0, 0, $sampleSize, $sampleSize, $width, $height); // 计算所有像素的灰度值总和 $graySum = 0; $grayArr = []; for ($y = 0; $y < $sampleSize; $y++) { for ($x = 0; $x < $sampleSize; $x++) { // 获取像素RGB值 $rgb = imagecolorat($sampleRes, $x, $y); $r = ($rgb >> 16) & 0xFF; $g = ($rgb >> 8) & 0xFF; $b = $rgb & 0xFF; // 转灰度值 $gray = intval(0.299 * $r + 0.587 * $g + 0.114 * $b); $grayArr[$y][$x] = $gray; $graySum += $gray; } } // 计算平均灰度,生成指纹 $avgGray = $graySum / ($sampleSize * $sampleSize); $fingerprint = ''; for ($y = 0; $y < $sampleSize; $y++) { for ($x = 0; $x < $sampleSize; $x++) { $fingerprint .= $grayArr[$y][$x] >= $avgGray ? '1' : '0'; } } // 释放资源 imagedestroy($imgRes); imagedestroy($sampleRes); return $fingerprint; } /** * 计算两个指纹的汉明距离 * @param string $fp1 图片1指纹 * @param string $fp2 图片2指纹 * @return int 汉明距离,值越小相似度越高 */ function hammingDistance(string $fp1, string $fp2): int { if (strlen($fp1) != strlen($fp2)) return 999; $distance = 0; for ($i = 0; $i < strlen($fp1); $i++) { if ($fp1[$i] != $fp2[$i]) $distance++; } return $distance; } if(isset($_POST['submit'])){ $filepath = pathinfo($_FILES['file']['name']); $extension = strtolower($filepath['extension']); // 校验上传文件格式 $allowExt = ['jpg','jpeg','png','gif']; if (!in_array($extension, $allowExt)) { exit('仅支持jpg/png/gif格式图片'); } $iname = date('H-i-s').'.'.$extension; $path = 'upload/'.$iname; if(move_uploaded_file($_FILES['file']['tmp_name'], $path)){ echo '上传图片路径:' . $path . '<br>'; echo '匹配到的相似图片:<br>'; // 计算上传图片的指纹 $uploadFp = getImageFingerprint($path); if (!$uploadFp) exit('上传图片解析失败'); $images = glob("img/*"); // 相似度阈值,可自行调整,越小匹配越严格 $similarThreshold = 5; foreach($images as $image){ $targetFp = getImageFingerprint($image); if (!$targetFp) continue; $distance = hammingDistance($uploadFp, $targetFp); if($distance <= $similarThreshold){ echo "<img height='70px' width='70px' src='".$image."' style='margin:5px;border:1px solid #ccc;'/>"; echo "<span>相似度距离:".$distance."</span><br>"; } } } } ?>
前端HTML代码
<html> <body> <form method="post" enctype="multipart/form-data"> <input type="file" name="file" accept="image/*"><br> <input type="submit" name="submit" value="提交查找相似图"> </form> </body> </html>
参数调整说明
- 采样尺寸:代码默认用88采样,需要更高匹配精度可以调整到1616,对应指纹长度变为256位,比对耗时会小幅上升
- 相似度阈值:默认汉明距离≤5判定为相似,需要严格匹配可以把阈值调到≤2,需要覆盖更多近似编辑的图片可以调到≤8,距离超过10的图片基本不存在内容相似性
- 格式支持:默认支持jpg/png/gif格式,需要兼容webp可以在
getImageFingerprint函数的格式判断分支中添加webp对应的处理逻辑,使用imagecreatefromwebp读取资源即可
内容的提问来源于stack exchange,提问作者user17767322
相关产品推荐
相关产品推荐

