
Détection de fonctionnalités, partie 1 : dérivés d’image, dégradés et opérateur Sobel
La vision par ordinateur est un vaste domaine d’analyse d’images et de vidéos. Alors que de nombreuses personnes ont tendance à penser principalement aux modèles d’apprentissage automatique lorsqu’elles entendent la vision par ordinateur, en réalité, il existe bien plus d’algorithmes existants qui, dans certains cas, sont plus performants que l’IA !
En vision par ordinateur, le domaine de détection de fonctionnalités implique d’identifier des régions d’intérêt distinctes dans une image. Ces résultats peuvent ensuite être utilisés pour créer descripteurs de fonctionnalités — des vecteurs numériques représentant les régions d’images locales. Après cela, les descripteurs de caractéristiques de plusieurs photos de la même scène peuvent être combinés pour effectuer une correspondance d’images ou même reconstruire une scène.
Dans cet article, nous ferons une analogie avec le calcul pour introduire dérivés d’imageset dégradés . Il nous faudra comprendre la logique derrière le noyau convolutif et le Opérateur Sobelen particulier — un filtre de vision par ordinateur utilisé pour détecter les contours de l’image.
Intensité de l’image
est l’une des principales caractéristiques d’une image. Chaque pixel de l’image comporte trois composantes : R (rouge), V (vert) et B (bleu), prenant des valeurs comprises entre 0 et 255. Plus la valeur est élevée, plus le pixel est lumineux. L’intensité d’un pixel n’est qu’une moyenne pondérée de ses composantes R, V et B.
En fait, il existe plusieurs normes définissant différents poids. Puisque nous allons nous concentrer sur OpenCV, nous utiliserons leur formule, donnée ci-dessous :

image = cv2.imread('image.png')
B, G, R = cv2.split(image)
grayscale_image = 0.299 * R + 0.587 * G + 0.114 * B
grayscale_image = np.clip(grayscale_image, 0, 255).astype('uint8')
intensity = grayscale_image.mean()
print(f"Image intensity: {intensity:2f}")
Images en niveaux de gris
Les images peuvent être représentées à l’aide de différents canaux de couleurs. Si les canaux RVB représentent une image originale, l’application de la formule d’intensité ci-dessus la transformera au format niveaux de gris, composé d’un seul canal.
Puisque la somme des poids dans la formule est égale à 1, l’image en niveaux de gris contiendra des valeurs d’intensité comprises entre 0 et 255, tout comme les canaux RVB.

Dans OpenCV, les canaux RVB peuvent être convertis au format niveaux de gris à l’aide de la fonction cv2.cvtColor(), qui est un moyen plus simple que la méthode que nous venons de voir ci-dessus.
image = cv2.imread('image.png')
grayscale_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
intensity = grayscale_image.mean()
print(f"Image intensity: {intensity:2f}")
Au lieu de la palette RVB standard, OpenCV utilise la palette BGR. Ils sont tous les deux identiques sauf que les éléments R et B sont simplement échangés. Par souci de simplicité, dans cet article et dans les suivants de cette série, nous allons utiliser les termes RVB et BGR de manière interchangeable.
Si nous calculons l’intensité de l’image en utilisant les deux méthodes dans OpenCV, nous pouvons obtenir des résultats légèrement différents. C’est tout à fait normal puisque, lors de l’utilisation de la fonction cv2.cvtColor, OpenCV arrondit les pixels transformés aux entiers les plus proches. Le calcul de la valeur moyenne entraînera une petite différence.
Dérivé d’image
Les dérivés d’image sont utilisés pour mesurer la vitesse à laquelle l’intensité des pixels change à travers l’image. Les images peuvent être considérées comme une fonction de deux arguments, I(x, y), où x et y spécifient la position du pixel et I représente l’intensité de ce pixel.
On pourrait écrire formellement :

