Статья Поиск одинаковых изображений с помощью Python

Наверное, почти у каждого из нас на жестком диске компьютера скапливается довольно большое количество цифровых изображений. Будь то фото или просто картинки, когда то скачанные из интернета и благополучно забытые в завалах. Порою размеры таких скоплений достигают довольно больших размеров. И одна из причин, по которым такое может случиться – это дубликаты. Сравнивать картинки вручную – то еще удовольствие. Утомительно, да и гарантии того, что не будет пропущено что-то, понятное дело нет. А что, если для этих целей использовать Python? Давайте попробуем сделать скрипт, который в автоматическом режиме проверит папку с фото, выведет в терминал пути к предположительно совпадающим изображениям и сохранит результаты проверки в текстовый файл.

000.jpg



Что потребуется?

Установим библиотеку Pillow. Именно в ней, в модуле ImageChops, есть функция difference, которая и находит разницу в изображениях. Для установки Pillow пишем в терминале:

pip install Pillow

И установим библиотеку colorama. С ее помощью мы будем раскрашивать вывод в терминале.

pip install colorama

Теперь нужно импортировать в скрипт все вспомогательные библиотеки, которые в нем будут использоваться и сразу же инициализируем colorama.

Python:
import os

from colorama import Fore
from colorama import init
from PIL import Image, ImageChops

init()


Сравнение изображений

Создадим функцию check_pictures(pic1, pic2), которая будет получать пути к сравниваемым изображениям.
Теперь нужно их открыть. Сделаем это с помощью модуля Image, куда передадим путь к изображению. Ну, а так как изображения два, то следовательно, создадим две переменные, которые будут содержать объект изображения.

Python:
    pic_1 = Image.open(pic1)
    pic_2 = Image.open(pic2)

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

Python:
    pic_1.thumbnail((400, 300))
    pic_2.thumbnail((400, 300))

Теперь будем сравнивать изображения. Используем функцию getbbox(), которая возвращает None, если изображения одинаковые или рамку с разницей. То есть, мы этим и воспользуемся. Если функция вернет None, следовательно, изображения совпадают.

res = ImageChops.difference(pic_1, pic_2).getbbox()

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

Python:
    if res is None:
        print(Fore.GREEN + f'\nВозможно совпадение\n{"-"*50}')
        print(Fore.YELLOW + f'   - {pic1}')
        print(Fore.CYAN + f'   - {pic2}')
        with open('result_diff.txt', 'a', encoding='utf-8') as file:
            file.write(f'Возможно совпадение\n{"-"*50}\n   - {pic1}\n   - {pic2}\n\n')

Если же изображения разные, просто завершаем работу функции:

return

Python:
def check_pictures(pic1, pic2):
    pic_1 = Image.open(pic1)
    pic_2 = Image.open(pic2)

    pic_1.thumbnail((400, 300))
    pic_2.thumbnail((400, 300))

    res = ImageChops.difference(pic_1, pic_2).getbbox()
    if res is None:
        print(Fore.GREEN + f'\nВозможно совпадение\n{"-"*50}')
        print(Fore.YELLOW + f'   - {pic1}')
        print(Fore.CYAN + f'   - {pic2}')
        with open('result_diff.txt', 'a', encoding='utf-8') as file:
            file.write(f'Возможно совпадение\n{"-"*50}\n   - {pic1}\n   - {pic2}\n\n')
    return


Перебор изображений для сравнения

Для того, чтобы сравнить изображения, нам нужно как-то прочитать их. Запрашиваем у пользователя путь к директории с изображениями. Проверяем ее наличие. Если директории не существует, сообщаем об этом пользователю и выходим из скрипта.

Python:
    path_pic = input('Введите путь к изображениям для сравнения: ')
    if not os.path.exists(path_pic):
        print(Fore.RED + '[-] Директории не существует')
        return

