Introducción al procesamiento de imágenes con Python

'Intro al procesamiento de imágenes con Python'

Episodio 2: Mejoras de Imagen, Parte 3: Manipulación de Histograma

Técnicas de Manipulación de Histograma. Foto por el Autor

¡Bienvenidos de nuevo a la tercera parte del segundo episodio de nuestra serie de procesamiento de imágenes! En las partes anteriores de la serie, discutimos las técnicas de Transformada de Fourier y Equilibrio de Blancos, y ahora exploraremos otra técnica emocionante llamada Manipulación de Histograma.

Si eres como yo, es posible que te preguntes cómo se puede manipular un histograma. Quiero decir, ¿no es simplemente un gráfico que muestra la distribución de los valores de píxeles en una imagen? Bueno, resulta que al manipular el histograma, podemos ajustar el contraste y el brillo de una imagen, lo que puede mejorar en gran medida su apariencia visual.

Entonces, sumerjámonos en el mundo de la manipulación de histogramas y descubramos cómo podemos mejorar el contraste y el brillo de nuestras imágenes utilizando varias técnicas de Manipulación de Histograma. Estas técnicas se pueden utilizar para mejorar la visibilidad de objetos en imágenes con poca luz, mejorar los detalles de una imagen y corregir imágenes sobre o subexpuestas.

Comencemos importando las bibliotecas relevantes de Python:

Paso 1: Importar bibliotecas, luego cargar y mostrar la imagen

import numpy as npimport matplotlib.pyplot as pltfrom skimage.color import rgb2grayfrom skimage.exposure import histogram, cumulative_distributionfrom skimage import filtersfrom skimage.color import rgb2hsv, rgb2gray, rgb2yuvfrom skimage import color, exposure, transformfrom skimage.exposure import histogram, cumulative_distribution

# Cargar la imagen y quitar el canal alfa u opacidad (transparencia) dark_image = imread('plasma_ball.png')[:,:,:3]# Visualizar la imagenplt.figure(figsize=(10, 10))plt.title('Imagen Original: Plasma Ball')plt.imshow(dark_image)plt.show()
Salida #1: Foto por el Autor

Paso 2: Verificar las estadísticas del canal y trazar el histograma de la imagen

def calc_color_overcast(image):    # Calcular la sobreexposición de color para cada canal    red_channel = image[:, :, 0]    green_channel = image[:, :, 1]    blue_channel = image[:, :, 2]    # Crear un dataframe para almacenar los resultados    channel_stats = pd.DataFrame(columns=['Media', 'Desviación Estándar', 'Mínimo', 'Mediana',                                           'P_80', 'P_90', 'P_99', 'Máximo'])    # Calcular y almacenar las estadísticas para cada canal de color    for channel, name in zip([red_channel, green_channel, blue_channel],                              ['Rojo', 'Verde', 'Azul']):        media = np.mean(channel)        desviacion_estandar = np.std(channel)        minimo = np.min(channel)        mediana = np.median(channel)        p_80 = np.percentile(channel, 80)        p_90 = np.percentile(channel, 90)        p_99 = np.percentile(channel, 99)        maximo = np.max(channel)        channel_stats.loc[name] = [media, desviacion_estandar, minimo, mediana, p_80, p_90, p_99, maximo]    return channel_stats

# Esta es la misma función del episodio anterior-Parte 2 (¡Échale un vistazo!)calc_color_overcast(dark_image)
Salida #2: DataFrame de Pandas de las Estadísticas del Canal de la Imagen. Foto por el Autor.
# Gráfico del histogramadark_image_intensity = img_as_ubyte(rgb2gray(dark_image))freq, bins = histogram(dark_image_intensity)plt.step(bins, freq*1.0/freq.sum())plt.xlabel('valor de intensidad')plt.ylabel('fracción de píxeles');
Resultado n.º 3: Gráfico de Histograma de los Valores de Intensidad de la Imagen. Foto del Autor.

