OAK-D-Lite と DepthAI を用いてバーチャル背景合成プログラムを自作してみようという企画の2回目です。 ステップ2「視差データを取得して表示する」 の実装をやります。

ゴールと段取り (おさらい)

ステップ2「視差データを取得して表示する」の実装

やることは以下2つです。

  1. パイプラインを修正して視差データを取得できるようにする
  2. 取得した視差データを画面上に表示する

1. パイプラインの修正

視差データは StereoDepth ノードから取得できます。StereoDepth ノードには左右のモノクロカメラノード (MonoCamera) の出力を与えます。

前回作成したカラーカメラ用のパイプラインにこれらのノードを加えるとこんな感じになりそうです:

パイプラインのイメージ

以下のページを参考にしつつ、これをコードにしてみます。

def create_pipeline():
    pipeline = dai.Pipeline()

    # カラーカメラを作成
    (...)

    # モノクロカメラ(左)を作成
    left = pipeline.createMonoCamera()
    left.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
    left.setBoardSocket(dai.CameraBoardSocket.LEFT)

    # モノクロカメラ(右)を作成
    right = pipeline.createMonoCamera()
    right.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
    right.setBoardSocket(dai.CameraBoardSocket.RIGHT)

    # ステレオ深度ノードを作成
    stereo = pipeline.createStereoDepth()
    stereo.setDepthAlign(dai.CameraBoardSocket.RGB)

    # ステレオ深度ノードに左右カメラを接続
    left.out.link(stereo.left)
    right.out.link(stereo.right)

    # 視差データ出力用ノードを作成
    xout_disp = pipeline.createXLinkOut()
    xout_disp.setStreamName("disparity")

    # 視差データ出力用ノードにステレオ深度ノードを接続
    stereo.disparity.link(xout_disp.input)

    return pipeline

最終的に 360*360 に加工するので高解像度にする必要はないため、モノクロカメラの解像度は 400p に設定しています。

参考ページ

2. 取得した視差データを画面に表示する

パイプラインに新たな出力ノードを加えたので、ここからデータを取り出すためのキューを作ります。

    q_disp = device.getOutputQueue(name="disparity", maxSize=1, blocking=False)

キューから取り出したデータは以下のようにすれば画面表示できます。

    # 視差データを取得してリサイズする
    disp_frame = resize(q_disp.get().getFrame())

    # 視差データフレームを画面に表示する
    cv2.imshow("Disp", disp_frame)

この状態でプログラムを起動すると、カメラに近いところは白い(明るい)、カメラから遠いところは黒い(暗い)画像が表示されます。 getFrame で取得できる視差データは、画像のピクセル毎に「近くは大きい値」「遠くは小さい値」となっているためです。

ただし、このままだとどんなにカメラに近づいても真っ白な部分はできず少し全体的に暗い印象になります。 これは、デフォルトの設定(標準モード)では、視差データは0~95の値をとるためです。 この値を 0~255 にすれば、カメラに最も近い部分は白 (255) となり、よりメリハリの効いた(遠近がわかりやすい)画像になります。

先ほどのプログラムに以下の処理を加えます。

# 視差データの各ピクセルの値を 0 ~ 255 に変換する
def to_grayscale(frame):
    multiplier = 255 / 95
    frame = (frame * multiplier).astype(np.uint8)
    return frame

(...)

    disp_frame = resize(q_disp.get().getFrame())

    # 視差データの各ピクセルの値を 0 ~ 255 に変換する
    disp_frame = to_grayscale(disp_frame)

    cv2.imshow("Disp", disp_frame)

実際に動作させるとこんな感じになります:

画面表示

コード全体

import cv2
import depthai as dai
import numpy as np

# パイプラインを作成する
def create_pipeline():
    pipeline = dai.Pipeline()

    # カラーカメラを作成
    cam = pipeline.createColorCamera()
    isp_xout = pipeline.createXLinkOut()
    isp_xout.setStreamName("cam")
    cam.isp.link(isp_xout.input)

    # モノクロカメラ(左)を作成
    left = pipeline.createMonoCamera()
    left.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
    left.setBoardSocket(dai.CameraBoardSocket.LEFT)

    # モノクロカメラ(右)を作成
    right = pipeline.createMonoCamera()
    right.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
    right.setBoardSocket(dai.CameraBoardSocket.RIGHT)

    # ステレオ深度ノードを作成
    stereo = pipeline.createStereoDepth()
    stereo.setDepthAlign(dai.CameraBoardSocket.RGB)

    # ステレオ深度ノードに左右カメラを接続
    left.out.link(stereo.left)
    right.out.link(stereo.right)

    # 視差データ出力用ノードを作成
    xout_disp = pipeline.createXLinkOut()
    xout_disp.setStreamName("disparity")

    # 視差データ出力用ノードにステレオ深度ノードを接続
    stereo.disparity.link(xout_disp.input)

    return pipeline

# フレームを 360*360 に縮小する
def resize(frame):
    h = frame.shape[0]  # 高さ
    w  = frame.shape[1] # 幅
    d = int((w-h) / 2)
    return cv2.resize(frame[0:h, d:w-d], (360, 360))

# 視差データの各ピクセルの値を 0 ~ 255 に変換する
def to_grayscale(frame):
    multiplier = 255 / 95 # ステレオ深度ノード標準モード時視差データの最大値は95
    frame = (frame * multiplier).astype(np.uint8)
    return frame

with dai.Device() as device:
    pipeline = create_pipeline()
    device.startPipeline(pipeline)

    q_color = device.getOutputQueue(name="cam", maxSize=1, blocking=False)

    # 視差データ出力キューを取得
    q_disp = device.getOutputQueue(name="disparity", maxSize=1, blocking=False)

    while True:
        # カラーカメラからフレーム取得する
        frame = resize(q_color.get().getCvFrame())

        # カラーフレームを画面に表示する
        cv2.imshow("Preview", frame)

        # 視差データを取得してリサイズする
        disp_frame = resize(q_disp.get().getFrame())

        # 視差データの各ピクセルの値を 0 ~ 255 に変換する
        disp_frame = to_grayscale(disp_frame)

        # 視差データフレームを画面に表示する
        cv2.imshow("Disp", disp_frame)

        # 画面で 'q' が入力されたら終了する
        key = cv2.waitKey(1)
        if key == ord('q'):
            break

次回

次回は ステップ3: カラー画像に対して視差データでマスクをかける をやってみます。

かわかみしんいち。島根県津和野町在住のフリーランスエンジニア。複合現実(Mixed Reality)と3DUXでおもちゃを作るのが趣味。 https://github.com/ototadana