web-dev-qa-db-fra.com

Comment trouver la couleur moyenne d'une image dans Python avec OpenCV?

J'ai essayé ce code:

import cv2
image = cv2.imread("sample.jpg")
pixel = image[200, 550]
print pixel

Mais je reçois une erreur en tant que:

'Non-type' pas d'erreur d'attribut getitem

Cette erreur est affichée après l'exécution de la troisième ligne de code.

19
Nikhith Tayambhath

Comment corriger l'erreur

Il existe deux causes possibles de cette erreur:

  1. Le nom du fichier est mal orthographié.
  2. Le fichier image ne se trouve pas dans le répertoire de travail actuel.

Pour résoudre ce problème, vous devez vous assurer que le nom du fichier est correctement orthographié (faites une vérification sensible à la casse) et que le fichier image se trouve dans le répertoire de travail actuel (vous avez le choix entre deux options: vous pouvez modifier le répertoire de travail actuel dans votre = IDE ou spécifiez le chemin complet du fichier).

Couleur moyenne vs couleur dominante

Ensuite, pour calculer la "couleur moyenne", vous devez décider ce que vous entendez par là. Dans une image en niveaux de gris, il s’agit simplement de la moyenne des niveaux de gris, mais avec les couleurs, la "moyenne" n’existe pas. En effet, les couleurs sont généralement représentées par des vecteurs tridimensionnels alors que les niveaux de gris sont des scalaires. Cela convient aux scalaires moyens mais cela n’a aucun sens de faire des vecteurs moyens.

Séparer l’image en ses composantes chromatiques et prendre la moyenne de chaque composante est une solution envisageable. Cependant, cette approche peut donner une couleur sans signification. Ce que vous pourriez vraiment souhaiter, c’est une couleur dominante plutôt qu’une couleur moyenne.

La mise en oeuvre

Passons lentement en revue le code. Nous commençons par importer les modules nécessaires et en lisant l'image:

import cv2
import numpy as np
from skimage import io

img = io.imread('https://i.stack.imgur.com/DNM65.png')[:, :, :-1]

On peut ensuite calculer la moyenne de chaque canal chromatique selon une méthode analogue à celle proposée par @Ruan B .:

average = img.mean(axis=0).mean(axis=0)

Ensuite, nous appliquons k-means clustering pour créer une palette avec les couleurs les plus représentatives de l'image (dans cet exemple de jouet n_colors était réglé sur 5).

pixels = np.float32(img.reshape(-1, 3))

n_colors = 5
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 200, .1)
flags = cv2.KMEANS_RANDOM_CENTERS

_, labels, palette = cv2.kmeans(pixels, n_colors, None, criteria, 10, flags)
_, counts = np.unique(labels, return_counts=True)

Et enfin, la couleur dominante est la couleur de palette qui apparaît le plus souvent sur l’image quantifiée:

dominant = palette[np.argmax(counts)]

Comparaison des résultats

Pour illustrer les différences entre les deux approches, j'ai utilisé l'exemple d'image suivant:

lego

Les valeurs obtenues pour la couleur moyenne, c’est-à-dire une couleur dont les composantes sont les moyennes des trois canaux chromatiques, et la couleur dominante calculée par le regroupement en k-moyennes sont assez différentes:

In [30]: average
Out[30]: array([91.63179156, 69.30190754, 58.11971896])

In [31]: dominant
Out[31]: array([179.3999  ,  27.341282,   2.294441], dtype=float32)

Voyons à quoi ressemblent ces couleurs pour mieux comprendre les différences entre les deux approches. Sur la partie gauche de la figure ci-dessous, la couleur moyenne est affichée. Il apparaît clairement que la couleur moyenne calculée ne décrit pas correctement le contenu en couleurs de l'image d'origine. En fait, il n'y a pas un seul pixel avec cette couleur dans l'image d'origine. La partie droite de la figure montre les cinq couleurs les plus représentatives triées de haut en bas par ordre décroissant d’importance (fréquence d’occurrence). Cette palette indique clairement que la couleur dominante est le rouge, ce qui concorde avec le fait que la plus grande région de couleur uniforme de l'image d'origine correspond à la pièce Lego rouge.

Results

C'est le code utilisé pour générer la figure ci-dessus:

import matplotlib.pyplot as plt

avg_patch = np.ones(shape=img.shape, dtype=np.uint8)*np.uint8(average)

indices = np.argsort(counts)[::-1]   
freqs = np.cumsum(np.hstack([[0], counts[indices]/counts.sum()]))
rows = np.int_(img.shape[0]*freqs)

dom_patch = np.zeros(shape=img.shape, dtype=np.uint8)
for i in range(len(rows) - 1):
    dom_patch[rows[i]:rows[i + 1], :, :] += np.uint8(palette[indices[i]])

fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(12,6))
ax0.imshow(avg_patch)
ax0.set_title('Average color')
ax0.axis('off')
ax1.imshow(dom_patch)
ax1.set_title('Dominant colors')
ax1.axis('off')
plt.show(fig)

Réponse TL; DR

En résumé, bien que le calcul de la couleur moyenne - comme proposé dans la réponse de @Ruan B. - soit techniquement correct d'un point de vue mathématique, le résultat obtenu peut ne pas représenter correctement le contenu en couleur de l'image. Une approche plus judicieuse consiste à déterminer la couleur dominante par quantification vectorielle (clustering).

51
Tonechas

J'ai pu obtenir la couleur moyenne en utilisant les éléments suivants:

import cv2
import numpy
myimg = cv2.imread('image.jpg')
avg_color_per_row = numpy.average(myimg, axis=0)
avg_color = numpy.average(avg_color_per_row, axis=0)
print(avg_color)

Résultat:

[ 197.53434769  217.88439451  209.63799938]

Grande ressource que j'ai référencée

19
Ruan B.