ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
Image.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "Image.h"
9 
10 #include <Logging.h>
11 #include <Parallel.h>
12 
13 #include "ecvBBox.h"
14 
15 namespace {
18 const std::vector<double> Gaussian3 = {0.25, 0.5, 0.25};
19 const std::vector<double> Gaussian5 = {0.0625, 0.25, 0.375, 0.25, 0.0625};
20 const std::vector<double> Gaussian7 = {0.03125, 0.109375, 0.21875, 0.28125,
21  0.21875, 0.109375, 0.03125};
22 const std::vector<double> Sobel31 = {-1.0, 0.0, 1.0};
23 const std::vector<double> Sobel32 = {1.0, 2.0, 1.0};
24 } // unnamed namespace
25 
26 namespace cloudViewer {
27 namespace geometry {
29 
31  width_ = 0;
32  height_ = 0;
33  num_of_channels_ = 0;
35  data_.clear();
36  return *this;
37 }
38 
39 Eigen::Vector2d Image::GetMin2DBound() const {
40  return Eigen::Vector2d(0.0, 0.0);
41 }
42 
43 Eigen::Vector2d Image::GetMax2DBound() const {
44  return Eigen::Vector2d(width_, height_);
45 }
46 
47 ccBBox Image::getOwnBB(bool withGLFeatures) {
49 }
50 
52  double v,
53  double inner_margin /* = 0.0 */) const {
54  return (u >= inner_margin && u < width_ - inner_margin &&
55  v >= inner_margin && v < height_ - inner_margin);
56 }
57 
58 std::pair<bool, double> Image::FloatValueAt(double u, double v) const {
59  if ((num_of_channels_ != 1) || (bytes_per_channel_ != 4) ||
60  (u < 0.0 || u > (double)(width_ - 1) || v < 0.0 ||
61  v > (double)(height_ - 1))) {
62  return std::make_pair(false, 0.0);
63  }
64  int ui = std::max(std::min((int)u, width_ - 2), 0);
65  int vi = std::max(std::min((int)v, height_ - 2), 0);
66  double pu = u - ui;
67  double pv = v - vi;
68  float value[4] = {*PointerAt<float>(ui, vi), *PointerAt<float>(ui, vi + 1),
69  *PointerAt<float>(ui + 1, vi),
70  *PointerAt<float>(ui + 1, vi + 1)};
71  return std::make_pair(true,
72  (value[0] * (1 - pv) + value[1] * pv) * (1 - pu) +
73  (value[2] * (1 - pv) + value[3] * pv) * pu);
74 }
75 
76 template <typename T>
77 T *Image::PointerAt(int u, int v) const {
78  return (T *)(data_.data() + (v * width_ + u) * sizeof(T));
79 }
80 
81 template float *Image::PointerAt<float>(int u, int v) const;
82 template int *Image::PointerAt<int>(int u, int v) const;
83 template uint8_t *Image::PointerAt<uint8_t>(int u, int v) const;
84 template uint16_t *Image::PointerAt<uint16_t>(int u, int v) const;
85 
86 template <typename T>
87 T *Image::PointerAt(int u, int v, int ch) const {
88  return (T *)(data_.data() +
89  ((v * width_ + u) * num_of_channels_ + ch) * sizeof(T));
90 }
91 
92 template float *Image::PointerAt<float>(int u, int v, int ch) const;
93 template int *Image::PointerAt<int>(int u, int v, int ch) const;
94 template uint8_t *Image::PointerAt<uint8_t>(int u, int v, int ch) const;
95 template uint16_t *Image::PointerAt<uint16_t>(int u, int v, int ch) const;
96 
97 std::shared_ptr<Image> Image::ConvertDepthToFloatImage(
98  double depth_scale /* = 1000.0*/, double depth_trunc /* = 3.0*/) const {
99  // don't need warning message about image type
100  // as we call CreateFloatImage
101  auto output = CreateFloatImage();
102  for (int y = 0; y < output->height_; y++) {
103  for (int x = 0; x < output->width_; x++) {
104  float *p = output->PointerAt<float>(x, y);
105  *p /= (float)depth_scale;
106  if (*p >= depth_trunc) *p = 0.0f;
107  }
108  }
109  return output;
110 }
111 
112 Image &Image::ClipIntensity(double min /* = 0.0*/, double max /* = 1.0*/) {
113  if (num_of_channels_ != 1 || bytes_per_channel_ != 4) {
114  utility::LogError("[ClipIntensity] Unsupported image format.");
115  }
116  for (int y = 0; y < height_; y++) {
117  for (int x = 0; x < width_; x++) {
118  float *p = PointerAt<float>(x, y);
119  if (*p > max) *p = (float)max;
120  if (*p < min) *p = (float)min;
121  }
122  }
123  return *this;
124 }
125 
126 Image &Image::LinearTransform(double scale, double offset /* = 0.0*/) {
127  if (num_of_channels_ != 1 || bytes_per_channel_ != 4) {
128  utility::LogError("[LinearTransform] Unsupported image format.");
129  }
130  for (int y = 0; y < height_; y++) {
131  for (int x = 0; x < width_; x++) {
132  float *p = PointerAt<float>(x, y);
133  (*p) = (float)(scale * (*p) + offset);
134  }
135  }
136  return *this;
137 }
138 
139 std::shared_ptr<Image> Image::Downsample() const {
140  auto output = std::make_shared<Image>();
141  if (num_of_channels_ != 1 || bytes_per_channel_ != 4) {
142  utility::LogError("[Downsample] Unsupported image format.");
143  }
144  int half_width = (int)floor((double)width_ / 2.0);
145  int half_height = (int)floor((double)height_ / 2.0);
146  output->Prepare(half_width, half_height, 1, 4);
147 
148 #ifdef _OPENMP
149 #ifdef _WIN32
150 #pragma omp parallel for schedule(static) \
151  num_threads(utility::EstimateMaxThreads())
152 #else
153 #pragma omp parallel for collapse(2) schedule(static) \
154  num_threads(utility::EstimateMaxThreads())
155 #endif
156 #endif
157  for (int y = 0; y < output->height_; y++) {
158  for (int x = 0; x < output->width_; x++) {
159  float *p1 = PointerAt<float>(x * 2, y * 2);
160  float *p2 = PointerAt<float>(x * 2 + 1, y * 2);
161  float *p3 = PointerAt<float>(x * 2, y * 2 + 1);
162  float *p4 = PointerAt<float>(x * 2 + 1, y * 2 + 1);
163  float *p = output->PointerAt<float>(x, y);
164  *p = (*p1 + *p2 + *p3 + *p4) / 4.0f;
165  }
166  }
167  return output;
168 }
169 
170 std::shared_ptr<Image> Image::FilterHorizontal(
171  const std::vector<double> &kernel) const {
172  auto output = std::make_shared<Image>();
173  if (num_of_channels_ != 1 || bytes_per_channel_ != 4 ||
174  kernel.size() % 2 != 1) {
176  "[FilterHorizontal] Unsupported image format or kernel "
177  "size.");
178  }
179  output->Prepare(width_, height_, 1, 4);
180 
181  const int half_kernel_size = (int)(floor((double)kernel.size() / 2.0));
182 
183 #ifdef _OPENMP
184 #ifdef _WIN32
185 #pragma omp parallel for schedule(static) \
186  num_threads(utility::EstimateMaxThreads())
187 #else
188 #pragma omp parallel for collapse(2) schedule(static) \
189  num_threads(utility::EstimateMaxThreads())
190 #endif
191 #endif
192  for (int y = 0; y < height_; y++) {
193  for (int x = 0; x < width_; x++) {
194  float *po = output->PointerAt<float>(x, y, 0);
195  double temp = 0;
196  for (int i = -half_kernel_size; i <= half_kernel_size; i++) {
197  int x_shift = x + i;
198  if (x_shift < 0) x_shift = 0;
199  if (x_shift > width_ - 1) x_shift = width_ - 1;
200  float *pi = PointerAt<float>(x_shift, y, 0);
201  temp += (*pi * (float)kernel[i + half_kernel_size]);
202  }
203  *po = (float)temp;
204  }
205  }
206  return output;
207 }
208 
209 std::shared_ptr<Image> Image::Filter(Image::FilterType type) const {
210  auto output = std::make_shared<Image>();
211  if (num_of_channels_ != 1 || bytes_per_channel_ != 4) {
212  utility::LogError("[Filter] Unsupported image format.");
213  }
214 
215  switch (type) {
217  output = Filter(Gaussian3, Gaussian3);
218  break;
220  output = Filter(Gaussian5, Gaussian5);
221  break;
223  output = Filter(Gaussian7, Gaussian7);
224  break;
226  output = Filter(Sobel31, Sobel32);
227  break;
229  output = Filter(Sobel32, Sobel31);
230  break;
231  default:
232  utility::LogError("[Filter] Unsupported filter type.");
233  break;
234  }
235  return output;
236 }
237 
240  std::vector<std::shared_ptr<Image>> output;
241  for (size_t i = 0; i < input.size(); i++) {
242  auto layer_filtered = input[i]->Filter(type);
243  output.push_back(layer_filtered);
244  }
245  return output;
246 }
247 
248 std::shared_ptr<Image> Image::Filter(const std::vector<double> &dx,
249  const std::vector<double> &dy) const {
250  auto output = std::make_shared<Image>();
251  if (num_of_channels_ != 1 || bytes_per_channel_ != 4) {
252  utility::LogError("[Filter] Unsupported image format.");
253  }
254 
255  auto temp1 = FilterHorizontal(dx);
256  auto temp2 = temp1->Transpose();
257  auto temp3 = temp2->FilterHorizontal(dy);
258  auto temp4 = temp3->Transpose();
259  return temp4;
260 }
261 
262 std::shared_ptr<Image> Image::Transpose() const {
263  auto output = std::make_shared<Image>();
265 
266  int out_bytes_per_line = output->BytesPerLine();
267  int in_bytes_per_line = BytesPerLine();
268  int bytes_per_pixel = num_of_channels_ * bytes_per_channel_;
269 
270 #ifdef _OPENMP
271 #ifdef _WIN32
272 #pragma omp parallel for schedule(static) \
273  num_threads(utility::EstimateMaxThreads())
274 #else
275 #pragma omp parallel for collapse(2) schedule(static) \
276  num_threads(utility::EstimateMaxThreads())
277 #endif
278 #endif
279  for (int y = 0; y < height_; y++) {
280  for (int x = 0; x < width_; x++) {
281  std::copy(
282  data_.data() + y * in_bytes_per_line + x * bytes_per_pixel,
283  data_.data() + y * in_bytes_per_line +
284  (x + 1) * bytes_per_pixel,
285  output->data_.data() + x * out_bytes_per_line +
286  y * bytes_per_pixel);
287  }
288  }
289 
290  return output;
291 }
292 
293 std::shared_ptr<Image> Image::FlipVertical() const {
294  auto output = std::make_shared<Image>();
296 
297  int bytes_per_line = BytesPerLine();
298 #ifdef _OPENMP
299 #pragma omp parallel for schedule(static) \
300  num_threads(utility::EstimateMaxThreads())
301 #endif
302  for (int y = 0; y < height_; y++) {
303  std::copy(data_.data() + y * bytes_per_line,
304  data_.data() + (y + 1) * bytes_per_line,
305  output->data_.data() + (height_ - y - 1) * bytes_per_line);
306  }
307  return output;
308 }
309 
310 std::shared_ptr<Image> Image::FlipHorizontal() const {
311  auto output = std::make_shared<Image>();
313 
314  int bytes_per_line = BytesPerLine();
315  int bytes_per_pixel = num_of_channels_ * bytes_per_channel_;
316 #ifdef _OPENMP
317 #ifdef _WIN32
318 #pragma omp parallel for schedule(static) \
319  num_threads(utility::EstimateMaxThreads())
320 #else
321 #pragma omp parallel for collapse(2) schedule(static) \
322  num_threads(utility::EstimateMaxThreads())
323 #endif
324 #endif
325  for (int y = 0; y < height_; y++) {
326  for (int x = 0; x < width_; x++) {
327  std::copy(data_.data() + y * bytes_per_line + x * bytes_per_pixel,
328  data_.data() + y * bytes_per_line +
329  (x + 1) * bytes_per_pixel,
330  output->data_.data() + y * bytes_per_line +
331  (width_ - x - 1) * bytes_per_pixel);
332  }
333  }
334 
335  return output;
336 }
337 
338 std::shared_ptr<Image> Image::Dilate(int half_kernel_size /* = 1 */) const {
339  auto output = std::make_shared<Image>();
340  if (num_of_channels_ != 1 || bytes_per_channel_ != 1) {
341  utility::LogError("[Dilate] Unsupported image format.");
342  }
343  output->Prepare(width_, height_, 1, 1);
344 
345 #ifdef _OPENMP
346 #ifdef _WIN32
347 #pragma omp parallel for schedule(static) \
348  num_threads(utility::EstimateMaxThreads())
349 #else
350 #pragma omp parallel for collapse(2) schedule(static) \
351  num_threads(utility::EstimateMaxThreads())
352 #endif
353 #endif
354  for (int y = 0; y < height_; y++) {
355  for (int x = 0; x < width_; x++) {
356  for (int yy = -half_kernel_size; yy <= half_kernel_size; yy++) {
357  for (int xx = -half_kernel_size; xx <= half_kernel_size; xx++) {
358  unsigned char *pi;
359  if (TestImageBoundary(x + xx, y + yy)) {
360  pi = PointerAt<unsigned char>(x + xx, y + yy);
361  if (*pi == 255) {
362  *output->PointerAt<unsigned char>(x, y, 0) = 255;
363  xx = half_kernel_size;
364  yy = half_kernel_size;
365  }
366  }
367  }
368  }
369  }
370  }
371  return output;
372 }
373 
374 std::shared_ptr<Image> Image::CreateDepthBoundaryMask(
375  double depth_threshold_for_discontinuity_check,
376  int half_dilation_kernel_size_for_discontinuity_map) const {
377  auto depth_image = CreateFloatImage(); // necessary?
378  int width = depth_image->width_;
379  int height = depth_image->height_;
380  auto depth_image_gradient_dx =
381  depth_image->Filter(Image::FilterType::Sobel3Dx);
382  auto depth_image_gradient_dy =
383  depth_image->Filter(Image::FilterType::Sobel3Dy);
384  auto mask = std::make_shared<Image>();
385  mask->Prepare(width, height, 1, 1);
386 
387 #ifdef _OPENMP
388 #ifdef _WIN32
389 #pragma omp parallel for schedule(static) \
390  num_threads(utility::EstimateMaxThreads())
391 #else
392 #pragma omp parallel for collapse(2) schedule(static) \
393  num_threads(utility::EstimateMaxThreads())
394 #endif
395 #endif
396  for (int v = 0; v < height; v++) {
397  for (int u = 0; u < width; u++) {
398  double dx = *depth_image_gradient_dx->PointerAt<float>(u, v);
399  double dy = *depth_image_gradient_dy->PointerAt<float>(u, v);
400  double mag = sqrt(dx * dx + dy * dy);
401  if (mag > depth_threshold_for_discontinuity_check) {
402  *mask->PointerAt<unsigned char>(u, v) = 255;
403  } else {
404  *mask->PointerAt<unsigned char>(u, v) = 0;
405  }
406  }
407  }
408  if (half_dilation_kernel_size_for_discontinuity_map >= 1) {
409  auto mask_dilated =
410  mask->Dilate(half_dilation_kernel_size_for_discontinuity_map);
411  return mask_dilated;
412  } else {
413  return mask;
414  }
415 }
416 
417 } // namespace geometry
418 } // namespace cloudViewer
int width
int height
int offset
char type
bool copy
Definition: VtkUtils.cpp:74
Bounding box structure.
Definition: ecvBBox.h:25
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
virtual void onDeletionOf(const ccHObject *obj)
This method is called when another object is deleted.
Definition: ecvHObject.cpp:519
virtual ccBBox GetAxisAlignedBoundingBox() const
Returns an axis-aligned bounding box of the geometry.
Definition: ecvHObject.cpp:447
The Image class stores image with customizable width, height, num of channels and bytes per channel.
Definition: Image.h:33
std::shared_ptr< Image > FlipVertical() const
Function to flip image vertically (upside down).
Definition: Image.cpp:293
Image & LinearTransform(double scale=1.0, double offset=0.0)
Definition: Image.cpp:126
Image & ClipIntensity(double min=0.0, double max=1.0)
Definition: Image.cpp:112
int num_of_channels_
Number of chanels in the image.
Definition: Image.h:223
virtual Eigen::Vector2d GetMax2DBound() const override
Definition: Image.cpp:43
std::shared_ptr< Image > FilterHorizontal(const std::vector< double > &kernel) const
Definition: Image.cpp:170
std::pair< bool, double > FloatValueAt(double u, double v) const
Definition: Image.cpp:58
std::shared_ptr< Image > Downsample() const
Function to 2x image downsample using simple 2x2 averaging.
Definition: Image.cpp:139
virtual void onDeletionOf(const ccHObject *obj) override
This method is called when another object is deleted.
Definition: Image.cpp:28
std::shared_ptr< Image > CreateFloatImage(Image::ColorToIntensityConversionType type=Image::ColorToIntensityConversionType::Weighted) const
Return a gray scaled float type image.
int height_
Height of the image.
Definition: Image.h:221
std::shared_ptr< Image > Dilate(int half_kernel_size=1) const
Function to dilate 8bit mask map.
Definition: Image.cpp:338
virtual ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
Definition: Image.cpp:47
int BytesPerLine() const
Returns data size per line (row, or the width) in bytes.
Definition: Image.h:122
int bytes_per_channel_
Number of bytes per channel.
Definition: Image.h:225
std::shared_ptr< Image > ConvertDepthToFloatImage(double depth_scale=1000.0, double depth_trunc=3.0) const
Definition: Image.cpp:97
int width_
Width of the image.
Definition: Image.h:219
bool TestImageBoundary(double u, double v, double inner_margin=0.0) const
Test if coordinate (u, v) is located in the inner_marge of the image.
Definition: Image.cpp:51
std::vector< uint8_t > data_
Image storage buffer.
Definition: Image.h:227
static ImagePyramid FilterPyramid(const ImagePyramid &input, Image::FilterType type)
Function to filter image pyramid.
Definition: Image.cpp:238
std::shared_ptr< Image > FlipHorizontal() const
Function to flip image horizontally (from left to right).
Definition: Image.cpp:310
std::shared_ptr< Image > Transpose() const
Definition: Image.cpp:262
FilterType
Specifies the Image filter type.
Definition: Image.h:52
@ Gaussian3
Gaussian filter of size 3 x 3.
@ Gaussian5
Gaussian filter of size 5 x 5.
@ Sobel3Dx
Sobel filter along X-axis.
@ Sobel3Dy
Sobel filter along Y-axis.
@ Gaussian7
Gaussian filter of size 7 x 7.
virtual Eigen::Vector2d GetMin2DBound() const override
Definition: Image.cpp:39
T * PointerAt(int u, int v) const
Function to access the raw data of a single-channel Image.
Definition: Image.cpp:77
std::shared_ptr< Image > CreateDepthBoundaryMask(double depth_threshold_for_discontinuity_check=0.1, int half_dilation_kernel_size_for_discontinuity_map=3) const
Function to create a depthmap boundary mask from depth image.
Definition: Image.cpp:374
std::shared_ptr< Image > Filter(Image::FilterType type) const
Function to filter image with pre-defined filtering type.
Definition: Image.cpp:209
#define LogError(...)
Definition: Logging.h:60
normal_z y
float
normal_z x
CLOUDVIEWER_HOST_DEVICE Pair< First, Second > make_pair(const First &_first, const Second &_second)
Definition: SlabTraits.h:49
std::vector< std::shared_ptr< Image > > ImagePyramid
Typedef and functions for ImagePyramid.
Definition: Image.h:24
MiniVec< float, N > floor(const MiniVec< float, N > &a)
Definition: MiniVec.h:75
Generic file read and write utility for python interface.