utilities.cc
Go to the documentation of this file.
1 
37 #ifdef WIN32
38 #ifndef WIN32_LEAN_AND_MEAN
39 #define WIN32_LEAN_AND_MEAN 1
40 #endif
41 
42 #include <windows.h>
43 #include <winsock2.h>
44 
45 #else
46 #include <arpa/inet.h>
47 #endif
48 
49 #include <algorithm>
50 #include <fstream>
51 #include <iostream>
52 #include <stdexcept>
53 
54 #ifdef HAVE_OPENCV
55 #include <opencv2/imgcodecs.hpp>
56 #endif
57 
59 
60 namespace multisense {
61 namespace {
62 
63 #ifndef HAVE_OPENCV
64 bool write_binary_image(const Image &image, const std::filesystem::path &path)
65 {
66  std::ofstream output(path, std::ios::binary | std::ios::out);
67 
68  if (!output.good())
69  {
70  std::cerr << "Failed to open: " << path << std::endl;
71  return false;
72  }
73 
74  switch(image.format)
75  {
77  {
78 
79  output << "P5\n"
80  << image.width << " " << image.height << "\n"
81  << 0xFF << "\n";
82 
83  output.write(reinterpret_cast<const char*>(image.raw_data->data()) + image.image_data_offset,
84  image.image_data_length);
85  break;
86  }
88  {
89  output << "P5\n"
90  << image.width << " " << image.height << "\n"
91  << 0xFFFF << "\n";
92 
93  //
94  // Make sure we swap our byte order if needed
95  //
96  const uint16_t* raw_data = reinterpret_cast<const uint16_t*>(image.raw_data->data() + image.image_data_offset);
97  for (int i = 0 ; i < (image.width * image.height) ; ++i)
98  {
99  const uint16_t o = htons(raw_data[i]);
100  output.write(reinterpret_cast<const char*>(&o), sizeof(uint16_t));
101  }
102 
103  break;
104  }
106  {
107  output << "P6\n"
108  << image.width << " " << image.height << "\n"
109  << 0xFF << "\n";
110 
111  //
112  // Convert BGR to RGB
113  //
114  for (int i = 0 ; i < (image.width * image.height) ; ++i)
115  {
116  const auto bgr = reinterpret_cast<const std::array<uint8_t, 3>*>(image.raw_data->data() + image.image_data_offset + (i * 3));
117  const std::array<uint8_t, 3> rgb{bgr->at(2), bgr->at(1), bgr->at(0)};
118  output.write(reinterpret_cast<const char*>(rgb.data()), sizeof(rgb));
119  }
120 
121  break;
122  }
123  default:
124  {
125  std::cerr << "Unhandled image format. Cannot write to disk" << std::endl;
126  return false;
127  }
128  }
129 
130  output.close();
131  return true;
132 }
133 #endif
134 
135 }
136 
137 std::string to_string(const Status &status)
138 {
139  switch(status)
140  {
141  case Status::OK: {return "OK";}
142  case Status::TIMEOUT: {return "TIMEOUT";}
143  case Status::INTERNAL_ERROR: {return "ERROR";}
144  case Status::FAILED: {return "FAILED";}
145  case Status::UNSUPPORTED: {return "UNSUPPORTED";}
146  case Status::UNKNOWN: {return "UNKNOWN";}
147  case Status::EXCEPTION: {return "EXCEPTION";}
148  case Status::UNINITIALIZED: {return "UNINITIALIZED";}
149  case Status::INCOMPLETE_APPLICATION: {return "INCOMPLETE_APPLICATION";}
150  }
151 
152  return "UNKNOWN";
153 }
154 
155 
156 #ifdef HAVE_OPENCV
157 cv::Mat Image::cv_mat() const
158 {
159  int cv_type = 0;
160  switch(format)
161  {
162  case Image::PixelFormat::MONO8: {cv_type = CV_8UC1; break;}
163  case Image::PixelFormat::BGR8: {cv_type = CV_8UC3; break;}
164  case Image::PixelFormat::MONO16: {cv_type = CV_16UC1; break;}
165  case Image::PixelFormat::FLOAT32: {cv_type = CV_32FC1; break;}
166  default: {throw std::runtime_error("invalid pixel format");}
167  }
168 
169  return cv::Mat{height,
170  width,
171  cv_type,
172  const_cast<uint8_t*>(raw_data->data() + image_data_offset)};
173 }
174 #endif
175 
176 bool write_image(const Image &image, const std::filesystem::path &path)
177 {
178 #ifdef HAVE_OPENCV
179  return cv::imwrite(path.string(), image.cv_mat());
180 #else
181  const auto extension = path.extension();
182  if (extension == ".pgm" || extension == ".PGM" || extension == ".ppm" || extension == ".PPM")
183  {
184  return write_binary_image(image, path);
185  }
186  throw std::runtime_error("Unsupported path extension: " + extension.string() + ". Try compiling with OpenCV");
187 #endif
188  return false;
189 }
190 
191 std::optional<Image> create_depth_image(const ImageFrame &frame,
192  const Image::PixelFormat &depth_format,
193  const DataSource &disparity_source,
194  float invalid_value)
195 {
196  if (!frame.has_image(disparity_source))
197  {
198  return std::nullopt;
199  }
200 
201  const auto disparity = frame.get_image(disparity_source);
202 
203  if (disparity.format != Image::PixelFormat::MONO16 ||
204  disparity.width < 0 ||
205  disparity.height < 0)
206  {
207  return std::nullopt;
208  }
209 
210  const double fx = disparity.calibration.P[0][0];
211  const double tx = frame.calibration.right.P[0][3] / frame.calibration.right.P[0][0];
212 
213  size_t bytes_per_pixel = 0;
214  switch (depth_format)
215  {
217  {
218  bytes_per_pixel = sizeof(uint16_t);
219  break;
220  }
222  {
223  bytes_per_pixel = sizeof(float);
224  break;
225  }
226  default:
227  {
228  std::cerr << "Unsupported depth pixel format" << std::endl;
229  return std::nullopt;
230  }
231  }
232 
233  auto data = std::make_shared<std::vector<uint8_t>>(disparity.width * disparity.height * bytes_per_pixel,
234  static_cast<uint8_t>(0));
235 
236  //
237  // MONO16 disparity images are quantized to 1/16th of a pixel
238  //
239  constexpr double scale = 1.0 / 16.0;
240 
241  for (size_t i = 0 ; i < static_cast<size_t>(disparity.width * disparity.height) ; ++i)
242  {
243  const size_t index = disparity.image_data_offset + (i * sizeof(uint16_t));
244 
245  const double d =
246  static_cast<double>(*reinterpret_cast<const uint16_t*>(disparity.raw_data->data() + index)) * scale;
247 
248  switch (depth_format)
249  {
251  {
252  //
253  // Quantize to millimeters
254  //
255  const uint16_t depth = (d == 0.0) ? static_cast<uint16_t>(invalid_value) :
256  static_cast<uint16_t>(1000 * fx * -tx / d);
257 
258  auto data_pointer = reinterpret_cast<uint16_t*>(data->data() + (sizeof(uint16_t) * i));
259  *data_pointer = depth;
260  continue;
261  }
263  {
264  const float depth = (d == 0.0) ? invalid_value :
265  static_cast<float>(fx * -tx / d);
266 
267  auto data_pointer = reinterpret_cast<float*>(data->data() + (sizeof(float) * i));
268  *data_pointer = depth;
269  continue;
270  }
271  default:
272  {
273  std::cerr << "Unsupported depth pixel format" << std::endl;
274  return std::nullopt;
275  }
276  }
277  }
278 
279  return Image{data,
280  0,
281  data->size(),
282  depth_format,
283  disparity.width,
284  disparity.height,
285  disparity.camera_timestamp,
286  disparity.ptp_timestamp,
287  disparity.source,
288  disparity.calibration};
289 }
290 
291 
292 std::optional<Image> create_bgr_from_ycbcr420(const Image &luma, const Image &chroma, const DataSource &output_source)
293 {
295  {
296  return std::nullopt;
297  }
298 
299  const size_t color_length = luma.image_data_length * 3;
300 
301  std::vector<uint8_t> raw_data(color_length, static_cast<uint8_t>(0));
302 
303  for (int h = 0 ; h < luma.height ; ++h)
304  {
305  const size_t row_offset = h * luma.width * 3;
306 
307  for (int w = 0 ; w < luma.width ; ++w)
308  {
309  const size_t luma_offset = (h * luma.width) + w;
310  const size_t chroma_offset = 2 * (((h / 2) * (luma.width / 2)) + (w / 2));
311 
312  const float px_y = static_cast<float>(*(luma.raw_data->data() + luma.image_data_offset + luma_offset));
313  const float px_cb = static_cast<float>(*(chroma.raw_data->data() + chroma.image_data_offset + chroma_offset)) - 128.0f;
314  const float px_cr = static_cast<float>(*(chroma.raw_data->data() + chroma.image_data_offset + chroma_offset + 1)) - 128.0f;
315 
316  const float px_r = std::clamp(px_y + 1.13983f * px_cr, 0.0f, 255.0f);
317  const float px_g = std::clamp(px_y - 0.39465f * px_cb - 0.58060f * px_cr, 0.0f, 255.0f);
318  const float px_b = std::clamp(px_y + 2.03211f * px_cb, 0.0f, 255.0f);
319 
320  auto bgr_pixel_ptr = reinterpret_cast<uint8_t*>(raw_data.data() + row_offset + (3 * w));
321 
322  bgr_pixel_ptr[0] = static_cast<uint8_t>(px_b);
323  bgr_pixel_ptr[1] = static_cast<uint8_t>(px_g);
324  bgr_pixel_ptr[2] = static_cast<uint8_t>(px_r);
325  }
326  }
327 
328  return Image{std::make_shared<std::vector<uint8_t>>(std::move(raw_data)),
329  0,
330  color_length,
332  luma.width,
333  luma.height,
334  luma.camera_timestamp,
335  luma.ptp_timestamp,
336  output_source,
337  luma.calibration};
338 }
339 
340 std::optional<Image> create_bgr_image(const ImageFrame &frame, const DataSource &output_source)
341 {
343  {
344  DataSource luma_source = DataSource::UNKNOWN;
345  DataSource chroma_source = DataSource::UNKNOWN;
346  switch (output_source)
347  {
348  case DataSource::AUX_RAW:
349  {
350  luma_source = DataSource::AUX_LUMA_RAW;
351  chroma_source = DataSource::AUX_CHROMA_RAW;
352  break;
353  }
355  {
357  chroma_source = DataSource::AUX_CHROMA_RECTIFIED_RAW;
358  break;
359  }
360  default: {return std::nullopt;}
361  }
362 
363  if (frame.has_image(luma_source) && frame.has_image(chroma_source))
364  {
365  return create_bgr_from_ycbcr420(frame.get_image(luma_source), frame.get_image(chroma_source), output_source);
366  }
367  }
368 
369  return std::nullopt;
370 }
371 
372 
373 std::optional<PointCloud<void>> create_pointcloud(const ImageFrame &frame,
374  double max_range,
375  const DataSource &disparity_source)
376 {
377  return create_color_pointcloud<void>(frame, max_range, DataSource::UNKNOWN, disparity_source);
378 }
379 
380 }
multisense::Image::image_data_length
size_t image_data_length
The length of the image data after the image_data_offset has been applied.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:216
multisense::Status::INCOMPLETE_APPLICATION
@ INCOMPLETE_APPLICATION
multisense::create_bgr_image
std::optional< Image > create_bgr_image(const ImageFrame &frame, const DataSource &output_source)
Convert a YCbCr420 luma + chroma image into a BGR color image.
Definition: utilities.cc:340
multisense::Image::camera_timestamp
TimeT camera_timestamp
The timestamp associated with the image based on the camera's clock. Starts at 0 on boot.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:237
multisense::Status::UNKNOWN
@ UNKNOWN
multisense::Image::height
int height
Height of the image in pixels.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:231
multisense::DataSource::AUX_RECTIFIED_RAW
@ AUX_RECTIFIED_RAW
multisense::Status::UNINITIALIZED
@ UNINITIALIZED
multisense::Image::ptp_timestamp
TimeT ptp_timestamp
The timestamp associated with the image based using the camera's clock which is potentially PTP synch...
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:243
multisense::Status
Status
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:67
multisense::ImageFrame::get_image
const Image & get_image(const DataSource &source) const
Retrieve image by DataSource. Throws if not found.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:313
multisense::ImageFrame
A frame containing multiple images (indexed by DataSource).
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:300
MultiSenseUtilities.hh
multisense::ImageFrame::has_image
bool has_image(const DataSource &source) const
Check if we have an image for a given data source.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:326
multisense::Image::PixelFormat::MONO8
@ MONO8
multisense::CameraCalibration::P
std::array< std::array< float, 4 >, 3 > P
Rectified projection matrix which takes points in the origin camera coordinate frame and projects the...
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:142
multisense::DataSource::AUX_LUMA_RAW
@ AUX_LUMA_RAW
multisense::DataSource::AUX_LUMA_RECTIFIED_RAW
@ AUX_LUMA_RECTIFIED_RAW
multisense::Image::PixelFormat
PixelFormat
Pixel formats.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:192
f
f
multisense::Image::calibration
CameraCalibration calibration
The scaled calibration associated with the image.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:253
multisense::DataSource
DataSource
Identifies which camera or data source the image is from.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:83
multisense::create_depth_image
std::optional< Image > create_depth_image(const ImageFrame &frame, const Image::PixelFormat &depth_format, const DataSource &disparity_source, float invalid_value)
Create a depth image from a image frame.
Definition: utilities.cc:191
multisense::Image::raw_data
std::shared_ptr< const std::vector< uint8_t > > raw_data
A pointer to the raw image data sent from the camera.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:206
multisense::ImageFrame::calibration
StereoCalibration calibration
The scaled calibration for the entire camera.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:344
multisense::create_bgr_from_ycbcr420
std::optional< Image > create_bgr_from_ycbcr420(const Image &luma, const Image &chroma, const DataSource &output_source)
Convert a YCbCr420 luma + chroma image into a BGR color image.
Definition: utilities.cc:292
multisense::Image::PixelFormat::MONO16
@ MONO16
d
d
multisense::Image::image_data_offset
int64_t image_data_offset
An offset into the raw_data pointer where the image data starts.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:211
multisense::Status::EXCEPTION
@ EXCEPTION
multisense::Status::UNSUPPORTED
@ UNSUPPORTED
multisense::Status::OK
@ OK
multisense::DataSource::AUX_CHROMA_RECTIFIED_RAW
@ AUX_CHROMA_RECTIFIED_RAW
multisense::DataSource::AUX_RAW
@ AUX_RAW
multisense::Status::TIMEOUT
@ TIMEOUT
multisense::Image::PixelFormat::BGR8
@ BGR8
multisense::StereoCalibration::right
CameraCalibration right
Calibration information for the right camera.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:176
multisense
Definition: factory.cc:39
multisense::Status::FAILED
@ FAILED
multisense::ColorImageEncoding::YCBCR420
@ YCBCR420
multisense::DataSource::AUX_CHROMA_RAW
@ AUX_CHROMA_RAW
multisense::Image
Represents a single image plus metadata.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:187
multisense::write_image
bool write_image(const Image &image, const std::filesystem::path &path)
Write a image to a specific path on disk. The type of serialization is determined by the input path.
Definition: utilities.cc:176
multisense::ImageFrame::aux_color_encoding
ColorImageEncoding aux_color_encoding
The encoding of the aux color image(s) in the frame.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:359
multisense::Image::PixelFormat::FLOAT32
@ FLOAT32
multisense::create_pointcloud
std::optional< PointCloud< void > > create_pointcloud(const ImageFrame &frame, double max_range, const DataSource &disparity_source)
Definition: utilities.cc:373
multisense::Image::width
int width
Width of the image in pixels.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:226
multisense::Image::format
PixelFormat format
The format of the image data stored in the raw_data stored in the raw_data buffer.
Definition: LibMultiSense/include/MultiSense/MultiSenseTypes.hh:221
multisense::DataSource::UNKNOWN
@ UNKNOWN
multisense::to_string
std::string to_string(const Status &status)
Convert a status object to a user readable string.
Definition: utilities.cc:137
point_cloud_utility.float
float
Definition: point_cloud_utility.py:114
multisense::Status::INTERNAL_ERROR
@ INTERNAL_ERROR


multisense_lib
Author(s):
autogenerated on Thu Apr 17 2025 02:49:09