【翻譯】Leapmotion-python開發官方文檔(10)

相機圖像

LeapMotion控制器採用雙目紅外相機作爲跟蹤傳感器。你可以使用Controller.images或者Frame.images來訪問由相機採集到的圖像。這些函數提供ImageList對象,包含Image對象。Controller.images提供一組最新的圖像。Frame.images提供用於分析生成當前數據幀的那一組圖像會比直接從Controller對象產生的圖像稍延遲一點。
從相機中採集到的一幅圖像。高亮網格點標記出了圖像中明顯而又複雜的畸變。
這些圖像可以用來:
頭戴式顯示器視頻顯示。
增強現實
計算機視覺。
Image 通過用接口提供了包括傳感器亮度值的緩衝器和包括相機校準映射矩陣,可以用來校準鏡頭畸變和其他的光學畸變。

圖像接口基礎

從Controller.images或者Frame.images獲得ImageList對象。Controller.images函數會返回最新獲得的照片。Frame.images返回與當前幀相關的數據幀。因爲處理數據幀會花一點時間,所以會落後於從Controller獲得的圖像。(在未來的版本中,數據幀速率可能從相機幀速率去耦,所以差距會變得更大)。從Controller中獲取的圖像有最小的延遲,但是可能和當前數據幀的跟蹤數據不匹配。當使用Controller.images你可以重新實現Listener對象中的On_images()回調函數。當一組新的圖像準備好時Controller就會調用你的Listener.on_images()回調函數。
圖像數據是一組像素值數組。數據格式通過Image.format值返回。一般當前只有一個格式在使用。“INFRARED”格式每個像素使用一個字節,爲傳感器位置定義了亮度測量。你可以用灰度圖顯示紅外格式的圖像。

圖象畸變

當一束光進入LeapMotion的攝像頭時,鏡片將光彎曲(折射)使光打在傳感器上,傳感器將其記錄爲一個特定像素位置的灰度值。當然,沒有鏡頭是完美的,所以一束光不可能打在傳感器的光學理想點上。校準映射矩陣提供修正這一畸變的數據。使你能夠計算出真實的光線原始角度,你可以利用這個修正後的角度生成無畸變圖像,同時利用雙攝像頭中獲得的角度,你能夠通過三角測量找到圖像中特定物體的真實位置。注意,校準映射矩陣只會修正鏡頭畸變而不會修正透視變形。
爲了校正圖像,帶有畸變的圖像數據可以傳送到一個着色器程序中,其可以對校準使用插值函數並應用到傳入鏡頭的光中。爲了獲得一個小點集的角度,你可以使用Image.warp()函數(但是這個函數在高幀率情況下轉換全位圖時效率不夠)
失真數據基於LeapMotion攝像頭的視野角度。image類提供了一些函數,Image.ray_scale_x和Image.ray_scal_y能夠按比例的使視角擴大來確保畸變映射矩陣能夠應用到整個視野中。大概當前LeapMotion設備能有150°可視範圍i,意味着一束穿過鏡頭的光最大坡度不超過4/1。

150°視角對應於±4的坡度。(75°的切線大約是4).
上圖展示了重建畸變矯正矩陣。每個像素的亮度值源於從特定方向進入相機的光線。圖像通過計算每個像素表示的水平和豎直坡度,同時利用校正映射矩陣在圖像數據中找到真實的亮度值。圖像的紅色區域代表無亮度的區域(實際的視角小於150°)

圖像方向

在LeapMotion座標系統中圖像頂部一直指向z軸的負半軸方向。默認的,LeapMotion軟件自動適應此座標系統這樣手就是從Z軸的正半軸方向進入圖像得了。(用戶可以在LeapMotion的控制面板中使自動方向失效。)在手進入LeapMotion視野之前,無法知道圖像的方向。因爲用戶可能正常放置設備也有可能以任意方向放置設備(即,設備長的那一側的綠色LED燈朝向一個方向或者其他)。如果用戶朝相反的方向放置設備,那麼圖像會顛倒,直到有手進入LeapMotion的視野中(或者將設備轉過來)

獲得原始圖像

