Metal Monkey !

Suivi de la germination des lentilles

Pour aider mon fils dans le suivi d’une expérience au collège… ou plus probablement mu par l’envie égoiste de me plonger dans l’utilisation de mon Raspberry Pi, ayant un protentiel projet à suivre, je me suis lancé dans une petite réalisation permettant de prendre une photo à intervalle régulier, ainsi qu’une compilation de ces images sous forme de vidéo.

Ce projet étant le premier que je réalise sur Rapsberry, et ne connaissant pas encore Python, il s’agit essentiellement d’un agrégat de scripts que j’ai pû glanner ça et là. Il y a probablement un meilleur moyen d’atteindre l’objectif, mais là, je travaille en mode newbie sur cette techno.

1/ Prises de vues

Le script doit prendre une photo à intervalle régulier, j’ai choisi toutes les 10 minutes.
A chaque fois qu’une image est prise, je la sauvegarde localement, et l’envoi vers un serveur en ligne, pour facilement suivre l’expérience, de n’importe où.
Pour permettre une prise de vue la nuit, sans laisser la lumière allumée, je crée un circuit simple de LED, entourant la caméra, contrôlée par le port GPIO.

Le script Python suivant allume les LED, prend une photo, l’envoie en ligne, avant de tout éteindre.
Dans mon cas, j’utilise les broches 31,32,33,35,36,37,38 et 40 pour connecter les LED, en plaçant les masses sur les broches 34 et 39.
Pour rappel, le placement des broches du port GPIO (source leprofnomade.fr) :

Script Python (led.py)

  • Allumage des LED
  • Allumage de la caméra
  • prise de vue
  • envoi du fichier
  • extinction de la caméra et des LED
#!/usr/bin/env python3
#-- coding: utf-8 --
from time import sleep
import time
from picamera import PiCamera
import requests
import RPi.GPIO as GPIO

#Définit le mode de numérotation (Board)
GPIO.setmode(GPIO.BOARD) 
GPIO.setwarnings(False)

#Définition des broches des LED
LedList = [40,38,36,32,37,35,33,31]

#Initialisation de la caméra et contrôles GPIO
camera = PiCamera()
camera.resolution = (1920,1080)
for l in LedList:
	GPIO.setup(l, GPIO.OUT)

#Allumage des LED
for l in LedList:
	GPIO.output(l, GPIO.HIGH)

#Prise de vue après une attente de 2s (focus éventuel)
camera.start_preview(fullscreen=False, window = (50,50,192,108))
sleep(2);
tmp = (time.strftime("%m-%d-%H-%M-%S"))
fname = '/home/pi/Desktop/pix/pix_'+tmp+'.jpg'
camera.capture(fname)
camera.stop_preview()
sleep(2);

#Envoi du fichier vers le serveur
with open(fname, "rb") as f:
	s = requests.Session()
	r = s.post('https://[SERVEUR CIBLE]/saveme.php',
		data={'lacle':'[CLE SECRETE]'},
		files={'files':f})
	print(r.status_code);
    
#On éteint les LED
for l in LedList:
	GPIO.output(l, GPIO.LOW)

Le script enregistre l’image sous /home/pi/Desktop/pix/pix_[timestamp].jpg, puis l’envoi vers [SERVEUR CIBLE], avec une clé pour limiter les risque d’upload non désirée.

2/ Création d’une vidéo à partir des prises de vues

La phase suivante permet de créer une vidéo à parti des différentes prises de vue. J’utilise Mencoder pour composer la vidéo. En 60 images par seconde, chaque journée d’expérience durera 2,4 secondes sur la vidéo.
La commande (script shell) est déclenchée par le cron. J’ai choisi de créer une nouvelle vidéo toutes les heures.
Une fois la vidéo créée, un script python l’envoi en ligne.

Script Shell (govideo.sh)

  • liste des images dans un fichier texte
  • création d’une vidéo à partir des images
  • envoi de la vidéo en ligne
