Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

La Tour Infernale

Un jeu construit par tous, pour tous

Bienvenue dans La Tour Infernale, un jeu Roblox que nous allons construire ensemble ! Chaque participant fabrique sa propre partie du jeu, et à la fin, tous les parcours se rejoignent au sommet d’une grande tour.

L’objectif du jeu

L’objectif est de construire un jeu à plusieurs :

  • Chaque participant construit un chemin semé d’embûches, mais réalisable, pour rejoindre la tour.
  • Une fois les parcours terminés, tous les joueurs devront rejoindre le haut panoramique de la tour pour admirer les différents projets de toute l’équipe.

L’état d’esprit : exigeant mais juste

Ton parcours doit respecter une règle d’or :

Difficile, oui. Impossible, non !

  • Construis un parcours réalisable : un bon joueur qui se concentre doit pouvoir le réussir.
  • Mais rends-le difficile pour les autres joueurs : pièges, obstacles, sauts risqués… tout est permis (tant que c’est possible) !
  • Teste ton propre parcours encore et encore : si toi tu ne peux pas le passer, personne ne pourra !

Démarrer : trouver son spawn

Chaque joueur dispose d’un spawn (point de départ) pour débuter son chemin.

  1. Repère le numéro de ton poste de travail (par exemple 07).
  2. Dans l’explorateur (à droite de Roblox Studio), ouvre Workspace, puis le dossier spawns.
  3. Cherche le spawn qui porte le numéro de ton poste : il s’appelle spawn123codageXX, où XX est ton numéro de poste.
    • Exemple : poste 07 → spawn123codage07
  4. Sélectionne ton spawn, puis appuie sur la touche F du clavier : tu es automatiquement positionné sur ton spawn dans l’espace du jeu. Tu peux commencer à construire !

Tester son parcours

Pour construire un bon parcours, il faut se mettre en test régulièrement :

  • Lance ton jeu en test pour essayer ton parcours.
  • Vérifie que chaque saut est faisable et que chaque piège est évitable.
  • Améliore, re-teste, recommence : c’est comme ça qu’on fabrique un parcours réussi !

Jouer à plusieurs : le « Team Test »

Quand tout le monde est prêt, on lance une partie en Team Test :

  • Chaque joueur est positionné aléatoirement sur le spawn d’un autre joueur.
  • Tu découvres donc le parcours construit par un de tes camarades… sans savoir à l’avance lequel !
  • À toi de franchir ses embûches pour rejoindre la tour.

Règles du jeu

  1. Crée ton Folder dans le workspace pour ranger toutes tes créations graphiques.
  2. Crée ton Folder dans ServerScriptService pour ranger tes scripts.
  3. Réalise un parcours d’embûches pour accéder à la tour, puis à la vue panoramique.
  4. Ton parcours doit être réalisable : un joueur déterminé doit pouvoir le finir.
  5. Ton objectif est de le rendre difficile pour les autres joueurs : plus c’est dur (mais possible), plus c’est fun !
  6. Respecte le travail des autres : ne modifie que ton parcours et ton spawn.
  7. Pas de contenu inapproprié, pas de triche : on joue dans les règles de Roblox.

Bonne construction, et rendez-vous au sommet de la tour !

Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU02

Des portes à ouvrir

Objectif : Créer un labyrinthe avec des portes à ouvrir

Ouvrir une porte avec un bouton poussoir :

Construis une porte avec un bouton poussoir pour l’ouverture :

Avec un ClickDetector sur le bouton :

Intercepter le click sur le ClikDetector dans le script :

-- Récupération de la porte
local door = script.Parent
-- Récupération du clickDetector
local clickDetector = door.PushButton.ClickDetector

clickDetector.MouseClick:Connect(function(player)
	print("Le joueur "..player.Name, "a demandé l'ouverture de la porte")
end)

Lance le jeu et click sur le bouton poussoir, un message s’affiche dans la sortie :

Puis modifie ton script pour « ouvrir la porte » en rendant invisible la Part de la porte :

-- Récupération de la porte
local door = script.Parent
-- Récupération du clickDetector
local clickDetector = door.PushButton.ClickDetector

clickDetector.MouseClick:Connect(function(player)
	print("Le joueur "..player.Name, 
		"a demandé l'ouverture de la porte")
	door.Door.Transparency = 1
end)

Mais tu peux constater que le joueur ne passe pas la porte, pour éviter ce problème tu peux éviter la collision entre la porte et le joueur :

-- Récupération de la porte
local door = script.Parent
-- Récupération du clickDetector
local parts= door:GetChildren()

function openDoor(player)
	print("Le joueur "..player.Name, 
		"a demandé l'ouverture de la porte")
	door.Door.Transparency = 1
	door.Door.CanCollide = false
end

Deux possibilités pour refermer la porte :

  • un autre click par le joueur
-- Récupération de la porte
local door = script.Parent
-- Récupération du clickDetector
local clickDetector = door.PushButton.ClickDetector

clickDetector.MouseClick:Connect(function(player)
	print("Le joueur "..player.Name, 
		"a demandé l'ouverture de la porte")
	if door.Door.CanCollide then
		door.Door.Transparency = 1
	else
		door.Door.Transparency = 0
	end
	door.Door.CanCollide = not door.Door.CanCollide
end)
  • fermeture automatique après un certain temps
-- Récupération de la porte
local door = script.Parent
-- Récupération du clickDetector
local clickDetector = door.PushButton.ClickDetector

clickDetector.MouseClick:Connect(function(player)
	print("Le joueur "..player.Name, 
		"a demandé l'ouverture de la porte")
	door.Door.Transparency = 1
	door.Door.CanCollide = false
	
	task.wait(2)
	
	door.Door.Transparency = 0
	door.Door.CanCollide = true

end)

Ouverture de la porte avec deux boutons poussoirs :

Comment faire si deux boutons poussoirs de chaque coté de la porte :

-- Récupération de la porte
local door = script.Parent
-- Récupération du clickDetector
local parts= door:GetChildren()

function openDoor(player)
	print("Le joueur "..player.Name, 
		"a demandé l'ouverture de la porte")
	if door.Door.CanCollide then
		door.Door.Transparency = 1
	else
		door.Door.Transparency = 0
	end
	door.Door.CanCollide = not door.Door.CanCollide

end

-- récupération de tous les clickDetectors
for _, part in ipairs(parts) do
	local clickDetector = 
              part:FindFirstChild("ClickDetector")
	if clickDetector then
		clickDetector.MouseClick:Connect(function(player)
			openDoor(player)
		end)
	end
end

Fermeture automatique de la porte

Comment refermer la porte automatiquement au bout d’un laps de temps :

-- Récupération de la porte
local door = script.Parent
-- Récupération du clickDetector
local parts= door:GetChildren()

function closeDoor()
	if not door.Door.CanCollide then
		door.Door.Transparency = 0
		door.Door.CanCollide = true
	end
end	
	
function openDoor(player)
	print("Le joueur "..player.Name, 
		"a demandé l'ouverture de la porte")
	if door.Door.CanCollide then
		door.Door.Transparency = 1
		task.delay(2, closeDoor)
	else
		door.Door.Transparency = 0
	end
	door.Door.CanCollide = not door.Door.CanCollide

end

-- récupération de tous les clickDetectors
for _, part in ipairs(parts) do
	local clickDetector = part:FindFirstChild("ClickDetector")
	if clickDetector then
		clickDetector.MouseClick:Connect(function(player)
			openDoor(player)
		end)
	end
end

Ouvrir une porte par une zone de détection :

Crée un part invisible autour de la porte qui aura la fonction d’une zone de détection de l’entrée ou la sortie du joueur :

Modifie les propriétés de la zone de détection

Supprime l’option de la possibilité de la collision :

Rend transparent la zone de collision :

Et évidement ancre la zone de détection :

Modifie le script pour détecter l’entrée et la sortie du joueur de la zone de détection :

-- Récupération de la porte
local door = script.Parent
local detectionZone = door.DetectionZone
-- Le joueur rentre dans la zone de détection
detectionZone.Touched:Connect(function()
	print("entrée")
end)
-- Le joueur sort de la zone de détection
detectionZone.TouchEnded:Connect(function()
	print("sortie")
end)

Ouvrir et fermer la porte avec la détection du joueur :

-- Récupération de la porte
local door = script.Parent
local detectionZone = door.DetectionZone

-- Le joueur rentre dans la zone de détection
detectionZone.Touched:Connect(function()
	print("entrée")
	if door.Door.CanCollide then
		door.Door.Transparency = 1
		door.Door.CanCollide = false
	end
end)

-- Le joueur sort de la zone de détection
detectionZone.TouchEnded:Connect(function()
	print("sortie")
	if not door.Door.CanCollide then
		door.Door.Transparency = 0
		door.Door.CanCollide = true
	end
end)

Rajoute une temporisation avant la fermeture de la porte pour éviter des ouvertures et des fermetures intempestives quand le joueur reste sur le bord de la zone de détection :

-- Récupération de la porte
local door = script.Parent
-- Récupération de la zone de détection
local detectionZone = door.DetectionZone
-- Temps d'attente avant fermeture de la porte
local TIMEOUTCLOSE = 2

-- Le joueur rentre dans la zone de détection
detectionZone.Touched:Connect(function()
	print("entrée")
	if door.Door.CanCollide then
		door.Door.Transparency = 1
		door.Door.CanCollide = false
	end
end)

-- Le joueur sort de la zone de détection
detectionZone.TouchEnded:Connect(function()
	print("sortie")
	if not door.Door.CanCollide then
		task.spawn(function()
			door.Door.CanCollide = true
			task.wait(TIMEOUTCLOSE)
			if door.Door.CanCollide then
				door.Door.Transparency = 0
			end
		end)
	end
end)

Rajouter un son d’ouverture

Ouvrir la boîte à outils pour trouver un son d’ouverture de porte :

Choisir Audio :

Saisir une recherche :

Choisir un son pour le mettre dans l’arborescence du workspace de la porte :

Puis modifie le script pour rajouter le son lors de l’ouverture de la porte :

-- Récupération de la porte
local door = script.Parent
-- Récupération de la zone de détection
local detectionZone = door.DetectionZone
-- Temps d'attente avant fermeture de la porte
local TIMEOUTCLOSE = 2
local soundDoor = door.door_open

-- Le joueur rentre dans la zone de détection
detectionZone.Touched:Connect(function()
	print("entrée")
	if door.Door.CanCollide then
		soundDoor:Play()
		door.Door.Transparency = 1
		door.Door.CanCollide = false
	end
end)

-- Le joueur sort de la zone de détection
detectionZone.TouchEnded:Connect(function()
	print("sortie")
	if not door.Door.CanCollide then
		task.spawn(function()
			door.Door.CanCollide = true
			task.wait(TIMEOUTCLOSE)
			if door.Door.CanCollide then
				door.Door.Transparency = 0
			end
		end)
	end
end)

Ouvrir une porte par une rotation :

Créez une porte à partir d’un part :

Rangez le part et renommez le dans l’explorateur :

Puis créez avec un part une poignée :

Créez une contrainte rigide entre la porte et la poignée :

Sélectionnez la poignée puis la porte puis demande une « soudure » entre les deux éléments :

Demandez un clickDetector sur la poignée :

Modifiez le pivot de la porte :

Créez un script pour ouvrir la porte :

-- Récupération du clickDetector
local clickDetector = script.Parent.Handle.ClickDetector
-- Récupération de la porte
local door = script.Parent.Door
-- Récupération de la distance d'activation du clickdetector
local distance = clickDetector.MaxActivationDistance

