python - por - Problemas con el uso de un algoritmo de escala de grises en bruto?
imagenes en python (7)
Así que estoy diseñando algunos programas para editar fotos en
python
usando
PIL
y uno de ellos fue convertir una imagen a escala de grises (estoy evitando el uso de cualquier función de
PIL
).
El algoritmo que he empleado es simple: para cada píxel (la profundidad de color es 24), calculé el promedio de los valores
R
,
G
y
B
y establecí los valores RGB a este promedio.
Mi programa estaba produciendo imágenes en escala de grises que parecían precisas, pero me preguntaba si había empleado el algoritmo correcto, y encontré
esta respuesta
a una pregunta, donde parece que el algoritmo "correcto" es calcular
0.299 R + 0.587 G + 0.114 B
Decidí comparar mi programa con este algoritmo.
Generé una imagen en escala de grises con mi programa y otra (con la misma entrada) desde
un sitio web en línea
(el resultado principal de Google para
''image to grayscale''
.
A simple vista, parecía que eran exactamente iguales, y si había alguna variación, no podía verla.
Sin embargo, decidí usar
este sitio web
(el resultado principal de Google para
''compare two images online''
) para comparar mis imágenes en escala de grises.
Resultó que en lo profundo de los píxeles, tenían ligeras variaciones, pero ninguna que fuera percibida por el ojo humano a primera vista (las diferencias pueden detectarse, pero generalmente solo cuando las imágenes se colocan unas sobre otras o se cambian entre milisegundos) .
Mis preguntas (la primera es la pregunta principal) :
- ¿Hay alguna desventaja al usar mi algoritmo de escala de grises "aproximado"?
- ¿Alguien tiene imágenes de entrada en las que mi algoritmo de escala de grises produzca una imagen visiblemente diferente de la que sería "correcta"?
- ¿Hay combinaciones de colores / RBG para las que mi algoritmo no funcione también?
Mi pieza clave de código (si es necesario):
def greyScale(pixelTuple):
return tuple([round(sum(pixelTuple) / 3)] * 3)
El algoritmo ''correcto'' (que parece tener un gran peso verde):
def greyScale(pixelTuple):
return tuple([round(0.299 * pixelTuple[0] + 0.587 * pixelTuple[1] + 0.114 * pixelTuple[2])] * 3)
La imagen en escala de grises que mi algoritmo produce:
La imagen en escala de grises que es "correcta":
Cuando las imágenes en escala de grises se comparan en línea (las diferencias se resaltan en rojo, con un fuzz del 10%):
A pesar de las variaciones en los píxeles resaltados arriba, las imágenes en escala de grises que aparecen arriba son casi iguales (al menos, para mí).
Además, con respecto a mi primera pregunta, si alguien está interesado, este sitio ha realizado un análisis de diferentes algoritmos para conversiones a escala de grises y también tiene algunos algoritmos personalizados.
EDITAR :
En respuesta a la respuesta de @ Szulat, mi algoritmo en realidad produce esta imagen (ignora el recorte incorrecto, la imagen original tenía tres círculos, pero solo necesitaba el primero):
En caso de que la gente se pregunte cuál es el motivo de la conversión a escala de grises (ya que parece que el algoritmo depende del propósito), solo estoy creando algunas herramientas de edición de fotos en
python
para poder tener un mini Photoshop y no hacerlo. No es necesario confiar en Internet para aplicar filtros y efectos.
Motivo de la recompensa : las diferentes respuestas aquí cubren diferentes cosas, todas relevantes y útiles. Esto hace que sea bastante difícil elegir qué respuesta aceptar. Comencé una recompensa porque me gustan algunas respuestas enumeradas aquí, pero también porque sería bueno tener una respuesta única que cubra todo lo que necesito para esta pregunta.
El ejemplo más obvio:
-
Original
-
Desaturado en Gimp (modo de luminosidad: esto es lo que hace su algoritmo)
-
Desaturado en Gimp (modo de luminosidad: esto es lo que hacen nuestros ojos)
Entonces, no promedie RGB. ¡Promover el RGB es simplemente incorrecto!
(De acuerdo, tiene razón, el promedio puede ser válido en algunas aplicaciones oscuras, aunque no tenga un significado físico o fisiológico cuando los valores RGB se tratan como color. Por cierto, la forma "regular" de hacer un promedio ponderado también es incorrecta. de una manera más sutil debido a gamma. sRGB debe primero ser linealizado y luego el resultado final debe convertirse de nuevo a sRGB (lo que sería equivalente a recuperar el componente L en el espacio de color Lab))
En respuesta a su pregunta principal, existen desventajas en el uso de una sola medida de gris. Depende de lo que quieras de tu imagen. Por ejemplo, si tiene texto coloreado sobre fondo blanco, si desea resaltar el texto, puede usar el mínimo de los valores r, g, b como medida. Pero si tiene texto negro sobre un fondo de color, puede usar el máximo de los valores para el mismo resultado. En mi software ofrezco la opción de valor máximo, mínimo o mediano para que el usuario elija. Los resultados en imágenes de tonos continuos también son iluminadores. En respuesta a los comentarios que solicitan más detalles, el código para un píxel está debajo (sin medidas defensivas).
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
}
Existen muchas fórmulas para la luminancia, según los colores primarios de R, G y 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 .
Esto es todo porque nuestros ojos son menos sensibles al azul que al rojo que al verde.
Dicho esto, probablemente estés calculando Luma, no Luminancia, por lo que las fórmulas son todas erróneas de todos modos. Para Constant-Luminance debes convertir a linear-light
R = R'' ^ 2.4 , G = G'' ^ 2.4 , B = B'' ^ 2.4 ,
Aplique la fórmula de luminancia y vuelva a convertirla al dominio gamma.
Y'' = Y ^ (1/2.4) .
Además, tenga en cuenta que convertir un espacio de color 3D en una cantidad 1D pierde 2/3 de la información, lo que puede morderle en los siguientes pasos de procesamiento. Dependiendo del problema, a veces una fórmula diferente es mejor, como V = MAX (R, G, B) (del espacio de color HSV).
¿Cómo puedo saber? Soy un seguidor y amigo del Dr. Poynton.
Hay muchos métodos diferentes para convertir a escala de grises, y dan resultados diferentes, aunque las diferencias pueden ser más fáciles de ver con diferentes imágenes de color de entrada.
Como no vemos realmente en escala de grises, el método "mejor" depende en cierta medida de la aplicación y del ojo del espectador.
La fórmula alternativa a la que se refiere se basa en que el ojo humano es más sensible a las variaciones en los tonos verdes y, por lo tanto, les otorga una mayor ponderación, de manera similar a una matriz de Bayer en una cámara donde hay 2 píxeles verdes para cada uno rojo y azul. Wiki - Bayer array
Las imágenes se ven bastante similares , pero su ojo puede notar la diferencia, especialmente si coloca una en lugar de la otra:
Por ejemplo, puede observar que las flores en el fondo se ven más brillantes en la conversión promediada.
No es que haya nada intrínsecamente "malo" en promediar los tres canales. La razón de esa fórmula es que no percibimos el rojo, el verde y el azul por igual, por lo que su contribución a las intensidades en una imagen en escala de grises no debería ser la misma; Como percibimos el verde con mayor intensidad, los píxeles verdes deberían verse más brillantes en escala de grises. Sin embargo, como comentó Mark, no existe una conversión perfecta única a escala de grises, ya que vemos en color, y en cualquier caso, la visión de todos es ligeramente diferente, por lo que cualquier fórmula intentará hacer una aproximación para que las intensidades de píxeles se sientan "correctas" para la mayoría gente.
Las respuestas proporcionadas son suficientes, pero quiero discutir un poco más sobre este tema de una manera diferente.
Desde que aprendí pintura digital por interés, más a menudo uso HSV.
Es mucho más controlable para el uso de HSV durante la pintura, pero, para abreviar, el punto principal es la S: Saturación que separa el concepto de color de la luz. Y el cambio de S a 0, ya es la escala de grises de la ''computadora''.
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'')
Este método verdaderamente reservó el ''brillo'' del color original. Sin embargo, sin considerar cómo el ojo humano procesa los datos .
Puede utilizar cualquier ecuación de conversión, escala, linealidad. El que encontraste:
I = 0.299 R + 0.587 G + 0.114 B
se basa en la sensibilidad de la percepción del color primario (R, G, B) del ojo promedio del ojo humano (al menos para el período de tiempo y la población / HW en el que se creó; tenga en cuenta que esos estándares se crearon antes de LED, TFT, etc. pantallas).
Hay varios problemas contra los que estás luchando:
-
nuestros ojos no son los mismos
Todos los humanos no perciben el color de la misma manera. Existen grandes discrepancias entre los géneros y más pequeñas también entre las regiones; Incluso la generación y la edad juegan un papel. Así que incluso un promedio debe manejarse como "promedio".
Tenemos diferente sensibilidad a la intensidad de la luz a través del espectro visible. El color más sensible es el verde (de ahí su mayor peso). Pero los picos de la curva XYZ pueden estar en diferentes longitudes de onda para diferentes personas (como yo, los cambié un poco, lo que causó una diferencia en el reconocimiento de ciertas longitudes de onda, como algunos tonos de Aqua; algunos los ven como verdes, otros como azules, aunque ninguno de ellos tenga ninguna color ceguera discapacidades o lo que sea).
-
Los monitores no utilizan las mismas longitudes de onda ni la dispersión espectral
Entonces, si toma 2 monitores diferentes, podrían usar longitudes de onda ligeramente diferentes para R, G, B o incluso diferentes anchos del filtro espectral ( solo use un espectroscopio y vea ). Sí, deberían estar "normalizados" por el HW pero eso no es lo mismo que usar longitudes de onda normalizadas. Es similar a los problemas con el uso de fuentes de luz de espectro RGB frente a ruido blanco.
-
linealidad del monitor
Los humanos no ven en una escala lineal: generalmente somos logarítmicos / exponenciales (depende de cómo se mire), así que sí podemos normalizar eso con HW (o incluso SW), pero el problema es que si linealizamos para un humano, entonces significa que dañamos. por otro
Si toma todo esto junto, puede usar promedios ... o equipo especial (y costoso) para medir / normalizar contra algún estándar o contra una persona calibrada (depende de la industria).
Pero eso es demasiado para manejar en las condiciones del hogar, así que deje todo eso para la industria y use los pesos para "promedio" como la mayoría del mundo ... Por suerte, nuestro cerebro puede manejarlo ya que no puede ver la diferencia a menos que comience a comparar ambas imágenes. De lado a lado o en una animación :). Así que (haría)
I = 0.299 R + 0.587 G + 0.114 B
R = I
G = I
B = I