Simple Thresholding (Umbralización) OpenCV en Python

Por Administrador


CONTENIDO

  1. Umbralización simple/ simple thresholding
  2. ¿Cómo funciona la umbralización simple?
    • ¿Qué necesito para aplicar umbralización simple en OpenCV?
    • Apliquemos a esta imagen otro tipo de umbralización
  3. Aplicando distintos tipos de umbralización con OpenCV

En esta oportunidad se tratará el tema de umbralización simple, de que se trata, como aplicarlo en imágenes y por supuesto código. ¡Empecemos!

UMBRALIZACIÓN SIMPLE / SIMPLE THRESHOLDING

La umbralización es el método más simple de la segmentación de imágenes cuyo objetivo es separar un objeto de interés del fondo de una imagen. Hay que tomar en cuenta que para su aplicación deben emplearse imágenes en las que el objeto y el fondo sean diferenciables, ya que si no lo son, va a ser mucho más difícil determinar dicho objeto.

¿CÓMO FUNCIONA LA UMBRALIZACIÓN SIMPLE?

Para la aplicación de esta técnica necesitamos principalmente de:

  • Imagen en escala de grises
  • Umbral

De aquí partiremos para la explicación. En un principio construyamos la siguiente imagen en escala de grises con numpy.

Figura 1: Imagen creada para explicar la umbralización simple.

En la imagen se puede apreciar como título «Umbral: T=130», seguido de 6 secciones cada una de ellas con su valor en escala de grises, seguido de una comparación entre dicho valor y 130. Esto servirá muchísimo para realizar el proceso de thresholding. Esta imagen se construye con las siguientes líneas de código en Python:

import cv2
import numpy as np

grises = np.zeros((500,600),dtype=np.uint8)

font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(grises,'Umbral: T=130',(100,70), font, 1.5,(255),2,cv2.LINE_AA)

grises[100:300,:200] = 130
grises[100:300,200:400] = 20
grises[100:300,400:600] = 210
grises[300:600,:200] = 35
grises[300:600,200:400] = 255
grises[300:600,400:600] = 70

cv2.putText(grises,'130',(60,150), font, 1, (255), 1, cv2.LINE_AA)
cv2.putText(grises,'20',(280,150), font, 1,(255), 1, cv2.LINE_AA)
cv2.putText(grises,'210',(470,150), font, 1,(0), 1, cv2.LINE_AA)
cv2.putText(grises,'35',(70,350), font, 1, (255), 1, cv2.LINE_AA)
cv2.putText(grises,'255',(270,350), font, 1, (0), 1, cv2.LINE_AA)
cv2.putText(grises,'70',(480,350), font, 1, (255), 1, cv2.LINE_AA)
cv2.putText(grises,'130>T?',(40,230), font, 1, (255), 1, cv2.LINE_AA)
cv2.putText(grises,'20>T?',(250,230), font, 1, (255), 1, cv2.LINE_AA)
cv2.putText(grises,'210>T?',(440,230), font, 1, (0), 1, cv2.LINE_AA)
cv2.putText(grises,'35>T?',(50,430), font, 1, (255), 1, cv2.LINE_AA)
cv2.putText(grises,'255>T?',(240,430), font, 1, (0), 1, cv2.LINE_AA)
cv2.putText(grises,'70>T?',(450,430), font, 1, (255), 1, cv2.LINE_AA)

cv2.imshow('Grises',grises)
cv2.waitKey(0)
cv2.destroyAllWindows()

Línea 1 y 2: Se importa OpenCV y numpy.

Línea 4: Construcción de una imagen con 500 pixeles de alto y 600 de ancho en negro. Hay que tomar en cuenta que esta imagen está en escala de grises puesto que no se han especificado los 3 canales corresponientes de RGB por ejemplo.

Línea 6: Se determina el tipo de fuente que se va a visualizar en el texto posteriormente.

Línea 9 a 14: Se divide a la imagen en secciones, para luego a cada una de ellas asignarle grises diferentes.

Línea 16 a 27: A través  de cv2.putTextse puede visualizar el texto en una imagen.

La imagen creada ya nos da una pista de lo que va a pasar y es que dado un umbral (entero de entre 0 y 255), va a ser comparado con el valor de cada pixel en la imagen, y se asigna un nuevo valor cuando el valor del  pixel sea mayor al del umbral, caso contrario el nuevo valor será cero.

¿QUÉ NECESITO PARA APLICAR UMBRALIZACIÓN SIMPLE EN OPENCV?

