реклама
разместить

Python. Как сравнить фотографии?

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

Совсем недавно у меня появилась интересная задача – необходимо было найти одинаковые фотографии на разных объектах недвижимости. Т.е. к объектам недвижимости расположенных с разным местоположением крепилась одна и та же фотография, может ошибочно, может специально, но такие объекты надо было найти. И я хотел бы поделиться тем, как я решал эту задачу. Для примера у Вас может быть домашняя фототека.

Инструменты

Посмотрев просторы интернета, первым делом на глаза мне попалась библиотека OpenCV, эта библиотека имеет интерфейсы на различных языках, среди которых Python, Java, C++ и Matlab. Мне стало интересно, есть ли у Python стандартная библиотека для работы с изображениями и вот она – Pillow. Это форк PIL, которая успешно развивается и был принят в качестве замены оригинальной библиотеки. Свой выбор я остановил на ней.

Решение задачи

Начнем работу с библиотекой, и попробуем открыть файл и показать его.

from PIL import Image #указываем необходимое имя файла im=Image.open('cbcf449ffc010b9f958d611e787fa48092ac31841.jpg') # Покажет нам изображение. im.show()

Данный скрипт откроет нам изображение. Почитав документацию, я нашел функцию, которая по пикселям сравнивает два изображения и выдает разницу. Функция называется difference и находится в модуле ImageChops. Что бы показать принцип работы функции, для примера возьмем фотографию и добавим на нее какой-нибудь текст:

Python. Как сравнить фотографии?
Python. Как сравнить фотографии?
from PIL import Image, ImageChops image_1=Image.open('06ebe74e5dfc3bd7f5e480cf611147bac45c33d2.jpg') image_2=Image.open('06ebe74e5dfc3bd7f5e480cf611147bac45c33d2_text.jpg') result=ImageChops.difference(image_1, image_2) result.show() #Вычисляет ограничивающую рамку ненулевых областей на изображении. print(result.getbbox()) # result.getbbox() в данном случае вернет (0, 0, 888, 666) result.save('result.jpg')

result.show() вернет разницу в пикселях. Так же прошу обратить внимание на result.getbbox(), функция либо вернет рамку где расходятся пиксели, либо вернет None если картинки идентичны. Если мы сравним первую картинку саму с собой, то получим полностью черное изображение.

Python. Как сравнить фотографии?

Напишем простенькую функцию по сравнению двух картинок:

def difference_images(img1, img2): image_1 = Image.open(img1) image_2 = Image.open(img2) result=ImageChops.difference(image_1, image_2).getbbox() if result==None: print(img1,img2,'matches') return

Теперь необходимо подумать над алгоритмом перебора имеющихся изображений.

path='images/' #Путь к папке где лежат файлы для сравнения imgs=os.listdir(path) check_file=0 #Проверяемый файл current_file=0 #Текущий файл while check_file<len(imgs): if current_file==check_file: current_file+=1 continue difference_images(path+imgs[current_file], path+imgs[check_file]) current_file+=1 if current_file==len(imgs): check_file+=1 current_file=check_file

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

А если файлов для сравнения очень много и их обработка очень долгая? Можно пойти двумя способами:

  • Создать миниатюры и работать с ними.
  • Запустить нашу обработку в несколько потоков.

Первый способ простой, в нашу функцию difference_images добавляем несколько строк:

def difference_images(img1, img2): image_1 = Image.open(img1) image_2 = Image.open(img2) size = [400,300] #размер в пикселях image_1.thumbnail(size) #уменьшаем первое изображение image_2.thumbnail(size) #уменьшаем второе изображение #сравниваем уменьшенные изображения result=ImageChops.difference(image_1, image_2).getbbox() if result==None: print(img1,img2,'matches') return

Второй способ уже сложнее и более интересный, потому что нужно будет управлять и потоками, и очередями, так же нужно будет переписать часть кода. Для этого нам понадобятся следующие библиотеки threading и Queue (подробней можно почитать в интернете), ниже приведен готовый код с внесенными изменениями, я постарался прокомментировать все действия что бы было понятно:

class diff_image(threading.Thread): #класс по сравнению картинок. """Потоковый обработчик""" def __init__(self, queue): """Инициализация потока""" threading.Thread.__init__(self) self.queue = queue def run(self): """Запуск потока""" while True: # Получаем пару путей из очереди files = self.queue.get() # Делим и сравниваем self.difference_images(files.split(':')[0],files.split(':')[1]) # Отправляем сигнал о том, что задача завершена self.queue.task_done() def difference_images(self, img1, img2): image_1 = Image.open(img1) image_2 = Image.open(img2) size = [400,300] #размер в пикселях image_1.thumbnail(size) #уменьшаем первое изображение image_2.thumbnail(size) #уменьшаем второе изображение result=ImageChops.difference(image_1, image_2).getbbox() if result==None: print(img1,img2,'matches') return def main(path): imgs=os.listdir(path) #Получаем список картинок queue = Queue() # Запускаем поток и очередь for i in range(4): # 4 - кол-во одновременных потоков t = diff_image(queue) t.setDaemon(True) t.start() # Даем очереди нужные пары файлов для проверки check_file=0 current_file=0 while check_file<len(imgs): if current_file==check_file: current_file+=1 continue queue.put(path+imgs[current_file]+':'+path+imgs[check_file]) current_file+=1 if current_file==len(imgs): check_file+=1 current_file=check_file # Ждем завершения работы очереди queue.join() if __name__ == "__main__": path='images/' main(path)

Резюме

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

Надеюсь, моя статья была полезна. Спасибо за внимание.

2020
реклама
разместить
14 комментариев

Достаточно ли оптимально?
Может, для каждой картинки сперва посчитать хэши, а потом уже хэши сравнивать? По ощущениям, должно быть быстрее.

2

100% быстрее. 
Из простых решений это phash.org, для похожести можно еще расстояние Хэмминга использовать. Загнать в постгрес, индексы которого помогут быстрее матчинг проводить, например, можно на это посмотреть https://github.com/fake-name/pg-spgist_hamming

2

Можно для начала сравнить размеры в байтах

Хорошая статейка, спасибо!

1

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

вот мой код, надеюсь он вам поможет:

import os
from PIL import Image, ImageChops

print('Введите ПОЛНЫЙ путь к папке: ', end = '')
directory_in_str = input()

directory = os.fsencode(directory_in_str)
imgs = os.listdir(directory_in_str)
dublicates = {}
try:
    for file in os.listdir(directory):
        filename = os.fsdecode(file)
        last_name = directory_in_str
        last_name += '\\' + filename
        f_info = os.stat(last_name)
        if f_info.st_size in dublicates:
            image_1 = Image.open(last_name)
            image_2 = Image.open(dublicates[f_info.st_size])
            result = ImageChops.difference(image_1, image_2)
            result = result.getbbox()
                if result == None:
                    couter += 1
                    print(f'Найден дубликат: [{last_name} и {dublicates[f_info.st_size]}]')
                else:
                    dublicates[f_info.st_size] = last_name
except:
    print('Ошибка поиска директории!')

print(f'Программа завершила работу, файлов обработано [{couter}].')

1
Автор

Спасибо за готовое решение!

80 нейросетей, которые помогут тебе разобраться в теме и проанализируют десятки источников. Залил в одну табличку + краткое описание — эпичная подборка
80 нейросетей, которые помогут тебе разобраться в теме и проанализируют десятки источников. Залил в одну табличку + краткое описание — эпичная подборка
2929
11
реклама
разместить
Сооснователь файлообменника The Pirate Bay Карл Лундстрём погиб в авиакатастрофе

Ему было 64 года.

Сооснователь файлообменника The Pirate Bay Карл Лундстрём погиб в авиакатастрофе
1414
22
Баффет больше неактуален?

Может ли быть такое, что человек, который заработал миллиарды, однажды станет неактуальным? А что, если этот человек Уоррен Баффет? Многие люди слышали что-то вроде: «Баффет — классика, однако время его стратегий ушло». Однако есть и противоположное мнение: «Баффет предоставил вечные принципы инвестиций». В статье мы вместе разберемся в том, что из…

Баффет больше неактуален?
Банки предложили разрешить им замораживать деньги на счету получателя при подозрении на мошеннический перевод

Сейчас блокировать деньги можно только на счету отправителя.

1111
33
22
11
То есть теперь можно будет перевести кому-то деньги за товар/услугу, написать а банк, что "не согласен", и тебе их обратно вернут?) Такое впечатление, что законы пишутся специально под мошенников. Чтоб у них ещё больше было вариантов.
Средняя стоимость рекламных постов в Telegram-каналах за 2024 год выросла на 38%

Сильнее всего в новостных — на 45%, до 273 тысяч рублей.

Фото Unsplash
44
Стартап Nex выпустил нейросеть, которая «сканирует» продукт и генерирует предметные фотосессии

Команда начинала с генератора картинок, но потом перешла на решения для корпоративных клиентов.

3838
66
да блэт я не успеваю регистрироваться в новых нейросетях 😱😱😱😱
Самый сложный поход на налоговую комиссию

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

Самый сложный поход на налоговую комиссию
Binance привлекла $2 млрд от фонда властей Абу-Даби MGX — это крупнейшая инвестиция в криптокомпанию

И первое вливание от институционального инвестора именно для Binance.

Источник фото: Kayak
77
[]