1. 説明
2. 奥行き画像
深度画像 (深度マップとも呼ばれる) は、各ピクセルがセンサー座標系に対する相対的な距離値を提供する画像です。深度画像は、構造化光センサーまたは飛行時間センサーによってキャプチャできます。深度データを計算するために、Microsoft Kinect V1 などの構造化光センサーは、投影された光と受信された光の歪みを比較します。Kinect V2 Microsoft のような飛行時間型センサーは、光線を投射し、その光線を投射してから受信するまでの時間を計算します。
一部のセンサーは、深度画像に加えて、RGB-D 画像を形成するために対応する RGB 画像も提供します。後者により、色付きの点群を計算できるようになります。このチュートリアルでは、例として Microsoft Kinect V1 RGB-D イメージを使用します。
ライブラリをインポートすることから始めましょう。
import imageio.v3 as iio
import numpy as np
import matplotlib.pyplot as plt
import open3d as o3d
これで、深度画像をインポートし、その解像度を出力して次のように入力できます。
# Read depth image:
depth_image = iio.imread('data/depth.png')
# print properties:
print(f"Image resolution: {depth_image.shape}")
print(f"Data type: {depth_image.dtype}")
print(f"Min value: {np.min(depth_image)}")
print(f"Max value: {np.max(depth_image)}")
画像解像度: (480, 640)
データ型: int32
最小値: 0
最大値: 2980
深度画像はサイズ640 x 480の行列 で、各ピクセルは ミリメートル単位の距離を表す32 (または 16 ) ビットの整数であるため、深度画像を開くと黒く表示されます (下の画像を参照)。最小値 0 はノイズ (距離なし) を表し、最大値 2980は 最も遠いピクセルの距離を表します。
Microsoft Kinect V1 によって生成された深度画像。
より良く視覚化するために、グレースケール画像を計算します。
depth_instensity = np.array(256 * depth_image / 0x0fff, dtype=np.uint8)
iio.imwrite('output/grayscale.png', depth_instensity)
グレースケール イメージを計算するということは、深度値を にスケーリングすることを意味します。画像がより鮮明になりました。[0, 255]
計算されたグレースケール画像。黒いピクセルはノイズを表します。
Matplotlib は深度画像を視覚化するときにも同じことを行うことに注意してください。
# Display depth and grayscale image:
fig, axs = plt.subplots(1, 2)
axs[0].imshow(depth_image, cmap="gray")
axs[0].set_title('Depth image')
axs[1].imshow(depth_grayscale, cmap="gray")
axs[1].set_title('Depth grayscale image')
plt.show()
3. 点群
深度画像をインポートして表示したので、そこから点群を推定するにはどうすればよいでしょうか? 最初のステップは、深度カメラをキャリブレーションしてカメラ マトリックスを推定することです。このマトリックスは点群の計算に使用されます。取得された点群は、レーザー センサーなどの 3D センサーではなく 2D 投影 (深度画像) から推定されるため、2.5D 点群とも呼ばれます。
3.2 深度カメラのキャリブレーション
カメラのキャリブレーションとは、歪み係数とカメラ行列 (固有パラメータとも呼ばれます) を見つけて、レンズとセンサーのパラメータを推定することを意味します。一般に、カメラをキャリブレーションするには 3 つの方法があります。工場から提供される標準パラメータを使用する方法、キャリブレーション研究で得られた結果を使用する方法、または Kinect を手動でキャリブレーションする方法です。カメラを手動でキャリブレーションするには、チェッカーボード アルゴリズムなどのキャリブレーション アルゴリズムを適用する必要があります [1]。このアルゴリズムはロボット オペレーティング システム (ROS) と OpenCV に実装されています。キャリブレーション行列 M は 3× 3 行列です。
ここで、fx、fy、cx、cy はそれぞれ焦点距離と光学中心です。このチュートリアルでは、NYU Deep V2 データセットから取得した結果を使用します。
# Depth camera parameters:
FX_DEPTH = 5.8262448167737955e+02
FY_DEPTH = 5.8269103270988637e+02
CX_DEPTH = 3.1304475870804731e+02
CY_DEPTH = 2.3844389626620386e+02
カメラを自分で調整したい場合は、この OpenCV チュートリアルを参照してください。
3.3 点群コンピューティング
ここでの点群の計算とは、深度ピクセルを深度画像の 2D 座標系から深度カメラの 3D 座標系 ( x、y、z ) に変換することを指します。3D 座標は次の式 [2] を使用して計算されます。ここで、 Depth(i, j)は行i と列j の深さの値 です 。
この式は各ピクセルに適用されます。
# compute point cloud:
pcd = []
height, width = depth_image.shape
for i in range(height):
for j in range(width):
z = depth_image[i][j]
x = (j - CX_DEPTH) * z / FX_DEPTH
y = (i - CY_DEPTH) * z / FY_DEPTH
pcd.append([x, y, z])
Open3D ライブラリを使用して表示してみましょう。
pcd_o3d = o3d.geometry.PointCloud() # create point cloud object
pcd_o3d.points = o3d.utility.Vector3dVector(pcd) # set pcd_np as the point cloud points
# Visualize:
o3d.visualization.draw_geometries([pcd_o3d])
深度画像から計算された点群。
4. カラー点群
RGB-D イメージからカラー点群を計算したい場合はどうすればよいでしょうか? 色情報は、点群の登録などの多くのタスクのパフォーマンスを向上させることができます。この場合、RGB イメージも提供する入力センサーを使用するのが最適です。色付き点群は次のように定義できます。
ここで 、 x、y 、および z は 3D 座標であり、r、g 、および b は RGB システムの色を表します。
まず、前の深度画像に対応する RGB 画像をインポートします。
# Read the rgb image:
rgb_image = iio.imread('../data/rgb.jpg')
# Display depth and grayscale image:
fig, axs = plt.subplots(1, 2)
axs[0].imshow(depth_image, cmap="gray")
axs[0].set_title('Depth image')
axs[1].imshow(rgb_image)
axs[1].set_title('RGB image')
plt.show()
深度画像とそれに対応する RGB 画像
深度センサーの 3D 座標系で定義された 特定の点 p(x, y, z) の色を見つけるには、次のようにします。
1. これを RGB カメラ座標系 [2] に変換します。
ここで、 R と T は 2 台のカメラ間の外部パラメータ、つまりそれぞれ回転行列と平行移動ベクトルです。
ここでも、 NYU Deep V2 データセットのパラメーター を使用します。
# Rotation matrix:
R = -np.array([[9.9997798940829263e-01, 5.0518419386157446e-03, 4.3011152014118693e-03],
[-5.0359919480810989e-03, 9.9998051861143999e-01, -3.6879781309514218e-03],
[- 4.3196624923060242e-03, 3.6662365748484798e-03, 9.9998394948385538e-01]])
# Translation vector:
T = np.array([2.5031875059141302e-02, -2.9342312935846411e-04, 6.6238747008330102e-04])
RGB カメラ座標系の点は次のように計算されます。
"""
Convert the point from depth sensor 3D coordinate system
to rgb camera coordinate system:
"""
[x_RGB, y_RGB, z_RGB] = np.linalg.inv(R).dot([x, y, z]) - np.linalg.inv(R).dot(T)
2. RGB カメラの固有パラメータを使用して、それをカラー画像座標系 [2] にマッピングします。
これらは、カラー ピクセルを取得するためのインデックスです。
前の式では、焦点距離と光学中心が RGB カメラのパラメーターであることに注意してください。ここでも、 NYU Deep V2 データセットのパラメーターを使用します。
# RGB camera intrinsic Parameters:
FX_RGB = 5.1885790117450188e+02
FY_RGB = 5.1946961112127485e+02
CX_RGB = 3.2558244941119034e+0
CY_RGB = 2.5373616633400465e+02
対応するピクセルのインデックスは次のように計算されます。
"""
Convert from rgb camera coordinate system
to rgb image coordinate system:
"""
j_rgb = int((x_RGB * FX_RGB) / z_RGB + CX_RGB + width / 2)
i_rgb = int((y_RGB * FY_RGB) / z_RGB + CY_RGB)
すべてをまとめて点群を表示しましょう。
colors = []
pcd = []
for i in range(height):
for j in range(width):
"""
Convert the pixel from depth coordinate system
to depth sensor 3D coordinate system
"""
z = depth_image[i][j]
x = (j - CX_DEPTH) * z / FX_DEPTH
y = (i - CY_DEPTH) * z / FY_DEPTH
"""
Convert the point from depth sensor 3D coordinate system
to rgb camera coordinate system:
"""
[x_RGB, y_RGB, z_RGB] = np.linalg.inv(R).dot([x, y, z]) - np.linalg.inv(R).dot(T)
"""
Convert from rgb camera coordinates system
to rgb image coordinates system:
"""
j_rgb = int((x_RGB * FX_RGB) / z_RGB + CX_RGB + width / 2)
i_rgb = int((y_RGB * FY_RGB) / z_RGB + CY_RGB)
# Add point to point cloud:
pcd.append([x, y, z])
# Add the color of the pixel if it exists:
if 0 <= j_rgb < width and 0 <= i_rgb < height:
colors.append(rgb_image[i_rgb][j_rgb] / 255)
else:
colors.append([0., 0., 0.])
# Convert to Open3D.PointCLoud:
pcd_o3d = o3d.geometry.PointCloud() # create a point cloud object
pcd_o3d.points = o3d.utility.Vector3dVector(pcd)
pcd_o3d.colors = o3d.utility.Vector3dVector(colors)
# Visualize:
o3d.visualization.draw_geometries([pcd_o3d])
RGB-D 画像から計算された色付き点群
5. コードの最適化
このセクションでは、より効率的でリアルタイム アプリケーションに適したコードを最適化する方法について説明します。
5.1 点群
ネストされたループを使用して点群を計算するには、非常に時間がかかります。解像度480×640 の 深度画像 の場合、 8GB RAM と i7-4500 CPU を搭載したマシンで点群を計算するのに約2.154 秒かかります。
計算時間を短縮するために、ネストされたループをベクトル化された演算に置き換えることができ、計算時間を約0.024 秒に短縮できます。
# get depth resolution:
height, width = depth_im.shape
length = height * width
# compute indices:
jj = np.tile(range(width), height)
ii = np.repeat(range(height), width)
# rechape depth image
z = depth_im.reshape(length)
# compute pcd:
pcd = np.dstack([(ii - CX_DEPTH) * z / FX_DEPTH,
(jj - CY_DEPTH) * z / FY_DEPTH,
z]).reshape((length, 3))
また、最初に定数を 1 回計算することで、計算時間を約 0.015 秒に短縮することもできます。
# compute indices:
jj = np.tile(range(width), height)
ii = np.repeat(range(height), width)
# Compute constants:
xx = (jj - CX_DEPTH) / FX_DEPTH
yy = (ii - CY_DEPTH) / FY_DEPTH
# transform depth image to vector of z:
length = height * width
z = depth_image.reshape(height * width)
# compute point cloud
pcd = np.dstack((xx * z, yy * z, z)).reshape((length, 3))
4.2 カラー点群
色付きの点群については、同じマシン上で前の例を実行するのに約36.263 秒 かかります 。ベクトル化を適用すると、実行時間は 0.722 秒に短縮されます。
# compute indices:
jj = np.tile(range(width), height)
ii = np.repeat(range(height), width)
# Compute constants:
xx = (jj - CX_DEPTH) / FX_DEPTH
yy = (ii - CY_DEPTH) / FY_DEPTH
# transform depth image to vector of z:
length = height * width
z = depth_image.reshape(length)
# compute point cloud
pcd = np.dstack((xx * z, yy * z, z)).reshape((length, 3))
cam_RGB = np.apply_along_axis(np.linalg.inv(R).dot, 1, pcd) - np.linalg.inv(R).dot(T)
xx_rgb = ((cam_RGB[:, 0] * FX_RGB) / cam_RGB[:, 2] + CX_RGB + width / 2).astype(int).clip(0, width - 1)
yy_rgb = ((cam_RGB[:, 1] * FY_RGB) / cam_RGB[:, 2] + CY_RGB).astype(int).clip(0, height - 1)
colors = rgb_image[yy_rgb, xx_rgb]/255
6. 結論
このチュートリアルでは、RGB-D データから点群を計算する方法を学びました。次のチュートリアルでは、単純な地面検出の例を取り上げ、点群を注意深く分析します。
ありがとうございます。この記事を楽しんで読んでいただければ幸いです。私のGitHub リポジトリに例があります 。
引用
[1] Zhang, S.、および Huang, PS (2006). 構造化光システムのキャリブレーションのための新しい方法。光学工学、 45 (8)、083601。
[2] Zhou Xu、(2012). Microsoft Kinect キャリブレーションに関する研究。ジョージ メイソン大学、フェアファックスのコンピュータ サイエンス学部。