Donc, je conçois quelques programmes pour éditer des photos dans python
en utilisant PIL
et l’un d’eux convertissait une image en niveaux de gris (j’évite l’utilisation de fonctions de PIL
).
L’algorithme que j’ai utilisé est simple: pour chaque pixel (profondeur de couleur égale à 24), j’ai calculé la moyenne des valeurs R
, G
et B
et définissez les valeurs RVB sur cette moyenne.
Mon programme produisait des images en niveaux de gris qui semblaient exactes, mais je me demandais si j'avais utilisé le bon algorithme, et je suis tombé sur cette réponse à une question, où il semble que l'algorithme 'correct' est calculer 0.299 R + 0.587 G + 0.114 B
.
J'ai décidé de comparer mon programme à cet algorithme. J'ai généré une image en niveaux de gris en utilisant mon programme et une autre (en utilisant la même entrée) à partir de n site Web en ligne (le premier résultat de Google pour 'image to grayscale'
.
À mon œil nu, ils semblaient être exactement les mêmes et s'il y avait une variation, je ne pouvais pas la voir. Cependant, j'ai décidé d'utiliser ce site Web (résultat Google supérieur pour 'compare two images online'
) pour comparer mes images en niveaux de gris. Il s'est avéré que profondément dans les pixels, ils présentaient de légères variations, mais aucune de celles que l'œil humain pouvait percevoir au premier abord (les différences peuvent être repérées, mais uniquement lorsque les images sont superposées ou commutées en quelques millisecondes). .
Mes questions (la première est la question principale) :
Mon morceau de code clé (si nécessaire):
def greyScale(pixelTuple):
return Tuple([round(sum(pixelTuple) / 3)] * 3)
L'algorithme 'correct' (qui semble peser lourd en vert):
def greyScale(pixelTuple):
return Tuple([round(0.299 * pixelTuple[0] + 0.587 * pixelTuple[1] + 0.114 * pixelTuple[2])] * 3)
L'image en niveaux de gris que mon algorithme produit:
L'image en niveaux de gris qui est 'correcte':
Lorsque les images en niveaux de gris sont comparées en ligne (les différences apparaissent en rouge, avec un fuzz de 10%):
Malgré les variations de pixels soulignées ci-dessus, les images en niveaux de gris ci-dessus semblent être à peu près identiques (du moins, à mes yeux).
En outre, en ce qui concerne ma première question, si vous êtes intéressé, ce site a analysé différents algorithmes de conversion en niveaux de gris et propose également des algorithmes personnalisés.
[~ # ~] éditer [~ # ~] :
En réponse à la réponse de @ Szulat, mon algorithme produit en fait cette image (ignore le rognage incorrect, l'image d'origine comportait trois cercles mais je n'avais besoin que du premier une):
Au cas où les gens se demandent quelle est la raison de la conversion en niveaux de gris (car il semble que l'algorithme dépend de l'objectif), je fabrique simplement de simples outils de retouche photo dans python
pour pouvoir disposer d'un mini -Photoshop et n'avez pas besoin de recourir à Internet pour appliquer des filtres et des effets.
Raison de la prime : Différentes réponses ici couvrent des choses différentes, qui sont toutes pertinentes et utiles. Cela rend difficile de choisir la réponse à accepter. J'ai commencé une prime parce que j'aime quelques réponses énumérées ici, mais aussi parce que ce serait bien d'avoir une seule réponse qui couvre tout ce dont j'ai besoin pour cette question.
Les images sont assez similaires , mais votre œil peut faire la différence, surtout si vous en mettez une à la place de l'autre:
Par exemple, vous pouvez noter que les fleurs de l'arrière-plan sont plus claires lors de la conversion de la moyenne.
Ce n’est pas qu’il y ait quelque chose de intrinsèquement "mauvais" à faire la moyenne des trois canaux. La raison de cette formule est que nous ne percevons pas le rouge, le vert et le bleu de la même manière. Par conséquent, leur contribution aux intensités dans une image en niveaux de gris ne devrait pas être la même; Dans la mesure où nous percevons le vert de manière plus intense, les pixels verts devraient être plus brillants en niveaux de gris. Cependant, comme l'a commenté Mark il n'y a pas de conversion parfaite en niveaux de gris, puisque nous voyons en couleur, et dans tous les cas, la vision de chacun est légèrement différente, de sorte que toute formule essaiera simplement de faire une approximation les intensités se sentent "bien" pour la plupart des gens.
L'exemple le plus évident:
Original
Désaturé dans Gimp (mode Lightness - c'est ce que fait votre algorithme)
Désaturé dans Gimp (mode Luminosity - c'est ce que font nos yeux)
Donc, ne faites pas moyenne RVB. La moyenne RVB est tout simplement fausse!
(D'accord, vous avez raison, la moyenne peut être valide dans certaines applications obscures, même si elle n'a pas de signification physique ou physiologique lorsque les valeurs RVB sont traitées comme des couleurs. En passant, la méthode "régulière" de pondération la moyenne est également incorrecte d'une manière plus subtile en raison de gamma. sRGB doit d'abord être linéarisé, puis le résultat final reconverti en sRGB (ce qui équivaudrait à extraire le composant L dans l'espace colorimétrique Lab))
Vous pouvez utiliser n'importe quelle équation de conversion, échelle, linéarité. Celui que vous avez trouvé:
I = 0.299 R + 0.587 G + 0.114 B
est basé sur la sensibilité de perception de la couleur primaire (moyenne, moyenne) (R, G, B) de l'œil humain moyen (du moins pour la période et la population/HW sur laquelle elle a été créée; gardez à l'esprit que ces normes ont été créées avant LED, TFT, etc. écrans).
Vous combattez plusieurs problèmes:
nos yeux ne sont pas les mêmes
Tous les humains ne perçoivent pas la couleur de la même manière. Il existe des différences majeures entre les sexes et plus petites également entre les régions; même la génération et l'âge jouent un rôle. Donc, même une moyenne devrait être traitée comme "moyenne".
Nous avons une sensibilité différente à l'intensité de la lumière dans le spectre visible. La couleur la plus sensible est le vert (d'où le poids le plus élevé). Mais les pics courbe XYZ peuvent avoir différentes longueurs d'onde pour différentes personnes (comme moi, je les ai décalés un peu, ce qui entraîne une différence de reconnaissance de certaines longueurs d'onde, comme certaines nuances de Aqua - certaines les voient aussi vertes que d'autres bleues même si aucun d’eux n’a de trouble de daltonisme ou autre).
les moniteurs n'utilisent pas les mêmes longueurs d'onde ni la même dispersion spectrale
Donc, si vous prenez 2 moniteurs différents, ils pourraient utiliser des longueurs d'onde légèrement différentes pour R, G, B ou même des largeurs différentes du filtre spectral ( tilisez simplement un spectroscope et voir ). Oui, ils devraient être "normalisés" par le matériel, mais ce n'est pas la même chose que d'utiliser des longueurs d'onde normalisées. Ce problème est similaire aux problèmes liés à l'utilisation de sources lumineuses à spectre RVB par rapport au bruit blanc.
surveiller la linéarité
Les humains ne voient pas sur une échelle linéaire: nous sommes généralement logarithmiques/exponentiels (cela dépend de votre façon de voir les choses) alors oui, nous pouvons normaliser cela avec HW (ou même SW), mais le problème est que si nous linéarisons pour un humain, nous endommageons pour un autre.
Si vous prenez tout cela ensemble, vous pouvez utiliser des moyennes ... ou des équipements spéciaux (et coûteux) pour mesurer/normaliser par rapport à une norme ou à une personne calibrée (selon le secteur).
Mais c’est trop difficile à gérer à la maison alors laissez tout cela pour l’industrie et utilisez les poids comme "moyens" comme dans la plupart des pays du monde ... Heureusement, notre cerveau peut le supporter car vous ne pouvez pas voir la différence à moins de comparer deux images. côte à côte ou dans une animation :). Donc je ferais (je ferais):
I = 0.299 R + 0.587 G + 0.114 B
R = I
G = I
B = I
Il existe de nombreuses formules pour la luminance, en fonction des couleurs primaires R, V, B:
Rec.601/NTSC: Y = 0.299*R + 0.587*G + 0.114*B ,
Rec.709/EBU: Y = 0.213*R + 0.715*G + 0.072*B ,
Rec.2020/UHD: Y = 0.263*R + 0.678*G + 0.059*B .
Tout cela parce que nos yeux sont moins sensibles au bleu qu'au rouge que au vert.
Cela étant dit, vous calculez probablement le Luma, pas la luminance, de sorte que les formules sont toutes fausses de toute façon. Pour Constant-Luminance, vous devez convertir en lumière linéaire.
R = R' ^ 2.4 , G = G' ^ 2.4 , B = B' ^ 2.4 ,
appliquer la formule de luminance et reconvertir dans le domaine gamma
Y' = Y ^ (1/2.4) .
En outre, considérez que la conversion d'un espace colorimétrique 3D en une quantité 1D perd les 2/3 de l'information, ce qui peut vous piquer lors des prochaines étapes de traitement. Selon le problème, une formule différente est parfois préférable, comme V = MAX (R, V, B) (à partir de l'espace colorimétrique HSV).
Comment puis-je savoir? Je suis un disciple et un ami du Dr Poynton.
Il existe de nombreuses méthodes de conversion en niveaux de gris, et elles donnent des résultats différents bien que les différences puissent être plus faciles à voir avec des images en couleurs d'entrée différentes.
Comme nous ne le voyons pas vraiment en niveaux de gris, la méthode "meilleure" dépend un peu de l'application et est quelque peu perçue par le spectateur.
La formule alternative à laquelle vous faites référence est basée sur le fait que l'œil humain est plus sensible aux variations des tons verts et leur donne donc une pondération plus importante - de la même manière qu'un tableau de Bayer dans une caméra comportant 2 pixels verts pour chaque rouge et bleu. Wiki - tableau de Bayer
Les réponses fournies sont suffisantes, mais je souhaite discuter un peu plus de ce sujet de manière différente.
Depuis que j’ai appris la peinture numérique par intérêt, j’utilise plus souvent le HSV.
Il est beaucoup plus contrôlable d’utiliser le HSV pendant la peinture, mais restez bref, le point principal est le S: Saturation qui sépare le concept de couleur de la lumière. Et mettre S à 0, c'est déjà l'échelle de gris de l'ordinateur.
from PIL import Image
import colorsys
def togrey(img):
if isinstance(img,Image.Image):
r,g,b = img.split()
R = []
G = []
B = []
for rd,gn,bl in Zip(r.getdata(),g.getdata(),b.getdata()) :
h,s,v = colorsys.rgb_to_hsv(rd/255.,gn/255.,bl/255.)
s = 0
_r,_g,_b = colorsys.hsv_to_rgb(h,s,v)
R.append(int(_r*255.))
G.append(int(_g*255.))
B.append(int(_b*255.))
r.putdata(R)
g.putdata(G)
b.putdata(B)
return Image.merge('RGB',(r,g,b))
else:
return None
a = Image.open('../a.jpg')
b = togrey(a)
b.save('../b.jpg')
Cette méthode a vraiment réservé le "brillant" de la couleur d'origine. Cependant, sans tenir compte de la façon dont l’œil humain traite les données.
En réponse à votre question principale, l’utilisation d’une seule mesure de gris présente des inconvénients. Cela dépend de ce que vous voulez de votre image. Par exemple, si vous avez du texte coloré sur un arrière-plan blanc, si vous souhaitez le faire ressortir, vous pouvez utiliser le minimum des valeurs r, g, b comme mesure. Mais si vous avez du texte noir sur un arrière-plan coloré, vous pouvez utiliser le maximum des valeurs pour le même résultat. Dans mon logiciel, j'offre l'option de choisir une valeur maximale, minimale ou médiane. Les résultats sur les images à tons continus sont également éclairants. En réponse aux commentaires demandant plus de détails, le code d'un pixel est ci-dessous (sans aucune mesure de défense).
int Ind0[3] = {0, 1, 2}; //all equal
int Ind1[3] = {2, 1, 0}; // top, mid ,bot from mask...
int Ind2[3] = {1, 0, 2};
int Ind3[3] = {1, 2, 0};
int Ind4[3] = {0, 2, 1};
int Ind5[3] = {2, 0, 1};
int Ind6[3] = {0, 1, 2};
int Ind7[3] = {-1, -1, -1}; // not possible
int *Inds[8] = {Ind0, Ind1, Ind2, Ind3, Ind4, Ind5, Ind6, Ind7};
void grecolor(unsigned char *rgb, int bri, unsigned char *grey)
{ //pick out bot, mid or top according to bri flag
int r = rgb[0];
int g = rgb[1];
int b = rgb[2];
int mask = 0;
mask |= (r > g);
mask <<= 1;
mask |= (g > b);
mask <<= 1;
mask |= (b > r);
grey[0] = rgb[Inds[mask][2 - bri]]; // 2, 1, 0 give bot, mid, top
}