Last active
November 12, 2024 16:22
-
-
Save bobuk/4522091 to your computer and use it in GitHub Desktop.
Сегодня я вам покажу, как написать маленький скриптик, который умеет искать похожие фотографии.
Зачем? Ну, если честно - совсем без причины.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
# | |
# Video of this screencast: https://vimeo.com/57296525 | |
# | |
# | |
from __future__ import print_function, division, absolute_import | |
from PIL import Image as pImage | |
import numpy | |
import os | |
import random | |
class Image: | |
"""Take an information from image file""" | |
BLOCK_SIZE = 20 | |
TRESHOLD = 60 | |
def __init__(self, filename): | |
self.filename = filename | |
def load(self): | |
img = pImage.open(self.filename) | |
small = img.resize( (Image.BLOCK_SIZE, Image.BLOCK_SIZE), | |
pImage.BILINEAR ) | |
self.t_data = numpy.array( | |
[sum(list(x)) for x in small.getdata()] | |
) | |
del img, small | |
return self | |
def __repr__(self): | |
return self.filename | |
def __mul__(self, other): | |
return sum(1 for x in self.t_data - other.t_data if abs(x) > Image.TRESHOLD) | |
class ImageList: | |
"""List of images information, built from directory. | |
All files must be *.jpg""" | |
def __init__(self, dirname): | |
self.dirname = dirname | |
self.load() | |
def load(self): | |
self.images = \ | |
[Image(os.path.join(self.dirname, filename)).load() \ | |
for filename in os.listdir(self.dirname) | |
if filename.endswith('.jpg')] | |
random.shuffle(self.images) | |
return self | |
def __repr__(self): | |
return '\n'.join( ( x.filename for x in self.images ) ) | |
def html(self): | |
res = ['<html><body>'] | |
for img in self.images: | |
distances = sorted([ (img * x, x) for x in self.images ]) | |
res += [ | |
'<img src="' + os.path.basename(x.filename) + '" width="200"/>' + str(dist) | |
for dist, x in distances if dist < 220] | |
res += ['<hr/>'] | |
res += ['</body></html>'] | |
return '\n'.join(res) | |
if __name__ == '__main__': | |
il = ImageList('/Users/bobuk/,misc/wm') | |
print(il.html()) |
IOError: decoder jpeg not available
pip remove PIL
sudo apt-get install python-image
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Спасибо за код и скринкаст - очень понятно и подробно. Возможно даже применю его в скором времени в реальном проекте, если получится. Именно поэтому полез его тестить. Нашел один баг (поправьте меня, если я ошибаюсь). Функция getdata() возвращает список из кортежей, каждый из которых содержит 3 целых числа в диапазоне от 0 до 255 - т.е. каждый пиксел идентифицируется RGB-вектором. Каждому такому кортежу сопоставляется число - сумма элементов. Например, если кортеж будет такой (1, 2, 3), то ему сопоставится число 6, а если такой (3, 2, 1) или такой (2, 2, 2), то тоже 6.
[sum(x) for x in img.getdata()]
Поэтому такие пикселы будут равны, а на самом деле это два разных пиксела.
Контрпример:
флаг 3х цветов (1, 2, 3), (2, 2, 2), (3, 2, 1) и флаг двух цветов (1, 2, 3), (2, 2, 2) - это два разных флага, но алгоритм, переводящий все в серый, будет их считать полностью идентичными изображениями.
Поэтому я немного модифицировал код https://gist.github.com/4579268 (взял сразу отрефакторенный, хорошо написан, спасибо). Число вектору не сопоставлял, а оставил вектор как есть.
[x for x in img.getdata()]
А после вычисления разности двух матриц посчитал евклидову норму разницы векторов (расстояние между ними в евклидовом пространстве RGB): sqrt(R^2 + G^2 + B^2).
sum(1 for x in data1 - data2 if math.sqrt(x[0]_x[0] + x[1]_x[1] + x[2]*x[2]) > THRESHOLD)
На моих тестах получилось, что получше группирует, чем исходный вариант программы. Кое-какие файлы исходная программа группировала лучше, но таких всего пара была, а измененный вариант работал лучше на большем числе файлов (т.е. объединял их в группы, а исходный вариант этого не делал). Вобщем, ошибка накапливается. И вообще, идея переводить все в серый мне кажется не очень хорошей, так как, во-первых, теряется много ценной информации - цвет - а также, способов преобразования в серый существует, насколько я понял, больше одного. Поэтому я бы так делать не стал. Это все равно что сравнивать фото, отснятые ночью.
И еще интересно, как этот скрипт можно адаптировать на большой объем картинок в базе (на С, pypy переписывать?). Работает он довольно долго.
Upd: преобразование из цветного изображения в черно-белое корректно делается не через среднее арифметическое цветов (но так, вроде бы тоже можно), а вот так (цитата отсюда guildalfa.ru/alsha/node/14):
"Хорошо известна формула для вычисления яркости цветного изображения по интенсивности цветовых составляющих:
Lum := Red * 0.299 + Green * 0.587 + Blue * 0.114;
Новое значение цвета пикселя в градациях серого получается при помощи задания одинаковой интенсивности всех цветовых составляющих:
Color := Lum * $00010101;"
Но при таком преобразовании, если, например, нужно сравнить два рисунка с одинаковыми котами в черной комнате, то, если один из котов будет светло-розовый, а другой - темно-синий (т.е. яркость котов будет разная), и это даже не говоря о том, что кто-то раскрасит, к примеру, котов пятнами разного цвета и формы, оставив сами рисунки одинаковым, то оттенки серого в преобразованных рисунках будут различны, поэтому сравнивать корректно алгоритм их не будет. Для более корректного сравнения необходимо оставить на картинке только пикселы двух цветов - черного и белого - причислив каждый пиксел исходной картинки к какой-то из этих 2х групп, но от бинарности точность может и упасть. Более сильным критерием при сравнении все таки является не сама яркость в точке, а характер ее изменения в ней, что и используется в методах сравнения изображений через рассчет характера изменений яркости в ключевых точках двух изображений через рассчет некоторого хеша в точке и сравнения этих хешей (в качестве хешей берутся производные различного типа по яркости) в этих точках (2 ссылка):
http://stackoverflow.com/questions/1005115/what-algorithm-could-be-used-to-identify-if-images-are-the-same-or-similar-re
http://stackoverflow.com/questions/10984313/opencv-2-4-1-computing-surf-descriptors-in-python