-- Fonction de rotation de la porte
local function doorRotation(door, push, rotation)
	clickDetector.MaxActivationDistance = 0
	if push ~= 0 then
		for i = 1, (rotation//math.abs(push)) do
			door:PivotTo(door:GetPivot() * 
				CFrame.Angles(0, math.rad(push), 0))
			wait(0.01)
		end
	end
	clickDetector.MaxActivationDistance = distance
end

-- Fonction de détection de clic di clickdetector
clickDetector.MouseClick:Connect(function()
	doorRotation(door, 1, 90)
end)

Refermez la porte au bout de x secondes, rajouter ces deux instructions :

-- Fonction de détection de clic
clickDetector.MouseClick:Connect(function()
	doorRotation(door, 1, 90)
	task.wait(5)
	doorRotation(door, -1, 90)
end)

Modifier le script pour un déplacement de la porte vers le haut :

local clickDetector = script.Parent.Handle.ClickDetector
local door = script.Parent.Door

local distance = clickDetector.MaxActivationDistance

local function doorPushUp(door, push)
	door.CFrame += Vector3.new(0,push,0)
	while door.CFrame.Position.Y>5 and door.CFrame.Position.Y<15   do
		wait(0.01)
		door.CFrame += Vector3.new(0,push,0)
	end
end

clickDetector.MouseClick:Connect(function()
	clickDetector.MaxActivationDistance = 0
	doorPushUp(door, 1)
	task.wait(5)
	doorPushUp(door, -1)
	clickDetector.MaxActivationDistance = distance

end)
Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU02

Crée un système de zones de danger

Crée des zones de danger qui vont réduire la santé des joueurs lorsqu’ils les touchent. Pour commencer, apprend à créer une grande partie invisible au niveau de l’eau, de sorte que tomber dans celle-ci entraine la disparition du joueur et le fasse recommencer au début du jeu.

Pour créer cette zone de danger :

  1. Dans la fenêtre de l’Explorateur , ajoute un nouveau dossier dans le dossier Monde , puis renomme-le : Hazards .
  2. Dans le dossier Hazards , insére une partie bloc et renomme-la Hazard :

Sur le + à droite de World demande à insérer un Model :

Puis renomme ce model en Hazards :

Puis dans l’onglet MODELE, demande un nouveau Elément Bloc :

Puis glisse ce nouvel objet sous Hazards :

Renomme le Hazard :

Déplace et met à l’échelle la zone de danger pour couvrir l’eau autour des plates-formes. Par exemple, avec ces données :

Puis modifie ces propriétés pour que la zone de danger soit invisible et que les joueurs puissent le traverser :

Règle la transparence sur 1. Cela rend le danger invisible, de sorte que l’eau réelle semble être le danger:

Désactive CanCollide . Cela indique au moteur ROBLOX que les autres parties peuvent traverser le danger sans interruption, ce qui signifie que les joueurs peuvent tomber à travers le danger.

Active Ancre . Cela indique au moteur ROBLOX de ne jamais modifier la position de la zone de danger.

Crée un script dans ServerScriptService , puis renommez-le en HazardService :

Remplace le code par défaut par le code suivant :

local workspace = game:GetService("Workspace")
local hazards = workspace.World.Hazards:GetChildren()

local function onHazardTouched(player, hazard)
	if not hazard:GetAttribute("Enabled") then
		return
	end
	local character = player.Parent
	local humanoid = character and character:FindFirstChild("Humanoid")
	if humanoid then
		humanoid.Health = 0
	end

end

for _, hazard in hazards do
	hazard:SetAttribute("Enabled", true)
	hazard.Touched:Connect(function(player)
		onHazardTouched(player, hazard)
	end)
end

Joue et tombe dans l’eau autour des plateformes :

Rajoute des animations sur les zones de danger

Mettre le feu

Installe une zone où un incendie se déclenche aléatoirement, à ne pas traverser au péril de perdre une vie.

Dans la boîte à outil recherche une animation d’un feu : fire :

Sélectionne l’animation Realistic Fire With Smoke, un feu avec de la fumée. Puis glisse cette animation sous Hazards :

Positionne ton incendie à un endroit stratégique de ton jeu :

Modifie le script HazardService en ajoutant cette programmation qui va sur un intervalle de temps aléatoire allumer ou éteindre le feu en rendant ainsi la zone dangereuse ou pas.

local workspace = game:GetService("Workspace")
local hazards = workspace.World.Hazards:GetChildren()

local function onHazardTouched(player, hazard)
	if not hazard:GetAttribute("Enabled") then
		return
	end
	local character = player.Parent
	local humanoid = character and character:FindFirstChild("Humanoid")
	if humanoid then
		humanoid.Health = 0
	end

end

for _, hazard in hazards do
	hazard:SetAttribute("Enabled", true)
	hazard.Touched:Connect(function(player)
		onHazardTouched(player, hazard)
	end)
end

local runService = game:GetService("RunService")
local interval = math.random(5, 10)

local function updateFire()
	for _, hazard in hazards do
		local fire = hazard:FindFirstChildOfClass("ParticuleEmitter")
		if fire then
			fire.Enabled = not fire.Enabled
			hazard.SetAttribut("Enabled", fire.Enabled)
		end
	end
end

Le joueur doit attendre la fin de l’incendie pour pouvoir passer.

Si tu le souhaites, tu peux installer plusieurs zones de feu dans ton jeu :

Une explosion

Rajoute un bloc sous Hazards et renomme le Explosion :

Puis utilise le + à droite de Explosion pour rajouter ParticleEmitter :

Que tu renommes Explosion :

Puis modifie les propriétés suivantes :

Teste le résultat et reviens sur les paramètres pour obtenir le résultat que tu souhaites :

Puis rends le socle transparent et canCollide à False :

Recommence pour une nouvelle zone de danger avec ce nouvel effet comme celui-ci :

Trouve les bons paramètres.

Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Rend une plateforme invisible avec un clickDetector ou ProximityPrompt

Objectif : Créer un part invisible qui apparaît lorsque le joueur clique sur un autre part avec un clickDetector.

Instructions :

  1. Créer un part avec ClickDetector : Ajoutez un part, renommez-la, et ajoutez un « ClickDetector ».
  2. Créer un part invisible : Ajoutez un autre part et définissez Transparency = 1 et CanCollide = false.
  3. Script d’apparition : Lorsque le joueur clique sur le part avec le « ClickDetector », le deuxième part apparaît.
    • Indices :
      • Utilisez myClickDetector.MouseClick:Connect(function() pour détecter le clic.

Comment coder un ClickDetector sur le part :

local myFirstPart = script.Parent
local clickDetector = myFirstPart.ClickDetector
local mySecondPart =myFirstPart.MySecondPart

clickDetector.MouseClick:Connect(function()
	mySecondPart.Transparency=0
end)

Objectif : Créer un part invisible qui apparaît lorsque le joueur clique sur un autre part avec un proximityPrompt :

Comment coder un ProximityPrompt sur le part

local myFirstPart = script.Parent
local proximity = myFirstPart.ProximityPrompt
local mySecondPart =myFirstPart.MySecondPart

proximity.Triggered:Connect(function(player)
	mySecondPart.Transparency=0
end)

Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Crée une plateforme mortelle

Objectif : Créer une part qui tue le joueur lorsqu’il la touche.

Instructions :

  1. Créer un part rouge : Ajoutez un part, colorez-le en rouge et utilisez le matériau « Néon ».
  1. Ajouter un script : Créez un script dans le part.
  2. Script de mort : Lorsque le joueur touche le part, il meurt.
    • Indices :
      • Utilisez [Nom du part].Touched:Connect(function(player) pour détecter le contact.
      • Utilisez character = player.Parent pour obtenir le personnage du joueur.
      • Utilisez humanoid = character and character:FindFirstChild("Humanoid") pour obtenir l’objet Humanoid du joueur.
      • Tuer le joueur : humanoid.Health = 0

Santé du joueur à 100 au départ du jeu :

print(humanoid.Health)

Réduire la santé du joueur :

humanoid.Health = humanoid.Health – 5
ou
humanoid:TakeDamage((5))

Le joueur meurt quand sa santé est à zéro :

humanoid.Health = 0

Un exemple de code avec une mort rapide :

local deadlyPart = script.Parent

deadlyPart.Touched:Connect(function(player)
	local character = player.Parent
	local humanoid = character and character:FindFirstChild("Humanoid")
	if humanoid then
		humanoid.Health = humanoid.Health - 5
	end
end)

Un exemple de code avec une mort lente :

local deadlyPart = script.Parent
local startTime = 0


deadlyPart.Touched:Connect(function(player)
	local character = player.Parent
	local humanoid = character and character:FindFirstChild("Humanoid")
	if humanoid then
		if startTime > 0 and (tick() - startTime) > 0.2 then
			startTime = 0
		end
		if startTime == 0 then
			print(humanoid.Health)
			humanoid.Health = humanoid.Health - 5
			startTime = tick()
		end
	end
end)
Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Crée un Chat au joueur

Comment gérer un message d’indication au joueur par un Chat :

Rajouter un ModuleScript ChatScript sous ReplicatedStorage :

Puis coder dans ce script :

local ChatScript = {}

function ChatScript.sendMessage(player, message)

    local character = player.Character or player.CharacterAdded:Wait()
    if character then
        local head = character:WaitForChild("Head") 
        if head then
            game:GetService("Chat"):Chat(head, message) 
        end
    end

end

return ChatScript

Rajouter dans votre script qui gère votre interface les instructions suivantes lorsque le joueur referme la fenêtre :

-- récupération du service de chat
local chat = require(game.ReplicatedStorage.ChatScript)

-- récupération du player dans un localScript
local player = game:GetService(« Players »).LocalPlayer

chat.sendMessage(player, "niveau : 1")
Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Crée un menu à ton joueur pour déplacer un part

Objectif : Créer un système pour déplacer une part à droite, à gauche, en haut, vers le bas.

Instructions :

  1. Créer une part : Ajoutez une part et renommez-la.
  2. Créer un « Screen Gui » : Dans « StarterGui », ajoutez un « Screen Gui » et placez une « Frame » à l’intérieur.
  3. Système d’ouverture/fermeture : Créez deux boutons pour ouvrir et fermer la « Frame ».
  4. Boutons de déplacement : Ajoutez quatres boutons pour déplacer la part à droite et à gauche, haut et bas.

Comment indiquer une ressource dans un script :

local openButton = script.Parent.QuitButton

Comment détecter un click de la souris sur un bouton :

openButton.MouseButton1Click:Connect(function()

end)

Programmation pour ouvrir ou fermer la fenêtre de contrôle dans un LocalScript :

local controFrame = script.Parent.Parent:WaitForChild("ControlFrame")
local openButton = script.Parent
local closeButton = controFrame.QuitButton

-- Ouverture de la fenêtre de contrôle
openButton.MouseButton1Click:Connect(function()
	controFrame.Visible = not controFrame.Visible
	openButton.Visible = false
end)

-- Fermeture de la fenêtre de contrôle	
closeButton.MouseButton1Click:Connect(function()
	controFrame.Visible = not controFrame.Visible
	openButton.Visible = true
end)


controFrame.Visible = false

Comment déplacer un objet Roblox :

myPart.Position += Vector3.new(10,0,0)

Exemple de code pour déplacer le part en modifiant ton localScript :

local controFrame = script.Parent.Parent:WaitForChild("ControlFrame")
local openButton = script.Parent
local closeButton = controFrame.QuitButton

local downButton = controFrame.DownButton
local leftButton = controFrame.LeftButton
local rightButton = controFrame.RightButton
local upButton = controFrame.UpButton

local myPart = game.Workspace:WaitForChild("MyPart")

-- Ouverture de la fenêtre de contrôle
openButton.MouseButton1Click:Connect(function()
	controFrame.Visible = not controFrame.Visible
	openButton.Visible = false
end)

-- Fermeture de la fenêtre de contrôle	
closeButton.MouseButton1Click:Connect(function()
	controFrame.Visible = not controFrame.Visible
	openButton.Visible = true
end)

-- Déplacement du part
leftButton.MouseButton1Click:Connect(function()
	myPart.Position += Vector3.new(-10,0,0)
end)

rightButton.MouseButton1Click:Connect(function()
	myPart.Position += Vector3.new(10,0,0)
end)

upButton.MouseButton1Click:Connect(function()
	myPart.Position += Vector3.new(0,2,0)
end)

downButton.MouseButton1Click:Connect(function()
	myPart.Position += Vector3.new(0,-2,0)
end)

controFrame.Visible = false
myPart.Anchored = true
Catégories
MICRO PYTHON MICRO:BIT

Créer une calculatrice

Objectif :

Réalisation d’un programme pour gérer une calculette.

Mission :

  • Utilise les boutons A ou B pour réveiller a calculette,
  • Choix des nombres de 0 à 9 avec le bouton A ou B,
  • Choix des opérateurs +,-,*,/,= avec le bouton A ou B,
  • Calcul par le choix de l’opérateur =, afficher les opérations puis le résultat
  • Un appui long permet l’arrêt la mise en veille

Etapes :

  1. Affichage d’un caractère
  2. Affichage des nombres et des opérateurs
  3. Attendre la saisie de la touche A ou B
  4. Affichage d’une liste de caractères et le choix
  5. Affichage d’un chiffre avec virgule avec la notion d’un ascenseur
  6. Calcul des différentes opérations
  7. Choix d’un nombre puis d’un opérateur
  8. Mémorisation des différentes opérations
  9. Calcul

Table des matières

  • Découverte des instructions à utiliser.
  • Le dictionnaire en python.
  • Les fonctions en Python.
  • Etape 1 Affichage d’un caractère.
  • Etape 2 Affichage des nombres.
  • Etape 3 Attendre la saisie de la touche A ou B.
  • Etape 4 Détection de la saisie du bouton A ou B.
  • Etape 5 Afficher une liste de caractères et choix d’un caractère.
  • Etape 6 Afficher un chiffre à virgule.
  • Etape 7 Calcul des différentes opérations arithmétiques de base.
  • Etape 8 Calculette choix de deux nombres et d’un opérateur.
  • Etape 9 Calculette mémorisation des opérations.
  • Etape 10 Affichage du résultat de l’opération.
  • Etape 11 Sonoriser ta calculette.

Découverte des instructions à utiliser

#Affichage d’une LED
from pystubit.board import display
display.set_pixel(0, 0, 9)
display.set_pixel(colonne de 0 à 4, ligne de 0 à 4,intensité de la LED de 0 à 9)
INTENSITE_LED=9
display.set_pixel(0, 0, INTENSITE _LED)

Une liste de données :

En Python, une liste est un type de données qui permet de stocker une collection ordonnée d’éléments, tels que des nombres, des chaînes de caractères ou d’autres objets. Les listes sont définies en utilisant des crochets [].

ceci_est_une_liste = [0,1,2,3,4]
#pour accéder à un élément de la liste
premier_element = ceci_est_une_liste[0]

#pour lire toute la liste
for ligne in ceci_est_un_liste:
    display.set_pixel(0, ligne, 9)

Une liste de liste :

une_liste_de_leds = [0,4,2,9,5]
ligne=0

for intensite in une_liste_de_leds:  
    display.set_pixel(0, ligne, intensite)
    ligne+=1

Le dictionnaire en python

Un dictionnaire en Python est un type de données qui permet de stocker des éléments sous forme de paires clé-valeur. Contrairement à la liste, les éléments dans un dictionnaire ne sont pas stockés dans un ordre particulier, mais plutôt associés à une clé unique qui permet d’y accéder facilement.

Les dictionnaires sont définis en utilisant des accolades {} et des paires clé-valeur séparées par des virgules. Par exemple, voici comment vous pouvez créer un dictionnaire pour stocker des informations :

from microbit import display

allume = {"OUI" :9,"NON" :0}

for colonne in range(5):
    for ligne in range(5):
        display.set_pixel(colonne, ligne, allume["OUI"])

Déclaration de la fonction :

Commence par déclarer la fonction en utilisant le mot-clé def, suivi du nom de la fonction et des parenthèses qui contiennent éventuellement des paramètres d’entrée. Par exemple :

def ma_fonction(parametre1, parametre2):
    # Addition de deux nombres
    # En paramètre deux nombres
    # Retourne la somme de deux nombres (optionnel)
    resultat = parametre1 + parametre2
    return resultat

nombre1=5
nombre2=2

print(nombre1,"+",nombre2,"=",ma_fonction(nombre1,nombre2))
#Bibliothèques à importer
from microbit import display

t_zero = [
    [0, 9, 9, 9, 0],
    [0, 9, 0, 9, 0],
    [0, 9, 0, 9, 0],
    [0, 9, 0, 9, 0],
    [0, 9, 9, 9, 0],
    ]

# Affichage d’un caractère en utilisant le tableau de LEDs
def affichage(caractere):
    num_ligne = 0
    # Affichage des lignes
    for ligne in caractere:
        num_colonne = 0
        # Affichage des colonnes
        for led in ligne:
            display.set_pixel(num_colonne, num_ligne, led)
            num_colonne += 1
        num_ligne += 1

affichage(t_zero)

Flasher pour tester puis bouton reset pour lancer

from microbit import display

#Tableaux pour afficher les nombres de 0 à 9
t_zero = [
    [0, 9, 9, 9, 0],
    [0, 9, 0, 9, 0],
    [0, 9, 0, 9, 0],
    [0, 9, 0, 9, 0],
    [0, 9, 9, 9, 0],
    ]

t_un = [
    [0, 0, 9, 0, 0],
    [0, 9, 9, 0, 0],
    [0, 0, 9, 0, 0],
    [0, 0, 9, 0, 0],
    [0, 9, 9, 9, 0],
    ]

t_deux = [
    [0, 9, 9, 0, 0],
    [9, 0, 0, 9, 0],
    [0, 0, 9, 0, 0],
    [0, 9, 0, 0, 0],
    [9, 9, 9, 9, 0],
    ]

t_trois = [
    [0, 9, 9, 0, 0],
    [0, 0, 0, 9, 0],
    [0, 9, 9, 9, 0],
    [0, 0, 0, 9, 0],
    [0, 9, 9, 0, 0],

]

t_quatre = [
    [0, 0, 9, 0, 0],
    [0, 9, 0, 0, 0],
    [9, 9, 9, 9, 0],
    [0, 0, 9, 0, 0],
    [0, 0, 9, 0, 0],
    ]

t_cinq = [
    [0, 9, 9, 9, 0],
    [0, 9, 0, 0, 0],
    [0, 9, 9, 9, 0],
    [0, 0, 0, 9, 0],
    [0, 9, 9, 0, 0],
]

t_six = [
    [0, 0, 9, 0, 0],
    [0, 9, 0, 0, 0],
    [9, 9, 9, 0, 0],
    [9, 0, 0, 9, 0],
    [0, 9, 9, 0, 0],
    ]

t_sept = [
    [0, 9, 9, 9, 0],
    [0, 0, 0, 9, 0],
    [0, 0, 9, 0, 0],
    [0, 0, 9, 0, 0],
    [0, 0, 9, 0, 0],
    ]

t_huit = [
    [0, 0, 9, 0, 0],
    [0, 9, 0, 9, 0],
    [0, 0, 9, 0, 0],
    [0, 9, 0, 9, 0],
    [0, 0, 9, 0, 0],
    ]

t_neuf = [
    [0, 9, 9, 9, 0],
    [0, 9, 0, 9, 0],
    [0, 9, 9, 9, 0],
    [0, 0, 0, 9, 0],
    [0, 9, 9, 0, 0],
    ]

# Tableaux pour afficher les opérateurs =,+,-,/,*

t_egal = [
    [0, 0, 0, 0, 0],
    [9, 9, 9, 9, 9],
    [0, 0, 0, 0, 0],
    [9, 9, 9, 9, 9],
    [0, 0, 0, 0, 0],
    ]

t_plus = [
    [0, 0, 9, 0, 0],
    [0, 0, 9, 0, 0],
    [9, 9, 9, 9, 9],
    [0, 0, 9, 0, 0],
    [0, 0, 9, 0, 0],
    ]

t_moins = [
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
    [9, 9, 9, 9, 9],
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
    ]

t_div = [
    [0, 0, 0, 0, 9],
    [0, 0, 0, 9, 0],
    [0, 0, 9, 0, 0],
    [0, 9, 0, 0, 0],
    [9, 0, 0, 0, 0],
    ]

t_multiple = [
    [9, 0, 0, 0, 9],
    [0, 9, 0, 9, 0],
    [0, 0, 9, 0, 0],
    [0, 9, 0, 9, 0],
    [9, 0, 0, 0, 9],
    ]

# Tableaux pour afficher un séparateur

t_point = [
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 9, 9, 9, 0],
    [0, 9, 9, 9, 0],
    [0, 9, 9, 9, 0],
]  

# Affichage d’un caractère

def affichage(caractere):
    num_ligne = 0
    for ligne in caractere:
        num_colonne = 0
        for led in ligne:
            display.set_pixel(num_colonne, num_ligne, led)
            num_colonne += 1
        num_ligne += 1

# Test pour afficher un caractère

affichage(t_huit)

Flasher pour tester puis bouton reset pour lancer

Peux-tu répondre à ce QCM sur la boucle FOR et les tableaux :

Quelle est la sortie du code suivant ?

tableau = [2, 4, 6, 8, 10]

for i in tableau:
    print(i)
  • A) 0 1 2 3 4
  • B) 2 4 6 8 10
  • C) 1 2 3 4 5
  • D) 0 2 4 6 8

Quelle est la sortie du code suivant ?

tableau = [3, 6, 9, 12, 15]

for i in range(len(tableau)):

    print(tableau[i])
  • A) 0 1 2 3 4
  • B) 3 6 9 12 15
  • C) 1 2 3 4 5
  • D) 0 3 6 9 12

Comment parcourir un tableau en utilisant une boucle for et l’index du tableau ?

A)

tableau = [5, 10, 15, 20]
for i in tableau:
    print(tableau[i])

B)

tableau = [5, 10, 15, 20]
for i in range(len(tableau)):
    print(tableau[i])

C)

tableau = [5, 10, 15, 20]
for i in range(tableau):
    print(tableau[i])

D)

tableau = [5, 10, 15, 20]
for i in tableau:
    print(i)

Quel est le résultat de ce code ?

tableau = [1, 2, 3, 4, 5]
total = 0

for i in tableau:
    total += i

print(total)

Comment ajouter tous les éléments d’un tableau en utilisant une boucle for ?

A)

tableau = [1, 2, 3, 4, 5]
total = 0
for i in tableau:
    total += tableau[i]
print(total)

B)

tableau = [1, 2, 3, 4, 5]
total = 0
for i in range(len(tableau)):
    total += tableau[i]
print(total)

C)

tableau = [1, 2, 3, 4, 5]
total = 0
for i in tableau:
    total += i
print(total)

D)

tableau = [1, 2, 3, 4, 5]
total = 0
for i in range(tableau):
    total += i
print(total)
#Tableau de tous les nombres# Tableau de la liste des chiffres de 0 à 9

chiffres = {
    0: t_zero,
    1: t_un,
    2: t_deux,
    3: t_trois,
    4: t_quatre,
    5: t_cinq,
    6: t_six,
    7: t_sept,
    8: t_huit,
    9: t_neuf,
    }

# Tableau de tous les opérateurs

operateurs = {"=": t_egal, "+": t_plus, "-": t_moins, "*": t_multiple, "/": t_div}

#Test pour afficher un nombre
affichage(chiffres[3])

Flasher pour tester puis bouton reset pour lancer

Peux-tu répondre à ce QCM sur l’utilisation des dictionnaires Python :

Quelle est la sortie du code suivant ?

dictionnaire = {"a": 1, "b": 2, "c": 3}
print(dictionnaire["a"])
  • A) « a »
  • B) 1
  • C) « b »
  • D) 2

Comment parcourir un dictionnaire en utilisant une boucle for ?

A)

dictionnaire = {"a": 1, "b": 2, "c": 3}
for cle in dictionnaire:
    print(cle, dictionnaire[cle])

B)

dictionnaire = {"a": 1, "b": 2, "c": 3}
for cle, valeur in dictionnaire.items():
    print(cle, valeur)

C)

dictionnaire = {"a": 1, "b": 2, "c": 3}
for cle in dictionnaire.keys():
    print(cle, dictionnaire[cle])

D) Toutes les réponses précédentes sont correctes

Etape 3 Attendre la saisie de la touche A ou B

Attente de l’appui du bouton A ou bouton B

# Bibliothèques à importer
from microbit import display, button_a, button_b
import time

#Test pour attendre l’appui du bouton A ou B ou time out
def attendre_boutonA_ou_boutonB(delai_attente = 0):
    start = time.ticks_ms()

    # Attente appui sur le bouton A ou bouton B ou au bout d'un time out
    while (not (button_a.is_pressed()) and
           not (button_b.is_pressed()) and
           ((delai_attente == 0) or ((time.ticks_ms()-start) < delai_attente *1000))):
        pass

    while button_a.is_pressed() or button_b.is_pressed():
        pass

affichage(chiffres[3])
attendre_boutonA_ou_boutonB(0)

affichage(chiffres[4])
attendre_boutonA_ou_boutonB(2)

affichage(chiffres[5])
attendre_boutonA_ou_boutonB(delai_attente = 2)

display.clear()

Flasher pour tester puis bouton reset pour lancer


Peux-tu répondre à ce QCM sur l’appel des fonctions Python :

Quelle est la sortie du code suivant ?

def fonction(a, b):
    return a + b
print(fonction(1, 2))
  • A) 1
  • B) 2
  • C) 3
  • D) Erreur

Comment définir une fonction avec un paramètre par défaut ? A)

def fonction(a = 1):
    return a

B)

def fonction(a):
    a = 1
    return a

C)

def fonction():
    a = 1
    return a

D) Toutes les réponses précédentes sont correctes

Comment appeler une fonction avec un argument nommé ?

A)

def fonction(a, b):
    return a + b
print(fonction(a = 1, b = 2))

B)

def fonction(a, b):
    return a + b
print(fonction(1, b = 2))

C)

def fonction(a, b):
    return a + b
print(fonction(a = 1, 2))

D) Toutes les réponses précédentes sont correctes

Comment définir une fonction avec un nombre variable d’arguments ?

A)

def fonction(*args):
    return args

B)

def fonction(args):
    return args

C)

def fonction(**args):
    return args

D) Toutes les réponses précédentes sont correctes

Etape 4 Détection de la saisie du bouton A ou B

Détection du bouton appuyé ou sur un délai d’attente

def attendre_boutonA_ou_boutonB(delai_attente = 0):

    button = "o"
    start = time.ticks_ms()

     # Attente appui sur le bouton A ou bouton B ou au bout d'un time out
    while (not (button_a.is_pressed()) and
           not (button_b.is_pressed()) and
           ((delai_attente == 0) or ((time.ticks_ms()-start) < delai_attente *1000))):
        pass

    if button_a.is_pressed():
        button = "a"

    if button_b.is_pressed():
        button = "b"

    while button_a.is_pressed() or button_b.is_pressed():
        pass

    return button

while True:
    # Test pour afficher un caractère
    affichage(chiffres[3])
    print(attendre_boutonA_ou_boutonB(2))
    display.clear()

Flasher pour tester puis bouton reset pour lancer

Vérifier que vous avez en retour de la fonction dans la fenêtre REPL :

  • a après l’appui du bouton A
  • b après l’appui du bouton B
  • après une attente de plus de 2 secondes

Détection d’un appui long sur la touche A ou B

def attendre_boutonA_ou_boutonB(delai_attente = 0, delai_appui_long = 1500):

    button = "o"
    start = time.ticks_ms()
    start_button = 0

     # Attente appui sur le bouton A ou bouton B ou au bout d'un time out
    while (not (button_a.is_pressed()) and
           not (button_b.is_pressed()) and
           ((delai_attente == 0) or ((time.ticks_ms()-start) < delai_attente *1000))):
        pass

    if button_a.is_pressed():
        button = "a"
        start_button = time.ticks_ms()

    if button_b.is_pressed():
        button = "b"
        start_button = time.ticks_ms()

    while button_a.is_pressed() or button_b.is_pressed():
        pass

    if (start_button > 0) and ((time.ticks_ms()-start_button) > delai_appui_long):
        button = button + "l"
    return button

while True:

    # Test pour afficher un caractère
    affichage(chiffres[3])
    print(attendre_boutonA_ou_boutonB(2))
    display.clear()

Flasher pour tester puis bouton reset pour lancer

Vérifier que vous avez en retour de la fonction dans la fenêtre REPL :

  • a après l’appui du bouton A
  • b après l’appui du bouton B
  • après une attente de plus de 2secondes
  • al après l’appui long sur le bouton A
  • bl après l’appui long sur le bouton B

Test de tous les nombres

nbr=0
while True:
    # Test pour afficher un caractère
    affichage(chiffres[nbr])
    print(attendre_boutonA_ou_boutonB(2))
    nbr += 1
    if nbr >= len(chiffres):
        nbr = 0

Flasher pour tester puis bouton reset pour lancer

En appuyant sur les boutons A ou B tu dois faire défiler les nombres de 0 à 9

Test de tous les opérateurs

nbr=0

# Mettre le dictionnaire des opérateurs en liste
operateurs_list = list(operateurs.items())

while True:
    # Test pour afficher un caractère
    # Utiliser la clé du dictionnaire pour afficher l'opérateur
    affichage(operateurs[operateurs_list[nbr][0]])
    print(attendre_boutonA_ou_boutonB(2))
    nbr += 1
    if nbr >= len(operateurs):
        nbr = 0

Flasher pour tester puis bouton reset pour lancer

En appuyant sur les boutons A ou B tu dois faire défiler les opérateurs de 0 à 9

L’utilisation de fonctions dans un code Python permet d’améliorer la visibilité et la maintenance du code. Voici quelques raisons pour lesquelles cela est vrai :

  1. Structuration du code : Les fonctions permettent de diviser le code en blocs logiques et fonctionnels, ce qui facilite la compréhension du code par les autres développeurs ou par soi-même plus tard. En regroupant les instructions qui effectuent une tâche spécifique dans une fonction, on peut rendre le code plus facile à lire et à comprendre.
  • Réutilisation du code : Les fonctions peuvent être réutilisées dans différentes parties du code, ce qui évite de dupliquer des instructions similaires à plusieurs endroits. Cela permet également de simplifier le code et de réduire les erreurs de copier-coller.
  • Facilité de maintenance : Les fonctions permettent de modifier le code plus facilement. Si une partie du code doit être modifiée, il suffit de modifier la fonction correspondante plutôt que de modifier plusieurs lignes de code dans différentes parties du programme. Cela permet de gagner du temps et de réduire les risques d’introduire de nouvelles erreurs.
  • Amélioration de la visibilité : Les fonctions peuvent être nommées de manière à décrire leur fonctionnalité, ce qui facilite la compréhension du code. En utilisant des noms de fonctions significatifs, on peut rendre le code plus facile à comprendre pour les autres développeurs ou pour soi-même plus tard.
  • Réduction des erreurs : Les fonctions permettent de réduire les erreurs en limitant la portée des variables et en évitant les conflits de noms. En utilisant des variables locales à l’intérieur d’une fonction, on peut éviter les conflits de noms avec des variables globales ou des variables d’autres fonctions.

