📜 ⬆️ ⬇️

Another version of the image comparison algorithm

This article hung in my drafts for a month until someone finally brought me karma to a tone. I do not know who, but thank you

Today, having once again entered the Habr, I came across this interesting article. It describes the image hashing algorithm. When I read this article, I got the idea how to change this algorithm so that it ate images that are very different, for example, brightness (but the images themselves are identical at the same time).

Perhaps this method has already existed for a long time and someone has long been described, well, in that case, I at least have ready code.
')
The algorithm is that we need to write the difference between each i-th and (i + 1) -th pixel into the array. Thus, this array is a hash for our image (if necessary, you can bring it into a string view). That is, based on this, we believe that our image is not a set of pixels of a certain color, but rather a set of differences between these pixels.

The results of testing such an algorithm turned out to be quite entertaining, but they will be lower after parsing the code.

Now we will sort this algorithm on the code.
I wrote a simple class that can compare any number of images, giving the difference between each of them.
Here is the class itself, if you want, you can stop at it, or you can read further what is there and to what.
class ImagesComparer { const BAD_ARGS = 1, UNSUPPORTED_FILETYPE = 2, ERROR_OPEN = 3; public $Images = array(); private $_types = array('', 'gif', 'jpeg', 'png', '', '', 'wbmp', '', '', '', '', ''); public $CompareWithFirst = false; public function __construct($Image1, $Image2 = null) { if (func_num_args() > 2) $Images = func_get_args(); else if (is_array($Image1)) $Images = $Image1; else $Images = array($Image1, $Image2); foreach ($Images as $Image) { if (is_string($Image)) $this->_openImage($Image); else if (is_resource($Image)) $this->Images[] = array($this->_getPixelsDiff($Image), array()); else throw new Exception('Bad arguments.', self::BAD_ARGS); } } private function _getImageType($Image) { $Type = getimagesize($Image); if (!$Type = $this->_types[$Type[2]]) throw new Exception('Image have an unsupported file type.', self::UNSUPPORTED_FILETYPE); return 'imagecreatefrom' . $Type; } private function _openImage($Image) { $Type = $this->_getImageType($Image); $Image = $Type($Image); if (!$Image) throw new Exception('Error opening image.', self::ERROR_OPEN); $this->Images[] = array($this->_getPixelsDiff($Image), array()); imagedestroy($Image); } private function _getPixelsDiff($Image) { $Sample = imagecreatetruecolor(8, 8); imagecopyresampled($Sample, $Image, 0, 0, 0, 0, 8, 8, imagesx($Image), imagesy($Image)); $Pixels = array(); $Color = array(0, 0, 0); for ($y = 0; $y < 8; $y++) { for ($x = 0; $x < 8; $x++) { $Color1 = imagecolorat($Sample, $x, $y); $Color1 = $this->_scale255To9(array( ($Color1 >> 16) & 0xFF, ($Color1 >> 8) & 0xFF, $Color & 0xFF )); if ($x != 0 || $y != 0) { $Pixels[] = array( $Color1[0] - $Color[0], $Color1[1] - $Color[1], $Color1[2] - $Color[2] ); } $Color = $Color1; } } imagedestroy($Sample); return $Pixels; } private function _scale255To9($NumArr) { return array( round($NumArr[0] / 28.3), round($NumArr[1] / 28.3), round($NumArr[2] / 28.3) ); } private function _getDiff($Img1, $Img2) { $Diff = 0; for ($i = 0; $i < 63; $i++) { $Diff += abs($this->Images[$Img1][0][$i][0] - $this->Images[$Img2][0][$i][0]); $Diff += abs($this->Images[$Img1][0][$i][1] - $this->Images[$Img2][0][$i][1]); $Diff += abs($this->Images[$Img1][0][$i][2] - $this->Images[$Img2][0][$i][2]); } return $Diff; } public function Compare() { $count = count($this->Images); if ($this->CompareWithFirst) { for ($i = 1; $i < $count; $i++) { $this->Images[0][1][$i] = $this->_getDiff(0, $i); } } else { for ($i = 0; $i < $count; $i++) { for ($k = $i + 1; $k < $count; $k++) { //echo "\r\n<br />" . $this->Images[$k][1][$i] = $this->Images[$i][1][$k] = $this->_getDiff($i, $k); } } } } } 


In the constructor, the _getPixelsDiff () method is called on each image, and its result is put into the Images array. This method produces such manipulations:
  1. Reduces the image to 8x8.
  2. Creates an array for colors.
  3. Passes through each pixel of the image:
    1. Takes its color in RGB.
    2. Each channel divides by 28.3 and rounds, so that the maximum value of the channel is equal to 9.
    3. Subtracts from each channel the value of the previous pixel.
    4. The result is put into an array and returned.
Well, further in Compare () the _getDiff () method is called, which finds the difference between the arrays.
By the way, based on this code, the biggest difference between images can be 1701 (63 * 3 * 9, correct if it is not).

And now the tests.

The first pair of pictures: one and two . The first picture is the Air Jordan logo. The second is a parody of the first performed by Bender. Agree, by eye pretty similar pictures. And our program gives them 68 points of the difference of 1701. That is, the probability of their identity is approximately 96.1%.

The second pair of pictures: one and two . It gives a difference of 266, although the colors are quite similar. By the way, it turns out that these figures with a probability of 85% are identical. So the bar (sorry, the threshold) should be set fairly high.

Here are a couple: one and two . The difference is 52.

In general, I do not think that this method is ideal, but at least I think it has the right to life.

UPD. Comparison of this image and this . The contrast of the second was greatly changed as we can see. The difference is 70 parrots from 1701.

Source: https://habr.com/ru/post/120577/


All Articles