AzureKinectPointCloudProvider.cpp
Go to the documentation of this file.
2
4
5#include <cstdio>
6#include <filesystem>
7#include <functional>
8#include <mutex>
9#include <utility>
10
11#include <IceUtil/Time.h>
12
13#include <opencv2/core/hal/interface.h>
14#include <opencv2/imgproc/imgproc.hpp>
15
25
27
33
34#include <Calibration/Calibration.h>
35#include <Image/ImageProcessor.h>
36
37#ifdef INCLUDE_BODY_TRACKING
39#endif
40
41
42namespace
43{
44 /**
45 * @brief Converts a k4a image to an IVT image.
46 * @param color_image Reference to a k4a image containing the color data that should be converted.
47 * @param result IVT image the converted image should be stored in. The size of this image and the color_image need to match.
48 */
49 [[maybe_unused]] void
50 k4aToIvtImage(const k4a::image& color_image, ::CByteImage& result)
51 {
52 if (color_image.get_format() == K4A_IMAGE_FORMAT_COLOR_YUY2)
53 {
54 // Convert YUY2 to RGB using OpenCV
55 cv::Mat yuy2_image(color_image.get_height_pixels(),
56 color_image.get_width_pixels(),
57 CV_8UC2,
58 const_cast<uint8_t*>(color_image.get_buffer()));
59 cv::Mat rgb_image;
60 cv::cvtColor(yuy2_image, rgb_image, cv::COLOR_YUV2RGB_YUY2);
61
62 // Convert the OpenCV Mat to the IVT CByteImage format
63 visionx::imrec::convert(rgb_image, result);
64 }
65 else if (color_image.get_format() == K4A_IMAGE_FORMAT_COLOR_BGRA32)
66 {
67 auto cw = static_cast<unsigned int>(color_image.get_width_pixels());
68 auto ch = static_cast<unsigned int>(color_image.get_height_pixels());
69 ARMARX_CHECK_EQUAL(static_cast<unsigned int>(result.width), cw);
70 ARMARX_CHECK_EQUAL(static_cast<unsigned int>(result.height), ch);
71
72 auto color_buffer = color_image.get_buffer();
73 auto rgb_buffer_ivt = result.pixels;
74
75 // Index in the IVT image. This value will be increased by 3 per pixel.
76 int index_ivt = 0;
77
78 // Index in the k4a image. This value increases by 4 per pixel.
79 int index_k4a = 0;
80
81 for (unsigned int y = 0; y < ch; ++y)
82 {
83 for (unsigned int x = 0; x < cw; ++x)
84 {
85 rgb_buffer_ivt[index_ivt] = color_buffer[index_k4a + 2];
86 rgb_buffer_ivt[index_ivt + 1] = color_buffer[index_k4a + 1];
87 rgb_buffer_ivt[index_ivt + 2] = color_buffer[index_k4a + 0];
88 index_ivt += 3;
89 index_k4a += 4;
90 }
91 }
92 }
93 else
94 {
95 throw std::runtime_error("Unsupported color format in k4a image");
96 }
97 }
98
99
100#ifdef INCLUDE_BODY_TRACKING
101 void
102 printBodyInformation(k4abt_body_t body)
103 {
104 ARMARX_VERBOSE << "Body ID: " << body.id;
105 for (int i = 0; i < static_cast<int>(K4ABT_JOINT_COUNT); i++)
106 {
107 const k4a_float3_t position = body.skeleton.joints[i].position;
108 const k4a_quaternion_t orientation = body.skeleton.joints[i].orientation;
109 const k4abt_joint_confidence_level_t confidence_level =
110 body.skeleton.joints[i].confidence_level;
111
112 ARMARX_VERBOSE << "Joint" << i << ": "
113 << "Position[mm] (" << position.v[0] << "," << position.v[1] << ","
114 << position.v[2] << "); "
115 << "Orientation (" << orientation.v[0] << "," << orientation.v[1] << ","
116 << orientation.v[2] << "," << orientation.v[3] << "); "
117 << "Confidence Level " << confidence_level;
118 }
119 }
120#endif
121
122 const char*
123 k4aImageFormatToString(k4a_image_format_t format)
124 {
125 switch (format)
126 {
127 case K4A_IMAGE_FORMAT_COLOR_MJPG:
128 return "COLOR_MJPG";
129 case K4A_IMAGE_FORMAT_COLOR_NV12:
130 return "COLOR_NV12";
131 case K4A_IMAGE_FORMAT_COLOR_YUY2:
132 return "COLOR_YUY2";
133 case K4A_IMAGE_FORMAT_COLOR_BGRA32:
134 return "COLOR_BGRA32";
135 case K4A_IMAGE_FORMAT_DEPTH16:
136 return "DEPTH16";
137 case K4A_IMAGE_FORMAT_IR16:
138 return "IR16";
139 case K4A_IMAGE_FORMAT_CUSTOM:
140 return "CUSTOM";
141 default:
142 return "UNKNOWN";
143 }
144 }
145
146} // namespace
147
148namespace visionx
149{
150
151 std::function<void(armarx::Duration)>
153 {
154 return [description, this](armarx::Duration duration)
155 {
156 std::string name = "duration " + description + " in [ms]";
157 {
158 std::lock_guard g{metaInfoMtx};
159 setMetaInfo(name, new armarx::Variant{duration.toMilliSecondsDouble()});
160 }
161
162 {
163 std::lock_guard g{debugObserverMtx};
164 setDebugObserverDatafield(name, duration.toMilliSecondsDouble());
165 }
166 };
167 }
168
172 {
174 K4A_COLOR_RESOLUTION_720P,
175 "Resolution of the RGB camera image.")
176 .map("0", K4A_COLOR_RESOLUTION_OFF) /** Color camera will be turned off */
177 .map("720", K4A_COLOR_RESOLUTION_720P) /** 1280x720 16:9 */
178 .map("1080", K4A_COLOR_RESOLUTION_1080P) /** 1920x1080 16:9 */
179 .map("1440", K4A_COLOR_RESOLUTION_1440P) /** 2560x1440 16:9 */
180 .map("1536", K4A_COLOR_RESOLUTION_1536P) /** 2048x1536 4:3 */
181 .map("2160", K4A_COLOR_RESOLUTION_2160P) /** 3840x2160 16:9 */
182 .map("3072", K4A_COLOR_RESOLUTION_3072P); /** 4096x3072 4:3 */
184 K4A_DEPTH_MODE_NFOV_UNBINNED,
185 "Resolution/mode of the depth camera image.")
186 .setCaseInsensitive(true)
187 .map("OFF", K4A_DEPTH_MODE_OFF)
188 .map("NFOV_2X2BINNED", K4A_DEPTH_MODE_NFOV_2X2BINNED)
189 .map("NFOV_UNBINNED", K4A_DEPTH_MODE_NFOV_UNBINNED)
190 .map("WFOV_2X2BINNED", K4A_DEPTH_MODE_WFOV_2X2BINNED)
191 .map("WFOV_UNBINNED", K4A_DEPTH_MODE_WFOV_UNBINNED)
192 .map("PASSIVE_IR", K4A_DEPTH_MODE_PASSIVE_IR);
193
195 6000.0,
196 "Max. allowed depth value in mm. Depth values above this "
197 "threshold will be set to nan.");
198
199 defineOptionalProperty<float>("CaptureTimeOffset",
200 16.0f,
201 "In Milliseconds. Time offset between capturing the image on "
202 "the hardware and receiving the image in this process.",
204
206 K4A_IMAGE_FORMAT_COLOR_BGRA32,
207 "Color format of the RGB camera image.")
208 .map("BGRA", K4A_IMAGE_FORMAT_COLOR_BGRA32)
209 .map("YUY2", K4A_IMAGE_FORMAT_COLOR_YUY2);
210 }
211
214 {
217
218 defs->optional(
219 enableColorUndistortion,
220 "EnableColorUndistortion",
221 "Undistort the color images using the full 8 radial and tangential distortion "
222 "parameters provided by the Azure Kinect.\n"
223 "This can help for processing tasks which cannot handle radial parameters k3-k6.\n"
224 "Note that this drastically reduces the FPS (to something like 3).");
225 defs->optional(externalCalibrationFilePath,
226 "ExternalCalibrationFilePath",
227 "Path to an optional external"
228 " calibration file, which has a"
229 " camera matrix and distortion"
230 " parameters.");
231 defs->optional(mDeviceId, "device_id", "ID of the device.");
232
233 defs->optional(robotName, "robotName");
234 defs->optional(bodyCameraFrameName, "bodyCameraFrameName");
235 defs->optional(framerate.value,
236 "framerate.images",
237 "The framerate of RGB-D images [frames per second]."
238 "\nNote that the point cloud and body tracking frame rates are controlled by"
239 " the properties 'framerate' (point cloud) and 'framerate.bodyTracking'"
240 " (body tracking), respectively.")
241 .setMin(5)
242 .setMax(30);
243
244#ifdef INCLUDE_BODY_TRACKING
245 defs->optional(bodyTrackingEnabled,
246 "bodyTrackingEnabled",
247 "Whether the Azure Kinect Body Tracking SDK should be enabled or not.");
248 defs->optional(bodyTrackingRunAtStart,
249 "bodyTrackingRunAtStart",
250 "Whether the Azure Kinect Body Tracking SDK should directly run when the "
251 "component is startet."
252 "Otherwise it has to be activated by the ice interface.");
253 defs->optional(bodyTrackingModelFilename,
254 "bodyTrackingModelPath",
255 "Path where the .onnx DNN files can be found");
256 defs->optional(bodyTrackingGPUDeviceID, "bodyTrackingGPUDeviceID", "GPU Device ID.");
257 defs->optional(bodyTrackingTemporalSmoothingFactor,
258 "bodyTrackingTemporalSmoothingFactor",
259 "Temporal smoothing factor for Azure Kinect body tracking.");
260 defs->optional(useCPU, "useCPU", "Whether to use cpu");
261
262 defs->optional(bodyTrackingDepthMaskMinX, "bodyTrackingDepthMaskMinX");
263 defs->optional(bodyTrackingDepthMaskMaxX, "bodyTrackingDepthMaskMaxX");
264 defs->optional(bodyTrackingDepthMaskMaxZ, "bodyTrackingDepthMaskMaxZ");
265 defs->optional(framerate.bodyTracking.value,
266 "framerate.bodyTracking",
267 "The framerate with with the body tracking is run [frames per second]."
268 "\nNote that the RGB-D image and point cloud frame rates are controlled by"
269 " the properties 'framerate.image' (RGB-D images) and 'framerate'"
270 " (point cloud), respectively.")
271 .setMin(1.)
272 .setMax(30.);
273
274 defs->optional(startIMU, "startIMU");
275
276 humanPoseWriter.registerPropertyDefinitions(defs);
277#endif
278
279 defs->optional(enableHeartbeat, "enableHeartbeat");
280
281 return defs;
282 }
283
284 void
286 {
287 config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL;
288 if (framerate.value == 5)
289 {
290 config.camera_fps = K4A_FRAMES_PER_SECOND_5;
291 }
292 else if (framerate.value == 15)
293 {
294 config.camera_fps = K4A_FRAMES_PER_SECOND_15;
295 }
296 else if (framerate.value == 30)
297 {
298 config.camera_fps = K4A_FRAMES_PER_SECOND_30;
299 }
300 else
301 {
302 throw armarx::LocalException("Invalid image framerate (property 'framerate.images'): ")
303 << framerate.value << ". Only framerates 5, 15 and 30 are "
304 << "supported by Azure Kinect.";
305 }
306
307 framerate.pointCloud.value =
308 getProperty<float>("framerate"); // point cloud provider framerate
309 framerate.pointCloud.update(framerate.value);
310
311#ifdef INCLUDE_BODY_TRACKING
312 framerate.bodyTracking.update(framerate.value);
313#endif
314
315 config.depth_mode = getProperty<k4a_depth_mode_t>("DepthMode");
316 config.color_format = getProperty<k4a_image_format_t>("ColorFormat");
317 config.color_resolution = getProperty<k4a_color_resolution_t>("ColorResolution");
318
319 // This means that we'll only get captures that have both color and depth images, so we don't
320 // need to check if the capture contains a particular type of image.
321 config.synchronized_images_only = true;
322
323 // Set number of images per frame.
325
326 auto depth_dim = GetDepthDimensions(config.depth_mode);
327 auto color_dim = GetColorDimensions(config.color_resolution);
328
329 ARMARX_INFO << "Depth image size: " << depth_dim.first << "x" << depth_dim.second;
330 ARMARX_INFO << "Color image size: " << color_dim.first << "x" << color_dim.second;
331
332 alignedDepthImage =
333 k4a::image::create(K4A_IMAGE_FORMAT_DEPTH16,
334 color_dim.first,
335 color_dim.second,
336 color_dim.first * 2 * static_cast<int32_t>(sizeof(uint8_t)));
337
338 setImageFormat(visionx::ImageDimension(color_dim.first, color_dim.second),
339 visionx::eRgb,
340 visionx::eBayerPatternGr);
341
342 resultDepthImage.reset(visionx::tools::createByteImage(getImageFormat(), visionx::eRgb));
343 resultColorImage =
344 std::make_unique<CByteImage>(color_dim.first, color_dim.second, CByteImage::eRGB24);
345
346 xyzImage = k4a::image::create(K4A_IMAGE_FORMAT_CUSTOM,
347 color_dim.first,
348 color_dim.second,
349 color_dim.first * 3 * static_cast<int32_t>(sizeof(int16_t)));
350
351 pointcloud = std::make_unique<pcl::PointCloud<CloudPointType>>();
352 }
353
354 void
356 {
357 ARMARX_DEBUG << "Connecting " << getName();
358
360
361 // Note: The point cloud processing task is *not* started here. It is started at the end
362 // of `onStartCapture()`, because it uses `transformation`, which only exists from that
363 // point on.
364
365#ifdef INCLUDE_BODY_TRACKING
366 if (bodyTrackingEnabled)
367 {
368 ARMARX_INFO << "Connecting to human memory ...";
369 humanPoseWriter.connect(memoryNameSystem());
370 ARMARX_INFO << "Connected to human memory.";
371
372 bodyTrackingPublishTask = new armarx::RunningTask<AzureKinectPointCloudProvider>(
373 this,
374 &AzureKinectPointCloudProvider::runPublishBodyTrackingResults);
375 }
376#endif
377
378 if (enableHeartbeat)
379 {
380 ARMARX_CHECK_NOT_NULL(heartbeatPlugin);
381 heartbeatPlugin->signUp(armarx::Duration::MilliSeconds(500),
383 {"Vision", "Camera"},
384 "AzureKinectPointCloudProvider");
385 }
386
387 ARMARX_VERBOSE << "Connected " << getName();
388 }
389
390 void
392 {
393 ARMARX_DEBUG << "Disconnecting " << getName();
394
395 // Stop task. It is only present if capturing was started (see `onStartCapture()`).
396 if (pointcloudTask)
397 {
398 std::lock_guard<std::mutex> lock(pointcloudProcMutex);
399 ARMARX_DEBUG << "Stopping pointcloud processing thread...";
400 const bool WAIT_FOR_JOIN = false;
401 pointcloudTask->stop(WAIT_FOR_JOIN);
402 pointcloudProcSignal.notify_all();
403 ARMARX_DEBUG << "Waiting for pointcloud processing thread to stop...";
404 pointcloudTask->waitForStop();
405 ARMARX_DEBUG << "Pointcloud processing thread stopped";
406 }
407
408#ifdef INCLUDE_BODY_TRACKING
409 if (bodyTrackingIsRunning)
410 {
411 bodyTrackingPublishTask->stop();
412 }
413#endif
414
415 ARMARX_DEBUG << "Disconnected " << getName();
416 }
417
418 void
424
425 void
427 {
428 ARMARX_INFO << "Closing Azure Kinect device";
429 device.close();
430 ARMARX_INFO << "Closed Azure Kinect device";
431 }
432
433 void
435 {
437
438 cloudFormat = getPointCloudFormat();
439
440 // Check for devices.
441 const uint32_t DEVICE_COUNT = k4a::device::get_installed_count();
442 if (DEVICE_COUNT == 0)
443 {
444 getArmarXManager()->asyncShutdown();
445 throw armarx::LocalException("No Azure Kinect devices detected!");
446 }
447
449
450 device = k4a::device::open(static_cast<uint32_t>(mDeviceId));
451 ARMARX_DEBUG << "Opened device id #" << mDeviceId << " with serial number "
452 << device.get_serialnum() << ".";
453
455
456 auto depthDim = GetDepthDimensions(config.depth_mode);
457 auto colorDim = GetColorDimensions(config.color_resolution);
458 (void)depthDim;
459 (void)colorDim;
460
461 k4aCalibration = device.get_calibration(config.depth_mode, config.color_resolution);
463 << "Color camera calibration:"
464 << "\n"
465 << "cx: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.cx
466 << "\n"
467 << "cy: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.cy
468 << "\n"
469 << "fx: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.fx
470 << "\n"
471 << "fy: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.fy
472 << "\n"
473 << "k1: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.k1
474 << "\n"
475 << "k2: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.k2
476 << "\n"
477 << "p1: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.p1
478 << "\n"
479 << "p2: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.p2
480 << "\n"
481 << "k3: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.k3
482 << "\n"
483 << "k4: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.k4
484 << "\n"
485 << "k5: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.k5
486 << "\n"
487 << "k6: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.k6;
488
489
490 auto c = getStereoCalibration(Ice::Current());
491 CCalibration* c_left = tools::convert(c.calibrationLeft);
492 c_left->PrintCameraParameters();
493 delete c_left;
494 CCalibration* c_right = tools::convert(c.calibrationRight);
495 c_right->PrintCameraParameters();
496 delete c_right;
497
498 // `k4a::transformation`'s calibration constructor is `noexcept`: on failure it silently
499 // yields a null handle, and the problem only surfaces later as a per-frame
500 // "Failed to transform depth image to point cloud!". Create the handle explicitly so
501 // that the actual cause is reported here, where it happens.
502 k4a_transformation_t transformationHandle = k4a_transformation_create(&k4aCalibration);
503 ARMARX_CHECK_NOT_NULL(transformationHandle)
504 << "Failed to create the Azure Kinect transformation. The most common cause is that "
505 "the depth engine plugin 'libdepthengine.so.2.0' could not be loaded. Make sure "
506 "it is on LD_LIBRARY_PATH (axii module 'deps/azure-kinect/depth-engine').";
507 transformation = k4a::transformation(transformationHandle);
508
509#ifdef INCLUDE_BODY_TRACKING
510 if (bodyTrackingEnabled)
511 {
512 // eventually, resolve environment variable
513 armarx::ArmarXDataPath::ReplaceEnvVars(bodyTrackingModelFilename);
514
515 const bool found = armarx::ArmarXDataPath::getAbsolutePath(bodyTrackingModelFilename,
516 bodyTrackingModelFilename);
517 ARMARX_CHECK(found) << "Body tracking DNN model could not be found/resolved at `"
518 << bodyTrackingModelFilename << "`.";
519
520 ARMARX_INFO << "Using body tracking DNN model from directory `"
521 << bodyTrackingModelFilename << "`.";
522
523 ARMARX_CHECK(std::filesystem::exists(bodyTrackingModelFilename))
524 << "The path `" << bodyTrackingModelFilename << "` does not exist!";
525
526 k4abt_tracker_configuration_t const bodyTrackingConfig{
527 .sensor_orientation = K4ABT_SENSOR_ORIENTATION_DEFAULT,
528 .processing_mode = useCPU ? K4ABT_TRACKER_PROCESSING_MODE_CPU
529 : K4ABT_TRACKER_PROCESSING_MODE_GPU_CUDA,
530 .gpu_device_id = bodyTrackingGPUDeviceID,
531 .model_path = bodyTrackingModelFilename.c_str()};
532
533 bodyTracker = k4abt::tracker::create(k4aCalibration, bodyTrackingConfig);
534 bodyTracker.set_temporal_smoothing(bodyTrackingTemporalSmoothingFactor);
535
536 if (bodyTrackingRunAtStart and not bodyTrackingIsRunning)
537 {
538 bodyTrackingIsRunning = true;
539 bodyTrackingPublishTask->start();
540 }
541 }
542#endif
543
545 device.set_color_control(K4A_COLOR_CONTROL_BRIGHTNESS, K4A_COLOR_CONTROL_MODE_MANUAL, 128);
546
547 device.start_cameras(&config);
548
550
551 if (startIMU)
552 {
553 device.start_imu();
554 ARMARX_INFO << "IMU is active" << std::endl;
555
557 }
558
559 setMetaInfo("serialNumber", new armarx::Variant(device.get_serialnum()));
560 setMetaInfo("rgbVersion", new armarx::Variant(VersionToString(device.get_version().rgb)));
561 setMetaInfo("depthVersion",
562 new armarx::Variant(VersionToString(device.get_version().depth)));
563 setMetaInfo("depthSensorVersion",
564 new armarx::Variant(VersionToString(device.get_version().depth_sensor)));
565 setMetaInfo("audioVersion",
566 new armarx::Variant(VersionToString(device.get_version().audio)));
567
569
570
571 // Color image calibration
572 {
573 // Load intrinsics from camera
574 const k4a_calibration_camera_t calibration{k4aCalibration.color_camera_calibration};
575 const k4a_calibration_intrinsic_parameters_t::_param param =
576 calibration.intrinsics.parameters.param;
577
578 // // Scale intrinsics according to image scale
579 // param.fx *= image_scale_;
580 // param.fy *= image_scale_;
581
582 // Calculate distortion map
583 cv::Mat1f camera_matrix(3, 3);
584 camera_matrix << param.fx, 0, param.cx, 0, param.fy, param.cy, 0, 0, 1;
585 cv::Mat1f new_camera_matrix(3, 3);
586 new_camera_matrix << param.fx, 0, param.cx, 0, param.fx, param.cy, 0, 0, 1;
587 cv::Mat1f distortion_coeff(1, 8);
588 distortion_coeff << param.k1, param.k2, param.p1, param.p2, param.k3, param.k4,
589 param.k5, param.k6;
590 cv::Mat map1, map2, map3;
591 cv::initUndistortRectifyMap(
592 camera_matrix,
593 distortion_coeff,
594 cv::Mat{},
595 new_camera_matrix,
596 cv::Size{calibration.resolution_width, calibration.resolution_height},
597 CV_32FC1,
598 map1,
599 map2);
600 cv::convertMaps(map1, map2, colorDistortionMap, map3, CV_16SC2, true);
601 }
602
603 // Start the point cloud processing task only now: everything it touches (in particular
604 // `transformation`, `alignedDepthImage` and `xyzImage`) is set up at this point.
605 if (pointcloudTask)
606 {
607 // Capturing was started before without an intermediate disconnect.
608 std::lock_guard<std::mutex> lock(pointcloudProcMutex);
609 pointcloudTask->stop(false);
610 pointcloudProcSignal.notify_all();
611 }
612 if (pointcloudTask)
613 {
614 pointcloudTask->waitForStop();
615 }
616 {
617 std::lock_guard<std::mutex> lock(pointcloudProcMutex);
618 depthImageReady = false;
619 depthImageProcessed = false;
620 }
623 this,
625 pointcloudTask->start();
626 }
627
628 void
630 {
631 if (startIMU)
632 {
633 device.stop_imu();
634 }
635 }
636
637 bool
639 {
642
643 ScopedStopWatch sw_total{createSwCallback("doCapture")};
644
645 k4a::capture capture;
646 const std::chrono::milliseconds TIMEOUT{1000};
647
648 StopWatch sw_get_capture;
649 bool status = false;
650 try
651 {
652 ARMARX_DEBUG << "Try capture.";
653 status = device.get_capture(&capture, TIMEOUT);
654 ARMARX_DEBUG << "Got capture.";
655 }
656 catch (const std::exception&)
657 {
658 ARMARX_WARNING << "Failed to get capture from device (#" << ++mDiagnostics.num_crashes
659 << "). Restarting camera.";
660 StopWatch sw;
661 device.stop_cameras();
662 device.start_cameras(&config);
663 ARMARX_INFO << "Restarting took " << sw.stop() << ".";
664 return false;
665 }
666
667 if (status)
668 {
669 createSwCallback("waiting for get_capture")(sw_get_capture.stop());
670
671 {
672 std::lock_guard g{metaInfoMtx};
673 setMetaInfo("temperature", new armarx::Variant(capture.get_temperature_c()));
674 }
675
676 // see ROS: This will give INCORRECT timestamps until the first image.
677 const bool mustInitializeTimestampOffset = [&]()
678 {
679 std::lock_guard g{deviceToRealtimeOffsetMtx};
680 return device_to_realtime_offset_.count() == 0;
681 }();
682 if (mustInitializeTimestampOffset)
683 {
684 initializeTimestampOffset(capture.get_depth_image().get_device_timestamp());
685 }
686
687 // next, we update the timestamp offset continuously
688 updateTimestampOffset(capture.get_ir_image().get_device_timestamp(),
689 capture.get_ir_image().get_system_timestamp());
690
691 const k4a::image DEPTH_IMAGE = capture.get_depth_image();
692
693 // This function assumes that the image is made of depth pixels (i.e. uint16_t's),
694 // which is only true for IR/depth images.
695 const k4a_image_format_t IMAGE_FORMAT = DEPTH_IMAGE.get_format();
696 if (IMAGE_FORMAT != K4A_IMAGE_FORMAT_DEPTH16 && IMAGE_FORMAT != K4A_IMAGE_FORMAT_IR16)
697 {
698 const char* format_str = ::k4aImageFormatToString(IMAGE_FORMAT);
699 std::stringstream error_msg;
700 error_msg << "Attempted to colorize a non-depth image with format: " << format_str;
701 throw std::logic_error(error_msg.str());
702 }
703
704 // Provide data for pointcloud processing thread and signal to start processing.
705 if (not framerate.pointCloud.skip())
706 {
707 ScopedStopWatch sw{createSwCallback("transform depth image to camera")};
708 // Acquire lock and write data needed by pointcloud thread (i.e.,
709 // alignedDepthImageScaled and depthImageReady).
710 {
711 std::lock_guard lock{pointcloudProcMutex};
712 transformation.depth_image_to_color_camera(DEPTH_IMAGE, &alignedDepthImage);
713
714 // Signal that point cloud processing may proceed and reset processed flag.
715 depthImageReady = true;
716 }
717
718 ARMARX_DEBUG << "Notifying pointcloud thread.";
719 pointcloudProcSignal.notify_one();
720 }
721
722#ifdef INCLUDE_BODY_TRACKING
723 if (bodyTrackingIsRunning)
724 {
725 ARMARX_DEBUG << "body Tracking Is Running.";
726 std::scoped_lock lock(bodyTrackingParameterMutex);
727
728 if (not framerate.bodyTracking.skip())
729 {
730 k4a::image ir_image = capture.get_ir_image();
731 std::uint8_t* ir_image_buffer = ir_image.get_buffer();
732
733 k4a::image depth_image = capture.get_depth_image();
734 std::uint8_t* depth_image_buffer = depth_image.get_buffer();
735
736 ARMARX_CHECK_EQUAL(ir_image.get_width_pixels(), depth_image.get_width_pixels());
737 ARMARX_CHECK_EQUAL(ir_image.get_height_pixels(),
738 depth_image.get_height_pixels());
739
740 const int stride = ir_image.get_stride_bytes() / ir_image.get_width_pixels();
741
742 for (int x = 0; x < ir_image.get_width_pixels(); x++)
743 {
744 for (int y = 0; y < ir_image.get_height_pixels(); y++)
745 {
746 const int i = (y * ir_image.get_width_pixels() * stride) + (x * stride);
747 const int z = (static_cast<int>(depth_image_buffer[i])) +
748 (static_cast<int>(depth_image_buffer[i + 1]) << 8);
749
750 if ((bodyTrackingDepthMaskMinX > 0 and x < bodyTrackingDepthMaskMinX) or
751 (bodyTrackingDepthMaskMaxX > 0 and x > bodyTrackingDepthMaskMaxX) or
752 (bodyTrackingDepthMaskMaxZ > 0 and z > bodyTrackingDepthMaskMaxZ))
753 {
754 ir_image_buffer[i] = std::numeric_limits<std::uint8_t>::max();
755 ir_image_buffer[i + 1] = std::numeric_limits<std::uint8_t>::max();
756 depth_image_buffer[i] = std::numeric_limits<std::uint8_t>::max();
757 depth_image_buffer[i + 1] =
758 std::numeric_limits<std::uint8_t>::max();
759 }
760 }
761 }
762
763 if (not bodyTracker.enqueue_capture(capture))
764 {
765 ARMARX_WARNING << "Add capture to tracker process queue timeout";
766 }
767 }
768 }
769#endif
770
771 const k4a::image COLOR_IMAGE = capture.get_color_image();
772 ARMARX_DEBUG << "Got COLOR image.";
773
774 auto real_time = IceUtil::Time::now();
775 auto monotonic_time = IceUtil::Time::now(IceUtil::Time::Monotonic);
776 auto clock_diff = real_time - monotonic_time;
777
778 auto image_monotonic_time =
779 IceUtil::Time::microSeconds(std::chrono::duration_cast<std::chrono::microseconds>(
780 DEPTH_IMAGE.get_system_timestamp())
781 .count());
782 long offset = long(getProperty<float>("CaptureTimeOffset").getValue() * 1000.0f);
783
784 imagesTime = image_monotonic_time + clock_diff - IceUtil::Time::microSeconds(offset);
785
786 {
787 std::lock_guard g{metaInfoMtx};
788 setMetaInfo("image age in [ms]",
789 new armarx::Variant{(real_time - imagesTime).toMilliSecondsDouble()});
790 }
791
792 {
793 std::lock_guard g{debugObserverMtx};
794 setDebugObserverDatafield("image age in [ms]",
795 (real_time - imagesTime).toMilliSecondsDouble());
796 }
797
798 if (enableColorUndistortion)
799 {
800 // only the color image needs to be rectified
801 cv::Mat tmp_rgb_image;
802
803 if (COLOR_IMAGE.get_format() == K4A_IMAGE_FORMAT_COLOR_YUY2)
804 {
805 ARMARX_DEBUG << "Converting YUY2 image.";
806 cv::Mat yuy2_image(COLOR_IMAGE.get_height_pixels(),
807 COLOR_IMAGE.get_width_pixels(),
808 CV_8UC2,
809 const_cast<uint8_t*>(COLOR_IMAGE.get_buffer()));
810
811 cv::cvtColor(yuy2_image, tmp_rgb_image, cv::COLOR_YUV2RGB_YUY2);
812 }
813 else if (COLOR_IMAGE.get_format() == K4A_IMAGE_FORMAT_COLOR_BGRA32)
814 {
815 cv::cvtColor(cv::Mat{cv::Size{COLOR_IMAGE.get_width_pixels(),
816 COLOR_IMAGE.get_height_pixels()},
817 CV_8UC4,
818 (void*)COLOR_IMAGE.get_buffer(),
819 cv::Mat::AUTO_STEP},
820 tmp_rgb_image,
821 cv::COLOR_BGRA2RGB);
822 }
823 else
824 {
825 throw std::runtime_error("Unsupported color format in k4a image");
826 }
827
828 cv::Mat cv_color_image_undistorted(COLOR_IMAGE.get_width_pixels(),
829 COLOR_IMAGE.get_height_pixels(),
830 CV_8UC3);
831
832 cv::remap(tmp_rgb_image,
833 cv_color_image_undistorted,
834 colorDistortionMap,
835 cv::Mat{},
836 cv::INTER_NEAREST,
837 cv::BORDER_CONSTANT);
838
839 visionx::imrec::convert_rgb2cbi(cv_color_image_undistorted, *resultColorImage);
840 }
841 else
842 {
843 // Convert the k4a image to an IVT image.
844 ScopedStopWatch sw{createSwCallback("convert k4a image to IVT")};
845 ::k4aToIvtImage(COLOR_IMAGE, *resultColorImage);
846 }
847
848
849 // Prepare result depth image.
850 {
851 ScopedStopWatch sw{createSwCallback("prepare result depth image")};
852
853 const int DW = alignedDepthImage.get_width_pixels();
854 const int DH = alignedDepthImage.get_height_pixels();
855
856 ARMARX_CHECK_EXPRESSION(resultDepthImage);
857 ARMARX_CHECK_EQUAL(resultDepthImage->width, DW);
858 ARMARX_CHECK_EQUAL(resultDepthImage->height, DH);
859
860 auto result_depth_buffer = resultDepthImage->pixels;
861 const auto* depth_buffer =
862 reinterpret_cast<const uint16_t*>(alignedDepthImage.get_buffer());
863
864 int index = 0;
865 int index_2 = 0;
866 for (int y = 0; y < DH; ++y)
867 {
868 for (int x = 0; x < DW; ++x)
869 {
870 uint16_t depth_value = depth_buffer[index_2];
872 result_depth_buffer[index],
873 result_depth_buffer[index + 1],
874 result_depth_buffer[index + 2]);
875 index += 3;
876 index_2 += 1;
877 }
878 }
879 }
880
881 // Broadcast RGB-D image.
882 {
883 ScopedStopWatch sw{createSwCallback("broadcast RGB-D image")};
884 CByteImage* images[2] = {resultColorImage.get(), resultDepthImage.get()};
885 provideImages(images, imagesTime);
886 }
887
888 // Wait until `alignedDepthImage` was processed and can be overridden again.
889 {
890 std::unique_lock signal_lock{pointcloudProcMutex};
891 ARMARX_DEBUG << "Capturing thread waiting for signal...";
892 // Never wait unconditionally here: if the point cloud thread is not running (or
893 // is stuck), an unbounded wait silently wedges the entire component -- no point
894 // clouds, no RGB-D images and no further log output.
895 const std::chrono::milliseconds POINTCLOUD_TIMEOUT{1000};
896 if (not pointcloudProcSignal.wait_for(
897 signal_lock, POINTCLOUD_TIMEOUT, [&] { return depthImageProcessed; }))
898 {
900 << "Point cloud processing did not signal completion within "
901 << POINTCLOUD_TIMEOUT.count()
902 << " ms. Continuing capture without it.";
903 }
904 ARMARX_DEBUG << "Capturing thread received signal.";
905 }
906
907 {
908 std::lock_guard g{debugObserverMtx};
910 }
911
912 if (enableHeartbeat)
913 {
914 heartbeatPlugin->heartbeat();
915 }
916
917 // return true;
918 }
919 else
920 {
921 ARMARX_INFO << deactivateSpam(30) << "Did not get frame until timeout of " << TIMEOUT;
922
923 {
924 std::lock_guard g{debugObserverMtx};
926 }
927
928 return false;
929 }
930
931 if (startIMU)
932 {
933 try
934 {
935 Eigen::Vector3f accMean = Eigen::Vector3f::Zero();
936 std::size_t cnt = 0;
937
938 k4a_imu_sample_t imu_sample;
939 while (device.get_imu_sample(&imu_sample, std::chrono::milliseconds(0)))
940 {
941 const Eigen::Vector3f acceleration{imu_sample.acc_sample.xyz.x,
942 imu_sample.acc_sample.xyz.y,
943 imu_sample.acc_sample.xyz.z};
944
945 accMean += acceleration;
946 cnt++;
947 }
948
949 accMean /= static_cast<float>(cnt);
950
951 ARMARX_INFO << "Got IMU sample";
952
953 {
954 std::lock_guard g{debugObserverMtx};
955
956 setDebugObserverDatafield("acceleration.x", accMean.x());
957 setDebugObserverDatafield("acceleration.y", accMean.y());
958 setDebugObserverDatafield("acceleration.z", accMean.z());
959
961 }
962 }
963 catch (const std::exception&)
964 {
965 ARMARX_WARNING << "Failed to get imu samples from device (#"
966 << ++mDiagnostics.num_crashes << ").";
967 }
968 }
969
970 return true;
971 }
972
973#ifdef INCLUDE_BODY_TRACKING
974 void
975 AzureKinectPointCloudProvider::runPublishBodyTrackingResults()
976 {
978 while (bodyTrackingIsRunning)
979 {
980
981 // body frames might just not be available.
982 const k4abt::frame body_frame = [&]() -> k4abt::frame
983 {
984 try
985 {
986 auto result = bodyTracker.pop_result();
987 return result;
988 }
989 catch (...)
990 {
991 ARMARX_VERBOSE << deactivateSpam(1) << "Exception in body tracking publishing: "
993 return {};
994 }
995 }();
996
997 if (body_frame != nullptr)
998 {
999 armarx::core::time::ScopedStopWatch sw{
1000 createSwCallback("publish body tracking result")};
1001 // see https://github.com/microsoft/Azure_Kinect_ROS_Driver/blob/melodic/src/k4a_ros_device.cpp
1002 const armarx::DateTime timestamp =
1003 timestampToArmarX(body_frame.get_device_timestamp());
1004
1005 {
1006 auto real_time = IceUtil::Time::now();
1007 auto monotonic_time = IceUtil::Time::now(IceUtil::Time::Monotonic);
1008 auto clock_diff = real_time - monotonic_time;
1009
1010 auto image_monotonic_time = IceUtil::Time::microSeconds(
1011 std::chrono::duration_cast<std::chrono::microseconds>(
1012 body_frame.get_system_timestamp())
1013 .count());
1014 // long offset = long(getProperty<float>("CaptureTimeOffset").getValue() * 1000.0f);
1015
1016 // IceUtil::Time imageTime = image_monotonic_time + clock_diff - IceUtil::Time::microSeconds(offset);
1017 IceUtil::Time imageTime = image_monotonic_time + clock_diff;
1018
1019 {
1020 std::lock_guard g{debugObserverMtx};
1021 setDebugObserverDatafield("ros_vs_ice_timestamp [µs]",
1022 imageTime.toMicroSeconds());
1023 }
1024 }
1025
1026 {
1027 const armarx::Clock realtimeClock = armarx::Clock(armarx::ClockType::Realtime);
1028 const armarx::Clock monotonicClock =
1029 armarx::Clock(armarx::ClockType::Monotonic);
1030
1031 auto real_time = realtimeClock.now();
1032 auto monotonic_time = monotonicClock.now();
1033 auto clock_diff = real_time - monotonic_time;
1034
1035 auto image_monotonic_time = armarx::Duration::MicroSeconds(
1036 std::chrono::duration_cast<std::chrono::microseconds>(
1037 body_frame.get_system_timestamp())
1038 .count());
1039 // long offset = long(getProperty<float>("CaptureTimeOffset").getValue() * 1000.0f);
1040
1041 // IceUtil::Time imageTime = image_monotonic_time + clock_diff - IceUtil::Time::microSeconds(offset);
1042 auto imageTime = image_monotonic_time + clock_diff;
1043
1044 armarx::DateTime imageTimestamp = armarx::DateTime(imageTime);
1045
1046 {
1047 std::lock_guard g{debugObserverMtx};
1048 setDebugObserverDatafield("ros_vs_armarx_timestamp [µs]",
1049 imageTime.toMicroSeconds());
1050 }
1051 }
1052
1053
1054 std::uint32_t num_bodies = body_frame.get_num_bodies();
1055 {
1056 std::lock_guard g{debugObserverMtx};
1057 setDebugObserverDatafield("n_bodies_detected", num_bodies);
1058 }
1059
1060 std::vector<armarx::armem::human::HumanPose> humanPoses;
1061 humanPoses.reserve(num_bodies);
1062
1063 for (std::uint32_t i = 0; i < num_bodies; i++)
1064 {
1065 k4abt_body_t body = body_frame.get_body(i);
1066 printBodyInformation(body);
1067
1068 armarx::armem::human::HumanPose humanPose;
1069 humanPose.humanTrackingId = std::to_string(body.id);
1070 humanPose.cameraFrameName = bodyCameraFrameName;
1072
1073 for (int i = 0; i < static_cast<int>(K4ABT_JOINT_COUNT); i++)
1074 {
1075 const auto joint =
1077 const auto name =
1079
1080 k4a_float3_t position = body.skeleton.joints[i].position;
1081 k4a_quaternion_t orientation = body.skeleton.joints[i].orientation;
1082 k4abt_joint_confidence_level_t confidence_level =
1083 body.skeleton.joints[i].confidence_level;
1084
1085 humanPose.keypoints[name] = armarx::armem::human::PoseKeypoint{
1086 .label = name,
1087 .confidence = static_cast<float>(static_cast<int>(confidence_level)),
1088 .positionCamera = armarx::FramedPosition(
1089 Eigen::Vector3f{position.v[0], position.v[1], position.v[2]},
1090 bodyCameraFrameName,
1091 robotName),
1092 .orientationCamera =
1093 armarx::FramedOrientation(Eigen::Quaternionf(orientation.wxyz.w,
1094 orientation.wxyz.x,
1095 orientation.wxyz.y,
1096 orientation.wxyz.z)
1097 .toRotationMatrix(),
1098 bodyCameraFrameName,
1099 robotName)};
1100 }
1101
1102 humanPoses.push_back(humanPose);
1103 }
1104
1105 {
1106 std::lock_guard g{debugObserverMtx};
1107 setDebugObserverDatafield("bodyTrackingCaptureUntilPublish [ms]",
1108 (armarx::Clock::Now() - timestamp).toMilliSeconds());
1109 }
1110
1111
1112 {
1114 //ARMARX_INFO << deactivateSpam(1) << "committing human poses";
1115 humanPoseWriter.commitHumanPosesInCameraFrame(humanPoses, getName(), timestamp);
1116 }
1117
1118
1119 // k4a::image body_index_map = body_frame.get_body_index_map();
1120 // if (body_index_map != nullptr)
1121 // {
1122 // //print_body_index_map_middle_line(body_index_map);
1123 // }
1124 // else
1125 // {
1126 // ARMARX_WARNING << "Error: Failed to generate bodyindex map!";
1127 // }
1128 }
1129 else
1130 {
1131 // It should never hit timeout when K4A_WAIT_INFINITE is set.
1132 ARMARX_VERBOSE << "Error! Pop body frame result time out!";
1133 }
1134
1135 metronome.waitForNextTick();
1136 }
1137 }
1138#endif
1139
1140 void
1142 {
1143 ARMARX_DEBUG << "Started pointcloud processing task.";
1144
1145 IceUtil::Time time;
1146
1147 // Main pointcloud processing loop.
1148 while (not pointcloudTask->isStopped())
1149 {
1150 // Wait for data.
1151 std::unique_lock signal_lock{pointcloudProcMutex};
1152 ARMARX_DEBUG << "Pointcloud thread waiting for signal...";
1153 pointcloudProcSignal.wait(signal_lock,
1154 [&]
1155 { return pointcloudTask->isStopped() or depthImageReady; });
1156 ARMARX_DEBUG << "Pointcloud thread received signal.";
1157
1158 if (pointcloudTask->isStopped())
1159 {
1160 break;
1161 }
1162
1163 // Assess timings, reset flags.
1164 const IceUtil::Time TIMESTAMP = imagesTime;
1165 depthImageReady = false;
1166 depthImageProcessed = false;
1167
1168 // Everything from here up to `depthImageProcessed = true` must not escape this loop:
1169 // an exception would terminate this thread, and the capturing thread would then never
1170 // be released again (see the wait in `doCapture()`).
1171 bool pointcloudValid = true;
1172 try
1173 {
1174
1175 // Transform depth image to pointcloud.
1176 {
1178 transformation.depth_image_to_point_cloud(
1179 alignedDepthImage, K4A_CALIBRATION_TYPE_COLOR, &xyzImage);
1180 ARMARX_DEBUG << "Transforming depth image to point cloud took "
1183 }
1184
1185 // Construct PCL pointcloud.
1186 {
1188
1189 ARMARX_CHECK_EXPRESSION(pointcloud);
1190
1191 pointcloud->width = static_cast<uint32_t>(xyzImage.get_width_pixels());
1192 pointcloud->height = static_cast<uint32_t>(xyzImage.get_height_pixels());
1193
1195
1196 pointcloud->is_dense = false;
1197 pointcloud->points.resize(pointcloud->width * pointcloud->height);
1198
1199 auto k4a_cloud_buffer = reinterpret_cast<const int16_t*>(xyzImage.get_buffer());
1200
1201 ARMARX_CHECK_EXPRESSION(k4a_cloud_buffer);
1202
1203 unsigned char* color_buffer = resultColorImage->pixels;
1204 while (color_buffer == nullptr)
1205 {
1207 << "color_buffer is null. This should never happen. There is "
1208 "probably a race condition somewhere that needs to be fixed. "
1209 "Temporarily we ignore this and continue.\n Timestamp: "
1211 color_buffer = resultColorImage->pixels;
1212 }
1213 ARMARX_CHECK_NOT_NULL(color_buffer);
1214
1215 ARMARX_CHECK_EQUAL(xyzImage.get_width_pixels(), resultColorImage->width);
1216 ARMARX_CHECK_EQUAL(xyzImage.get_height_pixels(), resultColorImage->height);
1217 ARMARX_CHECK_EQUAL(pointcloud->points.size(),
1218 pointcloud->width * pointcloud->height);
1219
1221
1222 size_t index = 0;
1223 float max_depth = getProperty<float>("MaxDepth").getValue();
1224 for (auto& p : pointcloud->points)
1225 {
1226 p.r = color_buffer[index];
1227 p.x = k4a_cloud_buffer[index];
1228
1229 index++;
1230
1231 p.g = color_buffer[index];
1232 p.y = k4a_cloud_buffer[index];
1233
1234 index++;
1235
1236 p.b = color_buffer[index];
1237 auto z = k4a_cloud_buffer[index];
1238
1239 index++;
1240
1241 if (z <= max_depth and z != 0)
1242 {
1243 p.z = z;
1244 }
1245 else
1246 {
1247 p.z = std::numeric_limits<float>::quiet_NaN();
1248 }
1249 }
1250
1251 ARMARX_DEBUG << "Constructing point cloud took "
1254 }
1255 }
1256 catch (const std::exception& e)
1257 {
1258 pointcloudValid = false;
1260 << "Failed to process depth image into a point cloud: " << e.what()
1261 << "\nSkipping this frame.";
1262 }
1263
1264 // Notify capturing thread that data was processed and may be overridden. This must
1265 // happen even if processing failed, otherwise the capturing thread stalls.
1266 depthImageProcessed = true;
1267 signal_lock.unlock();
1268 ARMARX_DEBUG << "Notifying capturing thread...";
1269 pointcloudProcSignal.notify_all();
1270
1272
1273 // Broadcast PCL pointcloud.
1274 if (pointcloudValid)
1275 {
1277 pointcloud->header.stamp = static_cast<unsigned long>(TIMESTAMP.toMicroSeconds());
1278 try
1279 {
1280 providePointCloud(pointcloud);
1281 }
1282 catch (const std::exception& e)
1283 {
1285 << "Failed to broadcast point cloud: " << e.what();
1286 }
1287 ARMARX_DEBUG << "Broadcasting pointcloud took "
1289 }
1290 }
1291
1292 ARMARX_DEBUG << "Stopped pointcloud processing task.";
1293 }
1294
1295 std::string
1300
1301 std::string
1303 {
1304 return "AzureKinectPointCloudProvider";
1305 }
1306
1307 void
1313
1314 void
1320
1321 void
1329
1330 void
1336
1337 StereoCalibration
1339 {
1340 using namespace Eigen;
1341 using Matrix3FRowMajor = Matrix<float, 3, 3, StorageOptions::RowMajor>;
1343
1344 // TODO: use the externally used cameraMatrix and distCoeffs
1345 const auto convert_calibration =
1346 [](const k4a_calibration_camera_t& k4a_calib, float scale = 1.f)
1347 {
1348 MonocularCalibration monocular_calibration;
1349
1350 const k4a_calibration_intrinsic_parameters_t& params = k4a_calib.intrinsics.parameters;
1351 monocular_calibration.cameraParam.principalPoint = {params.param.cx * scale,
1352 params.param.cy * scale};
1353 monocular_calibration.cameraParam.focalLength = {params.param.fx * scale,
1354 params.param.fy * scale};
1355 // TODO: Figure out convertions. IVT (Calibration.h) expects 4 parameters:
1356 // - The first radial lens distortion parameter.
1357 // - The second radial lens distortion parameter.
1358 // - The first tangential lens distortion parameter.
1359 // - The second tangential lens distortion parameter.
1360 // However, the Kinect offers k1-k6 radial distortion coefficients and 2 p1-p2
1361 // tangential distortion parameters, which means that k3-k6 are unused.
1362 // It is even unclear whether this is correct now, as previously it was a vector of all
1363 // 6 k-params, which resulted in failed assertions in TypeMapping.cpp in the function
1364 // CStereoCalibration* visionx::tools::convert(const visionx::StereoCalibration& stereoCalibration)
1365 // lin 314 at time of this commit.
1366 // See: https://microsoft.github.io/Azure-Kinect-Sensor-SDK/master/structk4a__calibration__intrinsic__parameters__t_1_1__param.html
1367 monocular_calibration.cameraParam.distortion = {
1368 params.param.k1,
1369 params.param.k2,
1370 params.param.p1,
1371 params.param.p2,
1372 };
1373
1374 // TODO this needs to be scaled! Or why shouldn't it ?
1375 monocular_calibration.cameraParam.width =
1376 std::floor(float(k4a_calib.resolution_width) * scale);
1377 monocular_calibration.cameraParam.height =
1378 std::floor(float(k4a_calib.resolution_height) * scale);
1379
1380 const Matrix3FRowMajor rotation =
1381 Map<const Matrix3FRowMajor>{k4a_calib.extrinsics.rotation};
1382
1383 monocular_calibration.cameraParam.rotation = convertEigenMatToVisionX(rotation);
1384 monocular_calibration.cameraParam.translation = {k4a_calib.extrinsics.translation,
1385 k4a_calib.extrinsics.translation + 3};
1386
1387 return monocular_calibration;
1388 };
1389
1390 StereoCalibration stereo_calibration;
1391
1392 stereo_calibration.calibrationLeft =
1393 convert_calibration(k4aCalibration.color_camera_calibration);
1394
1395 // if the camera images are rectified, the distortion params are 0
1396 if (enableColorUndistortion)
1397 {
1398 auto& colorDistortionParams = stereo_calibration.calibrationLeft.cameraParam.distortion;
1399 std::fill(colorDistortionParams.begin(), colorDistortionParams.end(), 0);
1400 }
1401
1402 // the depth image is rectified by default
1403 {
1404 // The depth image has been warped to to color camera frame. See depth_image_to_color_camera() above.
1405 // Therefore we use the calibration of the color camera (focal length etc) and set the distortion parameters to 0.
1406 stereo_calibration.calibrationRight =
1407 convert_calibration(k4aCalibration.color_camera_calibration);
1408
1409 auto& depthDistortionParams =
1410 stereo_calibration.calibrationRight.cameraParam.distortion;
1411 std::fill(depthDistortionParams.begin(), depthDistortionParams.end(), 0);
1412 }
1413
1414 stereo_calibration.rectificationHomographyLeft =
1415 convertEigenMatToVisionX(Matrix3f::Identity());
1416 stereo_calibration.rectificationHomographyRight =
1417 convertEigenMatToVisionX(Matrix3f::Identity());
1418
1419 return stereo_calibration;
1420 }
1421
1422 bool
1424 {
1425 return enableColorUndistortion;
1426 }
1427
1428 std::string
1430 {
1431 return getProperty<std::string>("frameName");
1432 }
1433
1434 std::vector<imrec::ChannelPreferences>
1436 {
1438
1439 imrec::ChannelPreferences rgb;
1440 rgb.requiresLossless = false;
1441 rgb.name = "rgb";
1442
1443 imrec::ChannelPreferences depth;
1444 depth.requiresLossless = true;
1445 depth.name = "depth";
1446
1447 return {rgb, depth};
1448 }
1449
1450 void
1452 const armarx::EnableHumanPoseEstimationInput& input,
1453 const Ice::Current&)
1454 {
1455#ifndef INCLUDE_BODY_TRACKING
1456 {
1457 ARMARX_ERROR << "INCLUDE_BODY_TRACKING is not defined.";
1458 return;
1459 }
1460#endif
1461
1462#ifdef INCLUDE_BODY_TRACKING
1463 if (bodyTrackingEnabled)
1464 {
1465 // should not require a mutex
1466 if (not bodyTrackingIsRunning and input.enable3d)
1467 {
1468 bodyTrackingIsRunning = true;
1469 bodyTrackingPublishTask->start();
1470 }
1471 else if (bodyTrackingIsRunning and not input.enable3d)
1472 {
1473 bodyTrackingIsRunning = false;
1474 bodyTrackingPublishTask->stop();
1475 }
1476 }
1477 else
1478 {
1479 ARMARX_ERROR << "Azure Kinect Body Tracking is not enabled";
1480 }
1481#endif
1482 }
1483
1484 void
1485 AzureKinectPointCloudProvider::setMaxDepthBodyTracking(int maxDepthInMM, const Ice::Current&)
1486 {
1487 std::scoped_lock lock(bodyTrackingParameterMutex);
1488 bodyTrackingDepthMaskMaxZ = maxDepthInMM;
1489 }
1490
1491 void
1493 int maxXinPixel,
1494 const Ice::Current&)
1495 {
1496 std::scoped_lock lock(bodyTrackingParameterMutex);
1497 bodyTrackingDepthMaskMinX = minXinPixel;
1498 bodyTrackingDepthMaskMaxX = maxXinPixel;
1499 }
1500
1501 // the code below is taken from https://github.com/microsoft/Azure_Kinect_ROS_Driver
1502 // -> MIT license
1503
1504 void
1506 const std::chrono::microseconds& k4a_device_timestamp_us,
1507 const std::chrono::nanoseconds& k4a_system_timestamp_ns)
1508 {
1509 // System timestamp is on monotonic system clock.
1510 // Device time is on AKDK hardware clock.
1511 // We want to continuously estimate diff between realtime and AKDK hardware clock as low-pass offset.
1512 // This consists of two parts: device to monotonic, and monotonic to realtime.
1513
1514 // First figure out realtime to monotonic offset. This will change to keep updating it.
1515 std::chrono::nanoseconds realtime_clock =
1516 std::chrono::system_clock::now().time_since_epoch();
1517 std::chrono::nanoseconds monotonic_clock =
1518 std::chrono::steady_clock::now().time_since_epoch();
1519
1520 std::chrono::nanoseconds monotonic_to_realtime = realtime_clock - monotonic_clock;
1521
1522 // Next figure out the other part (combined).
1523 std::chrono::nanoseconds device_to_realtime =
1524 k4a_system_timestamp_ns - k4a_device_timestamp_us + monotonic_to_realtime;
1525
1526 {
1527 std::lock_guard g{deviceToRealtimeOffsetMtx};
1528
1529 {
1530 std::lock_guard g{debugObserverMtx};
1531 setDebugObserverDatafield("device_to_realtime_offset [ms]",
1532 device_to_realtime_offset_.count() /
1533 1'000'000.f); // [ns] -> [ms]
1535 "clock_error",
1536 std::abs<float>((device_to_realtime_offset_ - device_to_realtime).count()) /
1537 1'000.f); // [ns] -> [µs]
1538 }
1539
1540 const std::int64_t timeOffsetThreshold = 1e7; // 10 ms
1541
1542 // If we're over a certain time off, just snap into place.
1543 if (device_to_realtime_offset_.count() == 0 ||
1544 std::abs((device_to_realtime_offset_ - device_to_realtime).count()) >
1545 timeOffsetThreshold)
1546 {
1548 << "Initializing or re-initializing the device to realtime offset: "
1549 << device_to_realtime.count() << " ns";
1550 device_to_realtime_offset_ = device_to_realtime;
1551 }
1552 else
1553 {
1554 // Low-pass filter!
1555 constexpr double alpha = 0.10;
1556
1557 const std::chrono::nanoseconds timeCorrection(static_cast<int64_t>(
1558 std::floor(alpha * (device_to_realtime - device_to_realtime_offset_).count())));
1559 device_to_realtime_offset_ = device_to_realtime_offset_ + timeCorrection;
1560
1561 {
1562 std::lock_guard g{debugObserverMtx};
1563 setDebugObserverDatafield("timeCorrection [µs]",
1564 timeCorrection.count() / 1'000); // [ns] -> [µs]
1565 }
1566 }
1567 }
1568 }
1569
1572 const std::chrono::microseconds& k4a_timestamp_us)
1573 {
1574 std::lock_guard g{deviceToRealtimeOffsetMtx};
1575
1576 // must be initialized beforehand
1577 ARMARX_CHECK(device_to_realtime_offset_.count() != 0);
1578
1579 std::chrono::nanoseconds timestamp_in_realtime =
1580 k4a_timestamp_us + device_to_realtime_offset_;
1581
1582 return armarx::Duration::MicroSeconds(timestamp_in_realtime.count() /
1583 1'000); // [ns] -> [µs]
1584 }
1585
1586 void
1588 const std::chrono::microseconds& k4a_device_timestamp_us)
1589 {
1590 std::lock_guard g{deviceToRealtimeOffsetMtx};
1591
1592 // We have no better guess than "now".
1593 std::chrono::nanoseconds realtime_clock =
1594 std::chrono::system_clock::now().time_since_epoch();
1595
1596 device_to_realtime_offset_ = realtime_clock - k4a_device_timestamp_us;
1597
1598 ARMARX_INFO << "Initializing the device to realtime offset based on wall clock: "
1599 << device_to_realtime_offset_.count() << " ns";
1600 }
1601
1606
1607 void
1609 {
1610 if (std::abs(std::fmod(higherFramerate, value)) > 1e-6)
1611 {
1612 std::stringstream ss;
1613 ss << "Invalid value (" << value << " fps) for framerate of '" << name << "'."
1614 << " Reason: The framerate has to be a divider of the property framerate.image ("
1615 << higherFramerate << " fps).";
1616 throw armarx::LocalException() << ss.str();
1617 }
1618 skipFrames = std::round(higherFramerate / value) - 1;
1619 if (skipFrames != 0)
1620 {
1621 ARMARX_INFO_S << "Only publishing every " << (skipFrames + 1) << "'th " << name
1622 << " result!";
1623 }
1624 }
1625
1626 bool
1628 {
1629 bool skip = false;
1630 if (skipFrames > 0)
1631 {
1634 skipFramesCount %= (skipFrames + 1);
1635 }
1636 return skip;
1637 }
1638
1641
1642} // namespace visionx
std::string timestamp()
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
uint8_t index
constexpr T c
Eigen::Matrix< T, 3, 3 > Matrix
static bool getAbsolutePath(const std::string &relativeFilename, std::string &storeAbsoluteFilename, const std::vector< std::string > &additionalSearchPaths={}, bool verbose=true)
static bool ReplaceEnvVars(std::string &string)
ReplaceEnvVars replaces environment variables in a string with their values, if the env.
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:88
Property< PropertyType > getProperty(const std::string &name)
static Duration MicroSeconds(std::int64_t microSeconds)
Constructs a duration in microseconds.
Definition Duration.cpp:24
static Duration MilliSeconds(std::int64_t milliSeconds)
Constructs a duration in milliseconds.
Definition Duration.cpp:48
static Frequency Hertz(std::int64_t hertz)
Definition Frequency.cpp:20
SpamFilterDataPtr deactivateSpam(float deactivationDurationSec=10.0f, const std::string &identifier="", bool deactivate=true) const
disables the logging for the current line for the given amount of seconds.
Definition Logging.cpp:99
virtual void onExitComponent()
Hook for subclass.
virtual void onDisconnectComponent()
Hook for subclass.
PluginT * addPlugin(const std::string prefix="", ParamsT &&... params)
virtual void onConnectComponent()=0
Pure virtual hook for the subclass.
std::string getName() const
Retrieve name of object.
virtual void onInitComponent()=0
Pure virtual hook for the subclass.
void setMetaInfo(const std::string &id, const VariantBasePtr &value)
Allows to set meta information that can be queried live via Ice interface on the ArmarXManager.
ArmarXManagerPtr getArmarXManager() const
Returns the ArmarX manager used to add and remove components.
std::string prefix
Prefix of the properties such as namespace, domain, component name, etc.
PropertyDefinition< PropertyType > & defineOptionalProperty(const std::string &name, PropertyType defaultValue, const std::string &description="", PropertyDefinitionBase::PropertyConstness constness=PropertyDefinitionBase::eConstant)
IceUtil::Handle< RunningTask< T > > pointer_type
Shared pointer type for convenience.
static IceUtil::Time GetTime(TimeMode timeMode=TimeMode::VirtualTime)
Get the current time.
Definition TimeUtil.cpp:42
static IceUtil::Time GetTimeSince(IceUtil::Time referenceTime, TimeMode timeMode=TimeMode::VirtualTime)
Get the difference between the current time and a reference time.
Definition TimeUtil.cpp:68
The Variant class is described here: Variants.
Definition Variant.h:224
DateTime now() const
Current date/time of the clock.
Definition Clock.cpp:22
Represents a point in time.
Definition DateTime.h:25
std::int64_t toMilliSecondsSinceEpoch() const
Definition DateTime.cpp:93
Represents a duration.
Definition Duration.h:17
Simple rate limiter for use in loops to maintain a certain frequency given a clock.
Definition Metronome.h:57
Measures the time this stop watch was inside the current scope.
Measures the passed time between the construction or calling reset() and stop().
Definition StopWatch.h:42
Brief description of class AzureKinectPointCloudProvider.
void onInitComponent() override
Pure virtual hook for the subclass.
void setMaxDepthBodyTracking(int maxDepthInMM, const Ice::Current &=Ice::emptyCurrent) override
armarx::DateTime timestampToArmarX(const std::chrono::microseconds &k4a_timestamp_us)
bool doCapture() override
Main capturing function.
void enableHumanPoseEstimation(const armarx::EnableHumanPoseEstimationInput &input, const Ice::Current &=Ice::emptyCurrent) override
void onExitCapturingPointCloudProvider() override
This is called when the Component::onExitComponent() setup is called.
void onDisconnectComponent() override
Hook for subclass.
static std::pair< int, int > GetDepthDimensions(const k4a_depth_mode_t depth_mode)
Returns the dimension of the depth images that will be produced for a certain resolution.
void onStartCapture(float frames_per_second) override
This is called when the point cloud provider capturing has been started.
static std::pair< int, int > GetColorDimensions(const k4a_color_resolution_t resolution)
Returns the dimension of the color images that will be produced for a certain resolution.
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
void onConnectImageProvider() override
This is called when the Component::onConnectComponent() setup is called.
visionx::StereoCalibration getStereoCalibration(const Ice::Current &c) override
std::string getReferenceFrame(const Ice::Current &c) override
static std::string VersionToString(const k4a_version_t &version)
Creates a string from a k4a_version_t.
std::function< void(armarx::Duration)> createSwCallback(const std::string &description)
void onInitCapturingPointCloudProvider() override
This is called when the Component::onInitComponent() is called.
void onConnectComponent() override
Pure virtual hook for the subclass.
void onStopCapture() override
This is called when the point cloud provider capturing has been stopped.
bool getImagesAreUndistorted(const ::Ice::Current &c) override
std::vector< imrec::ChannelPreferences > getImageRecordingChannelPreferences(const Ice::Current &) override
void onInitImageProvider() override
This is called when the Component::onInitComponent() is called.
void setWidthBodyTracking(int minXinPixel, int maxXinPixel, const Ice::Current &=Ice::emptyCurrent) override
void initializeTimestampOffset(const std::chrono::microseconds &k4a_device_timestamp_us)
void updateTimestampOffset(const std::chrono::microseconds &k4a_device_timestamp_us, const std::chrono::nanoseconds &k4a_system_timestamp_ns)
std::atomic_bool captureEnabled
Indicates that capturing is enabled and running.
void setPointCloudSyncMode(ImageSyncMode pointCloudSyncMode)
Sets the point cloud synchronization mode.
void onInitComponent() override
void onDisconnectComponent() override
Hook for subclass.
ImageFormatInfo getImageFormat(const Ice::Current &c=Ice::emptyCurrent) override
Returns the entire image format info struct via Ice.
void setImageFormat(ImageDimension imageDimension, ImageType imageType, BayerPatternType bayerPatternType=visionx::eBayerPatternRg)
Sets the image basic format data.
void provideImages(void **inputBuffers, const IceUtil::Time &imageTimestamp=IceUtil::Time())
send images raw.
void onConnectComponent() override
void setNumberImages(int numberImages)
Sets the number of images on each capture.
void onExitComponent() override
MetaPointCloudFormatPtr getPointCloudFormat(const Ice::Current &c=Ice::emptyCurrent) override
Returns the point cloud format info struct via Ice.
void providePointCloud(PointCloudPtrT pointCloudPtr)
offer the new point cloud.
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_CHECK_EQUAL(lhs, rhs)
This macro evaluates whether lhs is equal (==) rhs and if it turns out to be false it will throw an E...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:194
#define ARMARX_INFO_S
Definition Logging.h:200
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:182
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:185
Quaternion< float, 0 > Quaternionf
const simox::meta::EnumNames< Joints > JointNames
Names of the joints as defined in the body model.
Joints
Joints with index as defined in the body model.
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::string GetHandledExceptionString()
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
void convert(const CByteImage &in, cv::Mat &out)
Converts an IVT CByteImage to OpenCV's BGR Mat.
Definition helper.cpp:40
void convert_rgb2cbi(const cv::Mat &in, CByteImage &out)
Converts an OpenCV RGB Mat to IVT's CByteImage.
Definition helper.cpp:64
void depthValueToRGB(unsigned int depthInMM, unsigned char &r, unsigned char &g, unsigned char &b, bool noiseResistant=false)
Definition ImageUtil.h:183
CByteImage * createByteImage(const ImageFormatInfo &imageFormat, const ImageType imageType)
Creates a ByteImage for the destination type specified in the given imageProviderInfo.
visionx::types::Mat convertEigenMatToVisionX(Eigen::MatrixXf m)
CByteImage::ImageType convert(const ImageType visionxImageType)
Converts a VisionX image type into an image type of IVT's ByteImage.
ArmarX headers.
std::optional< std::string > humanTrackingId
Definition types.h:47
#define ARMARX_TRACE
Definition trace.h:75