Skip to content

Instantly share code, notes, and snippets.

@MihanEntalpo
Last active August 29, 2015 14:24
Show Gist options
  • Save MihanEntalpo/38e8667f74e6e0814bcd to your computer and use it in GitHub Desktop.
Save MihanEntalpo/38e8667f74e6e0814bcd to your computer and use it in GitHub Desktop.
imageHash functions
<?php
/**
* Построить хэш изображения для быстрого сравнения схожести изображений.
* Позволяет быстро определять схожесть изображений, которые являются одним
* и тем-же изображением, но, например, с изменённым размером и пропорциями,
* или с немного подкорректированными цветами.
* Длина хэша в байтах равна квадрату параметра $hashSizeRoot, правда хэш на выходе преобразуется в base64,
* поэтому он занимает на 25% больше.
*
* @param string $imageFileContent Содержимое файла изображения
* @param integer $hashSizeRoot Квадратный корень из размера хэша. От 4 и больше. Чем меньше - тем более грубая генерация хэша,
* и тем меньше по размеру будет хэш (пропорционально квадрату этого числа)
* @param integer $hashDetalization детализация хэша, от 2 до 256. Чем больше - тем точнее сравнение.
*/
function imageHash($imageFileContent, $hashSizeRoot = 10, $hashDetalization = 16)
{
if (!$imageFileContent)
{
return null;
}
$hashDetalization = (int)$hashDetalization;
$hashSizeRoot = (int) $hashSizeRoot;
if ($hashDetalization > 256) $hashDetalization = 256;
if ($hashDetalization < 2) $hashDetalization = 2;
if ($hashSizeRoot < 4) $hashSizeRoot = 4;
$image = imagecreatefromstring($imageFileContent);
$width = imagesx($image);
$height = imagesy($image);
$size = array($width, $height);
$littleSize = $hashSizeRoot;
$colorsPerPixel = $hashDetalization;
$colorsPerChannel = pow($colorsPerPixel, 1/3);
$channelDivision = 256 / $colorsPerChannel;
$colorSimplify = function($color) use($colorsPerPixel, $colorsPerChannel, $channelDivision) {
$r = ($color >> 16) & 0xFF;
$g = ($color >> 8) & 0xFF;
$b = $color & 0xFF;
$simpleR = $r / $channelDivision;
$simpleG = $g / $channelDivision;
$simpleB = $b / $channelDivision;
$simpleColor = (int)($simpleR + $simpleG * $colorsPerChannel + $simpleB * $colorsPerChannel * $colorsPerChannel);
if ($simpleColor < 0) $simpleColor = 0;
if ($simpleColor >= $colorsPerPixel) $simpleColor = $colorsPerPixel - 1;
return (int)$simpleColor;
};
$littleImg = imagecreatetruecolor($littleSize, $littleSize);
imagecopyresampled($littleImg, $image, 0, 0, 0, 0, $littleSize, $littleSize, $size[0], $size[1]);
$hash = "";
for($i=0;$i<$littleSize; $i++)
{
for ($j=0;$j<$littleSize;$j++)
{
$color = imagecolorat($littleImg, $i, $j);
$simpleColor = $colorSimplify($color);
$hash .= chr($simpleColor);
}
}
imagedestroy($littleImg);
imagedestroy($image);
return base64_encode($hash);
}
/**
* Сравнить два хэша изображений
* @param string $hash1 хэш первого изображения в формате base64
* @param string $hash2 хэш второго изображения в таком же формате
* @param float $epsilon Максимальная относительная ошибка. 1 это 100%, 0.5 это 50% и так далее.
* @param boolean $error Ссылка на переменную, в которую будет записана величина ошибки (число от 0 до 1)
* @param boolean $alreadyDecoded Флаг, указывающий, что переданные хэши уже декодированы из base64
* @return boolean возвращает true/false в зависимости от того, соответствуют ли друг другу хэши
*/
function compareImageHashes($hash1, $hash2, $epsilon = 0.01, &$error = 0, $alreadyDecoded = false)
{
$error = 1;
if ($epsilon == 0)
{
return $hash1 == $hash2;
}
else
{
if (!$alreadyDecoded)
{
$h1 = base64_decode($hash1);
$h2 = base64_decode($hash2);
}
else
{
$h1 = $hash1;
$h2 = $hash2;
}
if (strlen($h1) != strlen($h2)) return false;
$l = strlen($h1);
$error = 0;
$bytes1 = unpack("C*", $h1);
$bytes2 = unpack("C*", $h2);
for ($i=0;$i<$l;$i++)
{
$b1 = $bytes1[$i+1];
$b2 = $bytes2[$i+1];
if ($b1 != $b2)
{
$delta = abs($b1 - $b2);
$mid = ($b1 + $b2) / 2;
if ($mid > 2)
{
$e = $delta / $mid;
$error += $e / $l;
if ($error > $epsilon) return false;
}
}
}
return $error <= $epsilon;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment