用 F# 和 Kinect SDK 產生 Point Cloud
Posted by tjwei on 星期一, 7月 11, 2011 with No comments
本文附的 F# 程式碼,是用 Kinect SDK 來產生 "points cloud"。
程式的效果如影片。
過程中的一些紀錄:
- 因為之前的 visual studio 2010 過了試用期限,所以我有一段時間沒用 F#, 稍有一點生疏,但這問題不大。問題是我沒有寫過 .net 上的 GUI 程式,更別說是 3D 繪圖了,所以花了一點時間在網路上找範例。 F# 的 GUI 參考資料似乎不是很多,很多似乎也不太靈。
- Kinect SDK 和 driver 簡單好用,骨架判斷似乎比之前 OpenNI 容易(沒有打錯字,這不是用來判斷股價的軟體),在狹小空間(像是大多數寫程式的地方)就能辨認出骨架。
- 根據 Wikipedia,Kinect 的 depth sensor 橫向角度 57度,垂直角度 43度。
- 網路查詢結果,System.Media3D 似乎無法直接畫出「點」,沒辦法做出 point cloud。所以用 squares cloud 或者 cubes cloud 來代替。
- 我的筆記型電腦 GPU 太差,最多只能跑 80x60 的 cubes cloud。所以影片中解析度很差。
- 設法將 Video Image 和 Depth Image 組合,發現不吻合。理論上應該要用 Color in Depth Space 或者 Depth in Color Space 的參數來設定 Kinect,但是網路上查詢的結果是, Kinect SDK Beta 還不支援這些。
- 也許應該改用 OpenNI,但是程式碼已經寫了一半了,繼續用手工的方是將色彩和深度組合。
- 色彩和深度的圖形居然是鏡像(x軸方向相反)?應該是我哪裡搞錯了。
- 由於沒有硬體規格,解析度太低也很難實驗,所以用觀察和嘗試錯誤法很粗糙的將色彩資訊以及深度資訊勉強組合在一起。
- 之後改用 OpenNI 試試看。
open System open System.Xaml open System.Windows open System.Windows.Controls open System.Windows.Media open System.Windows.Media.Media3D open System.Threading open Microsoft.Research.Kinect.Nui let rx,ry,res= 80, 60, ImageResolution.Resolution80x60 //let rx,ry,res= 320, 240, ImageResolution.Resolution320x240 let vrx,vry,vres= 640, 480, ImageResolution.Resolution640x480 let screenZ = 100. let screenX = screenZ * 2.*(Math.Sin (38.5*Math.PI/180.0)) let screenY = screenZ * 2.*(Math.Sin (21.5*Math.PI/180.0)) let dX, dY=screenX /(float rx), screenY /(float ry) let smallCube (p:Vector3D) = let i,j=Vector3D(dX,0.,0.)/2.0,Vector3D(0.,dY,0.)/2.0 let k=Vector3D(0., 0., dX)/2.0 let g=new MeshGeometry3D() [p-i-j-k;p+i-j-k;p+i+j-k;p-i+j-k;p-i-j+k;p+i-j+k;p+i+j+k;p-i+j+k] |> List.iter (fun v -> Point3D(v.X, v.Y, v.Z) |> g.Positions.Add) [[2;1;0];[2;0;3]; [7;3;0];[7;0;4]; [6;5;1];[6;1;2]; [7;6;2];[7;2;3]; [5;6;7];[5;7;4]; [1;5;4];[0;1;4]] |> Seq.concat |> Seq.iter g.TriangleIndices.Add GeometryModel3D(Geometry = g, Material = DiffuseMaterial(Brushes.White)) let models = Model3DGroup() let vprt = let translation = TranslateTransform3D(0., 0., 50.) let rotation = AxisAngleRotation3D(Vector3D(0.,1.,0.), 30.) let rotation2 = AxisAngleRotation3D(Vector3D(1.,0.,0.), 30.) let anim = Animation.DoubleAnimation (-45., 45., Duration(System.TimeSpan.FromSeconds 8.), AutoReverse=true, RepeatBehavior = Animation.RepeatBehavior.Forever) let anim2 = Animation.DoubleAnimation (-20., 45., Duration(System.TimeSpan.FromSeconds 21.), AutoReverse=true, RepeatBehavior = Animation.RepeatBehavior.Forever) let tg=Transform3DGroup() rotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, anim) rotation2.BeginAnimation(AxisAngleRotation3D.AngleProperty, anim2) rotation2.BeginAnimation(TranslateTransform3D.OffsetYProperty, anim2) tg.Children.Add (RotateTransform3D rotation) tg.Children.Add (RotateTransform3D rotation2) tg.Children.Add translation Viewport3D(Camera = PerspectiveCamera(Point3D(0.,0., -50.), Vector3D(0., 0., 1.), Vector3D(0., 1., 0.), 60., Transform=tg)) let sqCloud = [for iy in [0 .. (ry-1)] do for ix in [0 .. (rx-1)] do yield Vector3D((float(ix-rx/2))*dX, (float(ry/2-iy))*dY, 100.) |> smallCube ] let nui = Runtime() let wnd=Window(Title = "Kinect Depth", Background=Brushes.Black, Content=vprt) let app=new Application() let updateCloud zList= sqCloud |> List.iter2 ( fun (z, argb) p -> if z=0.0 then p.Transform<-TranslateTransform3D(0.,0.,5000.0) p.Material<- DiffuseMaterial(Brushes.Green) else let c = Color.FromArgb argb p.Material<- DiffuseMaterial(SolidColorBrush(c)) p.Transform <- ScaleTransform3D(z,z,z)) zList let handleDepth (bits:byte[]) (vbits:byte[])= let diffx = 0.015 let getVix x z = if z=0.0 then -1 else (x/(float rx)+diffx-diffx/z)*(float vrx) |> Math.Round |> int let getViy y z = if z = 0.0 then -1 else (y/(float ry)*0.96+0.04)*(float vry) |> Math.Round |> int let getVixiy ix iy z = (getVix (float ix) z, getViy (float iy) z) let getVi vix viy = if vix<0 || vix>=vrx || viy<0 || viy>=vry then -1 else (vrx-vix-1+viy*vrx)*4 let zList=[for i in 0 .. 2 .. bits.Length-1 do let i1=int bits.[i] let i2=(bits.[i+1] &&& (byte 0xf) )|> int let z=(float ((i2<<<8)|||i1))/4096.0 let iy, ix = Math.DivRem (i>>>1, rx) let vix, viy = getVixiy ix iy z let vi = getVi vix viy let argb = if vi = -1 then (byte 0x30, byte 0,byte 0xff, byte 0) else (byte 0xff, vbits.[vi+2], vbits.[vi+1], vbits.[vi]) yield (z, argb)] ((new ThreadStart(fun () -> updateCloud zList), null) |> app.Dispatcher.BeginInvoke).Wait() |>ignore Thread.Sleep(7) let videoReady (e:ImageFrameReadyEventArgs) = let bits = (nui.DepthStream.GetNextFrame(100).Image.Bits) handleDepth bits (e.ImageFrame.Image.Bits) vprt.Children.Add(ModelVisual3D(Content = models)) sqCloud |> List.iter models.Children.Add AmbientLight() |> models.Children.Add nui.Initialize(RuntimeOptions.UseDepth ||| RuntimeOptions.UseColor) nui.DepthStream.Open(ImageStreamType.Depth, 2, res, ImageType.Depth) nui.VideoStream.Open(ImageStreamType.Video, 2, vres, ImageType.Color) nui.VideoFrameReady.Add videoReady wnd.Closed.Add (fun e -> do nui.Uninitialize() Environment.Exit 0 ) [<STAThread>] do app.Run(wnd) |> ignore
0 意見:
張貼留言