Mais étant donné que les images existent dans l’espace discret, leurs dérivées sont généralement approchées par des noyaux convolutifs :
- Pour l’axe X horizontal : [-1, 0, 1]
- Pour l’axe Y vertical : [-1, 0, 1]ᵀ
En d’autres termes, on peut réécrire les équations ci-dessus sous la forme suivante :

Pour mieux comprendre la logique derrière les noyaux, référons-nous à l’exemple ci-dessous.
Exemple
Supposons que nous ayons une matrice composée de 5 × 5 pixels représentant une zone d’image en niveaux de gris. Les éléments de cette matrice montrent l’intensité des pixels.

Pour calculer la dérivée de l’image, nous pouvons utiliser des noyaux convolutifs. L’idée est simple : en prenant un pixel dans l’image et plusieurs pixels dans son voisinage, on trouve la somme d’une multiplication élément par élément avec un noyau donné qui représente une matrice (ou vecteur) fixe.
Dans notre cas, nous utiliserons un vecteur à trois éléments [-1, 0, 1]. A partir de l’exemple ci-dessus, prenons par exemple un pixel en position (1, 1) dont la valeur est -3.
Puisque la taille du noyau (en jaune) est de 3×1, nous aurons besoin des éléments gauche et droit de -3 pour correspondre à la taille, donc par conséquent, nous prenons le vecteur [4, -3, 2]. Ensuite, en trouvant la somme du produit élément par élément, nous obtenons la valeur de -2 :

La valeur -2 représente une dérivée du pixel initial. Si on y regarde attentivement, on peut remarquer que la dérivée du pixel -3 est juste la différence entre le pixel le plus à droite (2) de -3 et son pixel le plus à gauche (4).
Pourquoi utiliser des formules complexes quand on peut faire la différence entre deux éléments ? En effet, dans cet exemple, on aurait pu simplement calculer la différence d’intensité entre les éléments I(x, y + 1) et I(x, y – 1). Mais en réalité, nous pouvons gérer des scénarios plus complexes lorsque nous devons détecter des caractéristiques plus sophistiquées et moins évidentes. Pour cette raison, il est pratique d’utiliser la généralisation des noyaux dont les matrices sont déjà connues pour détecter des types prédéfinis de caractéristiques.
Sur la base de la valeur dérivée, nous pouvons faire quelques observations :
- Si la valeur dérivée est significative dans une région d’image donnée, cela signifie que l’intensité y change radicalement. Sinon, il n’y a pas de changements notables en termes de luminosité.
- Si la valeur de la dérivée est positive, cela signifie que de gauche à droite, la région de l’image devient plus lumineuse ; s’il est négatif, la région de l’image devient plus sombre dans le sens de gauche à droite.
En faisant l’analogie avec l’algèbre linéaire, les noyaux peuvent être considérés comme des opérateurs linéaires sur des images qui transforment des régions d’images locales.
De manière analogue, nous pouvons calculer la convolution avec le noyau vertical. La procédure restera la même, sauf que nous déplaçons maintenant notre fenêtre (noyau) verticalement à travers la matrice image.

Vous pouvez remarquer qu’après avoir appliqué un filtre de convolution à l’image originale 5×5, elle est devenue 3×3. C’est normal car on ne peut pas appliquer la convolution de la même manière aux pixels de bord (sinon on sortirait des limites).
Pour préserver la dimensionnalité de l’image, la technique de remplissage est généralement utilisée, qui consiste à étendre/interpoler temporairement les bordures de l’image ou à les remplir de zéros, de sorte que la convolution puisse également être calculée pour les pixels de bord.
Par défaut, les bibliothèques comme OpenCV remplissent automatiquement les bordures pour garantir la même dimensionnalité pour les images d’entrée et de sortie.
Dégradé d’images
Un dégradé d’image montre à quelle vitesse l’intensité (luminosité) change au niveau d’un pixel donné dans les deux directions (X et Y).

Formellement, le gradient d’image peut être écrit comme un vecteur de dérivées d’image par rapport aux axes X et Y.
Magnitude du gradient
L’amplitude du gradient représente une norme du vecteur gradient et peut être trouvée à l’aide de la formule ci-dessous :

