source/depth_estimation/UpsampleDisparityLib.cpp (142 lines of code) (raw):
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "source/depth_estimation/UpsampleDisparityLib.h"
#include <utility>
#include <vector>
#include <glog/logging.h>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include "DerpUtil.h"
#include "source/util/Camera.h"
#include "source/util/MathUtil.h"
#include "source/util/ThreadPool.h"
namespace fb360_dep {
namespace depth_estimation {
// Clock-wise outward spiral starting at (0, 0) of diameter w
static std::vector<std::pair<int, int>> spiral(const int w) {
int x = 0;
int y = 0;
int dx = 0; // 1: right, -1: left, 0: don't move
int dy = -1;
int t = w;
int samples = t * t;
std::vector<std::pair<int, int>> locs;
for (int i = 0; i < samples; ++i) {
const bool isValidX = (-w / 2 <= x) && (x <= w / 2);
const bool isValidY = (-w / 2 <= y) && (y <= w / 2);
if (isValidX && isValidY) {
locs.push_back(std::make_pair(x, y));
}
const bool isCorner = x == y;
const bool isEdgeLeftX = (x < 0) && (x == -y);
const bool isEdgeRightX = (x > 0) && (x == 1 - y);
if (isCorner || isEdgeLeftX || isEdgeRightX) {
// Rotate 90 degrees and switch direction of x
t = dx;
dx = -dy;
dy = t;
}
x += dx;
y += dy;
}
return locs;
}
static cv::Mat_<float> replaceNans(
const cv::Mat_<float>& dispUp,
const cv::Mat_<float>& bgDispUp,
const cv::Mat_<bool>& maskUp,
const int radius) {
cv::Mat_<bool> maskNan(maskUp.size());
maskUp.copyTo(maskNan);
maskNan.setTo(false, dispUp > 0); // true = NAN inside mask
std::vector<cv::Point> nanLocs;
cv::findNonZero(maskNan, nanLocs);
cv::Mat_<float> dispOut(dispUp.size());
dispUp.copyTo(dispOut);
const std::vector<std::pair<int, int>> spiralLocs = spiral(radius * 2 + 1);
for (const cv::Point& p : nanLocs) {
// Find valid pixel in neighborhood
for (const auto& loc : spiralLocs) {
const int xx = math_util::clamp(p.x + loc.first, 0, maskNan.cols - 1);
const int yy = math_util::clamp(p.y + loc.second, 0, maskNan.rows - 1);
const float d = dispUp(yy, xx);
if (d > 0) {
dispOut(p.y, p.x) = d;
break;
}
}
}
for (int y = 0; y < dispOut.rows; ++y) {
for (int x = 0; x < dispOut.cols; ++x) {
if (std::isnan(dispOut(y, x)) || dispOut(y, x) == 0) {
dispOut(y, x) = bgDispUp(y, x);
}
}
}
return dispOut;
}
int getRadius(const cv::Size& size, const cv::Size& sizeUp) {
const float scale = float(sizeUp.width) / float(size.width);
return scale * scale + 1;
}
static void upsampleDisparityInPlace(
cv::Mat_<float>& dispUp,
const cv::Mat_<float>& disp,
const cv::Mat_<float>& bgDispUp,
const cv::Mat_<bool>& mask,
const cv::Mat_<bool>& maskUpIn,
const cv::Size& sizeUp,
const bool useForegroundMasks) {
// NOTE: This trick is only for foreground disparities
// The background disparity can be upscaled separately calling this app without a mask,
// and used to fill the NaNs outside the full-size mask
// 1) Downscale mask to disparity size, and set disparity outside the mask to NAN
// This prevents background disparities to be part of the upscaling
// 2) Upscale disparity using nearest pixel
// Any window contain NANs interpolates to NAN. Nearest pixel does not interpolate
// 3) Remove disparities outside full-size mask
// 4) Replace NAN pixels inside full-size mask with the closest valid value
// 5) Apply joint bilateral filter
const int radius = getRadius(mask.size(), sizeUp);
if (useForegroundMasks) {
// 1)
cv::Mat_<float> dispSmallMasked = disp.clone();
dispSmallMasked.setTo(NAN, mask == 0);
// 2)
cv::Mat_<float> dispUpMasked;
cv::resize(dispSmallMasked, dispUpMasked, sizeUp, 0, 0, cv::INTER_NEAREST);
// 3)
cv::Mat_<bool> maskUp;
if (maskUpIn.size() != sizeUp) {
LOG(WARNING) << "Warning: Desired resolution does not match mask resolution: " << sizeUp
<< " vs. " << maskUpIn.size() << ". Rescaling mask to " << sizeUp;
cv::resize(maskUp, maskUpIn, sizeUp, 0, 0, cv::INTER_NEAREST);
} else {
maskUp = maskUpIn;
}
dispUpMasked.setTo(NAN, maskUp == 0);
// 4)
dispUp = replaceNans(dispUpMasked, bgDispUp, maskUp, radius);
} else {
// OpenCV doesn't handle NaNs when resizing
const float minDisp = 1e-4;
cv::Mat_<float> dispSmallMasked = disp.clone();
dispSmallMasked.setTo(minDisp, disp != disp);
cv::resize(dispSmallMasked, dispUp, sizeUp, 0, 0, cv::INTER_LANCZOS4);
}
}
std::vector<cv::Mat_<float>> upsampleDisparities(
const Camera::Rig& rigIn,
const std::vector<cv::Mat_<float>>& disps,
const std::vector<cv::Mat_<float>>& bgDispsUp,
const std::vector<cv::Mat_<bool>>& masks,
const std::vector<cv::Mat_<bool>>& masksUpIn,
const cv::Size& sizeUp,
const bool useForegroundMasks,
const int threads) {
CHECK_EQ(disps.size(), masks.size());
CHECK_EQ(disps.size(), masksUpIn.size());
Camera::Rig rig = Camera::Rig(rigIn);
Camera::normalizeRig(rig);
const std::vector<cv::Mat_<bool>> fovMasks =
depth_estimation::generateFovMasks(rig, disps[0].size(), threads);
const std::vector<cv::Mat_<bool>> fovMasksUp =
depth_estimation::generateFovMasks(rig, sizeUp, threads);
std::vector<cv::Mat_<float>> dispsUp(disps.size());
ThreadPool threadPool(threads);
for (int i = 0; i < int(disps.size()); ++i) {
threadPool.spawn(
&upsampleDisparityInPlace,
std::ref(dispsUp[i]),
std::cref(disps[i]),
std::cref(bgDispsUp[i]),
fovMasks[i] & masks[i],
fovMasksUp[i] & masksUpIn[i],
std::cref(sizeUp),
useForegroundMasks);
}
threadPool.join();
return dispsUp;
}
} // namespace depth_estimation
} // namespace fb360_dep