Last active
August 29, 2015 14:24
-
-
Save MihanEntalpo/38e8667f74e6e0814bcd to your computer and use it in GitHub Desktop.
imageHash functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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