上一個教程: 使用 Kinect 和其他相容 OpenNI 的深度感測器
下一個教程: 使用奧比中光 3D 深度攝像頭 (UVC)
簡介
本教程專門介紹奧比中光 Astra 系列 3D 深度攝像頭 (https://www.orbbec.com/products/structured-light-camera/astra-series/)。這些攝像頭除了常見的彩色感測器外,還配備了深度感測器。深度感測器可以使用開源的 OpenNI API 透過 cv::VideoCapture 類進行讀取。影片流透過常規攝像頭介面提供。
安裝說明
為了在 OpenCV 中使用 Astra 攝像頭的深度感測器,您應該執行以下步驟:
- 下載最新版本的奧比中光 OpenNI SDK(從此處 https://www.orbbec.com/developers/openni-sdk/)。解壓存檔,根據您的作業系統選擇構建版本,並按照 Readme 檔案中提供的安裝步驟進行操作。
例如,如果您使用 64 位 GNU/Linux,請執行:
$ cd Linux/OpenNI-Linux-x64-2.3.0.63/
$ sudo ./install.sh
安裝完成後,請務必重新插拔您的裝置,以便 udev 規則生效。攝像頭現在應該可以作為通用攝像頭裝置工作。請注意,您當前的使用者應屬於 video 使用者組,才能訪問攝像頭。另外,請務必 source OpenNIDevEnvironment 檔案:
$ 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
如果上述兩個變數為空,則需要再次 source OpenNIDevEnvironment。
- 注意
- 奧比中光 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 在現代 Linux 上無法正常工作,即使按照說明重建 libusb 後也如此。已知可用的最新版本是 2.3.0.63(在 Ubuntu 18.04 amd64 上測試)。該版本並未在官方下載頁面提供,而是由奧比中光技術支援人員在奧比中光社群論壇 此處 釋出。
- 現在,您可以透過在 CMake 中設定
WITH_OPENNI2 標誌來配置啟用 OpenNI 支援的 OpenCV。您可能還希望啟用 BUILD_EXAMPLES 標誌,以獲取與 Astra 攝像頭配合使用的程式碼示例。在包含 OpenCV 原始碼的目錄中執行以下命令以啟用 OpenNI 支援:$ mkdir build
$ cd build
$ cmake -DWITH_OPENNI2=ON ..
如果找到 OpenNI 庫,OpenCV 將構建 OpenNI2 支援。您可以在 CMake 日誌中檢視 OpenNI2 支援的狀態:-- 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)
- 構建 OpenCV
程式碼
Astra Pro 攝像頭有兩個感測器——深度感測器和彩色感測器。深度感測器可以使用 OpenNI 介面透過 cv::VideoCapture 類讀取。影片流不透過 OpenNI API 提供,僅透過常規攝像頭介面提供。因此,要獲取深度和彩色幀,應建立兩個 cv::VideoCapture 物件:
第一個物件將使用 OpenNI2 API 獲取深度資料。第二個物件使用 Video4Linux2 介面訪問彩色感測器。請注意,上述示例假設 Astra 攝像頭是系統中的第一個攝像頭。如果您連線了多個攝像頭,可能需要明確設定正確的攝像頭編號。
在使用建立的 VideoCapture 物件之前,您可能希望透過設定物件的屬性來配置流引數。最重要的引數是幀寬、幀高和幀率。對於本示例,我們將兩個流的寬度和高度配置為 VGA 解析度(這是兩個感測器可用的最大解析度),並且我們希望兩個流引數相同,以便更輕鬆地進行彩色到深度資料註冊。
60
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::set 和 cv::VideoCapture::get 方法,例如:
74
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 介面可用的以下攝像頭屬性支援深度生成器:
VideoCapture 物件設定完成後,您可以開始從中讀取幀。
- 注意
- OpenCV 的 VideoCapture 提供同步 API,因此您必須在新執行緒中抓取幀,以避免在讀取另一個流時一個流被阻塞。VideoCapture 不是執行緒安全的類,因此您需要小心,以避免任何可能的死鎖或資料競爭。
由於有兩個影片源需要同時讀取,因此有必要建立兩個執行緒以避免阻塞。以下示例實現從新執行緒中的每個感測器獲取幀,並將其與時間戳一起儲存在列表中:
81
82 std::list<Frame> depthFrames, colorFrames;
83 const std::size_t maxFrames = 64;
84
85
86 std::mutex mtx;
87 std::condition_variable dataReady;
88 std::atomic<bool> isFinish;
89
90 isFinish = false;
91
92
93 std::thread depthReader([&]
94 {
95 while (!isFinish)
96 {
97
98 if (depthStream.grab())
99 {
100 Frame f;
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
121 std::thread colorReader([&]
122 {
123 while (!isFinish)
124 {
125
126 if (colorStream.grab())
127 {
128 Frame f;
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 可以檢索以下資料
- 深度生成器提供的資料:
- 彩色感測器提供的資料是常規的 BGR 影像 (CV_8UC3)。
當新資料可用時,每個讀取執行緒使用條件變數通知主執行緒。幀儲存在有序列表中——列表中的第一幀是捕獲最早的,最後一幀是捕獲最新的。由於深度和彩色幀是從獨立源讀取的,即使兩個流都設定為相同的幀率,它們也可能不同步。可以對流應用後同步過程,將深度和彩色幀組合成對。以下示例程式碼演示了此過程:
150
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
163 Frame depthFrame = depthFrames.front();
164 int64 depthT = depthFrame.timestamp;
165
166
167 Frame colorFrame = colorFrames.front();
168 int64 colorT = colorFrame.timestamp;
169
170
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
189 depthFrame.frame.convertTo(d8,
CV_8U, 255.0 / 2500);
191 imshow(
"Depth (colored)", dColor);
192
193
194 imshow(
"Color", colorFrame.frame);
196
197
199 if (key == 27)
200 {
201 isFinish = true;
202 break;
203 }
204 }
205 }
在上面的程式碼片段中,執行會一直阻塞,直到兩個幀列表都有幀。當有新幀可用時,會檢查它們的時間戳——如果它們相差超過半個幀週期,則丟棄其中一幀。如果時間戳足夠接近,則將兩幀配對。現在,我們有了兩幀:一幀包含彩色資訊,另一幀包含深度資訊。在上面的示例中,獲取的幀只是透過 cv::imshow 函式顯示,但您可以在此處插入任何其他處理程式碼。
在下面的示例影像中,您可以看到表示相同場景的彩色幀和深度幀。從彩色幀看,很難區分植物葉子和牆上畫的葉子,但深度資料使其變得容易。
完整的實現可以在 samples/cpp/tutorial_code/videoio 目錄下的 openni_orbbec_astra.cpp 檔案中找到。