Python. Как сравнить фотографии?
Наверняка у каждого из Вас есть большой домашний архив фотографий, а в нем лежат собственные снимки и фотографии, которыми с Вами поделились родственники. Просматривая свою фототеку, Вы наткнулись на дубли и тут же возник вопрос – сколько же еще таких? В этой статье я поделюсь тем, как я решал свою задачу по поиску одинаковых фотографий.
Совсем недавно у меня появилась интересная задача – необходимо было найти одинаковые фотографии на разных объектах недвижимости. Т.е. к объектам недвижимости расположенных с разным местоположением крепилась одна и та же фотография, может ошибочно, может специально, но такие объекты надо было найти. И я хотел бы поделиться тем, как я решал эту задачу. Для примера у Вас может быть домашняя фототека.
Инструменты
Посмотрев просторы интернета, первым делом на глаза мне попалась библиотека OpenCV, эта библиотека имеет интерфейсы на различных языках, среди которых Python, Java, C++ и Matlab. Мне стало интересно, есть ли у Python стандартная библиотека для работы с изображениями и вот она – Pillow. Это форк PIL, которая успешно развивается и был принят в качестве замены оригинальной библиотеки. Свой выбор я остановил на ней.
Решение задачи
Начнем работу с библиотекой, и попробуем открыть файл и показать его.
Данный скрипт откроет нам изображение. Почитав документацию, я нашел функцию, которая по пикселям сравнивает два изображения и выдает разницу. Функция называется difference и находится в модуле ImageChops. Что бы показать принцип работы функции, для примера возьмем фотографию и добавим на нее какой-нибудь текст:
result.show() вернет разницу в пикселях. Так же прошу обратить внимание на result.getbbox(), функция либо вернет рамку где расходятся пиксели, либо вернет None если картинки идентичны. Если мы сравним первую картинку саму с собой, то получим полностью черное изображение.
Напишем простенькую функцию по сравнению двух картинок:
Теперь необходимо подумать над алгоритмом перебора имеющихся изображений.
Данный алгоритм перебирает все файлы в папке и сравнивает их между собой исключая проверку между собой и файлы, которые уже были проверены на совпадение.
А если файлов для сравнения очень много и их обработка очень долгая? Можно пойти двумя способами:
- Создать миниатюры и работать с ними.
- Запустить нашу обработку в несколько потоков.
Первый способ простой, в нашу функцию difference_images добавляем несколько строк:
Второй способ уже сложнее и более интересный, потому что нужно будет управлять и потоками, и очередями, так же нужно будет переписать часть кода. Для этого нам понадобятся следующие библиотеки threading и Queue (подробней можно почитать в интернете), ниже приведен готовый код с внесенными изменениями, я постарался прокомментировать все действия что бы было понятно:
Резюме
В результате мы получили готовый алгоритм для поиска одинаковых картинок, а так же постарались ускорить обработку файлов двумя способами. Завершив свою задачу, я обнаружил 1227 совпадений в выборке из 6616 картинок.
Надеюсь, моя статья была полезна. Спасибо за внимание.
Достаточно ли оптимально?
Может, для каждой картинки сперва посчитать хэши, а потом уже хэши сравнивать? По ощущениям, должно быть быстрее.
100% быстрее.
Из простых решений это phash.org, для похожести можно еще расстояние Хэмминга использовать. Загнать в постгрес, индексы которого помогут быстрее матчинг проводить, например, можно на это посмотреть https://github.com/fake-name/pg-spgist_hamming
Можно для начала сравнить размеры в байтах
Хорошая статейка, спасибо!
вместо того, чтобы проверять картинку на соответствие каждой следующей, лучше просто сделать словарь (размер : расположение) и если размер картинки будет в словаре, то сравнивать их контент. В случае совпадения будет выводиться сообщение с расположением этих одинаковых картинок
вот мой код, надеюсь он вам поможет:
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}].')
Спасибо за готовое решение!
Вот сервис похожий https://jarjad.ru/compare-images/