Para poder implementar esta técnica, OpenCV tiene una función ya creada llamada cv2.threshold. En ella se debe especificar:

  • Imagen en escala de grises
  • Umbral
  • Nuevo Valor
  • Método de umbralización que se va a emplear

A su ves, esta nos devulve 2 valores, el primero no lo veremos en este post, pues es usado para otro tipo de umbralización, mientras que el segundo es la imagen resultante de la aplicación de esta técnica que por cierto es binaria, es decir que la imagen resultante se presentará en color blanco y negro.

Apliquemos entonces thresholding a la imagen anteriormente realizada:

import cv2
import numpy as np

grises = np.zeros((500,600),dtype=np.uint8)

font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(grises,'Umbral: T=130',(100,70), font, 1.5,(255),2,cv2.LINE_AA)

grises[100:300,:200]=130
grises[100:300,200:400]=20
grises[100:300,400:600]=210
grises[300:600,:200]=35
grises[300:600,200:400]=255
grises[300:600,400:600]=70
grises2 = grises.copy()
cv2.putText(grises,'130',(60,150), font, 1,(255),1,cv2.LINE_AA)
cv2.putText(grises,'20',(280,150), font, 1,(255),1,cv2.LINE_AA)
cv2.putText(grises,'210',(470,150), font, 1,(0),1,cv2.LINE_AA)
cv2.putText(grises,'35',(70,350), font, 1,(255),1,cv2.LINE_AA)
cv2.putText(grises,'255',(270,350), font, 1,(0),1,cv2.LINE_AA)
cv2.putText(grises,'70',(480,350), font, 1,(255),1,cv2.LINE_AA)
cv2.putText(grises,'130>T?',(40,230), font, 1,(255),1,cv2.LINE_AA)
cv2.putText(grises,'20>T?',(250,230), font, 1,(255),1,cv2.LINE_AA)
cv2.putText(grises,'210>T?',(440,230), font, 1,(0),1,cv2.LINE_AA)
cv2.putText(grises,'35>T?',(50,430), font, 1,(255),1,cv2.LINE_AA)
cv2.putText(grises,'255>T?',(240,430), font, 1,(0),1,cv2.LINE_AA)
cv2.putText(grises,'70>T?',(450,430), font, 1,(255),1,cv2.LINE_AA)

_, binarizada = cv2.threshold(grises2,130,255,cv2.THRESH_BINARY)

cv2.imshow('Grises',grises)
cv2.imshow('Binarizada',binarizada)
cv2.waitKey(0)
cv2.destroyAllWindows()

Nótese que se ha añadido en la línea 15 una copia de imagen previa la visualización del texto de cada uno de los valores de grises de las secciones y las comparaciones, esto para no aplicar la umbralización a ese texto.

Ahora sí, en la línea 29 se usa la función cv2.threshold, a ella se le ha alimentado con:

  • grises2, que es la imagen en escala de grises.
  • 130, el umbral al que se va a comparar cada valor de los pixeles de ima imagen.
  • 255, es el nuevo valor que será asignado cuando el valor del píxel sea mayor al del umbral
  • cv2.THRESH_BINARY, el tipo de umbralización que se va a usar, posteriormente en este mismo post, veremos otros.

Entonces obtenemos la siguiente imagen:

Figura 2: Imagen resultante, luego de aplicar umbralización simple a la imagen de la figura 1.

Como podemos apreciar en la figura 2, dentro de las 6 secciones que en un principio se dividió, solo en 2 de ellas el umbral estuvo por debajo de los valores de sus pixeles, por lo que se visualizan en blanco o 255. Mientras que en las 4 restantes el valor del umbral fue superior al de los valores de sus pixeles por lo cual automáticamente se visualizan en negro (0).

Como pudiste darte cuenta, la umbralización o thresholding, no es más que la compración entre un umbral y los valores de los píxeles de la imagen en escala de grises, si dicha compración (valor del píxel > umbral) es verdadera, sea asigna un nuevo valor, caso contrario se asigna 0.

Cabe destacar que el valor del umbral y del nuevo valor son determinados de acuerdo a la imagen con la que se vaya a trabajar.

Apliquemos a esta misma imagen, otro tipo de umbralización

Ahora en vez de cv2.THRESH_BINARY, apliquemos cv2.THRESH_BINARY_INV, en la línea 29.

_, binarizadaINV = cv2.threshold(grises2, 130, 255, cv2.THRESH_BINARY_INV)

Obteniendo la siguiente imagen:

Figura 3: Imagen resultante, luego de aplicar umbralización simple inversa a la imagen de la figura 1.

