Как сравнить подобие изображения с помощью php, независимо от масштаба, вращения?

Я хочу сравнить сходство между изображениями ниже. В соответствии с моими требованиями я хочу идентифицировать все эти изображения как похожие, так как он использует один и тот же цвет, тот же клип. Единственное отличие в этих изображениях – вращение, масштаб и размещение клипа. Поскольку все 3 футболки используют один и тот же цвет и клип, я хочу идентифицировать все 3 изображения как похожие. Я попробовал метод, описанный в hackerfactor.com . Но это не дает мне правильный результат в соответствии с моими требованиями. Как идентифицировать все эти изображения как похожие? У вас есть предложения? Пожалуйста, помогите мне.

введите описание изображения здесьвведите описание изображения здесьвведите описание изображения здесь

Нижеследующие изображения должны быть распознаны как отличные от предыдущих изображений. (Несмотря на то, что футболки имеют один и тот же цвет, клип отличается. Последняя футболка отличается от предыдущей, потому что она использует тот же клип, но дважды).

Изображение AИзображение BИзображение C

Solutions Collecting From Web of "Как сравнить подобие изображения с помощью php, независимо от масштаба, вращения?"

Перемещено в GitHub

Поскольку этот вопрос довольно интересен, я переместил все это в GitHub, где вы можете найти текущую реализацию: ImageCompare

Оригинальный ответ

Я сделал очень простой подход, используя img-resize и сравнивая средний цвет измененных изображений.

$binEqual = [ file_get_contents('http://i.stack.imgur.com/D8ct1.png'), file_get_contents('http://i.stack.imgur.com/xNZt1.png'), file_get_contents('http://i.stack.imgur.com/kjGjm.png') ]; $binDiff = [ file_get_contents('http://i.stack.imgur.com/WIOHs.png'), file_get_contents('http://i.stack.imgur.com/ljoBT.png'), file_get_contents('http://i.stack.imgur.com/qEKSK.png') ]; function getAvgColor($bin, $size = 10) { $target = imagecreatetruecolor($size, $size); $source = imagecreatefromstring($bin); imagecopyresized($target, $source, 0, 0, 0, 0, $size, $size, imagesx($source), imagesy($source)); $r = $g = $b = 0; foreach(range(0, $size - 1) as $x) { foreach(range(0, $size - 1) as $y) { $rgb = imagecolorat($target, $x, $y); $r += $rgb >> 16; $g += $rgb >> 8 & 255; $b += $rgb & 255; } } unset($source, $target); return (floor($r / $size ** 2) << 16) + (floor($g / $size ** 2) << 8) + floor($b / $size ** 2); } function compAvgColor($c1, $c2, $tolerance = 4) { return abs(($c1 >> 16) - ($c2 >> 16)) <= $tolerance && abs(($c1 >> 8 & 255) - ($c2 >> 8 & 255)) <= $tolerance && abs(($c1 & 255) - ($c2 & 255)) <= $tolerance; } $perms = [[0,1],[0,2],[1,2]]; foreach($perms as $perm) { var_dump(compAvgColor(getAvgColor($binEqual[$perm[0]]), getAvgColor($binEqual[$perm[1]]))); } foreach($perms as $perm) { var_dump(compAvgColor(getAvgColor($binDiff[$perm[0]]), getAvgColor($binDiff[$perm[1]]))); } 

Для используемого размера и цветоустойчивости я получаю ожидаемый результат:

 bool(true) bool(true) bool(true) bool(false) bool(false) bool(false) 

Более продвинутая реализация

