PHP

Сравнить два изображения с помощью PHP

Привет! Столкнулся с задачей сравнить два изображения, если уж совсем буквальнее – выбрать n-похожих на исходное и отсортировать по похожести. Не уверен, что PHP для этого подходит (точнее уверен, что это не лучший вариант), но для небольшого масштаба приложения решил изучить его возможности.

Представлю два опробованных мною способа.

Способ первый, алгоритм и GD

Довольно интересный способ, простой, но достаточно спорный, особенно если взглянуть на практические результаты.

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

Привожу сразу класс с полной реализацией этого метода.

class ImagesComparer
{

    const
        BAD_ARGS = 1,
        UNSUPPORTED_FILETYPE = 2,
        ERROR_OPEN = 3;

    public $Images = array();

    private
        $_types = array('', 'gif', 'jpeg', 'png', '', '', 'wbmp', '', '', '', '', '');

    public $CompareWithFirst = TRUE;

    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] = intval($this->_getDiff(0, $i));
            }
        }
        else
        {
            for ($i = 0; $i < $count; $i++)
            {
                for ($k = $i + 1; $k < $count; $k++)
                {
                    $this->Images[$k][1][$i] =
                        $this->Images[$i][1][$k] = intval($this->_getDiff($i, $k));
                }
            }
        }
    }

}


Представленный класс может принимать массив изображений, по умолчанию сравнивает каждое изображение с каждым другим. Опционально можну установить свойство CompareWithFirst = TRUE, тогда каждое изображение будет сравниваться с первым. Далее вызывается метод Compare(), который заполняет свойство $this->Images данными с результатами сравнения.

Посмотрим на результаты:



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

Очень спорно, хотя если постараться логику в таком сравнении найти можно, но такая похожая на мой взгляд серая фигурка слона отброшена на 4е место:))) Остался неудовлетворенным я этим способом и продолжил изучать вопрос.

автор алгоритма и оригинальный пост на хабре

Способ второй, ImageMagick


Данный способ еще проще первого, ввиду использования класса Imagick для PHP, реализующего работу с ImageMagick. У него есть простая обертка для сравнения двух изображений, вкратце одна строка

$Img1->compareImages($Img2, Imagick::METRIC_MEANSQUAREERROR);


Приведу сразу результаты сравнения этим способом:



Обратите внимание на показатель похожести – последние два изображения резко отброшены от первого, с которым все сравниваются. 3 других вполне похожи между собой визуально, что также коррелируется с цифрами. Работа этого метода мне показалась довольно схожей с визуальной оценкой изображений и я остановился на данном способе. Приведу класс целиком.

class ImagesComparer
{

    const
        WIDTH = 600,
        HEIGHT = 400;

    public $Images = array();

    public function __construct($Image1, $Image2 = null)
    {
        if (is_array($Image1))
        {
            $this->Images = $Image1;
            $this->mainImage = $Image1[0];
        }
        else
        {
            $this->Images = array($Image1);
            $this->mainImage = $Image1;
        }

        foreach ($this->Images as $key => $Image)
        {
            if (is_string($Image))
                $this->Images[$key] = array($Image, $this->_openImage($Image));
            else
                throw new Exception('Bad arguments.', self::BAD_ARGS);
        }
    }

    private function _openImage($Image)
    {
        $obj = new Imagick($Image);
        $d = $obj->getImageGeometry();
        $w = $d['width'];
        $h = $d['height'];
        if ($w != self::WIDTH || $h != self::HEIGHT)
            $obj->cropImage(self::WIDTH, self::HEIGHT, 0, 0);
        return $obj;
    }

    private function _getDiff($Img1, $Img2)
    {
        $res = $Img1->compareImages($Img2, Imagick::METRIC_MEANSQUAREERROR);
        $diff = round($res[1]*1000);
        return $diff;
    }

    public function Compare()
    {
        $count = count($this->Images);

        if ($count > 1)
        {
            foreach ($this->Images as $key => $value)
            {
                if ($key > 0)
                {
                    $this->Images[$key][] = $this->_getDiff($this->Images[0][1], $this->Images[$key][1]);
                }
            }
        }
    }

}


Обратите внимание на то, что метод compareImages() применим для изображений одинакового размера. Я определил их в константах класса, вы же можете их вынести в публичные свойства и варьировать в зависимости от предмета сравнения.
Теги:
imagick
Добавлено: 28 Февраля 2015 19:01:16 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...