Séquences d'animation utilisant Python ....


Les manipulations expérimentées ici s'inspirent de la façon dont on fabriquait autrefois les dessins animés :
- réalisation d'une succession de dessins (ici des images)
- création du film par enchaînement rapide de ces images (ici un fichier vidéo)

Le fait de procéder en deux temps permet d'isoler les difficultés.
Par exemple : une fois que l'on aura trouvé une manière de faire pratique et efficace pour enchaîner les images, on pourra concentrer toute son attention sur la réalisation de celles-ci.

Au début, j'ai commencé par créer les images une à une en utilisant gimp, ImageMagick... Puis j'ai tenté d'automatiser à l'ide d'un script Bash.
La suite des tentatives s'est faite en remplaçant Bash par de petits programmes en langage Python.
Un copain m'a suggéré le remplacement d'ImageMagick par la bibliothèque Pil. Un autre d'utiliser Pygame.

Ce qui suit est le regroupement de mes notes (rapides), transmises initialement par mail à la liste Armada, avant qu'elles ne soient égarées ou que je n'oublie le contexte d'expérimentation.

Remarque méthodologique : Comme chaque manipulation va générer un grand nombre d'images intermédiaires, il est judicieux de se placer dans un répertoire vide.
Si l'on utilise une (ou plusieurs) image initiale il est "astucieux" de lui choisir un format différent de celles que l'on va générer.
Par exemple image-initiale.png pour générer des images.jpg.


Consulter en ligne :

http://.....

- en français / en anglais


Quelques essais avec ImageMagick

1. Dans un répertoire, j'ai rangé une série d'images de dimensions et d'extensions identiques et je veux en faire une séquence vidéo. - je me positionne dans le répertoire
- convert -delay 20 *.png animation.mpg
Cela va plus vite qu'à la souris :-)

2. Je veux découper dans image1 une portion qui sera sauvegardée dans image2.
convert image1.png -crop 200x120+100+100 image2.png
-crop pour couper
200x120 largeur et hauteur de l'image résultant de la découpe
+100+100 valeurs x et y définissant la coordonnée de l'origine de la découpe

Une idée parmi d'autres : faire un petit script qui va faire varier x et y dans une grande image de façon à faire une sorte de balayage, pour ensuite générer une séquence vidéo. (je ne sais pas si je vais savoir) Exemple de script permettant de convertir tous les fichiers d'un répertoire.

ImageMagick et bash

Point de départ : une vidéo capturée sous Youtube.

1. Cette vidéo est transformée en une succession d'images au format .png
(pour faire vite j'ai utilisé EKD, ayant oublié la syntaxe en ligne de commande et voulant gagner du temps : la "manip" n'est pas "purement ligne de commande". !Arg...). Le son n'est pas récupéré.
Exemple de la première image :
http://lerautal.lautre.net/journal/video/image_000000001.png

2. Un rectangle de 200 de large sur 120 de haut est découpé dans chacune de ces images, à un point de coordonnée y= 80 et un x qui va varier selon l=l+1 en passant d'une image à une autre
On utilise un script bash (pourri : je suis ignare en bash) que voici :

#!/bin/bash
let "l=1"
for i in *.png; do
let "l=l+1"
j="${i%.jpg}"
convert "$j" "-crop" 200x120+$l+80 "$j.jpg"
done

3. On a obtenu ainsi une suite d'image .jpg que l'on va fondre dans une séquence vidéo par :
convert -delay 20 *.jpg animation.mpg
Le résultat, stupéfiant pour moi... :
http://lerautal.lautre.net/journal/video/animation.mpg


ImageMagick et autre chose que Bash = Python

La question était "comment lancer un appel à imagemagick, (avec passage de paramètres bien-sûr), avec Python" ?
Voici une réponse qui paraît simple.
Elle fait appel à la bibliothèque "os" (en langage approximatif = qui permet de lancer des commandes au système).

Voici un exemple de programme :

1-# -*- coding:Utf-8 -*-
2- import os
3- commande="convert image1.png -crop 200x120+100+100 coupe.png"
4- os.system(commande)

Commentaires (à partir des numéros de lignes) :
1- choix le la page de code = le texte source est en UTF-8
2- on demande l'accès à l'OS
3- création d'une variable qui contient le texte des la commande à passer
4- exécution de la commande.
Remarque : la chaîne qui contient la commande peut être constituée
par concaténations successives. Sous réserve de mettre tout cela dans une boucle, on doit pouvoir faire la même chose (en mieux) qu'avec mon script bash.

Autre piste (merci xxx) : la librairie Python dédiée à l'image, appelée Pil.
La documentation en ligne est ici http://www.pythonware.com/library/pil/handbook/index.htm
Un appel à http://translate.google.fr/# peut aider si votre anglais n'y est pas.
Ceci étant, la librairie pil permet de se passer d'imagemagick... et je sors de mon propos initial.