Пустая футболка для сравнения: Обычная футболка

 $binEqual = [ file_get_contents('http://i.stack.imgur.com/D8ct1.png'), file_get_contents('http://i.stack.imgur.com/xNZt1.png'), file_get_contents('http://i.stack.imgur.com/kjGjm.png') ]; $binDiff = [ file_get_contents('http://i.stack.imgur.com/WIOHs.png'), file_get_contents('http://i.stack.imgur.com/ljoBT.png'), file_get_contents('http://i.stack.imgur.com/qEKSK.png') ]; class Color { private $r = 0; private $g = 0; private $b = 0; public function __construct($r = 0, $g = 0, $b = 0) { $this->r = $r; $this->g = $g; $this->b = $b; } public function r() { return $this->r; } public function g() { return $this->g; } public function b() { return $this->b; } public function toInt() { return $this->r << 16 + $this->g << 8 + $this->b; } public function toRgb() { return [$this->r, $this->g, $this->b]; } public function mix(Color $color) { $this->r = round($this->r + $color->r() / 2); $this->g = round($this->g + $color->g() / 2); $this->b = round($this->b + $color->b() / 2); } public function compare(Color $color, $tolerance = 500) { list($r1, $g1, $b1) = $this->toRgb(); list($r2, $g2, $b2) = $color->toRgb(); $diff = round(sqrt(pow($r1 - $r2, 2) + pow($g1 - $g2, 2) + pow($b1 - $b2, 2))); printf("Comp r(%s : %s), g(%s : %s), b(%s : %s) Diff %s \n", $r1, $r2, $g1, $g2, $b1, $b2, $diff); return $diff <= $tolerance; } public static function fromInt($int) { return new self($int >> 16, $int >> 8 & 255, $int & 255); } } function getAvgColor($bin, $size = 5) { $target = imagecreatetruecolor($size, $size); $targetTmp = imagecreatetruecolor($size, $size); $sourceTmp = imagecreatefrompng('http://i.stack.imgur.com/gfn5A.png'); $source = imagecreatefromstring($bin); imagecopyresized($target, $source, 0, 0, 0, 0, $size, $size, imagesx($source), imagesy($source)); imagecopyresized($targetTmp, $sourceTmp, 0, 0, 0, 0, $size, $size, imagesx($source), imagesy($source)); $r = $g = $b = $relPx = 0; $baseColor = new Color(); foreach(range(0, $size - 1) as $x) { foreach(range(0, $size - 1) as $y) { if (imagecolorat($target, $x, $y) != imagecolorat($targetTmp, $x, $y)) $baseColor->mix(Color::fromInt(imagecolorat($target, $x, $y))); } } unset($source, $target, $sourceTmp, $targetTmp); return $baseColor; } $perms = [[0,0], [1,0], [2,0], [1,0], [1,1], [1,2], [2,0], [2,1], [2,2]]; echo "Equal\n"; foreach($perms as $perm) { var_dump(getAvgColor($binEqual[$perm[0]])->compare(getAvgColor($binEqual[$perm[1]]))); } echo "Different\n"; foreach($perms as $perm) { var_dump(getAvgColor($binEqual[$perm[0]])->compare(getAvgColor($binDiff[$perm[1]]))); } 

Результат:

 Equal Comp r(101 : 101), g(46 : 46), b(106 : 106) Diff 0 bool(true) Comp r(121 : 101), g(173 : 46), b(249 : 106) Diff 192 bool(true) Comp r(219 : 101), g(179 : 46), b(268 : 106) Diff 241 bool(true) Comp r(121 : 101), g(173 : 46), b(249 : 106) Diff 192 bool(true) Comp r(121 : 121), g(173 : 173), b(249 : 249) Diff 0 bool(true) Comp r(121 : 219), g(173 : 179), b(249 : 268) Diff 100 bool(true) Comp r(219 : 101), g(179 : 46), b(268 : 106) Diff 241 bool(true) Comp r(219 : 121), g(179 : 173), b(268 : 249) Diff 100 bool(true) Comp r(219 : 219), g(179 : 179), b(268 : 268) Diff 0 bool(true) Different Comp r(101 : 446), g(46 : 865), b(106 : 1242) Diff 1442 bool(false) Comp r(121 : 446), g(173 : 865), b(249 : 1242) Diff 1253 bool(false) Comp r(219 : 446), g(179 : 865), b(268 : 1242) Diff 1213 bool(false) Comp r(121 : 446), g(173 : 865), b(249 : 1242) Diff 1253 bool(false) Comp r(121 : 654), g(173 : 768), b(249 : 1180) Diff 1227 bool(false) Comp r(121 : 708), g(173 : 748), b(249 : 1059) Diff 1154 bool(false) Comp r(219 : 446), g(179 : 865), b(268 : 1242) Diff 1213 bool(false) Comp r(219 : 654), g(179 : 768), b(268 : 1180) Diff 1170 bool(false) Comp r(219 : 708), g(179 : 748), b(268 : 1059) Diff 1090 bool(false) 

В этом вычислении фон игнорируется, что приводит к большей разнице в avg-цвете.

Окончательная реализация (ООП)

Довольно интересная тема. Поэтому я попытался настроить его на битву. Теперь это полная реализация ООП. Теперь вы можете создать новое изображение и вычесть некоторую маску, чтобы исключить фон. Затем вы можете сравнить одно изображение с другим, используя метод сравнения. Чтобы ограничение было ограничено, лучше сначала изменить размер изображения (маски всегда привязаны к текущему изображению)

