Visuels générés et détourage
Premier essai de la journée, prompt explicite : « isolated on a perfectly uniform pure #00FF00 green background, no text, no letters ». Le modèle a rendu un entrepôt réussi, sur un sol gris qui touche les bords, avec « LOGISTICS » sur la façade et « EV » sur le camion. Les éléments visuels générés sont la partie la plus séduisante du projet et la moins prévisible : il faut des prompts qui ne demandent aucune information, un détourage qui mesure sa propre qualité et un cache qui évite de payer deux fois.
Le mécanisme
flowchart LR
P["Demande : nom, prompt, ratio, détourer ?"] --> K{"Dans le cache ? (hash modèle + ratio + prompt)"}
K -->|"oui"| R["Relire le PNG : 0 $"]
K -->|"non"| G["Gemini image"] --> W["Écrire dans sorties/cache/"]
W --> R2["Image RVB"]
R --> R2
R2 --> D{"Détourer ?"}
D -->|"non : fond"| OK["Visuel prêt"]
D -->|"oui"| C["Clé verte : alpha progressif + suppression du halo"]
C --> Q["Rapport : bord uni, résidu vert, couverture"]
Q --> OK
Étape 1 : des prompts sans information
Six demandes, déclarées une fois dans visuels.py :
| Nom | Ratio | Détourée | Rôle |
|---|---|---|---|
fond_portrait, fond_paysage | 9:16, 16:9 | non | ambiance, très faible contraste |
illustration | 1:1 | oui | le sujet (entrepôt solaire) |
icone_eclair, icone_baisse, icone_soleil | 1:1 | oui | repères des KPI |
SANS_TEXTE = "No text, no letters, no numbers, no logo, no watermark."
Demande(
"illustration",
"Flat vector illustration, isometric, of a modern logistics warehouse with solar panels on the "
"roof and one small electric delivery van. Palette: dark teal, slate blue, warm sand, white. "
"Isolated on a perfectly uniform pure #00FF00 chroma green background, no ground shadow, "
"no green in the subject. " + SANS_TEXTE,
"1:1",
True,
)Trois consignes font le travail : « no green in the subject » (sinon le détourage troue le sujet), « no ground shadow » (une ombre portée sur le vert devient une tache semi-transparente), et une palette nommée qui reprend les couleurs de la charte. Les fonds sont demandés « very low contrast, large empty calm areas » : ils seront sous du texte.
Les deux fonds ont leur propre ratio. Recadrer un fond 9:16 en 16:9 ne garde que 32 % de sa hauteur, agrandie ensuite : le flou se voit sur un écran large.
Étape 2 : générer une fois, relire ensuite
def obtenir(gen: Generateur, d: Demande, cache: Path) -> Image.Image:
"""Génère une fois, relit ensuite : même prompt + même modèle = même fichier, zéro coût."""
cle = hashlib.sha256(f"{gen.nom}|{d.aspect}|{d.prompt}".encode()).hexdigest()[:16]
chemin = cache / f"{d.nom}-{cle}.png"
if chemin.exists():
return Image.open(chemin).convert("RGB")
img = gen.generer(d)
...La clé contient le nom du modèle : passer de Flash Image à Pro Image régénère, modifier une virgule du prompt aussi. Pendant la mise au point de cette formation, onze runs réels ont coûté 12 images (6 par modèle) au lieu de 66.
Le client Gemini est créé une seule fois et gardé pendant tout le run, avec 4 tentatives sur les erreurs 429 et 5xx. Un genai.Client() créé dans une expression puis abandonné est fermé par le ramasse-miettes : l'appel suivant échoue avec « Cannot send a request, as the client has been closed ». Nous l'avons rencontré en écrivant le script de la leçon 1.
Étape 3 : détourer par clé de couleur
Le détourage par clé de couleur (chroma key) rend transparent tout pixel proche d'une couleur choisie. Le vert pur est retenu parce qu'il n'apparaît presque jamais dans un sujet industriel ; pour un sujet végétal, prenez le magenta #FF00FF.
def detourer_chroma(img, cle=CLE, bas: float = 70, haut: float = 160):
rgb = np.asarray(img.convert("RGB"), dtype=np.float32)
d = np.linalg.norm(rgb - np.array(cle, dtype=np.float32), axis=2)
alpha = np.clip((d - bas) / (haut - bas), 0, 1)
# dé-contamination du vert (« despill ») sur les bords semi-transparents
r, g, b = rgb[..., 0], rgb[..., 1], rgb[..., 2]
bord = alpha < 0.999
g_corrige = np.where(bord, np.minimum(g, np.maximum(r, b)), g)- Alpha progressif : en dessous d'une distance de 70 au vert, transparent ; au-dessus de 160, opaque ; entre les deux, un dégradé. Un seuil unique donne un bord en escalier.
- Despill : un pixel de bord mélange le sujet et le vert. On plafonne sa composante verte au maximum du rouge et du bleu, ce qui retire le liseré vert sans toucher l'intérieur du sujet.
- Les images arrivent en JPEG : la compression bave autour des contours, d'où des seuils larges plutôt qu'une égalité stricte.
Le détourage mesure sa propre qualité :
| Mesure | Définition | Seuil |
|---|---|---|
bord_uni | part des pixels du cadre proches du vert | ≥ 0,6, sinon le fond n'était pas uni |
residu_cle | part des pixels opaques encore verdâtres | ≤ 2 % |
couverture | part de l'image occupée par le sujet | entre 2 % et 95 % |
Quand le fond n'est pas uni (photo réelle, modèle qui a ignoré la consigne), detourer_masque() applique un masque fourni par un outil de segmentation ou dessiné, avec un léger flou du bord.
Le rapport donne bord_uni = 0,31 pour l'illustration. Le rendu final montre un rectangle beige autour de l'entrepôt. Quelle est la première action ?
bord_uni mesure le fond et non le détourage : 31 % de vert sur le cadre signifie que le modèle n'a pas respecté la consigne. Monter le seuil ne rendra pas transparent un fond beige et attaquera le sujet. Changer de modèle peut aider, mais seulement après avoir vu ce qui s'est passé.
Étape 4 : lancer avec la clé
cp .env.example .env # puis renseignez GOOGLE_API_KEY
make runSans clé, make run utilise FauxGenerateur : des formes procédurales déterministes, qui servent aussi aux tests. Le reste du pipeline est identique.
Ce qui casse en production
| Symptôme | Cause | Parade |
|---|---|---|
| Lettres parasites sur l'illustration | le modèle « décore » malgré la consigne | regarder chaque visuel une fois avant de figer le cache ; régénérer avec un autre prompt |
| Trous dans le sujet | le sujet contient du vert (feuillage, logo vert) | clé magenta, ou masque |
| Liseré vert sur fond sombre | despill absent ou seuil haut trop bas | garder le despill, élargir l'intervalle bas/haut |
| Facture qui grimpe en CI | make clean entre chaque run vide le cache | conserver sorties/cache/ comme artefact ou ne pas générer en CI (--hors-ligne) |
aucune image renvoyée | refus du filtre de sécurité, souvent sur un mot ambigu | l'erreur est explicite ; reformuler le prompt |
Règle de décision
- Demandez au modèle d'image des formes et une ambiance, jamais un mot ni un chiffre.
- Fond uni + clé pour tout sujet que vous contrôlez par le prompt ; masque pour une photo réelle.
- Relisez les visuels une fois quand ils entrent dans le cache ; le cache garantit ensuite qu'ils ne changent plus.
Un graphiste propose de générer les icônes en une seule image (une planche de trois icônes) pour économiser deux appels. Votre avis ?
L'argument de cohérence est réel, mais le cache supprime déjà le coût récurrent : on paie trois appels une fois. Le découpage ajoute du code fragile pour un gain négligeable. Les modèles savent dessiner plusieurs objets ; la difficulté est de les séparer proprement.
Exercice
Ajoutez une quatrième icône icone_camion et faites-la apparaître à côté du KPI meilleur_site_variation. Que se passe-t-il au premier make run et au second ?
Voir la solution
Dans DEMANDES :
Demande(
"icone_camion",
"Single flat icon of a delivery van seen from the side, solid dark teal #0F766E, thick simple "
"geometric shape, centered with generous margin, on a perfectly uniform pure #00FF00 green background. "
+ SANS_TEXTE,
"1:1",
True,
),Dans mise_en_page.py, ICONES["meilleur_site_variation"] = "icone_camion".
Premier make run : une seule génération (génération icone_camion avec …), les cinq autres visuels sortent du cache. Second run : aucune génération. Vérifiez detourage.icone_camion.acceptable dans manifeste.json.
Résultat obtenu
Premier run réel du 2026-10-15 (20:47, gemini-3.1-flash-image), puis run avec gemini-3-pro-image (20:51) : six générations chacun, 1 min 44 s et 2 min 26 s au total. Rapport de détourage du run Pro, extrait de manifeste.json :
"detourage": {
"illustration": {"bord_uni": 1.0, "residu_cle": 0.00066, "couverture": 0.40, "acceptable": true},
"icone_eclair": {"bord_uni": 1.0, "residu_cle": 0.0, "couverture": 0.09, "acceptable": true},
"icone_baisse": {"bord_uni": 1.0, "residu_cle": 0.0, "couverture": 0.11, "acceptable": true},
"icone_soleil": {"bord_uni": 1.0, "residu_cle": 0.0, "couverture": 0.17, "acceptable": true}
}Les quatre visuels détourés du run Pro ne contiennent aucun texte parasite.
Je sais faire
- Écrire un prompt d'image qui ne porte aucune information
- Mettre en cache les générations par empreinte du modèle et du prompt
- Détourer par clé avec alpha progressif et despill, et lire le rapport de qualité
- Choisir entre clé de couleur et masque