近來在做三維姿態(tài)恐怕的東西,用到了上的深度攝像頭(),我們都曉得深度攝像頭可以獲得某個點的三維信息(基于此可以做好多有趣的東西,例如三維重建,三維關(guān)鍵點跟蹤與檢查,后續(xù)有空漸漸填坑),但具體怎么獲得網(wǎng)上能找到的資料也不多,我在這兒整理了一下我近來收集到的資料并提供了基于Swift的示例代碼,該文章分成下邊幾小節(jié)來探討。
雖然最后估算獲得三維座標系的方式很簡單凸透鏡成像原理應用,但要了解為何要這樣估算須要清楚單反成像的原理。
單反大致工作原理
X采用的是結(jié)構(gòu)光的方案,這兒以的工作流程來解釋下它是怎樣獲取深度值的:
點陣投影器和泛光照明器都可以投射紅外線光點,不同之處在于后者幀率高前者幀率低,且后者投射結(jié)構(gòu)光,前者投射非結(jié)構(gòu)光。
這兒并不對原理展開講,感盛行的可以移步閱讀尾部的參考鏈接
凸透鏡成像中的焦距和光心
不同角度出發(fā)的光線經(jīng)過透鏡,跟透鏡表面產(chǎn)生不同的傾角,形成不同程度的折射。
從一個真實世界w的一點出發(fā)的光,經(jīng)過透鏡,又重新匯集到一點,最終產(chǎn)生了點對點的成像關(guān)系,從上圖我們可以得出以下幾個名詞的定義。
光心:凸透鏡的中心
焦點:一束光以凸透鏡的主軸穿過凸透鏡時,在凸透鏡的另外兩側(cè)會被凸透鏡凝聚成一點,這一點稱作焦點。
焦距:焦點到凸透鏡光心的距離就稱作這個凸透鏡的焦距,一個凸透鏡的一側(cè)各自有一個焦點。
清楚這幾個基本概念后理解下邊幾個座標系就愈加容易了。
單反成像中所用到的世界,單反凸透鏡成像原理應用,圖象,象素座標系
我們換一張成像的原理圖
成像過程中須要經(jīng)過幾個座標系的轉(zhuǎn)換,最后顯示在我們的屏幕上。
通常來說,須要經(jīng)過四個座標系的轉(zhuǎn)換。
世界座標系
描述現(xiàn)實世界中物體所處的三維座標。
單反座標系
以單反的光心為座標原點,x軸和y軸分別平行于圖象座標系的x軸和y軸,單反的光軸為z軸。
圖象座標系
以圖象平面(通常指傳感)的中心為座標原點,x軸和y軸分別平行于圖象平面的兩條垂直邊,用(x,y)表示其座標值,圖象座標系是用化學單位(比如毫米)表示象素在圖象中的位置。
象素座標系
以圖象平面左上角的頂點為原點,x軸和y軸分別平行于圖象坐標的x軸和y軸,用(u,v)表示其座標值。這個座標系也就是最終在我們手機上顯示的座標系。
所以,假若我們假如我們想獲得象素點對應的三維坐標的話,就要按照象素座標系反推回單反座標系中。而怎樣反推就涉及到幾個座標系之間的轉(zhuǎn)換方式。
已知一個現(xiàn)實世界中的物體點在世界座標系中的座標為(X,Y,Z),單反座標系為(Xc,Yc,Zc),圖象座標系中的座標為(x,y),象素座標系上的座標為(u,v)
象素座標系與圖象座標系之間的轉(zhuǎn)換為:
其中u0,v0是圖象座標系原點在象素座標系中的座標,dx和dy分別是每位象素在圖象平面上x和y方向上的規(guī)格,這種值也被稱為圖象的內(nèi)參矩陣,是可以通過API領(lǐng)到的。
圖象座標系與單反座標系之間的轉(zhuǎn)換為:
其中f為焦距,為何如此轉(zhuǎn)換是按照相像三角形定律得到的,如右圖所示:
最后則是單反座標系與世界座標系的轉(zhuǎn)換關(guān)系:
其中R為3x3的正交旋轉(zhuǎn)矩陣,t為三維平移向量,這幾個參數(shù)也被稱為單反的外參矩陣,也是可以領(lǐng)到的。
在通常的應用中,我們只須要從象素座標系轉(zhuǎn)換到單反座標系就夠用了。
基本知識都打算完畢,接下來看怎樣在上獲取象素點的三維座標。
在Swift按照象素點估算出它基于單反的三維座標
在Swift中啟動單反主要有以下三個步驟
// 1. 發(fā)現(xiàn) TruthDepth 相機
let videoDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInTrueDepthCamera], mediaType: .video, position: .front)
// 2. 初始化輸入和輸出
let videoDeviceInput = try AVCaptureDeviceInput(device: videoDeviceDiscoverySession.devices.first!)
// 3. 給 session 添加輸出
let depthDataOutput = AVCaptureDepthDataOutput() session.addOutput(depthDataOutput) depthDataOutput.setDelegate(self, callbackQueue: dataOutputQueue)
由于我們設(shè)置了,所以單反只要捕捉到一幀深度圖都會反彈下邊這個函數(shù)
func dataOutputSynchronizer(_ synchronizer: AVCaptureDataOutputSynchronizer, didOutput synchronizedDataCollection: AVCaptureSynchronizedDataCollection) {
...
// 獲得相機內(nèi)參數(shù)和對應的分辨率
let intrinsicMartix = syncedDepthData.depthData.cameraCalibrationData?.intrinsicMatrix
let refenceDimension = syncedDepthData.depthData.cameraCalibrationData?.intrinsicMatrixReferenceDimensions
self.camFx = intrinsicMartix![0][0]
self.camFy = intrinsicMartix![1][1]
self.camOx = intrinsicMartix![0][2]
self.camOy = intrinsicMartix![1][2]
self.refWidth = Float(refenceDimension!.width)
self.refHeight = Float(refenceDimension!.height)
...
}
在這個反彈函數(shù)里,我們可以獲得攝像頭的內(nèi)參數(shù),示例的程序中,只要觸摸預覽圖中某一個象素點,程序會調(diào)用下邊代碼塊輸出該象素點在單反座標系下的X,Y和Z的值。
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
let touchPoint = (touches as NSSet).allObjects[0] as! UITouch
// 獲得像素坐標系的坐標
let coord = touchPoint.location(in: self.preview)
let viewContent = self.preview.bounds
let xRatio = Float(coord.x / viewContent.size.width)
let yRatio = Float(coord.y / viewContent.size.height)
// 獲得觸摸像素點的深度值 Z,單位為 cm
let realZ = getDepth(from: depthPixelBuffer!, atXRatio: xRatio, atYRatio: yRatio)
// 獲得對應的 X 和 Y 值,計算公式其實就是兩個坐標轉(zhuǎn)換矩陣之間相乘后的結(jié)果
// 像素 -> 圖像 -> 相機坐標系
let realX = (xRatio * refWidth! - camOx!) * realZ / camFx!
let realY = (yRatio * refHeight! - camOy!) * realZ / camFy!
DispatchQueue.main.async {
self.touchCoord.text = String.localizedStringWithFormat("X = %.2f cm, Y = %.2f cm, Z = %.2f cm", realX, realY, realZ)
}
}
示例程序的療效圖如下:
image.png
這兒輸出的是黑色點對應的X,Y,Z值,完整代碼戳這兒。
參考鏈接
[1]手臂辨識技術(shù)解析
[2]Quoar:WhatisthefloodinXfor?
[3]世界,單反,圖象,象素座標系之間的關(guān)系
[4]Guide-Apple
[5]PhotoandVideoUsingDepth