Если же все в порядке, с помощью функции библиотеки os, listdir, считываем содержимое директории.
Создадим две числовые переменные. Одна будет равна проверяемому изображению, другая текущему.

Python:
    pictures = os.listdir(path_pic)

    check_pic = 0
    cur_pic = 0

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

Python:
    while check_pic < len(pictures):
        if cur_pic == check_pic:
            cur_pic += 1
            continue

На следующей итерации сравниваем изображения, одно за другим с текущим. И после сравнения увеличиваем число в проверяемом изображении на 1, чтобы проверить следующее. Таким образом, мы доходим до конца цикла. Здесь срабатывает исключение, по которому мы из цикла и выходим.

Python:
        try:
            check_pictures(os.path.join(path_pic, pictures[cur_pic]), os.path.join(path_pic, pictures[check_pic]))
            check_pic += 1
        except IndexError:
            break

Python:
def main():
    path_pic = input('Введите путь к изображениям для сравнения: ')

    start = time.monotonic()
    if not os.path.exists(path_pic):
        print(Fore.RED + '[-] Директории не существует')
        return

    pictures = os.listdir(path_pic)

    check_pic = 0
    cur_pic = 0

    while check_pic < len(pictures):
        if cur_pic == check_pic:
            cur_pic += 1
            continue
        try:
            check_pictures(os.path.join(path_pic, pictures[cur_pic]), os.path.join(path_pic, pictures[check_pic]))
            check_pic += 1
        except IndexError:
            break
    print(f'\nВремя работы скрипта: {time.monotonic() - start}')

Таким образом, можно довольно быстро прошерстить весь архив с фото. Конечно, здесь, для ускорения, можно использовать потоки. Но, в данном случае работа скрипта меня полностью устроила. Сорок изображений были сравнены за 3 секунды.

01.png

Python:
# pip install Pillow
# pip install colorama

import os
import time

from colorama import Fore
from colorama import init
from PIL import Image, ImageChops

init()


# уменьшаем изображения и сравниваем друг с другом
def check_pictures(pic1, pic2):
    pic_1 = Image.open(pic1)
    pic_2 = Image.open(pic2)

    pic_1.thumbnail((400, 300))
    pic_2.thumbnail((400, 300))

    res = ImageChops.difference(pic_1, pic_2).getbbox()
    if res is None:
        print(Fore.GREEN + f'\nВозможно совпадение\n{"-"*50}')
        print(Fore.YELLOW + f'   - {pic1}')
        print(Fore.CYAN + f'   - {pic2}')
        with open('result_diff.txt', 'a', encoding='utf-8') as file:
            file.write(f'Возможно совпадение\n{"-"*50}\n   - {pic1}\n   - {pic2}\n\n')
    return


# запускаем цикл и отправляем изображения для сравнения в функцию
def main():
    path_pic = input('Введите путь к изображениям для сравнения: ')

    start = time.monotonic()
    if not os.path.exists(path_pic):
        print(Fore.RED + '[-] Директории не существует')
        return

    pictures = os.listdir(path_pic)

    check_pic = 0
    cur_pic = 0

    while check_pic < len(pictures):
        if cur_pic == check_pic:
            cur_pic += 1
            continue
        try:
            check_pictures(os.path.join(path_pic, pictures[cur_pic]), os.path.join(path_pic, pictures[check_pic]))
            check_pic += 1
        except IndexError:
            break
    print(f'\nВремя работы скрипта: {time.monotonic() - start}')


if __name__ == "__main__":
    main()

А на этом, пожалуй, все.

Спасибо за внимание. Надеюсь, что данная информация будет вам полезна



UPD:


Спасибо за замечание по поводу сравнения файлов f22. С учетом предложенных им вариантов, если сравнивать файлы по размеру и hash, скорость конечно же увеличивается в разы, с почти 3 до 0,06 секунды. На том же количестве файлов. Таким образом, приняв к сведению замечания, я слегка изменил код. Теперь он не сравнивает изображения с помощью PIL, а сравнивает размеры файлов и их hash. Если оба параметра совпадают, тогда считаем, что файлы одинаковые, и выводим соответствующее сообщение.

