Simon CHABROL

Écriture et recherche indépendante (FR/EN)

Technicien de support IT

Documentation technique

Je présente ici un script Python autonome qui applique des filtres graphiques à des photographies pour les transformer en rendus d’estampe — linogravure, croquis au crayon, taille-douce ou plaque typographique. Il repose sur OpenCV pour les opérations de traitement d’image et sur NumPy pour les calculs matriciels. Son interface en ligne de commande permet d’ajuster le style, le niveau de détail et le contraste sans modifier le code source.

  1. 1. Architecture générale
    1. Structure de répertoires attendue
  2. 2. Pipeline de traitement
    1. Étape 1 — Conversion en niveaux de gris
    2. Étape 2 — Réduction du bruit
    3. Étape 3 — Application du style graphique
    4. Étape 4 — Ajustement du contraste
    5. Étape 5 — Inversion optionnelle
  3. 3. Détail des algorithmes de rendu
    1. 3.1 Style linocut
    2. 3.2 Style pencil
    3. 3.3 Style engraving
    4. 3.4 Style printing
  4. 4. Paramètres de la ligne de commande
  5. 5. Recommandations d’usage
    1. Photographies d’archives
    2. Images à texture dense (végétation, fourrure, tissu)
    3. Rendu proche d’un négatif photographique

1. Architecture générale

Le script est organisé autour de deux fonctions principales : process_directory(), qui pilote le traitement en lot, et apply_sketch_filter(), qui applique la chaîne de traitement à une image individuelle. Ces deux fonctions sont appelées depuis main(), qui gère l’interface en ligne de commande via argparse.

Structure de répertoires attendue

project/
├── sketch_filter.py
├── input_images/ ← images source
(jpg, png, bmp, tiff, webp)
└── output_sketchs/ ← généré automatiquement
au premier lancement

La fonction process_directory() parcourt le répertoire source, filtre les fichiers par extension, puis appelle apply_sketch_filter() pour chaque image. Les erreurs sur une image individuelle sont interceptées et consignées sans interrompre le traitement des images suivantes.

2. Pipeline de traitement

Chaque image passe par cinq étapes successives, quelle que soit la combinaison de paramètres choisie.

Étape 1 — Conversion en niveaux de gris

L’image est chargée par cv2.imread() au format BGR, puis convertie en niveaux de gris via cv2.cvtColor(img, cv2.COLOR_BGR2GRAY). Cette conversion est nécessaire car tous les algorithmes de rendu opèrent sur un canal unique d’intensité lumineuse.

Étape 2 — Réduction du bruit

Un débruitage non-local est appliqué avec cv2.fastNlMeansDenoising(). Les paramètres utilisés sont h=7 (force du filtre), templateWindowSize=7 (taille du patch de comparaison) et searchWindowSize=21 (fenêtre de recherche). Cette étape est critique : sans elle, les algorithmes de seuillage amplifient le bruit de capteur sous forme de granularité indésirable.

Étape 3 — Application du style graphique

Le style sélectionné détermine l’algorithme de rendu. Les quatre implémentations sont détaillées à la section 3.

Étape 4 — Ajustement du contraste

Un ajustement linéaire est appliqué via la fonction _adjust_contrast(). L’opération centre le contraste autour du point médian (128) :

adjusted = (img_float – 128) * factor + 128

Un facteur supérieur à 1.0 étend la plage dynamique (noir plus noir, blanc plus blanc). Un facteur inférieur à 1.0 la compresse, produisant un résultat plus doux. L’image résultante est recadrée entre 0 et 255 par np.clip() avant reconversion en entiers 8 bits.

Étape 5 — Inversion optionnelle

Si le paramètre –invert est activé, cv2.bitwise_not() permute les valeurs noir et blanc en sortie. Cette opération est appliquée en dernier, après l’ajustement de contraste, pour garantir sa compatibilité avec tous les styles.

3. Détail des algorithmes de rendu

3.1 Style linocut

Ce style imite la linogravure : larges zones noires opaques séparées par des traits blancs nets. Il est obtenu par la combinaison de quatre opérations.

Filtrage bilatéral. Un filtre bilatéral (cv2.bilateralFilter) lisse les textures tout en préservant les contours nets. Le paramètre sigmaSpace est inversement proportionnel au niveau de détail, ce qui permet de contrôler la quantité de micro-texture conservée.

Masque de Laplace. Le Laplacien de l’image accentuée (cv2.Laplacian avec ksize=3) produit un masque de contours normalisé entre 0 et 1. Ce masque est multiplié par 180 et soustrait au résultat du seuillage, ce qui assombrit les zones de forte transition — renforçant le côté gravé.

Seuillage adaptatif. La fonction cv2.adaptiveThreshold avec ADAPTIVE_THRESH_GAUSSIAN_C calcule un seuil local pour chaque région de l’image selon une fenêtre de taille blockSize. Cette approche est préférable à un seuil global sur les images à fort contraste local (lumière latérale, contre-jour).

