Visualizzazione post con etichetta Kinect. Mostra tutti i post
Visualizzazione post con etichetta Kinect. Mostra tutti i post

mercoledì 11 aprile 2018

Tango Tablet vs Kinect

Ho provato a mettere alla prova il Tablet di Project Tango con il Kinect sulle Mura Etrusche a Fiesole gia' utilizzate come bersaglio in questo post


Pur ammettendo una giornata piovosa la prova con il tablet Tango e' stata disastrosa. Il sensore di profondita' e' riuscito ad acquisire al massimo 1100 punti da una distanza compresa tra 1.0 e 1.5 m; allontanandosi non veniva effettuata nessuna acquisizione mediante il software ParaView.

L'idea che mi sono fatto e' che il sensore di profondita' del tablet Tango non risulta avere una potenza di emissione cosi' elevata da avere riflessioni significative su superfici non omogenee e poco riflettenti.

In buona sostanza non e' possibile farne un utilizzo "in campagna" per scopi geologici. Il suo mondo e' il rilevamento di interni

giovedì 5 aprile 2018

Project Tango

Visto che il Progetto Tango di Google e' stato dismesso a Marzo 2018 i prezzi dei dispositivi Tango sono scesi ad un livello umano e mi sono preso un Project Tango Tablet Development Kit. In estrema sintesi si tratta di una tablet Android NVidia con processore Tegra K1 e 128 Giga di memoria dotato di sensori simili a quelli del Kinect per il calcolo della distanza degli oggetti inquadrati


Il nome in codice del tablet e' Yellowstone altrimenti conosciuto con la sigla NX-74751 (in Star Trek la nave USS Yellowstone ha il codice NX-74751) ed e' stato sviluppato internamente da Google presso Google's ATAP (Advanced Technology and Projects). Per sapere quale hardware c'e' dentro si rimanda a questo link

Attualmente la documentazione delle API di Tango sono sparite dal sito sviluppatori di Google ma sono sempre recuperabili su Archive.org mentre il codice di esempio e' stato spostato qui. Al momento non sono riuscito a trovare una immagine del firmware da poter scaricare
Il sensore di profondita' e' un OV4682 con una matrice di 320x180 pixels (risoluzione molto lontana dal Kinect) ed ha funzione anche di camera RGB. Ci sono inoltre una camera standard da 4 Megapixels piu' una camera fisheye da 120° ed una da 180° (in totale di sono 4 camere)

OSAndroid™ 4.4 (KitKat®)
Screen7.02” 1920x1200 HD IPS display (323 ppi)
Scratch-resistant Corning® glass
Camera1 MP front facing, fixed focus
4 MP 2µm RGB-IR pixel sensor
Size119.77 x 196.33 x 15.36 mm
Weight0.82 lbs (370 g)
Battery4960 mAH cell (2 x 2480 cells)
ProcessorNVIDIA Tegra K1 with 192 CUDA cores
Network/Wireless4G LTE¹
Dual-band Wi-Fi (2.4GHz/5GHz) WiFi 802.11 a/b/g/n
Bluetooth 4.0
NFC (reader and peer to peer modes)
Memory128 GB internal storage (actual formatted capacity will be less)
4 GB RAM
ConnectivityMicro HDMI
USB 3.0 host via dock connector²
Micro SD card
Nano SIM slot
SensorsMotion tracking camera
3D depth sensing
Accelerometer
Ambient light
Barometer
Compass
aGPS
Gyroscope
Audio OutputDual stereo speakers
3.5 mm audio connector (OMTP)

Viene riportato che il sensore della bussola non sia funzionante

Il tablet viene fornito con un dock che funziona da ricarica ed ha una porta USB. La porta USB serve solo per connettere eventuali USB drive o simili, non funziona come canale Adb. Inoltre il tablet e' talmente assetato di risorse che se si connette il solo cavo USB (e non l'alimentazione del dock, 12V 2A con connettore positivo al centro del jack) il dispositivo si scarica invece di caricarsi)

Alcune applicazioni gia' pronte risultano essere
  • App Discovery Project Tango 
  • Room Scanner
  • RTAB-Map (permette esportazione in ply)
  • Tango Scan View (converte i file OBJ in PLY)
  • ADF Tools
  • Tango Explorer
  • Java Point Cloud Example (dagli esempi Java)
Scansione con RTAB-Map visualizzata con MeshLab

E' possibile ancora ricompilare gli esempi ma non sono riuscito a ricompilare questo progetto


Per ottenere i permessi di root si usa TangoRoot e ChainFireSU (io non sono riuscito ad eseguire il root perche' Android continua a dire che e' TangoRoot e' una applicazione pericolosa e non la mette in esecuzione)

I dati delle immagini di profondita' vengono immagazzinati di default in formato super compresso binario proprietario ADF che risulta poco documentato. In ogni caso ci sono applicazione di terze parti che esportano in formati piu' comuni come PLY. La directory di salvataggio dei file ADF e' mutata nel tempo ma in ogni caso sembra richiedere i permessi di root


lunedì 26 marzo 2018

Vettore normale su nuvola di punti

Il problema e' questo : data una nuvola di punti, generata da un sensore come Kinect, determinare i vettori normali delle superfici triangolari che hanno come vertici i punti della nuvola stessa.
Il sistema di coordinate ha l'asse Z orientato come la distanza dal sensore, X ed Y invece sono lungo la dimensione della matrice di acquisizione

Presi tre punti nello spazio (x1,y1,z1),(x2,y2,z2),(x3,y3,z3) non allineati (questa condizione visto che i dati vengono da uno strumento di misura reale diciamo che e' soddisfatta a priori) per calcolare l'equazione del piano si deve calcolare il determinante della seguente matrice con la condizione che sia uguale a zero

(x-x1,   y-y1, z-z1,
 x2-x1, y2-y1, z2-z1,
 x3-x2, y3-y2, z3-z2)

con la formula di Laplace per il determinante si ha che (x,y,z sono le variabili)

(x-x1)[(y2-y1)(z3-z1)-(y3-y1)(z2-z2)] - (y-y1)[(x2-x1)(z3-z1)-(x3-x1)(z2-z1)]+ (z-z1)[(x2-x1)(y3-y1)-(x3-x1)(y2-y1)]= 0

si ha cosi' un risultato nella forma

ax+by+cz = 0

una volta calcolato il piano si deve calcolare la normale (nx,ny,nz)si calcola la derivata parziale 

F = ax +by+cz

nx = dF/dx, ny = dF/dy, nz = dF/dz.

Visto che si tratta di un piano la derivata parziale e' banale e le componenti del vettore normale sono (a,b,c)

-----------------------------------------------------------
Un metodo alternativo (che non prevede di calcolare prima l'equazione di un piano) e' quella di calcolare il prodotto vettoriale tra i due del triangolo). Per esempio dati sempre i tre punti (x1,y1,z1),(x2,y2,z2),(x3,y3,z3) si prendono due vettori

v = (x2-x1, y2-y1, z2-z1)
w = (x3-x1, y3-y1, y3-y1)

il vettore normale e' dato dal determinante della matrice

(x,y,z
v1 ,v2 ,v3,
w1,w2,w2)

n = u X v = (v2w3-v3w2)x+(v3w1-v1w3)y+(v1w2-v2w1)z


Adesso c'e il problema e' di normali ce ne sono due essendo la superficie orientata (brutalmente una sotto ed una sopra al piano) oppure nel prodotto vettoriale dipende dall'ordine con cui si pongono i vettori (vXw e' diverso da wXv)

A questo punto si puo' procedere normalizzando il vettore.

nn1 = n1/sqrt(n1n1+n2n2+n3n3)
nn2 = n2/sqrt(n1n1+n2n2+n3n3)
nn3 = n3/sqrt(n1n1+n2n2+n3n3)

il passaggio da coordinate cartesiane a coordinate.




Se si guarda la libreria PCL la ricostruzione della normale ad una superficie viene effettuata mediante il calcolo delle componenti principali che permette anche di gestire anche che l'orientazione delle normali sia coerente 

giovedì 26 marzo 2015

Kinect v1 vs Kinect v2

Un po' di considerazioni sulla differenza tra Kinect 1 e Kinect2



Le maggiori differenze sono a livello dell'immagine RGB, passata da 640x480 a 1920x1080, ed IR, molto migliorata perche' e' presente in K2 un illuminatore infrarosso che rende la scena leggibile anche nel buio completo.

Per quanto riguarda il sensore di profondita' in K2 la matrice e' passata da 320x240 a 514x424 ma a causa dell'aumento dell'angolo di vista la densita' di punti per grado angolare e' solo passata da 5x5 pixel/grado a 7x7 pixel grado. Rimangono i problemi di acquisire la profondita' su oggetti molto scuri o di materiali particolari per cui la scena di profondita' puo' presentare dei buchi

Per prova ho ripreso in profondita' questi due oggetti, in particolare ero interessato a vedere se Kinect 2 riesce a vedere meglio i pulsanti del telecomando



Questa e' la visione di profondita' di K1 


mentre questa e' la visione di profondita' di K2

Non ci sono sensibili miglioramenti a parte il fatto che K2 produce una mappa piu' smussata perche' registra anche i decimi di millimetro


martedì 17 marzo 2015

CloudCompare e Kinect

Dopo un anno e qualcosa a cercare di installare tutti i pacchetti per usare Kinect su Linux ed oggi ho scoperto che installando CloudCompare tramite ppa su Ubuntu 14.04 viene inserito anche un comodo plugin per pilotare Kinect e per ottenere direttamente la nuvola dei punti gia' registrata con l'immagine RGB


saperlo prima era qualche mal di testa di meno...

giovedì 26 febbraio 2015

Da Kinect a Stampante 3D per paleontologia

Grazie ad un amico sono riuscito ad utilizzare una stampante a 3D.
Lo scopo era quello di, partendo da un fossile, effettuare una scansione 3D con Kinect e poi ottenere nuovamente un modello fisico mediante la stampante 3D

Kinect non e' molto adatto a questo scopo perche' ha un precisione sull'asse verticale di 1 mm e non per modelli piccoli si perde molta risoluzione



Inoltre, dato che la plastica per la stampa 3D e' costosa, mi e' stato concessa una prova su un modello molto ridotto ed alla risoluzione peggiore
La stampa e' durata una 40 (video in tempo estremamente accelerato)



venerdì 2 gennaio 2015

Skanect

Un po' di tempo fa avevo fatto degli esperimenti con Freenect (qui, qui e qui) ma cio' che mancava era la fusione tra il dato di profondita' e quello della foto


Mi e' stato segnalato il softwareSkanect (disponibile su Windows, Mac e Linux) che fa esattamente quanto richiesto (un esempio nel video soprastante, il soggetto e' quello che e'......)
Il programma e' costoso ma per un uso semiprofessionale puo' valere la spesa.

giovedì 5 giugno 2014

Kinect per paleontologia

Continuando la serie delle prove di Kinect come scanner 3D, e' stata provata una applicazione per paleontologia. L'oggetto scansionato e' un corno di cervo (piuttosto malridotto) parzialmente ancora inglobato in matrice argillosa



La scansione e' molto ravvicinata (circa 80 cm) e la scena e' stata tagliata per estrarre il solo dettaglio di interesse
Il colore blu indica le zone piu' vicine al sensore mentre il rosso/arancione sono le zone piu' lontane (ovvero il piano di appoggio)

Il file originale puo' essere scaricato a questo indirizzo

giovedì 22 maggio 2014

Visualizzare PointCloud via Web

Sempre giocando con Kinect mi sono imbattuto in Point Cloud Web Viewer, uno strumeno in WebGL che gestisce anche file di generose dimensioni


Per poter utilizzare e visualizzare i propri dati le uniche impostazioni importanti sono quelle relative alla posizione della telecamera (un cattivo orientamento della telecamera risultera' in uno schermo nero) che si trova alle righe 35,36 del file index.html

L'immagine e' navigabile via mouse o tastiera per cambiare l'orientazione


mercoledì 7 maggio 2014

Kinect per Belle Arti

Continuando gli esperimenti con Kinect ho provato ad acquisire la mappa di profondita' di un basso rilievo. Nello specifico si tratta dei fregi che si trovano sulla base dei leoni delle leonesse nel Piazzale delle Cascine a Firenze
Come si puo' osservare i risultati sono buoni (anche se un po' rumorosi) e le scansioni permettono si osservare dettagli anche molto fini

Fregio 1
Immagine di profondita'


Immagine RGB (da fotocamera)



Fregio 2 
Immagine di profondita'
Immagine RGB da Kinect



mercoledì 23 aprile 2014

Accuratezza del sensore di distanza di Kinect (3)

Sempre per cercare di capire quale sono i limiti del Kinect ho provato a riprendere un muro il piu' possibile in modo perpendicolare



Il risultato e' chiaramente visibile nell'immagine. Anche si tratta di uno zoom piuttosto estremo si vede che l'immagine e' rumorosa con valori anche di +/-2,3 mm millimetri rispetto al valore medio

martedì 22 aprile 2014

Prova Kinect sulle Mura Etrusche di Fiesole

Con lo stesso sistema del precedente post ho provato a scansionare le Mura Etrusche di Fiesole



Di seguito i risultati (decisamente incoraggianti) con il confronto tra l'immagine RGB e la Mesh dei dati di profondita' (elaborati con CloudCompare)




Link ai dati

Kinect per geologia

Ho provato a portare il Kinect in campagna per vedere una possibile applicazione geologica di rilevamento di piccoli affioramenti


Attrezzatura da campagna

Visto che il Kinect ha necessita' di una alimentazione esterna ho preso un inverter della Belkin da 130 W che ha svolto egregiamente il proprio lavoro (il portatile era alimentato mediante la propria batteria)




Le misure sono state effettuate in condizioni di pioggia battente ed affioramenti bagnati (tanto per rendere le condizioni di misura estreme). E' stato verificato che oltre il metro di distanza i risultati erano scarsi probabilmente perche' la roccia bagnata assorbe molto di piu' il laser che in condizioni asciutte e quindi non permette di calcolare la distanza



Foto ad alta risoluzione da cellulare del primo affioramento

Confronto tra immagine RGB e Mesh 

E' stato effettuato un altro rilievo in condizioni ancora peggiori perche' la superficie di affioramento non era ortogonale ma molto inclinata rispetto alla verticale

Immagine RGB da Kinect


Elaborazione Mesh della CloudPoint da Kinect

Qui si possono scaricare i dati originali (per un problema di acquisizione le immagini RGB sono specchiate lungo l'asse maggiore, nel post le immagini sono state corrette)

lunedì 21 aprile 2014

Programma completo per Kinect

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"

FOV IR Camera di Kinect

La camera infrarossa del Kinect (che viene usata per calcolare la mappa di distanza) ha una FOV (Field of View) di 58.5°x45.6° per una risoluzione di 640x480

Tradotto in radianti (prendendo come origine degli assi il centro dell'immagine) si ha che
alfa (angolo lungo l'asse maggiore) = +/- 0.510588 rad
beta (angolo lungo l'asse minore= +/- 0.397935 rad

dividendo per la risoluzione in pixel si hanno del dimensioni di ogni pixel
delta_alfa = 0.00159534
delta_beta = 0.001658052

Per calcolare la reale dimesione di ogni pixel, considerando un caso semplice ovvero una immagine piatta e ortogonale al sensore, le misure reali si possono calcolare come
dis_c = distanza del punto centrale dell'immagine
x_reale = dis_c * tg(alfa)
y_reale = dis_c * tg(beta)

ad una distanza di 80 cm la finestra di kinect risulta essere di 130x81 cm ovvero circa 1 metro quadrato.


tradotto in Python
----------------------------------------------------------
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")
              t=t+1
              alfa = alfa + delta_a
beta = beta - delta_b
----------------------------------------------------------
Per verificare che i calcoli siano corretti ho ripreso un oggetto di dimensioni conosciute ovvero un libro di 292x205 mm ad una distanza di 1342 mm (centro immagine)

Immagine RGB

CloudP Point in Meshlab

CloudP Point in Meshlab

Come si deve le dimensioni misurate risultano essere 288mmx198mm a conferma di come i calcoli siano corretti

martedì 15 aprile 2014

Accelerometro su Kinect

Kinect e' fornita di un accelerometro a 3 assi che secondo quando riportato da IFixIt dovrebbe essere un modello KXSD9 di Kionix



Per leggere i dati dell'accelorometro in Python si puo' usare il seguente script (ripreso da qui)
Attenzione: per funzionare e' necessario utilizzare PyUsb alla versione unstable 1.0 e non quella stable 0.4 che normalmente e' pacchettizzata per Linux
Prima e' quindi necessario scaricare il pacchetto unstable da GitHub
(installarea con il classico python setup.py install)
Lo script e' un demo anche per muovere il motore di tilt del Kinect
------------------------------------------------------
import usb.core
import usb.util
import sys
import time


# find our device
dev = usb.core.find(idVendor=0x045e, idProduct=0x02B0)
# was it found?
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')


# set the active configuration. With no arguments, the first
# configuration will be the active one
dev.set_configuration()

# (bmRequestType, bmRequestType, bmRequest, wValue, wIndex, nBytes)
ret = dev.ctrl_transfer(0xC0, 0x10, 0x0, 0x0, 1)

print hex(ret[0])   # should return 0x22 but dont know why ?
# ???
ret = dev.ctrl_transfer(0x40, 0x6, 0x1, 0x0, [])

time.sleep(1.5)
# head up!
ret = dev.ctrl_transfer(0x40, 0x31, 0xfff0, 0x0, [])
time.sleep(1.5)
# bring head down
ret = dev.ctrl_transfer(0x40, 0x31, 0xffd0, 0x0, [])
time.sleep(1.5)
# up!
ret = dev.ctrl_transfer(0x40, 0x31, 0xfff0, 0x0, [])
time.sleep(1.5)
# down!
ret = dev.ctrl_transfer(0x40, 0x31, 0xffd0, 0x0, [])
time.sleep(1.5)
# up!
ret = dev.ctrl_transfer(0x40, 0x31, 0xfff0, 0x0, [])


while True:
    # Get accel data
    ret = dev.ctrl_transfer(0xC0, 0x32, 0x0, 0x0, 10)
    #print map(hex, ret)
    # bytes 0 & 1 are always zero
    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
    print x, "\t", y, "\t", z
    time.sleep(0.5)
------------------------------------------------------
(attenzione l'asse x dello script in  Python coincide con l'asse Z della codifica Microsoft mentre l'asse z dello script in Python corrisponde con quello X della codifica Microsoft)

Leggendo i dati si ha che non e' presente un sensore di azimuth per cui non e' possibile avere una orientazione nello spazio kinect. In pratica non e' possibile distinguere la rotazione sull'asse Y

Sito Microsoft


L'asse Z Microsoft mostra la rotazione destra/sinistra (mettendo di fronte al kinect appoggiato sul tavolo) e ruotando di 90° verso sinistra mostra valori positivi di 858 unita' mentre ruotando a destra di -797 unita'

L'asse X mostra la rotazione con valori di +779 unita' per posizione a 90° con i sensori che guardano lo zenith mentre di -865 unita' con i sensori che guardano nadir

Se si continua la rotazione oltre i 90° i valori tornano a decrescere verso il valore 0. Per capire se il sensore sta guardando avanti od indietro (ovvero se e' appoggiato alla sua basetta od e' appeso alla sua basetta) vengono usati i valori dell'asse Y Microsoft (y Python) che sono positivi se il Kinect guarda avanti e negativi se guarda indietro

i valori riportati sono espressi come accelerazioni in funzione dell'accelerazione di gravita'
Per calcolare l'accelerazione reale misurata la regola sembra essere
(misura/819)*9.18 m/sec*sec

Sembra quindi che i valori letti debbano essere confinati tra 0 ed 819 mentre le mie letture eccedono questo valore. Se si calcola il valore medio tra la misura maggiore e minore si ha che
asse Z (858+797)/2 = 827
asse X (779+865)/2=822
quindi considerando l'incertezza (vedi sotto) c'e' solo da calcolare un piccolo offset dello zero ed i valori coincidono con quelli di fabbrica

Mantenendo fermo il kinect ho effettuato oltre 14200 misure ed ho ottenuto una deviazione standard dei dati nei vari assi come segue
x = +/- 6.9 unita'
y = +/- 7.45 unita'
z = +/- 21.7 unita'

Per riportare i valori di inclinazione in gradi si puo' usare il seguente calcolo
-------------------------------------------------------------------------
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)
-------------------------------------------------------------------------
Di seguito una immagine con l'orientazione ed il valore degli angoli in relazione a diverse orientazioni


RGB con PyOpenNi Kinect

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")

Kinect con SimpleCV


Un metodo alternatico a PyOpenNi per interagire con Kinect da Python e' utilizzare SimpleCV


Per l'installazione si puo' procedere come segue

sudo apt-get install libopencv-*
sudo apt-get isntall python-opencv
sudo apt-get install python-numpy
sudo apt-get install python-pygame
sudo apt-get install python-setuptools


si scarica la libreria Simple CV di GitHub al seguente link e si installa com
python setup.py install


se tutto e' andato a buon fine sono sufficiente le poche righe sottostanti per avere l'acquisizione dell'immagine RGB e di profondita' da Kinect

------------------------------------------------------
from SimpleCV import *

cam=Kinect()


depth = cam.getDepth()
depth.save('depth.jpg')
rgb = cam.getImage()
rgb.save('rgb.jpg')

Allineamento RGB/IR su Kinect

Sovrapponendo le immagini RGB e di profondita' di Kinect si vede chiaramente, indipendentemente se l'oggetto e' in primo piano o sullo sfondo


Leggendo questo link, si osserva che i parametri di calibrazioni delle camere sono codificate (e diversi) all'interno di ogni Kinect in funzione di come sono state montate e calibrate in fabbrica
Usando la versione completa di OpenNi (o l'SDK Microsoft) sono presenti specifiche funzioni che effettuano la fusione delle immagini dai due sensori come depth.GetAlternativeViewPointCap().SetViewPoint(image);

venerdì 11 aprile 2014

3D Scanner con Kinect

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.

Geologi

  E so anche espatriare senza praticamente toccare strada asfaltata