La figura 3 muestra que ha pasado con la aplicación de otro tipo de umbralización, en este cuando se cumple que el valor del pixel es mayor a la del umbral toma el valor de 0 o negro, mientras que cuando no se cumple se asigna 255, para este ejemplo.

APLICANDO DISTINTOS TIPOS DE UMBRALIZACIÓN CON OPENCV

Previamente en este post hemos visto los tipos de umbralización más básicos, como lo son cv2.THRESH_BINARY  y cv2.THRESH_BINARY_INV. Ahora a más de estos veremos tres másque son: cv2.THRESH_TRUNC, cv2.THRESH_TOZERO y cv2.THRESH_TOZERO_INV. Para ello usaremos la siguiente imagen:

Figura 4: Imagen que se usará para aplicar tipos de umbralización / thresholding

cv2.THRESH_BINARY y cv2.THRESH_BINARY

import cv2
import numpy as np
import imutils

image = cv2.imread('img3.jpg', 0)
image = imutils.resize(image, width=400)#Se puede omitir esta línea

_, binarizada = cv2.threshold(image, 210, 255, cv2.THRESH_BINARY)
_, binarizadaInv = cv2.threshold(image, 210, 255, cv2.THRESH_BINARY_INV)

cv2.imshow('Tipos: Binary - Binary Inv',np.hstack([binarizada,binarizadaInv]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Línea 1 a 3: Se importa OpenCV, numpy e imutils (que usaremos para redimensionar la imagen, se puede omitir este paso).

Línea 5: Se lee la imagen y directamente se la transforma en esca de grises, ya que en la lectura se esta usando 0 .

Línea 6: Redimensionamiento de  la imagen a 400 pixeles de ancho.

Línea 8: Aplicación de cv2.THRESH_BINARY

Línea 9: Aplicación de cv2.THRESH_BINARY_INV

Línea 11: Visualización de ambos tipos en una misma imagen con np.hstack.

Figura 5: Aplicación de cv2.THRESH_BINARY y cv2.THRESH_BINARY_INV a la figura 4.

El  umbral en este caso se ha determinado en 210, todos los valores de los pixeles por encima de este umbral se les asigna 255 (blanco) y 0 (negro) para aquellos que no superan en umbral, como se ve para cv2.THRESH_BINARY. Mientras que inversamente paracv2.THRESH_BINARY_INV, como vimos previamente en este post.

cv2.THRESH_TRUNC

import cv2
import numpy as np
import imutils

image = cv2.imread('img3.jpg',0)
image = imutils.resize(image, width=400)#Se puede omitir esta línea

_,Trunc = cv2.threshold(image,210,255,cv2.THRESH_TRUNC)

cv2.imshow('Tipos: Trunc',Trunc)
cv2.waitKey(0)
cv2.destroyAllWindows()

Figura 6: Aplicación de cv2.THRESH_TRUNC en la imagen de la figura 4.

En el tipo truncate, cuando los pixeles son mayores al valor del umbral, estos toman el valor del umbral, por lo tanto el tercer argumento que se le da a cv2.threshold es ignorado. Si el valor del pixel es menor al del umbral, entonces permanece la misma intensidad de la imagen de entrada.

cv2.THRESH_TOZERO y cv2.THRESH_TOZERO_INV

import cv2
import numpy as np
import imutils

image = cv2.imread('img3.jpg',0)
image = imutils.resize(image, width=400)#Se puede omitir esta línea

_, Toz = cv2.threshold(image, 210, 255, cv2.THRESH_TOZERO)
_, TozInv = cv2.threshold(image, 210, 255, cv2.THRESH_TOZERO_INV)

cv2.imshow('Tipos: Tozero - Tozero Inv',np.hstack([Toz,TozInv]))
cv2.waitKey(0)
cv2.destroyAllWindows()

Figura 7: Aplicación de cv2.THRESH_TOZERO y cv2.THRESH_TOZERO_INV en la imagen de la figura 4.

Para estos casos también el tercer argumento que le damos a la función cv2.threshold, va a ser ignorado.

En el caso de umbralización to zero, cuando el valor del pixel es mayor a la del umbral se mantiene el mismo gris de la imagen original, mientras que cuando el valor del pixel es menor al umbral se asigna 0 (negro). Y también se puede apreciar to zero de forma invertida con cv2.THRESH_TOZERO_INV.

Por último, comunmente el color blanco representará el área donde está presente el objeto de interés y en negro el fondo.

RESUMEN DEL CONTENIDO DE ESTE POST