source/util/ImageUtil.cpp (136 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/util/ImageUtil.h"
#include <boost/algorithm/string/split.hpp>
namespace fb360_dep {
namespace image_util {
static inline const std::vector<filesystem::path> checkAndGetSortedFiles(
const filesystem::path& imageDir,
const Camera::Rig& rig) {
CHECK_GT(rig.size(), 0);
filesystem::path camDir = imageDir / rig[0].id;
CHECK(filesystem::exists(camDir)) << folly::sformat("No folder found at {}", camDir.string());
const bool includeHidden = false;
const std::vector<filesystem::path> sortedFiles =
filesystem::getFilesSorted(camDir, includeHidden);
CHECK_GT(sortedFiles.size(), 0) << folly::sformat("No files found in {}", camDir.string());
return sortedFiles;
}
// Get first lexical frame if flag isn't filled in and validate the frame
int getSingleFrame(const filesystem::path& imageDir, const Camera::Rig& rig, std::string frame) {
if (frame == "") {
const std::vector<filesystem::path>& sortedFiles = checkAndGetSortedFiles(imageDir, rig);
frame = sortedFiles.front().stem().string();
}
verifyImagePaths(imageDir, rig, frame, frame);
return std::stoi(frame);
}
// Gets first and last lexical frame if flags aren't filled in and
// validates the frame range
std::pair<int, int> getFrameRange(
const filesystem::path& imageDir,
const Camera::Rig& rig,
std::string firstFrame,
std::string lastFrame) {
if (firstFrame == "" || lastFrame == "") {
const std::vector<filesystem::path>& sortedFiles = checkAndGetSortedFiles(imageDir, rig);
if (firstFrame == "") {
firstFrame = sortedFiles.front().stem().string();
}
if (lastFrame == "") {
lastFrame = sortedFiles.back().stem().string();
}
}
verifyImagePaths(imageDir, rig, firstFrame, lastFrame);
return std::make_pair<int, int>(std::stoi(firstFrame), std::stoi(lastFrame));
}
// Generates paths to images, one vector per camera
void verifyImagePaths(
const filesystem::path& imageDir,
const Camera::Rig& rig,
const std::string& firstFrame,
const std::string& lastFrame,
const std::string& extension) {
int first;
int last;
try {
first = std::stoi(firstFrame);
} catch (...) {
CHECK(false) << "Invalid frame name: " << firstFrame;
}
try {
last = std::stoi(lastFrame);
} catch (...) {
CHECK(false) << "Invalid frame name: " << lastFrame;
}
CHECK_LE(first, last);
CHECK_GT(rig.size(), 0);
const std::string extGuess = filesystem::getFirstExtension(imageDir / rig[0].id);
const std::string ext = !extension.empty() ? extension : extGuess;
for (const Camera& cam : rig) {
const filesystem::path camDir = imageDir / cam.id;
for (int frameNum = first; frameNum <= last; ++frameNum) {
const std::string frameName = intToStringZeroPad(frameNum, 6);
const filesystem::path p = camDir / (frameName + ext);
const bool exists = filesystem::is_regular_file(p);
CHECK(exists) << "Missing file: " << p;
}
}
}
// Returns a value between min disparity and max disparity.
// Since we take non-rational steps, we cannot guarantee perfect precision on
// ends of the range, but we can on the first one (probe = 0), so we choose it
// to be at the furthest disparity (= closest depth)
double probeDisparity(
const int probe,
const int probeCount,
const double minDisparity,
const double maxDisparity) {
const double fraction = double(probe) / double(probeCount - 1);
return fraction * minDisparity + (1 - fraction) * maxDisparity;
}
// Assuming comma-separated list of destinations as input
Camera::Rig filterDestinations(const Camera::Rig rigIn, const std::string& destinations) {
Camera::Rig rigOut;
if (destinations.empty()) {
return rigIn;
}
std::vector<std::string> destVec;
boost::split(destVec, destinations, [](char c) { return c == ','; });
for (const std::string& dest : destVec) {
for (auto& cam : rigIn) {
if (cam.id == dest) {
rigOut.push_back(cam);
}
}
}
return rigOut;
}
Camera::Vector2 worldToEquirect(const Camera::Vector3 world, const int eqrW, const int eqrH) {
const float depth = world.norm();
const float x = world.x() / depth;
const float y = world.y() / depth;
const float z = world.z() / depth;
const float phi = acos(z);
float theta = atan2(y, x);
if (theta > 0) {
theta -= 2 * M_PI;
}
const float v = phi / M_PI;
const float u = -theta / (2.0f * M_PI);
return Camera::Vector2(u * eqrW, v * eqrH);
}
cv::Mat_<cv::Vec2f> computeWarpDstToSrc(const Camera& dst, const Camera& src) {
const cv::Size srcSize(src.resolution.x(), src.resolution.y());
const cv::Size dstSize(dst.resolution.x(), dst.resolution.y());
cv::Mat_<cv::Vec2f> warpMap(dstSize, cv::Vec2f(NAN, NAN));
if (dst.id == src.id) {
return warpMap;
}
for (int y = 0; y < warpMap.rows; ++y) {
for (int x = 0; x < warpMap.cols; ++x) {
const Camera::Vector2 dstPixel(x + 0.5, y + 0.5);
if (dst.isOutsideImageCircle(dstPixel)) {
continue;
}
const Camera::Vector3 rig = dst.rigNearInfinity(dstPixel);
Camera::Vector2 srcPixel;
if (!src.sees(rig, srcPixel)) {
continue;
}
// note: convert to opencv coordinate convention because it must be fed to cv::remap later
warpMap(y, x) = cv::Vec2f(srcPixel.x() - 0.5f, srcPixel.y() - 0.5f);
}
}
return warpMap;
}
} // namespace image_util
} // namespace fb360_dep