En résumé, l’utilisation de fonctions permet de structurer le code, de faciliter la réutilisation et la maintenance du code, d’améliorer la visibilité et de réduire les erreurs. Il est donc recommandé d’utiliser des fonctions dans un code Python pour améliorer sa qualité et sa lisibilité.

Etape 5 Afficher une liste de caractères et choix d’un caractère

"""
Choix des chiffres ou des opérateurs
les boutons A et B pour faire défiler les caractères
Appui long sur A ou B pour valider le caractère
"""

def choix_caractere(caracteres, item=0, delai_attente=0, delai_appui_long=1500):
    button = ""
    caractereres_keys = list(caracteres.keys())
    try:
        item = caractereres_keys.index(item)
    except ValueError:
        item=0

    while True:
        if caractereres_keys[item] in caracteres:
            # Affichage du caratère
            affichage(caracteres[caractereres_keys[item]])
            # Attente bouton A ou B
            button = attendre_boutonA_ou_boutonB(delai_attente, delai_appui_long)
            # Défilement des caratères
            if button == "a":
                item -= 1
            if button == "b":
                item += 1
            # Appui long sur A ou B
            if button == "al" or button == "bl":
                return caractereres_keys[item]
            # Délai d'attente dépassé
            if button == "o":
                return None
        if item >= len(caractereres_keys):
            item = 0
        if item < 0:
            item = len(caractereres_keys) - 1

#Tester le défilement des chiffres               
while True:
    print(choix_caractere(chiffres, 0, 5, 1000))

#Tester le défilement des opérateurs
while True:
    print(choix_caractere(operateurs, 0, 5, 1000))

Flasher pour tester puis bouton reset pour lancer

QCM sur les différentes possibilités pour arrêter une boucle infinie while True: utilisée dans une fonction en Python :

Comment arrêter une boucle infinie en utilisant les instructions ?

  • A) En utilisant l’instruction continue
  • B) En utilisant l’instruction return
  • C) En utilisant l’instruction pass
  • D) En utilisant l’instruction break

Comment arrêter une boucle infinie en utilisant une variable de contrôle ?

  • A) En utilisant l’instruction continue
  • B) En utilisant l’instruction break
  • C) En utilisant une variable de contrôle
  • D) En utilisant l’instruction pass

Quelle instruction permet de sauter une itération dans une boucle ?

  • A) L’instruction break
  • B) L’instruction return
  • C) L’instruction pass
  • D) L’instruction continue
# Affichage d'un ascenceur pour les nombres de plusieurs chiffres
def barre_ascenceur(maxi, ascenceur):
    if maxi > 5:
        maxi = 5

    if ascenceur >= maxi:
        ascenceur = maxi - 1

    # Effacement 5éme colonne
    for ligne in range(5):
        display.set_pixel(4, ligne, 0)

    # Affichage de l'ascenceur
    display.set_pixel(4, ascenceur, 5)

#Affichage d’un nombre avec une virgule
# Affichage d’un nombre avec une virgule
def affichage_nombre(nombre, delai_attente=0, delai_appui_long=1500):
    # Convertir le nombre en une chaine de caractère
    nombreString = str(nombre)
    grandeur = len(nombreString)
    ascenceur = 0
    button = ""

    # Affichage du nombre jusqu'à appui long ou hors délai d'attente
    while (not (button == "al") and
           not (button == "bl") and
           not (button == "o")):

        # Affichage d'un caractère du nombre et de l'ascenceur
        if nombreString[ascenceur] == ".":
            affichage(t_point)
        elif nombreString[ascenceur] == "-":
            affichage(t_moins)
        else:
            affichage(chiffres[int(nombreString[ascenceur])])

        barre_ascenceur(grandeur, ascenceur)
        # Attente récation de l'utilisateur
        button = attendre_boutonA_ou_boutonB(delai_attente, delai_appui_long)
        if button == "a":
            ascenceur -= 1
        if button == "b":
            ascenceur += 1
        if ascenceur >= grandeur:
            ascenceur = 0
        if ascenceur < 0 :
            ascenceur = grandeur-1
    return button

# Tester un nombre avec deux chiffres, défilement de l’ascenseur avec les bouton A ou B
# Validation par un appui long               
while True:
    affichage_nombre(-123456.4,0)
    affichage_nombre(-987)
    affichage_nombre(-55.1234)

Etape 6 Afficher un chiffre à virgule

Flasher pour tester puis bouton reset pour lancer

Afficher un nombre avec plusieurs chiffres, avec éventuellement un signe moins, une virgule :

  • Faire défiler le nombre avec les touches A et B
  • Valider le nombre par un appui long sur A ou B
  • Vérifier la présence du signe moins – et la présence de la virgule .

Etape 7 Calcul des différentes opérations arithmétiques de base

# Calcul avec deux nombres
def calcul(nombre1, operateur="=", nombre2=0):
    resultat = 0
    if operateur == "=":
        resultat = nombre1
    if operateur == "+":
        resultat = nombre1 + nombre2
    if operateur == "-":
        resultat = nombre1 - nombre2
    if operateur == "*":
        resultat = nombre1 * nombre2
    if operateur == "/":
        if nombre2 > 0:
            resultat = nombre1 / nombre2
    return resultat

while True:
    # Initialiser
    resultat = calcul(2)
    affichage_nombre(resultat)
    # Additionner
    resultat = calcul(resultat, "+", 4)
    affichage_nombre(resultat)
    # Soustraire
    resultat = calcul(resultat, "-", 4)
    affichage_nombre(resultat)
    # Multiplier
    resultat = calcul(resultat, "*", 2)
    affichage_nombre(resultat)
    # Diviser
    resultat = calcul(resultat, "/", 2)
    affichage_nombre(resultat)
    # Division par zéro impossible
    resultat = calcul(resultat, "/", 0)
    affichage_nombre(resultat)

Flasher pour tester puis bouton reset pour lancer

Vérifier que le calcul est correct après chaque opération :

  • Faire défiler le résultat par les touches A et B
  • Valider le résultat par un appui long sur A ou B

Etape 8 Calculette choix de deux nombres et d’un opérateur

operateur = "+"
while True:
    nombre1 = choix_caractere(chiffres)
    operateur = choix_caractere(operateurs, operateur)
    nombre2 = choix_caractere(chiffres)
    resultat = calcul(nombre1, operateur, nombre2)
    affichage_nombre(resultat)

Flasher pour tester puis bouton reset pour lancer

Les opérateurs +, -, * et / sont respectivement appelés :

  • L’opérateur d’addition (+)
  • L’opérateur de soustraction (-)
  • L’opérateur de multiplication (*)
  • L’opérateur de division (/)

Ces opérateurs sont utilisés pour effectuer des opérations arithmétiques de base sur des nombres en Python. Par exemple :

# Addition
5 + 3  # Résultat : 8

# Soustraction
5 - 3  # Résultat : 2

# Multiplication
5 * 3  # Résultat : 15

# Division
5 / 3  # Résultat : 1.6666666666666667

Il est important de noter que l’opérateur de division (/) renvoie un résultat sous forme de nombre à virgule flottante, même si les nombres utilisés sont des entiers. Pour obtenir un résultat sous forme d’entier, il est possible d’utiliser l’opérateur de division entière (//) :

# Division entière
5 // 3  # Résultat : 1

L’opérateur de division entière renvoie le quotient entier de la division, en arrondissant vers le bas. Le reste de la division peut être obtenu en utilisant l’opérateur de modulo (%) :

# Modulo
5 % 3  # Résultat : 2

L’opérateur de modulo renvoie le reste de la division entière.

Etape 9 Calculette mémorisation des opérations

En Python, une liste est une structure de données qui permet de stocker une collection ordonnée d’éléments. Les éléments d’une liste peuvent être de n’importe quel type (entiers, chaînes de caractères, listes, etc.).

Pour ajouter des éléments à une liste en Python, on peut utiliser la méthode append(). La méthode append() ajoute un élément à la fin de la liste. Voici un exemple :

# Création d'une liste vide
ma_liste = []

# Ajout d'éléments à la liste
ma_liste.append(1)
ma_liste.append(2)
ma_liste.append(3)

# Affichage de la liste
print(ma_liste)  # Résultat : [1, 2, 3]

Dans cet exemple, on crée une liste vide ma_liste. On ajoute ensuite les éléments 1, 2 et 3 à la liste en utilisant la méthode append(). Enfin, on affiche la liste à l’aide de la fonction print(). Le résultat affiché est [1, 2, 3].

Il est également possible d’ajouter plusieurs éléments à une liste en utilisant la méthode extend(). La méthode extend() prend une liste en argument et ajoute tous les éléments de cette liste à la fin de la liste existante. Voici un exemple :

# Création d'une liste
ma_liste = [1, 2, 3]

# Ajout de plusieurs éléments à la liste
ma_liste.extend([4, 5, 6])

# Affichage de la liste
print(ma_liste)  # Résultat : [1, 2, 3, 4, 5, 6]

Dans cet exemple, on crée une liste ma_liste contenant les éléments 1, 2 et 3. On ajoute ensuite les éléments 4, 5 et 6 à la liste en utilisant la méthode extend(). Enfin, on affiche la liste à l’aide de la fonction print(). Le résultat affiché est [1, 2, 3, 4, 5, 6].

def saisie_operation():
    operation = []
    item = 0
    retour = 0

    operateur = "+"
    while ((retour is not None) and
            (retour != "=")):
        item += 1
        resultat = None
        # Saisie de l'opération nombre opérateur nombre ... jusqu'au égal =
        if item % 2 == 0:
            retour = choix_caractere(operateurs, operateur,
                                     delai_attente=60,
                                     delai_appui_long=700)

            if retour :
                operateur = retour
        else:
            retour = choix_caractere(chiffres,
                                     delai_attente=60,
                                     delai_appui_long=700)

        if retour :
            operation.append(retour)

    if retour :
        return operation
    else:      
        return None

while True:
    # Saisie de l’opération   
    print(saisie_operation())

Flasher pour tester puis bouton reset pour lancer et visualise le résultat dans REPL

  1. Fait le choix des nombres par les touches A et B puis valide le nombre par un appui long par les touches A ou B
  2. Puis fait le choix de l’opérateur de ton opération par les touches A et B puis valide par un appui long par les touches A ou B
  3. Valide ton calcul par le choix de l’opérateur « = » par un appui long sur A ou B
  4. Vérifie ton calcul dans la console REPL

Etape 10 Affichage du résultat de l’opération

def affichage_calcul(operation):
    resultat = 0
    if operation :
        item = 0
        for operande in operation:
            item += 1
            if item % 2 == 0:
                operateur = operande
                affichage(operateurs[operateur])
                attendre_boutonA_ou_boutonB(1)
            else:
                affichage(chiffres[operande])
                attendre_boutonA_ou_boutonB(1)
                # Calcul de l'opération
                if item == 1 :
                    resultat = calcul(operande)
                else:
                    resultat = calcul(resultat, operateur, operande)
    return resultat

button = ""   
while True:
    # Saisie de l'opération
    operation = saisie_operation()
    # Affichage de l'opération et calcul de l'opération
    if operation :
        # Affichage et calcul de l'opération
        resultat = affichage_calcul(operation)
        # Affichage du résultat
        button = affichage_nombre(resultat,
                         delai_attente=60,
                         delai_appui_long=700)

    if operation is None or button == "o" :
        # Mise en veille
        display.clear()
        attendre_boutonA_ou_boutonB()

Flasher pour tester puis bouton reset pour lancer

QCM sur les différentes possibilités d’ajouter des éléments dans une liste en Python :

Comment ajouter un élément à la fin d’une liste en Python ?

  • A) En utilisant la méthode add() 
  • B) En utilisant la méthode insert() 
  • C) En utilisant la méthode append() 
  • D) En utilisant la méthode extend()

Comment ajouter plusieurs éléments à la fin d’une liste en Python ?

  • A) En utilisant la méthode add() 
  • B) En utilisant la méthode insert() 
  • C) En utilisant la méthode append() 
  • D) En utilisant la méthode extend()

Comment ajouter un élément à une position spécifique dans une liste en Python ?

  • A) En utilisant la méthode add() 
  • B) En utilisant la méthode insert() 
  • C) En utilisant la méthode append() 
  • D) En utilisant la méthode extend()

Comment ajouter une liste à une autre liste en Python ?

  • A) En utilisant la méthode add() 
  • B) En utilisant la méthode insert() 
  • C) En utilisant la méthode append() 
  • D) En utilisant la méthode extend()

Comment ajouter un élément au début d’une liste en Python ?

  • A) En utilisant la méthode add() avec l’index 0.
  • B) En utilisant la méthode insert() avec l’index 0.
  • C) En utilisant la méthode append() avec l’index 0.
  • D) En utilisant la méthode extend()avec l’index 0.

Exemple pour comprendre :

liste1= [1,2,3,4]

liste2= [10,11,12,13]

liste1.append(6)

liste1.extend([7,8,9])

liste1.insert(4,5)

liste1.extend(liste2)

liste1.insert(0,0)

print(liste1)

Etape 11 Sonoriser ta calculette

La sonorisation des logiciels est un domaine de recherche qui vise à améliorer l’interaction entre l’utilisateur et le logiciel en utilisant des sons. Les sons peuvent être utilisés pour fournir des commentaires à l’utilisateur sur ses actions, pour signaler des événements importants ou pour renforcer la compréhension de l’interface utilisateur.

from microbit import display, button_a, button_b
import time
import music

def attendre_boutonA_ou_boutonB(delai_attente=0, delai_appui_long=1500):
    button = "o"
    start = time.ticks_ms()
    start_button = 0
    # Attente appui sur le bouton A ou bouton B ou au bout d'un time out
    while (
        not (button_a.is_pressed())
        and not (button_b.is_pressed())
        and ((delai_attente == 0) or ((time.ticks_ms() - start) < delai_attente * 1000))
    ):
        pass

    if button_a.is_pressed():
        button = "a"
        start_button = time.ticks_ms()

    if button_b.is_pressed():
        button = "b"
        start_button = time.ticks_ms()

    premierefois = True

    while button_a.is_pressed() or button_b.is_pressed():
        if ((time.ticks_ms() - start_button) > delai_appui_long):
            music.pitch(200, duration=40)
        else:
            if premierefois:
                music.pitch(500, duration=20)
                premierefois = False

    if (start_button > 0) and ((time.ticks_ms() - start_button) > delai_appui_long):
        button = button + "l"
        return button

    return button

Flasher pour tester puis bouton reset pour lancer

Voici quelques raisons pour lesquelles la sonorisation des logiciels peut être importante pour améliorer l’utilisation par l’humain des logiciels :

  1. Fournir des commentaires auditifs : Les sons peuvent être utilisés pour fournir des commentaires auditifs à l’utilisateur sur ses actions. Par exemple pour confirmer l’appui sur un bouton.
  2. Renforcer la compréhension de l’interface utilisateur : Les sons peuvent être utilisés pour renforcer la compréhension de l’interface utilisateur en fournissant des indices auditifs sur la fonctionnalité des différents éléments de l’interface. Par exemple lors de la confirmation d’un appui long sur un bouton.
  3. Signaler des événements importants : Les sons peuvent être utilisés pour signaler des événements importants dans le logiciel. Par exemple sur une erreur de saisie, indique par exemple l’erreur de la division par zéro.
  4. Améliorer l’accessibilité : Les sons peuvent être utilisés pour améliorer l’accessibilité des logiciels pour les personnes malvoyantes ou non voyantes. Les sons peuvent être utilisés pour fournir des commentaires auditifs sur les actions de l’utilisateur, pour décrire les éléments de l’interface utilisateur et pour signaler des événements importants. Les sons peuvent également être utilisés pour fournir une alternative auditive aux informations visuelles. Essaye de rendre la calculette utilisable par une personne non voyante ?

En résumé, la sonorisation des logiciels peut aider à améliorer l’interaction entre l’utilisateur et le logiciel en fournissant des commentaires auditifs, en renforçant la compréhension de l’interface utilisateur, en signalant des événements importants et en améliorant l’accessibilité. Les sons peuvent être utilisés pour fournir des informations supplémentaires à l’utilisateur, pour renforcer la compréhension de l’interface utilisateur et pour améliorer l’expérience utilisateur globale.

# Bibliothèques à importer
from microbit import display, button_a, button_b
import time

t_zero = [
    [0, 9, 9, 9, 0],
    [0, 9, 0, 9, 0],
    [0, 9, 0, 9, 0],
    [0, 9, 0, 9, 0],
    [0, 9, 9, 9, 0]]

t_un = [
    [0, 0, 9, 0, 0],
    [0, 9, 9, 0, 0],
    [0, 0, 9, 0, 0],
    [0, 0, 9, 0, 0],
    [0, 9, 9, 9, 0]]

t_deux = [
    [0, 9, 9, 0, 0],
    [9, 0, 0, 9, 0],
    [0, 0, 9, 0, 0],
    [0, 9, 0, 0, 0],
    [9, 9, 9, 9, 0]]

t_trois = [
    [0, 9, 9, 0, 0],
    [0, 0, 0, 9, 0],
    [0, 9, 9, 9, 0],
    [0, 0, 0, 9, 0],
    [0, 9, 9, 0, 0]]

t_quatre = [
    [0, 0, 9, 0, 0],
    [0, 9, 0, 0, 0],
    [9, 9, 9, 9, 0],
    [0, 0, 9, 0, 0],
    [0, 0, 9, 0, 0]]

t_cinq = [
    [0, 9, 9, 9, 0],
    [0, 9, 0, 0, 0],
    [0, 9, 9, 9, 0],
    [0, 0, 0, 9, 0],
    [0, 9, 9, 0, 0]]

t_six = [
    [0, 0, 9, 0, 0],
    [0, 9, 0, 0, 0],
    [9, 9, 9, 0, 0],
    [9, 0, 0, 9, 0],
    [0, 9, 9, 0, 0]]

t_sept = [
    [0, 9, 9, 9, 0],
    [0, 0, 0, 9, 0],
    [0, 0, 9, 0, 0],
    [0, 0, 9, 0, 0],
    [0, 0, 9, 0, 0]]

t_huit = [
    [0, 0, 9, 0, 0],
    [0, 9, 0, 9, 0],
    [0, 0, 9, 0, 0],
    [0, 9, 0, 9, 0],
    [0, 0, 9, 0, 0]]

t_neuf = [
    [0, 9, 9, 9, 0],
    [0, 9, 0, 9, 0],
    [0, 9, 9, 9, 0],
    [0, 0, 0, 9, 0],
    [0, 9, 9, 0, 0]]

# Tableaux pour afficher les opérateurs =,+,-,/,*
t_egal = [
    [0, 0, 0, 0, 0],
    [9, 9, 9, 9, 9],
    [0, 0, 0, 0, 0],
    [9, 9, 9, 9, 9],
    [0, 0, 0, 0, 0]]

t_plus = [
    [0, 0, 9, 0, 0],
    [0, 0, 9, 0, 0],
    [9, 9, 9, 9, 9],
    [0, 0, 9, 0, 0],
    [0, 0, 9, 0, 0]]

t_moins = [
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
    [9, 9, 9, 9, 9],
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0]]

t_div = [
    [0, 0, 0, 0, 9],
    [0, 0, 0, 9, 0],
    [0, 0, 9, 0, 0],
    [0, 9, 0, 0, 0],
    [9, 0, 0, 0, 0]]

t_multiple = [
    [9, 0, 0, 0, 9],
    [0, 9, 0, 9, 0],
    [0, 0, 9, 0, 0],
    [0, 9, 0, 9, 0],
    [9, 0, 0, 0, 9]]

# Tableaux pour afficher un séparateur
t_point = [
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 9, 9, 9, 0],
    [0, 9, 9, 9, 0],
    [0, 9, 9, 9, 0]]

# Tableau de tous les nombres# Tableau de la liste des chiffres de 0 à 9
chiffres = {
    0: t_zero,
    1: t_un,
    2: t_deux,
    3: t_trois,
    4: t_quatre,
    5: t_cinq,
    6: t_six,
    7: t_sept,
    8: t_huit,
    9: t_neuf,
    }

# Tableau de tous les opérateurs
operateurs = {"=": t_egal, "+": t_plus, "-": t_moins, "*": t_multiple, "/": t_div}

# Affichage d’un caractère en utilisant le tableau de LEDs
def affichage(caractere):
    num_ligne = 0
    # Affichage des lignes
    for ligne in caractere:
        num_colonne = 0
        # Affichage des colonnes
        for led in ligne:
            display.set_pixel(num_colonne, num_ligne, led)
            num_colonne += 1
        num_ligne += 1