Таким образом, скрипт получается более универсальным. Конечно, стопроцентной точности гарантировать не получиться, но, что-то находит. А универсальность его в том, что он, по-сути, сравнивает любые файлы, а не только изображения.

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

Таким образом, приведенные вами в пример методы сравнения могут использоваться как вспомогательные. Чтобы не быть голословным, приведу пример:


1.jpg

01.png

02.png

Таким образом, сравнение по размеру сразу же отпадает, так как размеру у них разный. Сравнил по хэшу:

03.png


Как видно, он тоже не совпадает. Хотя изображения идентичны. Таким образом, сравнение ни по размеру, ни по хэшу не покажет идентичности.
А вот вышеприведенная функция, несмотря на то, что нет метаданных в одной из картинок, а также их размер различается, совпадения нашла:

04.png


Плюс к тому же, если файлы сохранены в разных форматах, к примеру: jpg и png, сравнение по размеру и по хэш ничего не даст. А вот сравнение с помощью diffirence, несмотря на то, что изображения в разных форматах, все равно определяет, что картинки идентичны.

06.png


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


Python:
import hashlib
import os
import time

from colorama import Fore
from colorama import init

init()


def check_pictures(pic1, pic2):
    if os.path.isdir(pic1):
        return
    if os.path.isdir(pic2):
        return
    size1 = os.path.getsize(pic1)
    size2 = os.path.getsize(pic2)
    if size1 == size2:
        with open(pic1, 'rb') as file1:
            read1 = file1.read()
            md51 = hashlib.md5(read1).hexdigest()
        with open(pic2, 'rb') as file2:
            read2 = file2.read()
            md52 = hashlib.md5(read2).hexdigest()
        if md51 == md52:
            print(Fore.GREEN + f'\nВозможно совпадение\n{"-" * 50}')
            print(Fore.YELLOW + f'   - {pic1}')
            print(Fore.CYAN + f'   - {pic2}')
            with open('result_diff.txt', 'a', encoding='utf-8') as file:
                file.write(f'Возможно совпадение\n{"-" * 50}\n   - {pic1}\n   - {pic2}\n\n')


def main():
    path_pic = input('Введите путь директории: ')

    start = time.monotonic()
    if not os.path.exists(path_pic):
        print(Fore.RED + '[-] Директории не существует')
        return

    file_list = []
    for root, dirs, files in os.walk(path_pic):
        for file in files:
            file_list.append(f'{root}/{file}')

    check_pic = 0
    cur_pic = 0

    while check_pic < len(file_list):
        if cur_pic == check_pic:
            cur_pic += 1
            continue
        try:
            check_pictures(file_list[cur_pic], file_list[check_pic])
            check_pic += 1
        except IndexError:
            break
    print(f'\nВремя работы скрипта: {time.monotonic() - start}')


if __name__ == "__main__":
    main()

02.png
 
Последнее редактирование:
  • Нравится
Реакции: ROP

f22

Codeby Academy
Gold Team
05.05.2019
1 922
226
BIT
1 669
Сравнение изображений
Теперь нужно их открыть.
Для того, чтобы не сравнивать оригинальные размеры фото, а их размеры могут быть довольно большими. Тут уж все зависит от матрицы вашего аппарата, сделаем их миниатюры. Для этого есть специальная функция thumbnail, в которую нужно передать размер уменьшенного изображения.
Главный вопрос: зачем?
Есть пара десятков способов сравнить файлы не изменяя их...

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

Для пущей надёжности можно использовать оба этих метода, но вот создавать картинку предпросмотра лично мне видится очень странной и очень дорогой с точки зрения нагрузки на процессор идеей.
 
  • Нравится
Реакции: jiltfi и bayramdev
Мы в соцсетях:

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