Ho compilato da sorgenti le librerie OpenNI e PyOpenNI sul mio IBM X40 per poter usare il programmino per gestire il Kinect gia' visto in questi post
Con sopresa pero' lo script genera un errore di "Illegal Operation"
Dopo un po' di ricerche ho scoperto che OpenNi e' ottimizzato per utilizzare le estensioni delle istruzioni del processore SSE3 mentre il Centrino montato sull'X40 ammette solo le estensioni SSE2
In conclusione non si puo' usare Kinect accoppiato all'IBM X40
Utilizzando tutte le esperienze precedenti (1,2) ho scritto questo programmino che salva in un colpo solo tutti i parametri di Kinect
lo script deve essere lanciato con una stringa argomento che diventa il nome del progetto.
per esempio
./acquisizione test
genera poi i file
test_angolo.txt
test_rgb.jpg
test_cloud.asc
test_cloud.txt
nel file _angolo.txt sono salvati gli angolo di pitch, roll, la distanza al centro dell'immagine ed il numero di punti della nuvola che risultano corretti (non tutti i pixel di una ascquisizione con il laser risultano corretti, valori attorno al 70% sono gia' ottimali
nel file _rgb.rgb viene salvata una fotografia della scansione. Attenzione: in alcuni casi ci sono problemi di sincronia per cui questa immagine puo' risultare tagliata o mescolata al frame precedente
nel file _cloud.asc sono riportati i valori in x,y,z in millimetri della scansione gia' pronti per essere inclusi in Meshlab o Cloudcompare
il file _cloud.txt e' l'acquisizione della nuvola dei punti pura senza elaborazione e serve nel caso ci siano problemi al punto precedente
il ritardo iniziale serve solo a dare il tempo all'operatore di mettersi in posizione con il kinect
-------------------------------------- #!/usr/bin/python import usb.core import usb.util import sys import time import math from openni import * from PIL import Image import numpy as np import pickle print "10 secondi alla misura" time.sleep(5) print "5 secondi alla misura" time.sleep(5) print "Inizio misura" stringa = sys.argv[1] print "Progetto : "+stringa dev = usb.core.find(idVendor=0x045e, idProduct=0x02B0) if dev is None: raise ValueError('Device not found') for cfg in dev: sys.stdout.write("Configuration #"+str(cfg.bConfigurationValue) + '\n') for intf in cfg: sys.stdout.write('\tInterface #' + \ str(intf.bInterfaceNumber) + \ '\t, Alternate setting ' + \ str(intf.bAlternateSetting) + \ '\n') sys.stdout.write("\tEndpoints:\n") for ep in intf: sys.stdout.write('\t\t' + \ str(ep.bEndpointAddress) + \ '\n') dev.set_configuration() ret = dev.ctrl_transfer(0xC0, 0x10, 0x0, 0x0, 1) ret = dev.ctrl_transfer(0x40, 0x6, 0x1, 0x0, []) #calcola l'angolo ret = dev.ctrl_transfer(0xC0, 0x32, 0x0, 0x0, 10) x = (ret[2] << 8) | ret[3] x = (x + 2**15) % 2**16 - 2**15 # convert to signed 16b y = (ret[4] << 8) | ret[5] y = (y + 2**15) % 2**16 - 2**15 # convert to signed 16b z = (ret[6] << 8) | ret[7] z = (z + 2**15) % 2**16 - 2**15 # convert to signed 16b pitch = math.atan2(y,x)*(180/3.1415926) roll = math.atan2(y,math.sqrt((x*x)+(z*z)))*(180/3.1415926) if (z <0): roll = 90+(90-roll) #immagine rgb e profondita' ctx = Context() ctx.init() depth = DepthGenerator() rgb = ImageGenerator() depth.create(ctx) rgb.create(ctx) depth.set_resolution_preset(RES_VGA) depth.fps = 30 rgb.set_resolution_preset(RES_VGA) rgb.fps = 30 ctx.start_generating_all() ctx.wait_one_update_all(rgb) im = Image.fromstring('RGB',(640,480),rgb.get_raw_image_map()) im.save(stringa+"_rgb.jpg") print "RGB salvata" #ctx.wait_one_update_all(depth) #de = Image.fromstring('L',(640,480),depth.get_raw_depth_map_8()) #de.save(stringa+"_depth.jpg") #print "Depth salvata" time.sleep(0.5) #mappa_punti ctx.wait_one_update_all(depth) depthMap = depth.map depthMap2 = np.array(depthMap) f = open(stringa+"_cloud.txt","w") pickle.dump(depthMap2,f) f.close() f = open(stringa+"_cloud.txt") data = pickle.load(f) f.close() g = open(stringa+"_cloud.asc","w") t = 0 d = 0 alfa = -0.5105088 # angolo lungo x delta_a = 0.00159534 # delta alfa beta = 0.397935 # angolo lungo beta delta_b = 0.001658052 # delta beta dis_c = data[153600] # distanza del punto centrale 320x240 print "Distanza media "+str(dis_c) + " mm" for y in range(0,480): alfa = -0.5105088 for x in range (0,640): if (data[t] !=0): g.write(str(dis_c*math.tan(alfa))+","+str(dis_c*math.tan(beta))+","+str(data[t])+"\n") d = d + 1 t=t+1 alfa = alfa + delta_a beta = beta - delta_b g.close() perc = (d/307200.0)*100 print "Nuvola dei punti salvati" print "Punti validi : " + str(round(perc,1)) + "%" out = open(stringa + "_angolo.txt","w") out.write("Pitch: "+str(round(pitch,1))+", Roll : "+str(round(roll,1))+", Distanza : "+str(dis_c)+" mm, Punti validi :"+str(round(perc,1))+"% \n") out.close() print "Angoli salvati"
Non si tratta di una funzione molto bene documentata (anche perche' non presente nella cartella degli esempi) ma con PyOpenNi e' possibile acquisire immagini RBG da Kinect
il codice di riferimento (sostanzialmente autoesplicativo) e' il seguente
---------------------------------------------- from openni import * from PIL import Image ctx = Context() ctx.init() depth = DepthGenerator() rgb = ImageGenerator() depth.create(ctx) rgb.create(ctx) depth.set_resolution_preset(RES_VGA) depth.fps = 30 rgb.set_resolution_preset(RES_VGA) rgb.fps = 30 ctx.start_generating_all() ctx.wait_one_update_all(rgb) im = Image.fromstring('RGB',(640,480),rgb.get_raw_image_map()) im.save("rgb.jpg") ctx.wait_one_update_all(depth) de = Image.fromstring('L',(640,480),depth.get_raw_depth_map_8()) de.save("depth.jpg")
Per completare i testi con il Kinect ho provato a creare uno scanner 3D.
L'oggetto e' stata la caffettiera sotto riportata il cui centro di rotazione e' stato posto a 65 cm dal Kinect ed e' stato ripreso da 4 fotogrammi ruotando ogni volta l'oggetto di 90°
Ovviamente e' stata una prova casalinga e ci sono forti errori di allineamento tra il sensore Kinect e l'asse di rotazione.
L'oggetto reale
La sua scansione (da notare che era posto sopra una scatola)
Per l'acquisizione e' stato usato il seguente script
------------------------------------------------- from openni import * import time import pickle import numpy as np import Image import scipy ctx = Context() ctx.init() # Create a depth generator depth = DepthGenerator() depth.create(ctx) # Set it to VGA maps at 30 FPS depth.set_resolution_preset(RES_VGA) depth.fps = 30 # Start generating ctx.start_generating_all() # Update to next frame nRetVal = ctx.wait_one_update_all(depth) depthMap = depth.map depthMap2 = np.array(depthMap) f = open("ca_2700.txt","w+b") pickle.dump(depthMap2,f) f.close()
-------------------------------------------------
i vari file salvati con pickle sono stati poi trattati con i seguenti programmi (uno ogni 90°, di fatto l'immagine viene centrata e vengono filtrati solo i dati > 0 ed inferiori a 70 cm)
-------------------------------- #!/usr/bin/python import pickle import numpy as np import Image f = open("ca_000.txt") data = pickle.load(f) f.close() t = 0 for y in range(0,480): for x in range (0,640): if ((data[t] < 700) and (data[t]>0)): print str(x-320)+","+str(y-240)+","+str(660-data[t]) t = t + 1
-------------------------------- #!/usr/bin/python import pickle import numpy as np import Image f = open("ca_180.txt") data = pickle.load(f) f.close() t = 0 for y in range(0,480): for x in range (0,640): if ((data[t] < 700) and (data[t]>0)): print str(x-320)+","+str(y-240)+","+str(-(660-data[t])) t = t + 1
-------------------------------- #!/usr/bin/python import pickle import numpy as np import Image f = open("ca_090.txt") data = pickle.load(f) f.close() t = 0 for y in range(0,480): for x in range (0,640): if ((data[t] < 700) and (data[t]>0)): print str(660-data[t]) + "," + str(y-240) + "," + str(x-320) #non funziona t = t + 1
-------------------------------- #!/usr/bin/python import pickle import numpy as np import Image f = open("ca_090.txt") data = pickle.load(f) f.close() t = 0 for y in range(0,480): for x in range (0,640): if ((data[t] < 700) and (data[t]>0)): print str(-(660-data[t])) + "," + str(y-240) + "," + str(x-320) t = t + 1
--------------------------------
L'output e' stato dirottato su file testo che poi sono importati in Meshlab.
Per verificare l'accuratezza del sensore di distanza del Kinect ho provato ad acquisire due immagini di una stessa scena statica e a vedere le differenze
Mappa di profondita' di Kinect
Le due immagini sono state acquisite con lo script visto in questo post
Per calcolare la differenza ho usato il seguente script
------------------------------------------------ import pickle import numpy as np import Image posizione = 0 accumula = 0 f = open("3.txt") data1 = pickle.load(f) f.close f = open("4.txt") data = pickle.load(f) f.close accumula = data - data1
img = Image.new('L', (480,640), "black") pixels = img.load() for i in range(img.size[0]): for j in range(img.size[1]): pixels[i,j] = accumula[posizione] posizione = posizione + 1 img2 = img.rotate(270) img2.save("3-4.png") print np.histogram(accumula,[-3000,-100,-10,10,100,3000])
------------------------------------------------
Questa e' l'immagine di differenza
E' subito evidente che le maggiori differenze sono ubicate nei punti in cui c'e' la maggior distanza tra due pixel vicini
Mappa delle differenze test 1
La distribuzione di frequenza indica che per il 92.6% la differenza di distanza misurata e' compresa tra -1/+1 cm. Un altro 5.7% delle differenze e' compresa tra -10/+10 cm
Per maggiore dettaglio il 75% delle misure ha differenza 0
Un altro esempio
Mappa delle differenze Test 2
in questo caso il 77% dei punti mostra differenza di 0 mm con il 93% dei punti con differenza inferiore a 1 cm.
Un terzo esempio
Mappa delle differenze Test 3
anche qui il 76% dei punti mostra differenza di 0 mm con il 92.4% dei punti con differenza inferiore a 1 cm.
In buona sostanza il sensore di Kinect lavora molto bene su scene piatte mentre dove ci sono oggetti su piani a distanza differente con bruschi salti di distanza l'errore diventa sensibile
Su richiesta di un amico, ho provato ad usare il Kinect per fare un modello 3D del viso
Il lavoro e' diviso in due parti. In fase di acquisizione vengono salvati i dati del sensore di distanza del Kinect ogni 5 secondi (il Kinect genera una mappa di 640x480 punti per ogni immagini ed i dati acquisiti da PyOpenNi sono espressi direttamente in millimetri)
Successivamente si lancia lo script di lettura.py come
python lettura.py > dati.asc
il file .asc viene poi utilizzato dal programma Meshlab (presenti anche repository di Ubuntu) per visualizzare la nuvola dei punti. (Nell'esempio sono stati i punti a distanza maggiore di 1 metro per selezionare solo i punti del volto)
Francamente non amo Processing (a meno che non si declini come IDE di Arduino) ed dopo un po' di ricerche ho trovato il modo di pilotare Kinect mediante Python con la libreria PyOpenNi
L'installazione e' sostanzialmente manuale dai sorgenti con la procedura
Al termine della compilazione non c'e' la possibilita' di fare make install in quanto e' lasciato all'utente la fase finale. Per i miei scopi la cosa piu' semplice e' stata quella di spostare il file openni.so dalla PyOpenNI-build/lib nella cartella degli esempi
Un esempio veramente interessate e' dato da hand-tracker.py che, brevemente modificato, puo' essere utilizzato per creare un mouse virtuale per X (nel brevissimo esempio se la mano si trova tra 750 e 1000 mm dal Kinect viene agganciato il Kinect, se si trova a meno di 750 mm si fa clic sul punto del mouse....lo script e' ampiamente migliorabile)
------------------------------------------------ #! /usr/bin/python from openni import * import os context = Context() context.init() depth_generator = DepthGenerator() depth_generator.create(context) depth_generator.set_resolution_preset(RES_VGA) depth_generator.fps = 30 gesture_generator = GestureGenerator() gesture_generator.create(context) gesture_generator.add_gesture('Wave') hands_generator = HandsGenerator() hands_generator.create(context) # Declare the callbacks # gesture def gesture_detected(src, gesture, id, end_point): print "Detected gesture:", gesture hands_generator.start_tracking(end_point) # gesture_detected def gesture_progress(src, gesture, point, progress): pass # gesture_progress def create(src, id, pos, time): print 'Create ', id, pos # create def update(src, id, pos, time): print 'Update ', id, pos x = pos[0]+300 y = pos[1]+300 if (x < 0): x = 0 if (y < 0): y = 0 if ((pos[2] >750) and (pos[2] < 1000)): os.system("xdotool mousemove "+ str(x)+" "+str(y)) if (pos[2] <750): os.system("xdotool mousemove "+ str(x)+" "+str(y) + " click 1") # update def destroy(src, id, time): print 'Destroy ', id # destroy # Register the callbacks gesture_generator.register_gesture_cb(gesture_detected, gesture_progress) hands_generator.register_hand_cb(create, update, destroy) # Start generating context.start_generating_all() print 'Make a Wave to start tracking...' while True: context.wait_any_update_all() # while