No parece haber una gran nubosidad, pero la intensidad media de los píxeles parece extremadamente baja, confirmando la imagen oscura y la visualización subexpuesta de la imagen. El histograma muestra que la mayoría de los píxeles tienen valores de baja intensidad, lo cual tiene sentido ya que valores de baja intensidad en los píxeles significa que la mayoría de los píxeles son muy oscuros o negros en una imagen. Podemos aplicar diversas técnicas de manipulación del histograma a la imagen para mejorar su contraste.

Paso 3: Explorar diversas técnicas de Manipulación del Histograma

Antes de sumergirnos en las diversas técnicas de manipulación del histograma, vamos a entender la técnica de manipulación de histograma comúnmente utilizada llamada ecualización del histograma.

La ecualización del histograma es una técnica que redistribuye las intensidades de los píxeles en una imagen para hacer el histograma más uniforme. Una distribución de intensidades de píxeles no uniforme puede resultar en una imagen con bajo contraste y detalle, lo que dificulta distinguir objetos o características dentro de la imagen. Al hacer la distribución de intensidades de los píxeles más uniforme, se mejora el contraste de la imagen, facilitando la percepción de detalles y características.

Una forma de lograr una distribución uniforme de intensidades de píxeles es hacer que la función de distribución acumulativa (CDF) de la imagen sea lineal. Esto se debe a que una CDF lineal implica que cada valor de intensidad de píxel tiene la misma probabilidad de ocurrir en la imagen. Por otro lado, una CDF no lineal implica que ciertos valores de intensidad de píxeles ocurren con más frecuencia que otros, lo que resulta en una distribución de intensidad de píxeles no uniforme. Al hacer que la CDF sea lineal, podemos hacer que la distribución de intensidades de los píxeles sea más uniforme y mejorar el contraste de la imagen.

def plot_cdf(imagen):    """    Grafica la función de distribución acumulativa de una imagen.        Parámetros:    imagen (ndarray): Imagen de entrada.    """        # Convertir la imagen a escala de grises si es necesario    if len(imagen.shape) == 3:        imagen = rgb2gray(imagen[:,:,:3])        # Calcular la función de distribución acumulativa    intensidad = np.round(imagen * 255).astype(np.uint8)    frecuencia, bins = distribucion_acumulativa(intensidad)        # Graficar las CDFs actual y objetivo    bins_objetivo = np.arange(256)    frecuencia_objetivo = np.linspace(0, 1, len(bins_objetivo))    plt.step(bins, frecuencia, c='b', label='CDF actual')    plt.plot(bins_objetivo, frecuencia_objetivo, c='r', label='CDF objetivo')        # Graficar un ejemplo de búsqueda    intensidad_ejemplo = 50    objetivo_ejemplo = np.interp(frecuencia[intensidad_ejemplo], frecuencia_objetivo, bins_objetivo)    plt.plot([intensidad_ejemplo, intensidad_ejemplo, bins_objetivo[-11], bins_objetivo[-11]],             [0, frecuencia[intensidad_ejemplo], frecuencia[intensidad_ejemplo], 0],              'k--',              label=f'Búsqueda de ejemplo ({intensidad_ejemplo} -> {objetivo_ejemplo:.0f})')        # Personalizar la gráfica    plt.legend()    plt.xlim(0, 255)    plt.ylim(0, 1)    plt.xlabel('Valores de Intensidad')    plt.ylabel('Fracción Acumulativa de Píxeles')    plt.title('Función de Distribución Acumulativa')        return frecuencia, bins, frecuencia_objetivo, bins_objetivo

imagen_oscura = imread('plasma_ball.png')frecuencia, bins, frecuencia_objetivo, bins_objetivo = plot_cdf(imagen_oscura);
Resultado n.º 4: Gráfico de CDF

El código calcula la función de distribución acumulativa (CDF) de la imagen oscura y luego define un CDF objetivo basado en una distribución lineal. Luego, grafica el CDF actual de la imagen oscura en azul y el CDF objetivo (lineal) en rojo. También se muestra una búsqueda de ejemplo de un valor de intensidad, que muestra que el CDF actual es 50 en el ejemplo y queremos que sea 230.

# Conversión de ejemplo de valores de intensidad de valor actual de 50 a valor objetivo de 230imagen_oscura_230 = imagen_oscura_intensidad.copy()imagen_oscura_230[imagen_oscura_230==50] = 230plt.figure(figsize=(10,10))plt.imshow(imagen_oscura_230,cmap='gray');
Salida #5: Resultado de una imagen en escala de grises de muestra basada en la conversión del valor de intensidad real (50) al valor objetivo (230). Foto por Autor.

Después de obtener la función de distribución acumulativa (CDF) objetivo, el siguiente paso es calcular los valores de intensidad que se utilizarán para reemplazar las intensidades de píxeles originales. Esto se hace utilizando la interpolación para crear una tabla de búsqueda.

# Mostrar el resultado después de reemplazar todos los valores reales por los valores objetivosnew_vals = np.interp(freq, target_freq, target_bins)dark_image_eq = img_as_ubyte(new_vals[img_as_ubyte(rgb2gray(dark_image[:,:,:3]))].astype(int))plt.figure(figsize=(10,10))plt.imshow(dark_image_eq, cmap='gray');
Salida #6: Resultado de una imagen en escala de grises de muestra basada en la conversión de todos los valores de intensidad reales a valores objetivos. Foto por Autor.

La función np.interp() calcula los valores de intensidad que se utilizarán para reemplazar las intensidades de píxeles originales mediante la interpolación entre las CDFs actual y objetivo. Los valores de intensidad resultantes se utilizan luego para reemplazar las intensidades de píxeles originales utilizando la indexación de NumPy. Finalmente, la imagen ecualizada resultante se muestra utilizando imshow() en cmap= gray .

Ahora que he mostrado el tipo más básico de manipulación de histograma, probemos diferentes tipos de CDFs y técnicas y veamos por nosotros mismos qué técnica es adecuada para una imagen dada:

def ajuste_personalizado_rgb(imagen, freq_objetivo):    target_bins = np.arange(256)    freq_bins = [distribucion_acumulativa(imagen[:, :, i]) for i in range(3)]    canales_ajustados = []    # Rellenar frecuencias con la frecuencia mínima    freqs_rellenadas = []    for i in range(len(freq_bins)):        if len(freq_bins[i][0]) < 256:            frecuencias = list(freq_bins[i][0])            relleno_min = [min(frecuencias)] * (256 - len(frecuencias))            frecuencias = relleno_min + frecuencias        else:            frecuencias = freq_bins[i][0]        freqs_rellenadas.append(np.array(frecuencias))    for n in range(3):        interpolacion = np.interp(freqs_rellenadas[n], freq_objetivo, target_bins)        canal_ajustado = img_as_ubyte(interpolacion[imagen[:, :, n]].astype(int))        canales_ajustados.append([canal_ajustado])    imagen_ajustada = np.dstack((canales_ajustados[0][0], canales_ajustados[1][0], canales_ajustados[2][0]))    return imagen_ajustada

# Línea target_bins = np.arange(256)# Sigmoiddef sigmoid_cdf(x, a=1): return (1 + np.tanh(a * x)) / 2# Exponentialdef exponential_cdf(x, alpha=1): return 1 - np.exp(-alpha * x)# Powerdef power_law_cdf(x, alpha=1): return x ** alpha# Otras técnicas:def ecualizacion_histograma_adaptativa(imagen, limite_recorte=0.03, tamano_bloque=(8, 8)): clahe = exposure.equalize_adapthist( imagen, clip_limit=limite_recorte, nbins=256, kernel_size=(tamano_bloque[0], tamano_bloque[1])) return clahedef correccion_gamma(imagen, gamma=1.0): imagen_corregida = exposure.adjust_gamma(imagen, gamma) return imagen_corregidadef estiramiento_contraste_percentil(imagen, percentil_inferior=5, percentil_superior=95): rango = tuple(np.percentile(imagen, (percentil_inferior, percentil_superior))) imagen_estirada = exposure.rescale_intensity(imagen, rango) return imagen_estiradadef enmascaramiento_no_nitidez(imagen, radio=5, factor=1.0): imagen_desenfocada = filters.gaussian(imagen, sigma=radio, multichannel=True) imagen_afilada = (imagen + (imagen - imagen_desenfocada) * factor).clip(0, 1) return imagen_afiladadef ecualizacion_histograma_rgb(imagen): imagen_ecualizada = exposure.equalize_hist(imagen) return imagen_ecualizadadef ecualizacion_histograma_hsv(imagen): imagen_hsv = color.rgb2hsv(imagen[:,:,:3]) imagen_hsv[:, :, 2] = exposure.equalize_hist(imagen_hsv[:, :, 2]) hsv_ajustado = color.hsv2rgb(imagen_hsv) return hsv_ajustadodef ecualizacion_histograma_yuv(imagen): imagen_yuv = color.rgb2yuv(imagen[:,:,:3]) imagen_yuv[:, :, 0] = exposure.equalize_hist(imagen_yuv[:, :, 0]) yuv_ajustado = color.yuv2rgb(imagen_yuv) return yuv_ajustado

