web-dev-qa-db-fra.com

Le moyen le plus rapide de calculer la moyenne et l'écart type en termes de canaux de jeu de données d'image en Python

J'ai un énorme jeu de données d'image qui ne tient pas dans la mémoire. Je veux calculer les mean et standard deviation, en chargeant des images à partir du disque.

J'essaie actuellement d'utiliser cet algorithme trouvé sur wikipedia .

# for a new value newValue, compute the new count, new mean, the new M2.
# mean accumulates the mean of the entire dataset
# M2 aggregates the squared distance from the mean
# count aggregates the amount of samples seen so far
def update(existingAggregate, newValue):
    (count, mean, M2) = existingAggregate
    count = count + 1 
    delta = newValue - mean
    mean = mean + delta / count
    delta2 = newValue - mean
    M2 = M2 + delta * delta2

    return existingAggregate

# retrieve the mean and variance from an aggregate
def finalize(existingAggregate):
    (count, mean, M2) = existingAggregate
    (mean, variance) = (mean, M2/(count - 1)) 
    if count < 2:
        return float('nan')
    else:
        return (mean, variance)

Voici mon implémentation actuelle (informatique uniquement pour le canal rouge):

count = 0
mean = 0
delta = 0
delta2 = 0
M2 = 0
for i, file in enumerate(tqdm(first)):
    image = cv2.imread(file)
    for i in range(224):
        for j in range(224):
            r, g, b = image[i, j, :]
            newValue = r
            count = count + 1
            delta = newValue - mean
            mean = mean + delta / count
            delta2 = newValue - mean
            M2 = M2 + delta * delta2

print('first mean', mean)
print('first std', np.sqrt(M2 / (count - 1)))

Cette implémentation fonctionne assez bien sur un sous-ensemble du jeu de données que j'ai essayé.

Le problème est qu’il est extrêmement lent et donc non viable.

  • Y a-t-il un moyen standard de faire cela?

  • Comment puis-je adapter cela pour obtenir un résultat plus rapide ou calculer l'écart moyen et standard RVB pour tout le jeu de données sans tout charger en mémoire en même temps et à une vitesse raisonnable?

6
Bruno Klein

Comme il s’agit là d’une tâche très lourde en chiffres (beaucoup d’itérations autour d’une matrice ou d’un tenseur), je suggère toujours d’utiliser des bibliothèques performantes: numpy. 

Un numpy correctement installé doit pouvoir utiliser les routines BLAS (sous-routines d'algèbre linéaire de base) sous-jacentes optimisées pour l'utilisation d'un tableau de points flottants de la perspective de la hiérarchie de la mémoire.

imread devrait déjà vous donner le tableau numpy. Vous pouvez obtenir le tableau 1d remodelé de l’image du canal rouge en 

import numpy as np
val = np.reshape(image[:,:,0], -1)

la moyenne de tels par

np.mean(val)

et l'écart type par

np.std(val)

De cette façon, vous pouvez vous débarrasser de deux couches de boucles python:

count = 0
mean = 0
delta = 0
delta2 = 0
M2 = 0
for i, file in enumerate(tqdm(first)):
    image = cv2.imread(file)
        val = np.reshape(image[:,:,0], -1)
        img_mean = np.mean(val)
        img_std = np.std(val)
        ...

Le reste de la mise à jour incrémentielle devrait être simple.

Une fois que cela est fait, le goulot d'étranglement devient la vitesse de chargement de l'image, qui est limitée par les performances de la lecture du disque. À cet égard, j’imagine que l’utilisation de la technologie multi-thread, comme d’autres l’a suggéré, aidera beaucoup en me basant sur mon expérience antérieure.

2
Yo Hsiao

Vous pouvez également utiliser la méthode d'opencv meanstddev .

cv2.meanStdDev(src[, mean[, stddev[, mask]]]) → mean, stddev
1
Andrey Smorodov

Si vous ne souhaitez pas mettre des éléments dans une mémoire avec un tableau contenant l'ensemble de vos données, vous pouvez simplement le calculer de manière itérative.

# can be whatever I just made this number up
global_mean = 134.0
# just get a per-pixel array with the vals for (x_i - mu) ** 2 / |x|
sums = ((images[0] - global_mean) ** 2) / len(images)

for img in images[1:]:
    sums = sums + ((img - global_mean) ** 2) / len(images)

# Get mean of all per-pixel variances, and then take sqrt to get std
dataset_std = np.sqrt(np.mean(sums))
0
Greg