-
-
Save bobuk/4522091 to your computer and use it in GitHub Desktop.
#!/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()) |
Мне вот такой стиль нравится:
self.images = [
Image(os.path.join(self.dirname, filename)).load()
for filename in os.listdir(self.dirname)
if filename.endswith('.jpg')
]
Начал его использовать под впечатлением от http://en.wikipedia.org/wiki/FLWOR
Спасибо за код и скринкаст - очень понятно и подробно. Возможно даже применю его в скором времени в реальном проекте, если получится. Именно поэтому полез его тестить. Нашел один баг (поправьте меня, если я ошибаюсь). Функция 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
IOError: decoder jpeg not available
pip remove PIL
sudo apt-get install python-image
Мой вариант на java, правда работает менее точно.