web-dev-qa-db-fra.com

Comment obtenir une image moyenne de 100 images avec PIL?

Par exemple, j'ai 100 images dont la résolution est la même et je souhaite les fusionner en une seule image. Pour l'image finale, la valeur RVB de chaque pixel est la moyenne des 100 images à cette position. Je sais que la fonction getdata peut fonctionner dans cette situation, mais existe-t-il un moyen plus simple et plus rapide de le faire dans PIL (Python Image Library)?

18
Hanfei Sun

Supposons que vos images sont toutes des fichiers .png et qu'elles sont toutes stockées dans le répertoire de travail actuel. Le code python ci-dessous fera ce que vous voulez. Comme Ignacio le suggère, l’utilisation de numpy avec PIL est la clé ici. Vous devez juste faire preuve de prudence lors du basculement entre des tableaux de nombres entiers et flottants lors de la création de vos intensités de pixels moyennes.

import os, numpy, PIL
from PIL import Image

# Access all PNG files in directory
allfiles=os.listdir(os.getcwd())
imlist=[filename for filename in allfiles if  filename[-4:] in [".png",".PNG"]]

# Assuming all images are the same size, get dimensions of first image
w,h=Image.open(imlist[0]).size
N=len(imlist)

# Create a numpy array of floats to store the average (assume RGB images)
arr=numpy.zeros((h,w,3),numpy.float)

# Build up average pixel intensities, casting each image as an array of floats
for im in imlist:
    imarr=numpy.array(Image.open(im),dtype=numpy.float)
    arr=arr+imarr/N

# Round values in array and cast as 8-bit integer
arr=numpy.array(numpy.round(arr),dtype=numpy.uint8)

# Generate, save and preview final image
out=Image.fromarray(arr,mode="RGB")
out.save("Average.png")
out.show()

L'image ci-dessous a été générée à partir d'une séquence d'images vidéo HD à l'aide du code ci-dessus.

Average of HD video frames

35
CnrL

J'ai du mal à imaginer une situation où la mémoire est un problème ici, mais dans le cas (peu probable) où vous ne pouvez absolument pas vous permettre de créer le tableau de flottants requis pour ma réponse originale , vous pouvez utiliser la fonction de fusion de PIL, comme suggéré par @mHurley comme suit:

# Alternative method using PIL blend function
avg=Image.open(imlist[0])
for i in xrange(1,N):
    img=Image.open(imlist[i])
    avg=Image.blend(avg,img,1.0/float(i+1))
avg.save("Blend.png")
avg.show()

Vous pouvez dériver la séquence correcte des valeurs alpha en commençant par la définition de la fonction de mélange de PIL: 

out = image1 * (1.0 - alpha) + image2 * alpha

Pensez à appliquer cette fonction de manière récursive à un vecteur de nombres (plutôt qu'à des images) pour obtenir la moyenne du vecteur. Pour un vecteur de longueur N, vous aurez besoin d'opérations de mélange N-1, avec N-1 valeurs alpha différentes.

Cependant, il est probablement plus facile de penser intuitivement aux opérations. A chaque étape, vous voulez que l'image moyenne contienne des proportions égales des images source des étapes précédentes. Lors du mélange des première et deuxième images source, alpha doit être égal à 1/2 pour assurer des proportions égales. Lorsque vous mélangez la troisième avec la moyenne des deux premières, vous souhaitez que la nouvelle image soit composée de 1/3 de la troisième image, le reste étant constitué de la moyenne des images précédentes (valeur actuelle de moyenne) , etc.

En principe, cette nouvelle réponse, basée sur le mélange, devrait convenir. Cependant, je ne sais pas exactement comment fonctionne la fonction de fusion. Cela m'inquiète de la manière dont les valeurs de pixels sont arrondies après chaque itération. 

L'image ci-dessous a été générée à partir de 288 images sources à l'aide du code de ma réponse d'origine:

Averaged, original answer

D'autre part, cette image a été générée en appliquant de manière répétée la fonction de fusion de PIL aux mêmes 288 images:

Blended, using Image.blend

J'espère que vous pouvez voir que les résultats des deux algorithmes sont sensiblement différents. Je suppose que cela est dû à l'accumulation de petites erreurs d'arrondi lors de l'application répétée d'Image.blend

Je recommande fortement ma réponse originale sur cette alternative.

11
CnrL

On peut également utiliser la fonction numpy mean pour calculer la moyenne. Le code semble mieux et fonctionne plus rapidement.

Voici la comparaison des moments et des résultats pour 700 images de visages en niveaux de gris noisy:

def average_img_1(imlist):
    # Assuming all images are the same size, get dimensions of first image
    w,h=Image.open(imlist[0]).size
    N=len(imlist)

    # Create a numpy array of floats to store the average (assume RGB images)
    arr=np.zeros((h,w),np.float)

    # Build up average pixel intensities, casting each image as an array of floats
    for im in imlist:
        imarr=np.array(Image.open(im),dtype=np.float)
        arr=arr+imarr/N
    out = Image.fromarray(arr)
    return out

def average_img_2(imlist):
    # Alternative method using PIL blend function
    N = len(imlist)
    avg=Image.open(imlist[0])
    for i in xrange(1,N):
        img=Image.open(imlist[i])
        avg=Image.blend(avg,img,1.0/float(i+1))
    return avg

def average_img_3(imlist):
    # Alternative method using numpy mean function
    images = np.array([np.array(Image.open(fname)) for fname in imlist])
    arr = np.array(np.mean(images, axis=(0)), dtype=np.uint8)
    out = Image.fromarray(arr)
    return out

average_img_1() 
100 loops, best of 3: 362 ms per loop

average_img_2()
100 loops, best of 3: 340 ms per loop

average_img_3()
100 loops, best of 3: 311 ms per loop

BTW, les résultats de la moyenne sont très différents. Je pense que la première méthode perd des informations au cours de la moyenne. Et le second a des artefacts.

average_img_1

 enter image description here

average_img_2

 enter image description here

average_img_3

 enter image description here

4
Katrina Malakhova

Je envisagerais de créer un tableau de x par y nombres entiers commençant tous à (0, 0, 0), puis pour chaque pixel de chaque fichier, ajoutez la valeur RVB, divisez toutes les valeurs par 100, puis créez l'image à partir de celle-ci. trouvera probablement que numpy peut aider.

2
Steve Barnes

au cas où quelqu'un serait intéressé par une solution blueprint numpy (je le cherchais en fait), voici le code:

mean_frame = np.mean(([frame for frame in frames]), axis=0)
1
Fábio Ferreira

J'ai rencontré MemoryErrors en essayant la méthode dans la réponse acceptée. J'ai trouvé un moyen d'optimiser qui semble produire le même résultat. Fondamentalement, vous mélangez une image à la fois, au lieu de les additionner et de les diviser. 

N=len(images_to_blend)
avg = Image.open(images_to_blend[0])
for im in images_to_blend: #assuming your list is filenames, not images
    img = Image.open(im)
    avg = Image.blend(avg, img, 1/N)
avg.save(blah)

Cela fait deux choses, vous n’avez pas besoin de deux copies très denses de l’image lorsque vous la transformez en tableau, et vous n’aurez pas à utiliser de flottants de 64 bits. Vous obtenez une précision similaire, avec des nombres plus petits. Les résultats semblent être les mêmes, mais j'apprécierais que quelqu'un vérifie mes calculs.

0
Matt