Por qué funciona: Unidad 1.3: La imagen como datos

Encontrar el rango HSV de un objeto en OpenCV

Lee los píxeles de una mesa de billar en HSV con cv2.cvtColor y encuentra el rango que ocupa el paño: su valor de verde cae casi diez veces entre el extremo iluminado y una sombra, mientras su tono se queda entre 86 y 90.

Luis Condados · · 5 min de lectura Principiante

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.

El canal de tono de la foto del billar: el paño es un solo gris uniforme.
El canal de tono de la foto del billar: el paño es un solo gris uniforme.

Para separar un objeto de una foto por su color necesitas el rango de números que ocupa ese color, y la forma más rápida de encontrarlo es leer algunos de sus píxeles en HSV.

La foto de la mesa de billar con cuatro puntos de muestra marcados con un círculo, junto a su canal de tono, donde todo el paño es un solo gris uniforme y las bolas resaltan.
Izquierda: la foto, con los cuatro puntos que lee esta lección marcados con un círculo. Derecha: solo su canal de tono, estirado a 0–255 para mostrarlo. El paño es un solo gris uniforme desde el extremo cercano hasta la banda del fondo, y la sala oscura de arriba es ruido. Foto “Billiards table 2.JPG” de MarkBuckawicki, CC0 [4].

Dónde ya lo has visto: el filtro de chroma key (pantalla verde) de OBS y de los editores de video; Seleccionar › Por color en GIMP y Gama de colores en Photoshop; las clasificadoras ópticas que descartan granos y frutas de otro color.

TL;DR. Un color nos parece el mismo a la luz y a la sombra, pero los números que la cámara guarda para él cambian mucho, y uno de ellos, el tono, casi no se mueve, así que ese es el número que hay que probar. En la mesa de billar el valor de verde del paño cae casi diez veces, de 201 en el extremo iluminado a 21 en la sombra de una bola, mientras su tono se queda entre 86 y 90 en la escala 0–179 de OpenCV.

El problema

Este módulo encuentra todas las bolas de una mesa de billar (el proyecto). Las bolas vienen en todos los colores, pero el paño de debajo tiene uno solo, así que el plan es encontrar el paño y quedarse con lo que no es paño. Para eso hace falta una regla para “paño” que una computadora pueda probar en cada píxel.

Lee los píxeles en HSV

cv2.cvtColor convierte una imagen entera de un espacio de color a otro. Con COLOR_BGR2HSV cada píxel se vuelve tres números nuevos: tono (qué color, 0–179), saturación (qué tan puro, 0–255) y valor (qué tan claro, 0–255) [1].

Python · OpenCV
import cv2
import numpy as np

img = cv2.imread("pool.webp")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# (x, y) de cuatro puntos de la foto
points = {"cloth, near": (400, 240),
          "cloth, far": (700, 95),
          "shadow": (790, 336),
          "green ball": (727, 155)}
for name, (x, y) in points.items():
    print(f"{name:12} BGR {img[y, x]}  HSV {hsv[y, x]}")
Salida
cloth, near  BGR [198 201   9]  HSV [ 90 244 201]
cloth, far   BGR [32 37  2]  HSV [ 86 241  37]
shadow       BGR [18 21  0]  HSV [ 86 255  21]
green ball   BGR [ 86 117   5]  HSV [ 82 244 117]
LlamadaQué hace
cv2.cvtColor(img, cv2.COLOR_BGR2HSV)una imagen nueva del mismo tamaño, con H, S y V en lugar de B, G y R
img[y, x]los tres números de un píxel; primero la fila (y), luego la columna (x)

Los tres puntos del paño nos parecen un solo color. En BGR están lejos entre sí: el canal verde cae de 201 cerca de la lámpara a 37 en el extremo del fondo y 21 en la sombra de la bola blanca, y el azul y el verde caen juntos, así que ningún rango único en ningún canal cubre los tres sin cubrir también la mayoría de los demás colores. En HSV el tono es 90, 86 y 86 y solo cambia el valor. En esa propiedad se va a apoyar la máscara: el brillo tiene su propio número, así que un rango en el tono ignora la luz y la sombra [2].

La bola verde es la advertencia. Su tono es 82, a cuatro pasos del paño del fondo. Un rango lo bastante ancho para el paño con cualquier luz puede tragársela; la lección 5 cuenta cuánto cuesta eso.

Encuentra el rango en una zona

Un píxel solo puede tener mala suerte. La esquina inferior derecha de la foto es solo paño, así que su dispersión es el rango desde el que empezar:

Python · OpenCV
# una zona de la foto que es solo paño
patch = hsv[550:, 650:]
for i, name in enumerate(["H", "S", "V"]):
    lo, hi = np.percentile(patch[..., i], [2, 98])
    print(f"{name}: {lo:.0f} to {hi:.0f}")
cv2.imshow("hue (0-179)", hsv[..., 0])
Salida
H: 85 to 88
S: 233 to 253
V: 118 to 163
hue (0-179)
hue (0-179)

Los percentiles descartan el 2% extremo de cada lado, para que una mota suelta no fije el rango. En esta zona el tono va de 85 a 88 y el valor de 118 a 163. El extremo del fondo de la mesa y las sombras son más oscuros que cualquier cosa de la zona (el paño del fondo estaba en 37), así que el rango de valor tiene que bajar bastante por debajo de 118. El módulo empieza con una ventana amplia, que la lección 2 convierte en una máscara:

HSV
inferior758040
superior100255255

La imagen del tono muestra el paño como un solo gris de adelante hacia atrás. La sala negra sobre la mesa es ruido: cuando un píxel es casi negro, los tres canales están todos cerca de 0 y la fórmula divide diferencias pequeñas entre un número pequeño, así que su tono es casi aleatorio [2].

Rómpelo

cvtColor confía en el flag que le pasas. cv2.imread devuelve BGR, y el flag para eso es COLOR_BGR2HSV. Pasa COLOR_RGB2HSV y el rojo y el azul se intercambian antes de la conversión:

Rómpelo
# RGB2HSV en una imagen BGR: rojo y azul intercambiados
wrong = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
x, y = points["cloth, near"]
print("right:", hsv[y, x], " wrong:", wrong[y, x])
cv2.imshow("hue, wrong flag", wrong[..., 0])
Salida
right: [ 90 244 201]  wrong: [ 30 244 201]
hue, wrong flag
hue, wrong flag

El tono del paño sale como 30, que es amarillo en la escala de OpenCV, y una máscara construida sobre él no seleccionaría nada en la mesa. No hay ningún error que te avise.

Tu turno

Lee la bola morada en x = 414, y = 109 y guarda su tono en hue.

Tu turno
hue = None  # reemplaza esta línea
print(hue)

Por qué funciona

Una cámara registra cuánta luz roja, verde y azul llegó a cada píxel, así que una superficie en sombra da valores menores en los tres canales a la vez. HSV toma el mayor de los tres como valor y describe el color por la posición de los tres entre sí, que no cambia cuando los tres se reducen juntos [2]. Gonzalez y Woods derivan la misma idea para el modelo HSI, muy cercano, y lo usan para segmentar por color [3]. Colour spaces: choosing axes you can threshold (en inglés) hace la conversión paso a paso sobre un solo parche medido de una carta de color, y how close is your colour (en inglés) mide cuánto se alejan los colores de una cámara de los reales.

Errores comunes

  • Tono en grados. Los selectores de color dan el tono en 0–360. OpenCV lo divide entre dos para que quepa en un byte: 0–179 para imágenes de 8 bits [2]. El paño en 88 queda en unos 176 grados en GIMP.
  • El flag de conversión equivocado. imread devuelve BGR; usa COLOR_BGR2HSV, como muestra la celda Rómpelo.
  • Confiar en el tono de píxeles oscuros o grises. Cuando la saturación o el valor están cerca de 0, el tono es ruido. La bola blanca, blanco puro, tiene saturación 11: su tono no dice nada.
  • El rojo da la vuelta. El rojo está en los dos extremos de la escala de tono, cerca de 0 y cerca de 179, así que un solo rango no lo contiene. Colour spaces muestra la solución con dos máscaras.
  • Leer un solo píxel. Toma una zona y sus percentiles, y mira tanto la parte iluminada como la sombreada.

Referencias

[1] OpenCV. Changing Colorspaces (Python tutorial, doc/py_tutorials/py_imgproc/py_colorspaces/py_colorspaces.markdown). OpenCV 4.11.0, commit 31b0eee. github.com/opencv/opencv

[2] OpenCV. Color conversions, section “RGB ↔ HSV” (modules/imgproc/doc/colors.markdown). OpenCV 4.11.0, commit 31b0eee. github.com/opencv/opencv

[3] Gonzalez, R. C., & Woods, R. E. (2018). Digital Image Processing (4th ed.), section 7.2 “Color Models”, The HSI Color Model, p. 541, and section 7.7 “Using Color in Image Segmentation”, p. 575. Pearson.

[4] MarkBuckawicki. Billiards table 2.JPG (27 Feb 2014). Wikimedia Commons, CC0 1.0. commons.wikimedia.org