# Test pour attendre l’appui du bouton A ou B ou time out
def attendre_boutonA_ou_boutonB(delai_attente=0,
        delai_appui_long=1500):
    button = "o"
    start = time.ticks_ms()
    start_button = 0

    # Attente appui sur le bouton A ou bouton B ou au bout d'un time out
    while (not (button_a.is_pressed()) and
           not (button_b.is_pressed()) and
           ((delai_attente == 0) or
           ((time.ticks_ms() - start) < delai_attente * 1000))):
        pass

    if button_a.is_pressed():
        button = "a"
        start_button = time.ticks_ms()

    if button_b.is_pressed():
        button = "b"
        start_button = time.ticks_ms()

    while button_a.is_pressed() or button_b.is_pressed():
        pass

    if (start_button > 0) and ((time.ticks_ms()-start_button) > delai_appui_long):
        button = button + "l"

    return button

"""
Choix des chiffres ou des opérateurs
les boutons A et B pour faire défiler les caractères
Appui long sur A ou B pour valider le caractère
"""

def choix_caractere(caracteres,
        item=0,
        delai_attente=0,
        delai_appui_long=1500):

    button = ""
    caractereres_keys = list(caracteres.keys())
    try:
        item = caractereres_keys.index(item)
    except ValueError:
        item = 0

    while True:
        if caractereres_keys[item] in caracteres:
            # Affichage du caratère
            affichage(caracteres[caractereres_keys[item]])
            # Attente bouton A ou B
            button = attendre_boutonA_ou_boutonB(delai_attente, delai_appui_long)
            # Défilement des caratères
            if button == "a":
                item -= 1
            if button == "b":
                item += 1
            # Appui long sur A ou B
            if button == "al" or button == "bl":
                return caractereres_keys[item]
            # Délai d'attente dépassé
            if button == "o":
                return None

        if item >= len(caractereres_keys):
            item = 0
        if item < 0:
            item = len(caractereres_keys) - 1

# Affichage d'un ascenceur pour les nombres de plusieurs chiffres
def barre_ascenceur(maxi, ascenceur):
    if maxi > 5:
        maxi = 5

    if ascenceur >= maxi:
        ascenceur = maxi - 1

    # Effacement 5éme colonne
    for ligne in range(5):
        display.set_pixel(4, ligne, 0)

    # Affichage de l'ascenceur
    display.set_pixel(4, ascenceur, 5)

# Affichage d’un nombre avec une virgule
def affichage_nombre(nombre,
    delai_attente=0,
    delai_appui_long=1500):

    # Convertir le nombre en une chaine de caractère
    nombreString = str(nombre)
    grandeur = len(nombreString)
    ascenceur = 0
    button = ""

    # Affichage du nombre jusqu'à appui long ou hors délai d'attente
    while (not (button == "al") and
           not (button == "bl") and
           not (button == "o")):

        # Affichage d'un caractère du nombre et de l'ascenceur
        if nombreString[ascenceur] == ".":
            affichage(t_point)
        elif nombreString[ascenceur] == "-":
            affichage(t_moins)
        else:
            affichage(chiffres[int(nombreString[ascenceur])])

        barre_ascenceur(grandeur, ascenceur)

        # Attente récation de l'utilisateur
        button = attendre_boutonA_ou_boutonB(delai_attente, delai_appui_long)
        if button == "a":
            ascenceur -= 1
        if button == "b":
            ascenceur += 1
        if ascenceur >= grandeur:
            ascenceur = 0
        if ascenceur < 0 :
            ascenceur = grandeur-1

    return button

# Calcul avec deux nombres
def calcul(nombre1, operateur="=", nombre2=0):

    resultat = 0
    if operateur == "=":
        resultat = nombre1

    if operateur == "+":
        resultat = nombre1 + nombre2

    if operateur == "-":
        resultat = nombre1 - nombre2

    if operateur == "*":
        resultat = nombre1 * nombre2

    if operateur == "/":
        if nombre2 > 0:
            resultat = nombre1 / nombre2

    return resultat

def saisie_operation():
    operation = []
    item = 0
    retour = 0
    operateur = "+"
    while ((retour is not None) and
            (retour != "=")):
        item += 1
        resultat = None

        # Saisie de l'opération nombre opérateur nombre ... jusqu'au égal =
        if item % 2 == 0:
            retour = choix_caractere(operateurs, operateur,
                                     delai_attente=60,
                                     delai_appui_long=700)
            if retour :
                operateur = retour
        else:
            retour = choix_caractere(chiffres,
                                     delai_attente=60,
                                     delai_appui_long=700)
        if retour :
            operation.append(retour)

    if retour :
        return operation
    else:      
        return None

def affichage_calcul(operation):
    resultat = 0
    if operation :
        item = 0
        for operande in operation:
            item += 1
            if item % 2 == 0:
                operateur = operande
                affichage(operateurs[operateur])
                attendre_boutonA_ou_boutonB(1)
            else:
                affichage(chiffres[operande])
                attendre_boutonA_ou_boutonB(1)
                # Calcul de l'opération
                if item == 1 :
                    resultat = calcul(operande)
                else:
                    resultat = calcul(resultat, operateur, operande)

    return resultat

button = ""   
while True:
    # Saisie de l'opération
    operation = saisie_operation()
    # Affichage de l'opération et calcul de l'opération
    if operation :
        # Affichage et calcul de l'opération
        resultat = affichage_calcul(operation)
        # Affichage du résultat
        button = affichage_nombre(resultat,
                         delai_attente=60,
                         delai_appui_long=700)

    if operation is None or button == "o" :
        # Mise en veille
        display.clear()
        attendre_boutonA_ou_boutonB()
Catégories
MICRO PYTHON MICRO:BIT

Eclairage vélo

Objectif :

Réalisation d’un programme pour gérer un éclairage arrière personnalisable d’un vélo.

Tu apprendras l’utilisation des boucles For, l’importation des bibliothèques, l’utilisation des tableaux (liste) en python.

Mission :

  • Affichage d’une animation fixe.
  • Affichage d’une animation dynamique
  • Accélération de l’animation par le bouton A
  • Allumer ou éteindre l’animation par le bouton B

A découvrir avant de commencer : l’instruction For

En Python, la boucle for est utilisée pour itérer sur une séquence d’éléments tels qu’une liste, une chaîne de caractères, un tuple, un dictionnaire, etc. La boucle for exécute un bloc de code pour chaque élément de la séquence, jusqu’à ce que tous les éléments aient été parcourus.

for element in sequence:

    # Bloc de code à exécuter

Une explication pour chaque élément de la syntaxe :

  • Le mot-clé for est utilisé pour déclarer la boucle for.
  • element est une variable qui représente l’élément en cours d’itération dans la séquence. Tu peux choisir n’importe quel nom valide pour cette variable.
  • Le mot-clé in est utilisé pour spécifier la séquence sur laquelle itérer.
  • sequence est la séquence (liste, chaîne de caractères, tuple, etc.) sur laquelle la boucle for va itérer.
  • Le bloc de code qui suit le : est indenté et sera exécuté pour chaque élément de la séquence.

Voici un exemple simple pour illustrer son fonctionnement :

liste = [1, 2, 3, 4, 5]

for nombre in liste:

    print(nombre)  # Affiche chaque nombre de la liste

Utilise les indications suivantes pour réaliser ton premier programme. Reproduit le code en caractère GRAS avec l’icône « REPL »

Dans cet exemple, la boucle for itère sur chaque élément de la liste liste. À chaque itération, la variable nombre prend la valeur de l’élément en cours d’itération, puis le bloc de code indenté est exécuté, qui dans ce cas affiche le nombre. La boucle for se répète jusqu’à ce que tous les éléments de la liste aient été parcourus.

Il est également courant d’utiliser la fonction range() en conjonction avec la boucle for pour itérer sur une séquence de nombres. Par exemple :

for i in range(1, 6):

    print(i)  # Affiche les nombres de 1 à 5

Dans cet exemple, range(1, 6) génère une séquence de nombres de 1 à 5 (la borne supérieure spécifiée est exclusive). La boucle for itère sur chaque nombre de cette séquence, assigne la valeur à la variable i, puis exécute le bloc de code indenté.

QCM sur l’instruction FOR

Question 1 : Quelle est la principale utilité de l’instruction « for » en programmation ?

  1. Déclarer une fonction
  2. Répéter une séquence d’instructions un nombre fixe de fois
  3. Gérer les exceptions
  4. Établir une condition logique

Question 2 : Comment se structure généralement une boucle « for » en Python ?

  1. for (int i = 0; i < 5; i++)
  2. for i in range(5):
  3. loop (i = 0; i < 5; i++)
  4. repeat i in range(5):

Question 3 : Quelle est la fonction de la fonction intégrée range() utilisée fréquemment avec une boucle « for » en Python ?

  1. Générer une liste de nombres
  2. Calculer la moyenne d’une série de nombres
  3. Vérifier la présence d’un élément dans une liste
  4. Créer une chaîne de caractères

Question 4 : Comment peut-on sortir prématurément d’une boucle « for » en utilisant l’instruction « break » ?

  1. break for
  2. break loop
  3. exit for
  4. break

Question 5 : Quel mot-clé est utilisé pour passer à l’itération suivante dans une boucle « for » sans exécuter le reste du code à l’intérieur de la boucle ?

  1. pass
  2. skip
  3. continue
  4. next

Mu est un éditeur de code spécialement conçu pour les microcontrôleurs et plus particulièrement la carte micro :bit.

Utilise le menu « Mode » dans Mu pour choisir « BBC micro ».

Ainsi, tu pourras flasher directement ton programme sur la carte micro.

Choisis de créer un nouveau fichier pour ton programme en cliquant sur « + nouveau », puis demande à enregistrer ton fichier sous le nom de ton choix en utilisant l’option « Enregistrer ».

from microbit import display

Une bibliothèque en Python est comme une boîte à outils magique pleine de fonctions spéciales que tu peux utiliser pour rendre ton programme plus puissant. Plutôt que de tout créer depuis zéro, tu peux simplement emprunter des outils déjà créés par d’autres personnes.

Pour utiliser une bibliothèque, tu dois l’importer dans ton programme. C’est comme si tu vas à la bibliothèque emprunter un livre pour le lire. En Python, nous utilisons le mot clé import pour faire cela.

Tu vas utiliser deux bibliothèques une pour gérer ton robot et une autre pour gérer le temps.

#Double boucle pour afficher l’éclairage

for c in range(0, 5, 1):
for l in range(0, 5, 1):
      display.set_pixel(c, l, 9)

 Flasher puis bouton reset pour lancer puis vérifie que toutes les LEDs s’affichent

from microbit import display
import time
#Boucle sur la colonne pour afficher une LED
for c in range(0, 5, 1):
    #Boucle sur la ligne pour afficher une LED
    for l in range(0, 5, 1):
        #Attente de 300 millisecondes avant affichage de la LED
        time.sleep_ms(300)
        #Affichage de la LED
        display.set_pixel(c, l, 9)

Flasher puis bouton reset puis regarde comment fonctionne les LEDs

Amuse-toi en changeant les valeurs des boucles !!!

  • for c in range(2, 5, 1):
  • for c in range(0, 3, 1):
  • for c in range(0, 5, 2):
  • for l in range(2, 5, 1):
  • for l in range(0, 3, 1):
  • for l in range(0, 5, 2):

 Flasher puis bouton reset entre chaque essai

Question 1 : Comment peut-on importer une bibliothèque entière en Python ?

  1. import microbit
  2. include microbit
  3. import mocrobit as lib
  4. import microbit

Question 2 : Quelle est la méthode recommandée pour importer uniquement une fonction spécifique d’une bibliothèque en Python ?

  1. import display from microbit
  2. from microbit import display
  3. include display from microbit
  4. import microbit.display

Question 3 : Comment peut-on vérifier l’existence d’une bibliothèque dans l’éditeur MU ?

  1. Vérification manuelle dans le répertoire Python
  2. Import library, puis vérification avec la fonction library.check()
  3. Utiliser la commande pip show library dans le terminal
  4. Vérification avec le bouton du menu « REPL » puis saisir import microbit

Question 4 : Comment connaitre toutes les fonctions d’une bibliothèque dans l’éditeur MU ?

  1. List microbit.Image
  2. print microbit.Image
  3. dir(microbit.Image)
  4. show(microbit.Image )
from microbit import display, button_a, button_b

while not(button_a.is_pressed()) and not(button_b.is_pressed()):
            pass

while button_a.is_pressed() or button_b.is_pressed():
            pass

L’instruction while permet de répéter un ensemble d’instructions tant qu’une condition donnée est vraie. Elle est très utile lorsque tu as besoin d’exécuter le même code à plusieurs reprises sans avoir à le réécrire plusieurs fois.

while condition:
    # Code à exécuter tant que la condition est vraie

#Effacement de l’écran
display.clear()

Flasher puis bouton reset et vérifie que l’écran s’éteint après l’appui sur le bouton A ou B

display.set_pixel(…) et button_a.is_pressed() sont des objets de la bibliothèque microbit La première permet d’allumer ou éteindre une LED, la deuxième permet de contrôler les boutons A et B

L’instruction print est un outil précieux pour le débogage en programmation. Elle permet d’afficher des informations spécifiques à un moment donné de l’exécution du programme, ce qui permet de comprendre ce qui se passe dans ton code. Voici pourquoi elle est utile et comment l’utiliser pour le débogage :

Intérêt d’utiliser l’instruction print pour le débogage :

  1. Visualisation des données : La principale utilité de print est d’afficher des valeurs de variables, des résultats intermédiaires, ou des messages pour comprendre comment votre programme évolue pendant son exécution.
  2. Vérification des états : Vous pouvez utiliser print pour vérifier l’état des variables à différents points de votre code, ce qui te permet de confirmer que les valeurs sont correctes ou de repérer des erreurs potentielles.
  3. Traçage de l’exécution : En insérant des déclarations print aux différentes étapes de votre programme, tu peux « tracer » l’exécution du code et suivre son flux pour identifier les erreurs ou les incohérences.

Un exemple :

a = 10
print("La valeur de 'a' est :", a)

Tu veux rendre l’éclairage arrière de ton vélo super cool et dynamique !

Pour cela, tu veux créer un programme avec la carte micro:bit. Voici ce que ton éclairage puisse faire :

  1. Choisir ton Animation : Tu veux pouvoir appuyer sur le bouton A pour changer le style de l’éclairage. Chaque fois que tu appuies, l’éclairage passe à une nouvelle animation dynamique.
  2. Mettre en Veille : Si tu veux éteindre l’éclairage (peut-être quand tu es à l’arrêt ou que tu n’en as pas besoin), tu appuies sur le bouton B.
  3. Automatique : si la lumière du jour est trop forte ou si tu n’as pas bougé pendant un moment, l’éclairage doit se mette en veille tout seul pour économiser.

Maintenant, imagine les animations magiques que tu veux ! Ça pourrait être des clignotements, des pulsations ou même un effet de fondu. Quand tu as une idée, tu peux réaliser ton programme pour rendre ton vélo encore plus génial !

Objectif de la Mission : Programmer ton éclairage pour allumer toutes les LEDs en une seule fois.

#Bibliothèques à utiliser
from microbit import display, button_a, button_b
import time

Une fonction en programmation est une manière de regrouper un ensemble d’instructions pour effectuer une tâche spécifique.

def ma_fonction():
    # Corps de la fonction

C’est ainsi tu écris et utilises des fonctions en Python. Elles te permettent d’organiser ton code, de le rendre plus lisible et de le rendre plus réutilisable.

#Fonction pour l'affichage du tableau de LEDs
def affichage():
    for c in range(0, 5, 1):
        for l in range(0, 5, 1):
            time.sleep_ms(300)
            display.set_pixel(c, l, 9)

while True:
    #Appel de la fonction d'affichage
    affichage()
    #Attente de l'appui du bouton A ou B
    while not(button_a.is_pressed()) and not(button_b.is_pressed()):
        pass
    while button_a.is_pressed() or button_b.is_pressed():
        pass

    #Effacement de l'écran
    display.clear()

 Flasher puis bouton reset et constate l’animation.

En Python, les tableaux sont appelés des listes, et ils permettent de stocker une collection ordonnée d’éléments. Les listes peuvent contenir des valeurs de différents types tels que des entiers, des flottants, des chaînes de caractères, et même d’autres listes.

Création d’une liste : Pour créer une liste, tu utilises des crochets [] puis tu sépares les éléments de la liste par des virgules. Par exemple :

liste = [1, 2, 3, 4, 5]

Accès aux éléments d’une liste : Les éléments d’une liste sont accessibles par leur indice, qui commence à 0 pour le premier élément. Tu peux utiliser cet indice entre crochets [] pour accéder à un élément spécifique. Par exemple :

premier_element = liste[0]  # Accès au premier élément
deuxieme_element = liste[1]  # Accès au deuxième élément
print(premier_element)
print(deuxieme_element)

Modification d’un élément de liste : Les éléments d’une liste sont mutables, ce qui signifie qu’ils peuvent être modifiés. Tu peux assigner une nouvelle valeur à un élément spécifique en utilisant son indice. Par exemple :

liste[0] = 10  # Modification du premier élément
print(liste[0])

Longueur d’une liste : Tu peux obtenir la taille (le nombre d’éléments) d’une liste en utilisant la fonction len(). Par exemple :

taille = len(liste)  # Renvoie la taille de la liste
print(taille)

Opérations sur les listes : Les listes en Python offrent de nombreuses opérations utiles, telles que l’ajout d’éléments avec append(), l’insertion d’éléments avec insert(), la suppression d’éléments avec remove() ou del, la concaténation de listes avec +, etc. Tu peux également utiliser des boucles pour itérer sur les éléments d’une liste.

Voici un exemple illustrant certains de ces concepts :

liste = [1, 2, 3, 4, 5]  # Création d'une liste
print(liste[0])  # Affiche le premier élément (1)

liste[2] = 10  # Modification du troisième élément
print(len(liste))  # Affiche la taille de la liste (5)

liste.append(6)  # Ajout d'un nouvel élément (6) à la fin de la liste

for element in liste:
    print(element)  # Affiche chaque élément de la liste

print(len(liste))  # Affiche la taille de la liste (6)

Objectif de la Mission : Programmer ton éclairage pour allumer toutes les LEDs avec l’aide d’un tableau python !

Tu vass utiliser un tableau contenant des chiffres qui correspondent à l’état des LEDs. Ainsi, quand tu changes ces chiffres, tu contrôles la lumière de ton vélo !

from microbit import display, button_a, button_b
import time

# Tableau pour décrire le tableau des LEDs
affichageFixe_1 = [[9, 7, 5, 7, 9],
                   [7, 1, 1, 1, 7],
                   [5, 1, 9, 1, 5],
                   [7, 1, 1, 1, 7],
                   [9, 7, 5, 7, 9]]

# Fonction pour l'affichage du tableau de LEDs
def affichage(animation,vitesse=300):
    l = 0
    for ligne in animation:
        c = 0
        for led in ligne:
            time.sleep_ms(vitesse)
            display.set_pixel(c, l, led)
            c+= 1
        l+= 1

while True:
    # Appel de la fonction d'affichage
    affichage(affichageFixe_1,50)
    # Attente de l'appui du bouton A ou B
    while not(button_a.is_pressed()) and not(button_b.is_pressed()):
        pass

    while button_a.is_pressed() or button_b.is_pressed():
        pass

    # Effacement de l'écran
    display.clear()

 Flasher puis bouton reset et change les valeurs dans le tableau : affichageFixe_1 !!

Objectif de la Mission : Programmer plusieurs tableaux pour créer une animation dynamique !

from microbit import display, button_a, button_b
import time

# Tableau pour décrire le tableau des LEDs
affichageFixe_1 = [[9, 7, 5, 7, 9],
                   [7, 1, 1, 1, 7],
                   [5, 1, 9, 1, 5],
                   [7, 1, 1, 1, 7],
                   [9, 7, 5, 7, 9]]

affichageFixe_2 = [[0, 0, 9, 0, 0],
                   [0, 0, 9, 0, 0],
                   [9, 9, 9, 9, 9],
                   [0, 0, 9, 0, 0],
                   [0, 0, 9, 0, 0]]

affichageFixe_3 = [[9, 0, 0, 0, 9],
                   [0, 9, 0, 9, 0],
                   [0, 0, 9, 0, 0],
                   [0, 9, 0, 9, 0],
                   [9, 0, 0, 0, 9]]

affichageDynamique = [affichageFixe_1,
                      affichageFixe_2,
                      affichageFixe_3]

# Fonction pour l'affichage du tableau de LEDs
def affichage(animation, vitesse=300):
    l = 0
    for ligne in animation:
        c = 0
        for led in ligne:
            time.sleep_ms(vitesse)
            display.set_pixel(c, l, led)
            c += 1
        l += 1
animation = 0

while True:
    # Appel de la fonction d'affichage
    affichage(affichageDynamique[animation], 10)
    # Attente de l'appui du bouton A ou B
    while not(button_a.is_pressed()) and not(button_b.is_pressed()):
        pass

    while button_a.is_pressed() or button_b.is_pressed():
        pass

    # Effacement de l'écran
    display.clear()
    animation += 1

    if animation >= (len(affichageDynamique)):
        animation = 0

 Flasher puis bouton reset et créer ton affichage dynamique !! 

Objectif de la Mission : Gère dans ton tableau d’animation des temps pour l’affichage des LEDs pour des animations spectaculaires !

affichageDynamique = [[affichageFixe_1, 10],
                      [affichageFixe_2, 200],
                      [affichageFixe_3, 500]]

