Поиск похожих картинок
Недавно всплывал вопрос о поиске одинаковых картинок. Я хотел применить ImageMagick, но сломал голову его инструкцией и решил начать с решения попримитивнее. Питон 3.10 с установленными пакетами NumPy, Pillow и python-magic. Каждая картинка конвертируется в 24-битную 20x20, затем считается евклидово расстояние между каждой парой картинок (как корень из суммы квадратов разностей для каждого байта). Дефолтное MAX_IMAGE_PIXELS оказалось недостаточным для крупных сканов. Для простоты ограничился типами PNG, JPEG и GIF и файлами только в текущей директории — если нужно что-то сложнее, os.listdir()
нужно заменить на соответствующий список или генератор, например,
[os.path.join(root, file) for root, dirs, files in os.walk(os.getcwd()) for file in files]
или [r + '/' + f for r, _, files in os.walk('.') for f in files]
.
import os, magic
import numpy as np
from PIL import Image
thumb_size = 20
max_distance = ( 256**2 * thumb_size**2 * 3 )**0.5
allow_magic = {'PNG image ', 'JPEG image', 'GIF image '}
Image.MAX_IMAGE_PIXELS = 400_000_000
names = sorted( name for name in os.listdir() if os.path.isfile(name) and magic.from_file(name)[:10] in allow_magic )
thumbs = [ Image.open(name).convert(mode='RGB').resize((thumb_size, thumb_size)) for name in names ]
td = [ np.frombuffer(thumb.tobytes(), dtype=np.int8) for thumb in thumbs ]
table = np.full((len(names), len(names)), max_distance, dtype=np.float64)
for nout, hout in enumerate(td):
for nin, hin in enumerate(td[nout+1:]):
table[nout, nin + nout + 1] = np.linalg.norm(hout - hin)
Для идентичных картинок расстояние равно нулю. Для отличающихся размером и артефактами сжатия — существенно меньше 100. Пока ни разу не видел расстояния больше 3500.
Дальше нужно просматривать похожие пары. Дефолтный просмотрщик меня не устроил, поэтому nomacs. Для нулей я вызывал
m = table.min(); r = np.where(table == m); print(m, r);
for x, y in zip(r[0], r[1]): print(names[x], names[y]); os.system( f'nomacs "{names[x]}" & nomacs "{names[y]}"' )
А разобравшись с файлами, заменил все нули на недостижимо большую величину:
table[r] = max_distance
Для расстояний больше 0 вручную повторял однострочник
table[r] = max_distance; m = table.min(); r = np.where(table == m); print(m, r); x, y = r[0]; names[x], names[y]; os.system( f'nomacs "{names[x]}" & nomacs "{names[y]}"'
пока не надоело. В принципе, в таблице могут найтись одинаковые расстояния, поэтому вручную контролировал, что в r
вернуло только 1 пару номеров, но этого не произошло.
Для 20 000 картинок, из которых большинство размером 100-1000 пикселов, и которые прочлись в кеш, время вычисления на 1,8 ГГц ядре:
name (определяется magic.from_file) — 10 с,
thumbs (Image.open + Image.convert + Image.resize) — 272 c,
td (np.frombuffer + thumb.tobytes) — 0,3 с,
table — 3276 c.
Как-то улучшить можно? Ускорить?