HOWTO · Python
Enmascaramiento de imágenes en OpenCV
En este artículo, veremos cómo realizar el enmascaramiento de imágenes usando Bitwise AND en OpenCV Python.
En esta página
El enmascaramiento de imágenes permite conservar una parte de una imagen y combinarla con otra imagen. En OpenCV, el enmascaramiento se basa en la operación bit a bit AND: la función cv.bitwise_and() calcula el AND bit a bit elemento a elemento de dos matrices, y una máscara opcional decide qué píxeles de ese resultado llegan a la salida.
Este artículo recorre el flujo de trabajo estándar de enmascaramiento en Python. Con una foto de un coche y un archivo de logotipo, preparamos una región de interés (ROI), construimos una máscara binaria y su inversa a partir del logotipo, aplicamos ambas máscaras y componemos el logotipo en la esquina superior izquierda de la imagen del coche.
Cómo cv.bitwise_and() aplica una máscara
cv.bitwise_and() es una función a nivel de módulo del paquete cv2, no un método de un objeto de imagen. Su firma en Python es la siguiente:
cv.bitwise_and(src1, src2[, dst[, mask]])
src1ysrc2son las dos matrices de entrada. Deben tener el mismo tamaño y el mismo tipo de datos. Para enmascarar una sola imagen, pasa la misma matriz en ambos argumentos.dstes una matriz de salida opcional del mismo tamaño y tipo; cuando la omites, la función reserva y devuelve el resultado.maskes una matriz opcional de 8 bits y un solo canal, del mismo tamaño que las fuentes. Donde la máscara contiene un valor distinto de cero, el píxel de salida essrc1 & src2; donde la máscara contiene0, el píxel de salida es0. La máscara, por tanto, selecciona qué píxeles de la operación aparecen en el resultado: esa selección es la idea central del enmascaramiento de imágenes.
Ejecuta este ejemplo, que llama a cv.bitwise_and() cuando OpenCV está instalado y, en caso contrario, realiza el mismo AND elemento a elemento con listas simples de Python. El código y los mensajes visibles del ejemplo se mantienen en inglés:
import importlib.util
if importlib.util.find_spec("cv2") is not None:
import cv2 as cv
import numpy as np
pixels = np.array([[200, 120, 60], [30, 90, 250]], dtype=np.uint8)
mask = np.array([[255, 255, 0], [0, 255, 255]], dtype=np.uint8)
result = cv.bitwise_and(pixels, pixels, mask=mask).tolist()
else:
pixels = [[200, 120, 60], [30, 90, 250]]
mask = [[255, 255, 0], [0, 255, 255]]
result = [[v & m for v, m in zip(pr, mr)] for pr, mr in zip(pixels, mask)]
print(result)
[[200, 120, 0], [0, 90, 250]]
Cada píxel bajo un valor de máscara 255 se conserva; cada píxel bajo un valor 0 se elimina. En una máscara real, los valores se generan mediante umbralización en lugar de escribirse a mano, que es el siguiente paso.
Preparar la región de interés de la imagen
El objetivo es colocar el logotipo en la esquina superior izquierda de la imagen del coche, por lo que el código lee ambos archivos y recorta una región coincidente de la imagen del coche:
car = cv.imread("car.jpg")ylogo = cv.imread("logo.png")cargan las imágenes como matrices BGR.cv.imreaddevuelveNonecuando un archivo no se carga, así que comprueba ambos resultados antes de continuar.logo.shapedevuelve(rows, cols, channels); las dos primeras entradas dan las dimensiones del logotipo.roi = car[0:rows, 0:cols]toma una vista de NumPy de la esquina superior izquierda de la imagen del coche que coincide exactamente con el tamaño del logotipo. Al ser una vista, escribir el compuesto de vuelta encar[0:rows, 0:cols]actualiza la imagen original.
El logotipo debe caber dentro de la imagen del coche. Si el logotipo es más ancho o más alto que la foto, el recorte se reduce silenciosamente y cv.bitwise_and(roi, roi, mask=mask_inv) falla después con un desajuste de tamaño, porque mask_inv tiene el tamaño del logotipo y no el de la ROI reducida, así que redimensiona primero el logotipo o elige una ubicación que encaje. Ten en cuenta también que cv.imread carga un PNG como imagen BGR de tres canales por defecto (el canal alfa se descarta), de modo que el logotipo y la ROI terminan con el mismo número de canales.
Crear una máscara binaria para el logotipo
Del logotipo salen dos máscaras: mask, que selecciona los píxeles del propio logotipo, y mask_inv, que selecciona todo lo demás.
gray_logo = cv.cvtColor(logo, cv.COLOR_BGR2GRAY)convierte el logotipo a escala de grises; consulta la funcióncv.cvtColor()para más información sobre la conversión de color._, mask = cv.threshold(gray_logo, 10, 255, cv.THRESH_BINARY)produce la máscara binaria: los píxeles más brillantes que10pasan a255y los más oscuros a0. Para un logotipo sobre un fondo oscuro o negro, esto aísla la forma del logotipo; ajusta el10según tu propio fondo.mask_inv = cv.bitwise_not(mask)invierte cada bit, lo que da la máscara complementaria que selecciona la región de fondo.
Combinar el logotipo y la región de interés
Con ambas máscaras listas, dos AND enmascarados reparten el trabajo: cv.bitwise_and(roi, roi, mask=mask_inv) conserva el fondo del coche y ennegrece la forma del logotipo, mientras que cv.bitwise_and(logo, logo, mask=mask) conserva solo los píxeles del logotipo. cv.add() suma entonces los dos resultados en el compuesto, que se escribe de vuelta en la imagen del coche.
El script completo que sigue realiza cada paso. Cuando OpenCV o los archivos de imagen no están presentes, imprime un mensaje breve y se detiene sin traceback. El código y los mensajes visibles del script se mantienen en inglés:
import importlib.util
if importlib.util.find_spec("cv2") is None:
print("This example needs OpenCV installed: pip install opencv-python.")
raise SystemExit
import cv2 as cv
car = cv.imread("car.jpg")
logo = cv.imread("logo.png")
if car is None or logo is None:
print("car.jpg and logo.png must sit next to this script.")
raise SystemExit
rows, cols = logo.shape[:2]
if rows > car.shape[0] or cols > car.shape[1]:
print("The logo is larger than the car image; resize it first.")
raise SystemExit
roi = car[0:rows, 0:cols]
gray_logo = cv.cvtColor(logo, cv.COLOR_BGR2GRAY)
_, mask = cv.threshold(gray_logo, 10, 255, cv.THRESH_BINARY)
mask_inv = cv.bitwise_not(mask)
background = cv.bitwise_and(roi, roi, mask=mask_inv)
foreground = cv.bitwise_and(logo, logo, mask=mask)
composite = cv.add(background, foreground)
car[0:rows, 0:cols] = composite
cv.imwrite("car-with-logo.png", car)
print("Wrote car-with-logo.png")
El script guarda la imagen terminada con cv.imwrite() para que también funcione en máquinas sin pantalla; las llamadas de visualización aparecen en la siguiente sección.
Inspeccionar el resultado del enmascaramiento
El compuesto coloca el logotipo sobre la esquina superior izquierda del coche mientras el resto de la foto permanece intacto:
Para ver lo que produjo cada etapa, muestra o guarda los intermedios: cv.imshow("mask", mask), cv.imshow("mask_inv", mask_inv), cv.imshow("background", background) y cv.imshow("foreground", foreground) seguidos de cv.waitKey(0) los muestran en pantalla, mientras que cv.imwrite() los escribe en disco en máquinas sin entorno gráfico. cv.imshow() necesita una pantalla con interfaz gráfica, razón por la cual el script anterior guarda archivos.
En mask deberías ver la silueta blanca del logotipo sobre negro, y mask_inv es su imagen especular. background muestra la esquina del coche con la forma del logotipo recortada, y foreground muestra solo el logotipo. Ese es todo el patrón de enmascaramiento: umbraliza una vez, aplica cv.bitwise_and() dos veces, suma los resultados y la ROI contiene la imagen fusionada.