Lección 2 — Construire pas à pas

Des images fictives dans un bucket temporaire

5 min de lectura

Este contenido aún no está traducido. Se muestra la versión original.

Des images fictives dans un bucket temporaire

Pourquoi des images synthétiques ?

  • Aucune donnée réelle : pas de photo d'usine, pas de secret industriel, rien à anonymiser.
  • Vérité terrain gratuite : puisque c'est le code qui dessine le défaut, on sait exactement ce que contient chaque image.
  • Reproductible : une graine aléatoire fixe par pièce donne les mêmes images à chaque exécution.

La limite est évidente : de vraies photos ont des reflets, du flou, des cadrages variables. Ce lab valide la chaîne (bucket, outils, schéma, évaluation) ; la précision réelle se mesure ensuite sur de vraies photos étiquetées.

Dessiner une rondelle et ses défauts

Pillow est la bibliothèque Python de référence pour créer et modifier des images. ImageDraw dessine des formes, ImageFilter applique un léger flou pour éviter des bords trop parfaits.

files/generer_images.py
def dessiner_piece(defaut: str, graine: int) -> Image.Image:
    rnd = random.Random(graine)
    img = Image.new("RGB", (TAILLE, TAILLE), (235, 235, 230))  # fond du convoyeur
    d = ImageDraw.Draw(img)
    c, r_ext, r_int = TAILLE // 2, 190, 70
    d.ellipse([c - r_ext, c - r_ext, c + r_ext, c + r_ext], fill=(150, 155, 160), outline=(90, 90, 95), width=4)
    d.ellipse([c - r_int, c - r_int, c + r_int, c + r_int], fill=(235, 235, 230), outline=(90, 90, 95), width=4)
    ...
    if defaut == "rayure":
        d.line([(c - 160, c - 60), (c - 40, c - 150)], fill=(225, 225, 230), width=5)
    elif defaut == "fissure":
        ...  # ligne brisée sombre
    elif defaut == "tache":
        d.ellipse([c + 60, c + 70, c + 130, c + 125], fill=(120, 75, 35))  # rouille
    elif defaut == "manque":
        d.pieslice([c - r_ext, c - r_ext, c + r_ext, c + r_ext], 300, 330, fill=(235, 235, 230))
    return img.filter(ImageFilter.GaussianBlur(0.6))

La correspondance « fichier → défaut » est écrite dans verite_terrain.json, à côté du dossier images/ : ce fichier ne part pas dans le bucket, l'agent ne peut donc pas le lire.

flowchart LR
  G["generer_images.py"] --> I["images/piece-01.png ... piece-06.png"]
  G --> V["verite_terrain.json (reste local)"]
  I -->|"gcloud storage cp"| B["gs://lab-inspection-qualite-PROJET/images/"]
  V --> E["évaluation (leçon 4)"]

Pourquoi verite_terrain.json n'est-il pas copié dans le bucket ?

Le bucket temporaire

Un bucket est un conteneur d'objets (fichiers) dans Cloud Storage. Son nom est unique au monde : on y ajoute l'identifiant du projet.

files/preparer.sh
BUCKET="${BUCKET:-lab-inspection-qualite-${PROJECT_ID}}"   # nom unique au monde

gcloud storage buckets create "gs://${BUCKET}" --project "$PROJECT_ID" --location "$REGION" \
  --uniform-bucket-level-access --public-access-prevention
gcloud storage buckets update "gs://${BUCKET}" --update-labels formation-lab=inspection-qualite-vision-gcs
python generer_images.py images
gcloud storage cp images/*.png "gs://${BUCKET}/images/"
OptionPourquoi
--location europe-west1données et modèle dans la même région
--uniform-bucket-level-accessdroits gérés par IAM seulement, pas d'ACL objet par objet
--public-access-preventioninterdit tout partage public, même par erreur
--update-labels formation-lab=...buckets create n'accepte pas de labels dans notre version de gcloud : on les pose juste après

Un collègue propose d'activer l'accès public au bucket « pour que l'atelier voie les photos plus facilement ». Que répondez-vous ?

Lancer la préparation

terminal
cd files
export PROJECT_ID="mon-projet-gcp"
./preparer.sh
gcloud storage ls "gs://lab-inspection-qualite-${PROJECT_ID}/images/"

Résultat obtenu

Sortie réelle du 2026-10-15 (nom du projet masqué) :

preparer.sh (2026-10-15)
Creating gs://lab-inspection-qualite-mon-projet-gcp/...
6 images écrites dans images/
Average throughput: 2.9MiB/s
gs://lab-inspection-qualite-mon-projet-gcp/images/piece-01.png
gs://lab-inspection-qualite-mon-projet-gcp/images/piece-02.png
gs://lab-inspection-qualite-mon-projet-gcp/images/piece-03.png
gs://lab-inspection-qualite-mon-projet-gcp/images/piece-04.png
gs://lab-inspection-qualite-mon-projet-gcp/images/piece-05.png
gs://lab-inspection-qualite-mon-projet-gcp/images/piece-06.png

Exercice

Rendez le lot plus difficile : ajoutez une pièce piece-07.png qui porte deux défauts (rayure et tache), et dites ce que cela change pour le schéma de réponse.

Voir la solution

Dans generer_images.py, laissez dessiner_piece accepter une liste :

def dessiner_piece(defauts, graine):
    defauts = [defauts] if isinstance(defauts, str) else defauts
    ...
    if "rayure" in defauts:
        d.line([(c - 160, c - 60), (c - 40, c - 150)], fill=(225, 225, 230), width=5)
    if "tache" in defauts:
        d.ellipse([c + 60, c + 70, c + 130, c + 125], fill=(120, 75, 35))
    ...

PIECES["piece-07.png"] = ["rayure", "tache"]

Le schéma actuel n'a qu'un champ defaut : le modèle devra choisir un des deux. Deux options :

  • garder un seul champ mais demander le défaut le plus grave (règle à écrire dans la consigne) ;
  • passer à defauts: list[Literal[...]] et adapter le rapport et l'évaluation (comparer des ensembles).

Je sais faire

  • Générer des images synthétiques reproductibles avec Pillow
  • Tenir une vérité terrain hors de portée de l'agent
  • Créer un bucket privé, régional, étiqueté
  • Limiter les droits d'un agent à un seul bucket
Siguiente