Ищем разработчика, который без ума от JavaScript и клёвых анимаций
Machine learning
NTA
5993

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

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

В закладки

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

Инструменты

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

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

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

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

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

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 если картинки идентичны. Если мы сравним первую картинку саму с собой, то получим полностью черное изображение.

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

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 картинок.

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

Лайфхаки IT, проверенные решения для стандартных задач
{ "author_name": "NTA", "author_type": "editor", "tags": ["\u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c","\u0442\u0435\u043a\u0443\u0449\u0438\u0439","\u043f\u0443\u0442\u044c","\u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c\u044b\u0439","\u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442"], "comments": 9, "likes": 18, "favorites": 46, "is_advertisement": false, "subsite_label": "ml", "id": 148619, "is_wide": true, "is_ugc": false, "date": "Fri, 07 Aug 2020 18:13:37 +0300", "is_special": false }
Объявление на vc.ru
0
9 комментариев
Популярные
По порядку
Написать комментарий...
2

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

Ответить
2

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

Ответить
1

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

Ответить
0

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

Ответить
0

Хм. Сначала сравнить размеры в байтах, а потом - сами картинки при совпадении размеров? Не уверен, если честно, что это быстрее сравнений хэшей.

Ответить
1

Такое сравнение можно использовать в качестве пре-проверки, чтобы уменьшить размер списка сравнения.
Пример - есть список картинок "a b c A D e B a c f D g a", где:
- a и а - картинки, одинаковые и по содержанию, и по весу
- а и А - картинки, одинаковые по содержанию, но разные по весу
Если на первом шаге прогнать сравнение только по весу картинки, то будут найдены одинаковые группы "a a а", "с с" и "D D".
Убрав дубликаты, мы получим новый список - "a b c A D e B f g", который, очевидно, короче начального и для которого перебор "все со всеми" будет проходить быстрее.
Открытый вопрос - при каком количеству дубликатов в начальном списке, добавление такой пре-проверки оказывается выигрышным по времени?

Ответить
1

Чел, но ведь две разные картинки могут иметь одинаковый размер, ты оптимизируешь не в ту сторону...

Ответить
0

Могут, не отрицаю, но вероятность, что две разные картинки будут иметь идентичные размер в байтах чертовски мала + можно добавить сравнение по содержанию, чтобы быть уверенным, что это одинаковые картинки.

Ответить
0

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

Ответить

Комментарии

null