2 * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "mv_private.h"
18 #include <mv_common.h>
19 #include <MediaSource.h>
22 #include <opencv2/core.hpp>
23 #include <opencv2/imgcodecs.hpp>
24 #include <opencv2/imgproc.hpp>
26 #ifdef MV_3D_POINTCLOUD_IS_AVAILABLE
27 #include <open3d/Open3D.h>
28 using namespace open3d;
32 #define MV_3D_TIMEOUT_SEC 3
37 static void __destroyNotifyCb(gpointer data)
42 LOGW("Cleanup remained DfsInputData (%p)", data);
43 auto remained = static_cast<DfsInputData *>(data);
45 delete[] static_cast<unsigned char *>(remained->data);
46 delete[] static_cast<unsigned char *>(remained->extraData);
53 , mDfsAdaptor(nullptr)
54 , mMode(MV_3D_DEPTH_MODE_NONE)
60 , mOutlierRemovalPoints(0)
61 , mOutlierRemovalRadius(0.0)
63 , mDepthUserData(nullptr)
64 , mPointcloudUserData(nullptr)
65 , mDepthCallback(nullptr)
66 , mPointcloudCallback(nullptr)
68 , mDfsAsyncQueue(nullptr)
69 , mInternalSource(nullptr)
72 #ifdef MV_3D_POINTCLOUD_IS_AVAILABLE
73 utility::SetVerbosityLevel(utility::VerbosityLevel::Debug);
84 g_thread_join(mDfsThread);
88 g_async_queue_unref(mDfsAsyncQueue);
92 mDfsAdaptor->unBind();
96 if (mInternalSource) {
97 mv_destroy_source(mInternalSource);
98 mInternalSource = nullptr;
104 void Mv3d::SetParameters(double threshold, size_t windowWidth, size_t windowHeight, size_t speckleSize)
106 mDfsParameter.textureThreshold = threshold;
107 mDfsParameter.aggregationWindowWidth = windowWidth;
108 mDfsParameter.aggregationWindowHeight = windowHeight;
109 mDfsParameter.maxSpeckleSize = speckleSize;
112 int Mv3d::Configure(int mode, unsigned int width, unsigned int height, int minDisp, int maxDisp, double samplingRatio,
113 int outlierRemovalPoints, double outlierRemovalRadius, std::string stereoConfigPath,
114 std::string pointcloudOutputPath)
117 mWidth = static_cast<size_t>(width);
118 mHeight = static_cast<size_t>(height);
121 mSamplingRatio = samplingRatio;
122 mOutlierRemovalPoints = outlierRemovalPoints;
123 mOutlierRemovalRadius = outlierRemovalRadius;
124 mStereoConfigPath = stereoConfigPath;
125 size_t found = stereoConfigPath.rfind(".");
126 mIntrinsicPath = stereoConfigPath.substr(0, found) + std::string(".json");
127 mPointcloudOutputPath = pointcloudOutputPath;
131 mDfsAdaptor = new DfsAdaptor();
133 } catch (const std::bad_alloc &e) {
134 LOGE("Failed to create dfs adaptation : %s", e.what());
135 return MEDIA_VISION_ERROR_OUT_OF_MEMORY;
136 } catch (const std::runtime_error &e) {
137 LOGE("Failed to bind %s adaptor", e.what());
138 return MEDIA_VISION_ERROR_INVALID_OPERATION;
142 return MEDIA_VISION_ERROR_NONE;
145 void Mv3d::SetDepthCallback(mv_3d_depth_cb depthCallback, void *depthUserData)
147 mDepthCallback = depthCallback;
148 mDepthUserData = depthUserData;
151 #ifdef MV_3D_POINTCLOUD_IS_AVAILABLE
152 int Mv3d::SetPointcloudCallback(mv_3d_pointcloud_cb pointcloudCallback, void *pointcloudUserData)
154 if (pointcloudCallback == NULL || pointcloudUserData == NULL)
155 return MEDIA_VISION_ERROR_INVALID_PARAMETER;
157 mPointcloudCallback = pointcloudCallback;
158 mPointcloudUserData = pointcloudUserData;
160 return MEDIA_VISION_ERROR_NONE;
163 int Mv3d::SetPointcloudCallback(mv_3d_pointcloud_cb pointcloudCallback, void *pointcloudUserData)
165 return MEDIA_VISION_ERROR_NOT_SUPPORTED;
171 if (mMode == MV_3D_DEPTH_MODE_NONE) {
172 LOGE("Invalid Operation. Set proper MV_3D_DEPTH_MODE");
173 return MEDIA_VISION_ERROR_INVALID_OPERATION;
177 LOGE("Invalid Operation. Do Configure first.");
178 return MEDIA_VISION_ERROR_INVALID_OPERATION;
182 mDfsAdaptor->initialize(mDfsParameter, mWidth, mHeight, mMinDisp, mMaxDisp, mStereoConfigPath);
183 } catch (const std::exception &e) {
184 LOGE("Failed to initialize");
185 return MEDIA_VISION_ERROR_INVALID_OPERATION;
188 return MEDIA_VISION_ERROR_NONE;
192 * Get buffer data from mv_source.
193 * For now it only support no-stride image, or stride with nv12 format
194 * TODO: zero-copy, multi-planar image handle
196 void Mv3d::GetBufferFromSource(mv_source_h source, unsigned char *&buffer, unsigned int &width, unsigned int &height,
197 int &type, size_t &stride)
199 MediaVision::Common::MediaSource *mediaSource = static_cast<MediaVision::Common::MediaSource *>(source);
201 auto *_buffer = mediaSource->getBuffer();
202 auto _bufferSize = mediaSource->getBufferSize();
203 width = mediaSource->getWidth();
204 height = mediaSource->getHeight();
205 stride = mediaSource->getWidthStride();
207 switch (mediaSource->getColorspace()) {
208 case MEDIA_VISION_COLORSPACE_RGB888:
209 type = DFS_DATA_TYPE_UINT8C3;
211 case MEDIA_VISION_COLORSPACE_I420:
212 case MEDIA_VISION_COLORSPACE_NV12:
213 case MEDIA_VISION_COLORSPACE_NV21:
214 case MEDIA_VISION_COLORSPACE_Y800:
215 type = DFS_DATA_TYPE_UINT8C1;
218 default: //NOT tested
219 throw std::runtime_error("invalid color space");
222 if (width != stride && type == DFS_DATA_TYPE_UINT8C1) {
223 //copy only Y channel
224 _bufferSize = width * height;
225 buffer = new unsigned char[_bufferSize];
226 for (unsigned int i = 0; i < height; i++) {
227 memcpy(buffer + i * width, _buffer + i * stride, width);
230 buffer = new unsigned char[_bufferSize];
231 memcpy(buffer, _buffer, _bufferSize);
235 void Mv3d::GetDfsDataFromSources(mv_source_h baseSource, mv_source_h extraSource, DfsInputData &input)
237 unsigned char *baseBuffer = nullptr;
238 unsigned char *extraBuffer = nullptr;
239 unsigned int width = 0;
240 unsigned int height = 0;
244 GetBufferFromSource(baseSource, baseBuffer, width, height, type, stride);
245 input.data = static_cast<void *>(baseBuffer);
248 input.height = height;
249 input.stride = stride;
250 input.format = (mWidth == width && mHeight * 2 == height) ? DFS_DATA_INPUT_FORMAT_COUPLED_TB :
251 DFS_DATA_INPUT_FORMAT_COUPLED_SBS;
254 extraBuffer = nullptr;
255 GetBufferFromSource(extraSource, extraBuffer, width, height, type, stride);
257 if (input.type != type || input.width != width || input.height != height || input.stride != stride) {
258 throw std::runtime_error("left and right image's properties are different");
261 input.extraData = static_cast<void *>(extraBuffer);
262 input.format = DFS_DATA_INPUT_FORMAT_DECOUPLED_SBS;
266 #ifdef MV_3D_POINTCLOUD_IS_AVAILABLE
267 PointCloudPtr Mv3d::GetPointcloudFromSource(DfsInputData &input, DfsOutputData &depthData)
269 camera::PinholeCameraIntrinsic intrinsic;
270 io::ReadIJsonConvertible(mIntrinsicPath, intrinsic);
272 double depth_scale = 1000.0, depth_trunc = 200.0;
273 if (input.type == DFS_DATA_TYPE_UINT8C1) {
274 img = cv::Mat(cv::Size(input.width, input.height), CV_8UC1, input.data);
275 cv::cvtColor(img, img, cv::COLOR_GRAY2RGB);
276 } else if (input.type == DFS_DATA_TYPE_UINT8C3) {
277 img = cv::Mat(cv::Size(input.width, input.height), CV_8UC3, input.data);
278 cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
281 geometry::Image img_color, img_depth;
282 img_color.Prepare(input.width, input.height, 3, 1);
283 uint8_t *pImg_color_data = img_color.data_.data();
284 memcpy(pImg_color_data, img.data, input.width * input.height * 3 * sizeof(unsigned char));
286 img_depth.Prepare(input.width, input.height, 1, 2);
287 uint16_t *pImg_depth_data = (unsigned short *) img_depth.data_.data();
288 memcpy(pImg_depth_data, static_cast<DepthTypePtr>(depthData.data), depthData.width * depthData.height * 2);
290 std::shared_ptr<geometry::RGBDImage> rgbd_image =
291 geometry::RGBDImage::CreateFromColorAndDepth(img_color, img_depth, depth_scale, depth_trunc, false);
293 auto pcd = geometry::PointCloud::CreateFromRGBDImage(*rgbd_image, intrinsic);
294 if (mSamplingRatio < 1.0) {
295 utility::LogInfo("Downsampling... {}", mSamplingRatio);
296 pcd = pcd->RandomDownSample(mSamplingRatio);
299 if (mOutlierRemovalPoints > 0 && mOutlierRemovalRadius > 0.0) {
300 utility::LogInfo("RemoveRadiusOutliers... {} {}", mOutlierRemovalPoints, mOutlierRemovalRadius);
301 std::vector<size_t> pt_map;
302 std::tie(pcd, pt_map) = pcd->RemoveRadiusOutliers(mOutlierRemovalPoints, mOutlierRemovalRadius, true);
309 int Mv3d::Run(mv_source_h baseSource, mv_source_h extraSource)
315 g_thread_join(mDfsThread);
316 mDfsThread = nullptr;
319 if (mDfsAsyncQueue) {
320 g_async_queue_unref(mDfsAsyncQueue);
321 mDfsAsyncQueue = nullptr;
324 if (mInternalSource) {
325 int ret = mv_destroy_source(mInternalSource);
326 if (ret != MEDIA_VISION_ERROR_NONE) {
327 LOGE("Fail to destroy intern source. But keep going..");
329 mInternalSource = nullptr;
332 GetDfsDataFromSources(baseSource, extraSource, input);
334 mDfsAdaptor->run(input);
335 auto depthData = mDfsAdaptor->getOutputData();
337 mDepthCallback(baseSource, static_cast<DepthTypePtr>(depthData.data), depthData.width, depthData.height,
339 #ifdef MV_3D_POINTCLOUD_IS_AVAILABLE
340 if (mPointcloudCallback) {
341 mv_3d_pointcloud_s p { .type = MV_3D_POINTCLOUD_TYPE_PCD_BIN,
342 .pointcloud = GetPointcloudFromSource(input, depthData) };
344 mPointcloudCallback(baseSource, static_cast<mv_3d_pointcloud_h>(&p), mPointcloudUserData);
347 delete[] static_cast<unsigned char *>(input.data);
348 delete[] static_cast<unsigned char *>(input.extraData);
349 } catch (const std::exception &e) {
350 LOGE("Failed to Run with %s", e.what());
351 return MEDIA_VISION_ERROR_INVALID_OPERATION;
354 return MEDIA_VISION_ERROR_NONE;
357 int Mv3d::RunAsync(mv_source_h baseSource, mv_source_h extraSource)
360 if (!mDfsAsyncQueue) {
361 mDfsAsyncQueue = g_async_queue_new_full(__destroyNotifyCb);
362 if (!mDfsAsyncQueue) {
363 LOGE("Fail to g_async_queue_new()");
364 return MEDIA_VISION_ERROR_INTERNAL;
369 mDfsThread = g_thread_new("depth_thread", &Mv3d::DfsThreadLoop, static_cast<gpointer>(this));
372 g_async_queue_unref(mDfsAsyncQueue);
373 mDfsAsyncQueue = nullptr;
374 LOGE("Fail to g_thread_new()");
375 return MEDIA_VISION_ERROR_INTERNAL;
380 auto input = std::make_unique<DfsInputData>();
381 GetDfsDataFromSources(baseSource, extraSource, *input);
382 if (!mInternalSource) {
383 int ret = mv_create_source(&mInternalSource);
384 if (ret != MEDIA_VISION_ERROR_NONE) {
385 LOGE("Fail to create internal source");
386 return MEDIA_VISION_ERROR_INTERNAL;
389 g_async_queue_push(mDfsAsyncQueue, static_cast<gpointer>(input.release()));
390 } catch (const std::exception &e) {
391 LOGE("Failed to Run with %s", e.what());
392 return MEDIA_VISION_ERROR_INVALID_OPERATION;
395 return MEDIA_VISION_ERROR_NONE;
398 gpointer Mv3d::DfsThreadLoop(gpointer data)
400 auto handle = static_cast<Mv3d *>(data);
401 while (handle->mDfsIsLive) {
402 auto base = g_async_queue_timeout_pop(handle->mDfsAsyncQueue, MV_3D_TIMEOUT_SEC * G_TIME_SPAN_SECOND);
404 LOGW("Timeout! no more items. Exit thread");
408 auto queueRemaining = g_async_queue_length(handle->mDfsAsyncQueue);
409 LOGI("%d remaining", queueRemaining);
411 while (queueRemaining > 0) {
412 auto itemToDelete = static_cast<DfsInputData *>(base);
413 delete[] static_cast<unsigned char *>(itemToDelete->data);
414 delete[] static_cast<unsigned char *>(itemToDelete->extraData);
417 base = g_async_queue_pop(handle->mDfsAsyncQueue);
419 LOGI("%d remaining", queueRemaining);
422 std::unique_ptr<DfsInputData> pInput(static_cast<DfsInputData *>(base));
423 handle->mDfsAdaptor->run(*pInput);
425 auto depthData = handle->mDfsAdaptor->getOutputData();
426 auto leftData = handle->mDfsAdaptor->getLeftData();
427 int ret = mv_source_fill_by_buffer(handle->mInternalSource, static_cast<unsigned char *>(leftData.data),
428 leftData.stride * leftData.height, leftData.width, leftData.height,
429 leftData.type == DFS_DATA_TYPE_UINT8C3 ? MEDIA_VISION_COLORSPACE_RGB888 :
430 MEDIA_VISION_COLORSPACE_Y800);
431 if (MEDIA_VISION_ERROR_NONE != ret) {
432 LOGW("Errors were occurred during source filling %i", ret);
435 handle->mDepthCallback(static_cast<mv_source_h>(handle->mInternalSource),
436 static_cast<DepthTypePtr>(depthData.data), depthData.width, depthData.height,
437 handle->mDepthUserData);
438 #ifdef MV_3D_POINTCLOUD_IS_AVAILABLE
439 if (handle->mPointcloudCallback) {
440 mv_3d_pointcloud_s p { .type = MV_3D_POINTCLOUD_TYPE_PCD_BIN,
441 .pointcloud = handle->GetPointcloudFromSource(*pInput, depthData) };
443 handle->mPointcloudCallback(handle->mInternalSource, static_cast<mv_3d_pointcloud_h>(&p),
444 handle->mPointcloudUserData);
447 delete[] static_cast<unsigned char *>(pInput->data);
448 delete[] static_cast<unsigned char *>(pInput->extraData);
449 mv_source_clear(handle->mInternalSource);
455 int Mv3d::WritePointcloudFile(mv_3d_pointcloud_h pointcloud, mv_3d_pointcloud_type_e type, const char *fileName)
457 #ifdef MV_3D_POINTCLOUD_IS_AVAILABLE
458 auto s = static_cast<mv_3d_pointcloud_s *>(pointcloud);
460 LOGE("Pointcloud data is NULL");
461 return MEDIA_VISION_ERROR_INVALID_PARAMETER;
464 if (access(mPointcloudOutputPath.c_str(), W_OK) != 0) {
465 if (errno == EACCES || errno == EPERM) {
466 utility::LogInfo("Fail to access path[%s]: Permission Denied", mPointcloudOutputPath.c_str());
467 return MEDIA_VISION_ERROR_PERMISSION_DENIED;
470 utility::LogInfo("Fail to access path[%s]: Invalid Path", mPointcloudOutputPath.c_str());
471 return MEDIA_VISION_ERROR_INVALID_PARAMETER;
474 bool bText = (type == MV_3D_POINTCLOUD_TYPE_PCD_TXT || type == MV_3D_POINTCLOUD_TYPE_PLY_TXT);
476 std::string fullPath = mPointcloudOutputPath + std::string("/") + std::string(fileName);
478 if (!io::WritePointCloud(fullPath.c_str(), *(s->pointcloud), { bText, false, false, {} })) {
479 utility::LogError("Failed to write {}", fullPath.c_str());
480 return MEDIA_VISION_ERROR_INTERNAL;
483 utility::LogInfo("Successfully wrote {}", fullPath.c_str());
485 return MEDIA_VISION_ERROR_NONE;
487 return MEDIA_VISION_ERROR_NOT_SUPPORTED;
491 } // namespace mediavision