Skyduino:~#
Articles
arduino, programmation, projet

[Arduino] Light painting assisté par ordinateur (version 2)

Bonjour tout le monde !

Aujourd’hui j’ai décidé de publier l’article sur mon systéme de light painting assisté par ordinateur.

J’avais prévu de le publier il y a déja plus de 6 mois (oui ça fait pas mal de temps) mais la météo était (et est toujours) contre moi …
Du coup j’ai décidé de publier la partie hardware / software en attendant de pouvoir (un jour) faire les photos finales.

Le principe :

Avec un appareil photo en mode « exposition manuelle » il est possible de dessiner des images avec de la lumière, c’est le principe même du « light painting ».
http://fr.wikipedia.org/wiki/Light_painting

Sauf que faire des dessins à la main ne donne pas des résultats trés propre et précis, à moins d’avoir un plan pré-dessiné et une exposition de longue durée.
C’est pourquoi j’ai bricolé un systéme permettant de dessiner une image en light painting avec l’aide d’un ordinateur.

J’avais déjà fait un premier essai avec 32 led RGB capable d’afficher 8 couleurs différente (l’article est toujours sur le blog).

Dans cette nouvelle version j’utilise 64 led RGB sous la forme de rubans de led « commercial », ce qui me permet d’avoir 4096 couleurs différentes.
De plus je passe d’un montage « maison » affichant des images de 30cm, à un montage affichant des images de 2m de haut !

Le hardware :

Le hardware est constitué :
- un ruban de led RGB (adressable, vendu par adafruit) de 2m (soit 64 RGB led en tout)
- une carte arduino pro mini 5v + module FTDI basic 5v (vendu par sparkfun)
- une pile 5v rechargeable de 1.5A
- une baguette en bois, des morceaux de fils de fer et des élastiques
- des câbles et des borniers pour connecter tout le montage

Afin de pouvoir tenir le ruban de led bien droit, celui est fixé sur une baguette en bois de 2m.
Et est maintenu en place avec des morceaux de fils de fer et des élastiques (Mc Guyver inside).

Pour éviter les problémes de condensation et d’humidité, les extrémités du ruban sont scellé de manière étanche avec de la colle chaude.

Tout le systéme électronique est fixé autour de la baguette en bois afin de pouvoir transporter le tout en toute simplicité.

Pour l’alimentation (de puissance) j’utilise une batterie 1.5A rechargeable avec sortie USB (normalement conçu pour recharger des appareils USB pendant un voyage) avec un câble usb maison bricolé pour « piquer » le +5v et la masse en sortie de la batterie.

La liaison ordinateur / ruban de led est réalisé par une carte arduino pro mini 5v de sparkfun et un module FTDI basic.
Le tout est connecté à mon netbook (mini ordi portable) sur lequel tourne un script python transférant les données d’une image au ruban de led (voir plus bas).

Pour ce qui est du câblage :
Arduino -> ruban
VCC -> +5v
GND -> GND
D11 -> DI
D13 -> CI

Alimentation :
VCC = VCC (batterie usb) + VCC (ftdi basic) -> +5v (ruban led)
GND = GND (batterie usb) + GND (ftdi basic) -> GND (ruban led)

Le software :

Le software est composé de deux parties :
– un code arduino, qui reçoit via le port série les pixels de l’image à afficher
– un script python, qui lit puis transmet via le port série l’image dont on lui donne le nom en paramètre

Le protocole de communication est extrêmement simple :
Au démarage de l’arduino : « RDY » + \n (pour annoncer au script python que l’arduino est prêt à recevoir les données)
Pendant l’affichage : « FRM » + 64 séries de 3 octets (R, G, B) (soit une colonne de pixels)
l’arduino répond ensuite « ACK » + \n pour confirmer la bonne réception des données.

Le code arduino :

/* Includes */
#include <SPI.h>

/* Nombre de led du ruban */
#define LED_STRIP_LEN 64

/* Macro calculant le nombre de "latch" du ruban en fonction du nombre de led */
#define LATCH_COUNT(n) (((n + 63) / 64) * 3)

/* Buffer d'affichage et itérateur */
uint8_t pixels[LED_STRIP_LEN * 3];
unsigned int i;

/* Envoi n "0" sur le port SPI */
void writeZero(uint16_t n) {
  while(n--)
    SPI.transfer(0);
}

/* Envoi le contenu du buffer d'affichage au ruban de led */
void showPixels(void) {
  for (i = 0; i < LED_STRIP_LEN * 3; i++ ) 
    SPI.transfer(pixels[i]);
  writeZero(LATCH_COUNT(LED_STRIP_LEN));
  delay(10);
}

/* Assigne une couleur RGB à un pixel spécifique */
void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b) {
  n = n * 3;
  pixels[n] = g | 0x80; // LPD8806 color order is GRB,
  pixels[n + 1] = r | 0x80;
  pixels[n + 2] = b | 0x80; 
}

/* setup() */
void setup() {

  /* Initialisation du port série */
  Serial.begin(115200);

  /* Par défaut le ruban de led est éteint (0x80) */
  memset(pixels, 0x80, LED_STRIP_LEN * 3);

  /* Initialisation du port SPI */
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);
  SPI.setClockDivider(SPI_CLOCK_DIV8);  // 2 MHz

  /* Initialisation du ruban de led */
  writeZero(LATCH_COUNT(LED_STRIP_LEN));
  showPixels();

  /* Message de boot */
  Serial.println("RDY");
}