Essai de zoom avec Python et ImageMagick

Donc voici un nouveau zoom dans le radeau de la Méduse.
http://lerautal.lautre.net/journal/video/resul.mp4

Le but de la manip était de tester la faisabilité d'appels répétés à imagemagick via Python.
De ce point de vue, c'est réussi.
Quant au reste...
Voici le programme, d'un débutant en Python.

Commentaires :
La taille de l'image initiale est fixée à 600x400
La boucle de zoom s'arrêtera quand la largeur de l'image ne sera plus > 300

while larg>300:
Les nombres sont convertis en chaines :
nom=str(nb) sx=str(x)...
Je prends un nombre de 4 chiffres pour avoir des noms de fichiers tels que 1111.jpg 1112.jpg 1113.jpg
J'augmente y de 3 quand x a été augmenté de 3 (par la fonction modulo) :
if x%3==0:
y=y+2

la conséquence est un effet de tremblements.

Code du programme :

# -*- coding:Utf-8 -*-
import os
larg,haut=600,400
x=y=0
nb=1111
nom=str(nb)
slarg=str(larg)
shaut=str(haut)
sx=str(x)
sy=str(y)
commande="convert radeau.png -crop "+slarg+"x"+shaut+"+"+sx+"+"+sy+ " cible.jpg"
os.system(commande)<
commande2="convert cible.jpg -resize 300x200 -strip -quality 50 -interlace line "+nom+".jpg"
os.system(commande2)
while larg>300:
    x=x+1
    if x%3==0:
        y=y+2
    larg=larg-(x)
    haut=haut-(y)
    nb=nb+1
    nom=str(nb)
    slarg=str(larg)
    shaut=str(haut)
    sx=str(x)
    sy=str(y)
    commande="convert radeau.png -crop "+slarg+"x"+shaut+"+"+sx+"+"+sy+ " cible.jpg"
    os.system(commande)
    commande2="convert cible.jpg -resize 300x200 -strip -quality 50 -interlace line "+nom+".jpg"
    os.system(commande2) 

Balayage latéral avec Python et ImageMagick

Cette fois-ci, on déplace un rectangle qui a la hauteur de l'image.
On fait varier x = x+1.
Pas de sauts ici : les dimensions sont fixes et le "pas" est petit (1 pixel).
400 images cela donnait une plus grosse vidéo.

Afin d'accélérer le temps de chargement, le fichier a été ensuite réduit en dimension et recodé en utilisant ffmpeg.
La syntaxe étant
ffmpeg -i travers.mpg -cropbottom qcif travers2.mp4

Le résultat est ici :
http://lerautal.lautre.net/journal/video/travers2.mp4
Le code Python, plus simple est ici.

Le code du programme :

# -*- coding:Utf-8 -*-
import os
larg=200
haut=400
x=y=0
nb=1111
nom=str(nb)
slarg=str(larg)
shaut=str(haut)
sx=str(x)
sy=str(y)
commande="convert radeau.png -crop "+slarg+"x"+shaut+"+"+sx+"+"+sy+" "+nom+".jpg"
os.system(commande)
while x<400:
    x=x+1
    nb=nb+1
    nom=str(nb)
    sx=str(x)
    sy=str(y)
    commande="convert radeau.png -crop "+slarg+"x"+shaut+"+"+sx+"+"+sy+" "+nom+".jpg"   
    os.system(commande) 

Animation avec Python et Pil

J'ai utilisé deux images fournies ici :
http://nadiana.com/pil-tutorial-how-create-button-generator
Le résultat est ici :
http://lerautal.lautre.net/journal/video/papillon.mp4
L'image unique du papillon a été remplacée par deux images utilisées en alternance.

Le code du programme :

# -*- coding:Utf-8 -*-
import Image
nb=1111
nom=str(nb)
i=-10
y=10
flower=Image.open('flower.png')
papi1=Image.open('papi1.png')
papi2=Image.open('papi2.png')
flower=Image.open('flower.png')
while i<128:
	fleur=flower.copy()
	y=y+1	
	test=i%2
	if test==0:
		fleur.paste(papi1,(i,y),papi1)
	else:
		fleur.paste(papi2,(i,y),papi2)	
	nomfich=str(nb)+".jpg"
	fleur.save(nomfich)
	nb=nb+1
	i=i+5

Cela vaut ce que ça vaut : une animation comme on aimait en faire il y a 20 ans... mais enregistrée dans un fichier mpeg
Avec plein de petits bouts on peut imaginer faire un petit film.
Ceci étant, on est loin de la réactivité de SDL ou du côté à la mode des animations Blender.
Ne nous leurons pas, donc...


