| Ligne 29 : | Ligne 29 : | ||
-Imprimante 3D (ici la Ultimaker 2+)</translate> | -Imprimante 3D (ici la Ultimaker 2+)</translate> | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
}} | }} | ||
{{Tuto Step | {{Tuto Step | ||
| − | |Step_Title=<translate></translate> | + | |Step_Title=<translate>Découpe des pièces en bois du boitier</translate> |
| − | |Step_Content=<translate></translate> | + | |Step_Content=<translate>-Connecter l'ordinateur à la découpeuse laser |
| + | |||
| + | -Ouvrir le logiciel Trotek ( Nécessaire à la découpe) | ||
| + | |||
| + | -Ouvrir les pièces à découper en format dxf dans le logiciel | ||
| + | |||
| + | -Optimiser l'espace sur la planche afin d'avoir les moins de perte de matière et déplaçant les pièces | ||
| + | |||
| + | -Lancer la découpe | ||
| + | |||
| + | -Nettoyer les pièces afin d'éviter des tâches dues au bois brûlé.</translate> | ||
}} | }} | ||
{{Tuto Step | {{Tuto Step | ||
| − | |Step_Title=<translate></translate> | + | |Step_Title=<translate>Assemblage des pièces en bois</translate> |
|Step_Content=<translate></translate> | |Step_Content=<translate></translate> | ||
}} | }} | ||
| Ligne 58 : | Ligne 57 : | ||
}} | }} | ||
{{Tuto Step | {{Tuto Step | ||
| − | |Step_Title=<translate></translate> | + | |Step_Title=<translate>Branchement entre les carte électroniques et les servo-moteurs</translate> |
| − | |Step_Content=<translate></ | + | |Step_Content=<translate>==== - Brancher les servo moteur sur la carte arduino - inséré le code arduino<syntaxhighlight lang="c"> |
| − | }} | + | Servo servos[6]; // Tableau de 6 servos |
| − | {{ | + | const int pinsServos[6] = {3, 5, 6, 9, 10, 11}; // Broches des servos |
| − | + | ||
| − | + | unsigned long tempsServo = 0; | |
| − | + | bool servosActifs = false; | |
| + | |||
| + | void setup() { | ||
| + | Serial.begin(9600); // Initialisation du moniteur série | ||
| + | Serial.println("✅ Arduino prêt, en attente des commandes I2C..."); | ||
| + | |||
| + | Wire.begin(8); // Arduino en esclave I2C (adresse 8) | ||
| + | Wire.onReceive(recevoirCommande); | ||
| + | |||
| + | // Initialiser les servos à la position souhaitée (par exemple, 45°) | ||
| + | for (int i = 0; i < 6; i++) { | ||
| + | servos[i].attach(pinsServos[i]); | ||
| + | servos[i].write(45); // Position initiale (en bas) | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void loop() { | ||
| + | if (servosActifs && millis() - tempsServo >= 3000) { | ||
| + | Serial.println("🔄 Retour des servos à la position initiale."); | ||
| + | for (int i = 0; i < 6; i++) { | ||
| + | servos[i].write(45); // Retour à la position initiale (en bas) | ||
| + | } | ||
| + | servosActifs = false; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void recevoirCommande(int nombreOctets) { | ||
| + | if (Wire.available()) { | ||
| + | int valeur = Wire.read(); // Lire le nombre envoyé par le Raspberry Pi | ||
| + | Serial.print("📩 Commande reçue: "); | ||
| + | Serial.println(valeur, BIN); // Afficher la valeur en binaire (code Braille) | ||
| + | |||
| + | // Déplacer chaque servo un par un avec un petit délai | ||
| + | for (int i = 0; i < 6; i++) { | ||
| + | if (valeur & (1 << (5 - i))) { // Vérifier si le bit i est actif | ||
| + | Serial.print("🔼 Servo "); | ||
| + | Serial.print(i); | ||
| + | Serial.println(" activé."); | ||
| + | servos[i].write(135); // Position haute (en haut) | ||
| + | } else { | ||
| + | Serial.print("🔽 Servo "); | ||
| + | Serial.print(i); | ||
| + | Serial.println(" désactivé."); | ||
| + | servos[i].write(45); // Position initiale (en bas) | ||
| + | } | ||
| + | delay(100); // Petite pause entre les déplacements des servos | ||
| + | } | ||
| + | |||
| + | tempsServo = millis(); // Démarrer le timer pour retour automatique | ||
| + | servosActifs = true; | ||
| + | } | ||
| + | } | ||
| + | </syntaxhighlight> - Se munir de la carte raspery Pi - Brancher relier les broches Gpio raspery Pi avec celle de arduino(gnd,vcc) taper sur internet broche raspery pi - inséré ce code<syntaxhighlight lang="python"> | ||
| + | import smbus | ||
| + | import time | ||
| + | import RPi.GPIO as GPIO | ||
| + | import random | ||
| + | import os | ||
| + | import threading | ||
| + | import cv2 | ||
| + | import pytesseract | ||
| + | import numpy as np | ||
| + | |||
| + | I2C_ADDR = 8 | ||
| + | bus = smbus.SMBus(1) | ||
| + | |||
| + | # GPIO des boutons | ||
| + | BOUTON_PRECEDENT = 17 | ||
| + | BOUTON_SUIVANT = 27 | ||
| + | BOUTON_LECON = 23 | ||
| + | BOUTON_EXERCICE = 24 | ||
| + | BOUTON_CAMERA = 22 # Bouton pour la caméra | ||
| + | |||
| + | # GPIO du capteur ultrason HCSR04 | ||
| + | TRIG = 5 # GPIO pour le trigger du capteur | ||
| + | ECHO = 6 # GPIO pour l'echo du capteur | ||
| + | |||
| + | # Configuration des GPIO | ||
| + | try: | ||
| + | GPIO.cleanup() | ||
| + | except: | ||
| + | pass | ||
| + | |||
| + | GPIO.setmode(GPIO.BCM) | ||
| + | GPIO.setup(BOUTON_PRECEDENT, GPIO.IN, pull_up_down=GPIO.PUD_UP) | ||
| + | GPIO.setup(BOUTON_SUIVANT, GPIO.IN, pull_up_down=GPIO.PUD_UP) | ||
| + | GPIO.setup(BOUTON_LECON, GPIO.IN, pull_up_down=GPIO.PUD_UP) | ||
| + | GPIO.setup(BOUTON_EXERCICE, GPIO.IN, pull_up_down=GPIO.PUD_UP) | ||
| + | GPIO.setup(BOUTON_CAMERA, GPIO.IN, pull_up_down=GPIO.PUD_UP) | ||
| + | |||
| + | # Configuration du capteur ultrason | ||
| + | GPIO.setup(TRIG, GPIO.OUT) | ||
| + | GPIO.setup(ECHO, GPIO.IN) | ||
| + | GPIO.output(TRIG, False) # S'assurer que le trigger est bas au démarrage | ||
| + | |||
| + | # Dictionnaire Braille | ||
| + | braille_dict = { | ||
| + | "a": 0b100000, "b": 0b110000, "c": 0b101000, "d": 0b101100, | ||
| + | "e": 0b100100, "f": 0b111000, "g": 0b111100, "h": 0b110100, | ||
| + | "i": 0b011000, "j": 0b011100, "k": 0b100010, "l": 0b110010, | ||
| + | "m": 0b101010, "n": 0b101110, "o": 0b100110, "p": 0b111010, | ||
| + | "q": 0b111110, "r": 0b110110, "s": 0b011010, "t": 0b011110, | ||
| + | "u": 0b100011, "v": 0b110011, "w": 0b011101, "x": 0b101011, | ||
| + | "y": 0b101111, "z": 0b100111, " ": 0b000000 | ||
| + | } | ||
| + | |||
| + | texte = "bonjour" # Texte par défaut | ||
| + | index = 0 | ||
| + | lecon_en_cours = False | ||
| + | exercice_en_cours = False | ||
| + | attente_texte = False # Pour savoir si on attend une saisie de texte | ||
| + | camera_en_cours = False # Pour indiquer si une capture par caméra est en cours | ||
| + | |||
| + | # Paramètres de capture d'image | ||
| + | IMAGE_PATH = "capture.jpg" | ||
| + | CROPPED_PATH = "cropped.jpg" | ||
| + | DISTANCE_OPTIMALE = 15 # Distance optimale en cm | ||
| + | MARGE_DISTANCE = 2.5 # Marge de tolérance en cm (+/-) | ||
| + | |||
| + | def mesurer_distance(): | ||
| + | """Mesure la distance avec le capteur ultrason HCSR04.""" | ||
| + | # Envoyer une impulsion de 10µs au trigger | ||
| + | GPIO.output(TRIG, True) | ||
| + | time.sleep(0.00001) # 10µs | ||
| + | GPIO.output(TRIG, False) | ||
| + | |||
| + | # Attendre que l'écho commence | ||
| + | start_time = time.time() | ||
| + | timeout = start_time + 1.0 # Timeout de 1 seconde | ||
| + | |||
| + | while GPIO.input(ECHO) == 0: | ||
| + | if time.time() > timeout: | ||
| + | return -1 # Erreur: pas de signal | ||
| + | pulse_start = time.time() | ||
| + | |||
| + | # Attendre que l'écho se termine | ||
| + | while GPIO.input(ECHO) == 1: | ||
| + | if time.time() > timeout: | ||
| + | return -1 # Erreur: signal trop long | ||
| + | pulse_end = time.time() | ||
| + | |||
| + | # Calculer la durée de l'impulsion | ||
| + | pulse_duration = pulse_end - pulse_start | ||
| + | |||
| + | # Calculer la distance (vitesse du son = 34300 cm/s) | ||
| + | # Diviser par 2 car le signal fait l'aller-retour | ||
| + | distance = (pulse_duration * 34300) / 2 | ||
| + | |||
| + | return round(distance, 1) # Arrondir à 1 décimale | ||
| + | |||
| + | def guide_position(): | ||
| + | """Guide l'utilisateur pour positionner correctement le texte.""" | ||
| + | global camera_en_cours | ||
| + | |||
| + | print("📏 Positionnement du texte...") | ||
| + | os.system('espeak -v fr "Positionnez le texte" --stdout | aplay') | ||
| + | |||
| + | # Attendre que la distance soit stable dans la plage optimale | ||
| + | position_stable = False | ||
| + | nb_mesures_stables = 0 | ||
| + | |||
| + | while camera_en_cours and not position_stable: | ||
| + | distance = mesurer_distance() | ||
| + | |||
| + | if distance < 0: | ||
| + | print("⚠️ Erreur de mesure de distance") | ||
| + | time.sleep(0.5) | ||
| + | continue | ||
| + | |||
| + | print(f"📏 Distance mesurée : {distance} cm") | ||
| + | |||
| + | if distance < DISTANCE_OPTIMALE - MARGE_DISTANCE: | ||
| + | # Trop proche | ||
| + | os.system('espeak -v fr "Reculez" --stdout | aplay') | ||
| + | nb_mesures_stables = 0 | ||
| + | elif distance > DISTANCE_OPTIMALE + MARGE_DISTANCE: | ||
| + | # Trop loin | ||
| + | os.system('espeak -v fr "Avancez" --stdout | aplay') | ||
| + | nb_mesures_stables = 0 | ||
| + | else: | ||
| + | # Distance correcte | ||
| + | print(f"✅ Distance correcte : {distance} cm") | ||
| + | nb_mesures_stables += 1 | ||
| + | |||
| + | # Si 3 mesures consécutives sont dans la plage, on considère la position comme stable | ||
| + | if nb_mesures_stables >= 3: | ||
| + | position_stable = True | ||
| + | os.system('espeak -v fr "Position correcte" --stdout | aplay') | ||
| + | |||
| + | time.sleep(0.5) # Attendre avant la prochaine mesure | ||
| + | |||
| + | return position_stable | ||
| + | |||
| + | def envoyer_nombre(nombre, mode=None): | ||
| + | """Envoie une lettre en Braille et annonce (avec délai optionnel en mode exercice).""" | ||
| + | try: | ||
| + | lettre = [key for key, val in braille_dict.items() if val == nombre][0] | ||
| + | bus.write_byte(I2C_ADDR, nombre) | ||
| + | print(f"✅ Lettre envoyée: {lettre.upper()} ({bin(nombre)})") | ||
| + | |||
| + | # Si c'est le mode exercice, attendre 2 secondes avant d'énoncer la lettre | ||
| + | if mode == "exercice": | ||
| + | # Vérifier périodiquement si l'exercice a été arrêté pendant l'attente | ||
| + | debut = time.time() | ||
| + | while time.time() - debut < 2 and exercice_en_cours: | ||
| + | time.sleep(0.1) | ||
| + | |||
| + | # Ne prononcer la lettre que si l'exercice est toujours en cours | ||
| + | if exercice_en_cours: | ||
| + | os.system(f'espeak -v fr "{lettre}" --stdout | aplay') | ||
| + | else: | ||
| + | # Pour les autres modes, énoncer la lettre immédiatement | ||
| + | os.system(f'espeak -v fr "{lettre}" --stdout | aplay') | ||
| + | except IOError: | ||
| + | print("❌ Erreur de communication avec l'Arduino") | ||
| + | except IndexError: | ||
| + | print("❌ Erreur: code braille non trouvé dans le dictionnaire") | ||
| + | except Exception as e: | ||
| + | print(f"❌ Erreur inattendue: {str(e)}") | ||
| + | |||
| + | def demander_nouveau_texte(): | ||
| + | """Demande un nouveau texte et réinitialise l'index.""" | ||
| + | global texte, index, attente_texte | ||
| + | |||
| + | # Annonce vocale | ||
| + | os.system('espeak -v fr "Entrez un nouveau texte" --stdout | aplay') | ||
| + | |||
| + | # Lancer un thread pour attendre la saisie sans bloquer le reste du programme | ||
| + | def attendre_saisie(): | ||
| + | global texte, index, attente_texte | ||
| + | nouveau_texte = input("\n🆕 Entrez un texte (ou appuyez sur Entrée pour garder 'bonjour') : ").lower() | ||
| + | if nouveau_texte: # Si l'utilisateur a entré quelque chose | ||
| + | texte = nouveau_texte | ||
| + | index = 0 | ||
| + | print(f"🔠 Texte actuel : {texte}") | ||
| + | print("📌 Appuyez sur le bouton 'Suivant' pour parcourir le texte, ou sur 'Leçon'/'Exercice' pour ces modes.") | ||
| + | attente_texte = False # Fin de l'attente | ||
| + | |||
| + | attente_texte = True # Début de l'attente | ||
| + | t = threading.Thread(target=attendre_saisie) | ||
| + | t.daemon = True | ||
| + | t.start() | ||
| + | |||
| + | def bouton_precedent(channel): | ||
| + | """Affiche la lettre précédente pendant la lecture du texte.""" | ||
| + | global index | ||
| + | |||
| + | # Ne pas exécuter si un mode est en cours ou si on attend une saisie | ||
| + | if lecon_en_cours or exercice_en_cours or attente_texte or camera_en_cours: | ||
| + | return | ||
| + | |||
| + | time.sleep(0.2) # Anti-rebond | ||
| + | if GPIO.input(BOUTON_PRECEDENT) == GPIO.LOW: | ||
| + | if index > 0: | ||
| + | index -= 1 | ||
| + | envoyer_nombre(braille_dict.get(texte[index], 0)) | ||
| + | print(f"🔙 Lettre précédente: {texte[index]}") | ||
| + | else: | ||
| + | print("🚫 Déjà à la première lettre.") | ||
| + | os.system('espeak -v fr "Première lettre" --stdout | aplay') | ||
| + | |||
| + | def bouton_suivant(channel): | ||
| + | """Affiche la lettre suivante et lit le mot en entier à la fin.""" | ||
| + | global index | ||
| + | |||
| + | # Ne pas exécuter si un mode est en cours ou si on attend une saisie | ||
| + | if lecon_en_cours or exercice_en_cours or attente_texte or camera_en_cours: | ||
| + | return | ||
| + | |||
| + | time.sleep(0.2) # Anti-rebond | ||
| + | if GPIO.input(BOUTON_SUIVANT) == GPIO.LOW: | ||
| + | if texte and index < len(texte): | ||
| + | envoyer_nombre(braille_dict.get(texte[index], 0)) | ||
| + | index += 1 | ||
| + | print(f"🔜 Lettre suivante: {texte[index-1]}") | ||
| + | if index == len(texte): | ||
| + | print("🔁 Fin du texte, lecture complète...") | ||
| + | os.system(f'espeak -v fr "{texte}" --stdout | aplay') | ||
| + | demander_nouveau_texte() | ||
| + | |||
| + | def lancer_lecon(): | ||
| + | """Fonction pour lancer la leçon dans un thread séparé.""" | ||
| + | global lecon_en_cours | ||
| + | |||
| + | # Si on attend une saisie de texte, annuler cette attente | ||
| + | global attente_texte | ||
| + | attente_texte = False | ||
| + | |||
| + | # Marquer le début de la leçon | ||
| + | lecon_en_cours = True | ||
| + | print("📖 Début de la leçon...") | ||
| + | os.system('espeak -v fr "Début de la leçon" --stdout | aplay') | ||
| + | |||
| + | # Parcourir l'alphabet | ||
| + | for lettre in "abcdefghijklmnopqrstuvwxyz": | ||
| + | # Vérifier si la leçon a été arrêtée | ||
| + | if not lecon_en_cours: | ||
| + | return | ||
| + | |||
| + | # Afficher et annoncer la lettre | ||
| + | print(f"📝 Leçon : Lettre {lettre.upper()}") | ||
| + | envoyer_nombre(braille_dict[lettre]) | ||
| + | |||
| + | # Attendre 8 secondes, en vérifiant si la leçon a été arrêtée | ||
| + | debut = time.time() | ||
| + | while time.time() - debut < 8: | ||
| + | if not lecon_en_cours: | ||
| + | return # Sortir immédiatement si la leçon a été arrêtée | ||
| + | time.sleep(0.1) | ||
| + | |||
| + | # Fin normale de la leçon | ||
| + | print("✅ Leçon terminée !") | ||
| + | os.system('espeak -v fr "Leçon terminée" --stdout | aplay') | ||
| + | lecon_en_cours = False | ||
| + | |||
| + | # Proposer d'entrer un nouveau texte sans bloquer | ||
| + | demander_nouveau_texte() | ||
| + | |||
| + | def lancer_exercice(): | ||
| + | """Fonction pour lancer l'exercice dans un thread séparé.""" | ||
| + | global exercice_en_cours | ||
| + | |||
| + | # Si on attend une saisie de texte, annuler cette attente | ||
| + | global attente_texte | ||
| + | attente_texte = False | ||
| + | |||
| + | # Marquer le début de l'exercice | ||
| + | exercice_en_cours = True | ||
| + | print("🎲 Début de l'exercice...") | ||
| + | os.system('espeak -v fr "Début de l\'exercice" --stdout | aplay') | ||
| + | |||
| + | # Préparer les lettres dans un ordre aléatoire | ||
| + | lettres = list("abcdefghijklmnopqrstuvwxyz") | ||
| + | random.shuffle(lettres) | ||
| + | |||
| + | # Parcourir les lettres aléatoires | ||
| + | for lettre in lettres: | ||
| + | # Vérifier si l'exercice a été arrêté | ||
| + | if not exercice_en_cours: | ||
| + | return | ||
| + | |||
| + | # Afficher et annoncer la lettre avec délai de 2 secondes | ||
| + | print(f"📝 Exercice : Lettre {lettre.upper()}") | ||
| + | envoyer_nombre(braille_dict[lettre], mode="exercice") | ||
| + | |||
| + | # Attendre le reste des 8 secondes (moins les 2 secondes d'attente déjà écoulées), | ||
| + | # en vérifiant si l'exercice a été arrêté | ||
| + | debut = time.time() | ||
| + | while time.time() - debut < 6: # 8 - 2 = 6 secondes restantes | ||
| + | if not exercice_en_cours: | ||
| + | return # Sortir immédiatement si l'exercice a été arrêté | ||
| + | time.sleep(0.1) | ||
| + | |||
| + | # Fin normale de l'exercice | ||
| + | print("🏆 Exercice terminé !") | ||
| + | os.system('espeak -v fr "Exercice terminé" --stdout | aplay') | ||
| + | exercice_en_cours = False | ||
| + | |||
| + | # Proposer d'entrer un nouveau texte sans bloquer | ||
| + | demander_nouveau_texte() | ||
| + | |||
| + | def capturer_et_lire_texte(): | ||
| + | """Fonction pour capturer une image avec la caméra et en extraire le texte.""" | ||
| + | global camera_en_cours, texte, index, attente_texte | ||
| + | |||
| + | # Si on attend une saisie de texte, annuler cette attente | ||
| + | attente_texte = False | ||
| + | |||
| + | # Marquer le début de la capture | ||
| + | camera_en_cours = True | ||
| + | print("📸 Préparation de la capture...") | ||
| + | os.system('espeak -v fr "Préparation de la capture" --stdout | aplay') | ||
| + | |||
| + | # Guide de positionnement avec le capteur ultrason | ||
| + | if guide_position(): | ||
| + | # Capture de l'image (réduction du temps à 3s car on a déjà guidé l'utilisateur) | ||
| + | print("📸 Capture en cours...") | ||
| + | os.system('espeak -v fr "Capture en cours" --stdout | aplay') | ||
| + | os.system(f"libcamera-jpeg -o {IMAGE_PATH} -t 3000 --width 1920 --height 1080 --quality 100") | ||
| + | |||
| + | # Vérifier si l'image existe | ||
| + | if not os.path.exists(IMAGE_PATH): | ||
| + | print("❌ Erreur : L'image n'a pas été capturée.") | ||
| + | os.system('espeak -v fr "Erreur de capture" --stdout | aplay') | ||
| + | camera_en_cours = False | ||
| + | return | ||
| + | |||
| + | print("⚡ Optimisation de l'image...") | ||
| + | try: | ||
| + | # Chargement et conversion en niveaux de gris | ||
| + | image = cv2.imread(IMAGE_PATH) | ||
| + | gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | ||
| + | |||
| + | # Correction de l'orientation | ||
| + | try: | ||
| + | osd = pytesseract.image_to_osd(gray) | ||
| + | angle = int(osd.split("\n")[1].split(":")[1].strip()) | ||
| + | if angle != 0: | ||
| + | (h, w) = gray.shape[:2] | ||
| + | center = (w // 2, h // 2) | ||
| + | M = cv2.getRotationMatrix2D(center, -angle, 1.0) | ||
| + | gray = cv2.warpAffine(gray, M, (w, h)) | ||
| + | except: | ||
| + | print("⚠ Impossible de détecter l'orientation, passage à l'étape suivante.") | ||
| + | |||
| + | # Prétraitement (amélioration du contraste et du bruit) | ||
| + | gray = cv2.GaussianBlur(gray, (3, 3), 0) | ||
| + | gray = cv2.equalizeHist(gray) # Augmentation du contraste | ||
| + | gray = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 10) | ||
| + | |||
| + | # Détection des contours pour recadrer uniquement le texte | ||
| + | print("📐 Détection du texte...") | ||
| + | edges = cv2.Canny(gray, 50, 150) | ||
| + | contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | ||
| + | |||
| + | filtered_contours = [c for c in contours if cv2.contourArea(c) > 1000] | ||
| + | |||
| + | if filtered_contours: | ||
| + | x, y, w, h = cv2.boundingRect(cv2.convexHull(np.vstack(filtered_contours))) | ||
| + | cropped = image[y:y+h, x:x+w] | ||
| + | cropped = cv2.resize(cropped, None, fx=1.5, fy=1.5, interpolation=cv2.INTER_LINEAR) | ||
| + | cv2.imwrite(CROPPED_PATH, cropped) | ||
| + | else: | ||
| + | print("⚠ Aucun texte clairement détecté, extraction sur l'image entière.") | ||
| + | cropped = image | ||
| + | |||
| + | # OCR optimisé | ||
| + | print("🔍 Extraction du texte...") | ||
| + | detected_text = pytesseract.image_to_string(cropped, lang="eng+fra", config="--psm 6 --oem 3").strip() | ||
| + | |||
| + | # Nettoyer le texte détecté (supprimer les caractères spéciaux et garder lettres, chiffres et espaces) | ||
| + | cleaned_text = ''.join(c.lower() for c in detected_text if c.isalnum() or c.isspace()) | ||
| + | |||
| + | # Si le texte est vide après nettoyage, afficher un message | ||
| + | if not cleaned_text: | ||
| + | print("⚠ Aucun texte détecté après nettoyage.") | ||
| + | os.system('espeak -v fr "Aucun texte détecté" --stdout | aplay') | ||
| + | camera_en_cours = False | ||
| + | return | ||
| + | |||
| + | # Afficher le texte détecté | ||
| + | print(f"\n📄 Texte détecté : {cleaned_text}") | ||
| + | os.system(f'espeak -v fr "Texte détecté" --stdout | aplay') | ||
| + | |||
| + | # Mettre à jour le texte global et réinitialiser l'index | ||
| + | texte = cleaned_text | ||
| + | index = 0 | ||
| + | print(f"🔠 Nouveau texte : {texte}") | ||
| + | print("📌 Appuyez sur le bouton 'Suivant' pour parcourir le texte.") | ||
| + | |||
| + | except Exception as e: | ||
| + | print(f"❌ Erreur lors du traitement de l'image : {str(e)}") | ||
| + | os.system('espeak -v fr "Erreur de traitement" --stdout | aplay') | ||
| + | else: | ||
| + | print("❌ Positionnement annulé.") | ||
| + | os.system('espeak -v fr "Positionnement annulé" --stdout | aplay') | ||
| + | |||
| + | # Marquer la fin de la capture | ||
| + | camera_en_cours = False | ||
| + | |||
| + | def bouton_lecon_presse(channel): | ||
| + | """Fonction appelée lorsque le bouton leçon est pressé.""" | ||
| + | global lecon_en_cours | ||
| + | |||
| + | # Ne pas réagir si un autre mode est en cours | ||
| + | if exercice_en_cours or camera_en_cours: | ||
| + | return | ||
| + | |||
| + | time.sleep(0.2) # Anti-rebond | ||
| + | if GPIO.input(BOUTON_LECON) == GPIO.LOW: | ||
| + | # Basculer l'état de la leçon | ||
| + | if lecon_en_cours: | ||
| + | lecon_en_cours = False | ||
| + | print("📖 Leçon arrêtée par l'utilisateur.") | ||
| + | os.system('espeak -v fr "Leçon arrêtée" --stdout | aplay') | ||
| + | # La leçon est arrêtée, sera nettoyée dans le thread | ||
| + | else: | ||
| + | # Lancer la leçon dans un thread séparé | ||
| + | t = threading.Thread(target=lancer_lecon) | ||
| + | t.daemon = True # Le thread s'arrêtera quand le programme principal s'arrête | ||
| + | t.start() | ||
| + | |||
| + | def bouton_exercice_presse(channel): | ||
| + | """Fonction appelée lorsque le bouton exercice est pressé.""" | ||
| + | global exercice_en_cours | ||
| + | |||
| + | # Ne pas réagir si un autre mode est en cours | ||
| + | if lecon_en_cours or camera_en_cours: | ||
| + | return | ||
| + | |||
| + | time.sleep(0.2) # Anti-rebond | ||
| + | if GPIO.input(BOUTON_EXERCICE) == GPIO.LOW: | ||
| + | # Basculer l'état de l'exercice | ||
| + | if exercice_en_cours: | ||
| + | exercice_en_cours = False | ||
| + | print("🎲 Exercice arrêté par l'utilisateur.") | ||
| + | os.system('espeak -v fr "Exercice arrêté" --stdout | aplay') | ||
| + | # L'exercice est arrêté, sera nettoyé dans le thread | ||
| + | else: | ||
| + | # Lancer l'exercice dans un thread séparé | ||
| + | t = threading.Thread(target=lancer_exercice) | ||
| + | t.daemon = True # Le thread s'arrêtera quand le programme principal s'arrête | ||
| + | t.start() | ||
| + | |||
| + | def bouton_camera_presse(channel): | ||
| + | """Fonction appelée lorsque le bouton caméra est pressé.""" | ||
| + | global camera_en_cours | ||
| + | |||
| + | # Ne pas réagir si un autre mode est en cours | ||
| + | if lecon_en_cours or exercice_en_cours or camera_en_cours: | ||
| + | return | ||
| + | |||
| + | time.sleep(0.2) # Anti-rebond | ||
| + | if GPIO.input(BOUTON_CAMERA) == GPIO.LOW: | ||
| + | # Lancer la capture dans un thread séparé | ||
| + | t = threading.Thread(target=capturer_et_lire_texte) | ||
| + | t.daemon = True # Le thread s'arrêtera quand le programme principal s'arrête | ||
| + | t.start() | ||
| + | |||
| + | # Supprimer les détecteurs d'événements existants s'il y en a | ||
| + | try: | ||
| + | GPIO.remove_event_detect(BOUTON_SUIVANT) | ||
| + | GPIO.remove_event_detect(BOUTON_PRECEDENT) | ||
| + | GPIO.remove_event_detect(BOUTON_LECON) | ||
| + | GPIO.remove_event_detect(BOUTON_EXERCICE) | ||
| + | GPIO.remove_event_detect(BOUTON_CAMERA) | ||
| + | except: | ||
| + | pass | ||
| + | |||
| + | # Configuration des détecteurs d'événements | ||
| + | GPIO.add_event_detect(BOUTON_SUIVANT, GPIO.FALLING, callback=bouton_suivant, bouncetime=500) | ||
| + | GPIO.add_event_detect(BOUTON_PRECEDENT, GPIO.FALLING, callback=bouton_precedent, bouncetime=500) | ||
| + | GPIO.add_event_detect(BOUTON_LECON, GPIO.FALLING, callback=bouton_lecon_presse, bouncetime=500) | ||
| + | GPIO.add_event_detect(BOUTON_EXERCICE, GPIO.FALLING, callback=bouton_exercice_presse, bouncetime=500) | ||
| + | GPIO.add_event_detect(BOUTON_CAMERA, GPIO.FALLING, callback=bouton_camera_presse, bouncetime=500) | ||
| + | |||
| + | # Initialisation du capteur ultrason (attendre qu'il se stabilise) | ||
| + | print("⚙️ Initialisation du capteur ultrason...") | ||
| + | time.sleep(0.5) | ||
| + | GPIO.output(TRIG, False) | ||
| + | time.sleep(0.5) | ||
| + | |||
| + | # Afficher les instructions au démarrage | ||
| + | print("👋 Bienvenue dans l'application d'apprentissage du Braille") | ||
| + | print("📋 Instructions:") | ||
| + | print(" - Bouton LEÇON: Apprendre l'alphabet (8s par lettre)") | ||
| + | print(" - Bouton EXERCICE: Pratiquer avec des lettres aléatoires (2s de réflexion, puis annonce)") | ||
| + | print(" - Bouton SUIVANT: Afficher la lettre suivante du texte") | ||
| + | print(" - Bouton PRÉCÉDENT: Afficher la lettre précédente du texte") | ||
| + | print(" - Bouton CAMÉRA: Guide le positionnement, puis capture une image et extrait le texte") | ||
| + | print("⚠️ Appuyez à nouveau sur LEÇON/EXERCICE pour arrêter à tout moment") | ||
| + | print(f"🔠 Texte par défaut : {texte}") | ||
| + | print("📌 Appuyez sur le bouton 'Suivant' pour parcourir le texte, ou choisissez un mode.") | ||
| + | |||
| + | # Boucle principale pour maintenir le programme actif | ||
| + | try: | ||
| + | while True: | ||
| + | time.sleep(0.1) | ||
| + | except KeyboardInterrupt: | ||
| + | print("\n🛑 Arrêt du programme.") | ||
| + | GPIO.cleanup() | ||
| + | </syntaxhighlight> ====</translate> | ||
|Step_Picture_00=Syst_me_de_traduction_et_d_apprentissage_du_Braille_nm_arduino-front.jpg | |Step_Picture_00=Syst_me_de_traduction_et_d_apprentissage_du_Braille_nm_arduino-front.jpg | ||
|Step_Picture_01=Syst_me_de_traduction_et_d_apprentissage_du_Braille_91zSu44_34L._AC_UF1000_1000_QL80_.jpg | |Step_Picture_01=Syst_me_de_traduction_et_d_apprentissage_du_Braille_91zSu44_34L._AC_UF1000_1000_QL80_.jpg | ||
|Step_Picture_02=Syst_me_de_traduction_et_d_apprentissage_du_Braille_images_1_.jpeg | |Step_Picture_02=Syst_me_de_traduction_et_d_apprentissage_du_Braille_images_1_.jpeg | ||
|Step_Picture_02_annotation={"version":"3.5.0","objects":[{"type":"image","version":"3.5.0","originX":"left","originY":"top","left":-41,"top":7,"width":251,"height":200,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeMiterLimit":4,"scaleX":2.7,"scaleY":2.7,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"crossOrigin":"","cropX":0,"cropY":0,"src":"https://wikifab.org/images/7/7e/Syst_me_de_traduction_et_d_apprentissage_du_Braille_images_1_.jpeg","filters":[]}],"height":478.15384615384613,"width":600} | |Step_Picture_02_annotation={"version":"3.5.0","objects":[{"type":"image","version":"3.5.0","originX":"left","originY":"top","left":-41,"top":7,"width":251,"height":200,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeMiterLimit":4,"scaleX":2.7,"scaleY":2.7,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"crossOrigin":"","cropX":0,"cropY":0,"src":"https://wikifab.org/images/7/7e/Syst_me_de_traduction_et_d_apprentissage_du_Braille_images_1_.jpeg","filters":[]}],"height":478.15384615384613,"width":600} | ||
| + | }} | ||
| + | {{Tuto Step | ||
| + | |Step_Title=<translate>Système d'interface homme-machine</translate> | ||
| + | |Step_Content=<translate>Le système est équipé de '''5 boutons physiques''' qui permettent de contrôler les différents modes d'apprentissage et la détection du texte. | ||
| + | |||
| + | -Brancher les bouton aux broches gpio correspondantes grâce au code inséré précédement</translate> | ||
| + | |Step_Picture_00=Syst_me_de_traduction_et_d_apprentissage_du_Braille_raspberry-pi-camera-module-3-12mp-objectif-standard-haute-resolution-sc0872.jpg | ||
| + | |Step_Picture_01=Syst_me_de_traduction_et_d_apprentissage_du_Braille_images.jpeg | ||
}} | }} | ||
{{Tuto Step | {{Tuto Step | ||
|Step_Title=<translate>Système de détection de texte</translate> | |Step_Title=<translate>Système de détection de texte</translate> | ||
|Step_Content=<translate>===='''Le système utilise une caméra et un capteur à ultrasons pour détecter du texte .'''==== | |Step_Content=<translate>===='''Le système utilise une caméra et un capteur à ultrasons pour détecter du texte .'''==== | ||
| − | + | -Se munir du Capteur ultrason et le brancher sur la broche gpio (regardez le code rasperypi pour savoir ) | |
| − | + | - Brancher la camera à la Raspberry Pi, il y a un tuto [https://www.gotronic.fr/pj2-tutopicam-1585.pdf?srsltid=AfmBOorFOpsASuNtAhuMkrhdSImstiAlyNY9y20oJxfODo4Vyu3Cx9X6 ici] | |
| + | |||
| + | -Lancer en premier lieu sur raspery pi</translate> | ||
|Step_Picture_00=Syst_me_de_traduction_et_d_apprentissage_du_Braille_capteur-de-distance-ultrason-hc-sr04.jpg | |Step_Picture_00=Syst_me_de_traduction_et_d_apprentissage_du_Braille_capteur-de-distance-ultrason-hc-sr04.jpg | ||
|Step_Picture_01=Syst_me_de_traduction_et_d_apprentissage_du_Braille_raspberry-pi-camera-module-3-12mp-objectif-standard-haute-resolution-sc0872.jpg | |Step_Picture_01=Syst_me_de_traduction_et_d_apprentissage_du_Braille_raspberry-pi-camera-module-3-12mp-objectif-standard-haute-resolution-sc0872.jpg | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
}} | }} | ||
{{Tuto Step | {{Tuto Step | ||
| Ligne 113 : | Ligne 657 : | ||
}} | }} | ||
{{PageLang | {{PageLang | ||
| − | |||
|SourceLanguage=none | |SourceLanguage=none | ||
|IsTranslation=0 | |IsTranslation=0 | ||
| + | |Language=fr | ||
}} | }} | ||
{{Tuto Status | {{Tuto Status | ||
|Complete=Published | |Complete=Published | ||
}} | }} | ||
Auteur
GOACOLO | Dernière modification 26/05/2025 par Yawen
aveugles, Braille, Traduction, Apprentissage Syst_me_de_traduction_et_d_apprentissage_du_Braille_20250526_144220_1_.jpg Creation
-Connecter l'ordinateur à la découpeuse laser
-Ouvrir le logiciel Trotek ( Nécessaire à la découpe)
-Ouvrir les pièces à découper en format dxf dans le logiciel
-Optimiser l'espace sur la planche afin d'avoir les moins de perte de matière et déplaçant les pièces
-Lancer la découpe
-Nettoyer les pièces afin d'éviter des tâches dues au bois brûlé.
Servo servos[6]; // Tableau de 6 servos
const int pinsServos[6] = {3, 5, 6, 9, 10, 11}; // Broches des servos
unsigned long tempsServo = 0;
bool servosActifs = false;
void setup() {
Serial.begin(9600); // Initialisation du moniteur série
Serial.println("✅ Arduino prêt, en attente des commandes I2C...");
Wire.begin(8); // Arduino en esclave I2C (adresse 8)
Wire.onReceive(recevoirCommande);
// Initialiser les servos à la position souhaitée (par exemple, 45°)
for (int i = 0; i < 6; i++) {
servos[i].attach(pinsServos[i]);
servos[i].write(45); // Position initiale (en bas)
}
}
void loop() {
if (servosActifs && millis() - tempsServo >= 3000) {
Serial.println("🔄 Retour des servos à la position initiale.");
for (int i = 0; i < 6; i++) {
servos[i].write(45); // Retour à la position initiale (en bas)
}
servosActifs = false;
}
}
void recevoirCommande(int nombreOctets) {
if (Wire.available()) {
int valeur = Wire.read(); // Lire le nombre envoyé par le Raspberry Pi
Serial.print("📩 Commande reçue: ");
Serial.println(valeur, BIN); // Afficher la valeur en binaire (code Braille)
// Déplacer chaque servo un par un avec un petit délai
for (int i = 0; i < 6; i++) {
if (valeur & (1 << (5 - i))) { // Vérifier si le bit i est actif
Serial.print("🔼 Servo ");
Serial.print(i);
Serial.println(" activé.");
servos[i].write(135); // Position haute (en haut)
} else {
Serial.print("🔽 Servo ");
Serial.print(i);
Serial.println(" désactivé.");
servos[i].write(45); // Position initiale (en bas)
}
delay(100); // Petite pause entre les déplacements des servos
}
tempsServo = millis(); // Démarrer le timer pour retour automatique
servosActifs = true;
}
}
import smbus
import time
import RPi.GPIO as GPIO
import random
import os
import threading
import cv2
import pytesseract
import numpy as np
I2C_ADDR = 8
bus = smbus.SMBus(1)
# GPIO des boutons
BOUTON_PRECEDENT = 17
BOUTON_SUIVANT = 27
BOUTON_LECON = 23
BOUTON_EXERCICE = 24
BOUTON_CAMERA = 22 # Bouton pour la caméra
# GPIO du capteur ultrason HCSR04
TRIG = 5 # GPIO pour le trigger du capteur
ECHO = 6 # GPIO pour l'echo du capteur
# Configuration des GPIO
try:
GPIO.cleanup()
except:
pass
GPIO.setmode(GPIO.BCM)
GPIO.setup(BOUTON_PRECEDENT, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(BOUTON_SUIVANT, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(BOUTON_LECON, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(BOUTON_EXERCICE, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(BOUTON_CAMERA, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# Configuration du capteur ultrason
GPIO.setup(TRIG, GPIO.OUT)
GPIO.setup(ECHO, GPIO.IN)
GPIO.output(TRIG, False) # S'assurer que le trigger est bas au démarrage
# Dictionnaire Braille
braille_dict = {
"a": 0b100000, "b": 0b110000, "c": 0b101000, "d": 0b101100,
"e": 0b100100, "f": 0b111000, "g": 0b111100, "h": 0b110100,
"i": 0b011000, "j": 0b011100, "k": 0b100010, "l": 0b110010,
"m": 0b101010, "n": 0b101110, "o": 0b100110, "p": 0b111010,
"q": 0b111110, "r": 0b110110, "s": 0b011010, "t": 0b011110,
"u": 0b100011, "v": 0b110011, "w": 0b011101, "x": 0b101011,
"y": 0b101111, "z": 0b100111, " ": 0b000000
}
texte = "bonjour" # Texte par défaut
index = 0
lecon_en_cours = False
exercice_en_cours = False
attente_texte = False # Pour savoir si on attend une saisie de texte
camera_en_cours = False # Pour indiquer si une capture par caméra est en cours
# Paramètres de capture d'image
IMAGE_PATH = "capture.jpg"
CROPPED_PATH = "cropped.jpg"
DISTANCE_OPTIMALE = 15 # Distance optimale en cm
MARGE_DISTANCE = 2.5 # Marge de tolérance en cm (+/-)
def mesurer_distance():
"""Mesure la distance avec le capteur ultrason HCSR04."""
# Envoyer une impulsion de 10µs au trigger
GPIO.output(TRIG, True)
time.sleep(0.00001) # 10µs
GPIO.output(TRIG, False)
# Attendre que l'écho commence
start_time = time.time()
timeout = start_time + 1.0 # Timeout de 1 seconde
while GPIO.input(ECHO) == 0:
if time.time() > timeout:
return -1 # Erreur: pas de signal
pulse_start = time.time()
# Attendre que l'écho se termine
while GPIO.input(ECHO) == 1:
if time.time() > timeout:
return -1 # Erreur: signal trop long
pulse_end = time.time()
# Calculer la durée de l'impulsion
pulse_duration = pulse_end - pulse_start
# Calculer la distance (vitesse du son = 34300 cm/s)
# Diviser par 2 car le signal fait l'aller-retour
distance = (pulse_duration * 34300) / 2
return round(distance, 1) # Arrondir à 1 décimale
def guide_position():
"""Guide l'utilisateur pour positionner correctement le texte."""
global camera_en_cours
print("📏 Positionnement du texte...")
os.system('espeak -v fr "Positionnez le texte" --stdout | aplay')
# Attendre que la distance soit stable dans la plage optimale
position_stable = False
nb_mesures_stables = 0
while camera_en_cours and not position_stable:
distance = mesurer_distance()
if distance < 0:
print("⚠️ Erreur de mesure de distance")
time.sleep(0.5)
continue
print(f"📏 Distance mesurée : {distance} cm")
if distance < DISTANCE_OPTIMALE - MARGE_DISTANCE:
# Trop proche
os.system('espeak -v fr "Reculez" --stdout | aplay')
nb_mesures_stables = 0
elif distance > DISTANCE_OPTIMALE + MARGE_DISTANCE:
# Trop loin
os.system('espeak -v fr "Avancez" --stdout | aplay')
nb_mesures_stables = 0
else:
# Distance correcte
print(f"✅ Distance correcte : {distance} cm")
nb_mesures_stables += 1
# Si 3 mesures consécutives sont dans la plage, on considère la position comme stable
if nb_mesures_stables >= 3:
position_stable = True
os.system('espeak -v fr "Position correcte" --stdout | aplay')
time.sleep(0.5) # Attendre avant la prochaine mesure
return position_stable
def envoyer_nombre(nombre, mode=None):
"""Envoie une lettre en Braille et annonce (avec délai optionnel en mode exercice)."""
try:
lettre = [key for key, val in braille_dict.items() if val == nombre][0]
bus.write_byte(I2C_ADDR, nombre)
print(f"✅ Lettre envoyée: {lettre.upper()} ({bin(nombre)})")
# Si c'est le mode exercice, attendre 2 secondes avant d'énoncer la lettre
if mode == "exercice":
# Vérifier périodiquement si l'exercice a été arrêté pendant l'attente
debut = time.time()
while time.time() - debut < 2 and exercice_en_cours:
time.sleep(0.1)
# Ne prononcer la lettre que si l'exercice est toujours en cours
if exercice_en_cours:
os.system(f'espeak -v fr "{lettre}" --stdout | aplay')
else:
# Pour les autres modes, énoncer la lettre immédiatement
os.system(f'espeak -v fr "{lettre}" --stdout | aplay')
except IOError:
print("❌ Erreur de communication avec l'Arduino")
except IndexError:
print("❌ Erreur: code braille non trouvé dans le dictionnaire")
except Exception as e:
print(f"❌ Erreur inattendue: {str(e)}")
def demander_nouveau_texte():
"""Demande un nouveau texte et réinitialise l'index."""
global texte, index, attente_texte
# Annonce vocale
os.system('espeak -v fr "Entrez un nouveau texte" --stdout | aplay')
# Lancer un thread pour attendre la saisie sans bloquer le reste du programme
def attendre_saisie():
global texte, index, attente_texte
nouveau_texte = input("\n🆕 Entrez un texte (ou appuyez sur Entrée pour garder 'bonjour') : ").lower()
if nouveau_texte: # Si l'utilisateur a entré quelque chose
texte = nouveau_texte
index = 0
print(f"🔠 Texte actuel : {texte}")
print("📌 Appuyez sur le bouton 'Suivant' pour parcourir le texte, ou sur 'Leçon'/'Exercice' pour ces modes.")
attente_texte = False # Fin de l'attente
attente_texte = True # Début de l'attente
t = threading.Thread(target=attendre_saisie)
t.daemon = True
t.start()
def bouton_precedent(channel):
"""Affiche la lettre précédente pendant la lecture du texte."""
global index
# Ne pas exécuter si un mode est en cours ou si on attend une saisie
if lecon_en_cours or exercice_en_cours or attente_texte or camera_en_cours:
return
time.sleep(0.2) # Anti-rebond
if GPIO.input(BOUTON_PRECEDENT) == GPIO.LOW:
if index > 0:
index -= 1
envoyer_nombre(braille_dict.get(texte[index], 0))
print(f"🔙 Lettre précédente: {texte[index]}")
else:
print("🚫 Déjà à la première lettre.")
os.system('espeak -v fr "Première lettre" --stdout | aplay')
def bouton_suivant(channel):
"""Affiche la lettre suivante et lit le mot en entier à la fin."""
global index
# Ne pas exécuter si un mode est en cours ou si on attend une saisie
if lecon_en_cours or exercice_en_cours or attente_texte or camera_en_cours:
return
time.sleep(0.2) # Anti-rebond
if GPIO.input(BOUTON_SUIVANT) == GPIO.LOW:
if texte and index < len(texte):
envoyer_nombre(braille_dict.get(texte[index], 0))
index += 1
print(f"🔜 Lettre suivante: {texte[index-1]}")
if index == len(texte):
print("🔁 Fin du texte, lecture complète...")
os.system(f'espeak -v fr "{texte}" --stdout | aplay')
demander_nouveau_texte()
def lancer_lecon():
"""Fonction pour lancer la leçon dans un thread séparé."""
global lecon_en_cours
# Si on attend une saisie de texte, annuler cette attente
global attente_texte
attente_texte = False
# Marquer le début de la leçon
lecon_en_cours = True
print("📖 Début de la leçon...")
os.system('espeak -v fr "Début de la leçon" --stdout | aplay')
# Parcourir l'alphabet
for lettre in "abcdefghijklmnopqrstuvwxyz":
# Vérifier si la leçon a été arrêtée
if not lecon_en_cours:
return
# Afficher et annoncer la lettre
print(f"📝 Leçon : Lettre {lettre.upper()}")
envoyer_nombre(braille_dict[lettre])
# Attendre 8 secondes, en vérifiant si la leçon a été arrêtée
debut = time.time()
while time.time() - debut < 8:
if not lecon_en_cours:
return # Sortir immédiatement si la leçon a été arrêtée
time.sleep(0.1)
# Fin normale de la leçon
print("✅ Leçon terminée !")
os.system('espeak -v fr "Leçon terminée" --stdout | aplay')
lecon_en_cours = False
# Proposer d'entrer un nouveau texte sans bloquer
demander_nouveau_texte()
def lancer_exercice():
"""Fonction pour lancer l'exercice dans un thread séparé."""
global exercice_en_cours
# Si on attend une saisie de texte, annuler cette attente
global attente_texte
attente_texte = False
# Marquer le début de l'exercice
exercice_en_cours = True
print("🎲 Début de l'exercice...")
os.system('espeak -v fr "Début de l\'exercice" --stdout | aplay')
# Préparer les lettres dans un ordre aléatoire
lettres = list("abcdefghijklmnopqrstuvwxyz")
random.shuffle(lettres)
# Parcourir les lettres aléatoires
for lettre in lettres:
# Vérifier si l'exercice a été arrêté
if not exercice_en_cours:
return
# Afficher et annoncer la lettre avec délai de 2 secondes
print(f"📝 Exercice : Lettre {lettre.upper()}")
envoyer_nombre(braille_dict[lettre], mode="exercice")
# Attendre le reste des 8 secondes (moins les 2 secondes d'attente déjà écoulées),
# en vérifiant si l'exercice a été arrêté
debut = time.time()
while time.time() - debut < 6: # 8 - 2 = 6 secondes restantes
if not exercice_en_cours:
return # Sortir immédiatement si l'exercice a été arrêté
time.sleep(0.1)
# Fin normale de l'exercice
print("🏆 Exercice terminé !")
os.system('espeak -v fr "Exercice terminé" --stdout | aplay')
exercice_en_cours = False
# Proposer d'entrer un nouveau texte sans bloquer
demander_nouveau_texte()
def capturer_et_lire_texte():
"""Fonction pour capturer une image avec la caméra et en extraire le texte."""
global camera_en_cours, texte, index, attente_texte
# Si on attend une saisie de texte, annuler cette attente
attente_texte = False
# Marquer le début de la capture
camera_en_cours = True
print("📸 Préparation de la capture...")
os.system('espeak -v fr "Préparation de la capture" --stdout | aplay')
# Guide de positionnement avec le capteur ultrason
if guide_position():
# Capture de l'image (réduction du temps à 3s car on a déjà guidé l'utilisateur)
print("📸 Capture en cours...")
os.system('espeak -v fr "Capture en cours" --stdout | aplay')
os.system(f"libcamera-jpeg -o {IMAGE_PATH} -t 3000 --width 1920 --height 1080 --quality 100")
# Vérifier si l'image existe
if not os.path.exists(IMAGE_PATH):
print("❌ Erreur : L'image n'a pas été capturée.")
os.system('espeak -v fr "Erreur de capture" --stdout | aplay')
camera_en_cours = False
return
print("⚡ Optimisation de l'image...")
try:
# Chargement et conversion en niveaux de gris
image = cv2.imread(IMAGE_PATH)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Correction de l'orientation
try:
osd = pytesseract.image_to_osd(gray)
angle = int(osd.split("\n")[1].split(":")[1].strip())
if angle != 0:
(h, w) = gray.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, -angle, 1.0)
gray = cv2.warpAffine(gray, M, (w, h))
except:
print("⚠ Impossible de détecter l'orientation, passage à l'étape suivante.")
# Prétraitement (amélioration du contraste et du bruit)
gray = cv2.GaussianBlur(gray, (3, 3), 0)
gray = cv2.equalizeHist(gray) # Augmentation du contraste
gray = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 10)
# Détection des contours pour recadrer uniquement le texte
print("📐 Détection du texte...")
edges = cv2.Canny(gray, 50, 150)
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
filtered_contours = [c for c in contours if cv2.contourArea(c) > 1000]
if filtered_contours:
x, y, w, h = cv2.boundingRect(cv2.convexHull(np.vstack(filtered_contours)))
cropped = image[y:y+h, x:x+w]
cropped = cv2.resize(cropped, None, fx=1.5, fy=1.5, interpolation=cv2.INTER_LINEAR)
cv2.imwrite(CROPPED_PATH, cropped)
else:
print("⚠ Aucun texte clairement détecté, extraction sur l'image entière.")
cropped = image
# OCR optimisé
print("🔍 Extraction du texte...")
detected_text = pytesseract.image_to_string(cropped, lang="eng+fra", config="--psm 6 --oem 3").strip()
# Nettoyer le texte détecté (supprimer les caractères spéciaux et garder lettres, chiffres et espaces)
cleaned_text = ''.join(c.lower() for c in detected_text if c.isalnum() or c.isspace())
# Si le texte est vide après nettoyage, afficher un message
if not cleaned_text:
print("⚠ Aucun texte détecté après nettoyage.")
os.system('espeak -v fr "Aucun texte détecté" --stdout | aplay')
camera_en_cours = False
return
# Afficher le texte détecté
print(f"\n📄 Texte détecté : {cleaned_text}")
os.system(f'espeak -v fr "Texte détecté" --stdout | aplay')
# Mettre à jour le texte global et réinitialiser l'index
texte = cleaned_text
index = 0
print(f"🔠 Nouveau texte : {texte}")
print("📌 Appuyez sur le bouton 'Suivant' pour parcourir le texte.")
except Exception as e:
print(f"❌ Erreur lors du traitement de l'image : {str(e)}")
os.system('espeak -v fr "Erreur de traitement" --stdout | aplay')
else:
print("❌ Positionnement annulé.")
os.system('espeak -v fr "Positionnement annulé" --stdout | aplay')
# Marquer la fin de la capture
camera_en_cours = False
def bouton_lecon_presse(channel):
"""Fonction appelée lorsque le bouton leçon est pressé."""
global lecon_en_cours
# Ne pas réagir si un autre mode est en cours
if exercice_en_cours or camera_en_cours:
return
time.sleep(0.2) # Anti-rebond
if GPIO.input(BOUTON_LECON) == GPIO.LOW:
# Basculer l'état de la leçon
if lecon_en_cours:
lecon_en_cours = False
print("📖 Leçon arrêtée par l'utilisateur.")
os.system('espeak -v fr "Leçon arrêtée" --stdout | aplay')
# La leçon est arrêtée, sera nettoyée dans le thread
else:
# Lancer la leçon dans un thread séparé
t = threading.Thread(target=lancer_lecon)
t.daemon = True # Le thread s'arrêtera quand le programme principal s'arrête
t.start()
def bouton_exercice_presse(channel):
"""Fonction appelée lorsque le bouton exercice est pressé."""
global exercice_en_cours
# Ne pas réagir si un autre mode est en cours
if lecon_en_cours or camera_en_cours:
return
time.sleep(0.2) # Anti-rebond
if GPIO.input(BOUTON_EXERCICE) == GPIO.LOW:
# Basculer l'état de l'exercice
if exercice_en_cours:
exercice_en_cours = False
print("🎲 Exercice arrêté par l'utilisateur.")
os.system('espeak -v fr "Exercice arrêté" --stdout | aplay')
# L'exercice est arrêté, sera nettoyé dans le thread
else:
# Lancer l'exercice dans un thread séparé
t = threading.Thread(target=lancer_exercice)
t.daemon = True # Le thread s'arrêtera quand le programme principal s'arrête
t.start()
def bouton_camera_presse(channel):
"""Fonction appelée lorsque le bouton caméra est pressé."""
global camera_en_cours
# Ne pas réagir si un autre mode est en cours
if lecon_en_cours or exercice_en_cours or camera_en_cours:
return
time.sleep(0.2) # Anti-rebond
if GPIO.input(BOUTON_CAMERA) == GPIO.LOW:
# Lancer la capture dans un thread séparé
t = threading.Thread(target=capturer_et_lire_texte)
t.daemon = True # Le thread s'arrêtera quand le programme principal s'arrête
t.start()
# Supprimer les détecteurs d'événements existants s'il y en a
try:
GPIO.remove_event_detect(BOUTON_SUIVANT)
GPIO.remove_event_detect(BOUTON_PRECEDENT)
GPIO.remove_event_detect(BOUTON_LECON)
GPIO.remove_event_detect(BOUTON_EXERCICE)
GPIO.remove_event_detect(BOUTON_CAMERA)
except:
pass
# Configuration des détecteurs d'événements
GPIO.add_event_detect(BOUTON_SUIVANT, GPIO.FALLING, callback=bouton_suivant, bouncetime=500)
GPIO.add_event_detect(BOUTON_PRECEDENT, GPIO.FALLING, callback=bouton_precedent, bouncetime=500)
GPIO.add_event_detect(BOUTON_LECON, GPIO.FALLING, callback=bouton_lecon_presse, bouncetime=500)
GPIO.add_event_detect(BOUTON_EXERCICE, GPIO.FALLING, callback=bouton_exercice_presse, bouncetime=500)
GPIO.add_event_detect(BOUTON_CAMERA, GPIO.FALLING, callback=bouton_camera_presse, bouncetime=500)
# Initialisation du capteur ultrason (attendre qu'il se stabilise)
print("⚙️ Initialisation du capteur ultrason...")
time.sleep(0.5)
GPIO.output(TRIG, False)
time.sleep(0.5)
# Afficher les instructions au démarrage
print("👋 Bienvenue dans l'application d'apprentissage du Braille")
print("📋 Instructions:")
print(" - Bouton LEÇON: Apprendre l'alphabet (8s par lettre)")
print(" - Bouton EXERCICE: Pratiquer avec des lettres aléatoires (2s de réflexion, puis annonce)")
print(" - Bouton SUIVANT: Afficher la lettre suivante du texte")
print(" - Bouton PRÉCÉDENT: Afficher la lettre précédente du texte")
print(" - Bouton CAMÉRA: Guide le positionnement, puis capture une image et extrait le texte")
print("⚠️ Appuyez à nouveau sur LEÇON/EXERCICE pour arrêter à tout moment")
print(f"🔠 Texte par défaut : {texte}")
print("📌 Appuyez sur le bouton 'Suivant' pour parcourir le texte, ou choisissez un mode.")
# Boucle principale pour maintenir le programme actif
try:
while True:
time.sleep(0.1)
except KeyboardInterrupt:
print("\n🛑 Arrêt du programme.")
GPIO.cleanup()
Le système est équipé de 5 boutons physiques qui permettent de contrôler les différents modes d'apprentissage et la détection du texte.
-Brancher les bouton aux broches gpio correspondantes grâce au code inséré précédement
-Se munir du Capteur ultrason et le brancher sur la broche gpio (regardez le code rasperypi pour savoir )
- Brancher la camera à la Raspberry Pi, il y a un tuto ici
-Lancer en premier lieu sur raspery pi
fr none 0 Published
Vous avez entré un nom de page invalide, avec un ou plusieurs caractères suivants :
< > @ ~ : * € £ ` + = / \ | [ ] { } ; ? #