====== Panorama ======
===== Introduction =====
Le but de ce tutoriel sera d'effectuer un **panorama** utilisant le **raspberry pi** et sa **camera**.\\
On aurrait pu pour cela tourner la camera du raspberry pi **à la main**, ou utiliser **plusieurs** raspberry pi avec
leurs camera pour pouvoir avoir une vue panoramique, mais c'est asser **contraignant** soit en **temp** ou en **ressource**.\\
La seule solution possible étant d'utiliser un **support** avec **double servo moteurs** photo //ci contre//\\
qu'on peut trouver généralement avec des **kit** du raspberry pi.\\
{{ :diy:projets:supportservo.jpg?direct&200|support camera pi utilisant 2 servo moteurs}}
En premier lieu on montrera comment [[diy:projets:panorama#Commencement avec python avec 2 images|fusionner]] 2 images sous **python**, puis je présenterais le code python nommé[[https://github.com/hiergaut/unix/blob/master/raspbian/bin/servo.py|servo.py]] permettant de bouger la camera dans toutes les directions.\\
\\
Ensuite j'expliquerais aussi le [[diy:projets:panorama#Montage électrique |montage électrique]] asser simple à faire mais il faudra quand meme utiliser une **source 5v externe** et non celle du raspberry pi car l'utilisation des
**servo moteurs** peuvent tirer beaucoup d'ampere et risquerais de terminer le raspberry pi.\\
Initialisation des rapports cycliques (servo utilisant les gpio PWM) graces a un [[diy:projets:panorama#Expliquation du PWM (utilisation des servos)|code]] python.\\
Puis apres ça je présenterais un petit [[diy:projets:panorama#Prendre des photos en mode panoramique|code shell]] nommé [[https://github.com/hiergaut/unix/blob/master/raspbian/bin/fishEye.sh|fishEye.sh]] qui utiliserera la commande python faite précedement et qui prendra plusieurs photo avec une vue 360 degré.\\
Puis pour finir l'utilisation d'un [[diy:projets:panorama#Utilisation de stitching.cpp|programme opencv]] pour créer le panorama avec les photos prises,
on pourra spécifier le mode de rendu du panorama :
* **Cylindrique**
* **Sphérique** (__par default__)
* **Stéréographique**
* et pleins d'autres
\\
//Tapez dans un terminal la commande pour voir les autres options//
./stitching -h
\\
//Vous pouvez télécharger le code source directement avec wget en console, exemple avec le fichier [[https://github.com/hiergaut/unix/blob/master/raspbian/bin/fishEye.sh|fishEye.sh]]//
wget https://github.com/hiergaut/unix/blob/master/raspbian/bin/fishEye.sh
[[https://github.com/hiergaut/unix/blob/master/raspbian/bin/| lire aussi le README.md sur github]]
----
===== Commencement avec python avec 2 images =====
Le principe même du **panorama** entre deux photo est de reperer les **similitudes** entres elles comme des **coins**
ou autres formes facilement reperables.\\
\\
//Prenons ces deux photos pour entrainement (splitter au préalable par mes soins)//
{{:diy:projets:panorama_0.jpg?direct&400 |}}
{{ :diy:projets:panorama_1.jpg?direct&400 |}}\\
\\
//On peut visionner ces **formes** de cette façon en python//
img =cv2.imread('panorama_0.jpg')
img_gray =cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
sift_obj =cv2.xfeatures2d.SIFT_create()
kp, _ =sift_obj.detectAndCompute(img_gray, None)
img =cv2.drawKeypoints(img_gray, kp, img)
# img =cv2.drawKeypoints(img_gray, kp, img, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imshow('w', img)
while cv2.waitKey(10) != 27:
continue
{{ :diy:projets:screen5.png?direct&400 |}}\\
\\
//Avant de faire la **fusion** il faut trouver les **points** qui se trouve sur les 2 images, on peut visionner cela avec ce code//
img1c =cv2.imread('panorama_0.jpg')
img2c =cv2.imread('panorama_1.jpg')
sift_obj =cv2.xfeatures2d.SIFT_create()
kp1, des1 =sift_obj.detectAndCompute(img1c, None)
kp2, des2 =sift_obj.detectAndCompute(img2c, None)
# FLANN parameters
FLANN_INDEX_KDTREE = 0
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50) # or pass empty dictionary
flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)
img3 = cv2.drawMatchesKnn(img1c,kp1,img2c,kp2,matches,None)
cv2.imshow("correspondences", img3)
cv2.waitKey()
\\
//On visionne les **points** repere entres les 2 images, on peut voir qu'il y en a beaucoup//
{{ :diy:projets:screen6.png?direct&400 |}}
\\
Puis on peut utiliser le code decrit dans ce tuto pour fusionner les deux images
[[https://www.pyimagesearch.com/2016/01/11/opencv-panorama-stitching/|]]
\\
\\
----
===== Utilisation des servo moteurs avec python (lib GPIO) =====
On souhaite faire un panorama 360° avec des photos prise par la camera du rpi, on aurrait pu utiliser
plusieurs rpi cote à cote, avec des inclinaisons differentes, mais l'option d'utiliser 2 **servomoteurs**
permettra d'avoir une vue spherique de l'ensemble de l'environnement juste avec qu'un camera pi.
==== Montage électrique ====
\\
//Presentation du montage maison avec le support double servo attaché sur le boitier du raspberry pi avec des tyraps.//
{{ :diy:projets:double_servo.jpg?direct&600 |}}
\\
//Presentation du montage électrique (utilisation de **source 5v externe**)//
{{ :diy:projets:servoelec.png?direct&600 |}}
{{ :diy:projets:servoschema.png?direct&600 |}}
\\
==== Expliquation du PWM (utilisation des servos) ====
[[https://mespotesgeek.fr/fr/variation-de-puissance-electrique-via-raspberry/|Cliquer pour voir une expliquation détaillé]]
Le [[https://fr.wikipedia.org/wiki/Modulation_de_largeur_d%27impulsion|PWM]] (Pulse Width Modulation) permet
d'émettre un signal carré à une certaine fréquence, 2 états 0 ou 1, la proportion de l'état haut par rapport à l'état bas s'appelle le **rapport cyclique**.\\
Par exemple si je veux un signal carré parfait (sinus carré) peut importe la fréquence fixé,
je fixe le rapport cyclique à 50%, et donc la durée de l'état haut sera égale à celle de l'état bas.
\\
//En python on demande a un **gpio** du raspberry de fonctionner en **PWM** de cette façon ://
pwm =GPIO.PWM(pinDuGpio, fréquence)
\\
//Puis on fixe le **rapport cyclique** ://
pwm.start(20)
Le problème et que on ne connait pas à l'avance les rapport cycliques pour permettre aux 2 servo moteurs
de s'axer idéalement sur 180 degrés.
\\
//Donc je propose un code python permettant de régler cela//
python servo.py -i
def findMechanicalStop():
middle=10
hPwm =GPIO.PWM(hPin, f)
vPwm =GPIO.PWM(vPin, f)
# stop=False
# while (not stop):
# pos -=1
# hPwm.start(pos)
#
# #ret =ord(raw_input("space to continue, enter to confirm abut: "))
# ret =ord(sys.stdin.read(1))
# print(ret)
#non-blocking get input
stdscr = curses.initscr()
curses.noecho()
stdscr.nodelay(1) # set getch() non-blocking
stdscr.addstr(0,0,"Press \"ENTER to confirm abut, space to jump")
line = 1
toes =["vMin", "vMax", "hMin", "hMax"]
pwms =[vPwm, vPwm, hPwm, hPwm]
values =[0, 0, 0, 0]
sens =[-1, 1, -1, 1]
for i in range(4):
pos=middle
try:
while (1):
c = stdscr.getch()
if c == 10:
values[i] =round(pos, 2)
break
elif c == 32:
print("space")
pos =pos +sens[i] *5
else:
pos =pos +sens[i] *0.1
stdscr.addstr(line, 0, "actual pos "+ str(pos))
pwms[i].start(pos)
time.sleep(0.1)
finally:
curses.endwin()
for i in range(4):
print(toes[i], " =", str(values[i]))
\\
Avant de lancer le panorama, il faut preparer un programme python pour diriger la camera selon n'importe quel inclinaison.\\
Je décide de créer une fonction prenant 2 argument [-180..180] pour autour de l'axe vertical, [0..90] autour de l'axe horizontal.
\\
//Voici le code **python**//
python servo.py -m
def angle(vAngle, hAngle):
vMin=4.0
vMax=25.0
hMin=3.7
hMax=50.0
hPwm =GPIO.PWM(hPin, f)
vPwm =GPIO.PWM(vPin, f)
assert (-180 <= vAngle and vAngle <= 180)
assert (0 <= hAngle and hAngle <= 90)
if -90 <= vAngle and vAngle <= 90:
vPwm.start(vMax -(vAngle +90) *(vMax -vMin) /180.0)
hPwm.start(hAngle *hMax /180.0 +hMin)
elif vAngle < -90:
vPwm.start(vMin +(-vAngle -90) *(vMax -vMin) /180.0)
hPwm.start(hMax -hAngle *(hMax -hMin) /180.0)
else:
vPwm.start(vMax -(vAngle -90) *(vMax -vMin) /180.0)
hPwm.start(hMax -hAngle *(hMax -hMin) /180.0)
time.sleep(1)
hPwm.stop()
vPwm.stop()
On peut prendre des photos avec une inclinaisons autours de l'axe horizontal de 0 et 45 degre
tout autour de l'axe vertical d'un pas de 45 degre avec le code suivant, en n'oubliant pas d'importer le code precedent.
\\
==== Prendre des photos en mode panoramique ====
\\
//Voici le **script shell** utilisant le programme python pour prendre des photos en mode panoramique.//
./fishEye.sh
for j in 0 45 90 135 180 -135 -90 -45; do
python servo.py -m $j 0
if [ $j -ge -90 -a $j -le 90 ]; then
raspistill -t 100 -vf -hf -o photo_"$j"_0.jpg
else
raspistill -t 100 -o photo_"$j"_0.jpg
fi
done
J'utilise raspistill au lieu d'utiliser VideoCapture de opencv, en n'oubliant pas de tourner l'image avec l'option vf et hf de raspistill
\\
==== Video pour résumer ====
Utilisation de la commande servo.py et fishEye.sh
\\
//Vous pouvez télécharger les sources directement sur le raspberry de cette façon//
wget https://github.com/hiergaut/unix/blob/master/raspbian/bin/fishEye.sh
wget https://github.com/hiergaut/unix/blob/master/raspbian/bin/servo.py
\\
// Video résumant le init pwm, et le fishEye.//
{{youtube>S3QNFzmFdqs?medium}}
\\
\\
----
===== Utilisation de stitching.cpp =====
Le programme **python** permettant de merger plusieurs photo etant **lent** je décide dans la suite de ce tuto d'utiliser du **code cpp**,
un [[https://github.com/opencv/opencv/blob/master/samples/cpp/stitching.cpp|programme]] deja ecrit qui permettra de créer des panorama cylindrique et stéréographique.
./stitching --features orb --warp stereographic picture*.jpg
./stitching --features orb --warp cylindric picture*.jpg
\\
Maintenant reste plus qu'a fusionner les images recement capturées, pour cela
telechargeons le code cpp sur github
[[https://github.com/opencv/opencv/blob/master/samples/cpp/stitching.cpp]]
g++ stitching.cpp `pkg-config --cflags --libs opencv` -o stitching
\\
==== Divers mode (features) de fusion des images ====
* mode **cylindrique**:
./stitching --features orb --warp cylindric *.jpg
\\
//prise de vue salle de cours, team be traitement d'image sous rpi://
{{ :diy:projets:result.jpg?direct&600 |}}
\\
* mode **stereographique**:
./stitching --features orb --warp stereographic *.jpg
\\
\\
----
===== Timelapse en mode stéréographique =====