/* loop() */
void loop() {

  /* Attend l'arrivé d'au moins 3 octets */
  while(Serial.available() < 3);

  /* Vérifie que les 3 octets reçu correspondent à l'entête de la commande d'affichage */
  if(Serial.read() == 'F') {
    if(Serial.read() == 'R') {
      if(Serial.read() == 'M') {

        /* Reçoit les valeurs RGB de chaque pixel */
        for(i = 0 ; i < LED_STRIP_LEN ; i++) {
          while(Serial.available() < 3);
          setPixelColor(i, Serial.read(), Serial.read(), Serial.read());
        }

        /* Affiche les pixels et confirme la réception des données */
        showPixels();
        Serial.println("ACK");
      }
    }
  }
}

Le script python :

#!/usr/bin/python

# Port série utilisait par l'arduino
SERIALPORT = "COM8"

# Vitesse du port série
SERIALSPEED = 115200

# Temps entre deux frame (en secondes, nombre à virgule)
WAITTIME = 0.05

# Nombre de led sur le ruban
NBPIXELS = 64

# Image source
IMGSRC = "nyancat.png"

# Temps avant lancement du streaming
STREAMDELAY = 2

# ---------------------------------------------------------------------

import Image, serial, time

def delay(t):
    for i in range(t, 0):
        print i,
        time.sleep(1)

print "~ Led Strip - Ligth Painting Assistant ~"
print "Created by SkyWodd"
print " "

print "Checking source image ... ",
try:
    im = Image.open(IMGSRC)
    im2 = im.rotate(270)
    data = list(im2.getdata())
    if len(data) < NBPIXELS or len(data[0]) != 3 :
        print "FAIL ! =("
        print "Source image must contain more than %d pixels in 24bits colors !" % NBPIXELS
        exit(-1)
except:
    print "FAILLED !"
    exit(-1)
print  "OK !"

print "Openning serial port ... ",
try:
    arduino = serial.Serial(SERIALPORT, SERIALSPEED, timeout=1)
except:
    print "FAILLED !"
    exit(-1)
print "OK !"

print "Booting up arduino ... ",
arduino.setDTR(level = True)
time.sleep(0.5)
arduino.setDTR(level = False)
ligne = arduino.readline()
while not "RDY" in ligne:
    ligne = arduino.readline()
print "Ok !"
delay(5)

print "Streaming system loaded !"
delay(STREAMDELAY)
print " "

nbFrames = len(data) / NBPIXELS
print "Number of frames to sent : %d" % nbFrames
tstart = time.time()

for frame in range(0, nbFrames):
    print "# %d / %d" % (frame, nbFrames - 1)
    arduino.write("FRM")
    offset = frame * NBPIXELS
    for i in range(offset, offset + NBPIXELS):
        arduino.write(chr(data[i][0]))
        arduino.write(chr(data[i][1]))
        arduino.write(chr(data[i][2]))
    ack = arduino.readline()
    if not "ACK" in ack:
        print "- COM ERROR -"
    time.sleep(WAITTIME)
    
print "Power off led ..."
arduino.write("FRM")
for i in range(0, NBPIXELS):
    arduino.write(chr(0))
    arduino.write(chr(0))
    arduino.write(chr(0))
ack = arduino.readline()
if not "ACK" in ack:
    print "- COM ERROR -"

print "End of stream !"
print "Streaming take %d seconds" % (time.time() - tstart)

print "Closing serial port ... ",
arduino.close()
print "OK !"

print "Bye bye !"

Le résultat :

(Il s’agit juste de photos de test pour le moment, puisse que je n’ai pas pu faire les « vrai » photo à cause la météo)

Original :

Light painting :

Original :

Light painting :

Original :

Light painting (j’ai manqué de place pour faire l’image en entière) :

Original :

Light painting :

J’espère pouvoir faire les photos en HD avant la fin des vacances.
Pour le moment ça me semble bien mal parti vu la météo annoncé …

Enjoy 🙂

Discussion

6 réflexions sur “[Arduino] Light painting assisté par ordinateur (version 2)

  1. Bonjour, cher arduinaute, tes articles au passage sont toujouts très intéressants.
    Je n’ai pas bien compris le fonctionnement, il faut que tu te déplaces avec le bâton devant un appareil photo en pause ?
    En tout cas bravo pour la créativité
    Alain

    Publié par alainventeur | 6 juillet 2012, 6 h 23 min
    • >> Je n’ai pas bien compris le fonctionnement, il faut que tu te déplaces avec le bâton devant un appareil photo en pause ?

      Il faut avoir un appareil photo DSLR (bridge numérique ou reflex) en mode exposition longue durée >10 secondes.

      Ensuite dans le noir total, il suffit ce déplacer de gauche à droite à vitesse constante avec le bâton en main pour que l’exposition longue donne cet effet de light painting.

      Exemple en vidéo :

      (vidéo trouvé vite fait sur google)

      Publié par skywodd | 6 juillet 2012, 13 h 14 min
  2. Bonjour, je suis intéressé par ce projet, cependant je ne comprends pas comment vous gérez le code python… où intervient-il ?
    merci pour la réponse

    Publié par ewen queffelec | 9 avril 2015, 9 h 41 min
    • Le code python sert à transmettre l’image en temps réel depuis l’ordinateur vers le ruban de leds pour l’affichage.

      Publié par Skywodd | 9 avril 2015, 13 h 59 min

Rétroliens/Pings

  1. Pingback: Learn Python the Hard Way for Physical Computing - Lessons 11-20 - 2 septembre 2012

  2. Pingback: First Impressions of the pyMCU Python Microcontroller - 14 septembre 2012

Skyduino devient Carnet du Maker

Le site Carnet du Maker remplace désormais le blog Skyduino pour tout ce qui touche à l'Arduino, l'informatique et au DIY.