source/conversion/ImportPointCloud.cpp (116 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 a point cloud as an ASCII file with a single point per line and generates a disparity
image per camera.
Supports multiple point cloud formats, but only extracts the xyz coordinates.
The input file can have a single line header with a point count.
- Example:
./ImportPointCloud \
--output=/path/to/output \
--rig=/path/to/rigs/rig.json \
--point_cloud=/path/to/points.xyz
Where points.xyz may be of the form:
10000
-0.04503071680665016 -2.2521071434020996 4.965743541717529 1 90 104 136
-0.005194493103772402 -2.323836088180542 4.938142776489258 1 94 110 143
0.046292994171381 -2.2623345851898193 4.609960079193115 1 101 122 149
...
)";
#include <gflags/gflags.h>
#include <glog/logging.h>
#include "source/conversion/PointCloudUtil.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;
using namespace fb360_dep::point_cloud_util;
DEFINE_string(cameras, "", "comma-separated cameras to render (empty for all)");
DEFINE_double(max_depth, INFINITY, "ignore depths farther than this value (m)");
DEFINE_double(min_depth, 0, "ignore depths closer than this value (m)");
DEFINE_string(output, "", "output directory (required)");
DEFINE_string(point_cloud, "", "input point cloud (required)");
DEFINE_string(rig, "", "path to camera rig .json (required)");
DEFINE_int32(threads, -1, "number of threads (-1 = auto, 0 = none)");
DEFINE_int32(width, 1024, "width of output camera images (0 = size from rig file)");
void verifyInputs(const Camera::Rig& rig) {
CHECK_NE(FLAGS_point_cloud, "");
CHECK_NE(FLAGS_output, "");
CHECK_GE(FLAGS_width, 0);
CHECK_EQ(FLAGS_width % 2, 0) << "width must be a multiple of 2";
CHECK_GT(rig.size(), 0);
}
void rescaleCameras(Camera::Rig& rig) {
for (Camera& cam : rig) {
if (FLAGS_width > 0) {
int height = std::round(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());
}
}
std::vector<cv::Mat_<float>> projectPointsToCameras(
const PointCloud& points,
const Camera::Rig& rig) {
LOG(INFO) << "Projecting points to cameras...";
std::vector<cv::Mat_<float>> disparities;
for (const Camera& cam : rig) {
disparities.emplace_back(cam.resolution.y(), cam.resolution.x(), 0.0f);
}
ThreadPool threadPool(FLAGS_threads);
const int threads = threadPool.getMaxThreads();
// Evenly distribute lines across threads
const int pointCount = points.size();
const int pointsPerThread = float(pointCount) / threads;
const int remain = pointCount % threads;
for (int i = 0; i < threads; ++i) {
threadPool.spawn([&, i] {
const int begin =
i < remain ? i * (pointsPerThread + 1) : pointCount - (threads - i) * pointsPerThread;
const int end = begin + pointsPerThread + (i < remain);
for (int j = begin; j < end; ++j) {
// Project point to all cameras
for (ssize_t i = 0; i < ssize(rig); ++i) {
const Camera::Vector3& pWorld = points[j].coords;
Camera::Vector2 pSrc;
if (!rig[i].sees(pWorld, pSrc)) {
continue; // Outside src FOV, ignore
}
cv::Mat_<float>& disparity = disparities[i];
const int xSrc = math_util::clamp(int(std::round(pSrc.x())), 0, disparity.cols - 1);
const int ySrc = math_util::clamp(int(std::round(pSrc.y())), 0, disparity.rows - 1);
float depth = pWorld.norm();
if (depth < FLAGS_min_depth || depth > FLAGS_max_depth) {
depth = INFINITY;
}
disparity(ySrc, xSrc) =
std::max(disparity(ySrc, xSrc), 1.0f / depth); // get closest value
}
}
});
}
threadPool.join();
return disparities;
}
void saveImages(const std::vector<cv::Mat_<float>>& disparities, const Camera::Rig& rig) {
LOG(INFO) << "Saving images...";
const filesystem::path dirOut = filesystem::path(FLAGS_output);
for (ssize_t i = 0; i < ssize(rig); ++i) {
const filesystem::path fn = dirOut / rig[i].id / "000000.png";
filesystem::create_directories(fn.parent_path());
cv_util::imwriteExceptionOnFail(fn, cv_util::convertTo<uint16_t>(disparities[i]));
}
}
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 pointCount = getPointCount(FLAGS_point_cloud);
const PointCloud points = extractPoints(FLAGS_point_cloud, pointCount, FLAGS_threads);
const std::vector<cv::Mat_<float>> disparities = projectPointsToCameras(points, rig);
saveImages(disparities, rig);
return EXIT_SUCCESS;
}