Optimized Belief Propagation (CPU and GPU)
stereo.h
Go to the documentation of this file.
1 /*
2 Copyright (C) 2024 Scott Grauer-Gray
3 
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18 
26 /*
27  * stereo.h
28  *
29  * Created on: Feb 4, 2017
30  * Author: scottgg
31  */
32 
33 #ifndef STEREO_H_
34 #define STEREO_H_
35 
36 #include <cstdio>
37 #include <iostream>
38 #include <algorithm>
39 #include <assert.h>
40 #include <string>
41 #include <chrono>
42 #include <iostream>
43 #include <array>
44 #include <utility>
45 #include "image.h"
46 #include "misc.h"
47 #include "pnmfile.h"
48 #include "filter.h"
49 #include "imconv.h"
55 
64 template<typename T, unsigned int DISP_VALS, run_environment::AccSetting ACCELERATION>
65 class RunBpOnStereoSetSingleThreadCPU final : public RunBpOnStereoSet<T, DISP_VALS, ACCELERATION>
66 {
67 public:
68  std::optional<beliefprop::BpRunOutput> operator()(const std::array<std::string, 2>& ref_test_image_path,
69  const beliefprop::BpSettings& alg_settings,
70  const ParallelParams& parallel_params) const override;
71  std::string BpRunDescription() const override { return "Single-Thread CPU"; }
72 
73 private:
74  // compute message
76  void msg(float s1[DISP_VALS], float s2[DISP_VALS], float s3[DISP_VALS], float s4[DISP_VALS],
77  float dst[DISP_VALS], float disc_k_bp) const;
78  void dt(float f[DISP_VALS]) const;
79  bp_single_thread_imp::image<uchar> *output(bp_single_thread_imp::image<float[DISP_VALS]> *u, bp_single_thread_imp::image<float[DISP_VALS]> *d,
80  bp_single_thread_imp::image<float[DISP_VALS]> *l, bp_single_thread_imp::image<float[DISP_VALS]> *r,
81  bp_single_thread_imp::image<float[DISP_VALS]> *data) const;
82  void bp_cb(bp_single_thread_imp::image<float[DISP_VALS]> *u, bp_single_thread_imp::image<float[DISP_VALS]> *d,
83  bp_single_thread_imp::image<float[DISP_VALS]> *l, bp_single_thread_imp::image<float[DISP_VALS]> *r,
84  bp_single_thread_imp::image<float[DISP_VALS]> *data, unsigned int iter, float disc_k_bp) const;
85  std::pair<bp_single_thread_imp::image<uchar>*, RunData> stereo_ms(bp_single_thread_imp::image<uchar> *img1, bp_single_thread_imp::image<uchar> *img2,
86  const beliefprop::BpSettings& alg_settings, std::chrono::duration<double>& runtime) const;
87 };
88 
89 // dt of 1d function
90 template<typename T, unsigned int DISP_VALS, run_environment::AccSetting ACCELERATION>
91 inline void RunBpOnStereoSetSingleThreadCPU<T, DISP_VALS, ACCELERATION>::dt(float f[DISP_VALS]) const {
92  for (unsigned int q = 1; q < DISP_VALS; q++) {
93  float prev = f[q - 1] + 1.0F;
94  if (prev < f[q])
95  f[q] = prev;
96  }
97  for (int q = (int)DISP_VALS - 2; q >= 0; q--) {
98  float prev = f[q + 1] + 1.0F;
99  if (prev < f[q])
100  f[q] = prev;
101  }
102 }
103 
104 // compute message
105 template<typename T, unsigned int DISP_VALS, run_environment::AccSetting ACCELERATION>
107  float s2[DISP_VALS], float s3[DISP_VALS],
108  float s4[DISP_VALS], float dst[DISP_VALS],
109  float disc_k_bp) const {
110  float val;
111 
112  // aggregate and find min
113  float minimum = beliefprop::kHighValBp<float>;
114  for (unsigned int value = 0; value < DISP_VALS; value++) {
115  dst[value] = s1[value] + s2[value] + s3[value] + s4[value];
116  if (dst[value] < minimum)
117  minimum = dst[value];
118  }
119 
120  // dt
121  dt(dst);
122 
123  // truncate
124  minimum += disc_k_bp;
125  for (unsigned int value = 0; value < DISP_VALS; value++)
126  if (minimum < dst[value])
127  dst[value] = minimum;
128 
129  // normalize
130  val = 0;
131  for (unsigned int value = 0; value < DISP_VALS; value++)
132  val += dst[value];
133 
134  val /= DISP_VALS;
135  for (unsigned int value = 0; value < DISP_VALS; value++)
136  dst[value] -= val;
137 }
138 
139 // computation of data costs
140 template<typename T, unsigned int DISP_VALS, run_environment::AccSetting ACCELERATION>
143  unsigned int width{(unsigned int)img1->width()};
144  unsigned int height{(unsigned int)img1->height()};
146 
148  if (alg_settings.smoothing_sigma >= 0.1) {
151  } else {
152  sm1 = imageUCHARtoFLOAT(img1);
153  sm2 = imageUCHARtoFLOAT(img2);
154  }
155 
156  for (unsigned int y = 0; y < height; y++) {
157  for (unsigned int x = DISP_VALS - 1; x < width; x++) {
158  for (unsigned int value = 0; value < DISP_VALS; value++) {
159  const float val = abs(imRef(sm1, x, y) - imRef(sm2, x - value, y));
160  imRef(data, x, y)[value] = alg_settings.lambda_bp * std::min(val, alg_settings.data_k_bp);
161  }
162  }
163  }
164 
165  delete sm1;
166  delete sm2;
167  return data;
168 }
169 
170 // generate output from current messages
171 template<typename T, unsigned int DISP_VALS, run_environment::AccSetting ACCELERATION>
173  bp_single_thread_imp::image<float[DISP_VALS]> *d, bp_single_thread_imp::image<float[DISP_VALS]> *l,
174  bp_single_thread_imp::image<float[DISP_VALS]> *r, bp_single_thread_imp::image<float[DISP_VALS]> *data) const {
175  unsigned int width{(unsigned int)data->width()};
176  unsigned int height{(unsigned int)data->height()};
178 
179  for (unsigned int y = 1; y < height - 1; y++) {
180  for (unsigned int x = 1; x < width - 1; x++) {
181  // keep track of best value for current pixel
182  unsigned int best = 0;
183  float best_val = beliefprop::kHighValBp<float>;
184  for (unsigned int value = 0; value < DISP_VALS; value++) {
185  const float val =
186  imRef(u, x, y+1)[value] +
187  imRef(d, x, y-1)[value] +
188  imRef(l, x+1, y)[value] +
189  imRef(r, x-1, y)[value] +
190  imRef(data, x, y)[value];
191 
192  if (val < best_val) {
193  best_val = val;
194  best = value;
195  }
196  }
197  imRef(out, x, y) = best;
198  }
199  }
200 
201  return out;
202 }
203 
204 // belief propagation using checkerboard update scheme
205 template<typename T, unsigned int DISP_VALS, run_environment::AccSetting ACCELERATION>
207  bp_single_thread_imp::image<float[DISP_VALS]> *l, bp_single_thread_imp::image<float[DISP_VALS]> *r,
208  bp_single_thread_imp::image<float[DISP_VALS]> *data, unsigned int iter, float disc_k_bp) const {
209  unsigned int width{(unsigned int)data->width()};
210  unsigned int height{(unsigned int)data->height()};
211 
212  for (unsigned int t = 0; t < iter; t++) {
213  for (unsigned int y = 1; y < height - 1; y++) {
214  for (unsigned int x = ((y + t) % 2) + 1; x < width - 1; x += 2) {
215 
216  msg(imRef(u, x, y + 1), imRef(l, x + 1, y), imRef(r, x - 1, y),
217  imRef(data, x, y), imRef(u, x, y), disc_k_bp);
218 
219  msg(imRef(d, x, y - 1), imRef(l, x + 1, y), imRef(r, x - 1, y),
220  imRef(data, x, y), imRef(d, x, y), disc_k_bp);
221 
222  msg(imRef(u, x, y + 1), imRef(d, x, y - 1), imRef(r, x - 1, y),
223  imRef(data, x, y), imRef(r, x, y), disc_k_bp);
224 
225  msg(imRef(u, x, y + 1), imRef(d, x, y - 1), imRef(l, x + 1, y),
226  imRef(data, x, y), imRef(l, x, y), disc_k_bp);
227 
228  }
229  }
230  }
231 }
232 
233 // multiscale belief propagation for image restoration
234 template<typename T, unsigned int DISP_VALS, run_environment::AccSetting ACCELERATION>
236  const beliefprop::BpSettings& alg_settings, std::chrono::duration<double>& runtime) const {
242 
243  auto timeStart = std::chrono::system_clock::now();
244 
245  // data costs
246  data[0] = comp_data(img1, img2, alg_settings);
247 
248  // data pyramid
249  for (unsigned int i = 1; i < alg_settings.num_levels; i++) {
250  const unsigned int old_width = (unsigned int)data[i - 1]->width();
251  const unsigned int old_height = (unsigned int)data[i - 1]->height();
252  const unsigned int new_width = (unsigned int)ceil(old_width / 2.0);
253  const unsigned int new_height = (unsigned int)ceil(old_height / 2.0);
254 
255  assert(new_width >= 1);
256  assert(new_height >= 1);
257 
258  data[i] = new bp_single_thread_imp::image<float[DISP_VALS]>(new_width, new_height);
259  for (unsigned int y = 0; y < old_height; y++) {
260  for (unsigned int x = 0; x < old_width; x++) {
261  for (unsigned int value = 0; value < DISP_VALS; value++) {
262  imRef(data[i], x/2, y/2)[value] +=
263  imRef(data[i-1], x, y)[value];
264  }
265  }
266  }
267  }
268 
269  // run bp from coarse to fine
270  for (int i = alg_settings.num_levels - 1; i >= 0; i--) {
271  unsigned int width = (unsigned int)data[i]->width();
272  unsigned int height = (unsigned int)data[i]->height();
273 
274  // allocate & init memory for messages
275  if ((unsigned int)i == (alg_settings.num_levels - 1)) {
276  // in the coarsest level messages are initialized to zero
277  u[i] = new bp_single_thread_imp::image<float[DISP_VALS]>(width, height);
278  d[i] = new bp_single_thread_imp::image<float[DISP_VALS]>(width, height);
279  l[i] = new bp_single_thread_imp::image<float[DISP_VALS]>(width, height);
280  r[i] = new bp_single_thread_imp::image<float[DISP_VALS]>(width, height);
281  } else {
282  // initialize messages from values of previous level
283  u[i] = new bp_single_thread_imp::image<float[DISP_VALS]>(width, height, false);
284  d[i] = new bp_single_thread_imp::image<float[DISP_VALS]>(width, height, false);
285  l[i] = new bp_single_thread_imp::image<float[DISP_VALS]>(width, height, false);
286  r[i] = new bp_single_thread_imp::image<float[DISP_VALS]>(width, height, false);
287 
288  for (unsigned int y = 0; y < height; y++) {
289  for (unsigned int x = 0; x < width; x++) {
290  for (unsigned int value = 0; value < DISP_VALS; value++) {
291  imRef(u[i], x, y)[value] =
292  imRef(u[i+1], x/2, y/2)[value];
293  imRef(d[i], x, y)[value] =
294  imRef(d[i+1], x/2, y/2)[value];
295  imRef(l[i], x, y)[value] =
296  imRef(l[i+1], x/2, y/2)[value];
297  imRef(r[i], x, y)[value] =
298  imRef(r[i+1], x/2, y/2)[value];
299  }
300  }
301  }
302  // delete old messages and data
303  delete u[i + 1];
304  delete d[i + 1];
305  delete l[i + 1];
306  delete r[i + 1];
307  delete data[i + 1];
308  }
309 
310  // BP
311  bp_cb(u[i], d[i], l[i], r[i], data[i], alg_settings.num_iterations, alg_settings.disc_k_bp);
312  }
313 
314  bp_single_thread_imp::image<uchar> *out = output(u[0], d[0], l[0], r[0], data[0]);
315 
316  auto timeEnd = std::chrono::system_clock::now();
317  runtime = timeEnd-timeStart;
318 
319  RunData run_data;
320  run_data.AddDataWHeader(std::string(run_eval::kSingleThreadRuntimeHeader), runtime.count());
321 
322  delete u[0];
323  delete d[0];
324  delete l[0];
325  delete r[0];
326  delete data[0];
327 
328  return {out, run_data};
329 }
330 
331 template<typename T, unsigned int DISP_VALS, run_environment::AccSetting ACCELERATION>
332 inline std::optional<beliefprop::BpRunOutput> RunBpOnStereoSetSingleThreadCPU<T, DISP_VALS, ACCELERATION>::operator()(const std::array<std::string, 2>& ref_test_image_path,
333  const beliefprop::BpSettings& alg_settings, const ParallelParams& parallel_params) const
334 {
335  //return no value if acceleration setting is not NONE
336  if constexpr (ACCELERATION != run_environment::AccSetting::kNone) {
337  return {};
338  }
339 
340  //load input
342  img1 = loadPGMOrPPMImage(ref_test_image_path[0].c_str());
343  img2 = loadPGMOrPPMImage(ref_test_image_path[1].c_str());
344 
345  //run single-thread belief propagation implementation and return output
346  //disparity map and run data
347  std::chrono::duration<double> runtime;
348  const auto [output_disp_map, output_run_data] = stereo_ms(img1, img2, alg_settings, runtime);
349 
350  //setup run output to return
351  std::optional<beliefprop::BpRunOutput> output{beliefprop::BpRunOutput{}};
352  output->run_time = runtime;
353  output->run_data = output_run_data;
354  output->out_disparity_map =
356  std::array<unsigned int, 2>{(unsigned int)img1->width(), (unsigned int)img1->height()});
357 
358  //set disparity at each point in disparity map from single-thread run output
359  for (unsigned int y = 0; y < (unsigned int)img1->height(); y++) {
360  for (unsigned int x = 0; x < (unsigned int)img1->width(); x++) {
361  output->out_disparity_map.SetPixelAtPoint({x, y}, (float)imRef(output_disp_map, x, y));
362  }
363  }
364 
365  //free dynamically allocated memory
366  delete img1;
367  delete img2;
368  delete output_disp_map;
369 
370  //return run output
371  return output;
372 }
373 
374 #endif /* STEREO_H_ */
File with namespace for enums, constants, structures, and functions specific to belief propagation pr...
Header file that contains information about the stereo sets used for evaluation of the bp implementat...
Declares child class of ParallelParams to store and process parallelization parameters to use in each...
Declares abstract class to set up and run belief propagation on target device using specified acceler...
Declares and defines structure that stores settings for current implementation run as well as functio...
void SetPixelAtPoint(const std::array< unsigned int, 2 > &point_xy, T val)
Definition: BpImage.h:97
Abstract class for holding and processing parallelization parameters. Child class(es) specific to im...
Abstract class to set up and run belief propagation on target device using specified acceleration.
Child class of RunBpOnStereoSet to run single-threaded CPU implementation of belief propagation on a ...
Definition: stereo.h:66
std::optional< beliefprop::BpRunOutput > operator()(const std::array< std::string, 2 > &ref_test_image_path, const beliefprop::BpSettings &alg_settings, const ParallelParams &parallel_params) const override
Pure virtual operator() overload that must be defined in child class.
Definition: stereo.h:332
std::string BpRunDescription() const override
Pure virtual function to return run description corresponding to target acceleration.
Definition: stereo.h:71
Class to store headers with data corresponding to current program run and evaluation.
Definition: RunData.h:42
void AddDataWHeader(const std::string &header, const std::string &data)
Add string data with header describing added data.
Definition: RunData.cpp:49
static image< float > * smooth(image< float > *src, float sigma)
Definition: filter.h:70
int height() const
Definition: image.h:51
#define imRef(im, x, y)
Definition: image.h:64
T abs(const T &x)
Definition: misc.h:45
constexpr std::string_view kSingleThreadRuntimeHeader
Structure with output disparity map, runtime, and other evaluation data.
std::chrono::duration< double > run_time
Structure to store the belief propagation settings including the number of levels and iterations.
Definition: BpSettings.h:87
unsigned int num_iterations
Definition: BpSettings.h:90
unsigned int num_levels
Definition: BpSettings.h:89
float disc_k_bp
Discontinuity cost cap set to high value by default but is expected to be dependent on number of disp...
Definition: BpSettings.h:98