Сравните algorythme, он сам разбивает два изображения на серверные плитки, а затем удаляет плитки, которые почти равны среднему белому цвету, а затем сравнивает средний цвет всех оставшихся черепичных перестановок.

 Class Image { const HASH_SIZE = 8; const AVG_SIZE = 10; private $img = null; public function __construct($resource) { $this->img = $resource;; } private function permute(array $a1, array $a2) { $perms = array(); for($i = 0; $i < sizeof($a1); $i++) { for($j = $i; $j < sizeof($a2); $j++) { if ($i != $j) { $perms[] = [$a1[$i], $a2[$j]]; } } } return $perms; } public function compare(Image $comp) { $avgComp = array(); foreach($comp->chunk(25) as $chunk) { $avgComp[] = $chunk->avg(); } $avgOrg = array(); foreach($this->chunk(25) as $chunk) { $avgOrg[] = $chunk->avg(); } $white = Color::fromInt(0xFFFFFF); $avgComp = array_values(array_filter($avgComp, function(Color $color) use ($white){ return $white->compare($color, 1000); })); $avgOrg = array_values(array_filter($avgOrg, function(Color $color) use ($white){ return $white->compare($color, 1000); })); $equal = 0; $pairs = $this->permute($avgOrg, $avgComp); foreach($pairs as $pair) { $equal += $pair[0]->compare($pair[1], 100) ? 1 : 0; } return ($equal / sizeof($pairs)); } public function substract(Image $mask, $tolerance = 50) { $size = $this->size(); if ($mask->size() != $size) { $mask = $mask->resize($size); } for ($x = 0; $x < $size[0]; $x++) { for ($y = 0; $y < $size[1]; $y++) { if ($this->colorat($x, $y)->compare($mask->colorat($x, $y), $tolerance)) imagesetpixel($this->img, $x, $y, 0xFFFFFF); } } return $this; } public function avg($size = 10) { $target = $this->resize([self::AVG_SIZE, self::AVG_SIZE]); $avg = Color::fromInt(0x000000); $white = Color::fromInt(0xFFFFFF); for ($x = 0; $x < self::AVG_SIZE; $x++) { for ($y = 0; $y < self::AVG_SIZE; $y++) { $color = $target->colorat($x, $y); if (!$color->compare($white, 10)) $avg->mix($color); } } return $avg; } public function colorat($x, $y) { return Color::fromInt(imagecolorat($this->img, $x, $y)); } public function chunk($chunkSize = 10) { $collection = new ImageCollection(); $size = $this->size(); for($x = 0; $x < $size[0]; $x += $chunkSize) { for($y = 0; $y < $size[1]; $y += $chunkSize) { switch (true) { case ($x + $chunkSize > $size[0] && $y + $chunkSize > $size[1]): $collection->push($this->slice(['x' => $x, 'y' => $y, 'height' => $size[0] - $x, 'width' => $size[1] - $y])); break; case ($x + $chunkSize > $size[0]): $collection->push($this->slice(['x' => $x, 'y' => $y, 'height' => $size[0] - $x, 'width' => $chunkSize])); break; case ($y + $chunkSize > $size[1]): $collection->push($this->slice(['x' => $x, 'y' => $y, 'height' => $chunkSize, 'width' => $size[1] - $y])); break; default: $collection->push($this->slice(['x' => $x, 'y' => $y, 'height' => $chunkSize, 'width' => $chunkSize])); break; } } } return $collection; } public function slice(array $rect) { return Image::fromResource(imagecrop($this->img, $rect)); } public function size() { return [imagesx($this->img), imagesy($this->img)]; } public function resize(array $size = array(100, 100)) { $target = imagecreatetruecolor($size[0], $size[1]); imagecopyresized($target, $this->img, 0, 0, 0, 0, $size[0], $size[1], imagesx($this->img), imagesy($this->img)); return Image::fromResource($target); } public function show() { header("Content-type: image/png"); imagepng($this->img); die(); } public function save($name = null, $path = '') { if ($name === null) { $name = $this->hash(); } imagepng($this->img, $path . $name . '.png'); return $this; } public function hash() { // Resize the image. $resized = imagecreatetruecolor(self::HASH_SIZE, self::HASH_SIZE); imagecopyresampled($resized, $this->img, 0, 0, 0, 0, self::HASH_SIZE, self::HASH_SIZE, imagesx($this->img), imagesy($this->img)); // Create an array of greyscale pixel values. $pixels = []; for ($y = 0; $y < self::HASH_SIZE; $y++) { for ($x = 0; $x < self::HASH_SIZE; $x++) { $rgb = imagecolorsforindex($resized, imagecolorat($resized, $x, $y)); $pixels[] = floor(($rgb['red'] + $rgb['green'] + $rgb['blue']) / 3); } } // Free up memory. imagedestroy($resized); // Get the average pixel value. $average = floor(array_sum($pixels) / count($pixels)); // Each hash bit is set based on whether the current pixels value is above or below the average. $hash = 0; $one = 1; foreach ($pixels as $pixel) { if ($pixel > $average) $hash |= $one; $one = $one << 1; } return $hash; } public static function fromResource($resource) { return new self($resource); } public static function fromBin($binf) { return new self(imagecreatefromstring($bin)); } public static function fromFile($path) { return new self(imagecreatefromstring(file_get_contents($path))); } } class ImageCollection implements IteratorAggregate { private $images = array(); public function __construct(array $images = array()) { $this->images = $images; } public function push(Image $image) { $this->images[] = $image; return $this; } public function pop() { return array_pop($this->images); } public function save() { foreach($this->images as $image) { $image->save(); } return $this; } public function getIterator() { return new ArrayIterator($this->images); } } class Color { private $r = 0; private $g = 0; private $b = 0; public function __construct($r = 0, $g = 0, $b = 0) { $this->r = $r; $this->g = $g; $this->b = $b; } public function r() { return $this->r; } public function g() { return $this->g; } public function b() { return $this->b; } public function toInt() { return $this->r << 16 + $this->g << 8 + $this->b; } public function toRgb() { return [$this->r, $this->g, $this->b]; } public function mix(Color $color) { $this->r = round($this->r + $color->r() / 2); $this->g = round($this->g + $color->g() / 2); $this->b = round($this->b + $color->b() / 2); } public function compare(Color $color, $tolerance = 500) { list($r1, $g1, $b1) = $this->toRgb(); list($r2, $g2, $b2) = $color->toRgb(); $diff = round(sqrt(pow($r1 - $r2, 2) + pow($g1 - $g2, 2) + pow($b1 - $b2, 2))); //printf("Comp r(%s : %s), g(%s : %s), b(%s : %s) Diff %s \n", $r1, $r2, $g1, $g2, $b1, $b2, $diff); return $diff <= $tolerance; } public static function fromInt($int) { return new self($int >> 16, $int >> 8 & 255, $int & 255); } } $mask = Image::fromFile('http://i.stack.imgur.com/gfn5A.png'); $image1 = Image::fromFile('http://i.stack.imgur.com/D8ct1.png')->resize([50, 100])->substract($mask, 100); $image2 = Image::fromFile('http://i.stack.imgur.com/xNZt1.png')->resize([50, 100])->substract($mask, 100); $image3 = Image::fromFile('http://i.stack.imgur.com/kjGjm.png')->resize([50, 100])->substract($mask, 100); $other1 = Image::fromFile('http://i.stack.imgur.com/WIOHs.png')->resize([50, 100])->substract($mask, 100); $other2 = Image::fromFile('http://i.stack.imgur.com/ljoBT.png')->resize([50, 100])->substract($mask, 100); $other3 = Image::fromFile('http://i.stack.imgur.com/qEKSK.png')->resize([50, 100])->substract($mask, 100); echo "Equal\n"; var_dump( $image1->compare($image2), $image1->compare($image3), $image2->compare($image3) ); echo "Image 1 to Other\n"; var_dump( $image1->compare($other1), $image1->compare($other2), $image1->compare($other3) ); echo "Image 2 to Other\n"; var_dump( $image2->compare($other1), $image2->compare($other2), $image2->compare($other3) ); echo "Image 3 to Other\n"; var_dump( $image3->compare($other1), $image3->compare($other2), $image3->compare($other3) ); 

Результат:

 Equal float(0.47619047619048) float(0.53333333333333) float(0.4) Image 1 to Other int(0) int(0) int(0) Image 2 to Other int(0) int(0) int(0) Image 3 to Other int(0) int(0) int(0) 

Я не претендую на то, чтобы действительно знать что-либо об этой теме, которая, как я думаю, в целом называется «видением».

Однако я бы сделал следующее:

Поток:

  • Постер , до минимального количества цветов / оттенков (догадка).
  • Удалите два самых больших цвета (белый + рубашка).
  • Сравните оставшуюся цветовую палитру и сбой, если схемы слишком сильно отличаются.
  • Вычислите грубый многоугольник вокруг любых оставшихся «цветных пятен» (см. https://en.wikipedia.org/wiki/Convex_hull )
  • Сравните количество полигонов и наибольшее количество углов и угловых значений (не размер) полигона от каждого изображения, а также провалиться или пройти.

Основная проблема в такой установке будет округлять … как при постерировании цвета, то есть именно в середине между двумя цветами … иногда он получает colorA, иногда он получает colorB. Я думаю, что и с полигонами.

SIMILAR вычисляет нормированную метрику сходства взаимной корреляции между двумя равными размерными изображениями. Нормализованная кросс-корреляционная метрика измеряет, насколько похожи два изображения, а не насколько они различны. Диапазон значений ncc-metric находится между 0 (несходным) и 1 (аналогичным). Если mode = g, то два изображения будут преобразованы в оттенки серого. Если mode = rgb, то два изображения сначала будут преобразованы в colorspace = rgb. Затем метрика подобия ncc будет вычислена для каждого канала. Наконец, они будут объединены в среднеквадратичное значение. ПРИМЕЧАНИЕ. Эта метрика не работает для постоянных цветовых каналов, так как она генерирует метку ncc = 0/0 для этого канала. Таким образом, не рекомендуется запускать сценарий с любым изображением, имеющим полностью непрозрачный или полностью прозрачный альфа-канал, который включен.

попробуйте этот api,

 http://www.phpclasses.org/package/8255-PHP-Compare-two-images-to-find-if-they-are-similar.html 

Как уже упоминалось, все, что угодно, кроме вычисления гистограммы изображений и их сравнения, нелегко. Вот пример, который дает правильный результат для изображений, о которых идет речь. Ключевым моментом здесь является получение правильного баланса между количеством пиковых уровней цвета и приемлемым количеством их ( similarity( $histograms, $levels = 30, $enough = 28 ) ).

 function histograms( $images ) { foreach( $images as $img ) { $image = imagecreatefrompng( $img ); $width = imagesx( $image ); $height = imagesy( $image ); $num_pixels = $width * $height; $histogram = []; for ( $x = 0; $x < $width; $x++ ) { for ( $y = 0; $y < $height; $y++ ) { $rgb = imagecolorat( $image, $y, $x ); $rgb = [ $rgb >> 16, ( $rgb >> 8 ) & 0xFF, $rgb & 0xFF ]; $histo_v = (int) round( ( $rgb[0] + $rgb[1] + $rgb[02] ) / 3 ); $histogram[ $histo_v ] = array_key_exists( $histo_v, $histogram ) ? $histogram[ $histo_v ] + $histo_v/$num_pixels : $histo_v/$num_pixels; } } $histograms[$img] = $histogram; arsort( $histograms[$img] ); } return $histograms; } function similarity( $histograms, $levels = 30, $enough = 28 ) { $keys = array_keys( $histograms ); $output = []; for ( $x = 0; $x < count( $histograms ) - 1; $x++ ) { for ( $y = $x + 1; $y < count( $histograms ); $y++ ) { $similarity = count( array_intersect_key( array_slice( $histograms[ $keys[$x] ], 0, $levels, true ), array_slice( $histograms[ $keys[$y] ], 0, $levels, true ) ) ); if ( $similarity > $enough ) $output[] = [ $keys[$x], $keys[$y], $similarity ]; } } return $output; } $histograms = histograms( [ 'http://i.stack.imgur.com/D8ct1.png', 'http://i.stack.imgur.com/xNZt1.png', 'http://i.stack.imgur.com/kjGjm.png', 'http://i.stack.imgur.com/WIOHs.png', 'http://i.stack.imgur.com/ljoBT.png', 'http://i.stack.imgur.com/qEKSK.png' ] ); $similarity = similarity( $histograms ); print_r( $similarity ); /* Array ( [0] => Array ( [0] => http://i.stack.imgur.com/D8ct1.png [1] => http://i.stack.imgur.com/xNZt1.png [2] => 30 ) [1] => Array ( [0] => http://i.stack.imgur.com/D8ct1.png [1] => http://i.stack.imgur.com/kjGjm.png [2] => 30 ) [2] => Array ( [0] => http://i.stack.imgur.com/D8ct1.png [1] => http://i.stack.imgur.com/qEKSK.png [2] => 29 ) [3] => Array ( [0] => http://i.stack.imgur.com/xNZt1.png [1] => http://i.stack.imgur.com/kjGjm.png [2] => 30 ) [4] => Array ( [0] => http://i.stack.imgur.com/xNZt1.png [1] => http://i.stack.imgur.com/qEKSK.png [2] => 29 ) [5] => Array ( [0] => http://i.stack.imgur.com/kjGjm.png [1] => http://i.stack.imgur.com/qEKSK.png [2] => 29 ) ) */ 

Эта статья также помогла мне создать гистограммы.