====== 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 =====