相機圖像
圖像接口基礎
圖象畸變
圖像方向
獲得原始圖像
獲得校準映射矩陣
圖像射線校準
使用Image.warp()校正
使用32位ARGB紋理編碼畸變數據
Color encodeFloatRGBA(float input)
{
input = (input + 0.6)/2.3; //scale the input value to the range [0..1]
float r = input;
float g = input * 255;
float b = input * 255 * 255;
float a = input * 255 * 255 * 255;
r = r - (float)Math.floor(r);
g = g - (float)Math.floor(g);
b = b - (float)Math.floor(b);
a = a - (float)Math.floor(a);
return Color(r, g, b, a);
}
爲了重組在片斷着色器中的值,你可以在紋理中查找對應值並進行倒數操作。爲避免丟失太多精度,將x,y畸變值編碼成獨立的紋理。一旦失真指數從紋理中採樣和解碼,你能夠在相機圖像紋理中找到校正後的亮度值。(注:GLSL亂入中)
uniform sampler2D texture;
uniform sampler2D vDistortion;
uniform sampler2D hDistortion;
varying vec2 distortionLookup;
varying vec4 vertColor;
varying vec4 vertTexCoord;
const vec4 decoderCoefficients = vec4(1.0, 1.0/255.0, 1.0/(255.0*255.0), 1.0/(255.0*255.0*255.0));
void main() {
vec4 vEncoded = texture2D(vDistortion, vertTexCoord.st);
vec4 hEncoded = texture2D(hDistortion, vertTexCoord.st);
float vIndex = dot(vEncoded, decoderCoefficients) * 2.3 - 0.6;
float hIndex = dot(hEncoded, decoderCoefficients) * 2.3 - 0.6;
if(vIndex >= 0.0 && vIndex <= 1.0
&& hIndex >= 0.0 && hIndex <= 1.0)
{
gl_FragColor = texture2D(texture, vec2(hIndex, vIndex)) * vertColor;
} else {
gl_FragColor = vec4(1.0, 0, 0, 1.0); //show invalid pixels as red
}
}
使用雙線性插值校正
重新調用含有64X64網格元素的畸變映射矩陣,想象這些網格均勻的散佈在你的目標圖像中。(元素[0,0]在左下方角點同時[64,64]在右上方)。每一個元素包括水平座標與垂直座標用於確定傳感器圖像數據中的圖像數據,以找到目標圖像中像素的記錄亮度。爲了尋找到畸變網格元素間的像素的亮度值,你應當利用該像素周圍的4個網格點進行插值計算。
用於在目標圖像中尋找給定像素的畸變修正亮度值的基本算法:
1.找到目標像素周圍四個校正網格點。
2.計算基於目標點距離網格點的距離計算插值權重。
3.查找這四個網格點的每一個的水平值和垂直值(譯者注:x座標?y座標?)
4.使用基於距離的權重因子計算水平值得雙線性插值。
5.爲縱向值重複插值計算。
6.拒絕那些橫向座標或縱向座標不在[0,1]範圍之內的。
7.反歸一化這些值,使他們變成原始傳感器數據中的像素座標。
8.查找傳感器值所計算的像素座標。
9.在目標圖像中爲原始座標設置亮度值。
在Python中遍歷圖像像素進行雙線性插值是很慢的。你可以使用OpenCV提供的函數進行插值計算。第一將畸變數據轉換成cv2.remap()函數能使用的數據格式:
import cv2, Leap, math, ctypes
import numpy as np
def convert_distortion_maps(image):
distortion_length = image.distortion_width * image.distortion_height
xmap = np.zeros(distortion_length/2, dtype=np.float32)
ymap = np.zeros(distortion_length/2, dtype=np.float32)
for i in range(0, distortion_length, 2):
xmap[distortion_length/2 - i/2 - 1] = image.distortion[i] * image.width
ymap[distortion_length/2 - i/2 - 1] = image.distortion[i + 1] * image.height
xmap = np.reshape(xmap, (image.distortion_height, image.distortion_width/2))
ymap = np.reshape(ymap, (image.distortion_height, image.distortion_width/2))
#resize the distortion map to equal desired destination image size
resized_xmap = cv2.resize(xmap,
(image.width, image.height),
0, 0,
cv2.INTER_LINEAR)
resized_ymap = cv2.resize(ymap,
(image.width, image.height),
0, 0,
cv2.INTER_LINEAR)
#Use faster fixed point maps
coordinate_map, interpolation_coefficients = cv2.convertMaps(resized_xmap,
resized_ymap,
cv2.CV_32FC1,
nninterpolation = False)
return coordinate_map, interpolation_coefficients
然後將這些map和相應的圖像傳給cv2.map()函數:
def undistort(image, coordinate_map, coefficient_map, width, height):
destination = np.empty((width, height), dtype = np.ubyte)
#wrap image data in numpy array
i_address = int(image.data_pointer)
ctype_array_def = ctypes.c_ubyte * image.height * image.width
# as ctypes array
as_ctype_array = ctype_array_def.from_address(i_address)
# as numpy array
as_numpy_array = np.ctypeslib.as_array(as_ctype_array)
img = np.reshape(as_numpy_array, (image.height, image.width))
#remap image to destination
destination = cv2.remap(img,
coordinate_map,
coefficient_map,
interpolation = cv2.INTER_LINEAR)
#resize output to desired destination size
destination = cv2.resize(destination,
(width, height),
0, 0,
cv2.INTER_LINEAR)
return destination
注意你應當避免每一幀都轉換畸變映射矩陣,只有當一個新的設備接入時、圖像反轉方向時(當手從相反的方向進入)或者設備被重新校準此矩陣纔會發生改變。下面代碼只會進行一次轉換畸變映射矩陣(所以無法處理當畸變映射矩陣發生變化的情況)
def run(controller):
maps_initialized = False
while(True):
frame = controller.frame()
image = frame.images[0]
if image.is_valid:
if not maps_initialized:
left_coordinates, left_coefficients = convert_distortion_maps(frame.images[0])
right_coordinates, right_coefficients = convert_distortion_maps(frame.images[1])
maps_initialized = True
undistorted_left = undistort(image, left_coordinates, left_coefficients, 400, 400)
undistorted_right = undistort(image, right_coordinates, right_coefficients, 400, 400)
#display images
cv2.imshow('Left Camera', undistorted_left)
cv2.imshow('Right Camera', undistorted_right)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
def main():
controller = Leap.Controller()
controller.set_policy_flags(Leap.Controller.POLICY_IMAGES)
try:
run(controller)
except KeyboardInterrupt:
sys.exit(0)
if __name__ == '__main__':
main()
在圖像上畫出追蹤數據
左相機的計算過程。加上偏移距離代替了減去右相機的對應值。
只要你知道光線的斜率,你就能通過調用warp()函數獲得像素的座標。
注意:偏移值可能因爲LeapMotion控制器的不同形式的因素而不同,但是目前沒有方法從API中獲得這個值。
如果你顯示了校正後的圖像數據,那麼將追蹤數據與圖像數據相關聯取決於你顯示圖像的方式。對於3D視角,使用不變的縮放因子和紋理貼圖的校正後的位置顯示圖像是有問題的。對於其他形式的顯示,你必須根據你矯正圖像的方式將代表LeapMotion中一個位置的光線斜率轉換成目標圖像像素座標
計算圖象特徵的方向向量
使用Image.rectify()獲得圖像特徵方向向量。Image.rectify()返回一個向量包含水平和豎直斜率(以相機原點定義)給出了在原始圖像數據中的像素座標。
如果你能以足夠的精度辯認出圖像中的相同的特徵,你可以使用從兩個攝像機獲得的斜率值計算出3D位置/
頭戴式顯示模式
LeapMotion服務/守護程序提供了當LeapMotion附着於一個頭戴式顯示器上時優化跟蹤數據的模式。在這個模式下LeapMotion軟件會從上方而不是底部檢測手。但是,手心是朝向LeapMotion傳感器還是背離傳感器是有二義性的,設置這個模式使得LeapMotion軟件會初始化手部模型使得它默認手心是背向傳感器的。這樣有利於將LeapMotion設備安裝在頭戴式顯示器的表面作爲控制器。
爲了在你的應用中開啓這個模式,你需要開啓優化HMD方法
controller.set_policy(Leap.Controller.POLICY_OPTIMIZE_HMD)
對於那些不能安裝在HMD上的硬件,這個方法是會被拒絕的,如那些嵌在筆記本上或者鍵盤上的設備。