Наверное, почти у каждого из нас на жестком диске компьютера скапливается довольно большое количество цифровых изображений. Будь то фото или просто картинки, когда то скачанные из интернета и благополучно забытые в завалах. Порою размеры таких скоплений достигают довольно больших размеров. И одна из причин, по которым такое может случиться – это дубликаты. Сравнивать картинки вручную – то еще удовольствие. Утомительно, да и гарантии того, что не будет пропущено что-то, понятное дело нет. А что, если для этих целей использовать Python? Давайте попробуем сделать скрипт, который в автоматическом режиме проверит папку с фото, выведет в терминал пути к предположительно совпадающим изображениям и сохранит результаты проверки в текстовый файл.
Что потребуется?
Установим библиотеку Pillow. Именно в ней, в модуле ImageChops, есть функция difference, которая и находит разницу в изображениях. Для установки Pillow пишем в терминале:
И установим библиотеку colorama. С ее помощью мы будем раскрашивать вывод в терминале.
Теперь нужно импортировать в скрипт все вспомогательные библиотеки, которые в нем будут использоваться и сразу же инициализируем colorama.
Сравнение изображений
Создадим функцию check_pictures(pic1, pic2), которая будет получать пути к сравниваемым изображениям.
Теперь нужно их открыть. Сделаем это с помощью модуля Image, куда передадим путь к изображению. Ну, а так как изображения два, то следовательно, создадим две переменные, которые будут содержать объект изображения.
Для того, чтобы не сравнивать оригинальные размеры фото, а их размеры могут быть довольно большими. Тут уж все зависит от матрицы вашего аппарата, сделаем их миниатюры. Для этого есть специальная функция thumbnail, в которую нужно передать размер уменьшенного изображения.
Теперь будем сравнивать изображения. Используем функцию getbbox(), которая возвращает None, если изображения одинаковые или рамку с разницей. То есть, мы этим и воспользуемся. Если функция вернет None, следовательно, изображения совпадают.
В этом случае, мы будем считать, что нашли искомые совпадающие изображения, выводим в терминал сообщение, что они найдены, а также путь к каждому из них. Затем записываем в текстовый документ найденные значения.
Если же изображения разные, просто завершаем работу функции:
Перебор изображений для сравнения
Для того, чтобы сравнить изображения, нам нужно как-то прочитать их. Запрашиваем у пользователя путь к директории с изображениями. Проверяем ее наличие. Если директории не существует, сообщаем об этом пользователю и выходим из скрипта.
Если же все в порядке, с помощью функции библиотеки os, listdir, считываем содержимое директории.
Создадим две числовые переменные. Одна будет равна проверяемому изображению, другая текущему.
Создадим цикл, который будет работать, пока число проверяемого изображения меньше длины списка файлов. Для начала сравним созданные ранее переменные. И если они равны, значит сравнивать нечего, потому увеличиваем значение текущего изображения на 1 и переходим к следующей итерации.
На следующей итерации сравниваем изображения, одно за другим с текущим. И после сравнения увеличиваем число в проверяемом изображении на 1, чтобы проверить следующее. Таким образом, мы доходим до конца цикла. Здесь срабатывает исключение, по которому мы из цикла и выходим.
Таким образом, можно довольно быстро прошерстить весь архив с фото. Конечно, здесь, для ускорения, можно использовать потоки. Но, в данном случае работа скрипта меня полностью устроила. Сорок изображений были сравнены за 3 секунды.
А на этом, пожалуй, все.
Спасибо за внимание. Надеюсь, что данная информация будет вам полезна
UPD:
Спасибо за замечание по поводу сравнения файлов f22. С учетом предложенных им вариантов, если сравнивать файлы по размеру и hash, скорость конечно же увеличивается в разы, с почти 3 до 0,06 секунды. На том же количестве файлов. Таким образом, приняв к сведению замечания, я слегка изменил код. Теперь он не сравнивает изображения с помощью PIL, а сравнивает размеры файлов и их hash. Если оба параметра совпадают, тогда считаем, что файлы одинаковые, и выводим соответствующее сообщение.
Таким образом, скрипт получается более универсальным. Конечно, стопроцентной точности гарантировать не получиться, но, что-то находит. А универсальность его в том, что он, по-сути, сравнивает любые файлы, а не только изображения.
Однако, хотел бы заметить, одну небольшую деталь. Если вы, для примера, не будете производить манипуляций с изображением, то есть сама по себе картинка останется такой же, а всего лишь удалите из нее метаданные, то скрипт приведенный ниже уже не определит, что изображения одинаковые. Так как измениться как размер картинки, так и ее хэш. А вот скрипт приведенный выше, с этой задачей справляется.
Таким образом, приведенные вами в пример методы сравнения могут использоваться как вспомогательные. Чтобы не быть голословным, приведу пример:
Таким образом, сравнение по размеру сразу же отпадает, так как размеру у них разный. Сравнил по хэшу:
Как видно, он тоже не совпадает. Хотя изображения идентичны. Таким образом, сравнение ни по размеру, ни по хэшу не покажет идентичности.
А вот вышеприведенная функция, несмотря на то, что нет метаданных в одной из картинок, а также их размер различается, совпадения нашла:
Плюс к тому же, если файлы сохранены в разных форматах, к примеру: jpg и png, сравнение по размеру и по хэш ничего не даст. А вот сравнение с помощью diffirence, несмотря на то, что изображения в разных форматах, все равно определяет, что картинки идентичны.
А вот создание миниатюр можно убрать. Изначально предполагалось, что создание миниатюр несколько увеличит скорость чтения пикселей на изображениях. Но, так как на создание миниатюр все же тратиться процессорное время, скорость работы как с созданием, так и без будет примерно одинаковой.
Что потребуется?
Установим библиотеку 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 секунды.
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. Если оба параметра совпадают, тогда считаем, что файлы одинаковые, и выводим соответствующее сообщение.
Таким образом, скрипт получается более универсальным. Конечно, стопроцентной точности гарантировать не получиться, но, что-то находит. А универсальность его в том, что он, по-сути, сравнивает любые файлы, а не только изображения.
Однако, хотел бы заметить, одну небольшую деталь. Если вы, для примера, не будете производить манипуляций с изображением, то есть сама по себе картинка останется такой же, а всего лишь удалите из нее метаданные, то скрипт приведенный ниже уже не определит, что изображения одинаковые. Так как измениться как размер картинки, так и ее хэш. А вот скрипт приведенный выше, с этой задачей справляется.
Таким образом, приведенные вами в пример методы сравнения могут использоваться как вспомогательные. Чтобы не быть голословным, приведу пример:
Таким образом, сравнение по размеру сразу же отпадает, так как размеру у них разный. Сравнил по хэшу:
Как видно, он тоже не совпадает. Хотя изображения идентичны. Таким образом, сравнение ни по размеру, ни по хэшу не покажет идентичности.
А вот вышеприведенная функция, несмотря на то, что нет метаданных в одной из картинок, а также их размер различается, совпадения нашла:
Плюс к тому же, если файлы сохранены в разных форматах, к примеру: jpg и png, сравнение по размеру и по хэш ничего не даст. А вот сравнение с помощью diffirence, несмотря на то, что изображения в разных форматах, все равно определяет, что картинки идентичны.
А вот создание миниатюр можно убрать. Изначально предполагалось, что создание миниатюр несколько увеличит скорость чтения пикселей на изображениях. Но, так как на создание миниатюр все же тратиться процессорное время, скорость работы как с созданием, так и без будет примерно одинаковой.
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()
Последнее редактирование: