Traitement d’images (partie 3: Seuillage d’image)

Partager cet article

Dans cet article (qui est le 3ème épisode de la série sur le traitement d’images) nous allons voir comment utiliser et mettre en pratique les histogrammes d’images que nous avons abordé dans l’article 2 pour effectuer une retouche de base: le seuillage. Nous allons donc analyser et à partir des histogrammes changer les valeurs d’intensité des différents canaux de couleurs de nos images. Cette technique s’appelle donc le seuillage, et est très souvent utilisée comme prétraitement d’image afin de pouvoir faire d’autres opérations par la suite (comme de la détection de forme, etc.). Nous utiliserons, comme dans les articles précédents Python et scikit-image pour manipuler les images

Retouche d’une image en niveau de gris

Premièrement importons les librairies Python et ouvrons l’image :

import matplotlib.pyplot as plt
from skimage.io import imread, imshow
from skimage import exposure
import matplotlib.pyplot as plt
from skimage.color import rgb2gray
import numpy as np
from skimage.filters import threshold_mean, threshold_otsu
import pandas as pd

image1_Gray = imread('railway.jpg', as_gray=True)

Nous ouvrons l’image (qui est originalement en couleurs) en mode Niveau de gris (dernière ligne).

Regardons son histogramme (Cf. article 2) :

def histGrayScale(img, _xlim=255, _ylim=2400):
    _, axes = plt.subplots(ncols=2, figsize=(12, 3))
    ax = axes.ravel()
    ax[0].imshow(img, cmap=plt.get_cmap('gray'))
    ax[0].set_title('Image')
    hist = exposure.histogram(img)
    ax[1].plot(hist[0])
    # to provide a better display we just change the plot display
    ax[1].set_xlim([0, _xlim])
    ax[1].set_ylim([0, _ylim])
histGrayScale(image1_Gray)

Dans cette image en niveau de gris on voit que la majorité des pixels ont une intensité de 50 à 120.

Forçons tous les pixels au dessus de 120 à 255 pour voir ce que cela donne. En Python c’est une opération (Numpy) très simple :

im = np.where(image1_Gray>120/256, 1, image1_Gray)
histGrayScale(im, 254, 2400)

Le résultat est assez visible, l’image se rapproche de plus en plus d’un pur noir et blanc. Remarquez aussi l’histogramme qui est naturellement coupé à partir de 120 (la partie de droite n’existe plus du tout).

On peut aussi retirer la partie de gauche de l’histogramme (pixels inférieurs à 50) :

im = np.where(im<50/256, 0, im)
histGrayScale(im, 254, 2400)

Voyez le résultat en détail :

Récupérer les statistiques globales de l’image

On a vu l’importance des données présentées par nos histogrammes pour effectuer des seuillages. Les histogrammes apportent une compréhension globale de l’image mais comment déterminer nos seuils ? (comme ceux de 120 et 50 que nous avons utilisés précédemment). Quelques données statistiques comme la moyenne, la médiane et même pourquoi pas les quartiles pourraient être utiles. Voici une petite fonction Python qui nous affiche ces éléments :

def RGBStats(image): 
    colors = [] 
    for i in range(0, 3): 
        max_color =np.max(image[:,:,i]) 
        min_color =np.min(image[:,:,i]) 
        mean_color = np.mean(image[:,:,i]) 
        median_color = np.median(image[:,:,i]) 
        row = (min_color, max_color, mean_color, median_color)
        colors.append(row)
    return pd.DataFrame(colors,  
                        index = ['Red', ' Green', 'Blue'], 
                        columns = ['Min', 'Max', 'Mean', 'Median'])

RGBStats(image1)

Seuillage binaire sur la moyenne

Très souvent la technique de seuillage est utilisée pour créer une image Noir et Blanc: c’est ce que l’on appelle un seuillage binaire. Dans ces cas là il parait logique de se baser sur la moyenne des pixels. Tout ce qui est en dessous de la moyenne sera mis à 0 et tout ce qui sera au dessus à 1. La fonction skimage threshold_mean() le fait tout seul pour vous :

def thresholdMeanDisplay(image):
    thresh = threshold_mean(image)
    binary = image > thresh
    fig, axes = plt.subplots(ncols=2, figsize=(8, 3))
    ax = axes.ravel()
    ax[0].imshow(image, cmap=plt.cm.gray)
    ax[0].set_title('Original')
    ax[1].imshow(binary, cmap=plt.cm.gray)
    ax[1].set_title('Mean thresolded')
    
thresholdMeanDisplay(image1_Gray)

Seuillage Otsu

Le seuillage Otsu est une technique calcul du seuil basée sur la forme de l’histogramme de l’image. Nous utiliserons la fonction skimage toute faite threshold_otsu().

def thresholdOtsuDisplay(image):
    thresh = threshold_otsu(image)
    binary = image > thresh
    fig, axes = plt.subplots(ncols=2, figsize=(8, 3))
    ax = axes.ravel()
    ax[0].imshow(image, cmap=plt.cm.gray)
    ax[0].set_title('Original')
    ax[1].imshow(binary, cmap=plt.cm.gray)
    ax[1].set_title('Otsu thresolded')
    
thresholdOtsuDisplay(image1_Gray)

Seuillage des images en couleur

Pour les images en couleur, le principe de seuillage est le même mais à la différence près que l’on va faire cette opération par canaux (RGB).

Prennons un image en couleur, et regardons son histogramme :

image1 = imread('tulip.jpg')
def histColor(img):
    _, axes = plt.subplots(ncols=2, figsize=(12, 3))
    axes[0].imshow(img)
    axes[0].set_title('Image')
    axes[1].set_title('Histogram')
    axes[1].plot(exposure.histogram(img[...,0])[0], color='red')
    axes[1].plot(exposure.histogram(img[...,1])[0], color='green')
    axes[1].plot(exposure.histogram(img[...,2])[0], color='blue')
    axes[1].set_xlim([1, 254])
    axes[1].set_ylim([0, 3500])
histColor(image1)

Et si on affichait les 3 canaux séparément :

rgb = ['Reds','Greens','Blues'] 
_, axes = plt.subplots(1, 3, figsize=(15,5), sharey = True) 
for i in range(3): 
   axes[i].imshow(image1[:,:,i], cmap = rgb[i]) 
   axes[i].set_title(rgb_list[i], fontsize = 15)

Vous remarquerez la « vision en négatif » … regardez de plus près les 4 tulipes en bas au centres … elles sont blanches et apparaissent donc logiquement foncée sur les 3 canaux (le blanc étant la somme de toutes les couleurs).

Effectuons un seuillage sur le canal vert seulement en retirant les verts d’intensité inférieure à 150 :

thresold_G = 150
image1_modified = image1.copy()
image1_modified[:,:,1] = np.where(image1[:,:,1]>thresold_G, 
                                  image1[:,:,1], 
                                  0)
histColor(image1_modified)

Voilà le résultat de l’image à qui on a supprimé une bonne partie de son canal vert les verts d’intensité faible en fait) 🙂

Partager cet article