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
363 this,
365 pointcloudTask->start();
366
367#ifdef INCLUDE_BODY_TRACKING
368 if (bodyTrackingEnabled)
369 {
370 ARMARX_INFO << "Connecting to human memory ...";
371 humanPoseWriter.connect(memoryNameSystem());
372 ARMARX_INFO << "Connected to human memory.";
373
374 bodyTrackingPublishTask = new armarx::RunningTask<AzureKinectPointCloudProvider>(
375 this,
376 &AzureKinectPointCloudProvider::runPublishBodyTrackingResults);
377 }
378#endif
379
380 if (enableHeartbeat)
381 {
382 ARMARX_CHECK_NOT_NULL(heartbeatPlugin);
383 heartbeatPlugin->signUp(armarx::Duration::MilliSeconds(500),
385 {"Vision", "Camera"},
386 "AzureKinectPointCloudProvider");
387 }
388
389 ARMARX_VERBOSE << "Connected " << getName();
390 }
391
392 void
394 {
395 ARMARX_DEBUG << "Disconnecting " << getName();
396
397 // Stop task
398 {
399 std::lock_guard<std::mutex> lock(pointcloudProcMutex);
400 ARMARX_DEBUG << "Stopping pointcloud processing thread...";
401 const bool WAIT_FOR_JOIN = false;
402 pointcloudTask->stop(WAIT_FOR_JOIN);
403 pointcloudProcSignal.notify_all();
404 ARMARX_DEBUG << "Waiting for pointcloud processing thread to stop...";
405 pointcloudTask->waitForStop();
406 ARMARX_DEBUG << "Pointcloud processing thread stopped";
407 }
408
409#ifdef INCLUDE_BODY_TRACKING
410 if (bodyTrackingIsRunning)
411 {
412 bodyTrackingPublishTask->stop();
413 }
414#endif
415
416 ARMARX_DEBUG << "Disconnected " << getName();
417 }
418
419 void
425
426 void
428 {
429 ARMARX_INFO << "Closing Azure Kinect device";
430 device.close();
431 ARMARX_INFO << "Closed Azure Kinect device";
432 }
433
434 void
436 {
438
439 cloudFormat = getPointCloudFormat();
440
441 // Check for devices.
442 const uint32_t DEVICE_COUNT = k4a::device::get_installed_count();
443 if (DEVICE_COUNT == 0)
444 {
445 getArmarXManager()->asyncShutdown();
446 throw armarx::LocalException("No Azure Kinect devices detected!");
447 }
448
450
451 device = k4a::device::open(static_cast<uint32_t>(mDeviceId));
452 ARMARX_DEBUG << "Opened device id #" << mDeviceId << " with serial number "
453 << device.get_serialnum() << ".";
454
456
457 auto depthDim = GetDepthDimensions(config.depth_mode);
458 auto colorDim = GetColorDimensions(config.color_resolution);
459 (void)depthDim;
460 (void)colorDim;
461
462 k4aCalibration = device.get_calibration(config.depth_mode, config.color_resolution);
464 << "Color camera calibration:"
465 << "\n"
466 << "cx: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.cx
467 << "\n"
468 << "cy: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.cy
469 << "\n"
470 << "fx: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.fx
471 << "\n"
472 << "fy: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.fy
473 << "\n"
474 << "k1: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.k1
475 << "\n"
476 << "k2: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.k2
477 << "\n"
478 << "p1: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.p1
479 << "\n"
480 << "p2: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.p2
481 << "\n"
482 << "k3: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.k3
483 << "\n"
484 << "k4: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.k4
485 << "\n"
486 << "k5: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.k5
487 << "\n"
488 << "k6: " << k4aCalibration.color_camera_calibration.intrinsics.parameters.param.k6;
489
490
491 auto c = getStereoCalibration(Ice::Current());
492 CCalibration* c_left = tools::convert(c.calibrationLeft);
493 c_left->PrintCameraParameters();
494 delete c_left;
495 CCalibration* c_right = tools::convert(c.calibrationRight);
496 c_right->PrintCameraParameters();
497 delete c_right;
498
499 transformation = k4a::transformation(k4aCalibration);
500
501#ifdef INCLUDE_BODY_TRACKING
502 if (bodyTrackingEnabled)
503 {
504 // eventually, resolve environment variable
505 armarx::ArmarXDataPath::ReplaceEnvVars(bodyTrackingModelFilename);
506
507 const bool found = armarx::ArmarXDataPath::getAbsolutePath(bodyTrackingModelFilename,
508 bodyTrackingModelFilename);
509 ARMARX_CHECK(found) << "Body tracking DNN model could not be found/resolved at `"
510 << bodyTrackingModelFilename << "`.";
511
512 ARMARX_INFO << "Using body tracking DNN model from directory `"
513 << bodyTrackingModelFilename << "`.";
514
515 ARMARX_CHECK(std::filesystem::exists(bodyTrackingModelFilename))
516 << "The path `" << bodyTrackingModelFilename << "` does not exist!";
517
518 k4abt_tracker_configuration_t const bodyTrackingConfig{
519 .sensor_orientation = K4ABT_SENSOR_ORIENTATION_DEFAULT,
520 .processing_mode = useCPU ? K4ABT_TRACKER_PROCESSING_MODE_CPU
521 : K4ABT_TRACKER_PROCESSING_MODE_GPU_CUDA,
522 .gpu_device_id = bodyTrackingGPUDeviceID,
523 .model_path = bodyTrackingModelFilename.c_str()};
524
525 bodyTracker = k4abt::tracker::create(k4aCalibration, bodyTrackingConfig);
526 bodyTracker.set_temporal_smoothing(bodyTrackingTemporalSmoothingFactor);
527
528 if (bodyTrackingRunAtStart and not bodyTrackingIsRunning)
529 {
530 bodyTrackingIsRunning = true;
531 bodyTrackingPublishTask->start();
532 }
533 }
534#endif
535
537 device.set_color_control(K4A_COLOR_CONTROL_BRIGHTNESS, K4A_COLOR_CONTROL_MODE_MANUAL, 128);
538
539 device.start_cameras(&config);
540
542
543 if (startIMU)
544 {
545 device.start_imu();
546 ARMARX_INFO << "IMU is active" << std::endl;
547
549 }
550
551 setMetaInfo("serialNumber", new armarx::Variant(device.get_serialnum()));
552 setMetaInfo("rgbVersion", new armarx::Variant(VersionToString(device.get_version().rgb)));
553 setMetaInfo("depthVersion",
554 new armarx::Variant(VersionToString(device.get_version().depth)));
555 setMetaInfo("depthSensorVersion",
556 new armarx::Variant(VersionToString(device.get_version().depth_sensor)));
557 setMetaInfo("audioVersion",
558 new armarx::Variant(VersionToString(device.get_version().audio)));
559
561
562
563 // Color image calibration
564 {
565 // Load intrinsics from camera
566 const k4a_calibration_camera_t calibration{k4aCalibration.color_camera_calibration};
567 const k4a_calibration_intrinsic_parameters_t::_param param =
568 calibration.intrinsics.parameters.param;
569
570 // // Scale intrinsics according to image scale
571 // param.fx *= image_scale_;
572 // param.fy *= image_scale_;
573
574 // Calculate distortion map
575 cv::Mat1f camera_matrix(3, 3);
576 camera_matrix << param.fx, 0, param.cx, 0, param.fy, param.cy, 0, 0, 1;
577 cv::Mat1f new_camera_matrix(3, 3);
578 new_camera_matrix << param.fx, 0, param.cx, 0, param.fx, param.cy, 0, 0, 1;
579 cv::Mat1f distortion_coeff(1, 8);
580 distortion_coeff << param.k1, param.k2, param.p1, param.p2, param.k3, param.k4,
581 param.k5, param.k6;
582 cv::Mat map1, map2, map3;
583 cv::initUndistortRectifyMap(
584 camera_matrix,
585 distortion_coeff,
586 cv::Mat{},
587 new_camera_matrix,
588 cv::Size{calibration.resolution_width, calibration.resolution_height},
589 CV_32FC1,
590 map1,
591 map2);
592 cv::convertMaps(map1, map2, colorDistortionMap, map3, CV_16SC2, true);
593 }
594 }
595
596 void
598 {
599 if (startIMU)
600 {
601 device.stop_imu();
602 }
603 }
604
605 bool
607 {
610
611 ScopedStopWatch sw_total{createSwCallback("doCapture")};
612
613 k4a::capture capture;
614 const std::chrono::milliseconds TIMEOUT{1000};
615
616 StopWatch sw_get_capture;
617 bool status = false;
618 try
619 {
620 ARMARX_DEBUG << "Try capture.";
621 status = device.get_capture(&capture, TIMEOUT);
622 ARMARX_DEBUG << "Got capture.";
623 }
624 catch (const std::exception&)
625 {
626 ARMARX_WARNING << "Failed to get capture from device (#" << ++mDiagnostics.num_crashes
627 << "). Restarting camera.";
628 StopWatch sw;
629 device.stop_cameras();
630 device.start_cameras(&config);
631 ARMARX_INFO << "Restarting took " << sw.stop() << ".";
632 return false;
633 }
634
635 if (status)
636 {
637 createSwCallback("waiting for get_capture")(sw_get_capture.stop());
638
639 {
640 std::lock_guard g{metaInfoMtx};
641 setMetaInfo("temperature", new armarx::Variant(capture.get_temperature_c()));
642 }
643
644 // see ROS: This will give INCORRECT timestamps until the first image.
645 const bool mustInitializeTimestampOffset = [&]()
646 {
647 std::lock_guard g{deviceToRealtimeOffsetMtx};
648 return device_to_realtime_offset_.count() == 0;
649 }();
650 if (mustInitializeTimestampOffset)
651 {
652 initializeTimestampOffset(capture.get_depth_image().get_device_timestamp());
653 }
654
655 // next, we update the timestamp offset continuously
656 updateTimestampOffset(capture.get_ir_image().get_device_timestamp(),
657 capture.get_ir_image().get_system_timestamp());
658
659 const k4a::image DEPTH_IMAGE = capture.get_depth_image();
660
661 // This function assumes that the image is made of depth pixels (i.e. uint16_t's),
662 // which is only true for IR/depth images.
663 const k4a_image_format_t IMAGE_FORMAT = DEPTH_IMAGE.get_format();
664 if (IMAGE_FORMAT != K4A_IMAGE_FORMAT_DEPTH16 && IMAGE_FORMAT != K4A_IMAGE_FORMAT_IR16)
665 {
666 const char* format_str = ::k4aImageFormatToString(IMAGE_FORMAT);
667 std::stringstream error_msg;
668 error_msg << "Attempted to colorize a non-depth image with format: " << format_str;
669 throw std::logic_error(error_msg.str());
670 }
671
672 // Provide data for pointcloud processing thread and signal to start processing.
673 if (not framerate.pointCloud.skip())
674 {
675 ScopedStopWatch sw{createSwCallback("transform depth image to camera")};
676 // Acquire lock and write data needed by pointcloud thread (i.e.,
677 // alignedDepthImageScaled and depthImageReady).
678 {
679 std::lock_guard lock{pointcloudProcMutex};
680 transformation.depth_image_to_color_camera(DEPTH_IMAGE, &alignedDepthImage);
681
682 // Signal that point cloud processing may proceed and reset processed flag.
683 depthImageReady = true;
684 }
685
686 ARMARX_DEBUG << "Notifying pointcloud thread.";
687 pointcloudProcSignal.notify_one();
688 }
689
690#ifdef INCLUDE_BODY_TRACKING
691 if (bodyTrackingIsRunning)
692 {
693 ARMARX_DEBUG << "body Tracking Is Running.";
694 std::scoped_lock lock(bodyTrackingParameterMutex);
695
696 if (not framerate.bodyTracking.skip())
697 {
698 k4a::image ir_image = capture.get_ir_image();
699 std::uint8_t* ir_image_buffer = ir_image.get_buffer();
700
701 k4a::image depth_image = capture.get_depth_image();
702 std::uint8_t* depth_image_buffer = depth_image.get_buffer();
703
704 ARMARX_CHECK_EQUAL(ir_image.get_width_pixels(), depth_image.get_width_pixels());
705 ARMARX_CHECK_EQUAL(ir_image.get_height_pixels(),
706 depth_image.get_height_pixels());
707
708 const int stride = ir_image.get_stride_bytes() / ir_image.get_width_pixels();
709
710 for (int x = 0; x < ir_image.get_width_pixels(); x++)
711 {
712 for (int y = 0; y < ir_image.get_height_pixels(); y++)
713 {
714 const int i = (y * ir_image.get_width_pixels() * stride) + (x * stride);
715 const int z = (static_cast<int>(depth_image_buffer[i])) +
716 (static_cast<int>(depth_image_buffer[i + 1]) << 8);
717
718 if ((bodyTrackingDepthMaskMinX > 0 and x < bodyTrackingDepthMaskMinX) or
719 (bodyTrackingDepthMaskMaxX > 0 and x > bodyTrackingDepthMaskMaxX) or
720 (bodyTrackingDepthMaskMaxZ > 0 and z > bodyTrackingDepthMaskMaxZ))
721 {
722 ir_image_buffer[i] = std::numeric_limits<std::uint8_t>::max();
723 ir_image_buffer[i + 1] = std::numeric_limits<std::uint8_t>::max();
724 depth_image_buffer[i] = std::numeric_limits<std::uint8_t>::max();
725 depth_image_buffer[i + 1] =
726 std::numeric_limits<std::uint8_t>::max();
727 }
728 }
729 }
730
731 if (not bodyTracker.enqueue_capture(capture))
732 {
733 ARMARX_WARNING << "Add capture to tracker process queue timeout";
734 }
735 }
736 }
737#endif
738
739 const k4a::image COLOR_IMAGE = capture.get_color_image();
740 ARMARX_DEBUG << "Got COLOR image.";
741
742 auto real_time = IceUtil::Time::now();
743 auto monotonic_time = IceUtil::Time::now(IceUtil::Time::Monotonic);
744 auto clock_diff = real_time - monotonic_time;
745
746 auto image_monotonic_time =
747 IceUtil::Time::microSeconds(std::chrono::duration_cast<std::chrono::microseconds>(
748 DEPTH_IMAGE.get_system_timestamp())
749 .count());
750 long offset = long(getProperty<float>("CaptureTimeOffset").getValue() * 1000.0f);
751
752 imagesTime = image_monotonic_time + clock_diff - IceUtil::Time::microSeconds(offset);
753
754 {
755 std::lock_guard g{metaInfoMtx};
756 setMetaInfo("image age in [ms]",
757 new armarx::Variant{(real_time - imagesTime).toMilliSecondsDouble()});
758 }
759
760 {
761 std::lock_guard g{debugObserverMtx};
762 setDebugObserverDatafield("image age in [ms]",
763 (real_time - imagesTime).toMilliSecondsDouble());
764 }
765
766 if (enableColorUndistortion)
767 {
768 // only the color image needs to be rectified
769 cv::Mat tmp_rgb_image;
770
771 if (COLOR_IMAGE.get_format() == K4A_IMAGE_FORMAT_COLOR_YUY2)
772 {
773 ARMARX_DEBUG << "Converting YUY2 image.";
774 cv::Mat yuy2_image(COLOR_IMAGE.get_height_pixels(),
775 COLOR_IMAGE.get_width_pixels(),
776 CV_8UC2,
777 const_cast<uint8_t*>(COLOR_IMAGE.get_buffer()));
778
779 cv::cvtColor(yuy2_image, tmp_rgb_image, cv::COLOR_YUV2RGB_YUY2);
780 }
781 else if (COLOR_IMAGE.get_format() == K4A_IMAGE_FORMAT_COLOR_BGRA32)
782 {
783 cv::cvtColor(cv::Mat{cv::Size{COLOR_IMAGE.get_width_pixels(),
784 COLOR_IMAGE.get_height_pixels()},
785 CV_8UC4,
786 (void*)COLOR_IMAGE.get_buffer(),
787 cv::Mat::AUTO_STEP},
788 tmp_rgb_image,
789 cv::COLOR_BGRA2RGB);
790 }
791 else
792 {
793 throw std::runtime_error("Unsupported color format in k4a image");
794 }
795
796 cv::Mat cv_color_image_undistorted(COLOR_IMAGE.get_width_pixels(),
797 COLOR_IMAGE.get_height_pixels(),
798 CV_8UC3);
799
800 cv::remap(tmp_rgb_image,
801 cv_color_image_undistorted,
802 colorDistortionMap,
803 cv::Mat{},
804 cv::INTER_NEAREST,
805 cv::BORDER_CONSTANT);
806
807 visionx::imrec::convert_rgb2cbi(cv_color_image_undistorted, *resultColorImage);
808 }
809 else
810 {
811 // Convert the k4a image to an IVT image.
812 ScopedStopWatch sw{createSwCallback("convert k4a image to IVT")};
813 ::k4aToIvtImage(COLOR_IMAGE, *resultColorImage);
814 }
815
816
817 // Prepare result depth image.
818 {
819 ScopedStopWatch sw{createSwCallback("prepare result depth image")};
820
821 const int DW = alignedDepthImage.get_width_pixels();
822 const int DH = alignedDepthImage.get_height_pixels();
823
824 ARMARX_CHECK_EXPRESSION(resultDepthImage);
825 ARMARX_CHECK_EQUAL(resultDepthImage->width, DW);
826 ARMARX_CHECK_EQUAL(resultDepthImage->height, DH);
827
828 auto result_depth_buffer = resultDepthImage->pixels;
829 const auto* depth_buffer =
830 reinterpret_cast<const uint16_t*>(alignedDepthImage.get_buffer());
831
832 int index = 0;
833 int index_2 = 0;
834 for (int y = 0; y < DH; ++y)
835 {
836 for (int x = 0; x < DW; ++x)
837 {
838 uint16_t depth_value = depth_buffer[index_2];
840 result_depth_buffer[index],
841 result_depth_buffer[index + 1],
842 result_depth_buffer[index + 2]);
843 index += 3;
844 index_2 += 1;
845 }
846 }
847 }
848
849 // Broadcast RGB-D image.
850 {
851 ScopedStopWatch sw{createSwCallback("broadcast RGB-D image")};
852 CByteImage* images[2] = {resultColorImage.get(), resultDepthImage.get()};
853 provideImages(images, imagesTime);
854 }
855
856 // Wait until `alignedDepthImage` was processed and can be overridden again.
857 {
858 std::unique_lock signal_lock{pointcloudProcMutex};
859 ARMARX_DEBUG << "Capturing thread waiting for signal...";
860 pointcloudProcSignal.wait(signal_lock, [&] { return depthImageProcessed; });
861 ARMARX_DEBUG << "Capturing thread received signal.";
862 }
863
864 {
865 std::lock_guard g{debugObserverMtx};
867 }
868
869 if (enableHeartbeat)
870 {
871 heartbeatPlugin->heartbeat();
872 }
873
874 // return true;
875 }
876 else
877 {
878 ARMARX_INFO << deactivateSpam(30) << "Did not get frame until timeout of " << TIMEOUT;
879
880 {
881 std::lock_guard g{debugObserverMtx};
883 }
884
885 return false;
886 }
887
888 if (startIMU)
889 {
890 try
891 {
892 Eigen::Vector3f accMean = Eigen::Vector3f::Zero();
893 std::size_t cnt = 0;
894
895 k4a_imu_sample_t imu_sample;
896 while (device.get_imu_sample(&imu_sample, std::chrono::milliseconds(0)))
897 {
898 const Eigen::Vector3f acceleration{imu_sample.acc_sample.xyz.x,
899 imu_sample.acc_sample.xyz.y,
900 imu_sample.acc_sample.xyz.z};
901
902 accMean += acceleration;
903 cnt++;
904 }
905
906 accMean /= static_cast<float>(cnt);
907
908 ARMARX_INFO << "Got IMU sample";
909
910 {
911 std::lock_guard g{debugObserverMtx};
912
913 setDebugObserverDatafield("acceleration.x", accMean.x());
914 setDebugObserverDatafield("acceleration.y", accMean.y());
915 setDebugObserverDatafield("acceleration.z", accMean.z());
916
918 }
919 }
920 catch (const std::exception&)
921 {
922 ARMARX_WARNING << "Failed to get imu samples from device (#"
923 << ++mDiagnostics.num_crashes << ").";
924 }
925 }
926
927 return true;
928 }
929
930#ifdef INCLUDE_BODY_TRACKING
931 void
932 AzureKinectPointCloudProvider::runPublishBodyTrackingResults()
933 {
935 while (bodyTrackingIsRunning)
936 {
937
938 // body frames might just not be available.
939 const k4abt::frame body_frame = [&]() -> k4abt::frame
940 {
941 try
942 {
943 auto result = bodyTracker.pop_result();
944 return result;
945 }
946 catch (...)
947 {
948 ARMARX_VERBOSE << deactivateSpam(1) << "Exception in body tracking publishing: "
950 return {};
951 }
952 }();
953
954 if (body_frame != nullptr)
955 {
956 armarx::core::time::ScopedStopWatch sw{
957 createSwCallback("publish body tracking result")};
958 // see https://github.com/microsoft/Azure_Kinect_ROS_Driver/blob/melodic/src/k4a_ros_device.cpp
959 const armarx::DateTime timestamp =
960 timestampToArmarX(body_frame.get_device_timestamp());
961
962 {
963 auto real_time = IceUtil::Time::now();
964 auto monotonic_time = IceUtil::Time::now(IceUtil::Time::Monotonic);
965 auto clock_diff = real_time - monotonic_time;
966
967 auto image_monotonic_time = IceUtil::Time::microSeconds(
968 std::chrono::duration_cast<std::chrono::microseconds>(
969 body_frame.get_system_timestamp())
970 .count());
971 // long offset = long(getProperty<float>("CaptureTimeOffset").getValue() * 1000.0f);
972
973 // IceUtil::Time imageTime = image_monotonic_time + clock_diff - IceUtil::Time::microSeconds(offset);
974 IceUtil::Time imageTime = image_monotonic_time + clock_diff;
975
976 {
977 std::lock_guard g{debugObserverMtx};
978 setDebugObserverDatafield("ros_vs_ice_timestamp [µs]",
979 imageTime.toMicroSeconds());
980 }
981 }
982
983 {
984 const armarx::Clock realtimeClock = armarx::Clock(armarx::ClockType::Realtime);
985 const armarx::Clock monotonicClock =
986 armarx::Clock(armarx::ClockType::Monotonic);
987
988 auto real_time = realtimeClock.now();
989 auto monotonic_time = monotonicClock.now();
990 auto clock_diff = real_time - monotonic_time;
991
992 auto image_monotonic_time = armarx::Duration::MicroSeconds(
993 std::chrono::duration_cast<std::chrono::microseconds>(
994 body_frame.get_system_timestamp())
995 .count());
996 // long offset = long(getProperty<float>("CaptureTimeOffset").getValue() * 1000.0f);
997
998 // IceUtil::Time imageTime = image_monotonic_time + clock_diff - IceUtil::Time::microSeconds(offset);
999 auto imageTime = image_monotonic_time + clock_diff;
1000
1001 armarx::DateTime imageTimestamp = armarx::DateTime(imageTime);
1002
1003 {
1004 std::lock_guard g{debugObserverMtx};
1005 setDebugObserverDatafield("ros_vs_armarx_timestamp [µs]",
1006 imageTime.toMicroSeconds());
1007 }
1008 }
1009
1010
1011 std::uint32_t num_bodies = body_frame.get_num_bodies();
1012 {
1013 std::lock_guard g{debugObserverMtx};
1014 setDebugObserverDatafield("n_bodies_detected", num_bodies);
1015 }
1016
1017 std::vector<armarx::armem::human::HumanPose> humanPoses;
1018 humanPoses.reserve(num_bodies);
1019
1020 for (std::uint32_t i = 0; i < num_bodies; i++)
1021 {
1022 k4abt_body_t body = body_frame.get_body(i);
1023 printBodyInformation(body);
1024
1025 armarx::armem::human::HumanPose humanPose;
1026 humanPose.humanTrackingId = std::to_string(body.id);
1027 humanPose.cameraFrameName = bodyCameraFrameName;
1029
1030 for (int i = 0; i < static_cast<int>(K4ABT_JOINT_COUNT); i++)
1031 {
1032 const auto joint =
1034 const auto name =
1036
1037 k4a_float3_t position = body.skeleton.joints[i].position;
1038 k4a_quaternion_t orientation = body.skeleton.joints[i].orientation;
1039 k4abt_joint_confidence_level_t confidence_level =
1040 body.skeleton.joints[i].confidence_level;
1041
1042 humanPose.keypoints[name] = armarx::armem::human::PoseKeypoint{
1043 .label = name,
1044 .confidence = static_cast<float>(static_cast<int>(confidence_level)),
1045 .positionCamera = armarx::FramedPosition(
1046 Eigen::Vector3f{position.v[0], position.v[1], position.v[2]},
1047 bodyCameraFrameName,
1048 robotName),
1049 .orientationCamera =
1050 armarx::FramedOrientation(Eigen::Quaternionf(orientation.wxyz.w,
1051 orientation.wxyz.x,
1052 orientation.wxyz.y,
1053 orientation.wxyz.z)
1054 .toRotationMatrix(),
1055 bodyCameraFrameName,
1056 robotName)};
1057 }
1058
1059 humanPoses.push_back(humanPose);
1060 }
1061
1062 {
1063 std::lock_guard g{debugObserverMtx};
1064 setDebugObserverDatafield("bodyTrackingCaptureUntilPublish [ms]",
1065 (armarx::Clock::Now() - timestamp).toMilliSeconds());
1066 }
1067
1068
1069 {
1071 //ARMARX_INFO << deactivateSpam(1) << "committing human poses";
1072 humanPoseWriter.commitHumanPosesInCameraFrame(humanPoses, getName(), timestamp);
1073 }
1074
1075
1076 // k4a::image body_index_map = body_frame.get_body_index_map();
1077 // if (body_index_map != nullptr)
1078 // {
1079 // //print_body_index_map_middle_line(body_index_map);
1080 // }
1081 // else
1082 // {
1083 // ARMARX_WARNING << "Error: Failed to generate bodyindex map!";
1084 // }
1085 }
1086 else
1087 {
1088 // It should never hit timeout when K4A_WAIT_INFINITE is set.
1089 ARMARX_VERBOSE << "Error! Pop body frame result time out!";
1090 }
1091
1092 metronome.waitForNextTick();
1093 }
1094 }
1095#endif
1096
1097 void
1099 {
1100 ARMARX_DEBUG << "Started pointcloud processing task.";
1101
1102 IceUtil::Time time;
1103
1104 // Main pointcloud processing loop.
1105 while (not pointcloudTask->isStopped())
1106 {
1107 // Wait for data.
1108 std::unique_lock signal_lock{pointcloudProcMutex};
1109 ARMARX_DEBUG << "Pointcloud thread waiting for signal...";
1110 pointcloudProcSignal.wait(signal_lock,
1111 [&]
1112 { return pointcloudTask->isStopped() or depthImageReady; });
1113 ARMARX_DEBUG << "Pointcloud thread received signal.";
1114
1115 // Assess timings, reset flags.
1116 const IceUtil::Time TIMESTAMP = imagesTime;
1117 depthImageReady = false;
1118 depthImageProcessed = false;
1119
1120 // Transform depth image to pointcloud.
1121 {
1123 transformation.depth_image_to_point_cloud(alignedDepthImage,
1124 K4A_CALIBRATION_TYPE_COLOR,
1125 &xyzImage);
1126 ARMARX_DEBUG << "Transforming depth image to point cloud took "
1128 }
1129
1130 // Construct PCL pointcloud.
1131 {
1133
1134 ARMARX_CHECK_EXPRESSION(pointcloud);
1135
1136 pointcloud->width = static_cast<uint32_t>(xyzImage.get_width_pixels());
1137 pointcloud->height = static_cast<uint32_t>(xyzImage.get_height_pixels());
1138
1140
1141 pointcloud->is_dense = false;
1142 pointcloud->points.resize(pointcloud->width * pointcloud->height);
1143
1144 auto k4a_cloud_buffer = reinterpret_cast<const int16_t*>(xyzImage.get_buffer());
1145
1146 ARMARX_CHECK_EXPRESSION(k4a_cloud_buffer);
1147
1148 unsigned char* color_buffer = resultColorImage->pixels;
1149 while (color_buffer == nullptr)
1150 {
1151 ARMARX_INFO << "color_buffer is null. This should never happen. There is "
1152 "probably a race condition somewhere that needs to be fixed. "
1153 "Temporarily we ignore this and continue.\n Timestamp: "
1155 color_buffer = resultColorImage->pixels;
1156 }
1157 ARMARX_CHECK_NOT_NULL(color_buffer);
1158
1159 ARMARX_CHECK_EQUAL(xyzImage.get_width_pixels(), resultColorImage->width);
1160 ARMARX_CHECK_EQUAL(xyzImage.get_height_pixels(), resultColorImage->height);
1161 ARMARX_CHECK_EQUAL(pointcloud->points.size(),
1162 pointcloud->width * pointcloud->height);
1163
1165
1166 size_t index = 0;
1167 float max_depth = getProperty<float>("MaxDepth").getValue();
1168 for (auto& p : pointcloud->points)
1169 {
1170 p.r = color_buffer[index];
1171 p.x = k4a_cloud_buffer[index];
1172
1173 index++;
1174
1175 p.g = color_buffer[index];
1176 p.y = k4a_cloud_buffer[index];
1177
1178 index++;
1179
1180 p.b = color_buffer[index];
1181 auto z = k4a_cloud_buffer[index];
1182
1183 index++;
1184
1185 if (z <= max_depth and z != 0)
1186 {
1187 p.z = z;
1188 }
1189 else
1190 {
1191 p.z = std::numeric_limits<float>::quiet_NaN();
1192 }
1193 }
1194
1195 ARMARX_DEBUG << "Constructing point cloud took "
1197 }
1198
1199 // Notify capturing thread that data was processed and may be overridden.
1200 depthImageProcessed = true;
1201 signal_lock.unlock();
1202 ARMARX_DEBUG << "Notifying capturing thread...";
1203 pointcloudProcSignal.notify_all();
1204
1206
1207 // Broadcast PCL pointcloud.
1208 {
1210 pointcloud->header.stamp = static_cast<unsigned long>(TIMESTAMP.toMicroSeconds());
1211 providePointCloud(pointcloud);
1212 ARMARX_DEBUG << "Broadcasting pointcloud took "
1214 }
1215 }
1216
1217 ARMARX_DEBUG << "Stopped pointcloud processing task.";
1218 }
1219
1220 std::string
1225
1226 std::string
1228 {
1229 return "AzureKinectPointCloudProvider";
1230 }
1231
1232 void
1238
1239 void
1245
1246 void
1254
1255 void
1261
1262 StereoCalibration
1264 {
1265 using namespace Eigen;
1266 using Matrix3FRowMajor = Matrix<float, 3, 3, StorageOptions::RowMajor>;
1268
1269 // TODO: use the externally used cameraMatrix and distCoeffs
1270 const auto convert_calibration =
1271 [](const k4a_calibration_camera_t& k4a_calib, float scale = 1.f)
1272 {
1273 MonocularCalibration monocular_calibration;
1274
1275 const k4a_calibration_intrinsic_parameters_t& params = k4a_calib.intrinsics.parameters;
1276 monocular_calibration.cameraParam.principalPoint = {params.param.cx * scale,
1277 params.param.cy * scale};
1278 monocular_calibration.cameraParam.focalLength = {params.param.fx * scale,
1279 params.param.fy * scale};
1280 // TODO: Figure out convertions. IVT (Calibration.h) expects 4 parameters:
1281 // - The first radial lens distortion parameter.
1282 // - The second radial lens distortion parameter.
1283 // - The first tangential lens distortion parameter.
1284 // - The second tangential lens distortion parameter.
1285 // However, the Kinect offers k1-k6 radial distortion coefficients and 2 p1-p2
1286 // tangential distortion parameters, which means that k3-k6 are unused.
1287 // It is even unclear whether this is correct now, as previously it was a vector of all
1288 // 6 k-params, which resulted in failed assertions in TypeMapping.cpp in the function
1289 // CStereoCalibration* visionx::tools::convert(const visionx::StereoCalibration& stereoCalibration)
1290 // lin 314 at time of this commit.
1291 // See: https://microsoft.github.io/Azure-Kinect-Sensor-SDK/master/structk4a__calibration__intrinsic__parameters__t_1_1__param.html
1292 monocular_calibration.cameraParam.distortion = {
1293 params.param.k1,
1294 params.param.k2,
1295 params.param.p1,
1296 params.param.p2,
1297 };
1298
1299 // TODO this needs to be scaled! Or why shouldn't it ?
1300 monocular_calibration.cameraParam.width =
1301 std::floor(float(k4a_calib.resolution_width) * scale);
1302 monocular_calibration.cameraParam.height =
1303 std::floor(float(k4a_calib.resolution_height) * scale);
1304
1305 const Matrix3FRowMajor rotation =
1306 Map<const Matrix3FRowMajor>{k4a_calib.extrinsics.rotation};
1307
1308 monocular_calibration.cameraParam.rotation = convertEigenMatToVisionX(rotation);
1309 monocular_calibration.cameraParam.translation = {k4a_calib.extrinsics.translation,
1310 k4a_calib.extrinsics.translation + 3};
1311
1312 return monocular_calibration;
1313 };
1314
1315 StereoCalibration stereo_calibration;
1316
1317 stereo_calibration.calibrationLeft =
1318 convert_calibration(k4aCalibration.color_camera_calibration);
1319
1320 // if the camera images are rectified, the distortion params are 0
1321 if (enableColorUndistortion)
1322 {
1323 auto& colorDistortionParams = stereo_calibration.calibrationLeft.cameraParam.distortion;
1324 std::fill(colorDistortionParams.begin(), colorDistortionParams.end(), 0);
1325 }
1326
1327 // the depth image is rectified by default
1328 {
1329 // The depth image has been warped to to color camera frame. See depth_image_to_color_camera() above.
1330 // Therefore we use the calibration of the color camera (focal length etc) and set the distortion parameters to 0.
1331 stereo_calibration.calibrationRight =
1332 convert_calibration(k4aCalibration.color_camera_calibration);
1333
1334 auto& depthDistortionParams =
1335 stereo_calibration.calibrationRight.cameraParam.distortion;
1336 std::fill(depthDistortionParams.begin(), depthDistortionParams.end(), 0);
1337 }
1338
1339 stereo_calibration.rectificationHomographyLeft =
1340 convertEigenMatToVisionX(Matrix3f::Identity());
1341 stereo_calibration.rectificationHomographyRight =
1342 convertEigenMatToVisionX(Matrix3f::Identity());
1343
1344 return stereo_calibration;
1345 }
1346
1347 bool
1349 {
1350 return enableColorUndistortion;
1351 }
1352
1353 std::string
1355 {
1356 return getProperty<std::string>("frameName");
1357 }
1358
1359 std::vector<imrec::ChannelPreferences>
1361 {
1363
1364 imrec::ChannelPreferences rgb;
1365 rgb.requiresLossless = false;
1366 rgb.name = "rgb";
1367
1368 imrec::ChannelPreferences depth;
1369 depth.requiresLossless = true;
1370 depth.name = "depth";
1371
1372 return {rgb, depth};
1373 }
1374
1375 void
1377 const armarx::EnableHumanPoseEstimationInput& input,
1378 const Ice::Current&)
1379 {
1380#ifndef INCLUDE_BODY_TRACKING
1381 {
1382 ARMARX_ERROR << "INCLUDE_BODY_TRACKING is not defined.";
1383 return;
1384 }
1385#endif
1386
1387#ifdef INCLUDE_BODY_TRACKING
1388 if (bodyTrackingEnabled)
1389 {
1390 // should not require a mutex
1391 if (not bodyTrackingIsRunning and input.enable3d)
1392 {
1393 bodyTrackingIsRunning = true;
1394 bodyTrackingPublishTask->start();
1395 }
1396 else if (bodyTrackingIsRunning and not input.enable3d)
1397 {
1398 bodyTrackingIsRunning = false;
1399 bodyTrackingPublishTask->stop();
1400 }
1401 }
1402 else
1403 {
1404 ARMARX_ERROR << "Azure Kinect Body Tracking is not enabled";
1405 }
1406#endif
1407 }
1408
1409 void
1410 AzureKinectPointCloudProvider::setMaxDepthBodyTracking(int maxDepthInMM, const Ice::Current&)
1411 {
1412 std::scoped_lock lock(bodyTrackingParameterMutex);
1413 bodyTrackingDepthMaskMaxZ = maxDepthInMM;
1414 }
1415
1416 void
1418 int maxXinPixel,
1419 const Ice::Current&)
1420 {
1421 std::scoped_lock lock(bodyTrackingParameterMutex);
1422 bodyTrackingDepthMaskMinX = minXinPixel;
1423 bodyTrackingDepthMaskMaxX = maxXinPixel;
1424 }
1425
1426 // the code below is taken from https://github.com/microsoft/Azure_Kinect_ROS_Driver
1427 // -> MIT license
1428
1429 void
1431 const std::chrono::microseconds& k4a_device_timestamp_us,
1432 const std::chrono::nanoseconds& k4a_system_timestamp_ns)
1433 {
1434 // System timestamp is on monotonic system clock.
1435 // Device time is on AKDK hardware clock.
1436 // We want to continuously estimate diff between realtime and AKDK hardware clock as low-pass offset.
1437 // This consists of two parts: device to monotonic, and monotonic to realtime.
1438
1439 // First figure out realtime to monotonic offset. This will change to keep updating it.
1440 std::chrono::nanoseconds realtime_clock =
1441 std::chrono::system_clock::now().time_since_epoch();
1442 std::chrono::nanoseconds monotonic_clock =
1443 std::chrono::steady_clock::now().time_since_epoch();
1444
1445 std::chrono::nanoseconds monotonic_to_realtime = realtime_clock - monotonic_clock;
1446
1447 // Next figure out the other part (combined).
1448 std::chrono::nanoseconds device_to_realtime =
1449 k4a_system_timestamp_ns - k4a_device_timestamp_us + monotonic_to_realtime;
1450
1451 {
1452 std::lock_guard g{deviceToRealtimeOffsetMtx};
1453
1454 {
1455 std::lock_guard g{debugObserverMtx};
1456 setDebugObserverDatafield("device_to_realtime_offset [ms]",
1457 device_to_realtime_offset_.count() /
1458 1'000'000.f); // [ns] -> [ms]
1460 "clock_error",
1461 std::abs<float>((device_to_realtime_offset_ - device_to_realtime).count()) /
1462 1'000.f); // [ns] -> [µs]
1463 }
1464
1465 const std::int64_t timeOffsetThreshold = 1e7; // 10 ms
1466
1467 // If we're over a certain time off, just snap into place.
1468 if (device_to_realtime_offset_.count() == 0 ||
1469 std::abs((device_to_realtime_offset_ - device_to_realtime).count()) >
1470 timeOffsetThreshold)
1471 {
1473 << "Initializing or re-initializing the device to realtime offset: "
1474 << device_to_realtime.count() << " ns";
1475 device_to_realtime_offset_ = device_to_realtime;
1476 }
1477 else
1478 {
1479 // Low-pass filter!
1480 constexpr double alpha = 0.10;
1481
1482 const std::chrono::nanoseconds timeCorrection(static_cast<int64_t>(
1483 std::floor(alpha * (device_to_realtime - device_to_realtime_offset_).count())));
1484 device_to_realtime_offset_ = device_to_realtime_offset_ + timeCorrection;
1485
1486 {
1487 std::lock_guard g{debugObserverMtx};
1488 setDebugObserverDatafield("timeCorrection [µs]",
1489 timeCorrection.count() / 1'000); // [ns] -> [µs]
1490 }
1491 }
1492 }
1493 }
1494
1497 const std::chrono::microseconds& k4a_timestamp_us)
1498 {
1499 std::lock_guard g{deviceToRealtimeOffsetMtx};
1500
1501 // must be initialized beforehand
1502 ARMARX_CHECK(device_to_realtime_offset_.count() != 0);
1503
1504 std::chrono::nanoseconds timestamp_in_realtime =
1505 k4a_timestamp_us + device_to_realtime_offset_;
1506
1507 return armarx::Duration::MicroSeconds(timestamp_in_realtime.count() /
1508 1'000); // [ns] -> [µs]
1509 }
1510
1511 void
1513 const std::chrono::microseconds& k4a_device_timestamp_us)
1514 {
1515 std::lock_guard g{deviceToRealtimeOffsetMtx};
1516
1517 // We have no better guess than "now".
1518 std::chrono::nanoseconds realtime_clock =
1519 std::chrono::system_clock::now().time_since_epoch();
1520
1521 device_to_realtime_offset_ = realtime_clock - k4a_device_timestamp_us;
1522
1523 ARMARX_INFO << "Initializing the device to realtime offset based on wall clock: "
1524 << device_to_realtime_offset_.count() << " ns";
1525 }
1526
1531
1532 void
1534 {
1535 if (std::abs(std::fmod(higherFramerate, value)) > 1e-6)
1536 {
1537 std::stringstream ss;
1538 ss << "Invalid value (" << value << " fps) for framerate of '" << name << "'."
1539 << " Reason: The framerate has to be a divider of the property framerate.image ("
1540 << higherFramerate << " fps).";
1541 throw armarx::LocalException() << ss.str();
1542 }
1543 skipFrames = std::round(higherFramerate / value) - 1;
1544 if (skipFrames != 0)
1545 {
1546 ARMARX_INFO_S << "Only publishing every " << (skipFrames + 1) << "'th " << name
1547 << " result!";
1548 }
1549 }
1550
1551 bool
1553 {
1554 bool skip = false;
1555 if (skipFrames > 0)
1556 {
1559 skipFramesCount %= (skipFrames + 1);
1560 }
1561 return skip;
1562 }
1563
1566
1567} // 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:90
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:181
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#define ARMARX_INFO_S
Definition Logging.h:202
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
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:77