Estoy buscando algún tipo de fórmula o algoritmo para determinar el brillo de un color dados los valores RGB. Sé que no puede ser tan simple como sumar los valores RGB y que las sumas más altas sean más brillantes, pero no sé por dónde empezar.
Estoy buscando algún tipo de fórmula o algoritmo para determinar el brillo de un color dados los valores RGB. Sé que no puede ser tan simple como sumar los valores RGB y que las sumas más altas sean más brillantes, pero no sé por dónde empezar.
Respuestas:
¿Te refieres al brillo? Brillo percibido? Luminancia?
(0.2126*R + 0.7152*G + 0.0722*B)
[1](0.299*R + 0.587*G + 0.114*B)
[2]sqrt( 0.241*R^2 + 0.691*G^2 + 0.068*B^2 )
sqrt( 0.299*R^2 + 0.587*G^2 + 0.114*B^2 )
(gracias a @MatthewHerbst ) [3]0.299*(R^2)
(porque la exponenciación va antes de la multiplicación)
Creo que lo que estás buscando es la fórmula de conversión RGB -> Luma .
Fotométrico / digital ITU BT.709 :
Y = 0.2126 R + 0.7152 G + 0.0722 B
Digital ITU BT.601 (da más peso a los componentes R y B):
Y = 0.299 R + 0.587 G + 0.114 B
Si está dispuesto a cambiar la precisión por el rendimiento, hay dos fórmulas de aproximación para esta:
Y = 0.33 R + 0.5 G + 0.16 B
Y = 0.375 R + 0.5 G + 0.125 B
Estos se pueden calcular rápidamente como
Y = (R+R+B+G+G+G)/6
Y = (R+R+R+B+G+G+G+G)>>3
Blue
+ 3 * Verde) / 6, la segunda es (3 * Rojo + Blue
+ 4 * Verde) >> 3. concedido, en ambas aproximaciones rápidas, el azul tiene el peso más bajo, pero sigue ahí.
Y = (R<<1+R+G<<2+B)>>3
(eso es solo 3-4 ciclos de CPU en ARM) pero supongo que un buen compilador hará esa optimización por usted.
He hecho una comparación de los tres algoritmos en la respuesta aceptada. Generé colores en un ciclo donde solo se usaba cada 400 colores. Cada color está representado por 2x2 píxeles, los colores se ordenan del más oscuro al más claro (de izquierda a derecha, de arriba a abajo).
Primera imagen - Luminancia (relativa)
0.2126 * R + 0.7152 * G + 0.0722 * B
Segunda imagen: http://www.w3.org/TR/AERT#color-contrast
0.299 * R + 0.587 * G + 0.114 * B
3a imagen - Modelo de color HSP
sqrt(0.299 * R^2 + 0.587 * G^2 + 0.114 * B^2)
4a imagen: WCAG 2.0 SC 1.4.3 fórmula relativa de luminancia y relación de contraste (consulte la respuesta de @ Synchro aquí )
El patrón a veces se puede ver en la primera y segunda imagen según la cantidad de colores en una fila. Nunca vi ningún patrón en la imagen del tercer o cuarto algoritmo.
Si tuviera que elegir, usaría el algoritmo número 3, ya que es mucho más fácil de implementar y es aproximadamente un 33% más rápido que el 4to.
^2
e sqrt
incluidos en la tercera fórmula son una forma más rápida de aproximar RGB lineal de RGB no lineal en lugar de ^2.2
y ^(1/2.2)
eso sería más correcto. Desafortunadamente, el uso de entradas no lineales en lugar de lineales es extremadamente común.
A continuación se muestra el único algoritmo CORRECTO para convertir imágenes sRGB, como se usa en navegadores, etc., en escala de grises.
Es necesario aplicar un inverso de la función gamma para el espacio de color antes de calcular el producto interno. Luego aplica la función gamma al valor reducido. Si no se incorpora la función gamma, pueden producirse errores de hasta el 20%.
Para material informático típico, el espacio de color es sRGB. Los números correctos para sRGB son aprox. 0.21, 0.72, 0.07. Gamma para sRGB es una función compuesta que se aproxima a la exponenciación en 1 / (2.2). Aquí está todo en C ++.
// sRGB luminance(Y) values
const double rY = 0.212655;
const double gY = 0.715158;
const double bY = 0.072187;
// Inverse of sRGB "gamma" function. (approx 2.2)
double inv_gam_sRGB(int ic) {
double c = ic/255.0;
if ( c <= 0.04045 )
return c/12.92;
else
return pow(((c+0.055)/(1.055)),2.4);
}
// sRGB "gamma" function (approx 2.2)
int gam_sRGB(double v) {
if(v<=0.0031308)
v *= 12.92;
else
v = 1.055*pow(v,1.0/2.4)-0.055;
return int(v*255+0.5); // This is correct in C++. Other languages may not
// require +0.5
}
// GRAY VALUE ("brightness")
int gray(int r, int g, int b) {
return gam_sRGB(
rY*inv_gam_sRGB(r) +
gY*inv_gam_sRGB(g) +
bY*inv_gam_sRGB(b)
);
}
Las únicas respuestas que son precisas son las respuestas @ jive-dadson y @EddingtonsMonkey , y en soporte @ nils-pipenbrinck . Las otras respuestas (incluidas las aceptadas) se vinculan o citan fuentes que son incorrectas, irrelevantes, obsoletas o rotas.
Debido a que este hilo aparece altamente en los motores de búsqueda, estoy agregando esta respuesta para aclarar los diversos conceptos erróneos sobre el tema.
El brillo es un atributo perceptual, no tiene una medida directa.
La ligereza percibida se mide mediante algunos modelos de visión como CIELAB, aquí L * (Lstar) es una medida de la ligereza perceptiva y no es lineal para aproximar la curva de respuesta no lineal de la visión humana.
La luminancia es una medida lineal de la luz, espectralmente ponderada para la visión normal pero no ajustada para la percepción no lineal de la luminosidad.
Luma ( Y ' prime) es una señal ponderada codificada en gamma utilizada en algunas codificaciones de video. No debe confundirse con la luminancia lineal.
La curva de transferencia o gamma (TRC) es una curva que a menudo es similar a la curva perceptiva, y se aplica comúnmente a los datos de imagen para almacenamiento o transmisión para reducir el ruido percibido y / o mejorar la utilización de datos (y razones relacionadas).
Para determinar la luminosidad percibida , primero convierta los valores de imagen R´G´B´ codificados con gamma en luminancia lineal ( L
o Y
) y luego en luminosidad percibida no lineal ( L*
)
... Porque aparentemente se perdió en alguna parte ...
Convierta todos los valores enteros de 8 bits sRGB a decimal 0.0-1.0
vR = sR / 255;
vG = sG / 255;
vB = sB / 255;
Convierta un RGB codificado en gamma a un valor lineal. sRGB (estándar de la computadora), por ejemplo, requiere una curva de potencia de aproximadamente V ^ 2.2, aunque la transformación "precisa" es:
Donde V´ es el canal R, G o B codificado en gamma de sRGB.
Pseudocódigo:
function sRGBtoLin(colorChannel) {
// Send this function a decimal sRGB gamma encoded color value
// between 0.0 and 1.0, and it returns a linearized value.
if ( colorChannel <= 0.04045 ) {
return colorChannel / 12.92;
} else {
return pow((( colorChannel + 0.055)/1.055),2.4));
}
}
Para encontrar la luminancia (Y) aplique los coeficientes estándar para sRGB:
Pseudocódigo que utiliza las funciones anteriores:
Y = (0.2126 * sRGBtoLin(vR) + 0.7152 * sRGBtoLin(vG) + 0.0722 * sRGBtoLin(vB))
Tome la luminancia Y desde arriba y transfórmela en L *
function YtoLstar(Y) {
// Send this function a luminance value between 0.0 and 1.0,
// and it returns L* which is "perceptual lightness"
if ( Y <= (216/24389) { // The CIE standard states 0.008856 but 216/24389 is the intent for 0.008856451679036
return Y * (24389/27); // The CIE standard states 903.3, but 24389/27 is the intent, making 903.296296296296296
} else {
return pow(Y,(1/3)) * 116 - 16;
}
}
L * es un valor de 0 (negro) a 100 (blanco) donde 50 es el "gris medio" perceptivo. L * = 50 es el equivalente de Y = 18.4, o en otras palabras, una tarjeta gris al 18%, que representa la mitad de una exposición fotográfica (zona V de Ansel Adams).
IEC 61966-2-1:1999 Standard
Wikipedia sRGB
Wikipedia CIELAB
Wikipedia CIEXYZ
Preguntas frecuentes sobre Gamma de Charles Poynton
Luma=rgb2gray(RGB);LAB=rgb2lab(RGB);LAB(:,:,2:3)=0;PerceptualGray=lab2rgb(LAB);
L*a*b*
no tiene en cuenta una serie de atributos psicofísicos. El efecto Helmholtz-Kohlrausch es uno, pero hay muchos otros. CIELAB no es un modelo de evaluación de imagen "completo" de ninguna manera. En mi publicación estaba tratando de cubrir los conceptos básicos lo más completamente posible sin aventurarme en las minucias muy profundas. El modelo Hunt, los modelos de Fairchild y otros hacen un trabajo más completo, pero también son sustancialmente más complejos.
Encontré este código (escrito en C #) que hace un excelente trabajo al calcular el "brillo" de un color. En este escenario, el código está tratando de determinar si se debe colocar texto blanco o negro sobre el color.
Curiosamente, esta formulación para RGB => HSV solo usa v = MAX3 (r, g, b). En otras palabras, puedes usar el máximo de (r, g, b) como V en HSV.
Verifiqué y en la página 575 de Hearn & Baker, así es como también calculan el "Valor".
En lugar de perderse entre la selección aleatoria de fórmulas mencionadas aquí, le sugiero que elija la fórmula recomendada por los estándares del W3C.
Aquí hay una implementación PHP sencilla pero exacta de las fórmulas de relación de luminancia relativa y contraste WCAG 2.0 SC 1.4.3 . Produce valores que son apropiados para evaluar las proporciones requeridas para el cumplimiento de WCAG, como en esta página , y como tal es adecuado y apropiado para cualquier aplicación web. Esto es trivial para portar a otros idiomas.
/**
* Calculate relative luminance in sRGB colour space for use in WCAG 2.0 compliance
* @link http://www.w3.org/TR/WCAG20/#relativeluminancedef
* @param string $col A 3 or 6-digit hex colour string
* @return float
* @author Marcus Bointon <marcus@synchromedia.co.uk>
*/
function relativeluminance($col) {
//Remove any leading #
$col = trim($col, '#');
//Convert 3-digit to 6-digit
if (strlen($col) == 3) {
$col = $col[0] . $col[0] . $col[1] . $col[1] . $col[2] . $col[2];
}
//Convert hex to 0-1 scale
$components = array(
'r' => hexdec(substr($col, 0, 2)) / 255,
'g' => hexdec(substr($col, 2, 2)) / 255,
'b' => hexdec(substr($col, 4, 2)) / 255
);
//Correct for sRGB
foreach($components as $c => $v) {
if ($v <= 0.04045) {
$components[$c] = $v / 12.92;
} else {
$components[$c] = pow((($v + 0.055) / 1.055), 2.4);
}
}
//Calculate relative luminance using ITU-R BT. 709 coefficients
return ($components['r'] * 0.2126) + ($components['g'] * 0.7152) + ($components['b'] * 0.0722);
}
/**
* Calculate contrast ratio acording to WCAG 2.0 formula
* Will return a value between 1 (no contrast) and 21 (max contrast)
* @link http://www.w3.org/TR/WCAG20/#contrast-ratiodef
* @param string $c1 A 3 or 6-digit hex colour string
* @param string $c2 A 3 or 6-digit hex colour string
* @return float
* @author Marcus Bointon <marcus@synchromedia.co.uk>
*/
function contrastratio($c1, $c2) {
$y1 = relativeluminance($c1);
$y2 = relativeluminance($c2);
//Arrange so $y1 is lightest
if ($y1 < $y2) {
$y3 = $y1;
$y1 = $y2;
$y2 = $y3;
}
return ($y1 + 0.05) / ($y2 + 0.05);
}
Para agregar lo que todos los demás dijeron:
Todas estas ecuaciones funcionan bastante bien en la práctica, pero si necesita ser muy preciso, primero debe convertir el color en un espacio de color lineal (aplicar gamma de imagen inversa), hacer el promedio de peso de los colores primarios y, si desea muestra el color: recupera la luminancia en el monitor gamma.
La diferencia de luminancia entre ignorar gamma y hacer gamma adecuada es de hasta 20% en los grises oscuros.
Estaba resolviendo una tarea similar hoy en JavaScript. Me he decidido por esta getPerceivedLightness(rgb)
función para un color HEX RGB. Se trata del efecto Helmholtz-Kohlrausch a través de la fórmula Fairchild y Perrotta para la corrección de luminancia.
/**
* Converts RGB color to CIE 1931 XYZ color space.
* https://www.image-engineering.de/library/technotes/958-how-to-convert-between-srgb-and-ciexyz
* @param {string} hex
* @return {number[]}
*/
export function rgbToXyz(hex) {
const [r, g, b] = hexToRgb(hex).map(_ => _ / 255).map(sRGBtoLinearRGB)
const X = 0.4124 * r + 0.3576 * g + 0.1805 * b
const Y = 0.2126 * r + 0.7152 * g + 0.0722 * b
const Z = 0.0193 * r + 0.1192 * g + 0.9505 * b
// For some reason, X, Y and Z are multiplied by 100.
return [X, Y, Z].map(_ => _ * 100)
}
/**
* Undoes gamma-correction from an RGB-encoded color.
* https://en.wikipedia.org/wiki/SRGB#Specification_of_the_transformation
* /programming/596216/formula-to-determine-brightness-of-rgb-color
* @param {number}
* @return {number}
*/
function sRGBtoLinearRGB(color) {
// Send this function a decimal sRGB gamma encoded color value
// between 0.0 and 1.0, and it returns a linearized value.
if (color <= 0.04045) {
return color / 12.92
} else {
return Math.pow((color + 0.055) / 1.055, 2.4)
}
}
/**
* Converts hex color to RGB.
* /programming/5623838/rgb-to-hex-and-hex-to-rgb
* @param {string} hex
* @return {number[]} [rgb]
*/
function hexToRgb(hex) {
const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
if (match) {
match.shift()
return match.map(_ => parseInt(_, 16))
}
}
/**
* Converts CIE 1931 XYZ colors to CIE L*a*b*.
* The conversion formula comes from <http://www.easyrgb.com/en/math.php>.
* https://github.com/cangoektas/xyz-to-lab/blob/master/src/index.js
* @param {number[]} color The CIE 1931 XYZ color to convert which refers to
* the D65/2° standard illuminant.
* @returns {number[]} The color in the CIE L*a*b* color space.
*/
// X, Y, Z of a "D65" light source.
// "D65" is a standard 6500K Daylight light source.
// https://en.wikipedia.org/wiki/Illuminant_D65
const D65 = [95.047, 100, 108.883]
export function xyzToLab([x, y, z]) {
[x, y, z] = [x, y, z].map((v, i) => {
v = v / D65[i]
return v > 0.008856 ? Math.pow(v, 1 / 3) : v * 7.787 + 16 / 116
})
const l = 116 * y - 16
const a = 500 * (x - y)
const b = 200 * (y - z)
return [l, a, b]
}
/**
* Converts Lab color space to Luminance-Chroma-Hue color space.
* http://www.brucelindbloom.com/index.html?Eqn_Lab_to_LCH.html
* @param {number[]}
* @return {number[]}
*/
export function labToLch([l, a, b]) {
const c = Math.sqrt(a * a + b * b)
const h = abToHue(a, b)
return [l, c, h]
}
/**
* Converts a and b of Lab color space to Hue of LCH color space.
* /programming/53733379/conversion-of-cielab-to-cielchab-not-yielding-correct-result
* @param {number} a
* @param {number} b
* @return {number}
*/
function abToHue(a, b) {
if (a >= 0 && b === 0) {
return 0
}
if (a < 0 && b === 0) {
return 180
}
if (a === 0 && b > 0) {
return 90
}
if (a === 0 && b < 0) {
return 270
}
let xBias
if (a > 0 && b > 0) {
xBias = 0
} else if (a < 0) {
xBias = 180
} else if (a > 0 && b < 0) {
xBias = 360
}
return radiansToDegrees(Math.atan(b / a)) + xBias
}
function radiansToDegrees(radians) {
return radians * (180 / Math.PI)
}
function degreesToRadians(degrees) {
return degrees * Math.PI / 180
}
/**
* Saturated colors appear brighter to human eye.
* That's called Helmholtz-Kohlrausch effect.
* Fairchild and Pirrotta came up with a formula to
* calculate a correction for that effect.
* "Color Quality of Semiconductor and Conventional Light Sources":
* https://books.google.ru/books?id=ptDJDQAAQBAJ&pg=PA45&lpg=PA45&dq=fairchild+pirrotta+correction&source=bl&ots=7gXR2MGJs7&sig=ACfU3U3uIHo0ZUdZB_Cz9F9NldKzBix0oQ&hl=ru&sa=X&ved=2ahUKEwi47LGivOvmAhUHEpoKHU_ICkIQ6AEwAXoECAkQAQ#v=onepage&q=fairchild%20pirrotta%20correction&f=false
* @return {number}
*/
function getLightnessUsingFairchildPirrottaCorrection([l, c, h]) {
const l_ = 2.5 - 0.025 * l
const g = 0.116 * Math.abs(Math.sin(degreesToRadians((h - 90) / 2))) + 0.085
return l + l_ * g * c
}
export function getPerceivedLightness(hex) {
return getLightnessUsingFairchildPirrottaCorrection(labToLch(xyzToLab(rgbToXyz(hex))))
}
El espacio de color HSV debería ser el truco, consulte el artículo de Wikipedia, dependiendo del idioma en el que esté trabajando, puede obtener una conversión de biblioteca.
H es un tono que es un valor numérico para el color (es decir, rojo, verde ...)
S es la saturación del color, es decir, cuán 'intenso' es
V es el 'brillo' del color.
Valor de luminancia RGB = 0.3 R + 0.59 G + 0.11 B
http://www.scantips.com/lumin.html
Si está buscando qué tan cerca está el color blanco, puede usar la distancia euclidiana desde (255, 255, 255)
Creo que el espacio de color RGB es perceptiblemente no uniforme con respecto a la distancia euclidiana L2. Los espacios uniformes incluyen CIE LAB y LUV.
La fórmula de gamma inversa de Jive Dadson necesita que se elimine el medio ajuste cuando se implementa en Javascript, es decir, el retorno de la función gam_sRGB debe ser devuelto int (v * 255); no devuelve int (v * 255 + .5); La mitad del ajuste se redondea hacia arriba, y esto puede causar un valor demasiado alto en un R = G = B, es decir, una tríada de color gris. La conversión en escala de grises en una tríada R = G = B debería producir un valor igual a R; Es una prueba de que la fórmula es válida. Ver Nueve sombras de escala de grises para la fórmula en acción (sin el medio ajuste).
Me pregunto cómo se determinaron esos coeficientes rgb. Hice un experimento yo mismo y terminé con lo siguiente:
Y = 0.267 R + 0.642 G + 0.091 B
Cercano pero obviamente diferente a los coeficientes ITU establecidos desde hace mucho tiempo. Me pregunto si esos coeficientes podrían ser diferentes para cada observador, porque todos podemos tener una cantidad diferente de conos y bastones en la retina en nuestros ojos, y especialmente la relación entre los diferentes tipos de conos puede diferir.
Para referencia:
ITU BT.709:
Y = 0.2126 R + 0.7152 G + 0.0722 B
ITU BT.601:
Y = 0.299 R + 0.587 G + 0.114 B
Hice la prueba moviendo rápidamente una pequeña barra gris sobre un fondo rojo brillante, verde brillante y azul brillante, y ajustando el gris hasta que se mezclara lo más posible. También repetí esa prueba con otros tonos. Repetí la prueba en diferentes pantallas, incluso una con un factor de gamma fijo de 3.0, pero todo me parece igual. Más aún, los coeficientes ITU son literalmente incorrectos para mis ojos.
Y sí, presumiblemente tengo una visión normal del color.
Aquí hay un poco de código C que debería calcular correctamente la luminancia percibida.
// reverses the rgb gamma
#define inverseGamma(t) (((t) <= 0.0404482362771076) ? ((t)/12.92) : pow(((t) + 0.055)/1.055, 2.4))
//CIE L*a*b* f function (used to convert XYZ to L*a*b*) http://en.wikipedia.org/wiki/Lab_color_space
#define LABF(t) ((t >= 8.85645167903563082e-3) ? powf(t,0.333333333333333) : (841.0/108.0)*(t) + (4.0/29.0))
float
rgbToCIEL(PIXEL p)
{
float y;
float r=p.r/255.0;
float g=p.g/255.0;
float b=p.b/255.0;
r=inverseGamma(r);
g=inverseGamma(g);
b=inverseGamma(b);
//Observer = 2°, Illuminant = D65
y = 0.2125862307855955516*r + 0.7151703037034108499*g + 0.07220049864333622685*b;
// At this point we've done RGBtoXYZ now do XYZ to Lab
// y /= WHITEPOINT_Y; The white point for y in D65 is 1.0
y = LABF(y);
/* This is the "normal conversion which produces values scaled to 100
Lab.L = 116.0*y - 16.0;
*/
return(1.16*y - 0.16); // return values for 0.0 >=L <=1.0
}
Por favor, defina el brillo. Si está buscando qué tan cerca está el color blanco, puede usar la distancia euclidiana desde (255, 255, 255)
Este enlace explica todo en profundidad, incluido por qué esas constantes multiplicadoras existen antes de los valores R, G y B.
Editar: tiene una explicación para una de las respuestas aquí también (0.299 * R + 0.587 * G + 0.114 * B)
Para determinar el brillo de un color con R, convierto el color del sistema RGB en color del sistema HSV.
En mi script, utilizo el código del sistema HEX antes por otra razón, pero también puede comenzar con el código del sistema RGB con rgb2hsv {grDevices}
. La documentación está aquí .
Aquí está esta parte de mi código:
sample <- c("#010101", "#303030", "#A6A4A4", "#020202", "#010100")
hsvc <-rgb2hsv(col2rgb(sample)) # convert HEX to HSV
value <- as.data.frame(hsvc) # create data.frame
value <- value[3,] # extract the information of brightness
order(value) # ordrer the color by brightness
Para mayor claridad, las fórmulas que usan una raíz cuadrada deben ser
sqrt(coefficient * (colour_value^2))
no
sqrt((coefficient * colour_value))^2
La prueba de esto radica en la conversión de una tríada R = G = B a escala de grises R. Eso solo será cierto si cuadras el valor del color, no el valor del color por el coeficiente. Ver Nueve sombras de escala de grises