python - ¿Cómo puedo cuantificar la diferencia entre dos imágenes?
image-processing background-subtraction (18)
Idea general
Opción 1: cargue ambas imágenes como matrices ( scipy.misc.imread
) y calcule una diferencia de elemento (píxel por píxel). Calcule la norma de la diferencia.
Opción 2: Cargue ambas imágenes. Calcule algún vector de características para cada uno de ellos (como un histograma). Calcule la distancia entre vectores de características en lugar de imágenes.
Sin embargo, hay algunas decisiones que tomar primero.
Preguntas
Debe responder estas preguntas primero:
¿Son imágenes de la misma forma y dimensión?
De lo contrario, es posible que deba cambiar el tamaño o recortarlos. La biblioteca PIL ayudará a hacerlo en Python.
Si se toman con la misma configuración y el mismo dispositivo, probablemente sean iguales.
¿Las imágenes están bien alineadas?
Si no, es posible que desee ejecutar una correlación cruzada primero, para encontrar la mejor alineación primero. SciPy tiene funciones para hacerlo.
Si la cámara y la escena están quietas, es probable que las imágenes estén bien alineadas.
¿La exposición de las imágenes es siempre la misma? (¿La ligereza / el contraste son iguales?)
Si no, puede querer normalizar las imágenes.
Pero ten cuidado, en algunas situaciones esto puede hacer más mal que bien. Por ejemplo, un único píxel brillante sobre un fondo oscuro hará que la imagen normalizada sea muy diferente.
¿La información de color es importante?
Si desea observar cambios de color, tendrá un vector de valores de color por punto, en lugar de un valor escalar como en una imagen de escala de grises. Necesita más atención al escribir dicho código.
¿Hay bordes distintos en la imagen? ¿Es probable que se muevan?
En caso afirmativo, puede aplicar primero el algoritmo de detección de bordes (por ejemplo, calcular el gradiente con la transformación Sobel o Prewitt, aplicar algún umbral) y luego comparar los bordes de la primera imagen con los bordes de la segunda.
¿Hay ruido en la imagen?
Todos los sensores contaminan la imagen con cierta cantidad de ruido. Los sensores de bajo costo tienen más ruido. Es posible que desee aplicar alguna reducción de ruido antes de comparar imágenes. Blur es el enfoque más simple (pero no el mejor) aquí.
¿Qué tipo de cambios quieres notar?
Esto puede afectar la elección de la norma a usar para la diferencia entre imágenes.
Considere usar la norma de Manhattan (la suma de los valores absolutos) o la norma cero (la cantidad de elementos no igual a cero) para medir cuánto ha cambiado la imagen. El primero le dirá cuánto se apaga la imagen, este último solo le dirá cuántos píxeles difieren.
Ejemplo
Supongo que sus imágenes están bien alineadas, del mismo tamaño y forma, posiblemente con diferente exposición. Para simplificar, los convierto en escala de grises, incluso si son imágenes en color (RGB).
Necesitarás estas importaciones:
import sys
from scipy.misc import imread
from scipy.linalg import norm
from scipy import sum, average
Función principal, leer dos imágenes, convertir a escala de grises, comparar e imprimir resultados:
def main():
file1, file2 = sys.argv[1:1+2]
# read images as 2D arrays (convert to grayscale for simplicity)
img1 = to_grayscale(imread(file1).astype(float))
img2 = to_grayscale(imread(file2).astype(float))
# compare
n_m, n_0 = compare_images(img1, img2)
print "Manhattan norm:", n_m, "/ per pixel:", n_m/img1.size
print "Zero norm:", n_0, "/ per pixel:", n_0*1.0/img1.size
Cómo comparar img1
e img2
son matrices 2D SciPy aquí:
def compare_images(img1, img2):
# normalize to compensate for exposure difference, this may be unnecessary
# consider disabling it
img1 = normalize(img1)
img2 = normalize(img2)
# calculate the difference and its norms
diff = img1 - img2 # elementwise for scipy arrays
m_norm = sum(abs(diff)) # Manhattan norm
z_norm = norm(diff.ravel(), 0) # Zero norm
return (m_norm, z_norm)
Si el archivo es una imagen en color, imread
devuelve una matriz 3D, canales RGB promedio (el último eje de la matriz) para obtener intensidad. No es necesario hacerlo para imágenes en escala de grises (por ejemplo, .pgm
):
def to_grayscale(arr):
"If arr is a color image (3D array), convert it to grayscale (2D array)."
if len(arr.shape) == 3:
return average(arr, -1) # average over the last axis (color channels)
else:
return arr
La normalización es trivial, puede optar por normalizar a [0,1] en lugar de [0,255]. arr
es una matriz SciPy aquí, por lo que todas las operaciones son por elementos:
def normalize(arr):
rng = arr.max()-arr.min()
amin = arr.min()
return (arr-amin)*255/rng
Ejecute la función main
:
if __name__ == "__main__":
main()
Ahora puede poner todo esto en una secuencia de comandos y ejecutar contra dos imágenes. Si comparamos la imagen consigo misma, no hay diferencia:
$ python compare.py one.jpg one.jpg
Manhattan norm: 0.0 / per pixel: 0.0
Zero norm: 0 / per pixel: 0.0
Si borramos la imagen y la comparamos con el original, hay alguna diferencia:
$ python compare.py one.jpg one-blurred.jpg
Manhattan norm: 92605183.67 / per pixel: 13.4210411116
Zero norm: 6900000 / per pixel: 1.0
PS compare.py script completo de compare.py
Actualización: técnicas relevantes
Como la pregunta es acerca de una secuencia de video, donde es probable que los marcos sean casi iguales, y usted busca algo inusual, me gustaría mencionar algunos enfoques alternativos que pueden ser relevantes:
- sustracción de fondo y segmentación (para detectar objetos en primer plano)
- flujo óptico escaso (para detectar movimiento)
- comparando histogramas u otras estadísticas en lugar de imágenes
Recomiendo echar un vistazo al libro "Learning OpenCV", los capítulos 9 (partes de imágenes y segmentación) y 10 (seguimiento y movimiento). El primero enseña a utilizar el método de resta de fondo, el último proporciona cierta información sobre los métodos de flujo óptico. Todos los métodos se implementan en la biblioteca OpenCV. Si usa Python, sugiero usar OpenCV ≥ 2.3 y su módulo cv2
Python.
La versión más simple de la resta de fondo:
- aprende el valor promedio μ y la desviación estándar σ para cada píxel del fondo
- compare los valores de píxel actuales con el rango de (μ-2σ, μ + 2σ) o (μ-σ, μ + σ)
Las versiones más avanzadas tienen en cuenta series de tiempo para cada píxel y manejan escenas no estáticas (como mover árboles o pasto).
La idea del flujo óptico es tomar dos o más cuadros, y asignar un vector de velocidad a cada píxel (flujo óptico denso) o a algunos de ellos (flujo óptico disperso). Para estimar el flujo óptico disperso, puede usar el método Lucas-Kanade (también se implementa en OpenCV). Obviamente, si hay mucho flujo (promedio alto sobre valores máximos del campo de velocidad), entonces algo se mueve en el cuadro, y las imágenes siguientes son más diferentes.
La comparación de histogramas puede ayudar a detectar cambios repentinos entre cuadros consecutivos. Este enfoque fue utilizado en Courbon et al, 2010 :
Similitud de cuadros consecutivos. La distancia entre dos cuadros consecutivos se mide. Si es demasiado alto, significa que el segundo cuadro está dañado y, por lo tanto, la imagen se elimina. La distancia de Kullback-Leibler , o entropía mutua, en los histogramas de los dos cuadros:
donde pyq son los histogramas de los marcos que se utilizan. El umbral está fijado en 0.2.
Esto es lo que me gustaría hacer:
Estoy tomando fotos con una cámara web a intervalos regulares. Algo así como una cosa de lapso de tiempo. Sin embargo, si nada ha cambiado realmente, es decir, la imagen parece casi igual, no quiero almacenar la última instantánea.
Me imagino que hay alguna manera de cuantificar la diferencia, y tendría que determinar empíricamente un umbral.
Estoy buscando simplicidad en lugar de perfección. Estoy usando Python.
¿Has visto el Algoritmo para encontrar una pregunta similar sobre imágenes ? Compruébalo para ver sugerencias.
Sugeriría una transformación wavelet de sus marcos (he escrito una extensión C para eso utilizando la transformación Haar); luego, al comparar los índices de los factores wavelet más grandes (proporcionalmente) entre las dos imágenes, debe obtener una aproximación de similitud numérica.
Algo trivial para probar:
Vuelva a muestrear ambas imágenes en miniaturas pequeñas (por ejemplo, 64 x 64) y compare las miniaturas píxel por píxel con un cierto umbral. Si las imágenes originales son casi las mismas, las miniaturas remuestreadas serán muy similares o incluso exactamente iguales. Este método se ocupa del ruido que puede ocurrir especialmente en escenas con poca luz. Incluso puede ser mejor si vas en escala de grises.
Creo que podría simplemente calcular la distancia euclidiana (es decir, sqrt (suma de cuadrados de diferencias, píxel por píxel)) entre la luminancia de las dos imágenes y considerarlas iguales si caen por debajo de algún umbral empírico. Y será mejor que lo hagas envolviendo una función C.
Dos métodos populares y relativamente simples son: (a) la distancia euclidiana ya sugerida, o (b) correlación cruzada normalizada. La correlación cruzada normalizada tiende a ser notablemente más robusta a los cambios de iluminación que la simple correlación cruzada. Wikipedia da una fórmula para la correlación cruzada normalizada . También existen métodos más sofisticados, pero requieren bastante más trabajo.
Usando sintaxis tipo numpy,
dist_euclidean = sqrt(sum((i1 - i2)^2)) / i1.size dist_manhattan = sum(abs(i1 - i2)) / i1.size dist_ncc = sum( (i1 - mean(i1)) * (i2 - mean(i2)) ) / ( (i1.size - 1) * stdev(i1) * stdev(i2) )
suponiendo que i1
e i2
son matrices de imágenes 2D en escala de grises.
He tenido mucha suerte con las imágenes jpg tomadas con la misma cámara en un trípode al: (1) simplificar enormemente (como pasar de 3000 píxeles de ancho a 100 píxeles de ancho o incluso menos) (2) aplanar cada matriz de jpg en una sola vector (3) imágenes secuenciales correlacionadas por pares con un algoritmo de correlación simple para obtener coeficiente de correlación (4) coeficiente de correlación de cuadratura para obtener r-cuadrado (es decir, fracción de variabilidad en una imagen explicada por variación en la siguiente) (5) generalmente en mi aplicación si r-square <0.9, digo que las dos imágenes son diferentes y algo sucedió en el medio.
Esto es robusto y rápido en mi implementación (Mathematica 7)
Vale la pena jugar con la parte de la imagen que le interesa y centrarse en eso recortando todas las imágenes en esa pequeña área; de lo contrario, se perderá un cambio distante de la cámara pero importante.
No sé cómo usar Python, pero estoy seguro de que también hace correlaciones, ¿no?
La mayoría de las respuestas dadas no tratarán los niveles de iluminación.
Primero normalizaría la imagen a un nivel de luz estándar antes de hacer la comparación.
Me refiero específicamente a la cuestión de cómo calcular si son "lo suficientemente diferentes". Supongo que puedes descubrir cómo restar los píxeles uno por uno.
Primero, tomaría un montón de imágenes sin cambiar nada , y averiguaría la cantidad máxima que cualquier píxel cambia, solo por las variaciones en la captura, el ruido en el sistema de imágenes, los artefactos de compresión JPEG y los cambios de iluminación momento a momento. . Quizás encuentre que se esperan diferencias de 1 o 2 bits, incluso cuando nada se mueve.
Luego, para la prueba "real", quiere un criterio como este:
- lo mismo si hasta P píxeles difieren en no más de E.
Entonces, quizás, si E = 0.02, P = 1000, eso significaría (aproximadamente) que sería "diferente" si cualquier píxel individual cambia en más de ~ 5 unidades (suponiendo imágenes de 8 bits), o si son más de 1000 los píxeles tenían algún error.
Esto se pretende principalmente como una buena técnica de "clasificación" para identificar rápidamente las imágenes que están lo suficientemente cerca como para no necesitar un examen más detenido. Las imágenes que "fallan" pueden ser más una técnica más elaborada / costosa que no tendría falsos positivos si la cámara vibraba, por ejemplo, o era más robusta a los cambios de iluminación.
Ejecuto un proyecto de código abierto, OpenImageIO , que contiene una utilidad llamada "idiff" que compara las diferencias con umbrales como este (incluso más elaborados, en realidad). Incluso si no desea utilizar este software, es posible que desee consultar la fuente para ver cómo lo hicimos. Se usa bastante comercialmente y esta técnica de umbralización se desarrolló para que pudiéramos tener un banco de pruebas para el procesamiento y el software de procesamiento de imágenes, con "imágenes de referencia" que podrían tener pequeñas diferencias de plataforma a plataforma o cuando hicimos ajustes menores a tha algoritmos, por lo que queríamos una operación de "coincidencia dentro de la tolerancia".
Mira cómo Haar Wavelets es implementado por isk-daemon . Puede usar su código imgdb C ++ para calcular la diferencia entre imágenes sobre la marcha:
isk-daemon es un servidor de base de datos de código abierto capaz de agregar imágenes basadas en el contenido (visual) a cualquier sitio web o software relacionado con imágenes.
Esta tecnología permite a los usuarios de cualquier sitio web o software relacionado con imágenes esbozar en un widget qué imagen quieren encontrar y hacer que el sitio web les responda las imágenes más similares o simplemente solicitar más fotos similares en cada página de detalles de la imagen.
Otra manera agradable y simple de medir la similitud entre dos imágenes:
import sys
from skimage.measure import compare_ssim
from skimage.transform import resize
from scipy.ndimage import imread
# get two images - resize both to 1024 x 1024
img_a = resize(imread(sys.argv[1]), (2**10, 2**10))
img_b = resize(imread(sys.argv[2]), (2**10, 2**10))
# score: {-1:1} measure of the structural similarity between the images
score, diff = compare_ssim(img_a, img_b, full=True)
print(score)
Si a otros les interesa una forma más poderosa de comparar la similitud de la imagen, armé un tutorial y una app web para medir y visualizar imágenes similares con Tensorflow.
Puede comparar dos imágenes usando funciones de PIL .
import Image
import ImageChops
im1 = Image.open("splash.png")
im2 = Image.open("splash2.png")
diff = ImageChops.difference(im2, im1)
El objeto diff es una imagen en la que cada píxel es el resultado de la resta de los valores de color de ese píxel en la segunda imagen de la primera imagen. Usando la imagen de diferencia puede hacer varias cosas. El más simple es la función diff.getbbox()
. Te dirá el rectángulo mínimo que contiene todos los cambios entre tus dos imágenes.
Probablemente puedas implementar aproximaciones de las otras cosas mencionadas aquí usando funciones de PIL también.
Tuve el mismo problema y escribí un módulo simple de Python que compara dos imágenes del mismo tamaño usando ImageChops de pillow para crear una imagen de diff blanco / negro y resume los valores del histograma.
Puede obtener este puntaje directamente, o un valor porcentual en comparación con un diff completo negro contra blanco.
También contiene una función is_equal simple, con la posibilidad de proporcionar un umbral difuso debajo (e incluyendo) los pases de imagen como iguales.
El enfoque no es muy elaborado, pero tal vez sea útil para otros que luchan con el mismo problema.
Un enfoque algo más basado en principios es usar un descriptor global para comparar imágenes, como GIST o CENTRIST. Una función hash, como se describe here , también proporciona una solución similar.
puedes calcular el histograma de ambas imágenes y luego calcular el Coeficiente Bhattacharyya , este es un algoritmo muy rápido y lo he usado para detectar cambios de disparo en un video de cricket (en C usando openCV)
La distancia de los motores de tierra puede ser exactamente lo que necesitas. Sin embargo, podría ser un poco difícil de implementar en tiempo real.
Una solución simple:
Codifique la imagen como un jpeg y busque un cambio sustancial en el tamaño del archivo .
Implementé algo similar con las miniaturas de video, y tuve mucho éxito y escalabilidad.
import os
from PIL import Image
from PIL import ImageFile
import imagehash
#just use to the size diferent picture
def compare_image(img_file1, img_file2):
if img_file1 == img_file2:
return True
fp1 = open(img_file1, ''rb'')
fp2 = open(img_file2, ''rb'')
img1 = Image.open(fp1)
img2 = Image.open(fp2)
ImageFile.LOAD_TRUNCATED_IMAGES = True
b = img1 == img2
fp1.close()
fp2.close()
return b
#through picturu hash to compare
def get_hash_dict(dir):
hash_dict = {}
image_quantity = 0
for _, _, files in os.walk(dir):
for i, fileName in enumerate(files):
with open(dir + fileName, ''rb'') as fp:
hash_dict[dir + fileName] = imagehash.average_hash(Image.open(fp))
image_quantity += 1
return hash_dict, image_quantity
def compare_image_with_hash(image_file_name_1, image_file_name_2, max_dif=0):
"""
max_dif: The maximum hash difference is allowed, the smaller and more accurate, the minimum is 0.
recommend to use
"""
ImageFile.LOAD_TRUNCATED_IMAGES = True
hash_1 = None
hash_2 = None
with open(image_file_name_1, ''rb'') as fp:
hash_1 = imagehash.average_hash(Image.open(fp))
with open(image_file_name_2, ''rb'') as fp:
hash_2 = imagehash.average_hash(Image.open(fp))
dif = hash_1 - hash_2
if dif < 0:
dif = -dif
if dif <= max_dif:
return True
else:
return False
def compare_image_dir_with_hash(dir_1, dir_2, max_dif=0):
"""
max_dif: The maximum hash difference is allowed, the smaller and more accurate, the minimum is 0.
"""
ImageFile.LOAD_TRUNCATED_IMAGES = True
hash_dict_1, image_quantity_1 = get_hash_dict(dir_1)
hash_dict_2, image_quantity_2 = get_hash_dict(dir_2)
if image_quantity_1 > image_quantity_2:
tmp = image_quantity_1
image_quantity_1 = image_quantity_2
image_quantity_2 = tmp
tmp = hash_dict_1
hash_dict_1 = hash_dict_2
hash_dict_2 = tmp
result_dict = {}
for k in hash_dict_1.keys():
result_dict[k] = None
for dif_i in range(0, max_dif + 1):
have_none = False
for k_1 in result_dict.keys():
if result_dict.get(k_1) is None:
have_none = True
if not have_none:
return result_dict
for k_1, v_1 in hash_dict_1.items():
for k_2, v_2 in hash_dict_2.items():
sub = (v_1 - v_2)
if sub < 0:
sub = -sub
if sub == dif_i and result_dict.get(k_1) is None:
result_dict[k_1] = k_2
break
return result_dict
def main():
print(compare_image(''image1//815.jpg'', ''image2//5.jpg''))
print(compare_image_with_hash(''image1//815.jpg'', ''image2//5.jpg'', 6))
r = compare_image_dir_with_hash(''image1//', image2//', 10)
for k in r.keys():
print(k, r.get(k))
if __name__ == ''__main__'':
main()