Mise en évidence d'une portion d'image

L'idée est celle d'un parcours guidé dans une oeuvre d'art pour attirer l'attention sur tel ou tel point jugé significatif.
Le test proposé ici permet d'obtenir une image avec un cadre conservé en pleine luminosité alors que le reste de l'image est assombri.
Deux images de départ :
- la "belle" qui contient l'information qui va être mise en valeur http://lerautal.lautre.net/journal/video/radeau1.png
- la "grise" qui est celle dans laquelle un "projecteur rectangulaire" va se déplacer au fur et à mesure : http://lerautal.lautre.net/journal/video/radeau2.png

Le programme suivant fait un découpage d'une zone "belle" pour la coller, à la bonne place, en zone "grise".


# -*- coding:Utf-8 -*-
import Image
nb=1111
nom=str(nb)+".jpg"
box=(40,200,260,400)
belle=Image.open('radeau1.png')
grise=Image.open('radeau2.png')
region=belle.crop(box)
grise.paste(region,(40,200))
grise.save(nom)

Compte-tenu des information déjà rédigée, le code doit vous paraître compréhensible (sinon me demander des éclaircissement).

Le résultat est ici : http://lerautal.lautre.net/journal/video/1111.jpg

Il est peu spectaculaire. Pourtant, le simple fait d'y parvenir ouvre la possibilité à plusieurs extensions.
Parmi lesquelles :
- déplacement progressif d'une zone à une autre (en changeant les coordonnées par programme)
- zoom dans une zone sombre pour obtenir en "pleine dimension" la zone "belle".
- succession d'images fixes permettant de réaliser un diaporama avec commentaires sonores enregistrés (pour une expo ou une séquence didactique-tique -et élastic-tic :-))

Compte-tenu des maigres moyens mis en oeuvre...


Simuler le déplacement d'un train dans un paysage

Le point de départ a été un film célèbre des frères Lumière : http://www.youtube.com/watch?v=MsWYfb4uCzA

Après des essais laborieux et pas très convaincants, il a fallu reconnaître que la gestion du point de fuite (avec pour conséquence des calculs "trigonométriques") ne pouvait être éludée.

Un paysage simpliste mais contenant le tracé de lignes de fuite a été dessiné : http://lerautal.lautre.net/journal/video/paysage.png

Ensuite a été dessiné un véhicule géométrique, adaptaté à ce paysage : http://lerautal.lautre.net/journal/video/vehicule.png

Le programme utilise une fonction nouvelle : im.resize, qui permet de redimendionner l'image du "train" en fonction de sa position dans le paysage.

Ne voulant pas approfondir la gestion des variable locales/globales, j'ai utilisé des valeurs numériques à la place de variables. Ce n'est pas bien, je le sais.

A partir du schema suivant : http://lerautal.lautre.net/journal/video/distfuite.jpg Voici quelques explications, pour compenser :

Le paysage correspond au rectangle jaune (300x200).
L'image du train va aller successivement de la gauche (rectangle rouge) vers la droite (point de fuite).
La distance au point de fuite est contenue dans la variable distfuite. Au départ, elle vaut : largeur (300) +50+50 (voir dessin).
Le reste est affaire de "trigonométrie" élémentaire : rap1 et rap1 correspondent à la tangente de deux triangles rectangles.

Pour éviter un trop grand nombre d'images, j'ai adopté un "pas" de 10
while x<340:
.... x=x+10
ce qui fait que le "film" saccade encore beaucoup.
http://lerautal.lautre.net/journal/video/train2.mp4

Quand j'en aurai le courage, je referai la manip avec une véritable image de train telle que celle-ci : http://lerautal.lautre.net/journal/video/train.png

Le code du programme :

# -*- coding:Utf-8 -*-
import Image
nb,x,y=2111,-50,0
nom=str(nb)
h1=150
h2=50
distfuite=400
largeur,hauteur=50,200
rap1=150.0/400
rap2=50.0/400
def calcdistfuite(n):
    z=350-n
    return z

def calcy(n):
    z=int(150-n)
    return z
       
fond=Image.open('paysage.png')
fondsv=fond.copy()
train=Image.open('vehicule.png')

while x<340:
    fondsv=fond.copy()
    nvtrain=train.resize((largeur,hauteur))
    fondsv.paste(nvtrain,(x,y),nvtrain)
    nomfich=str(nb)+".jpg"
    fondsv.save(nomfich)
    x=x+10
    nb=nb-1
    distfuite=calcdistfuite(x)
    h1=distfuite*rap1
    h2=distfuite*rap2
    y=calcy(h1)
    hauteur=h1+h2
    largeur=int((hauteur*50)/200)