在你獲得圖像數據之前,你必須使用Controller.set_policy()函數設置POLICY_IMAGES標誌。出於保護隱私,每個使用者也必須在LeapMotion控制面板中使能關鍵字使得任何程序可以訪問相機的原始數據。
爲了獲得圖像數據,用戶應使用Controller,images或Frame.images函數。因爲LeapMotion設備有兩個攝像頭。這些函數能夠返回一個ImageList對象包含兩張圖象(這一點在將來可能會變,如果多個LeapMotion可以同時工作)。
圖像編號從零開始,先左相機的圖像後右相機的圖像。注意設備的左右方向可以基於用戶放入手的方向自動檢測。自動方向檢測使能設置在LeapMotion的控制面板中。
一旦你獲得了一個Image對象,你可以從數據緩衝中獲得8位的亮度值。這個緩衝器的大小等於Image.width乘以Image.height乘以Image.bytes_per_pixel。圖像的長和寬跟隨當前控制器的操作模式的變化而變化,這可以逐幀的變化。注意在強健模式下圖像變爲原來的一半。

獲得校準映射矩陣

校準映射矩陣可以用來修正由鏡頭曲率及其他原因產生的圖像畸變。這個映射矩陣是一個64*64的分格。每一個點都包括兩個32位的值,所以緩衝大小爲128乘以64乘以4.你可以通過使用Image.distortion函數獲得該矩陣的緩衝。
每一個在緩衝區的點指出了在原始圖像中對應於修正後的亮度值的像素的位置。有效的座標是歸一化了的,值在0與1之間的小數。校準映射矩陣中單個元素的值範圍在[-0.6,2.3],但是低於0和高於1的座標是不合法的,在使用校準數據時要將[0,1]範圍之外的值拋棄。
爲轉換像素座標要與圖像的寬或者長相乘。對於那些位於校準網格點之間的點,你可以使用相鄰網格點的插值。相機鏡頭有很大的視角和畸變。因於此,在座標網格中並不是所有的點都是有效點。下面的渲染圖用顏色值展示了鏡頭校準數據。左邊圖是X方向的值,右邊是Y方向的。

紅色值是位於圖像之外的映射值
校準映射矩陣的大小可能在將來發生改變,所以Image類提供了網格尺寸(通過使用.distortion_width和.distirtion_height)實際上是網格點的兩倍(寬)。包括校準數據的緩衝器的長度是distortion_width乘以 distortion_height乘以4字節。

圖像射線校準

你可以用一下兩種方法校準圖像:
1使用Image.warp()和Image.rectify()函數。
2直接使用Image.distortion緩衝區裏的數據
warp()和rectify()函數是較簡單的方法但是在CPU上對圖像進行逐像素處理還是相對來說很慢的。如果你只是校正少量的點,或者你不需要實時處理數據或者你不能使用GPU的着色器時你可以使用這個函數。畸變緩衝區被設計成在高幀率的情況下使用GPU着色器同時可以矯正整張原始圖像。

使用Image.warp()校正

Image.warp()獲取光線方向返回原始圖像數據中的像素座標,轉換成指定記錄該光線方向上的亮度值。

使用32位ARGB紋理編碼畸變數據

如果每分量32位的紋理格式在你的平臺上不受支持,你可以在x軸和y軸使用一個分離的紋理查找值以及將浮點值編碼成多個8位顏色分量在使用他們查找原始亮度值之前你需要將這些值解碼。
對紋理中浮點型數據編碼的一個共同的方法是將輸入分解成4個低精度值然後再着色器中恢復。例如你能夠將浮點型數據編碼成擁有8比特分量的顏色數據:(注C++亂入中...)
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
  }
}

使用雙線性插值校正

在使用着色器不可行的情況下,你可能會使用優化雙線性插值來快速矯正圖像來代替使用warp()函數。(正如對待任何優化,你需要使用優化測試來驗證你的結果)

重新調用含有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()

在圖像上畫出追蹤數據

直接在相機圖像上畫出LeapMotion的追蹤數據十分簡單。如果你曾將原始圖像數據·繪製爲位圖,你可以通過使用warp()函數找到像素與LeapMotion位置的對應關係。
將LeapMotion座標系中一個位置轉換成水平和垂直斜度(以相機的視角),需要知道相機與LeapMotion座標系統的原點之間的距離。對於目前的版本,對於每一側來說x軸的偏移量是20mm。因爲相機在x軸,所以在z軸上沒有偏移量。斜度是在相機中的圖像平面的距離——x座標是水平斜度;z軸是垂直斜度。由到圖像平面的距離分割。下面的圖展示了水平斜度的關係。


左相機的計算過程。加上偏移距離代替了減去右相機的對應值。

只要你知道光線的斜率,你就能通過調用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上的硬件,這個方法是會被拒絕的,如那些嵌在筆記本上或者鍵盤上的設備。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章