目录
一、需求
给出两张图片,对比这两张图片的相似度,如果相似度大于设定的某个阈值(比如说大于80),就可以判断两张图片是类似的,如果小于某个阈值说明两张图片差异较大。
下面的示例使用的是ThinkPHP5。
二、相似度算法
下面介绍两种计算相似度的方法,一种是差异值哈希,一种是均值哈希。
1、差异值哈希
- 将图片缩小为8*8的尺寸
- 将小图片变为灰度图像
- 计算每个像素的灰度的差值
- 将两个图片的指纹依次进行比较
2、均值哈希
- 将图片缩小为8*8的尺寸
- 将小图片变为灰度图像
- 计算每个像素的灰度平均值
- 与平均值进行比较,大于等于为 1,小于为 0,得到指纹
- 将两个图片的指纹依次进行比较,相同 count++ count越大,相似度越高
两种算法均是先把图片缩小,变成灰度图像,差异值哈希是计算每个像素的灰度差值,再将两张图片依次进行比较,均值哈希算法是计算每个像素的灰度平均值,再与平均值作比较,再将两张图片的指纹依次对比
三、示例
1、差异值哈希
<?php
namespace app\index\controller;
use think\Controller;
/* 差异值哈希
* 1.将图片缩小为8*8的尺寸
* 2.将小图片变为灰度图像
* 3.计算每个像素的灰度的差值
* 4.将两个图片的指纹依次进行比较
* */
class ImgCompareDHASH extends Controller
{
//正方形图片
public $width = 8;
public $height = 8;
public $scalar = 8;
/**
* 主方法 - 对比两张图片,得到相似度 -- 步骤4
*/
public function compare($img1, $img2){
$hash1 = $this->gethash($img1);
$hash2 = $this->gethash($img2);
if(strlen($hash1) !== strlen($hash2)) {
return false;
}
$result = $this->hd($hash1, $hash2);
//计算相似度
$result_percent = ($result / ($this->scalar * $this->scalar)) * 100;
return $result_percent;
}
/**
* 计算汉明距离
*/
public function hd($h1, $h2)
{
$len = strlen($h1);
$dist = 0;
for ($i = 0;$i < $len; $i++) {
if ( $h1[$i] == $h2[$i] )
$dist++;
}
return $dist;
}
// 获得图片指纹
public function gethash($url){
/**
* 新建一个 width * height 真彩色图像 -- 步骤1
* 返回一个图像标识符
*/
$new_img = imagecreatetruecolor($this->width, $this->height);
//获取图片宽和高
list($ex_w, $ex_h) = getimagesize($url);
// 获得图片文件的后缀名
$name = pathinfo($url, PATHINFO_EXTENSION);
$ex_img = call_user_func('imagecreatefrom'. ( $name == 'jpg' ? 'jpeg' : $name ) , $url);
//调整图片尺寸 - 重采样拷贝部分图像并调整大小
imagecopyresampled($new_img, $ex_img, 0, 0, 0, 0, $this->width, $this->height, $ex_w, $ex_h);
//转换图片为灰度图 -- 步骤2
imagefilter($new_img, IMG_FILTER_GRAYSCALE);
//销毁缓存
imagedestroy($ex_img);
//记录像素
$pixels = [];
for($i = 0; $i < $this->scalar; $i++){
for($j = 0; $j < $this->scalar; $j++){
/**
* 获得每个位置像素的索引值
* 0xFF:表示16进制 相当于 十进制 的 255
* 1111 1111
* 计算图片灰度值
*/
$gray = ImageColorAt($new_img, $i, $j) & 255;
//记录每个点的像素值
$pixels[] = $gray;
}
}
imagedestroy($new_img);
// 计算所有像素的灰阶平均值 -- 步骤3
$average = intval(array_sum($pixels) / count($pixels));
//获取图片指纹
$hashStr = '';
foreach ($pixels as $gray){
$hashStr .= ($gray >= $average) ? '1' : '0';
}
return $hashStr;
}
}
2、均值哈希
<?php
namespace app\index\controller;
use think\Controller;
/* 均值哈希
* 1.将图片缩小为8*8的尺寸
* 2.将小图片变为灰度图像
* 3.计算每个像素的灰度平均值
* 4.与平均值进行比较,大于等于为 1,小于为 0,得到指纹
* 5.将两个图片的指纹依次进行比较,相同 count++ count越大,相似度越高
* */
class ImgCompareAVG extends Controller
{
// 比较相似度 实现步骤5
public function compare($img1,$img2){
static $self;
if(!$self) $self = new static;
$hash1 = $self->gethash($img1);
$hash2 = $self->gethash($img2);
if(strlen($hash1) !== strlen($hash2)) return false;
$count = 0;
$len = strlen($hash1);
for($i = 0; $i < $len; $i++){
if($hash1[$i] == $hash2[$i]){
$count++;
}
}
return $count; // 相当于返回相似度
}
// 将图片文件返回为图像标识符 代表图片
public function getimg($url){
$name = pathinfo($url,PATHINFO_EXTENSION); // 获得图片文件的后缀名
$img = call_user_func('imagecreatefrom'. ( $name == 'jpg' ? 'jpeg' : $name ) , $url); // 由文件创建图片
return $img;
}
// 获得图片指纹
public function gethash($url){
$array = array();
$total = 0;
$new_img = imagecreatetruecolor(8,8); // 建立一个 8*8 的黑色图像
list($ex_w,$ex_h) = getimagesize($url); // 将获得的图像长宽赋值给 $ex_w $ex_h
$ex_img = $this->getimg($url); // 获得图片
imagecopyresampled($new_img, $ex_img,0,0,0,0,8,8,$ex_w,$ex_h); // 实现步骤 1 2
imagedestroy($ex_img); // 销毁原图
for($i=0;$i<8;$i++){
for($j=0;$j<8;$j++){
$gray = (imagecolorat($new_img, $j, $i) >> 8) & 0xFF; // 获得每个位置像素的索引值 0xFF 1111 1111
$array[$i][$j] = $gray; // 记录每个点的像素值
$total += $gray; // 计算总和
}
}
imagedestroy($new_img);
$average = intval($total / (8 * 8 * 2)); // 平均值 实现步骤3
$hash = '';
for($i=0;$i<8;$i++){
for($j=0;$j<8;$j++){
$hash .= ($array[$i][$j] >= $average) ? '1' : '0'; // 实现步骤4
}
}
return$hash;
}
}
四、计算结果
输入的两张图片
1、差异值哈希
2、均值哈希
这两种算法是通过计算汉明距离或相同位数来评估相似度,不能代表绝对的相似,但可作为参考,两种算法各有特点,其中差异值哈希计算的结果相对比较可靠。