source/conversion/PointCloudUtil.cpp (145 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/conversion/PointCloudUtil.h"
#include <regex>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/lexical_cast.hpp>
#include "source/util/ThreadPool.h"
using Image = cv::Mat_<cv::Vec3b>;
using DisparityImage = cv::Mat_<float>;
using CoordinateImage = cv::Mat_<cv::Point3f>;
namespace fb360_dep {
namespace point_cloud_util {
std::vector<PointCloudProjection> generateProjectedImages(
const PointCloud& pointCloud,
const Camera::Rig& rig) {
std::vector<PointCloudProjection> projections;
for (const auto& camera : rig) {
PointCloudProjection projection;
projection.image =
cv::Mat(camera.resolution[1], camera.resolution[0], CV_8UC3, cv::Scalar(0, 0, 0));
projection.disparityImage =
cv::Mat(camera.resolution[1], camera.resolution[0], CV_32F, cv::Scalar(0));
projection.coordinateImage =
cv::Mat(camera.resolution[1], camera.resolution[0], CV_32FC3, cv::Scalar(0, 0, 0));
projections.emplace_back(projection);
}
for (const auto& point : pointCloud) {
for (int i = 0; i < int(rig.size()); ++i) {
if (rig[i].sees(point.coords)) {
const Camera::Vector2& projectedCoors = rig[i].pixel(point.coords);
PointCloudProjection& projection = projections[i];
const float depth = (point.coords - rig[i].position).norm();
const float disparity = 1.0f / depth;
if (projection.disparityImage(projectedCoors.y(), projectedCoors.x()) < disparity) {
projection.disparityImage(projectedCoors.y(), projectedCoors.x()) = disparity;
projection.image(projectedCoors.y(), projectedCoors.x()) = point.bgrColor;
projection.coordinateImage(projectedCoors.y(), projectedCoors.x()).x = point.coords.x();
projection.coordinateImage(projectedCoors.y(), projectedCoors.x()).y = point.coords.y();
projection.coordinateImage(projectedCoors.y(), projectedCoors.x()).z = point.coords.z();
}
}
}
}
return projections;
}
void goToLine(std::ifstream& file, const int lineNumber) {
file.seekg(0, std::ios::beg);
for (int i = 1; i < lineNumber; ++i) {
file.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
// PCL header entries must be specified in the following order
// Comment
// VERSION
// FIELDS
// SIZE
// TYPE
// COUNT
// WIDTH
// HEIGHT
// VIEWPOINT
// POINTS
// DATA
void verifyPCLHeader(std::ifstream& file) {
std::string line;
// Make sure FIELDS starts with x y z
const int lineFields = 3;
goToLine(file, lineFields);
std::getline(file, line);
CHECK(boost::starts_with(line, "FIELDS x y z")) << "PCL header: FIELDS must start with x y z";
// Make DATA is ascii
const int lineData = 11;
goToLine(file, lineData);
std::getline(file, line);
CHECK_EQ(line, "DATA ascii") << "PCL header: DATA must be ascii";
}
int extractPCLPointCount(std::ifstream& file) {
goToLine(file, 0);
std::string line;
const int linePoints = 10;
goToLine(file, linePoints);
std::getline(file, line);
CHECK(boost::starts_with(line, "POINTS"))
<< folly::sformat("PCL header: expected point count in line {}, got {}", linePoints, line);
std::regex rgx("POINTS (\\w+)");
std::smatch match;
const std::string lineConst = line;
CHECK(std::regex_search(lineConst.begin(), lineConst.end(), match, rgx))
<< "Could not parse point count from line " << line;
const std::string pointCountStr = match[1];
try {
return boost::lexical_cast<int>(pointCountStr);
} catch (boost::bad_lexical_cast&) {
CHECK(false) << folly::sformat("PCL header: invalid point count {}", pointCountStr);
}
}
int extractASCIIPointCount(std::ifstream& file) {
goToLine(file, 0);
std::string line;
file >> line;
try {
return boost::lexical_cast<int>(line);
} catch (boost::bad_lexical_cast&) {
CHECK(false) << "First line should contain point count: " << line;
}
}
int getPointCount(const std::string& pointCloudFile) {
// Check if first line is point counter
std::ifstream srcFile(pointCloudFile);
CHECK(srcFile.good()) << "File does not exist: " << pointCloudFile;
const std::string pcExt = filesystem::path(pointCloudFile).extension().string();
if (pcExt == ".pcd") {
verifyPCLHeader(srcFile);
return extractPCLPointCount(srcFile);
} else {
return extractASCIIPointCount(srcFile);
}
}
PointCloud
extractPoints(const std::string& pointCloudFile, const int pointCount, const int maxThreads) {
LOG(INFO) << folly::sformat("Extracting {} points from {}...", pointCount, pointCloudFile);
ThreadPool threadPool(maxThreads);
const int threads = threadPool.getMaxThreads();
// Evenly distribute lines across threads
const int pointsPerThread = float(pointCount) / threads;
const int remain = pointCount % threads;
const std::string pcExt = filesystem::path(pointCloudFile).extension().string();
const int headerNumLines = pcExt == ".pcd" ? 11 : 1;
PointCloud points(pointCount);
for (int i = 0; i < threads; ++i) {
threadPool.spawn([&, i] {
const int begin = headerNumLines + 1 + // skip header
(i < remain ? i * (pointsPerThread + 1) : pointCount - (threads - i) * pointsPerThread);
const int end = begin + pointsPerThread + (i < remain);
// Lines may not have the same length in characters, we have to loop our way to the start
// line
std::ifstream file(pointCloudFile);
goToLine(file, begin);
for (int j = begin; j < end; ++j) {
// Read XYZ coordinates and discard the rest of the line
BGRPoint& point = points[j];
int ignored, r, g, b;
file >> point.coords.x() >> point.coords.y() >> point.coords.z() >> ignored >> r >> g >> b;
point.bgrColor[0] = b;
point.bgrColor[1] = g;
point.bgrColor[2] = r;
file.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
});
}
threadPool.join();
LOG(INFO) << folly::sformat("Extracted {} points.", points.size());
if (pointCount > 0) {
CHECK_EQ(pointCount, points.size()) << folly::sformat(
"Point count in header ({}) does not match number of extracted points ({})",
pointCount,
points.size());
}
return points;
}
PointCloud extractPoints(const std::string& pointCloudFile, const int maxThreads) {
const int pointCount = getPointCount(pointCloudFile);
return extractPoints(pointCloudFile, pointCount, maxThreads);
}
} // namespace point_cloud_util
} // namespace fb360_dep