styde programacion poo orientada objetos cursos curso php image-processing similarity

php - programacion - styde laravel



Cómo comparar la similitud de la imagen usando php independientemente de la escala, la rotación? (4)

No pretendo saber realmente nada sobre este tema, que en general creo que se llama ''visión''.

Lo que haría, sin embargo, es algo en esta línea:

Fluir:

  • Posterise , al número mínimo de colores / sombras (adivinar).
  • Retire dos colores más grandes (blanco + camisa).
  • Compara la paleta de colores restante y falla si los esquemas difieren demasiado.
  • Calcule un polígono grueso alrededor de las "manchas de color" restantes (consulte https://en.wikipedia.org/wiki/Convex_hull )
  • Compara el número de polígonos y el número de ángulos y valores angulares más grandes (no el tamaño), de cada imagen, y falla o pasa.

El problema principal en tal configuración será el redondeo ... como en la posterización de un color, que es precisamente a mitad de camino entre dos colores ... a veces adquiere color A, a veces adquiere colorB. Lo mismo con los polígonos, supongo.

Quiero comparar la similitud entre las imágenes de abajo. De acuerdo con mis requisitos, quiero identificar todas estas imágenes como similares, ya que ha usado el mismo color, el mismo clip art. La única diferencia en estas imágenes es la rotación, la escala y la ubicación del clip art. Como las 3 camisetas han usado el mismo color y clip art, quiero identificar las 3 imágenes como similares. Probé el método descrito en hackerfactor.com . Pero no me da el resultado correcto según mis requisitos. ¿Cómo identificar todas estas imágenes como similares? ¿Tiene alguna sugerencia? Por favor, ayúdame.

Las imágenes de abajo deben ser reconocidas como diferentes de las imágenes de arriba. (Aunque las camisetas tienen el mismo color, las imágenes prediseñadas son diferentes. La última camiseta es diferente a la anterior, porque usó el mismo clip art, pero dos veces).


SIMILAR calcula la similitud de similitud de correlación cruzada normalizada entre dos imágenes de dimensiones iguales. La métrica de correlación cruzada normalizada mide cuán similares son dos imágenes, no cuán diferentes son. El rango de valores métricos ncc está entre 0 (disímil) y 1 (similar). Si mode = g, las dos imágenes se convertirán a escala de grises. Si mode = rgb, las dos imágenes primero se convertirán a colorspace = rgb. A continuación, la métrica de similitud ncc se calculará para cada canal. Finalmente, se combinarán en un valor rms. NOTA: esta métrica no funciona para los canales de color constantes, ya que produce una métrica ncc = 0/0 para ese canal. Por lo tanto, no se recomienda ejecutar el script con ninguna imagen que tenga un canal alfa totalmente opaco o totalmente transparente que esté habilitado.

prueba esta API

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


Como alguien mencionó, cualquier cosa más que calcular el histograma de las imágenes y compararlas no es fácil de lograr. Aquí hay un ejemplo que da el resultado correcto para las imágenes proporcionadas en cuestión. El punto clave aquí es cómo obtener el equilibrio correcto entre el número de niveles máximos de color y la cantidad aceptable de ellos ( 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 ) ) */

Este artículo también me ayudó a crear los histogramas.


Movido a GitHub

Debido a que esta pregunta es bastante interesante, moví todo a GitHub, donde puede encontrar la implementación actual: ImageCompare

Respuesta original

Hice un enfoque muy simple, usando img-resize y comparando el color promedio de las imágenes redimensionadas.

$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]]))); }

Para el tamaño utilizado y la tolerancia de color, obtengo el resultado esperado:

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

Implementación más avanzada

Camiseta vacía para comparar:

$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]]))); }

Resultado:

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)

En este cálculo, se ignora el fondo, lo que conduce a una mayor diferencia en el color promedio.

Implementación final (OOP)

Un tema bastante interesante. Así que intenté afinarlo un poco más. Esta es ahora una implementación completa de OOP. Ahora puede crear una nueva imagen y restar alguna máscara para eliminar un fondo. Luego puede comparar una imagen con otra usando el método de comparación. Para mantener el cálculo limitado, es mejor cambiar el tamaño de la imagen primero (las máscaras están siempre ajustadas a la imagen actual)

El algoritmo de comparación separa las dos imágenes en varias fichas, luego elimina las fichas, que son casi iguales al color blanco promedio y luego compara el color promedio de todas las permutaciones de fichas restantes.

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) );

Resultado:

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)