# Guardar cada técnica en una variablelinear = ajuste_personalizado_rgb(imagen_oscura, np.linspace(0, 1, len(target_bins)))sigmoid = ajuste_personalizado_rgb(imagen_oscura, sigmoid_cdf((target_bins - 128) / 64, a=1))exponential = ajuste_personalizado_rgb(imagen_oscura, exponential_cdf(target_bins / 255, alpha=3))power = ajuste_personalizado_rgb(imagen_oscura, power_law_cdf(target_bins / 255, alpha=2))clahe_imagen = ecualizacion_histograma_adaptativa( imagen_oscura, limite_recorte=0.09, tamano_bloque=(50, 50))imagen_corregida_gamma = correccion_gamma(imagen_oscura, gamma=0.4)imagen_afilada = enmascaramiento_no_nitidez(imagen_oscura, radio=10, factor=-0.98)imagen_contraste_estirado = estiramiento_contraste_percentil(imagen_oscura, 0, 70)rgb_ecualizado = ecualizacion_histograma_rgb(imagen_oscura)hsv_ecualizado = ecualizacion_histograma_hsv(imagen_oscura)yuv_ecualizado = ecualizacion_histograma_yuv(imagen_oscura)

# Plotfig, axes = plt.subplots(3, 4, figsize=(20, 20))# Imagen originalaxes[0, 0].imshow(imagen_oscura)axes[0, 0].set_title('Imagen Original', fontsize=20)# Ecualización del Histograma: Ajuste RGBaxes[0, 1].imshow(rgb_ecualizado)axes[0, 1].set_title('Ajuste RGB', fontsize=20)# Ajuste HSVaxes[0, 2].imshow(hsv_ecualizado)axes[0, 2].set_title('Ajuste HSV', fontsize=20)# Ajuste YUVaxes[0, 3].imshow(yuv_ecualizado)axes[0, 3].set_title('Ajuste YUV', fontsize=20)# CDF Linealaxes[1, 0].imshow(linear)axes[1, 0].set_title('Lineal', fontsize=20)# CDF Sigmoidalaxes[1, 1].imshow(sigmoid)axes[1, 1].set_title('Sigmoidal', fontsize=20)# CDF Exponencialaxes[1, 2].imshow(exponential)axes[1, 2].set_title('Exponencial', fontsize=20)# CDF Potenciaaxes[1, 3].imshow(power)axes[1, 3].set_title('Potencia', fontsize=20)# Estiramiento de Contrasteaxes[2, 0].imshow(imagen_contraste_estirado)axes[2, 0].set_title('Estiramiento de Contraste', fontsize=20)# Ecualización del Histograma Adaptativa (CLAHE)axes[2, 1].imshow(clahe_imagen)axes[2, 1].set_title('Ecualización del Histograma Adaptativa', fontsize=20)# Corrección Gammaaxes[2, 2].imshow(imagen_corregida_gamma)axes[2, 2].set_title('Corrección Gamma', fontsize=20)# Enmascaramiento No Nitidezaxes[

Output #7: Gráfico de las imágenes corregidas utilizando diversas técnicas de manipulación de histogramas. Foto del autor.

Existen muchas formas/técnicas para corregir una imagen en RGB, pero la mayoría de ellas requieren ajuste manual de los parámetros. La salida #7 muestra un gráfico de las imágenes corregidas generadas utilizando varias técnicas de manipulación de histogramas.

