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.
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 ?
Les outils de l'agent lisent le bucket. Si la vérité terrain y était, un outil mal écrit (ou un futur outil de recherche) pourrait la remonter au modèle, et l'exactitude mesurée serait artificiellement parfaite.
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.
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/"| Option | Pourquoi |
|---|---|
--location europe-west1 | données et modèle dans la même région |
--uniform-bucket-level-access | droits gérés par IAM seulement, pas d'ACL objet par objet |
--public-access-prevention | interdit 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 ?
Un nom de bucket n'est pas un secret, et les photos de production sont des données de l'entreprise. L'accès se donne à des identités précises via IAM ; --public-access-prevention empêche justement l'erreur.
Lancer la préparation
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é) :
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.pngExercice
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