OpenCV 4.13.0
Open Source Computer Vision
読み込み中...
検索中...
見つかりません
🤖 AIによる機械翻訳(非公式) — これは OpenCV 4.13.0 公式リファレンス(英語)を AI (Claude) で自動翻訳したものです。訳に誤りを含む場合があります。正確な情報は 公式英語版(原文) を参照してください。
Orbbec Astra 3Dカメラの利用

前のチュートリアル: Kinect やその他の OpenNI 互換深度センサーの利用
次のチュートリアル: Orbbec 3D カメラ(UVC)の利用

はじめに

このチュートリアルは Orbbec 3D カメラの Astra シリーズ(https://www.orbbec.com/products/structured-light-camera/astra-series/)を扱う。これらのカメラは通常のカラーセンサーに加えて深度センサーを備えている。深度センサーはオープンソースの OpenNI API を用いて cv::VideoCapture クラスで読み取ることができる。ビデオストリームは通常のカメラインターフェイスを通じて提供される。

インストール手順

OpenCV で Astra カメラの深度センサーを使用するには、次の手順を実行する必要がある:

  1. Orbbec OpenNI SDK の最新版をダウンロードする(こちらから https://www.orbbec.com/developers/openni-sdk/)。アーカイブを解凍し、使用しているオペレーティングシステムに応じたビルドを選択し、Readme ファイルに記載されたインストール手順に従う。
  2. たとえば 64bit の GNU/Linux を使用している場合は次を実行する:

    $ cd Linux/OpenNI-Linux-x64-2.3.0.63/
    $ sudo ./install.sh

    インストールが完了したら、udev ルールを有効にするためにデバイスを必ず接続し直すこと。これでカメラは一般的なカメラデバイスとして動作するはずである。カメラにアクセスするには、現在のユーザーが video グループに属している必要があることに注意する。また、OpenNIDevEnvironment ファイルを必ず source すること:

    $ source OpenNIDevEnvironment

    source コマンドが機能し、OpenNI ライブラリとヘッダファイルが見つかることを確認するには、次のコマンドを実行する。ターミナルに次のような出力が表示されるはずである:

    $ echo $OPENNI2_INCLUDE
    /home/user/OpenNI_2.3.0.63/Linux/OpenNI-Linux-x64-2.3.0.63/Include
    $ echo $OPENNI2_REDIST
    /home/user/OpenNI_2.3.0.63/Linux/OpenNI-Linux-x64-2.3.0.63/Redist

    上記の 2 つの変数が空の場合は、OpenNIDevEnvironment を再度 source する必要がある。

    覚え書き
    Orbbec OpenNI SDK バージョン 2.3.0.86 以降では install.sh は提供されなくなった。環境を初期化するには次のスクリプトを使用できる:
    # Check if user is root/running with sudo
    if [ `whoami` != root ]; then
    echo Please run this script with sudo
    exit
    fi
    ORIG_PATH=`pwd`
    cd `dirname $0`
    SCRIPT_PATH=`pwd`
    cd $ORIG_PATH
    if [ "`uname -s`" != "Darwin" ]; then
    # Install UDEV rules for USB device
    cp ${SCRIPT_PATH}/orbbec-usb.rules /etc/udev/rules.d/558-orbbec-usb.rules
    echo "usb rules file install at /etc/udev/rules.d/558-orbbec-usb.rules"
    fi
    OUT_FILE="$SCRIPT_PATH/OpenNIDevEnvironment"
    echo "export OPENNI2_INCLUDE=$SCRIPT_PATH/../sdk/Include" > $OUT_FILE
    echo "export OPENNI2_REDIST=$SCRIPT_PATH/../sdk/libs" >> $OUT_FILE
    chmod a+r $OUT_FILE
    echo "exit"
    最後に試したバージョン 2.3.0.86_202210111154_4c8f5aa4_beta6 は、手順の推奨どおり libusb を再ビルドしても、最近の Linux では正しく動作しない。最後に判明している正常動作する構成はバージョン 2.3.0.63 である(Ubuntu 18.04 amd64 でテスト済み)。これはダウンロードページでは公式には提供されていないが、Orbbec のテクニカルサポートが Orbbec コミュニティフォーラムの こちらで公開している。
  3. これで、CMake で WITH_OPENNI2 フラグを設定することで、OpenNI サポートを有効にして OpenCV を構成できる。Astra カメラで動作するコードサンプルを取得するために BUILD_EXAMPLES フラグも有効にするとよい。OpenNI サポートを有効にするには、OpenCV のソースコードを含むディレクトリで次のコマンドを実行する:
    $ mkdir build
    $ cd build
    $ cmake -DWITH_OPENNI2=ON ..
    OpenNI ライブラリが見つかると、OpenCV は OpenNI2 サポート付きでビルドされる。OpenNI2 サポートの状態は CMake のログで確認できる:
    -- Video I/O:
    -- DC1394: YES (2.2.6)
    -- FFMPEG: YES
    -- avcodec: YES (58.91.100)
    -- avformat: YES (58.45.100)
    -- avutil: YES (56.51.100)
    -- swscale: YES (5.7.100)
    -- avresample: NO
    -- GStreamer: YES (1.18.1)
    -- OpenNI2: YES (2.3.0)
    -- v4l/v4l2: YES (linux/videodev2.h)
  4. OpenCV をビルドする:
    $ make

コード

Astra Pro カメラには 2 つのセンサー(深度センサーとカラーセンサー)がある。深度センサーは OpenNI インターフェイスを用いて cv::VideoCapture クラスで読み取ることができる。ビデオストリームは OpenNI API からは利用できず、通常のカメラインターフェイスを通じてのみ提供される。したがって、深度フレームとカラーフレームの両方を取得するには、2 つの cv::VideoCapture オブジェクトを作成する必要がある:

39 // Open depth stream
40 VideoCapture depthStream(CAP_OPENNI2_ASTRA);
41 // Open color stream
42 VideoCapture colorStream(0, CAP_V4L2);

1 つ目のオブジェクトは OpenNI2 API を使って深度データを取得する。2 つ目は Video4Linux2 インターフェイスを使ってカラーセンサーにアクセスする。上記の例では Astra カメラがシステム内の最初のカメラであることを前提としている点に注意する。複数のカメラが接続されている場合は、適切なカメラ番号を明示的に設定する必要があるかもしれない。

作成した VideoCapture オブジェクトを使用する前に、オブジェクトのプロパティを設定してストリームパラメータを設定するとよい。最も重要なパラメータはフレーム幅、フレーム高さ、fps である。この例では、両方のストリームの幅と高さを、両センサーで利用可能な最大解像度である VGA 解像度に設定し、カラーから深度へのデータレジストレーションを容易にするために両ストリームのパラメータを同じにする:

60 // Set color and depth stream parameters
61 colorStream.set(CAP_PROP_FRAME_WIDTH, 640);
62 colorStream.set(CAP_PROP_FRAME_HEIGHT, 480);
63 depthStream.set(CAP_PROP_FRAME_WIDTH, 640);
64 depthStream.set(CAP_PROP_FRAME_HEIGHT, 480);
65 depthStream.set(CAP_PROP_OPENNI2_MIRROR, 0);

センサーデータジェネレータのプロパティを設定および取得するには、それぞれ cv::VideoCapture::setcv::VideoCapture::get メソッドを使用する。例:

74 // Print depth stream parameters
75 cout << "Depth stream: "
76 << depthStream.get(CAP_PROP_FRAME_WIDTH) << "x" << depthStream.get(CAP_PROP_FRAME_HEIGHT)
77 << " @" << depthStream.get(CAP_PROP_FPS) << " fps" << endl;

OpenNI インターフェイスを通じて利用可能なカメラの次のプロパティが、深度ジェネレータでサポートされている:

  • cv::CAP_PROP_FRAME_WIDTH – フレーム幅(ピクセル単位)。
  • cv::CAP_PROP_FRAME_HEIGHT – フレーム高さ(ピクセル単位)。
  • cv::CAP_PROP_FPS – フレームレート(FPS 単位)。
  • cv::CAP_PROP_OPENNI_REGISTRATION – 深度ジェネレータの視点を変更することで深度マップを画像マップに再マッピングするレジストレーションを行う(フラグが「on」の場合)か、その視点を通常の視点に設定する(フラグが「off」の場合)かを指定するフラグ。レジストレーション処理の結果として得られる画像はピクセル単位で位置合わせされており、画像内のすべてのピクセルが深度画像内のピクセルに対応していることを意味する。
  • cv::CAP_PROP_OPENNI2_MIRROR – このストリームのミラーリングを有効または無効にするフラグ。ミラーリングを無効にするには 0 に設定する

    次のプロパティは取得のみ可能である:

  • cv::CAP_PROP_OPENNI_FRAME_MAX_DEPTH – カメラがサポートする最大深度(mm 単位)。
  • cv::CAP_PROP_OPENNI_BASELINE – 基線長(mm 単位)。

VideoCapture オブジェクトの設定が完了したら、それらからフレームの読み取りを開始できる。

覚え書き
OpenCV の VideoCapture は同期 API を提供するため、一方のストリームが読み取られている間にもう一方のストリームがブロックされるのを避けるには、新しいスレッドでフレームを grab する必要がある。VideoCapture はスレッドセーフなクラスではないため、デッドロックやデータ競合が起こらないよう注意する必要がある。

同時に読み取るべきビデオソースが 2 つあるため、ブロッキングを避けるには 2 つのスレッドを作成する必要がある。各センサーから新しいスレッドでフレームを取得し、タイムスタンプとともにリストに格納する実装例:

81 // Create two lists to store frames
82 std::list<Frame> depthFrames, colorFrames;
83 const std::size_t maxFrames = 64;
84
85 // Synchronization objects
86 std::mutex mtx;
87 std::condition_variable dataReady;
88 std::atomic<bool> isFinish;
89
90 isFinish = false;
91
92 // Start depth reading thread
93 std::thread depthReader([&]
94 {
95 while (!isFinish)
96 {
97 // Grab and decode new frame
98 if (depthStream.grab())
99 {
100 Frame f;
101 f.timestamp = cv::getTickCount();
102 depthStream.retrieve(f.frame, CAP_OPENNI_DEPTH_MAP);
103 if (f.frame.empty())
104 {
105 cerr << "ERROR: Failed to decode frame from depth stream" << endl;
106 break;
107 }
108
109 {
110 std::lock_guard<std::mutex> lk(mtx);
111 if (depthFrames.size() >= maxFrames)
112 depthFrames.pop_front();
113 depthFrames.push_back(f);
114 }
115 dataReady.notify_one();
116 }
117 }
118 });
119
120 // Start color reading thread
121 std::thread colorReader([&]
122 {
123 while (!isFinish)
124 {
125 // Grab and decode new frame
126 if (colorStream.grab())
127 {
128 Frame f;
129 f.timestamp = cv::getTickCount();
130 colorStream.retrieve(f.frame);
131 if (f.frame.empty())
132 {
133 cerr << "ERROR: Failed to decode frame from color stream" << endl;
134 break;
135 }
136
137 {
138 std::lock_guard<std::mutex> lk(mtx);
139 if (colorFrames.size() >= maxFrames)
140 colorFrames.pop_front();
141 colorFrames.push_back(f);
142 }
143 dataReady.notify_one();
144 }
145 }
146 });

VideoCapture は次のデータを取得できる:

  1. data given from the depth generator:
  2. カラーセンサーから得られるデータは通常の BGR 画像(CV_8UC3)である。

新しいデータが利用可能になると、各読み取りスレッドは条件変数を使ってメインスレッドに通知する。フレームは順序付きリストに格納される。リストの最初のフレームが最も早く取得されたもの、最後のフレームが最も新しく取得されたものである。深度フレームとカラーフレームは独立したソースから読み取られるため、両ストリームが同じフレームレートに設定されていても、2 つのビデオストリームは同期がずれることがある。深度フレームとカラーフレームをペアに組み合わせるために、ストリームに対して後処理としての同期手順を適用できる。以下のサンプルコードはこの手順を示している:

150 // Pair depth and color frames
151 while (!isFinish)
152 {
153 std::unique_lock<std::mutex> lk(mtx);
154 while (!isFinish && (depthFrames.empty() || colorFrames.empty()))
155 dataReady.wait(lk);
156
157 while (!depthFrames.empty() && !colorFrames.empty())
158 {
159 if (!lk.owns_lock())
160 lk.lock();
161
162 // Get a frame from the list
163 Frame depthFrame = depthFrames.front();
164 int64 depthT = depthFrame.timestamp;
165
166 // Get a frame from the list
167 Frame colorFrame = colorFrames.front();
168 int64 colorT = colorFrame.timestamp;
169
170 // Half of frame period is a maximum time diff between frames
171 const int64 maxTdiff = int64(1000000000 / (2 * colorStream.get(CAP_PROP_FPS)));
172 if (depthT + maxTdiff < colorT)
173 {
174 depthFrames.pop_front();
175 continue;
176 }
177 else if (colorT + maxTdiff < depthT)
178 {
179 colorFrames.pop_front();
180 continue;
181 }
182 depthFrames.pop_front();
183 colorFrames.pop_front();
184 lk.unlock();
185
187 // Show depth frame
188 Mat d8, dColor;
189 depthFrame.frame.convertTo(d8, CV_8U, 255.0 / 2500);
190 applyColorMap(d8, dColor, COLORMAP_OCEAN);
191 imshow("Depth (colored)", dColor);
192
193 // Show color frame
194 imshow("Color", colorFrame.frame);
196
197 // Exit on Esc key press
198 int key = waitKey(1);
199 if (key == 27) // ESC
200 {
201 isFinish = true;
202 break;
203 }
204 }
205 }

上記のコードスニペットでは、両方のフレームリストにフレームが存在するまで実行がブロックされる。新しいフレームがある場合、それらのタイムスタンプが確認される。フレーム周期の半分以上の差がある場合は、いずれか一方のフレームが破棄される。タイムスタンプが十分に近い場合は、2 つのフレームがペアにされる。これで、カラー情報を含むフレームと深度情報を含むフレームの 2 つが得られる。上記の例では取得したフレームを単に cv::imshow 関数で表示しているが、ここに任意の他の処理コードを挿入できる。

以下のサンプル画像では、同じシーンを表すカラーフレームと深度フレームを確認できる。カラーフレームを見ると、植物の葉と壁に描かれた葉を区別するのは難しいが、深度データを使えば容易になる。

完全な実装は samples/cpp/tutorial_code/videoio ディレクトリ内の openni_orbbec_astra.cpp にある。