HSV Ajustado, Exponencial, Estiramiento de Contraste y Enmascaramiento Desenfocado parecen ser satisfactorios. Tenga en cuenta que los resultados variarán en función de la imagen original utilizada. Dependiendo de la imagen específica, puede experimentar con diferentes valores de parámetros para lograr la calidad de imagen deseada.

Las técnicas de manipulación de histogramas pueden mejorar en gran medida el contraste y la apariencia general de una imagen. Sin embargo, es importante utilizarlas con cuidado, ya que también pueden introducir artefactos y dar lugar a una apariencia poco natural si se utilizan en exceso, como se puede observar en algunas de las técnicas utilizadas en la salida #7 (por ejemplo, Ecualización Adaptativa de Histograma con un fondo granulado y bordes sobre enfatizados).

En contraste con la imagen oscura utilizada anteriormente, también intenté ejecutar mis códigos, con los mismos valores de parámetros, en una imagen brillante. Observemos qué sucedió aquí:

Output #8: Foto original de Johen Redman en Unsplash, otras imágenes procesadas realizadas por el autor.

Como habrás notado, la mayoría de las técnicas que funcionaron bien para la imagen oscura no funcionaron bien para la imagen brillante. Técnicas como HSV Ajustado, Exponencial y Enmascaramiento Desenfocado empeoraron y agregaron artefactos o ruido a la imagen. Esto podría deberse al hecho de que estas técnicas pueden mejorar o amplificar el brillo existente en la imagen, lo que resulta en sobreexposición o la adición de artefactos o ruido.

Sin embargo, es bueno saber que el Estiramiento de Contraste, a pesar de hacer que algunas partes sean más brillantes que la imagen original como se esperaba, ya que el estiramiento de contraste expande literalmente el rango de valores de píxeles en una imagen para aumentar el contraste general, proporciona una solución más flexible que se puede utilizar tanto para imágenes brillantes como oscuras.

Conclusión

En este episodio, nos adentramos más en el mundo del procesamiento de imágenes, explorando diversas técnicas de mejora de imágenes. Cubrimos la Transformada de Fourier (Parte 1), Algoritmos de Equilibrio de Blancos (Parte 2) y técnicas de Manipulación de Histogramas (Parte 3, esta parte), junto con el código Python correspondiente utilizando la biblioteca skimage.

En última instancia, la elección de las técnicas de mejora de imágenes más adecuadas depende de la imagen específica y de los requisitos de calidad de salida. Como buena práctica, experimenta con múltiples técnicas de mejora de imágenes y ajusta diferentes valores de parámetros para lograr la calidad de imagen deseada. Espero que esta exploración te haya ayudado a comprender mejor el impacto de diversas técnicas de mejora de imágenes.

A medida que continuamos este emocionante viaje en el procesamiento de imágenes, aún hay mucho más por aprender y explorar. ¡Estén atentos para la próxima entrega de mi serie de Introducción al Procesamiento de Imágenes con Python, donde discutiré técnicas y aplicaciones aún más avanzadas!

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Inteligencia Artificial

Desvelando GPTBot La audaz movida de OpenAI para rastrear la web

En un torbellino de innovación digital, OpenAI ha dado un golpe sorprendente al lanzar GPTBot, un rastreador web dise...

Inteligencia Artificial

Amplios horizontes La presentación de NVIDIA señala el camino hacia nuevos avances en Inteligencia Artificial

Los avances dramáticos en el rendimiento del hardware han dado lugar a la IA generativa y a una rica variedad de idea...

Aprendizaje Automático

Conoce a FastSAM La solución revolucionaria en tiempo real que logra una segmentación de alto rendimiento con una carga computacional mínima.

El Modelo Segment Anything (SAM) es una propuesta más reciente en el campo. Es un concepto fundamental en la visión q...

Inteligencia Artificial

¿Cómo sobrevivir en el mundo de la IA? ¿Está en riesgo tu trabajo?

¿Está en riesgo tu trabajo? Es una pregunta que atormenta a muchos trabajadores, y no estoy hablando en el contexto d...