Érosion morphologique. Une érosion avec un noyau elliptique 2×2 (cv2.erode) épaissit légèrement les zones noires, renforçant l’aspect massif typique de la linogravure.

3.2 Style pencil

Le rendu crayon repose sur une technique de division d’image. L’image originale en niveaux de gris est divisée pixel par pixel par son négatif flouté :

inv = cv2.bitwise_not(gray)
blurred_inv = cv2.GaussianBlur(inv, (sigma, sigma), 0)
sketch = cv2.divide(gray, 255 - blurred_inv, scale=256.0)

Cette opération produit un résultat proche du blanc là où l’image est uniforme, et des traits sombres là où se trouvent les transitions. Un seuillage THRESH_TRUNC à 230 écrête les hautes lumières, puis une normalisation étire la plage de valeurs pour maximiser le contraste des traits fins.

3.3 Style engraving

Ce style simule la taille-douce par superposition de hachures diagonales régulières, dont la densité varie selon la luminosité locale.

Génération des hachures. Un tableau blanc (255) est rempli de lignes diagonales espacées de spacing pixels (inversement proportionnel au niveau de détail). L’inclinaison est obtenue en décalant les coordonnées de fin de chaque ligne de 10 pixels par rapport au début.

Masquage par luminosité. Un masque binaire identifie les zones sombres de l’image (valeur < 128). Ce masque supprime les hachures dans les zones sombres, simulant l’accumulation de tailles dans les demi-teintes. Les bords détectés par cv2.Canny() sont dilatés et soustraits par opération bitwise_and pour ajouter des contours nets.

3.4 Style printing

Ce style produit l’effet d’une plaque typographique ou offset : grandes plages noires denses avec trouées blanches nettes.

Égalisation d’histogramme. La fonction cv2.equalizeHist() redistribue les niveaux de l’image pour qu’ils occupent toute la plage 0–255. Cette étape normalise les images sous-exposées ou surexposées avant le seuillage.

Unsharp mask renforcé. Un masque flou gaussien est soustrait à l’image lissée avec un coefficient de 1.8 (contre 1.5 pour le style linocut), produisant une accentuation plus agressive des transitions.

Fermeture morphologique. Après inversion du seuillage, une fermeture (cv2.morphologyEx avec MORPH_CLOSE) comble les petits trous dans les plages noires, renforçant l’aspect massif des aplats.

Réinjection des contours. Les bords détectés par cv2.Canny() avec des seuils bas (30 et 100) sont dilatés et fusionnés par cv2.bitwise_or() avec le résultat de la fermeture, assurant des contours toujours nets même sur les zones d’aplat.

4. Paramètres de la ligne de commande

ParamètreValeursDéfautEffet
–stylelinocut | pencil | engraving | printinglinocutSélectionne l’algorithme de rendu.
–detail0.5 → 2.01.0Contrôle la finesse des textures. Influence la taille des noyaux et des blocs adaptatifs dans chaque style.
–contrast0.5 → 2.01.2Ajustement linéaire noir/blanc en sortie, indépendant du style.
–invertdrapeaudésactivéInverse noir et blanc en dernière étape. Compatible avec tous les styles.
–inputchemininput_images/Répertoire source. Formats acceptés : jpg, jpeg, png, bmp, tiff, tif, webp.
–outputcheminoutput_sketchs/Répertoire de sortie. Créé automatiquement. Noms de fichiers conservés.

Le paramètre –detail influe concrètement sur plusieurs valeurs internes selon le style : taille du bloc de seuillage adaptatif (blockSize = int(31 / detail)), espacement des hachures pour l’engraving (spacing = int(6 / detail)), et sigma du filtre bilatéral (sigma = int(15 / detail)). Une valeur élevée produit donc des détails plus fins mais peut amplifier le bruit sur les images de faible résolution.

5. Recommandations d’usage

Photographies d’archives

python sketch_filter.py –style printing –invert –detail 1.5 –contrast 1.2

Combinaison recommandée par la documentation pour les clichés à fort contraste local. L’inversion restitue un fond blanc sur les images numérisées à fond sombre.

Images à texture dense (végétation, fourrure, tissu)

python sketch_filter.py –style linocut –detail 2.0 –contrast 1.0

Le niveau de détail maximal est compensé par une réduction du contraste pour éviter l’amplification du bruit de texture.

Rendu proche d’un négatif photographique

python sketch_filter.py –style engraving –invert –detail 1.5 –contrast 1.3

L’inversion du style engraving produit des hachures claires sur fond sombre, évoquant un négatif argentique.

Note : au-delà d’un niveau de détail de 1.8, il est conseillé d’abaisser le contraste à 1.0 ou moins pour compenser l’amplification du bruit sur les images de faible résolution.

Et voici quelques rendus :

Laisser un commentaire