Bonjour, mon projet (tipe de prepa) consiste à suivre une led (représentant une planète) à l'aide d'une caméra OV7670 sans fifo et de 2 moteurs CC (pivots en série).
L'objectif d'utiliser arduino pour détecter le point le + lumineux de l'image et d'envoyer les coordonnées sur python via Firmata. Ensuite, en fonction de x et y, les 2 moteurs tournent à une certaine vitesse pour ramener la led à la position initiale sur l'image. Attention: c'est la caméra qui est fixe sur les moteurs et la led que je fais bouger (simplement la lampe torche de mon smartphone à travers un petit trou avec du carton).
J'ai un gros problème: la caméra sort des données YUV (Y=luminance), donc je garde seulement la luminance ça c'est ok. Le problème c'est que le pixel obtenu par le programme semble toujours random, et la luminance c'est pire. Que la cam soit dans le noir ou avec un lampe juste devant, Y reste autour de 170, ou 140, ou 200, ou même 250 (255 étant le max). La valeur de Y dépend du programme, de l'humeur de la caméra, et je sais pas trop...
J'ai essayé de simplement chercher le pixel (x,y), j'ai essayé en appliquant un seuil (ex Y>180), j'ai essayé d'appliquer un filtre qui fait la moyenne sur un carré de pixel 33; rien à faire.
Quelque chose qui me saoule un peu mais j'y peu rien: mon module caméra ne possède pas de I2C donc impossible de modifier la résolution de base (320240), cela ralentis le traitement mais c'est pas trop grave.
Chose à savoir: j'ai déjà réussi à récuperer des images de la caméra et à les afficher sur l'IDE avec un plugin, j'arrive à envoyer (x,y) via le port série principale de la méga, python controle les moteurs à l'aide de Firmata (Serial1 de la mega et passe à travers un uno pour avec un 2e port usb), le programme python est normalement bon, juste (x,y) sont mauvais donc les moteurs font nimp. Le problème provient surement de la loop arduino.
Je vais donner les programmes, j'espère que vous pourrez m'aider, j'ai jusqu'à ce we dernier délais (je viens de passer 1 semaine complète à tout tester pour résoudre ce problème)
from pyfirmata import ArduinoMega, util
import serial
import time
# Firmata et Serial
board = ArduinoMega('COM15') # Contrôle moteurs
xy_serial = serial.Serial('COM18', 9600, timeout=1) # 1s max d'attente
# Moteurs
motor1_dir = board.digital[3] # Moteur X
motor1_pwm = board.digital[5]
motor2_dir = board.digital[4] # Moteur Y
motor2_pwm = board.digital[6]
motor1_pwm.mode = 3
motor2_pwm.mode = 3
# Image dimensions (OV7670 = 320x240)
IMAGE_WIDTH = 320
IMAGE_HEIGHT = 240
# Données initiales
x0, y0, maxVal = None, None, None # Coordonnées initiales (centre de référence)
X, Y = [], [] # Historique des points
def zone_morte(val, val0, image_dim):
"""Renvoie True si val est dans la zone morte de ±10%"""
margin = image_dim * 0.10
return abs(val - val0) <= margin
def calcul_vitesse(val, val0, image_dim):
"""Renvoie vitesse normalisée entre 0.3 et 1 selon éloignement"""
distance = abs(val - val0)
margin = image_dim * 0.10
max_dist = (image_dim / 2) - margin
if distance <= margin:
return 0 # Zone morte
ratio = min(distance / max_dist, 1)
return round(0.4 + (0.3 * ratio), 2) #Vmax=70%
def direction(val, val0):
"""Renvoie 0 ou 1 pour définir la direction selon position"""
return 1 if val > val0 else 0
def update_moteur(motor_dir, motor_pwm, val, val0, image_dim):
if zone_morte(val, val0, image_dim):
motor_pwm.write(0)
else:
dir_val = direction(val, val0)
vitesse = calcul_vitesse(val, val0, image_dim)
motor_dir.write(dir_val)
motor_pwm.write(vitesse)
# Démarrage
time.sleep(2)
print("Initialisation terminée. Attente coordonnées Arduino...")
try:
while True:
line = xy_serial.readline().decode().strip()
if ',' in line:
try:
x, y, maxVal = map(int, line.split(','))
if x0 is None or y0 is None:
x0, y0 = x, y # Initialisation
print(f"Coordonnée de référence : x0 = {x0}, y0 = {y0}")
else:
X.append(x)
Y.append(y)
print(f"Reçu x = {x}, y = {y}, maxVal = {maxVal}")
# Moteur 1 (X)
#update_moteur(motor1_dir, motor1_pwm, x, x0, IMAGE_WIDTH)
# Moteur 2 (Y)
#update_moteur(motor2_dir, motor2_pwm, y, y0, IMAGE_HEIGHT)
except Exception as e:
print("Erreur de parsing :", line, "|", e)
except KeyboardInterrupt:
print("Arrêt manuel.")
motor1_pwm.write(0)
motor2_pwm.write(0)
board.exit()
#include <Wire.h>
#include <Firmata.h>
int analogInputsToReport = 0;
byte reportPINs[TOTAL_PORTS];
byte previousPINs[TOTAL_PORTS];
byte portConfigInputs[TOTAL_PORTS];
boolean isResetting=false;
#define OV7670_ADDR 0x21 // Adresse I2C de la caméra
const int vsyncPin = 2; // Synchro début d'image
const int mclkPin = 11;
const int pclkPin = 12; // Horloge pixel
const int dataPins[8] = {22, 23, 24, 25, 26, 27, 28, 29}; // D0 à D7
int maxVal = -1; //Initialisation luminance (valeur impossible)
int brightX = 0, brightY = 0;
void setPinModeCallback(byte pin, int mode){
if (Firmata.getPinMode(pin) == PIN_MODE_IGNORE)
return;
if (IS_PIN_DIGITAL(pin)) {
if (mode == INPUT || mode == PIN_MODE_PULLUP) {
portConfigInputs[pin / 8] |= (1 << (pin & 7));
} else {
portConfigInputs[pin / 8] &= ~(1 << (pin & 7));
}
}
Firmata.setPinState(pin, 0);
switch (mode) {
case PIN_MODE_ANALOG:
if (IS_PIN_ANALOG(pin)) {
if (IS_PIN_DIGITAL(pin)) {
pinMode(PIN_TO_DIGITAL(pin), INPUT);
#if ARDUINO <= 100
digitalWrite(PIN_TO_DIGITAL(pin), LOW);
#endif
}
Firmata.setPinMode(pin, PIN_MODE_ANALOG);
}
break;
case INPUT:
if (IS_PIN_DIGITAL(pin)) {
pinMode(PIN_TO_DIGITAL(pin), INPUT);
#if ARDUINO <= 100
digitalWrite(PIN_TO_DIGITAL(pin), LOW);
#endif
Firmata.setPinMode(pin, INPUT);
}
break;
case PIN_MODE_PULLUP:
if (IS_PIN_DIGITAL(pin)) {
pinMode(PIN_TO_DIGITAL(pin), INPUT_PULLUP);
Firmata.setPinMode(pin, PIN_MODE_PULLUP);
Firmata.setPinState(pin, 1);
}
break;
case OUTPUT:
if (IS_PIN_DIGITAL(pin)) {
if (Firmata.getPinMode(pin) == PIN_MODE_PWM) {
digitalWrite(PIN_TO_DIGITAL(pin), LOW);
}
pinMode(PIN_TO_DIGITAL(pin), OUTPUT);
Firmata.setPinMode(pin, OUTPUT);
}
break;
case PIN_MODE_PWM:
if (IS_PIN_PWM(pin)) {
pinMode(PIN_TO_PWM(pin), OUTPUT);
analogWrite(PIN_TO_PWM(pin), 0);
Firmata.setPinMode(pin, PIN_MODE_PWM);
}
break;
case PIN_MODE_SERVO:
if (IS_PIN_DIGITAL(pin)) {
Firmata.setPinMode(pin, PIN_MODE_SERVO);
}
break;
case PIN_MODE_I2C:
if (IS_PIN_I2C(pin)) {
Firmata.setPinMode(pin, PIN_MODE_I2C);
}
break;
case PIN_MODE_SERIAL:
#ifdef FIRMATA_SERIAL_FEATURE
serialFeature.handlePinMode(pin, PIN_MODE_SERIAL);
#endif
break;
default:
Firmata.sendString("Unknown pin mode");
}
}
void analogWriteCallback(byte pin, int value){
if (pin < TOTAL_PINS) {
switch (Firmata.getPinMode(pin)) {
case PIN_MODE_PWM:
if (IS_PIN_PWM(pin))
analogWrite(PIN_TO_PWM(pin), value);
Firmata.setPinState(pin, value);
break;
}
}
}
void digitalWriteCallback(byte port, int value){
byte pin, lastPin, pinValue, mask = 1, pinWriteMask = 0;
if (port < TOTAL_PORTS) {
// create a mask of the pins on this port that are writable.
lastPin = port * 8 + 8;
if (lastPin > TOTAL_PINS) lastPin = TOTAL_PINS;
for (pin = port * 8; pin < lastPin; pin++) {
// do not disturb non-digital pins (eg, Rx & Tx)
if (IS_PIN_DIGITAL(pin)) {
// do not touch pins in PWM, ANALOG, SERVO or other modes
if (Firmata.getPinMode(pin) == OUTPUT || Firmata.getPinMode(pin) == INPUT) {
pinValue = ((byte)value & mask) ? 1 : 0;
if (Firmata.getPinMode(pin) == OUTPUT) {
pinWriteMask |= mask;
} else if (Firmata.getPinMode(pin) == INPUT && pinValue == 1 && Firmata.getPinState(pin) != 1) {
// only handle INPUT here for backwards compatibility
#if ARDUINO > 100
pinMode(pin, INPUT_PULLUP);
#else
// only write to the INPUT pin to enable pullups if Arduino v1.0.0 or earlier
pinWriteMask |= mask;
#endif
}
Firmata.setPinState(pin, pinValue);
}
}
mask = mask << 1;
}
writePort(port, (byte)value, pinWriteMask);
}
}
void systemResetCallback(){
isResetting = true;
// initialize a defalt state
// TODO: option to load config from EEPROM instead of default
#ifdef FIRMATA_SERIAL_FEATURE
serialFeature.reset();
#endif
for (byte i = 0; i < TOTAL_PORTS; i++) {
reportPINs[i] = false; // by default, reporting off
portConfigInputs[i] = 0; // until activated
previousPINs[i] = 0;
}
for (byte i = 0; i < TOTAL_PINS; i++) {
// pins with analog capability default to analog input
// otherwise, pins default to digital output
if (IS_PIN_ANALOG(i)) {
// turns off pullup, configures everything
setPinModeCallback(i, PIN_MODE_ANALOG);
} else if (IS_PIN_DIGITAL(i)) {
// sets the output to 0, configures portConfigInputs
setPinModeCallback(i, OUTPUT);
}
}
// by default, do not report any analog inputs
analogInputsToReport = 0;
/* send digital inputs to set the initial state on the host computer,
* since once in the loop(), this firmware will only send on change */
/*
TODO: this can never execute, since no pins default to digital input
but it will be needed when/if we support EEPROM stored config
for (byte i=0; i < TOTAL_PORTS; i++) {
outputPort(i, readPort(i, portConfigInputs[i]), true);
}
*/
isResetting = false;
}
void waitForVSync() {
while (digitalRead(vsyncPin) == HIGH);
while (digitalRead(vsyncPin) == LOW);
}
int readCameraByte() { //Lecture données pixels
return PINA; // Lecture directe (8 bits)
}
void setupMCLK() { //Horloge virtuel
pinMode(mclkPin, OUTPUT);
TCCR1A = _BV(COM1A0);
TCCR1B = _BV(WGM12) | _BV(CS10);
OCR1A=0;
}
void setup() {
Serial.begin(57600); //Port USB Firmata
Serial1.begin(9600); //2e port pour communication python de x et y (données passant par UNO)
Wire.begin();
DDRA=0x00;
pinMode(vsyncPin, INPUT);
pinMode(pclkPin, INPUT);
for (int i = 0; i < 8; i++) {
pinMode(dataPins[i], INPUT);
}
setupMCLK();
Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION);
Firmata.attach(ANALOG_MESSAGE, analogWriteCallback);
Firmata.attach(DIGITAL_MESSAGE, digitalWriteCallback);
Firmata.attach(SET_PIN_MODE, setPinModeCallback);
Firmata.attach(SYSTEM_RESET, systemResetCallback);
Firmata.begin(Serial);
}
void loop() {
Firmata.processInput(); // Communication avec Python si nécessaire
waitForVSync(); // Attente début d'image
bool isLuminance = true; // On commence par supposer qu’on lit un Y
maxVal=-1;
brightX, brightY = 0,0;
for (int y = 0; y < 240; y+=6) { //Lecture 1 pixel sur 8 pour gagner en rapidité
for (int x = 0; x < 320; x+=8) {
while (digitalRead(pclkPin) == LOW); // Attente front montant PCLK
int pixelVal = readCameraByte(); // Lecture pixel
while (digitalRead(pclkPin) == HIGH); // Fin cycle PCLK
if (isLuminance) {
if (pixelVal > maxVal && pixelVal>100) { //seuil valeur luminance (0-255)
maxVal = pixelVal;
brightX = x;
brightY = y;
}
}
isLuminance = !isLuminance; //On saute les U/V un octet sur deux
}
}
Serial1.print(brightX);
Serial1.print(",");
Serial1.print(brightY);
Serial1.print(",");
Serial1.println(maxVal);
}