Introducción al procesamiento de imágenes en Python con OpenCV

El artículo proporciona una breve introducción a varias tareas de procesamiento de imágenes con la ayuda del lenguaje de programación Python.

Introducción

En este tutorial, vamos a aprender cómo podemos realizar el procesamiento de imágenes utilizando el lenguaje Python. No nos vamos a restringir a una sola biblioteca o marco; sin embargo, hay una que usaremos con más frecuencia, la biblioteca abrir currículum. Comenzaremos hablando un poco sobre el procesamiento de imágenes y luego pasaremos a ver diferentes aplicaciones/escenarios donde el procesamiento de imágenes puede resultar útil. ¡Vamos a empezar!

¿Qué es el procesamiento de imágenes?

Es importante saber qué es exactamente el procesamiento de imágenes y cuál es su papel en el panorama general antes de sumergirse en sus procedimientos. El procesamiento de imágenes se denomina más comúnmente 'Procesamiento de imágenes digitales' y el dominio en el que se usa con frecuencia es 'Visión por computadora'. No se confunda: vamos a hablar sobre estos dos términos y cómo se conectan. Tanto los algoritmos de procesamiento de imágenes como los algoritmos de visión artificial (CV) toman una imagen como entrada; sin embargo, en el procesamiento de imágenes, la salida también es una imagen, mientras que en la visión artificial la salida puede ser algunas características/información sobre la imagen.

