source/conversion/ProjectEquirectsToCameras.cpp (107 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. */ const char* kUsage = R"( - Reads equirect masks and projects them to individual cameras assuming a given depth. - Example: ./ProjectEquirectsToCameras \ --eqr_masks=/path/to/video/equirect_masks/ \ --rig=/path/to/rigs/rig.json \ --first=000000 \ --last=000000 \ --output=/path/to/output/ )"; #include <gflags/gflags.h> #include <glog/logging.h> #include "source/util/Camera.h" #include "source/util/ImageUtil.h" #include "source/util/SystemUtil.h" #include "source/util/ThreadPool.h" using namespace fb360_dep; using namespace fb360_dep::image_util; DEFINE_string(cameras, "", "comma-separated cameras to render (empty for all)"); DEFINE_double(depth, 1000, "depth to project at (m)"); DEFINE_string(eqr_masks, "", "path to input equirect masks (required)"); DEFINE_string(file_type, "png", "Supports any image type allowed in OpenCV"); DEFINE_string(first, "000000", "first frame to process (lexical) (required)"); DEFINE_string(last, "000000", "last frame to process (lexical) (required)"); DEFINE_string(output, "", "output directory (required)"); DEFINE_string(rig, "", "path to camera rig .json (required)"); DEFINE_int32(threads, -1, "number of threads (-1 = auto, 0 = none)"); DEFINE_int32(width, 0, "width of projected camera images (0 = size from rig file)"); void verifyInputs(const Camera::Rig& rig) { CHECK_NE(FLAGS_eqr_masks, ""); CHECK_NE(FLAGS_first, ""); CHECK_NE(FLAGS_last, ""); CHECK_NE(FLAGS_output, ""); CHECK_GT(FLAGS_depth, 0); CHECK_GE(FLAGS_width, 0); CHECK_EQ(FLAGS_width % 2, 0) << "equirect width must be a multiple of 2"; CHECK_GT(rig.size(), 0); if (!FLAGS_eqr_masks.empty()) { verifyImagePaths(FLAGS_eqr_masks, rig, FLAGS_first, FLAGS_last); } } void rescaleCameras(Camera::Rig& rig) { for (Camera& cam : rig) { if (FLAGS_width > 0) { int height = ceil(FLAGS_width * cam.resolution.y() / float(cam.resolution.x())); height += height % 2; // force even number of rows cam = cam.rescale({FLAGS_width, height}); } LOG(INFO) << folly::sformat( "{} output resolution: {}x{}", cam.id, cam.resolution.x(), cam.resolution.y()); } } int main(int argc, char** argv) { gflags::SetUsageMessage(kUsage); system_util::initDep(argc, argv); CHECK_NE(FLAGS_rig, ""); Camera::Rig rig = filterDestinations(Camera::loadRig(FLAGS_rig), FLAGS_cameras); verifyInputs(rig); rescaleCameras(rig); const int first = std::stoi(FLAGS_first); const int last = std::stoi(FLAGS_last); for (int iFrame = first; iFrame <= last; ++iFrame) { const std::string frameName = image_util::intToStringZeroPad(iFrame, 6); LOG(INFO) << folly::sformat("Frame {}: Loading equirect masks...", frameName); const std::vector<cv::Mat_<bool>> eqrMasks = loadImages<bool>(FLAGS_eqr_masks, rig, frameName); CHECK_EQ(ssize(eqrMasks), ssize(rig)); for (ssize_t i = 0; i < ssize(rig); ++i) { const Camera& cam = rig[i]; LOG(INFO) << folly::sformat("-- Frame {}: Projecting to {}...", frameName, cam.id); // For each pixel in the current camera find out where in equirect space we land ThreadPool threadPool(FLAGS_threads); cv::Mat_<bool> camMask(cam.resolution.y(), cam.resolution.x(), false); const cv::Mat_<bool>& eqrMask = eqrMasks[i]; int h = camMask.rows; int w = camMask.cols; const int edgeX = w; const int edgeY = 1; for (int yBegin = 0; yBegin < h; yBegin += edgeY) { for (int xBegin = 0; xBegin < w; xBegin += edgeX) { const int xEnd = std::min(xBegin + edgeX, w); const int yEnd = std::min(yBegin + edgeY, h); threadPool.spawn([&, xBegin, yBegin, xEnd, yEnd] { for (int y = yBegin; y < yEnd; ++y) { for (int x = xBegin; x < xEnd; ++x) { const Camera::Vector3 world = cam.rig({x + 0.5, y + 0.5}, FLAGS_depth); const Camera::Vector2 pEqr = image_util::worldToEquirect(world, eqrMask.cols, eqrMask.rows); if (pEqr.x() < 0 || pEqr.y() < 0 || pEqr.x() >= eqrMask.cols || pEqr.y() >= eqrMask.rows) { continue; // rounding can put us at image edge. Ignore these cases } if (eqrMask(pEqr.y(), pEqr.x())) { camMask(y, x) = true; } } } }); } } threadPool.join(); const filesystem::path filename = filesystem::path(FLAGS_output) / cam.id / (frameName + "." + FLAGS_file_type); filesystem::create_directories(filename.parent_path()); cv_util::imwriteExceptionOnFail(filename, 255.0f * camMask); } } return EXIT_SUCCESS; }