Совсем недавно у меня появилась интересная задача – необходимо было найти одинаковые фотографии на разных объектах недвижимости. Т.е. к объектам недвижимости расположенных с разным местоположением крепилась одна и та же фотография, может ошибочно, может специально, но такие объекты надо было найти. И я хотел бы поделиться тем, как я решал эту задачу. Для примера у Вас может быть домашняя фототека.
Инструменты
Посмотрев просторы интернета, первым делом на глаза мне попалась библиотека 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 картинок.
Надеюсь, моя статья была полезна. Спасибо за внимание.