¿Por qué lo necesitamos? {#Por qué lo necesitamos}

Los datos que recopilamos o generamos son en su mayoría datos sin procesar, es decir, no son aptos para ser utilizados en aplicaciones directamente debido a una serie de posibles razones. Por lo tanto, primero debemos analizarlo, realizar el preprocesamiento necesario y luego usarlo.

Por ejemplo, supongamos que estamos tratando de construir un clasificador de gatos. Nuestro programa tomaría una imagen como entrada y luego nos diría si la imagen contiene un gato o no. El primer paso para construir este clasificador sería recopilar cientos de imágenes de gatos. Un problema común es que todas las imágenes que hemos raspado no serían del mismo tamaño/dimensiones, por lo que antes de enviarlas al modelo para el entrenamiento, necesitaríamos cambiar el tamaño/preprocesarlas todas a un tamaño estándar.

Esta es solo una de las muchas razones por las que el procesamiento de imágenes es esencial para cualquier aplicación de visión artificial.

Requisitos previos

Antes de continuar, analicemos lo que necesita saber para seguir este tutorial con facilidad. En primer lugar, debe tener algunos conocimientos básicos de programación en cualquier idioma. En segundo lugar, debe saber qué es el aprendizaje automático y los conceptos básicos de su funcionamiento, ya que en este artículo utilizaremos algunos algoritmos de aprendizaje automático para el procesamiento de imágenes. Como beneficio adicional, sería útil si ha tenido alguna exposición o conocimiento básico de Open CV antes de continuar con este tutorial. Pero esto no es obligatorio.

Una cosa que definitivamente debe saber para seguir este tutorial es cómo se representa exactamente una imagen en la memoria. Cada imagen está representada por un conjunto de píxeles, es decir, una matriz de valores de píxeles. Para una imagen en escala de grises, los valores de píxel van de 0 a 255 y representan la intensidad de ese píxel. Por ejemplo, si tiene una imagen de 20 x 20 dimensiones, estaría representada por una matriz de 20 x 20 (un total de valores de 400 píxeles).

Si se trata de una imagen en color, debe saber que tendría tres canales: rojo, verde y azul (RGB). Por lo tanto, habría tres matrices de este tipo para una sola imagen.

Instalación

Nota: Dado que vamos a utilizar OpenCV a través de Python, es un requisito implícito que ya tenga instalado Python (versión 3) en su estación de trabajo.

ventanas

1
$ pip install opencv-python

Mac OS

1
$ brew install opencv3 --with-contrib --with-python3

####linux

1
$ sudo apt-get install libopencv-dev python-opencv

Para verificar si su instalación fue exitosa o no, ejecute el siguiente comando en un shell de Python o en su símbolo del sistema:

1
import cv2

Algunos conceptos básicos que debe saber {#algunos conceptos básicos que debe saber}

Antes de pasar al uso del procesamiento de imágenes en una aplicación, es importante tener una idea de qué tipo de operaciones se incluyen en esta categoría y cómo realizarlas. Estas operaciones, junto con otras, serían utilizadas más adelante en nuestras aplicaciones. Vamos a por ello.

Para este artículo usaremos la siguiente imagen:

imagen original utilizada para el procesamiento básico de imágenes

Nota: La imagen se ha escalado para mostrarla en este artículo, pero el tamaño original que estamos usando es de aproximadamente 1180x786.

Probablemente haya notado que la imagen está coloreada actualmente, lo que significa que está representada por tres canales de color, es decir, rojo, verde y azul. Convertiremos la imagen a escala de grises, así como dividiremos la imagen en sus canales individuales usando el código a continuación.

Búsqueda de detalles de imágenes

Después de cargar la imagen con la función imread(), podemos recuperar algunas propiedades simples sobre ella, como el número de píxeles y las dimensiones:

1
2
3
4
5
6
7
import cv2

img = cv2.imread('rose.jpg')

print("Image Properties")
print("- Number of Pixels: " + str(img.size))
print("- Shape/Dimensions: " + str(img.shape))

Producción:

1
2
3
Image Properties
- Number of Pixels: 2782440
- Shape/Dimensions: (1180, 786, 3)

Dividir una imagen en canales individuales {#dividir una imagen en canales individuales}

Ahora dividiremos la imagen en sus componentes rojo, verde y azul usando OpenCV y los mostraremos:

1
2
3
4
5
6
7
8
9
from google.colab.patches import cv2_imshow

blue, green, red = cv2.split(img) # Split the image into its channels
img_gs = cv2.imread('rose.jpg', cv2.IMREAD_GRAYSCALE) # Convert image to grayscale

cv2_imshow(red) # Display the red channel in the image
cv2_imshow(blue) # Display the red channel in the image
cv2_imshow(green) # Display the red channel in the image
cv2_imshow(img_gs) # Display the grayscale version of image

Para abreviar, solo mostraremos la imagen en escala de grises.

Imagen en escala de grises:

flower image in greyscale

Umbral de imagen

El concepto de umbralización es bastante simple. Como se discutió anteriormente en la representación de la imagen, los valores de píxel pueden ser cualquier valor entre 0 y 255. Digamos que deseamos convertir una imagen en una imagen binaria, es decir, asignarle a un píxel un valor de 0 o 1. Para hacer esto, podemos realizar la umbralización. Por ejemplo, si el valor del Umbral (T) es 125, a todos los píxeles con valores superiores a 125 se les asignará un valor de 1, y a todos los píxeles con valores menores o iguales se les asignará un valor de 0. Let\ Lo hacemos a través del código para obtener una mejor comprensión.

Imagen utilizada para Umbral:

imagen utilizada para el umbral

1
2
3
4
5
6
7
8
import cv2

# Read image
img = cv2.imread('image.png', 0)

# Perform binary thresholding on the image with T = 125
r, threshold = cv2.threshold(img, 125, 255, cv2.THRESH_BINARY)
cv2_imshow(threshold)

Producción:

salida de umbral de imagen

Como puede ver, en la imagen resultante, se han establecido dos regiones, es decir, la región negra (valor de píxel 0) y la región blanca (valor de píxel 1). Resulta que el umbral que establecimos estaba justo en el medio de la imagen, por lo que los valores de blanco y negro se dividen allí.

Aplicaciones

#1: Eliminar el ruido de una imagen

Ahora que tiene una idea básica de qué es el procesamiento de imágenes y para qué se utiliza, avancemos y conozcamos algunas de sus aplicaciones específicas.

En la mayoría de los casos, los datos sin procesar que recopilamos tienen ruido, es decir, características no deseadas que hacen que la imagen sea difícil de percibir. Aunque estas imágenes se pueden usar directamente para la extracción de características, la precisión del algoritmo sufriría mucho. Esta es la razón por la cual el procesamiento de imágenes se aplica a la imagen antes de pasarla al algoritmo para obtener una mayor precisión.

Hay muchos tipos diferentes de ruido, como el ruido gaussiano, el ruido de sal y pimienta, etc. Podemos eliminar ese ruido de una imagen aplicando un filtro que elimina ese ruido o, al menos, minimiza su efecto. También hay muchas opciones cuando se trata de filtros, cada uno de ellos tiene diferentes puntos fuertes y, por lo tanto, es el mejor para un tipo específico de ruido.

Para entender esto correctamente, vamos a agregar ruido 'sal y pimienta' a la versión en escala de grises de la imagen de la rosa que consideramos anteriormente, y luego intentaremos eliminar ese ruido de nuestra imagen ruidosa usando diferentes filtros y veremos cuál es mejor ajuste para ese tipo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import numpy as np

# Adding salt & pepper noise to an image
def salt_pepper(prob):
      # Extract image dimensions
      row, col = img_gs.shape

      # Declare salt & pepper noise ratio
      s_vs_p = 0.5
      output = np.copy(img_gs)

      # Apply salt noise on each pixel individually
      num_salt = np.ceil(prob * img_gs.size * s_vs_p)
      coords = [np.random.randint(0, i - 1, int(num_salt))
            for i in img_gs.shape]
      output[coords] = 1

      # Apply pepper noise on each pixel individually
      num_pepper = np.ceil(prob * img_gs.size * (1. - s_vs_p))
      coords = [np.random.randint(0, i - 1, int(num_pepper))
            for i in img_gs.shape]
      output[coords] = 0
      cv2_imshow(output)

      return output

# Call salt & pepper function with probability = 0.5
# on the grayscale image of rose
sp_05 = salt_pepper(0.5)

# Store the resultant image as 'sp_05.jpg'
cv2.imwrite('sp_05.jpg', sp_05)

Muy bien, hemos agregado ruido a nuestra imagen de rosa, y así es como se ve ahora:

Imagen ruidosa:

image with noise

Ahora apliquemos diferentes filtros y anotemos nuestras observaciones, es decir, qué tan bien cada filtro reduce el ruido.

Filtro aritmético con núcleo de nitidez
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Create our sharpening kernel, the sum of all values must equal to one for uniformity
kernel_sharpening = np.array([[-1,-1,-1],
                              [-1, 9,-1],
                              [-1,-1,-1]])

# Applying the sharpening kernel to the grayscale image & displaying it.
print("\n\n--- Effects on S&P Noise Image with Probability 0.5 ---\n\n")

# Applying filter on image with salt & pepper noise
sharpened_img = cv2.filter2D(sp_05, -1, kernel_sharpening)
cv2_imshow(sharpened_img)

La imagen resultante, al aplicar un filtro aritmético a la imagen con ruido de sal y pimienta, se muestra a continuación. Al comparar con la imagen original en escala de grises, podemos ver que ilumina demasiado la imagen y no puede resaltar los puntos brillantes de la rosa. Por lo tanto, se puede concluir que el filtro aritmético no elimina el ruido de sal y pimienta.

Salida de filtro aritmético:

imagen sin ruido mediante filtro aritmético

Filtro de punto medio
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from scipy.ndimage import maximum_filter, minimum_filter

def midpoint(img):
    maxf = maximum_filter(img, (3, 3))
    minf = minimum_filter(img, (3, 3))
    midpoint = (maxf + minf) / 2
    cv2_imshow(midpoint)

print("\n\n---Effects on S&P Noise Image with Probability 0.5---\n\n")
midpoint(sp_05)

La imagen resultante, al aplicar el filtro de punto medio en la imagen con ruido de sal y pimienta, se muestra a continuación. Al comparar con la imagen original en escala de grises, podemos ver que, al igual que el método kernel anterior, ilumina demasiado la imagen; sin embargo, es capaz de resaltar los puntos brillantes de la rosa. Por tanto, podemos decir que es una mejor elección que el filtro aritmético, pero aún así no recupera la imagen original por completo.

Salida de filtro de punto medio:

image without noise via midpoint filter

Filtro medio contraarmónico

Nota: Las implementaciones de estos filtros se pueden encontrar fácilmente en línea y cómo funcionan exactamente está fuera del alcance de este tutorial. Examinaremos las aplicaciones desde un nivel abstracto/superior.

1
2
3
4
5
6
7
8
9
def contraharmonic_mean(img, size, Q):
    num = np.power(img, Q + 1)
    denom = np.power(img, Q)
    kernel = np.full(size, 1.0)
    result = cv2.filter2D(num, -1, kernel) / cv2.filter2D(denom, -1, kernel)
    return result

print("\n\n--- Effects on S&P Noise Image with Probability 0.5 ---\n\n")
cv2_imshow(contraharmonic_mean(sp_05, (3,3), 0.5))

La imagen resultante, al aplicar el filtro Media contraarmónica sobre la imagen con ruido de sal y pimienta, se muestra a continuación. Tras la comparación con la imagen original en escala de grises, podemos ver que ha reproducido prácticamente la misma imagen que la original. Su nivel de intensidad/brillo es el mismo y también resalta los puntos brillantes de la rosa. Por lo tanto, podemos concluir que el filtro medio contraarmónico es muy efectivo para lidiar con el ruido de sal y pimienta.

Salida del filtro medio contraarmónico:

image without noise via contraharmonic filter

Ahora que hemos encontrado el mejor filtro para recuperar la imagen original de una ruidosa, podemos pasar a nuestra siguiente aplicación.

#2: Detección de bordes usando Canny Edge Detector

La imagen de rosa que hemos estado usando hasta ahora tiene un fondo constante, es decir, negro, por lo tanto, usaremos una imagen diferente para esta aplicación para mostrar mejor las capacidades del algoritmo. La razón es que si el fondo es constante, hace que la tarea de detección de bordes sea bastante simple, y no queremos eso.

Hablamos sobre un clasificador de gatos anteriormente en este tutorial, llevemos ese ejemplo hacia adelante y veamos cómo el procesamiento de imágenes juega un papel integral en eso.

En un algoritmo de clasificación, primero se escanea la imagen en busca de 'objetos', es decir, cuando ingresa una imagen, el algoritmo encuentra todos los objetos en esa imagen y luego los compara con las características del objeto que está tratando de encontrar. En el caso de un clasificador de gatos, compararía todos los objetos encontrados en una imagen con las características de una imagen de gato, y si se encuentra una coincidencia, nos dice que la imagen de entrada contiene un gato.

Dado que estamos usando el clasificador de gatos como ejemplo, es justo que usemos una imagen de gato en el futuro. A continuación se muestra la imagen que usaremos:

Imagen utilizada para la detección de bordes:

imagen utilizada para la detección de bordes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import cv2
import numpy as np
from matplotlib import pyplot as plt

# Declaring the output graph's size
plt.figure(figsize=(16, 16))

# Convert image to grayscale
img_gs = cv2.imread('cat.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imwrite('gs.jpg', img_gs)

# Apply canny edge detector algorithm on the image to find edges
edges = cv2.Canny(img_gs, 100,200)

# Plot the original image against the edges
plt.subplot(121), plt.imshow(img_gs)
plt.title('Original Gray Scale Image')
plt.subplot(122), plt.imshow(edges)
plt.title('Edge Image')

# Display the two images
plt.show()

Salida de detección de bordes:

edge detection output

Como puede ver, la parte de la imagen que contiene un objeto, que en este caso es un gato, se ha punteado/separado mediante la detección de bordes. Ahora debe preguntarse qué es el Detector de borde astuto y cómo hizo que esto sucediera; así que hablemos de eso ahora.

Para entender lo anterior, hay tres pasos clave que necesitan ser discutidos. Primero, realiza la reducción de ruido en la imagen de una manera similar a la que discutimos anteriormente. En segundo lugar, utiliza la primera derivada de cada píxel para encontrar los bordes. La lógica detrás de esto es que en el punto donde existe un borde, hay un cambio de intensidad abrupto, lo que provoca un pico en el valor de la primera derivada, por lo que ese píxel se convierte en un 'píxel de borde'.

Al final, realiza un umbral de histéresis; Dijimos anteriormente que hay un pico en el valor de la primera derivada en un borde, pero no dijimos “qué tan alto” debe ser el pico para que se clasifique como un borde. ¡Esto se llama umbral! Anteriormente en este tutorial discutimos qué es el umbral simple. El umbral de histéresis es una mejora en eso, utiliza dos valores de umbral en lugar de uno. La razón detrás de esto es que, si el valor del umbral es demasiado alto, es posible que pasemos por alto algunos bordes reales (verdaderos negativos) y si el valor es demasiado bajo, obtendríamos muchos puntos clasificados como bordes que en realidad no son bordes (falsos positivos). ). Un valor de umbral se establece en alto y el otro en bajo. Todos los puntos que están por encima del 'valor de umbral alto' se identifican como bordes, luego se evalúan todos los puntos que están por encima del valor de umbral bajo pero por debajo del valor de umbral alto; los puntos que están cerca o son vecinos de puntos que han sido identificados como bordes, también se identifican como bordes y el resto se descarta.

Estos son los conceptos/métodos subyacentes que utiliza el algoritmo Canny Edge Detector para identificar los bordes de una imagen.

Conclusión

En este artículo, aprendimos cómo instalar OpenCV, la biblioteca más popular para el procesamiento de imágenes en Python, en diferentes plataformas como Windows, MacOS y Linux, así como también cómo verificar que la instalación fue exitosa.

Continuamos discutiendo qué es el procesamiento de imágenes y sus usos en el dominio de visión por computadora del aprendizaje automático. Hablamos sobre algunos tipos comunes de ruido y cómo podemos eliminarlo de nuestras imágenes usando diferentes filtros, antes de usar las imágenes en nuestras aplicaciones.

Además, aprendimos cómo el procesamiento de imágenes juega un papel integral en aplicaciones de alto nivel como la detección o clasificación de objetos. Tenga en cuenta que este artículo fue solo la punta del iceberg, y Digital Image Processing tiene mucho más en la tienda que no se puede cubrir en un solo tutorial. Leer esto debería permitirle profundizar más y aprender sobre otros conceptos avanzados relacionados con el procesamiento de imágenes. ¡Buena suerte!