Я хочу сравнить сходство между изображениями ниже. В соответствии с моими требованиями я хочу идентифицировать все эти изображения как похожие, так как он использует один и тот же цвет, тот же клип. Единственное отличие в этих изображениях – вращение, масштаб и размещение клипа. Поскольку все 3 футболки используют один и тот же цвет и клип, я хочу идентифицировать все 3 изображения как похожие. Я попробовал метод, описанный в hackerfactor.com . Но это не дает мне правильный результат в соответствии с моими требованиями. Как идентифицировать все эти изображения как похожие? У вас есть предложения? Пожалуйста, помогите мне.
Нижеследующие изображения должны быть распознаны как отличные от предыдущих изображений. (Несмотря на то, что футболки имеют один и тот же цвет, клип отличается. Последняя футболка отличается от предыдущей, потому что она использует тот же клип, но дважды).
Поскольку этот вопрос довольно интересен, я переместил все это в 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)
Я не претендую на то, чтобы действительно знать что-либо об этой теме, которая, как я думаю, в целом называется «видением».
Однако я бы сделал следующее:
Поток:
Основная проблема в такой установке будет округлять … как при постерировании цвета, то есть именно в середине между двумя цветами … иногда он получает 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 ) ) */
Эта статья также помогла мне создать гистограммы.