Por qué funciona: Unidad 1.2: Captura de la imagen, Unidad 1.3: La imagen como datos
Brillo y contraste en OpenCV
Aclara una foto subexpuesta de dos maneras, sumando una constante con cv2.add o multiplicando con cv2.convertScaleAbs, y mide cuál queda más cerca de la exposición objetivo: 18,0 contra 9,1.
Traducción asistida por máquina del original en inglés. Los números, el código y las referencias se comprobaron contra él; el texto no fue revisado por un hablante nativo. Leer el original. Los labs interactivos y el código están en inglés.
Aclarar una foto es cambiar el número de cada píxel, y hay dos maneras simples de hacerlo: sumar la misma cantidad a todos o multiplicar todos por el mismo factor.

Dónde ya lo has visto: los controles de Brillo y Contraste de cualquier editor de fotos del celular; Colores › Brillo-Contraste en GIMP; el control de Exposición de Lightroom y darktable.
TL;DR. Multiplicar los valores de los píxeles aclara una foto oscura con mucha más fidelidad que sumarles algo, porque una exposición más larga multiplica la luz. En esta foto, sumar 50 deja una diferencia media de 18,0 con la exposición objetivo y multiplicar por 2,4 deja 9,1.
El problema
La lección 2 mostró el histograma de la foto oscura apretado en el extremo izquierdo. La corrección tiene que moverlo a la derecha. El módulo tiene una toma más larga y bien expuesta de la misma escena (el objetivo), tomada a 5 segundos, así que cada corrección se puede puntuar: en promedio, ¿a qué distancia está cada píxel y canal de la foto corregida del mismo píxel del objetivo? Menos es mejor, y 0 es idéntico.
Sumar, luego multiplicar
El tutorial de brillo y contraste de OpenCV escribe las dos operaciones en una sola fórmula, salida = α × entrada + β [1]. β se suma a cada valor (brillo); α multiplica cada valor (contraste, o ganancia). cv2.add hace la suma, y cv2.convertScaleAbs hace la multiplicación, redondea y limita el resultado a 0–255.
import cv2
import numpy as np
img = cv2.imread("dark.webp")
ref = cv2.imread("reference.webp")
def distance(a, b):
# diferencia media por píxel y canal, en la escala 0-255
return np.abs(a.astype(int) - b.astype(int)).mean()
added = cv2.add(img, np.full_like(img, 50))
multiplied = cv2.convertScaleAbs(img, alpha=2.4)
print("as it is: ", round(distance(img, ref), 1))
print("add 50: ", round(distance(added, ref), 1))
print("times 2.4: ", round(distance(multiplied, ref), 1))as it is: 47.0 add 50: 18.0 times 2.4: 9.1
| Función | Argumentos | Qué hace |
|---|---|---|
cv2.add(img, other) | dos arrays de la misma forma | los suma, deteniéndose en 255 |
cv2.convertScaleAbs(img, alpha, beta) | ganancia alpha, desplazamiento beta | alpha * img + beta, redondeado, en valor absoluto, limitado a 0–255 |
np.full_like(img, 50) | una forma y un valor | un array como img, lleno de 50 |
Sumar 50 fue la mejor suma, y multiplicar por 2,4 la mejor multiplicación, cuando cada una se probó por pasos a lo largo de su rango (la búsqueda está en el código del módulo, enlazado desde la página del proyecto). Multiplicar reduce más o menos a la mitad la diferencia que queda.
def half(x):
return cv2.resize(x, None, fx=0.5, fy=0.5)
small = [half(x) for x in (added, multiplied, ref)]
cv2.imshow("add 50 | times 2.4 | reference", np.hstack(small))
La versión sumada (izquierda) tiene un velo gris: la mesa y la pared oscuras subieron los mismos 50 que todo lo demás, así que los negros ya no son negros. La versión multiplicada mantiene oscuras las zonas oscuras y estira el resto, que es lo que hace una exposición más larga. El tutorial de OpenCV describe el mismo velo para β y señala que α cuesta detalle en las zonas que ya eran claras [1], que es lo que le pasó a la lámpara.
Una ganancia tan grande funciona porque la foto está solo dos pasos oscura. Para la foto más de tres pasos oscura, la lección 4 muestra dónde una ganancia deja de funcionar.
Rómpelo
cv2.add se detiene en 255. El + de NumPy sobre arrays uint8 no: da la vuelta, y 253 + 50 se convierte en 47.
# El + de NumPy sobre uint8 da la vuelta en lugar de detenerse en 255
wrapped = img + np.full_like(img, 50)
y, x = np.unravel_index(img.max(axis=2).argmax(), img.shape[:2])
print("brightest pixel:", img[y, x])
print("numpy:", wrapped[y, x], " cv2.add:", added[y, x])
cv2.imshow("img + 50 with NumPy", wrapped)brightest pixel: [253 251 255] numpy: [47 45 49] cv2.add: [255 255 255]
Las partes más claras de la imagen se vuelven las más oscuras. Usa cv2.add, o convierte a un tipo más amplio, suma y recorta con clip.
Tu turno
La foto sobreexpuesta, bright.webp, necesita oscurecerse. Elige un multiplicador que deje su distancia al objetivo por debajo de 30.
bright = cv2.imread("bright.webp")
fixed = cv2.convertScaleAbs(bright, alpha=1.0) # cambia alpha
print(round(distance(fixed, ref), 1))Por qué funciona
La foto oscura se expuso durante 1,3 segundos y el objetivo durante 5, así que el objetivo recogió casi cuatro veces más luz. Los conteos en bruto de una cámara crecen en proporción a la luz, y por eso una ganancia, y no un desplazamiento, es la forma correcta de corrección (From photons to counts (en inglés)). La mejor ganancia aquí es 2,4 y no cerca de 4 porque la foto no está en bruto: sus valores pasaron por una curva de tonos que comprime los valores claros, y The response curve (en inglés) mide esa curva en esta misma escena. La suma y la multiplicación de imágenes píxel a píxel son las operaciones aritméticas básicas del procesamiento de imágenes [2].
Errores comunes
img + 50con NumPy. Da la vuelta pasado 255, como muestra la celda Rómpelo.cv2.add(img, 50)con un número suelto. En OpenCV 4.8.1 eso suma 50 solo al canal azul; en 4.11 y 5.0 lo suma a los tres (ambos comprobados el 30 de septiembre de 2026). Una instalación antigua da un resultado azulado sin ningún error. Pasa un array, como hacen las celdas de aquí.- Esperar que la multiplicación recupere luces quemadas. Un valor que ya está en 255 se queda en 255 con cualquier ganancia. La lámpara de esta foto se pierde con cualquier ajuste.
- Juzgar la corrección a ojo. La versión sumada puede verse más clara y más bonita en la pantalla del celular y aun así estar más lejos de la exposición objetivo. Mide contra algo.
Referencias
[1] OpenCV. Changing the contrast and brightness of an image! (tutorial, doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown). OpenCV 4.11.0, commit 31b0eee. github.com/opencv/opencv
[2] Gonzalez, R. C., & Woods, R. E. (2018). Digital Image Processing (4th ed.), section 2.6 “Introduction to the Basic Mathematical Tools Used in Digital Image Processing”, Arithmetic Operations, p. 69. Pearson.
[3] Fairchild, M. D. (2007). The HDR Photographic Survey. IS&T 15th Color Imaging Conference, pp. 233–238. doi:10.2352/CIC.2007.15.1.art00044