def affichage(animation, vitesse=300):
    l = 0
    for ligne in animation:
        c = 0
        for led in ligne:
            if led > 0 :
                time.sleep_ms(vitesse)
            display.set_pixel(c, l, led)
            c += 1
        l += 1

et dans le corps principal du programme :

    affichage(affichageDynamique[animation][0],affichageDynamique[animation][1]) 

Flasher puis bouton reset

Question 1 : Quel est le terme utilisé en Python pour désigner une structure de données ordonnée qui peut contenir différents types d’éléments ?

  1. Ensemble
  2. Liste
  3. Dictionnaire
  4. Tuple

Question 2 : Comment créer une liste vide en Python ?

  1. list()
  2. empty_list()
  3. []
  4. new List()

Question 3 : Comment accéder au deuxième élément d’une liste en Python (en supposant que l’index commence à 0) ?

  1. list[1]
  2. list(2)
  3. list[2]
  4. list.second()

Question 4 : Comment déclarer une liste de listes ?

  1. [[],[],[]…]
  2. {(),(),()…}
  3. new List(new List, new List …)
  4. ((),(),()…)

Question 5 : Comment accéder au deuxième élément d’une liste en Python qui est elle-même dans la troisième occurrence d’une liste (en supposant que les index des listes commencent à 0) ?

  1. List[2][1]
  2. list(2,1)
  3. list[3][2]
  4. list[3,1]

Objectif de la Mission : Programme l’enchainement dynamique des tableaux lumineux en utilisant la bibliothèque time et en créant un chronomètre de temps pour gérer la durée d’affichage d’un écran.

# Départ du chronomètre       
def start_chronometre(temps):
    global start
    global chrono
    start = time.ticks_ms()
    chrono = temps

# Si temps du chronomètre écoulé   
def chronometre():
    if time.ticks_ms() - start > chrono :
        return True
    else:
        return False

animation = 0
while True:
    # Appel de la fonction d'affichage
    affichage(affichageDynamique[animation][0], affichageDynamique[animation][1])
    start_chronometre(1000)

    # Attente de l'appui du bouton A ou B ou le chronometre
    while (not (button_a.is_pressed()) and
           not (button_b.is_pressed()) and
           not (chronometre())):
        pass

    while button_a.is_pressed() or button_b.is_pressed():
        pass

    # Effacement de l'écran
    display.clear()
    animation += 1
    if animation >= (len(affichageDynamique)):
        animation = 0

 Flasher puis bouton reset

Les variables globales en programmation avec Python sont des variables qui sont définies en dehors de toute fonction et qui peuvent être utilisées à n’importe quel endroit du programme. Elles sont accessibles de manière globale, d’où leur nom.

Tu peux utiliser des variables globales lorsque tu as besoin de partager des données entre plusieurs parties de ton programme et que ces données ont une signification globale. Cependant, évite de les utiliser pour des données temporaires ou des valeurs qui ne sont pertinentes que dans des parties spécifiques de ton code. Cela aidera à rendre ton code plus lisible et à éviter des erreurs potentielles.

Si la même information est utilisée dans plusieurs fonctions, une variable globale peut être pratique. Cela évite de passer la valeur en paramètre à chaque appel de fonction.

Si tu as une valeur constante qui reste la même tout au long du programme, tu peux la définir comme une variable globale plutôt que de la répéter à chaque fois dans différentes fonctions.

Objectif de la Mission : Programme pour que le temps d’affichage d’un écran soit variable et que l’on puisse le programmer dans le tableau d’affichage dynamique.

affichageDynamique = [[affichageFixe_1, 10,1000],
                      [affichageFixe_2, 200,5000],
                      [affichageFixe_3, 500,10000]]

 start_chronometre(affichageDynamique[animation][2])

 Flasher puis bouton reset

Objectif de la Mission : Donne la possibilité de créer plusieurs animations et que l’utilisateur puisse changer d’animation avec le bouton A.

"""
Enchainement de plusieurs affichages fixes :
 - affichage fixe
 - temps de l'affichage des LEDs de l'affichage fixe
 - temps entre chaque affichage fixe
"""

affichageDynamique_1 = [[affichageFixe_1, 10, 1000],
   [affichageFixe_2, 200, 5000],
   [affichageFixe_3, 500, 10000]]

affichageDynamique_2 = [[affichageFixe_3, 2, 100],
   [affichageFixe_2, 2, 100]]

affichageDynamique_3 = [[affichageFixe_1, 2, 1000],
   [affichageFixe_2, 2, 1000]]

# Liste des affichages dynamiques
affichageDynamique = [affichageDynamique_1, affichageDynamique_2, affichageDynamique_3]

animation = 0
choix = 0
while True:
    # Appel de la fonction d'affichage
    affichage(affichageDynamique[choix][animation][0],
              affichageDynamique[choix][animation][1])
    start_chronometre(affichageDynamique[choix][animation][2])

    # Attente de l'appui du bouton A ou B ou le chronometre
    while (not (button_a.is_pressed()) and
           not (button_b.is_pressed()) and
           not (chronometre())):
        pass

    # Changement du choix de l'animation dynamique
    if button_a.is_pressed():
        choix += 1
        animation = 0

    if choix >= (len(affichageDynamique)):
        choix = 0

    # Attente du bouton relâché
    while button_a.is_pressed() or button_b.is_pressed():
        pass

    # Effacement de l'écran
    display.clear()

    # Progression de l'animation de l'affichage dynamique
    animation += 1
    if animation >= (len(affichageDynamique[choix])):
        animation = 0

 Flasher puis bouton reset

Objectif de la Mission : Par le bouton B, l’utilisateur met le dispositif en veille, puis par l’appui du bouton  A ou B l’utilisateur réveille le dispositif.

# Mise en veille du dispositif attendre un appui sur les touches A ou B
def mettre_en_veille():
    display.clear()
    # Attentre que les boutons A et B soient relachés
    while button_a.is_pressed() or button_b.is_pressed():
        pass

    # Attentre l’appui d’un bouton A ou A pour le réveil du dispositif
    while (not (button_a.is_pressed()) and
           not (button_b.is_pressed())):
        pass

animation = 0
choix = 0
mettre_en_veille()

while True:
    # Appel de la fonction d'affichage
    affichage(affichageDynamique[choix][animation][0],
              affichageDynamique[choix][animation][1])

    start_chronometre(affichageDynamique[choix][animation][2])

    # Attente de l'appui du bouton A ou B ou le chronometre
    while (not (button_a.is_pressed()) and
           not (button_b.is_pressed()) and
           not (chronometre())):
        pass

    # Changement du choix de l'animation dynamique
    if button_a.is_pressed():
        choix += 1
        animation = 0

    if choix >= (len(affichageDynamique)):
        choix = 0

    # Fermeture des animations par l'appui du bouton B
    if button_b.is_pressed():
        mettre_en_veille()

    # Attente du bouton relaché
    while button_a.is_pressed() or button_b.is_pressed():
        pass

    # Effacement de l'écran
    display.clear()

    # Progression de l'animation de l'affichage dynamique
    animation += 1
    if animation >= (len(affichageDynamique[choix])):
        animation = 0

Objectif de la Mission : Le dispositif se met automatiquement en veille lorsque la luminosité dépasse un seuil de la luminosité du jour.

La carte MICRO :BIT est équipée de plusieurs capteurs, dont un capteur de luminosité. Le capteur de luminosité sur la carte micro:bit est constitué d’une photorésistance (capteur de lumière) qui mesure l’intensité de la lumière réfléchie.

Voici comment vous pouvez utiliser le capteur de luminosité sur la carte micro:bit en programmation avec MicroPython :

from microbit import *

while True:
    # Lire la valeur de luminosité du capteur
    luminosite = display.read_light_level()

    # Afficher la valeur de luminosité sur l'écran LED
    display.scroll("Lum: " + str(luminosite))

    # Pause avant la prochaine mesure
    sleep(1000)

# Initialisation varaible pour la luminosité
def init_luminosite():
    global chrono_luminosite
    chrono_luminosite = time.ticks_ms()       

# Détection si la luminosité actuelle est supérieure à celle de la luminosité du jour
def lec_luminosite():
    global chrono_luminosite
    if display.read_light_level() < LUMINOSITE_JOUR:
        chrono_luminosite = time.ticks_ms()

# Si depuis un certain temps dans la luminosité du jour
def si_luminosite():
    if (chrono_luminosite and
       (time.ticks_ms() - chrono_luminosite > (DELAI_LUMINOSITE*1000))):
        return True
    else:
        return False

# Mise en veille du dispositif attendre un appui sur les touches A ou B
def mettre_en_veille():
    display.clear()
    # Attentre que les boutons A et B soient relachés
    while button_a.is_pressed() or button_b.is_pressed():
        pass

    # Attentre l’appui d’un bouton A ou A pour le réveil du dispositif
    while (not (button_a.is_pressed()) and
           not (button_b.is_pressed())):
        pass

    init_luminosite()

LUMINOSITE_JOUR = 100
DELAI_LUMINOSITE = 2
animation = 0
choix = 0
mettre_en_veille()

while True:
    # Appel de la fonction d'affichage
    affichage(affichageDynamique[choix][animation][0],
              affichageDynamique[choix][animation][1])
    start_chronometre(affichageDynamique[choix][animation][2])

    # Test de la luminosité
    lec_luminosite()

    # Attente de l'appui du bouton A ou B ou le chronometre
    while (not (button_a.is_pressed()) and
           not (button_b.is_pressed()) and
           not (chronometre())):
        pass

    # Changement du choix de l'animation dynamique
    if button_a.is_pressed():
        choix += 1
        animation = 0

    if choix >= (len(affichageDynamique)):
        choix = 0

    # Fermeture des animations par l'appui du bouton B
    if button_b.is_pressed():
        mettre_en_veille()

    # Si luminosité du jour depuis un certain temps alors mise en veille

    if si_luminosite():
        mettre_en_veille()

    # Attente du bouton relaché
    while button_a.is_pressed() or button_b.is_pressed():
        pass

    # Effacement de l'écran
    display.clear()

    # Progression de l'animation de l'affichage dynamique
    animation += 1
    if animation >= (len(affichageDynamique[choix])):
        animation = 0
Catégories
Non classé UNIHIKER

Trouver une zone de couleur dans une image avec OpenCV

À quoi ça sert ?

Sur les parcours de nos compétitons ROBOCUP à identifier des zones de couleurs et les lignes noires !

Pourquoi ne pas inventer un robot qui va réagir à des feux de circulation ?

Préparer ton atelier

  1. Utilise MU Python sur ton poste de travail.
  1. Installe si besoin le package opencv-python
pip install opencv-python numpy
  1. Crée un dossier avec les images suivantes pour test (par exemple image08.png) qui contient des zones rouges.

Les concepts à connaître avant de commencer

Un pixel, c’est quoi ?

Une image, c’est une grille de minuscules carrés de couleur : les pixels. Comme un jeu de bataille navale, chaque pixel a une ligne (position verticale) et une colonne (position horizontale).

BGR : la recette de la couleur

Chez OpenCV, chaque pixel est mélangé comme un pot de peinture avec 3 doses : Bleu, Vert (Green), Rouge — attention, c’est BGR chez OpenCV, pas RGB ! Chaque dose va de 0 (rien) à 255 (le maximum).

  • Rouge pur = (0, 0, 255)
  • Blanc = (255, 255, 255)
  • Gris moyen = (127, 127, 127)

HSV : la couleur vue par un peintre

Le BGR, c’est la recette chimique. Le HSV, c’est comme un humain décrit une couleur :

  • H = Hue (la teinte : « c’est du rouge ? du vert ? »)
  • S = Saturation (la force : « flashy ou délavé ? »)
  • V = Value (la luminosité : « clair ou sombre ? »)

Pourquoi HSV ? Parce qu’un « rouge » peut être clair, sombre, vif ou pâle. Avec HSV, on peut dire « toutes les teintes entre 0 et 10 » et ça attrape TOUS les rouges, quel que soit leur éclairage !

Piège OpenCV : la teinte H va de 0 à 179 (pas 360°), et S et V de 0 à 255.

Le cercle des teintes (rouges coupés en deux !)

La teinte est un cercle : le rouge est à cheval sur le début (H≈0) et la fin (H≈179). C’est pour ça qu’on cherche le rouge avec deux plages : 0-10 et 170-179.

Rouge:   0→10    et   170→179
Jaune:  20→35
Vert:   40→85

Étape 0 : La version « brute de décoffrage » (tout dans un seul bloc)

Le premier programme fait tout d’un coup : il charge une image, la réduit, parcourt tous les pixels un par un, encadre la zone rouge et dessine une flèche. Juste pour comprendre comment fonctionne une image !

Chargement d’une image avec opencv :

import cv2
# Chargement de l'image
img = cv2.imread("C:/Users/patri/Pictures/image01.png",cv2.IMREAD_COLOR)
# Réduire la taille de l'image
img = cv2.resize(img,(0,0),fx=0.3,fy=0.3)
# Eventuellement touner l'image
#img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)


cv2.imshow("image",img)
# attente de l'appui d'une touche
cv2.waitKey(0)
# destruction des fenêtres
cv2.destroyAllWindows()

Cloner l’image initial pour visualiser une image cible :

import cv2
# Chargement de l'image
img = cv2.imread("C:/Users/patri/Pictures/image01.png",cv2.IMREAD_COLOR)
# Réduire la taille de l'image
img = cv2.resize(img,(0,0),fx=0.3,fy=0.3)
# Eventuellement touner l'image
#img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)

# Copier l'image dans une image cible pour voir le résultat
img_cible =img.copy()
# Dimansion de l'image
print(img.shape)

# affichage de l'image initiale et du résultat de la couleur rouge
cv2.imshow("image",img)
cv2.imshow("image cible",img_cible)
# attente de l'appui d'une touche
cv2.waitKey(0)
# destruction des fenêtres
cv2.destroyAllWindows()

Détection d’une zone rouge et contour pour visualiser :

import cv2
# Chargement de l'image
img = cv2.imread("C:/Users/patri/Pictures/image01.png",cv2.IMREAD_COLOR)
# Réduire la taille de l'image
img = cv2.resize(img,(0,0),fx=0.3,fy=0.3)
# Eventuellement touner l'image
#img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)

# Copier l'image dans une image cible pour voir le résultat
img_cible =img.copy()
# Dimansion de l'image
print(img.shape)
lignes, colonnes, colors = img.shape
l1=0
c1=0
l2=0
c2=0
# seuil pour retrouver la couleur
seuil = 80

# analyse de l'image pour retrouver la couleur rouge
for ligne in range(lignes):
    for colonne in range(colonnes) :
        if img[ligne][colonne][2] > img[ligne][colonne][0]+seuil and img[ligne][colonne][2] > img[ligne][colonne][1]+seuil:
            if l1 == 0 :
                l1 = ligne
                c1 = colonne
            l2 = ligne
            c2 = colonne

# dessin d'un rectangle autour de la couleur rouge
cv2.rectangle(img_cible,(c1,l1),(c2,l2),(0,255,0),4)

# affichage de l'image initiale et du résultat de la couleur rouge
cv2.imshow("image",img)
cv2.imshow("image cible",img_cible)
# attente de l'appui d'une touche
cv2.waitKey(0)
# destruction des fenêtres
cv2.destroyAllWindows()

Elimination des zones trop petites :

import cv2
# Chargement de l'image
img = cv2.imread("C:/Users/patri/Pictures/image01.png",cv2.IMREAD_COLOR)
# Réduire la taille de l'image
img = cv2.resize(img,(0,0),fx=0.3,fy=0.3)
# Eventuellement touner l'image
#img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)

# Copier l'image dans une image cible pour voir le résultat
img_cible =img.copy()
# Dimansion de l'image
print(img.shape)
lignes, colonnes, colors = img.shape
l1=0
c1=0
l2=0
c2=0
# seuil pour retrouver la couleur
seuil = 80

# analyse de l'image pour retrouver la couleur rouge
for ligne in range(lignes):
    for colonne in range(colonnes) :
        if img[ligne][colonne][2] > img[ligne][colonne][0]+seuil and img[ligne][colonne][2] > img[ligne][colonne][1]+seuil:
            if l1 == 0 :
                l1 = ligne
                c1 = colonne
            l2 = ligne
            c2 = colonne
        else:
            # élimination des petites zones rouges
            if l1 > 0 and ligne == l2 + 1 and colonne == c2:
                if (l2-l1) * (c2-c1) < 2000 :
                     l1=0

# dessin d'un rectangle autour de la couleur rouge
cv2.rectangle(img_cible,(c1,l1),(c2,l2),(0,255,0),4)

# affichage de l'image initiale et du résultat de la couleur rouge
cv2.imshow("image",img)
cv2.imshow("image cible",img_cible)
# attente de l'appui d'une touche
cv2.waitKey(0)
# destruction des fenêtres
cv2.destroyAllWindows()

Affichage d’une flèche pour indiquer la direction vers la zone :

import cv2
# Chargement de l'image
img = cv2.imread("C:/Users/patri/Pictures/image01.png",cv2.IMREAD_COLOR)
# Réduire la taille de l'image
img = cv2.resize(img,(0,0),fx=0.3,fy=0.3)
# Eventuellement touner l'image
#img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)

# Copier l'image dans une image cible pour voir le résultat
img_cible =img.copy()
# Dimansion de l'image
print(img.shape)
lignes, colonnes, colors = img.shape
l1=0
c1=0
l2=0
c2=0
# seuil pour retrouver la couleur
seuil = 80

# analyse de l'image pour retrouver la couleur rouge
for ligne in range(lignes):
    for colonne in range(colonnes) :
        if img[ligne][colonne][2] > img[ligne][colonne][0]+seuil and img[ligne][colonne][2] > img[ligne][colonne][1]+seuil:
            if l1 == 0 :
                l1 = ligne
                c1 = colonne
            l2 = ligne
            c2 = colonne
        else:
            # élimination des petites zones rouges
            if l1 > 0 and ligne == l2 + 1 and colonne == c2:
                if (l2-l1) * (c2-c1) < 2000 :
                     l1=0
# dessin d'un rectangle autour de la couleur rouge
cv2.rectangle(img_cible,(c1,l1),(c2,l2),(0,255,0),4)