#Liste des images dans un fichier txt
ls /home/pi/Desktop/pix/*.jpg > /home/pi/Desktop/stills.txt

#Création de la vidéo à partir de la liste des images
mencoder -nosound -of lavf -ovc x264 -x264encopts preset=ultrafast:threads=auto -vf scale=1920:1080 -o /home/pi/Desktop/tmlps.mp4 -mf type=jpeg:fps=24 mf://@/home/pi/Desktop/stills.txt

#Script python envoyant la vidéo en ligne
/home/pi/Desktop/sendvid.py

Script Python (sendvid.py) – envoi de la vidéo

#!/usr/bin/env python3
#-- coding: utf-8 --
import requests

with open("/home/pi/Desktop/tmlps.mp4", "rb") as f:
    s = requests.Session()
    r = s.post('https://[SERVEUR CIBLE]/savevi.php',
               data={'lacle':'[CLE SECRETE]'},
               files={'files':f})
    print(r.status_code);

3/ Scripts PHP sur le serveur cible

Ici, on arrive à un domaine que je maîtrise clairement plus, le PHP.
Le premier script enregistre la nouvelle image et crée une miniature, pour utilisation sur une page listant les images.

Script PHP (saveme.php)

  • Contrôle la clé secrète
  • renomme le fichier selon l’heure serveur et l’enregistre
  • crée une miniature
if(isset($_POST["lacle"]) && $_POST["lacle"]=='[CLE SECRETE]')
{
  $tmpf = $_FILES["files"]["tmp_name"];
  $endf = "pix_".time().".jpg";
  echo $tmpf." => ".$endf;

  move_uploaded_file($tmpf, 'sauvegarde/src/'.$endf);

  $thb = imagecreatetruecolor(576, 324);
  $src = imagecreatefromjpeg('sauvegarde/src/'.$endf);
  imagecopyresized($thb, $src, 0, 0, 0, 0, 576, 324, 1920, 1080);
  imagejpeg($thb, 'sauvegarde/thb/'.$endf);
}

L’image et la miniature sont enregistrées dans 2 dossiers distincts – je n’ai pas inclue de script de contrôle d’existence du dossier et de création éventuelle, nous sommes sur un test, il ne faudra donc pas oublier de le créer si vous utilisez ces scripts.

Le script suivant enregistre simplement la nouvelle vidéo sur le serveur.

Script PHP (savevi.php)

  • Contrôle la clé secrète
  • enregistre la vidéo sur le serveur
if(isset($_POST["lacle"]) && $_POST["lacle"]=='[CLE SECRETE]')
{
  $endf = './sauvegarde/lavideo.mp4';
  if(!file_exists($endf) || $_FILES["files"]["size"]>filesize($endf))
  {
    move_uploaded_file($_FILES["files"]["tmp_name"], $endf);
  }
}

La dernière vidéo est systématiquement enregistrée sous le nom “lavideo.mp4”, inutile de conserver les versions précédente, il s’agit d’incrémentation. En cas de problème, on pourra simplement déclencher le script de création de vidéo sur le Raspberry Pi.

4/ Montage électronique

Pour permettre une prise de vue quelque soit l’heure, j’ai installé 8 LED sur 2 plaques de montage. Chacune connectée à une broche du port GPIO (cf partie 1) et précédée d’une résistance 220 Ω.

5/ Support de montage

Pour permettre une prise de vue optimale, sans flou, la caméra doit se trouver à distance raisonnable de l’expérience. voici le montage que nous avons réalisé, avec mon fils, à l’aide de légo. e ne vais pas tout détailler ici, mais l’idée est d’avoir un support qui permet de maintenir:

  • Le raspberry PI
  • Un pont due port GPIO
  • Deux plaques de montage équipées de LED
  • La caméra
  • Une zone où placer l’expérience, facile à retrouvée si on doit bouger le sujet

6/ Mise en place de l’expérience

Suivant les instruction, les lentilles sont placée entre 2 cotons imbibés d’eau. On attend 24h (début de la germination observée sur une précédente “fournée”) pour retirer le coton du haut et démarrer le suivi par la caméra.

L’expérience en place, il ne reste plus qu’à lancer le suivi grâce à la crontab. Dans la console de lignes de commandes, on commence par rendre les 3 script présents sur le Raspberry Pi exécutables, puis, on lance l’édition de la planification:

sudo chmod +x led.py
sudo chmod +x govideo.sh
sudo chmod +x sendvid.py
sudo crontab -e

Et on ajoute les lignes suivantes

*/10 * * * * /home/pi/Desktop/led.py
1 */1 * * * /home/pi/Desktop/govideo.sh

La première ligne permet de lancer le script led.py

*/10toutes les 10 minutes
*quelque soit l’heure
*quelque soit le jour
*quelque soit le mois
*quelque soit le jour de la semaine

La seconde ligne lance le script govideo.sh

1à la minute 1
*/1toutes les 1 heure
*quelque soit le jour
*quelque soit le mois
*quelque soit le jour de la semaine

7/ Visualisation

D’ici 10 minutes, la première photo sera placée sur le serveur, il ne reste plus qu’à placer de quoi visualiser le résultat. Sur le serveur en ligne, on place les scripts suivants.

Script PHP (index.php)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>xPerience !</title>
  <style>
    img, video {display: block; max-width: 100%; height: auto;}  
  </style>
</head>
<body>
<?php
$fld = 'sauvegarde/src/';
$files = scandir($fld, SCANDIR_SORT_DESCENDING);
echo '<h1>Dernière vue - '.strftime('%d/%m/%Y à %H:%M:%S', str_replace(['.jpg', 'pix_'], '', $files[0])).'</h1>';
echo '<img src="'.$fld.$files[0].'">';

echo '<h1>Timelapse - '.strftime('%d/%m/%Y à %H:%M:%S', filemtime('./sauvegarde/lavideo.mp4')).'</h1>';
echo '<video src="sauvegarde/lavideo.mp4" controls></video>';

?>
</body>
</html>

Ce script est l’accueil du serveur en ligne, il affiche la dernière image et le timelapse vidéo.

Script PHP (tout.php)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>All view !</title>
  <style>
  th {vertical-align: top; text-align: right; padding-right: 10px;} 
  img {max-width:100%; heihgt: auto;}
  </style>
</head>
<body>
<table>
<?php

$f = scandir('./sauvegarde/thb');

foreach($f as $v)
{
  if($v!='..' && $v!='.')
  {
    $exp = explode('.', $v);
    echo '<tr><th>'.strftime('%d/%m/%Y<br>%H:%M:%S', str_replace('pix_', '', $exp[0])).'</th><td><img src="sauvegarde/thb/'.$v.'"></td></tr>';
  }
}

?>
</table>  
</body>
</html>

Ce second script affiche la liste des miniature depuis le début de l’expérience. Débutant l’expérience au moment de ces lignes, je n’ai pas de soucis de charge, mais en ajoutant 144 images par jour, il faudra rapidement mettre à jour ce script pour ajouter une pagination.

Chacun est libre de piocher parmi ces script, de les utiliser, améliorer… il s’agit essentiellement pour moi d’un premier contact avec python !

8/ Mise à jour !

Le code est disponible sur github
29/03 : suite à un blocage du réseau dans la nuit, aucune prise de vue n’a été faite pendant une heure, la caméra n’ayant pas été libérée. J’ai donc scindé le script led.py en 2, pour séparer la prise de vue de l’envoi du dernier fichier.