Orientation du dégradé
En utilisant les Gx et Gy trouvés, il est également possible de calculer l’angle du vecteur gradient :

Exemple
Voyons comment calculer manuellement les dégradés en fonction de l’exemple ci-dessus. Pour cela, nous aurons besoin des matrices 3×3 calculées après l’application du noyau de convolution.
Si on prend le pixel en haut à gauche, il a les valeurs Gₓ = -2 et Gᵧ = 11. Nous pouvons facilement calculer l’ampleur et l’orientation du gradient :

Pour l’ensemble de la matrice 3×3, nous obtenons la visualisation suivante des dégradés :

En pratique, il est recommandé de normaliser les noyaux avant de les appliquer aux matrices. Nous ne l’avons pas fait par souci de simplicité de l’exemple.
Opérateur Sobel
Après avoir appris les principes fondamentaux des dérivées d’images et des gradients, il est maintenant temps de s’attaquer à l’opérateur Sobel, qui sert à les approximer. Par rapport aux noyaux précédents de tailles 3×1 et 1×3, l’opérateur Sobel est défini par une paire de noyaux 3×3 (pour les deux axes) :

Cela donne un avantage à l’opérateur Sobel car les noyaux mesuraient auparavant uniquement les changements 1D, ignorant les autres lignes et colonnes du voisinage. L’opérateur Sobel considère plus d’informations sur les régions locales.
Un autre avantage est que Sobel est plus robuste au bruit de manipulation. Regardons le patch d’image ci-dessous. Si nous calculons la dérivée autour de l’élément rouge au centre, qui se trouve à la frontière entre les pixels sombres (2) et clairs (7), nous devrions obtenir 5. Le problème est qu’il y a un pixel bruyant avec une valeur de 10.

Si nous appliquons le noyau 1D horizontal près de l’élément rouge, cela accordera une importance significative à la valeur du pixel 10, qui est clairement une valeur aberrante. En même temps, l’opérateur Sobel est plus robuste : il prendra en compte 10, ainsi que les pixels de valeur 7 qui l’entourent. Dans un certain sens, l’opérateur Sobel applique un lissage.
Lors de la comparaison de plusieurs noyaux en même temps, il est recommandé de normaliser les noyaux matriciels pour s’assurer qu’ils sont tous à la même échelle. L’une des applications les plus courantes des opérateurs en général dans l’analyse d’images est la détection de caractéristiques.
Dans le cas des opérateurs Sobel et Scharr, ils sont couramment utilisés pour détecter les contours, c’est-à-dire les zones où l’intensité des pixels (et son gradient) change radicalement.
OuvrirCV
Pour appliquer les opérateurs Sobel, il suffit d’utiliser la fonction OpenCV cv2.Sobel. Regardons ses paramètres :
derivative_x = cv2.Sobel(image, cv2.CV_64F, 1, 0)
derivative_y = cv2.Sobel(image, cv2.CV_64F, 0, 1)
- Le premier paramètre est une image NumPy d’entrée.
- Le deuxième paramètre (cv2.CV_64F) est la profondeur des données de l’image de sortie. Le problème est qu’en général, les opérateurs peuvent produire des images de sortie contenant des valeurs en dehors de l’intervalle 0-255. C’est pourquoi nous devons spécifier le type de pixels que nous voulons que l’image de sortie ait.
- Les troisième et quatrième paramètres représentent respectivement l’ordre de la dérivée dans la direction x et dans la direction y. Dans notre cas, nous voulons uniquement la dérivée première dans les directions x et y, nous transmettons donc les valeurs (1, 0) et (0, 1)
Regardons l’exemple suivant, où l’on nous donne une image d’entrée Sudoku :