# affichage d'une flêche vers le zone de couleur rouge
m = (c1 + c2) // 2
if m < colonnes//2 :
    m = (colonnes // 2) - m
    cv2.arrowedLine(img_cible, (colonnes // 2,lignes-10), ((colonnes // 2)-m,lignes-10), (0,255,0),4)
else:
    m = m -(colonnes // 2)
    cv2.arrowedLine(img_cible, (colonnes // 2,lignes-10), ((colonnes // 2)+m,lignes-10), (0,255,0),4)

# affichage de l'image initiale et du résultat de la couleur rouge
cv2.imshow("image",img)
cv2.imshow("image cible",img_cible)
# attente de l'appui d'une touche
cv2.waitKey(0)
# destruction des fenêtres
cv2.destroyAllWindows()

Ce que fait le code

img = cv2.imread(".../image08.png", cv2.IMREAD_COLOR)
img = cv2.resize(img, (0,0), fx=0.7, fy=0.7)
  • imread charge l’image en mémoire.
  • resize la réduit à 70 % (fx=0.7, fy=0.7) : moins de pixels = programme plus rapide !
lignes, colonnes, colors = img.shape

img.shape donne les dimensions : combien de lignes, de colonnes, et combien de couches de couleur (3 pour BGR).

La chasse au rouge pixel par pixel

for ligne in range(lignes):
    for colonne in range(colonnes):
        if img[ligne][colonne][2] > img[ligne][colonne][0] + seuil \
        and img[ligne][colonne][2] > img[ligne][colonne][1] + seuil:

Pour chaque pixel, on compare les 3 doses BGR : si le canal Rouge (index [2]) dépasse largement le Bleu ([0]) et le Vert ([1]) (au moins de seuil = 80), alors c’est un pixel rouge !

On mémorise les coordonnées extrêmes de la zone : l1, c1 (le premier pixel rouge trouvé = coin en haut à gauche) et l2, c2 (le dernier = coin en bas à droite). On peut alors tracer un rectangle avec cv2.rectangle, et une flèche (cv2.arrowedLine) qui indique si la zone est à gauche ou à droite du milieu de l’image.

Les défauts de cette version

  • Deux boucles for imbriquées sur des milliers de pixels = très lent.
  • La comparaison « R > B + 80 » est fragile : ça se dérègle dès que la lumière change.
  • Le rectangle englobe tous les pixels rouges : si tu as 2 zones rouges séparées, il les relie par un seul grand cadre.
  • Tout est mélangé : impossible de réutiliser une partie du code ailleurs.

L’objectif des étapes suivantes : réécrire ce programme proprement, comme un pro ! On va ranger le code dans des fonctions — des petites machines, chacune avec un seul rôle.


Étape 1 : Charger et afficher l’image (le squelette du programme)

Ici on construit la structure du programme : les paramètres en haut (comme le sommaire d’une recette), et une fonction load_image() qui a un seul job : charger l’image.

import cv2
import numpy as np

# ============================================================
# PARAMÈTRES (constantes)
# ============================================================
IMAGE_PATH = "C:/Users/patri/Pictures/image08.png"
# IMAGE_PATH = "C:/Users/patri/Pictures/Screenshots/163159.png"

RESIZE_FACTOR = 0.5          # Facteur de redimensionnement de l'image
ROTATE_IMAGE = False         # Mettre True pour tourner l'image de 90° (sens horaire)

def load_image(path, resize_factor, rotate):
    """Charge l'image, la redimensionne et la tourne si nécessaire."""
    image = cv2.imread(path, cv2.IMREAD_COLOR)
    if image is None:
        raise FileNotFoundError(f"Impossible de charger l'image : {path}")

    image = cv2.resize(image, (0, 0), fx=resize_factor, fy=resize_factor)
    if rotate:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
    print("Dimension : ",image.shape)
    return image


def main():

    image = load_image(IMAGE_PATH, RESIZE_FACTOR, ROTATE_IMAGE)

    # Affichage
    cv2.imshow("image", image)

    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == "__main__":
    main()

Les paramètres au sommet

IMAGE_PATH = "C:/Users/patri/Pictures/image08.png"
RESIZE_FACTOR = 0.5
ROTATE_IMAGE = False

Tout ce qui peut changer est regroupé en haut du fichier, en MAJUSCULES. Tu veux tester une autre image ? Tu changes une seule ligne, et tu ne risques pas de casser le reste.

La fonction load_image()

def load_image(path, resize_factor, rotate):
    image = cv2.imread(path, cv2.IMREAD_COLOR)
    if image is None:
        raise FileNotFoundError(f"Impossible de charger l'image : {path}")
    image = cv2.resize(image, (0, 0), fx=resize_factor, fy=resize_factor)
    if rotate:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
    return image

Une fonction, c’est comme une machine dans une usine : elle reçoit des matières premières (le chemin, le facteur, la rotation) et renvoie un produit fini (l’image prête à analyser).

L’astuce du pro : if image is None attrape l’erreur classique « faute de frappe dans le chemin » : au lieu d’un crash mystérieux plus loin, on affiche un message clair.

Le chef d’orchestre : main()

def main():
    image = load_image(IMAGE_PATH, RESIZE_FACTOR, ROTATE_IMAGE)
    cv2.imshow("image", image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

main() ne fait presque rien lui-même : il appelle les fonctions dans le bon ordre. Et if __name__ == "__main__": signifie « c’est ce fichier qui est lancé » — c’est le drapeau de départ du programme !

Test : tu dois voir ta photo apparaître dans une fenêtre. Appuie sur une touche pour fermer (waitKey(0) attend un appui, destroyAllWindows ferme les fenêtres).


Étape 2 : Le masque binaire —  » rouge ou pas rouge ? « 

On passe à la recherche moderne : plus de double boucle ! On convertit l’image en HSV, puis on fabrique un masque : une image en noir et blanc où les pixels de la couleur cherchée sont blancs et tout le reste noir.

import cv2
import numpy as np

# ============================================================
# PARAMÈTRES (constantes)
# ============================================================
IMAGE_PATH = "C:/Users/patri/Pictures/image08.png"
# IMAGE_PATH = "C:/Users/patri/Pictures/Screenshots/163159.png"

RESIZE_FACTOR = 0.5          # Facteur de redimensionnement de l'image
ROTATE_IMAGE = False         # Mettre True pour tourner l'image de 90° (sens horaire)

SHOW_MASKS = True            # Afficher une fenêtre de masque par couleur analysée
# --- Choix des couleurs à analyser : "red", "yellow", "green" ---
# Exemples : ["red"]  |  ["red", "green"]  |  ["red", "yellow", "green"]
COLORS_TO_ANALYZE = ["red"]
MORPH_KERNEL_SIZE = 5        # Taille du noyau pour nettoyer les masques
# --- Plages HSV par couleur (OpenCV : H de 0 à 179, S et V de 0 à 255) ---
# Chaque couleur a une liste de plages (min, max) car le rouge est à cheval
# sur le début et la fin du cercle des teintes.
SATURATION_MIN = 100         # Saturation minimale (exclut les couleurs délavées)
VALUE_MIN = 70               # Luminosité minimale (exclut les zones trop sombres)

HSV_RANGES = {
    "red": [
        ((0, SATURATION_MIN, VALUE_MIN), (10, 255, 255)),     # Rouge proche de 0°
        ((170, SATURATION_MIN, VALUE_MIN), (179, 255, 255)),  # Rouge proche de 180°
    ]
}

def load_image(path, resize_factor, rotate):
    """Charge l'image, la redimensionne et la tourne si nécessaire."""
    image = cv2.imread(path, cv2.IMREAD_COLOR)
    if image is None:
        raise FileNotFoundError(f"Impossible de charger l'image : {path}")

    image = cv2.resize(image, (0, 0), fx=resize_factor, fy=resize_factor)
    if rotate:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
    print("Dimension : ",image.shape)
    return image


def build_color_mask(hsv, color_name, kernel_size):
    """Crée le masque binaire d'une couleur à partir de l'image HSV."""
    mask = np.zeros(hsv.shape[:2], np.uint8)

    # On fusionne toutes les plages de la couleur (ex : 2 plages pour le rouge)
    for lower, upper in HSV_RANGES[color_name]:
        mask |= cv2.inRange(hsv, np.array(lower), np.array(upper))

    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # Ouverture : supprime les petits pixels isolés (bruit)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    # Fermeture : comble les petits trous à l'intérieur des zones
    return cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

def analyze_colors(image, colors):
    """Analyse chaque couleur demandée. Retourne (résultats, masques)."""
    # La conversion HSV n'est faite qu'une seule fois pour toutes les couleurs
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

    masks = {}
    for color_name in colors:
        masks[color_name] = build_color_mask(hsv, color_name, MORPH_KERNEL_SIZE)
    return masks


def main():

    image = load_image(IMAGE_PATH, RESIZE_FACTOR, ROTATE_IMAGE)

    # Analyse des couleurs choisies
    masks = analyze_colors(image, COLORS_TO_ANALYZE)
    # Affichage
    cv2.imshow("image", image)
    if SHOW_MASKS:
        for color_name, mask in masks.items():
            cv2.imshow(f"mask {color_name}", mask)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == "__main__":
    main()

Fabriquer le masque avec inRange

hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, np.array(lower), np.array(upper))

cv2.inRange est un détecteur de métaux géant : il passe sur chaque pixel et demande « ta teinte est-elle entre 0 et 10, ta saturation au-dessus de 100, ta luminosité au-dessus de 70 ? » Si oui → pixel blanc (255), sinon → noir (0). Et il fait ça pour toute l’image d’un coup, des centaines de fois plus vite que nos boucles !

Le rouge, en deux plages

HSV_RANGES = {
    "red": [
        ((0, 100, 70), (10, 255, 255)),      # rouge proche de H=0
        ((170, 100, 70), (179, 255, 255)),   # rouge proche de H=179
    ],
}

Souviens-toi : le rouge est à cheval sur le début et la fin du cercle des teintes, donc il faut deux plages. Le symbole |= fusionne les deux résultats : « blanc si trouvé dans l’une OU l’autre plage ».

Décryptage des valeurs HSV

Chaque couleur a deux bornes : (min) et (max). Chaque borne contient 3 nombres : H, S, V. cv2.inRange garde tous les pixels dont les 3 valeurs sont entre la borne min et la borne max.

Les 3 nombres
LettreNomFourchette OpenCVCe qu’elle mesure
HHue (teinte)0 à 179 ⚠️Laquelle couleur c’est : rouge, jaune, vert…
SSaturation0 à 255La force : 0 = délavé/gris, 255 = très vif
VValue (luminosité)0 à 255La lumière : 0 = noir, 255 = très clair

Piège classique : dans le monde réel la teinte va de 0 à 360°, mais OpenCV la divise par 2 → 0 à 179.

Lecture ligne par ligne

pythonCopier

((0, 100, 70), (10, 255, 255))

Borne min (0, 100, 70) : teinte ≥ 0, saturation ≥ 100 (on ignore les couleurs délavées/grises), luminosité ≥ 70 (on ignore les zones presque noires).
Borne max (10, 255, 255) : teinte ≤ 10, saturation et luminosité au maximum.

Traduction : « toutes les teintes de 0 à 10, à condition d’être assez vives et assez éclairées ».

((170, 100, 70), (179, 255, 255))

Même chose, mais pour les teintes de 170 à 179.

Pourquoi le rouge a besoin de 2 plages ?

La teinte est un cercle (comme l’arc-en-ciel refermé sur lui-même). Le rouge pur est à H = 0… mais aussi juste à côté, à H = 179 (la fin du cercle touche le début !). Un rouge légèrement orangé ou rosé peut donc tomber d’un côté ou de l’autre. Solution : deux plages, fusionnées avec |= (« OU »).

        0  rouge
   179        10
 168            20 jaune
 magenta         35
   90  cyan  85  40
        vert
À retenir
  • S ≥ 100 : élimine le gris et les couleurs « pastel » — pratique pour ignorer le fond d’une photo.
  • V ≥ 70 : élimine les ombres et le noir.
  • S et V max à 255 = on accepte toutes les intensités au-dessus du minimum.

Le nettoyage : la morphologie

Un vrai masque a du bruit : des pixels blancs isolés (grain de poussière) et des trous dans les zones. On nettoie avec un petit carré (le noyau, kernel) qu’on promène sur le masque :

mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)   # OUVERTURE : gomme les points isolés
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)  # FERMETURE : bouche les petits trous
  • Ouverture = on enlève les tâches plus petites que le noyau (érosion puis dilatation).
  • Fermeture = on comble les fissures plus petites que le noyau (dilatation puis érosion).

Test : une fenêtre mask red s’affiche : ta zone rouge apparaît en blanc sur fond noir. Si rien n’est blanc, baisse SATURATION_MIN ou VALUE_MIN !


Étape 3 : Plusieurs couleurs à la fois (rouge, jaune, vert)

On agrandit simplement le dictionnaire HSV_RANGES — pas besoin de toucher au reste du code, c’est la magie de la structure en fonctions !

import cv2
import numpy as np

# ============================================================
# PARAMÈTRES (constantes)
# ============================================================
IMAGE_PATH = "C:/Users/patri/Pictures/image08.png"
# IMAGE_PATH = "C:/Users/patri/Pictures/Screenshots/163159.png"

RESIZE_FACTOR = 0.5          # Facteur de redimensionnement de l'image
ROTATE_IMAGE = False         # Mettre True pour tourner l'image de 90° (sens horaire)

SHOW_MASKS = True            # Afficher une fenêtre de masque par couleur analysée
# --- Choix des couleurs à analyser : "red", "yellow", "green" ---
# Exemples : ["red"]  |  ["red", "green"]  |  ["red", "yellow", "green"]
COLORS_TO_ANALYZE = ["red", "yellow", "green"]
MORPH_KERNEL_SIZE = 5        # Taille du noyau pour nettoyer les masques
# --- Plages HSV par couleur (OpenCV : H de 0 à 179, S et V de 0 à 255) ---
# Chaque couleur a une liste de plages (min, max) car le rouge est à cheval
# sur le début et la fin du cercle des teintes.
SATURATION_MIN = 100         # Saturation minimale (exclut les couleurs délavées)
VALUE_MIN = 70               # Luminosité minimale (exclut les zones trop sombres)

HSV_RANGES = {
    "red": [
        ((0, SATURATION_MIN, VALUE_MIN), (10, 255, 255)),     # Rouge proche de 0°
        ((170, SATURATION_MIN, VALUE_MIN), (179, 255, 255)),  # Rouge proche de 180°
    ],
    "yellow": [
        ((20, SATURATION_MIN, VALUE_MIN), (35, 255, 255)),
    ],
    "green": [
        ((40, SATURATION_MIN, VALUE_MIN), (85, 255, 255)),
    ],
}

def load_image(path, resize_factor, rotate):
    """Charge l'image, la redimensionne et la tourne si nécessaire."""
    image = cv2.imread(path, cv2.IMREAD_COLOR)
    if image is None:
        raise FileNotFoundError(f"Impossible de charger l'image : {path}")

    image = cv2.resize(image, (0, 0), fx=resize_factor, fy=resize_factor)
    if rotate:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
    print("Dimension : ",image.shape)
    return image


def build_color_mask(hsv, color_name, kernel_size):
    """Crée le masque binaire d'une couleur à partir de l'image HSV."""
    mask = np.zeros(hsv.shape[:2], np.uint8)

    # On fusionne toutes les plages de la couleur (ex : 2 plages pour le rouge)
    for lower, upper in HSV_RANGES[color_name]:
        mask |= cv2.inRange(hsv, np.array(lower), np.array(upper))

    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # Ouverture : supprime les petits pixels isolés (bruit)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    # Fermeture : comble les petits trous à l'intérieur des zones
    return cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

def analyze_colors(image, colors):
    """Analyse chaque couleur demandée. Retourne (résultats, masques)."""
    # La conversion HSV n'est faite qu'une seule fois pour toutes les couleurs
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

    masks = {}
    for color_name in colors:
        masks[color_name] = build_color_mask(hsv, color_name, MORPH_KERNEL_SIZE)
    return masks


def main():

    image = load_image(IMAGE_PATH, RESIZE_FACTOR, ROTATE_IMAGE)

    # Analyse des couleurs choisies
    masks = analyze_colors(image, COLORS_TO_ANALYZE)
    # Affichage
    cv2.imshow("image", image)
    if SHOW_MASKS:
        for color_name, mask in masks.items():
            cv2.imshow(f"mask {color_name}", mask)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == "__main__":
    main()


HSV_RANGES = {
    "red":    [((0, 100, 70), (10, 255, 255)), ((170, 100, 70), (179, 255, 255))],
    "yellow": [((20, 100, 70), (35, 255, 255))],
    "green":  [((40, 100, 70), (85, 255, 255))],
}
COLORS_TO_ANALYZE = ["red", "yellow", "green"]

La fonction analyze_colors() fait une boucle sur la liste des couleurs et fabrique un masque pour chacune. La conversion HSV n’est faite qu’une seule fois et servie à toutes les couleurs : pas de travail inutile !

Métaphore : l’image HSV est le buffet, chaque couleur prend ce qui l’intéresse dans les plats, sans recuire le buffet à chaque fois.

Test : trois fenêtres de masques. Change COLORS_TO_ANALYZE pour n’analyser que ["red"] et vérifie que seules les zones rouges apparaissent.


Étape 4 : Protéger le code avec check_colors()

Que se passe-t-il si quelqu’un écrit COLORS_TO_ANALYZE = ["blue"] alors que "blue" n’existe pas dans HSV_RANGES ? Erreur mystérieuse plus tard ! On ajoute un contrôleur d’entrée :

import cv2
import numpy as np

# ============================================================
# PARAMÈTRES (constantes)
# ============================================================
IMAGE_PATH = "C:/Users/patri/Pictures/image08.png"
# IMAGE_PATH = "C:/Users/patri/Pictures/Screenshots/163159.png"

RESIZE_FACTOR = 0.5          # Facteur de redimensionnement de l'image
ROTATE_IMAGE = False         # Mettre True pour tourner l'image de 90° (sens horaire)

SHOW_MASKS = True            # Afficher une fenêtre de masque par couleur analysée
# --- Choix des couleurs à analyser : "red", "yellow", "green" ---
# Exemples : ["red"]  |  ["red", "green"]  |  ["red", "yellow", "green"]
COLORS_TO_ANALYZE = ["red", "yellow", "green"]
MORPH_KERNEL_SIZE = 5        # Taille du noyau pour nettoyer les masques
# --- Plages HSV par couleur (OpenCV : H de 0 à 179, S et V de 0 à 255) ---
# Chaque couleur a une liste de plages (min, max) car le rouge est à cheval
# sur le début et la fin du cercle des teintes.
SATURATION_MIN = 100         # Saturation minimale (exclut les couleurs délavées)
VALUE_MIN = 70               # Luminosité minimale (exclut les zones trop sombres)

HSV_RANGES = {
    "red": [
        ((0, SATURATION_MIN, VALUE_MIN), (10, 255, 255)),     # Rouge proche de 0°
        ((170, SATURATION_MIN, VALUE_MIN), (179, 255, 255)),  # Rouge proche de 180°
    ],
    "yellow": [
        ((20, SATURATION_MIN, VALUE_MIN), (35, 255, 255)),
    ],
    "green": [
        ((40, SATURATION_MIN, VALUE_MIN), (85, 255, 255)),
    ],
}

def load_image(path, resize_factor, rotate):
    """Charge l'image, la redimensionne et la tourne si nécessaire."""
    image = cv2.imread(path, cv2.IMREAD_COLOR)
    if image is None:
        raise FileNotFoundError(f"Impossible de charger l'image : {path}")

    image = cv2.resize(image, (0, 0), fx=resize_factor, fy=resize_factor)
    if rotate:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
    print("Dimension : ",image.shape)
    return image


def check_colors(colors):
    """Vérifie que les couleurs demandées sont connues."""
    unknown = [c for c in colors if c not in HSV_RANGES]
    if unknown:
        raise ValueError(f"Couleur(s) inconnue(s) : {unknown}. Disponibles : {list(HSV_RANGES)}")



def build_color_mask(hsv, color_name, kernel_size):
    """Crée le masque binaire d'une couleur à partir de l'image HSV."""
    mask = np.zeros(hsv.shape[:2], np.uint8)

    # On fusionne toutes les plages de la couleur (ex : 2 plages pour le rouge)
    for lower, upper in HSV_RANGES[color_name]:
        mask |= cv2.inRange(hsv, np.array(lower), np.array(upper))

    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # Ouverture : supprime les petits pixels isolés (bruit)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    # Fermeture : comble les petits trous à l'intérieur des zones
    return cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

def analyze_colors(image, colors):
    """Analyse chaque couleur demandée. Retourne (résultats, masques)."""
    # La conversion HSV n'est faite qu'une seule fois pour toutes les couleurs
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)


    masks = {}
    for color_name in colors:
        masks[color_name] = build_color_mask(hsv, color_name, MORPH_KERNEL_SIZE)
    return masks


def main():
    check_colors(COLORS_TO_ANALYZE)
    image = load_image(IMAGE_PATH, RESIZE_FACTOR, ROTATE_IMAGE)

    # Analyse des couleurs choisies
    masks = analyze_colors(image, COLORS_TO_ANALYZE)
    # Affichage
    cv2.imshow("image", image)
    if SHOW_MASKS:
        for color_name, mask in masks.items():
            cv2.imshow(f"mask {color_name}", mask)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == "__main__":
    main()



def check_colors(colors):
    unknown = [c for c in colors if c not in HSV_RANGES]
    if unknown:
        raise ValueError(f"Couleur(s) inconnue(s) : {unknown}. Disponibles : {list(HSV_RANGES)}")

C’est le videur du club : il vérifie les couleurs à l’entrée, et refuse poliment celles qui ne sont pas dans la liste, avec un message qui dit quelles couleurs sont acceptées.

C’est une habitude de pro : détecter l’erreur au début avec un message clair plutôt que laisser le programme planter au milieu sans explication.

Test : écris volontairement "blue" dans COLORS_TO_ANALYZE → tu reçois un message d’erreur limpide. Remets une vraie couleur !


Étape 5 : Mesurer les zones avec find_largest_zone()

Le masque nous dit où est la couleur, en pixels blancs. Mais on veut une boîte (position + taille) et la plus grande zone seulement.

import cv2
import numpy as np

# ============================================================
# PARAMÈTRES (constantes)
# ============================================================
IMAGE_PATH = "C:/Users/patri/Pictures/image08.png"
# IMAGE_PATH = "C:/Users/patri/Pictures/Screenshots/163159.png"

RESIZE_FACTOR = 0.5          # Facteur de redimensionnement de l'image
ROTATE_IMAGE = False         # Mettre True pour tourner l'image de 90° (sens horaire)

SHOW_MASKS = True            # Afficher une fenêtre de masque par couleur analysée
# --- Choix des couleurs à analyser : "red", "yellow", "green" ---
# Exemples : ["red"]  |  ["red", "green"]  |  ["red", "yellow", "green"]
COLORS_TO_ANALYZE = ["red", "yellow", "green"]
MORPH_KERNEL_SIZE = 5        # Taille du noyau pour nettoyer les masques
# --- Plages HSV par couleur (OpenCV : H de 0 à 179, S et V de 0 à 255) ---
# Chaque couleur a une liste de plages (min, max) car le rouge est à cheval
# sur le début et la fin du cercle des teintes.
SATURATION_MIN = 100         # Saturation minimale (exclut les couleurs délavées)
VALUE_MIN = 70               # Luminosité minimale (exclut les zones trop sombres)

HSV_RANGES = {
    "red": [
        ((0, SATURATION_MIN, VALUE_MIN), (10, 255, 255)),     # Rouge proche de 0°
        ((170, SATURATION_MIN, VALUE_MIN), (179, 255, 255)),  # Rouge proche de 180°
    ],
    "yellow": [
        ((20, SATURATION_MIN, VALUE_MIN), (35, 255, 255)),
    ],
    "green": [
        ((40, SATURATION_MIN, VALUE_MIN), (85, 255, 255)),
    ],
}

MIN_AREA = 4000 # Surface minimale (en pixels) pour considérer une zone comme "grande"

def load_image(path, resize_factor, rotate):
    """Charge l'image, la redimensionne et la tourne si nécessaire."""
    image = cv2.imread(path, cv2.IMREAD_COLOR)
    if image is None:
        raise FileNotFoundError(f"Impossible de charger l'image : {path}")

    image = cv2.resize(image, (0, 0), fx=resize_factor, fy=resize_factor)
    if rotate:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
    print("Dimension : ",image.shape)
    return image


def check_colors(colors):
    """Vérifie que les couleurs demandées sont connues."""
    unknown = [c for c in colors if c not in HSV_RANGES]
    if unknown:
        raise ValueError(f"Couleur(s) inconnue(s) : {unknown}. Disponibles : {list(HSV_RANGES)}")



def build_color_mask(hsv, color_name, kernel_size):
    """Crée le masque binaire d'une couleur à partir de l'image HSV."""
    mask = np.zeros(hsv.shape[:2], np.uint8)

    # On fusionne toutes les plages de la couleur (ex : 2 plages pour le rouge)
    for lower, upper in HSV_RANGES[color_name]:
        mask |= cv2.inRange(hsv, np.array(lower), np.array(upper))

    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # Ouverture : supprime les petits pixels isolés (bruit)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    # Fermeture : comble les petits trous à l'intérieur des zones
    return cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

def find_largest_zone(mask, min_area):
    """Retourne (x, y, w, h, area) de la plus grande zone du masque, ou None."""
    # Composantes connexes : chaque zone séparée reçoit un label
    count, _, stats, _ = cv2.connectedComponentsWithStats(mask, connectivity=8)

    if count <= 1:  # Seul le fond a été trouvé
        return None

    # On ignore le label 0 (fond) et on cherche la plus grande surface
    best = np.argmax(stats[1:, cv2.CC_STAT_AREA]) + 1
    area = stats[best, cv2.CC_STAT_AREA]

    if area < min_area:
        return None

    return (
        stats[best, cv2.CC_STAT_LEFT],
        stats[best, cv2.CC_STAT_TOP],
        stats[best, cv2.CC_STAT_WIDTH],
        stats[best, cv2.CC_STAT_HEIGHT],
        area,
    )

def analyze_colors(image, colors):
    """Analyse chaque couleur demandée. Retourne (résultats, masques)."""
    # La conversion HSV n'est faite qu'une seule fois pour toutes les couleurs
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

    results = {}
    masks = {}
    for color_name in colors:
        masks[color_name] = build_color_mask(hsv, color_name, MORPH_KERNEL_SIZE)
        results[color_name] = find_largest_zone(masks[color_name], MIN_AREA)
    return results, masks

def print_report(results):
    """Affiche un résumé de l'analyse dans la console."""
    for color_name, zone in results.items():
        if zone is None:
            print(f"{color_name:>7} : aucune grande zone détectée")
        else:
            x, y, w, h, area = zone
            print(f"{color_name:>7} : x={x}, y={y}, largeur={w}, hauteur={h}, surface={area} px")


def main():
    check_colors(COLORS_TO_ANALYZE)
    image = load_image(IMAGE_PATH, RESIZE_FACTOR, ROTATE_IMAGE)

    # Analyse des couleurs choisies
    results, masks = analyze_colors(image, COLORS_TO_ANALYZE)
    print_report(results)
    # Affichage
    cv2.imshow("image", image)
    if SHOW_MASKS:
        for color_name, mask in masks.items():
            cv2.imshow(f"mask {color_name}", mask)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == "__main__":
    main()

Les composantes connexes : regrouper les pixels voisins

count, labels, stats, centroids = cv2.connectedComponentsWithStats(mask, connectivity=8)

Imagine que chaque groupe de pixels blancs qui se touchent forme une île. connectedComponentsWithStats compte les îles et donne pour chacune sa boîte englobante (x, y, largeur, hauteur) et sa surface.

Sorties (4 tableaux)

labels : image de même taille que l’entrée. Chaque pixel reçoit un étiquette :

  • 0 = fond,
  • 1, 2, 3… = identifiant de la région connexe.

stats : matrice de N lignes (une par région, y compris le fond en ligne 0) et 5 colonnes :

ColonneConstanteSignification
0CC_STAT_LEFTCoordonnée x du côté gauche de la boîte englobante
1CC_STAT_TOPCoordonnée y du côté haut
2CC_STAT_WIDTHLargeur de la boîte englobante
3CC_STAT_HEIGHTHauteur de la boîte englobante
4CC_STAT_AREANombre de pixels de la région

centroids : coordonnées (x, y) du centre de gravité de chaque région.

best = np.argmax(stats[1:, cv2.CC_STAT_AREA]) + 1

np.argmax trouve l’île à la plus grande surface. Le +1 décale car on a ignoré le label 0.

Le filtre anti-poussière

if area < min_area:
    return None

Avec MIN_AREA = 4000, les toutes petites taches (moins de 4000 pixels) sont ignorées : elles sont trop petites pour être la vraie cible. Si aucune zone ne passe le filtre, la fonction renvoie None (= « rien trouvé »).

Le rapport dans la console

print_report(results)

Affiche pour chaque couleur : position, dimensions et surface en pixels. Très utile pour vérifier ce que « voit » le programme !

Test : regarde la console :

    red : x=210, y=95, largeur=180, hauteur=160, surface=18400 px
 yellow : aucune grande zone détectée

Varie MIN_AREA : trop grand → plus rien n’est détecté ; trop petit → le bruit est détecté !


Étape 6 : Dessiner les résultats sur l’image

Les chiffres dans la console, c’est bien ; voir les cadres sur l’image, c’est mieux !

import cv2
import numpy as np

# ============================================================
# PARAMÈTRES (constantes)
# ============================================================
IMAGE_PATH = "C:/Users/patri/Pictures/image08.png"
# IMAGE_PATH = "C:/Users/patri/Pictures/Screenshots/163159.png"

RESIZE_FACTOR = 0.5          # Facteur de redimensionnement de l'image
ROTATE_IMAGE = False         # Mettre True pour tourner l'image de 90° (sens horaire)

SHOW_MASKS = True            # Afficher une fenêtre de masque par couleur analysée
# --- Choix des couleurs à analyser : "red", "yellow", "green" ---
# Exemples : ["red"]  |  ["red", "green"]  |  ["red", "yellow", "green"]
COLORS_TO_ANALYZE = ["red", "yellow", "green"]
MORPH_KERNEL_SIZE = 5        # Taille du noyau pour nettoyer les masques
# --- Plages HSV par couleur (OpenCV : H de 0 à 179, S et V de 0 à 255) ---
# Chaque couleur a une liste de plages (min, max) car le rouge est à cheval
# sur le début et la fin du cercle des teintes.
SATURATION_MIN = 100         # Saturation minimale (exclut les couleurs délavées)
VALUE_MIN = 70               # Luminosité minimale (exclut les zones trop sombres)

HSV_RANGES = {
    "red": [
        ((0, SATURATION_MIN, VALUE_MIN), (10, 255, 255)),     # Rouge proche de 0°
        ((170, SATURATION_MIN, VALUE_MIN), (179, 255, 255)),  # Rouge proche de 180°
    ],
    "yellow": [
        ((20, SATURATION_MIN, VALUE_MIN), (35, 255, 255)),
    ],
    "green": [
        ((40, SATURATION_MIN, VALUE_MIN), (85, 255, 255)),
    ],
}

MIN_AREA = 4000 # Surface minimale (en pixels) pour considérer une zone comme "grande"

# --- Couleurs d'affichage des contours (BGR) pour chaque couleur détectée ---
DISPLAY_COLORS = {
    "red": (255, 0, 0),
    "yellow": (0, 255, 255),
    "green": (0, 255, 0),
}

RECT_THICKNESS = 4           # Épaisseur des rectangles
FONT_SCALE = 0.8             # Taille du texte des étiquettes
FONT_THICKNESS = 2           # Épaisseur du texte des étiquettes

def load_image(path, resize_factor, rotate):
    """Charge l'image, la redimensionne et la tourne si nécessaire."""
    image = cv2.imread(path, cv2.IMREAD_COLOR)
    if image is None:
        raise FileNotFoundError(f"Impossible de charger l'image : {path}")

    image = cv2.resize(image, (0, 0), fx=resize_factor, fy=resize_factor)
    if rotate:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
    print("Dimension : ",image.shape)
    return image


def check_colors(colors):
    """Vérifie que les couleurs demandées sont connues."""
    unknown = [c for c in colors if c not in HSV_RANGES]
    if unknown:
        raise ValueError(f"Couleur(s) inconnue(s) : {unknown}. Disponibles : {list(HSV_RANGES)}")



def build_color_mask(hsv, color_name, kernel_size):
    """Crée le masque binaire d'une couleur à partir de l'image HSV."""
    mask = np.zeros(hsv.shape[:2], np.uint8)

    # On fusionne toutes les plages de la couleur (ex : 2 plages pour le rouge)
    for lower, upper in HSV_RANGES[color_name]:
        mask |= cv2.inRange(hsv, np.array(lower), np.array(upper))

    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # Ouverture : supprime les petits pixels isolés (bruit)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    # Fermeture : comble les petits trous à l'intérieur des zones
    return cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

def find_largest_zone(mask, min_area):
    """Retourne (x, y, w, h, area) de la plus grande zone du masque, ou None."""
    # Composantes connexes : chaque zone séparée reçoit un label
    count, _, stats, _ = cv2.connectedComponentsWithStats(mask, connectivity=8)

    if count <= 1:  # Seul le fond a été trouvé
        return None

    # On ignore le label 0 (fond) et on cherche la plus grande surface
    best = np.argmax(stats[1:, cv2.CC_STAT_AREA]) + 1
    area = stats[best, cv2.CC_STAT_AREA]

    if area < min_area:
        return None

    return (
        stats[best, cv2.CC_STAT_LEFT],
        stats[best, cv2.CC_STAT_TOP],
        stats[best, cv2.CC_STAT_WIDTH],
        stats[best, cv2.CC_STAT_HEIGHT],
        area,
    )

def analyze_colors(image, colors):
    """Analyse chaque couleur demandée. Retourne (résultats, masques)."""
    # La conversion HSV n'est faite qu'une seule fois pour toutes les couleurs
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

    results = {}
    masks = {}
    for color_name in colors:
        masks[color_name] = build_color_mask(hsv, color_name, MORPH_KERNEL_SIZE)
        results[color_name] = find_largest_zone(masks[color_name], MIN_AREA)
    return results, masks

def print_report(results):
    """Affiche un résumé de l'analyse dans la console."""
    for color_name, zone in results.items():
        if zone is None:
            print(f"{color_name:>7} : aucune grande zone détectée")
        else:
            x, y, w, h, area = zone
            print(f"{color_name:>7} : x={x}, y={y}, largeur={w}, hauteur={h}, surface={area} px")


def draw_results(image, results):
    """Dessine un rectangle étiqueté par couleur et la flèche vers la plus grande zone."""
    output = image.copy()
    rows, cols = output.shape[:2]
    image_center_x = cols // 2

    for color_name, zone in results.items():
        if zone is None:
            continue
        x, y, w, h, _ = zone
        color = DISPLAY_COLORS[color_name]
        cv2.rectangle(output, (x, y), (x + w, y + h), color, RECT_THICKNESS)
        # Étiquette placée au-dessus du rectangle (ou dedans si trop près du bord)
        label_y = y - 8 if y > 25 else y + 25
        cv2.putText(output, color_name, (x, label_y),
                    cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE, color, FONT_THICKNESS)

    return output


def main():
    check_colors(COLORS_TO_ANALYZE)
    image = load_image(IMAGE_PATH, RESIZE_FACTOR, ROTATE_IMAGE)

    # Analyse des couleurs choisies
    results, masks = analyze_colors(image, COLORS_TO_ANALYZE)
    print_report(results)
    output = draw_results(image, results)
    # Affichage
    cv2.imshow("image", image)
    cv2.imshow("target image", output)
    if SHOW_MASKS:
        for color_name, mask in masks.items():
            cv2.imshow(f"mask {color_name}", mask)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()





La fonction draw_results() copie l’image (image.copy() — on ne dessine jamais sur l’original, comme on ne griffonne pas sur la photo de famille !) et dessine dessus :

cv2.rectangle(output, (x, y), (x + w, y + h), color, RECT_THICKNESS)
  • cv2.rectangle : un cadre autour de la zone, dans la couleur choisie (DISPLAY_COLORS).
  • cv2.putText : le nom de la couleur écrit au-dessus du cadre.
label_y = y - 8 if y > 25 else y + 25

Petite sécurité : si le cadre touche le haut de l’image, on écrit le texte dedans pour qu’il ne sorte pas de l’écran.

Test : la fenêtre target image montre les cadres colorés, chaque fenêtre de masque reste disponible pour vérifier. Compare avec le rapport console !


Étape 7 : La flèche du capitaine (le programme final !)

Dernière touche : une flèche blanche au bas de l’image qui pointe vers la zone la plus grande, toutes couleurs confondues. Comme un guide qui dit « par ici la cible ! ».

import cv2
import numpy as np

# ============================================================
# PARAMÈTRES (constantes)
# ============================================================
IMAGE_PATH = "C:/Users/patri/Pictures/image08.png"
# IMAGE_PATH = "C:/Users/patri/Pictures/Screenshots/163159.png"

RESIZE_FACTOR = 0.5          # Facteur de redimensionnement de l'image
ROTATE_IMAGE = False         # Mettre True pour tourner l'image de 90° (sens horaire)

SHOW_MASKS = True            # Afficher une fenêtre de masque par couleur analysée
# --- Choix des couleurs à analyser : "red", "yellow", "green" ---
# Exemples : ["red"]  |  ["red", "green"]  |  ["red", "yellow", "green"]
COLORS_TO_ANALYZE = ["red", "yellow", "green"]
MORPH_KERNEL_SIZE = 5        # Taille du noyau pour nettoyer les masques
# --- Plages HSV par couleur (OpenCV : H de 0 à 179, S et V de 0 à 255) ---
# Chaque couleur a une liste de plages (min, max) car le rouge est à cheval
# sur le début et la fin du cercle des teintes.
SATURATION_MIN = 100         # Saturation minimale (exclut les couleurs délavées)
VALUE_MIN = 70               # Luminosité minimale (exclut les zones trop sombres)

HSV_RANGES = {
    "red": [
        ((0, SATURATION_MIN, VALUE_MIN), (10, 255, 255)),     # Rouge proche de 0°
        ((170, SATURATION_MIN, VALUE_MIN), (179, 255, 255)),  # Rouge proche de 180°
    ],
    "yellow": [
        ((20, SATURATION_MIN, VALUE_MIN), (35, 255, 255)),
    ],
    "green": [
        ((40, SATURATION_MIN, VALUE_MIN), (85, 255, 255)),
    ],
}

MIN_AREA = 4000 # Surface minimale (en pixels) pour considérer une zone comme "grande"

# --- Couleurs d'affichage (BGR) pour chaque couleur détectée ---
DISPLAY_COLORS = {
    "red": (255, 0, 0),
    "yellow": (0, 255, 255),
    "green": (0, 255, 0),
}
RECT_THICKNESS = 4           # Épaisseur des rectangles
FONT_SCALE = 0.8             # Taille du texte des étiquettes
FONT_THICKNESS = 2           # Épaisseur du texte des étiquettes

DRAW_ARROW = True            # Dessiner la flèche vers la zone la plus grande (toutes couleurs confondues)
ARROW_COLOR = (255, 255, 255)  # Couleur de la flèche (BGR) : blanc
ARROW_THICKNESS = 4          # Épaisseur de la flèche
ARROW_MARGIN = 10            # Distance entre la flèche et le bas de l'image

def load_image(path, resize_factor, rotate):
    """Charge l'image, la redimensionne et la tourne si nécessaire."""
    image = cv2.imread(path, cv2.IMREAD_COLOR)
    if image is None:
        raise FileNotFoundError(f"Impossible de charger l'image : {path}")

    image = cv2.resize(image, (0, 0), fx=resize_factor, fy=resize_factor)
    if rotate:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
    print("Dimension : ",image.shape)
    return image


def check_colors(colors):
    """Vérifie que les couleurs demandées sont connues."""
    unknown = [c for c in colors if c not in HSV_RANGES]
    if unknown:
        raise ValueError(f"Couleur(s) inconnue(s) : {unknown}. Disponibles : {list(HSV_RANGES)}")



def build_color_mask(hsv, color_name, kernel_size):
    """Crée le masque binaire d'une couleur à partir de l'image HSV."""
    mask = np.zeros(hsv.shape[:2], np.uint8)

    # On fusionne toutes les plages de la couleur (ex : 2 plages pour le rouge)
    for lower, upper in HSV_RANGES[color_name]:
        mask |= cv2.inRange(hsv, np.array(lower), np.array(upper))

    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # Ouverture : supprime les petits pixels isolés (bruit)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    # Fermeture : comble les petits trous à l'intérieur des zones
    return cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

def find_largest_zone(mask, min_area):
    """Retourne (x, y, w, h, area) de la plus grande zone du masque, ou None."""
    # Composantes connexes : chaque zone séparée reçoit un label
    count, _, stats, _ = cv2.connectedComponentsWithStats(mask, connectivity=8)

    if count <= 1:  # Seul le fond a été trouvé
        return None

    # On ignore le label 0 (fond) et on cherche la plus grande surface
    best = np.argmax(stats[1:, cv2.CC_STAT_AREA]) + 1
    area = stats[best, cv2.CC_STAT_AREA]

    if area < min_area:
        return None

    return (
        stats[best, cv2.CC_STAT_LEFT],
        stats[best, cv2.CC_STAT_TOP],
        stats[best, cv2.CC_STAT_WIDTH],
        stats[best, cv2.CC_STAT_HEIGHT],
        area,
    )

def analyze_colors(image, colors):
    """Analyse chaque couleur demandée. Retourne (résultats, masques)."""
    # La conversion HSV n'est faite qu'une seule fois pour toutes les couleurs
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

    results = {}
    masks = {}
    for color_name in colors:
        masks[color_name] = build_color_mask(hsv, color_name, MORPH_KERNEL_SIZE)
        results[color_name] = find_largest_zone(masks[color_name], MIN_AREA)
    return results, masks

def print_report(results):
    """Affiche un résumé de l'analyse dans la console."""
    for color_name, zone in results.items():
        if zone is None:
            print(f"{color_name:>7} : aucune grande zone détectée")
        else:
            x, y, w, h, area = zone
            print(f"{color_name:>7} : x={x}, y={y}, largeur={w}, hauteur={h}, surface={area} px")


def draw_results(image, results):
    """Dessine un rectangle étiqueté par couleur et la flèche vers la plus grande zone."""
    output = image.copy()
    rows, cols = output.shape[:2]
    image_center_x = cols // 2

    for color_name, zone in results.items():
        if zone is None:
            continue
        x, y, w, h, _ = zone
        color = DISPLAY_COLORS[color_name]
        cv2.rectangle(output, (x, y), (x + w, y + h), color, RECT_THICKNESS)
        # Étiquette placée au-dessus du rectangle (ou dedans si trop près du bord)
        label_y = y - 8 if y > 25 else y + 25
        cv2.putText(output, color_name, (x, label_y),
                    cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE, color, FONT_THICKNESS)

    # Flèche vers la zone la plus grande, toutes couleurs confondues
    detected = {name: zone for name, zone in results.items() if zone is not None}
    if DRAW_ARROW and detected:
        biggest = max(detected.values(), key=lambda z: z[4])
        x, _, w, _, _ = biggest
        offset = (x + w // 2) - image_center_x
        arrow_y = rows - ARROW_MARGIN
        cv2.arrowedLine(output, (image_center_x, arrow_y),
                        (image_center_x + offset, arrow_y),
                        ARROW_COLOR, ARROW_THICKNESS)


    return output


def main():
    check_colors(COLORS_TO_ANALYZE)
    image = load_image(IMAGE_PATH, RESIZE_FACTOR, ROTATE_IMAGE)

    # Analyse des couleurs choisies
    results, masks = analyze_colors(image, COLORS_TO_ANALYZE)
    print_report(results)
    output = draw_results(image, results)
    # Affichage
    cv2.imshow("image", image)
    cv2.imshow("target image", output)
    if SHOW_MASKS:
        for color_name, mask in masks.items():
            cv2.imshow(f"mask {color_name}", mask)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()
detected = {name: zone for name, zone in results.items() if zone is not None}
if DRAW_ARROW and detected:
    biggest = max(detected.values(), key=lambda z: z[4])
  1. On garde uniquement les couleurs détectées (zones non None).
  2. max(..., key=lambda z: z[4]) choisit la zone dont la surface (5e valeur du tuple) est la plus grande.
  3. On calcule l’écart entre le milieu de la zone et le milieu de l’image :
offset = (x + w // 2) - image_center_x
cv2.arrowedLine(output, (image_center_x, arrow_y),
                (image_center_x + offset, arrow_y),
                ARROW_COLOR, ARROW_THICKNESS)

La flèche part du centre et s’allonge vers la cible : offset négatif = cible à gauche, offset positif = à droite, offset nul = pile au centre !

Ce genre de flèche, c’est exactement ce qui permettrait à un robot ou un jeu de « viser » une cible automatiquement. Tu viens de coder un mini système de vision !

Récapitulatif du voyage

ÉtapeOn a appris…
0Le programme tout-en-un et ses défauts (lent, fragile)
1Charger, redimensionner, structurer en fonctions avec des paramètres
2Le masque binaire HSV et le nettoyage morphologique
3Analyser plusieurs couleurs avec le même code
4Valider les entrées avec un message d’erreur clair
5Mesurer les zones : composantes connexes + filtre de surface
6Dessiner rectangles et étiquettes sur l’image
7La flèche vers la plus grande cible : un mini système de vision !

Analyse d’une zone de couleur noire avec un contrôle sur la largeur minimum sur le coté le plus petit :

import cv2
import numpy as np

# ============================================================
# PARAMÈTRES (constantes)
# ============================================================
IMAGE_PATH = "C:/Users/patri/Pictures/image10.png"
# IMAGE_PATH = "C:/Users/patri/Pictures/Screenshots/163159.png"

RESIZE_FACTOR = 0.5          # Facteur de redimensionnement de l'image
ROTATE_IMAGE = False         # Mettre True pour tourner l'image de 90° (sens horaire)

SHOW_MASKS = True            # Afficher une fenêtre de masque par couleur analysée
# --- Choix des couleurs à analyser : "red", "yellow", "green" ---
# Exemples : ["red"]  |  ["red", "green"]  |  ["red", "yellow", "green"]
COLORS_TO_ANALYZE = ["red", "yellow", "green", "black"]
MORPH_KERNEL_SIZE = 5        # Taille du noyau pour nettoyer les masques
# --- Plages HSV par couleur (OpenCV : H de 0 à 179, S et V de 0 à 255) ---
# Chaque couleur a une liste de plages (min, max) car le rouge est à cheval
# sur le début et la fin du cercle des teintes.
SATURATION_MIN = 100         # Saturation minimale (exclut les couleurs délavées)
VALUE_MIN = 70               # Luminosité minimale (exclut les zones trop sombres)

# --- Plages spécifiques au noir ---
BLACK_V_MAX = 60            # Valeur maximale : plus c'est élevé, plus on accepte de gris foncé
BLACK_S_MAX = 80            # Saturation maximale : exclut les couleurs sombres mais teintées

HSV_RANGES = {
    "red": [
        ((0, SATURATION_MIN, VALUE_MIN), (10, 255, 255)),     # Rouge proche de 0°
        ((170, SATURATION_MIN, VALUE_MIN), (179, 255, 255)),  # Rouge proche de 180°
    ],
    "yellow": [
        ((20, SATURATION_MIN, VALUE_MIN), (35, 255, 255)),
    ],
    "green": [
        ((40, SATURATION_MIN, VALUE_MIN), (85, 255, 255)),
    ],
    # Noir : S et V faibles, teinte quelconque
    "black": [
        ((0, 0, 0), (179, BLACK_S_MAX, BLACK_V_MAX)),
    ],
}

# --- Filtre sur le plus petit côté, par couleur ---
# None = pas de filtre ; pour "black" : garde uniquement les lignes
# assez épaisses (en pixels) et élimine le bruit fin
MIN_SMALL_SIDE = {
    "red": None,
    "yellow": None,
    "green": None,
    "black": 3,       # <<< une ligne noire doit faire au moins 3 px d'épaisseur
}

MIN_AREA = 4000 # Surface minimale (en pixels) pour considérer une zone comme "grande"
# Les lignes fines ont une petite surface : seuil réduit pour le noir
MIN_AREA_BY_COLOR = {
    "red": MIN_AREA,
    "yellow": MIN_AREA,
    "green": 5,
    "black": 500,     # <<< surface minimale réduite pour les lignes
}

# --- Couleurs d'affichage (BGR) pour chaque couleur détectée ---
DISPLAY_COLORS = {
    "red": (255, 0, 0),
    "yellow": (0, 255, 255),
    "green": (255, 0, 0),
    "black": (255, 0, 0),   # blanc pour rester visible sur du noir
}

RECT_THICKNESS = 4           # Épaisseur des rectangles
FONT_SCALE = 0.8             # Taille du texte des étiquettes
FONT_THICKNESS = 2           # Épaisseur du texte des étiquettes

DRAW_ARROW = True            # Dessiner la flèche vers la zone la plus grande (toutes couleurs confondues)
ARROW_COLOR = (255, 255, 255)  # Couleur de la flèche (BGR) : blanc
ARROW_THICKNESS = 4          # Épaisseur de la flèche
ARROW_MARGIN = 10            # Distance entre la flèche et le bas de l'image

def load_image(path, resize_factor, rotate):
    """Charge l'image, la redimensionne et la tourne si nécessaire."""
    image = cv2.imread(path, cv2.IMREAD_COLOR)
    if image is None:
        raise FileNotFoundError(f"Impossible de charger l'image : {path}")

    image = cv2.resize(image, (0, 0), fx=resize_factor, fy=resize_factor)
    if rotate:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
    print("Dimension : ",image.shape)
    return image


def check_colors(colors):
    """Vérifie que les couleurs demandées sont connues."""
    unknown = [c for c in colors if c not in HSV_RANGES]
    if unknown:
        raise ValueError(f"Couleur(s) inconnue(s) : {unknown}. Disponibles : {list(HSV_RANGES)}")



def build_color_mask(hsv, color_name, kernel_size):
    """Crée le masque binaire d'une couleur à partir de l'image HSV."""
    mask = np.zeros(hsv.shape[:2], np.uint8)

    # On fusionne toutes les plages de la couleur (ex : 2 plages pour le rouge)
    for lower, upper in HSV_RANGES[color_name]:
        mask |= cv2.inRange(hsv, np.array(lower), np.array(upper))

    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # Ouverture : supprime les petits pixels isolés (bruit)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    # Fermeture : comble les petits trous à l'intérieur des zones
    return cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

def find_largest_zone(mask, min_area, min_small_side=None):
    """Retourne (x, y, w, h, area) de la plus grande zone du masque, ou None.
    Si min_small_side est défini, ignore les zones dont le plus petit côté
    (largeur ou hauteur) est inférieur à cette valeur."""
    count, _, stats, _ = cv2.connectedComponentsWithStats(mask, connectivity=8)

    if count <= 1:
        return None

    best = None
    best_area = 0
    for label in range(1, count):          # 0 = fond, on le saute
        x = stats[label, cv2.CC_STAT_LEFT]
        y = stats[label, cv2.CC_STAT_TOP]
        w = stats[label, cv2.CC_STAT_WIDTH]
        h = stats[label, cv2.CC_STAT_HEIGHT]
        area = stats[label, cv2.CC_STAT_AREA]

        if area < min_area:
            continue
        if min_small_side is not None and min(w, h) < min_small_side:
            continue
        if area > best_area:
            best_area = area
            best = (x, y, w, h, area)

    return best

def analyze_colors(image, colors):
    """Analyse chaque couleur demandée. Retourne (résultats, masques)."""
    # La conversion HSV n'est faite qu'une seule fois pour toutes les couleurs
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

    results = {}
    masks = {}
    for color_name in colors:
        masks[color_name] = build_color_mask(hsv, color_name, MORPH_KERNEL_SIZE)
        results[color_name] = find_largest_zone(
            masks[color_name],
            MIN_AREA_BY_COLOR[color_name],      # seuil de surface adapté
            MIN_SMALL_SIDE[color_name],         # filtre sur le plus petit côté
        )
    return results, masks

def print_report(results):
    """Affiche un résumé de l'analyse dans la console."""
    for color_name, zone in results.items():
        if zone is None:
            print(f"{color_name:>7} : aucune grande zone détectée")
        else:
            x, y, w, h, area = zone
            print(f"{color_name:>7} : x={x}, y={y}, largeur={w}, hauteur={h}, surface={area} px")


def draw_results(image, results):
    """Dessine un rectangle étiqueté par couleur et la flèche vers la plus grande zone."""
    output = image.copy()
    rows, cols = output.shape[:2]
    image_center_x = cols // 2

    for color_name, zone in results.items():
        if zone is None:
            continue
        x, y, w, h, _ = zone
        color = DISPLAY_COLORS[color_name]
        cv2.rectangle(output, (x, y), (x + w, y + h), color, RECT_THICKNESS)
        # Étiquette placée au-dessus du rectangle (ou dedans si trop près du bord)
        label_y = y - 8 if y > 25 else y + 25
        cv2.putText(output, color_name, (x, label_y),
                    cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE, color, FONT_THICKNESS)

    # Flèche vers la zone la plus grande, toutes couleurs confondues
    detected = {name: zone for name, zone in results.items() if zone is not None}
    if DRAW_ARROW and detected:
        biggest = max(detected.values(), key=lambda z: z[4])
        x, _, w, _, _ = biggest
        offset = (x + w // 2) - image_center_x
        arrow_y = rows - ARROW_MARGIN
        cv2.arrowedLine(output, (image_center_x, arrow_y),
                        (image_center_x + offset, arrow_y),
                        ARROW_COLOR, ARROW_THICKNESS)


    return output


def main():
    check_colors(COLORS_TO_ANALYZE)
    image = load_image(IMAGE_PATH, RESIZE_FACTOR, ROTATE_IMAGE)

    # Analyse des couleurs choisies
    results, masks = analyze_colors(image, COLORS_TO_ANALYZE)
    print_report(results)
    output = draw_results(image, results)
    # Affichage
    cv2.imshow("image", image)
    cv2.imshow("target image", output)
    if SHOW_MASKS:
        for color_name, mask in masks.items():
            cv2.imshow(f"mask {color_name}", mask)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()





Traitement de plusieurs zones en leur donnant une priorité :

import cv2
import numpy as np

# ============================================================
# PARAMÈTRES (constantes)
# ============================================================
IMAGE_PATH = "C:/Users/patri/Pictures/image11.png"
# IMAGE_PATH = "C:/Users/patri/Pictures/Screenshots/163159.png"

RESIZE_FACTOR = 0.5          # Facteur de redimensionnement de l'image
ROTATE_IMAGE = False         # Mettre True pour tourner l'image de 90° (sens horaire)

SHOW_MASKS = True            # Afficher une fenêtre de masque par couleur analysée
# --- Choix des couleurs à analyser : "red", "yellow", "green" ---
# Exemples : ["red"]  |  ["red", "green"]  |  ["red", "yellow", "green"]
COLORS_TO_ANALYZE = ["red", "yellow", "green", "black"]
MORPH_KERNEL_SIZE = 5        # Taille du noyau pour nettoyer les masques
# --- Plages HSV par couleur (OpenCV : H de 0 à 179, S et V de 0 à 255) ---
# Chaque couleur a une liste de plages (min, max) car le rouge est à cheval
# sur le début et la fin du cercle des teintes.
SATURATION_MIN = 100         # Saturation minimale (exclut les couleurs délavées)
VALUE_MIN = 70               # Luminosité minimale (exclut les zones trop sombres)

# --- Plages spécifiques au noir ---
BLACK_V_MAX = 60            # Valeur maximale : plus c'est élevé, plus on accepte de gris foncé
BLACK_S_MAX = 80            # Saturation maximale : exclut les couleurs sombres mais teintées

HSV_RANGES = {
    "red": [
        ((0, SATURATION_MIN, VALUE_MIN), (10, 255, 255)),     # Rouge proche de 0°
        ((170, SATURATION_MIN, VALUE_MIN), (179, 255, 255)),  # Rouge proche de 180°
    ],
    "yellow": [
        ((20, SATURATION_MIN, VALUE_MIN), (35, 255, 255)),
    ],
    "green": [
        ((40, SATURATION_MIN, VALUE_MIN), (85, 255, 255)),
    ],
    # Noir : S et V faibles, teinte quelconque
    "black": [
        ((0, 0, 0), (179, BLACK_S_MAX, BLACK_V_MAX)),
    ],
}

# --- Filtre sur le plus petit côté, par couleur ---
# None = pas de filtre ; pour "black" : garde uniquement les lignes
# assez épaisses (en pixels) et élimine le bruit fin
MIN_SMALL_SIDE = {
    "red": None,
    "yellow": None,
    "green": None,
    "black": 3,       # <<< une ligne noire doit faire au moins 3 px d'épaisseur
}

MIN_AREA = 4000 # Surface minimale (en pixels) pour considérer une zone comme "grande"
# Les lignes fines ont une petite surface : seuil réduit pour le noir
MIN_AREA_BY_COLOR = {
    "red": 50,
    "yellow": MIN_AREA,
    "green": 5,
    "black": 500,     # <<< surface minimale réduite pour les lignes
}

# --- Couleurs d'affichage (BGR) pour chaque couleur détectée ---
DISPLAY_COLORS = {
    "red": (255, 0, 0),
    "yellow": (0, 255, 255),
    "green": (255, 0, 0),
    "black": (255, 0, 0),   # blanc pour rester visible sur du noir
}

RECT_THICKNESS = 4           # Épaisseur des rectangles
FONT_SCALE = 0.8             # Taille du texte des étiquettes
FONT_THICKNESS = 2           # Épaisseur du texte des étiquettes

DRAW_ARROW = True            # Dessiner la flèche vers la zone la plus grande (toutes couleurs confondues)
ARROW_COLOR = (255, 255, 255)  # Couleur de la flèche (BGR) : blanc
ARROW_THICKNESS = 4          # Épaisseur de la flèche
ARROW_MARGIN = 10            # Distance entre la flèche et le bas de l'image

MAX_ZONES_PER_COLOR = 5    # <<< Nombre maximum de zones affichées par couleur
                            # Mettre None pour tout afficher

def load_image(path, resize_factor, rotate):
    """Charge l'image, la redimensionne et la tourne si nécessaire."""
    image = cv2.imread(path, cv2.IMREAD_COLOR)
    if image is None:
        raise FileNotFoundError(f"Impossible de charger l'image : {path}")

    image = cv2.resize(image, (0, 0), fx=resize_factor, fy=resize_factor)
    if rotate:
        image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
    print("Dimension : ",image.shape)
    return image


def check_colors(colors):
    """Vérifie que les couleurs demandées sont connues."""
    unknown = [c for c in colors if c not in HSV_RANGES]
    if unknown:
        raise ValueError(f"Couleur(s) inconnue(s) : {unknown}. Disponibles : {list(HSV_RANGES)}")



def build_color_mask(hsv, color_name, kernel_size):
    """Crée le masque binaire d'une couleur à partir de l'image HSV."""
    mask = np.zeros(hsv.shape[:2], np.uint8)

    # On fusionne toutes les plages de la couleur (ex : 2 plages pour le rouge)
    for lower, upper in HSV_RANGES[color_name]:
        mask |= cv2.inRange(hsv, np.array(lower), np.array(upper))

    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # Ouverture : supprime les petits pixels isolés (bruit)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    # Fermeture : comble les petits trous à l'intérieur des zones
    return cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

def find_largest_zone(mask, min_area, min_small_side=None):
    """Retourne (x, y, w, h, area) de la plus grande zone du masque, ou None.
    Si min_small_side est défini, ignore les zones dont le plus petit côté
    (largeur ou hauteur) est inférieur à cette valeur."""
    count, _, stats, _ = cv2.connectedComponentsWithStats(mask, connectivity=8)

    if count <= 1:
        return None

    best = None
    best_area = 0
    for label in range(1, count):          # 0 = fond, on le saute
        x = stats[label, cv2.CC_STAT_LEFT]
        y = stats[label, cv2.CC_STAT_TOP]
        w = stats[label, cv2.CC_STAT_WIDTH]
        h = stats[label, cv2.CC_STAT_HEIGHT]
        area = stats[label, cv2.CC_STAT_AREA]

        if area < min_area:
            continue
        if min_small_side is not None and min(w, h) < min_small_side:
            continue
        if area > best_area:
            best_area = area
            best = (x, y, w, h, area)

    return best

def find_all_zones(mask, min_area, min_small_side=None, max_zones=None):
    """Retourne la liste [(x, y, w, h, area), ...] des zones du masque,
    triées de la plus grande à la plus petite, ou [] si aucune.
    - min_area : surface minimale d'une zone
    - min_small_side : si défini, plus petit côté (w ou h) minimal
    - max_zones : nombre maximum de zones retournées (None = toutes)"""
    count, _, stats, _ = cv2.connectedComponentsWithStats(mask, connectivity=8)

    if count <= 1:
        return []

    zones = []
    for label in range(1, count):          # 0 = fond, on le saute
        x = stats[label, cv2.CC_STAT_LEFT]
        y = stats[label, cv2.CC_STAT_TOP]
        w = stats[label, cv2.CC_STAT_WIDTH]
        h = stats[label, cv2.CC_STAT_HEIGHT]
        area = stats[label, cv2.CC_STAT_AREA]

        if area < min_area:
            continue
        if min_small_side is not None and min(w, h) < min_small_side:
            continue
        zones.append((x, y, w, h, area))

    # Tri de la plus grande à la plus petite
    zones.sort(key=lambda z: z[4], reverse=True)

    if max_zones is not None:
        zones = zones[:max_zones]

    return zones
    
def analyze_colors(image, colors):
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

    results = {}      # {couleur: [(x, y, w, h, area), ...]}
    masks = {}
    for color_name in colors:
        masks[color_name] = build_color_mask(hsv, color_name, MORPH_KERNEL_SIZE)
        results[color_name] = find_all_zones(
            masks[color_name],
            MIN_AREA_BY_COLOR[color_name],
            MIN_SMALL_SIDE[color_name],
            MAX_ZONES_PER_COLOR,
        )
    return results, masks

def assign_priorities(results):
    """Attribue un rang de priorité À L'INTÉRIEUR DE CHAQUE COULEUR.

    Pour chaque couleur, la zone dont le bas est le plus proche du BAS
    de l'image reçoit la priorité 1 (la plus forte). Les suivantes sont
    numérotées en remontant vers le haut de l'image.

    Retourne (by_color, all_ranked) :
      by_color    : {couleur: {zone_tuple: rang}}
      all_ranked  : liste [(couleur, zone, rang), ...] de toutes les zones
                    prioritaires, utile pour la flèche
    """
    by_color = {}
    all_ranked = []

    for color_name, zones in results.items():
        if not zones or color_name not in DISPLAY_COLORS:
            continue

        # Tri du bas vers le haut : bas de zone (y + h) décroissant
        zones_sorted = sorted(
            zones,
            key=lambda z: (z[1] + z[3]),      # y + h
            reverse=True,                     # la plus basse d'abord
        )

        by_color[color_name] = {}
        for rank, zone in enumerate(zones_sorted, start=1):
            by_color[color_name][zone] = rank
            all_ranked.append((color_name, zone, rank))

    return by_color, all_ranked

def print_report(results, by_color=None):
    for color_name, zones in results.items():
        if not zones:
            print(f"{color_name:>7} : aucune zone détectée")
            continue
        print(f"{color_name:>7} : {len(zones)} zone(s)")
        for i, zone in enumerate(zones, start=1):
            x, y, w, h, area = zone
            rank = by_color.get(color_name, {}).get(zone, "-")
            print(f"           zone {i} : x={x}, y={y}, largeur={w}, "
                  f"hauteur={h}, surface={area} px, priorite={rank}")

def draw_results(image, results, all_ranked=None, by_zone=None):
    output = image.copy()
    rows, cols = output.shape[:2]
    image_center_x = cols // 2

    for color_name, zones in results.items():
        color = DISPLAY_COLORS[color_name]
        for i, (x, y, w, h, area) in enumerate(zones, start=1):
            cv2.rectangle(output, (x, y), (x + w, y + h), color, RECT_THICKNESS)
            label_y = y - 8 if y > 25 else y + 25
            rank = by_zone.get(color_name, {}).get((x, y, w, h, area))
            label = f"{color_name} {i}" if rank is None else f"{color_name} {i} P{rank}"
            cv2.putText(output, label, (x, label_y),
                        cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE, color, FONT_THICKNESS)

    # Flèche vers la zone la plus grande, toutes couleurs confondues
    detected = {name: zone for name, zone in results.items() if zone is not None}
    if DRAW_ARROW and all_ranked:
        firsts = [(c, z, r) for c, z, r in all_ranked if r == 1]  # une par couleur
        # Si plusieurs couleurs ont une priorité 1, choisir par ex. la plus basse :
        color_name, (x, y, w, h, _), rank = max(firsts, key=lambda e: e[1][1] + e[1][3])
        offset = (x + w // 2) - image_center_x
        arrow_y = rows - ARROW_MARGIN
        cv2.arrowedLine(output, (image_center_x, arrow_y),
                        (image_center_x + offset, arrow_y),
                        ARROW_COLOR, ARROW_THICKNESS)


    return output


def main():
    check_colors(COLORS_TO_ANALYZE)
    image = load_image(IMAGE_PATH, RESIZE_FACTOR, ROTATE_IMAGE)

    # Analyse des couleurs choisies
    results, masks = analyze_colors(image, COLORS_TO_ANALYZE)
    by_color, all_ranked = assign_priorities(results)
    print_report(results, by_color)
    output = draw_results(image, results, all_ranked, by_color)
    # Affichage
    cv2.imshow("image", image)
    cv2.imshow("target image", output)
    if SHOW_MASKS:
        for color_name, mask in masks.items():
            cv2.imshow(f"mask {color_name}", mask)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()