41 float Median(std::vector<T>* elems) {
42 CHECK(!elems->empty());
43 const size_t mid_idx = elems->size() / 2;
44 std::nth_element(elems->begin(), elems->begin() + mid_idx, elems->end());
45 if (elems->size() % 2 == 0) {
46 const float mid_element1 =
static_cast<float>((*elems)[mid_idx]);
47 const float mid_element2 =
static_cast<float>(
48 *std::max_element(elems->begin(), elems->begin() + mid_idx));
49 return (mid_element1 + mid_element2) / 2.0f;
51 return static_cast<float>((*elems)[mid_idx]);
58 int FindNextImage(
const std::vector<std::vector<int>>& overlapping_images,
59 const std::vector<char>& used_images,
60 const std::vector<char>& fused_images,
61 const int prev_image_idx) {
62 CHECK_EQ(used_images.size(), fused_images.size());
64 if (prev_image_idx >= 0 &&
65 static_cast<size_t>(prev_image_idx) < overlapping_images.size()) {
66 for (
const auto image_idx : overlapping_images[prev_image_idx]) {
68 static_cast<size_t>(image_idx) < used_images.size() &&
69 static_cast<size_t>(image_idx) < fused_images.size() &&
70 used_images[image_idx] && !fused_images[image_idx]) {
78 for (
size_t image_idx = 0; image_idx < fused_images.size(); ++image_idx) {
79 if (used_images[image_idx] && !fused_images[image_idx]) {
90 #define PrintOption(option) std::cout << #option ": " << option << std::endl
103 const auto& bbox_min =
bounding_box.first.transpose().eval();
104 const auto& bbox_max =
bounding_box.second.transpose().eval();
123 const std::string& workspace_path,
124 const std::string& workspace_format,
125 const std::string& pmvs_option_name,
126 const std::string& input_type)
128 workspace_path_(workspace_path),
129 workspace_format_(workspace_format),
130 pmvs_option_name_(pmvs_option_name),
131 input_type_(input_type),
132 max_squared_reproj_error_(options_.max_reproj_error *
133 options_.max_reproj_error),
134 min_cos_normal_error_(
std::cos(
DegToRad(options_.max_normal_error))) {
135 CHECK(options_.
Check());
139 return fused_points_;
144 return fused_points_visibility_;
147 void StereoFusion::Run() {
148 fused_points_.clear();
149 fused_points_visibility_.clear();
154 std::cout <<
"Reading workspace..." <<
std::endl;
158 auto workspace_format_lower_case = workspace_format_;
160 if (workspace_format_lower_case ==
"pmvs") {
174 workspace_path_, workspace_options.
stereo_folder,
"fusion.cfg"));
177 workspace_.reset(
new CachedWorkspace(workspace_options));
179 workspace_.reset(
new Workspace(workspace_options));
180 workspace_->Load(image_names);
189 std::cout <<
"Reading configuration..." <<
std::endl;
191 const auto& model = workspace_->GetModel();
193 const double kMinTriangulationAngle = 0;
194 if (model.GetMaxOverlappingImagesFromPMVS().empty()) {
195 overlapping_images_ = model.GetMaxOverlappingImages(
198 overlapping_images_ = model.GetMaxOverlappingImagesFromPMVS();
201 task_fused_points_.resize(num_threads);
202 task_fused_points_visibility_.resize(num_threads);
204 used_images_.resize(model.images.size(),
false);
205 fused_images_.resize(model.images.size(),
false);
206 fused_pixel_masks_.resize(model.images.size());
207 depth_map_sizes_.resize(model.images.size());
208 bitmap_scales_.resize(model.images.size());
209 P_.resize(model.images.size());
210 inv_P_.resize(model.images.size());
211 inv_R_.resize(model.images.size());
213 for (
const auto& image_name : image_names) {
214 const int image_idx = model.GetImageIdx(image_name);
216 if (!workspace_->HasBitmap(image_idx) ||
217 !workspace_->HasDepthMap(image_idx) ||
218 !workspace_->HasNormalMap(image_idx)) {
220 "WARNING: Ignoring image %s, because input "
227 const auto&
image = model.images.at(image_idx);
228 const auto& depth_map = workspace_->GetDepthMap(image_idx);
230 used_images_.at(image_idx) =
true;
232 InitFusedPixelMask(image_idx, depth_map.GetWidth(),
233 depth_map.GetHeight());
235 depth_map_sizes_.at(image_idx) =
239 static_cast<float>(depth_map.GetWidth()) /
image.GetWidth(),
240 static_cast<float>(depth_map.GetHeight()) /
image.GetHeight());
242 Eigen::Matrix<float, 3, 3, Eigen::RowMajor> K =
243 Eigen::Map<const Eigen::Matrix<float, 3, 3, Eigen::RowMajor>>(
245 K(0, 0) *= bitmap_scales_.at(image_idx).first;
246 K(0, 2) *= bitmap_scales_.at(image_idx).first;
247 K(1, 1) *= bitmap_scales_.at(image_idx).second;
248 K(1, 2) *= bitmap_scales_.at(image_idx).second;
251 P_.at(image_idx).data());
253 inv_P_.at(image_idx).data());
254 inv_R_.at(image_idx) =
255 Eigen::Map<const Eigen::Matrix<float, 3, 3, Eigen::RowMajor>>(
260 std::cout <<
StringPrintf(
"Starting fusion with %d threads", num_threads)
262 ThreadPool thread_pool(num_threads);
267 const int kRowStride = 10;
268 auto ProcessImageRows = [&,
this](
const int row_start,
const int height,
269 const int width,
const int image_idx,
270 const Mat<char>& fused_pixel_mask) {
271 const int row_end = std::min(
height, row_start + kRowStride);
272 for (
int row = row_start; row < row_end; ++row) {
273 for (
int col = 0; col <
width; ++col) {
274 if (fused_pixel_mask.Get(row, col) > 0) {
277 const int thread_id = thread_pool.GetThreadIndex();
278 Fuse(thread_id, image_idx, row, col);
283 size_t num_fused_images = 0;
284 size_t total_fused_points = 0;
285 for (
int image_idx = 0; image_idx >= 0;
287 fused_images_, image_idx)) {
295 std::cout <<
StringPrintf(
"Fusing image [%d/%d] with index %d",
296 num_fused_images + 1, model.images.size(),
300 const int width = depth_map_sizes_.at(image_idx).first;
301 const int height = depth_map_sizes_.at(image_idx).second;
302 const auto& fused_pixel_mask = fused_pixel_masks_.at(image_idx);
304 for (
int row_start = 0; row_start <
height; row_start += kRowStride) {
305 thread_pool.AddTask(ProcessImageRows, row_start,
height,
width,
306 image_idx, fused_pixel_mask);
310 num_fused_images += 1;
311 fused_images_.at(image_idx) =
true;
313 total_fused_points = 0;
314 for (
const auto& task_fused_points : task_fused_points_) {
315 total_fused_points += task_fused_points.size();
318 timer.ElapsedSeconds(), total_fused_points)
322 fused_points_.reserve(total_fused_points);
323 fused_points_visibility_.reserve(total_fused_points);
324 for (
size_t thread_id = 0; thread_id < task_fused_points_.size();
326 fused_points_.insert(fused_points_.end(),
327 task_fused_points_[thread_id].begin(),
328 task_fused_points_[thread_id].end());
329 task_fused_points_[thread_id].clear();
331 fused_points_visibility_.insert(
332 fused_points_visibility_.end(),
333 task_fused_points_visibility_[thread_id].begin(),
334 task_fused_points_visibility_[thread_id].end());
335 task_fused_points_visibility_[thread_id].clear();
338 if (fused_points_.empty()) {
339 std::cout <<
"WARNING: Could not fuse any points. This is likely "
341 "incorrect settings - filtering must be enabled for the "
343 "call to patch match stereo."
347 std::cout <<
"Number of fused points: " << fused_points_.size()
352 void StereoFusion::InitFusedPixelMask(
int image_idx,
356 Mat<char>& fused_pixel_mask = fused_pixel_masks_.at(image_idx);
357 const std::string mask_path =
359 workspace_->GetModel().GetImageName(image_idx) +
".png");
362 mask.Read(mask_path,
false)) {
363 BitmapColor<uint8_t>
color;
364 mask.Rescale(
static_cast<int>(
width),
static_cast<int>(
height),
366 for (
size_t row = 0; row <
height; ++row) {
367 for (
size_t col = 0; col <
width; ++col) {
368 mask.GetPixel(col, row, &
color);
369 fused_pixel_mask.Set(row, col,
color.r == 0 ? 1 : 0);
373 fused_pixel_mask.Fill(0);
377 void StereoFusion::Fuse(
const int thread_id,
382 std::vector<FusionData> fusion_queue;
383 fusion_queue.emplace_back(image_idx, row, col, 0);
385 Eigen::Vector4f fused_ref_point = Eigen::Vector4f::Zero();
386 Eigen::Vector3f fused_ref_normal = Eigen::Vector3f::Zero();
389 std::vector<float> fused_point_x;
390 std::vector<float> fused_point_y;
391 std::vector<float> fused_point_z;
392 std::vector<float> fused_point_nx;
393 std::vector<float> fused_point_ny;
394 std::vector<float> fused_point_nz;
395 std::vector<uint8_t> fused_point_r;
396 std::vector<uint8_t> fused_point_g;
397 std::vector<uint8_t> fused_point_b;
398 std::unordered_set<int> fused_point_visibility;
400 while (!fusion_queue.empty()) {
401 const auto data = fusion_queue.back();
402 const int image_idx =
data.image_idx;
403 const int row =
data.row;
404 const int col =
data.col;
405 const int traversal_depth =
data.traversal_depth;
407 fusion_queue.pop_back();
410 auto& fused_pixel_mask = fused_pixel_masks_.at(image_idx);
411 if (fused_pixel_mask.Get(row, col) > 0) {
415 const auto& depth_map = workspace_->GetDepthMap(image_idx);
416 const float depth = depth_map.Get(row, col);
425 if (traversal_depth > 0) {
427 const Eigen::Vector3f proj = P_.at(image_idx) * fused_ref_point;
430 if (std::abs(proj(2)) < std::numeric_limits<float>::epsilon()) {
435 const float depth_error = std::abs((proj(2) - depth) / depth);
441 const float col_diff = proj(0) / proj(2) - col;
442 const float row_diff = proj(1) / proj(2) - row;
443 const float squared_reproj_error =
444 col_diff * col_diff + row_diff * row_diff;
445 if (squared_reproj_error > max_squared_reproj_error_) {
451 const auto& normal_map = workspace_->GetNormalMap(image_idx);
452 const Eigen::Vector3f
normal =
453 inv_R_.at(image_idx) *
454 Eigen::Vector3f(normal_map.Get(row, col, 0),
455 normal_map.Get(row, col, 1),
456 normal_map.Get(row, col, 2));
459 if (traversal_depth > 0) {
460 const float cos_normal_error = fused_ref_normal.dot(
normal);
461 if (cos_normal_error < min_cos_normal_error_) {
467 const Eigen::Vector3f xyz =
468 inv_P_.at(image_idx) *
469 Eigen::Vector4f(col * depth, row * depth, depth, 1.0f);
472 BitmapColor<uint8_t>
color;
473 const auto& bitmap_scale = bitmap_scales_.at(image_idx);
474 workspace_->GetBitmap(image_idx).InterpolateNearestNeighbor(
475 col / bitmap_scale.first, row / bitmap_scale.second, &
color);
478 fused_pixel_mask.Set(row, col, 1);
491 fused_point_x.push_back(xyz(0));
492 fused_point_y.push_back(xyz(1));
493 fused_point_z.push_back(xyz(2));
494 fused_point_nx.push_back(
normal(0));
495 fused_point_ny.push_back(
normal(1));
496 fused_point_nz.push_back(
normal(2));
497 fused_point_r.push_back(
color.r);
498 fused_point_g.push_back(
color.g);
499 fused_point_b.push_back(
color.b);
500 fused_point_visibility.insert(image_idx);
503 if (traversal_depth == 0) {
504 fused_ref_point = Eigen::Vector4f(xyz(0), xyz(1), xyz(2), 1.0f);
505 fused_ref_normal =
normal;
508 if (fused_point_x.size() >=
517 if (
static_cast<size_t>(image_idx) < overlapping_images_.size()) {
518 for (
const auto next_image_idx : overlapping_images_[image_idx]) {
519 if (next_image_idx < 0 ||
520 static_cast<size_t>(next_image_idx) >=
521 used_images_.size() ||
522 static_cast<size_t>(next_image_idx) >=
523 fused_images_.size() ||
524 !used_images_[next_image_idx] ||
525 fused_images_[next_image_idx]) {
529 if (
static_cast<size_t>(next_image_idx) >= P_.size()) {
530 std::cout <<
"P_ size: " << P_.size() <<
std::endl;
531 std::cout <<
"next_image_idx: " << next_image_idx
536 const Eigen::Vector3f next_proj =
537 P_[next_image_idx] * xyz.homogeneous();
540 if (std::abs(next_proj(2)) <
541 std::numeric_limits<float>::epsilon()) {
542 std::cout <<
"next_proj(2) is zero: " << next_proj(2)
547 const int next_col =
static_cast<int>(
548 std::round(next_proj(0) / next_proj(2)));
549 const int next_row =
static_cast<int>(
550 std::round(next_proj(1) / next_proj(2)));
552 if (
static_cast<size_t>(next_image_idx) >=
553 depth_map_sizes_.size()) {
554 std::cout <<
"depth_map_sizes_ size: "
556 std::cout <<
"next_image_idx: " << next_image_idx
561 const auto& depth_map_size = depth_map_sizes_[next_image_idx];
562 if (next_col < 0 || next_row < 0 ||
563 next_col >= depth_map_size.first ||
564 next_row >= depth_map_size.second) {
567 fusion_queue.emplace_back(next_image_idx, next_row, next_col,
568 traversal_depth + 1);
573 const size_t num_pixels = fused_point_x.size();
576 if (fused_point_x.empty() || fused_point_y.empty() ||
577 fused_point_z.empty() || fused_point_nx.empty() ||
578 fused_point_ny.empty() || fused_point_nz.empty() ||
579 fused_point_r.empty() || fused_point_g.empty() ||
580 fused_point_b.empty()) {
581 std::cout <<
"fused_point_x.empty(): " << fused_point_x.empty()
583 std::cout <<
"fused_point_y.empty(): " << fused_point_y.empty()
585 std::cout <<
"fused_point_z.empty(): " << fused_point_z.empty()
587 std::cout <<
"fused_point_nx.empty(): " << fused_point_nx.empty()
589 std::cout <<
"fused_point_ny.empty(): " << fused_point_ny.empty()
591 std::cout <<
"fused_point_nz.empty(): " << fused_point_nz.empty()
593 std::cout <<
"fused_point_r.empty(): " << fused_point_r.empty()
598 PlyPoint fused_point;
600 Eigen::Vector3f fused_normal;
604 const float fused_normal_norm = fused_normal.norm();
605 if (fused_normal_norm < std::numeric_limits<float>::epsilon()) {
613 fused_point.nx = fused_normal.x() / fused_normal_norm;
614 fused_point.ny = fused_normal.y() / fused_normal_norm;
615 fused_point.nz = fused_normal.z() / fused_normal_norm;
617 fused_point.r = TruncateCast<float, uint8_t>(
619 fused_point.g = TruncateCast<float, uint8_t>(
621 fused_point.b = TruncateCast<float, uint8_t>(
624 if (
static_cast<size_t>(thread_id) < task_fused_points_.size() &&
625 static_cast<size_t>(thread_id) <
626 task_fused_points_visibility_.size()) {
627 task_fused_points_[thread_id].push_back(fused_point);
628 task_fused_points_visibility_[thread_id].emplace_back(
629 fused_point_visibility.begin(),
630 fused_point_visibility.end());
636 const std::string&
path,
637 const std::vector<std::vector<int>>& points_visibility) {
638 std::fstream file(
path, std::ios::out | std::ios::binary);
639 CHECK(file.is_open()) <<
path;
641 WriteBinaryLittleEndian<uint64_t>(&file, points_visibility.size());
643 for (
const auto& visibility : points_visibility) {
644 WriteBinaryLittleEndian<uint32_t>(&file, visibility.size());
645 for (
const auto& image_idx : visibility) {
646 WriteBinaryLittleEndian<uint32_t>(&file, image_idx);
std::shared_ptr< core::Tensor > image
const Timer & GetTimer() const
void PrintMinutes() const
const std::vector< std::vector< int > > & GetFusedPointsVisibility() const
const std::vector< PlyPoint > & GetFusedPoints() const
StereoFusion(const StereoFusionOptions &options, const std::string &workspace_path, const std::string &workspace_format, const std::string &pmvs_option_name, const std::string &input_type)
#define PrintOption(option)
#define CHECK_OPTION_LE(val1, val2)
#define CHECK_OPTION_GT(val1, val2)
#define CHECK_OPTION_GE(val1, val2)
QTextStream & endl(QTextStream &stream)
CLOUDVIEWER_HOST_DEVICE Pair< First, Second > make_pair(const First &_first, const Second &_second)
static const std::string path
float Median(std::vector< T > *elems)
int FindNextImage(const std::vector< std::vector< int >> &overlapping_images, const std::vector< char > &used_images, const std::vector< char > &fused_images, const int prev_image_idx)
void WritePointsVisibility(const std::string &path, const std::vector< std::vector< int >> &points_visibility)
void ComposeInverseProjectionMatrix(const float K[9], const float R[9], const float T[3], float inv_P[12])
void ComposeProjectionMatrix(const float K[9], const float R[9], const float T[3], float P[12])
void PrintHeading2(const std::string &heading)
void StringToLower(std::string *str)
std::vector< std::string > ReadTextFileLines(const std::string &path)
bool ExistsFile(const std::string &path)
std::string JoinPaths(T const &... paths)
std::string StringPrintf(const char *format,...)
float DegToRad(const float deg)
int GetEffectiveNumThreads(const int num_threads)
std::pair< Eigen::Vector3f, Eigen::Vector3f > bounding_box
std::string workspace_format
std::string stereo_folder
std::string workspace_path