Appliquons le filtre Sobel :
import cv2
import matplotlib.pyplot as plt
image = cv2.imread("data/input/sudoku.png")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
derivative_x = cv2.Scharr(image, cv2.CV_64F, 1, 0)
derivative_y = cv2.Scharr(image, cv2.CV_64F, 0, 1)
derivative_combined = cv2.addWeighted(derivative_x, 0.5, derivative_y, 0.5, 0)
min_value = min(derivative_x.min(), derivative_y.min(), derivative_combined.min())
max_value = max(derivative_x.max(), derivative_y.max(), derivative_combined.max())
print(f"Value range: ({min_value:.2f}, {max_value:.2f})")
fig, axes = plt.subplots(1, 3, figsize=(16, 6), constrained_layout=True)
axes[0].imshow(derivative_x, cmap='gray', vmin=min_value, vmax=max_value)
axes[0].set_title("Horizontal derivative")
axes[0].axis('off')
image_1 = axes[1].imshow(derivative_y, cmap='gray', vmin=min_value, vmax=max_value)
axes[1].set_title("Vertical derivative")
axes[1].axis('off')
image_2 = axes[2].imshow(derivative_combined, cmap='gray', vmin=min_value, vmax=max_value)
axes[2].set_title("Combined derivative")
axes[2].axis('off')
color_bar = fig.colorbar(image_2, ax=axes.ravel().tolist(), orientation='vertical', fraction=0.025, pad=0.04)
plt.savefig("data/output/sudoku.png")
plt.show()
Du coup, on voit que les dérivées horizontales et verticales détectent très bien les droites ! De plus, la combinaison de ces lignes nous permet de détecter les deux types de caractéristiques :

Opérateur de Scharr
Une autre alternative populaire au noyau Sober est l’opérateur Scharr :

Malgré sa similitude substantielle avec la structure de l’opérateur Sobel, le noyau Scharr atteint une plus grande précision dans les tâches de détection de contours. Il possède plusieurs propriétés mathématiques critiques que nous n’allons pas considérer dans cet article.
OuvrirCV
L’utilisation du filtre Scharr dans OpenCV est très similaire à ce que nous avons vu ci-dessus avec le filtre Sobel. La seule différence est un autre nom de méthode (les autres paramètres sont les mêmes) :
derivative_x = cv2.Scharr(image, cv2.CV_64F, 1, 0)
derivative_y = cv2.Scharr(image, cv2.CV_64F, 0, 1)
Voici le résultat que l’on obtient avec le filtre Scharr :

Dans ce cas, il est difficile de constater les différences de résultats pour les deux opérateurs. Cependant, en regardant la carte des couleurs, nous pouvons voir que la plage de valeurs possibles produites par l’opérateur de Scharr est beaucoup plus large (-800, +800) qu’elle ne l’était pour Sobel (-200, +200). C’est normal puisque le noyau Scharr a des constantes plus grandes.
C’est également un bon exemple de la raison pour laquelle nous devons utiliser un type spécial cv2.CV_64F. Sinon, les valeurs auraient été tronquées dans la plage standard comprise entre 0 et 255 et nous aurions perdu des informations précieuses sur les gradients.
Note. L’application des méthodes de sauvegarde directement aux images cv2.CV_64F provoquerait une erreur. Pour enregistrer de telles images sur un disque, elles doivent être converties dans un autre format et ne contenir que des valeurs comprises entre 0 et 255.
Conclusion
En appliquant les principes fondamentaux du calcul à la vision par ordinateur, nous avons étudié les propriétés essentielles des images qui nous permettent de détecter les pics d’intensité dans les images. Cette connaissance est utile puisque la détection de caractéristiques est une tâche courante dans l’analyse d’images, en particulier lorsqu’il existe des contraintes sur le traitement de l’image ou lorsque les algorithmes d’apprentissage automatique ne sont pas utilisés.
Nous avons également examiné un exemple utilisant OpenCV pour voir comment la détection des contours fonctionne avec les opérateurs Sobel et Scharr. Dans les articles suivants, nous étudierons des algorithmes plus avancés pour la détection de fonctionnalités et examinerons des exemples OpenCV.
Ressources
Toutes les images, sauf indication contraire, sont de l’auteur.



