Commit a9d066e6 authored by David Flynn's avatar David Flynn
Browse files

octree/m43602: perform recolouring after geometry processing

This commit prepares for the possibility that geometry coding itself is
lossy.  Instead of performing recolouring just after input quantisation,
it is deferred until after geometry processing.

The recolouring functions are updated to handle differences in
pointcloud scales rather than requiring the target pointcloud to be
scaled identically to the source.
parent a03dc25d
......@@ -37,6 +37,7 @@
#define PCCPointSetProcessing_h
#include <cstddef>
#include <set>
#include <vector>
#include "KDTreeVectorOfVectorsAdaptor.h"
......@@ -45,9 +46,124 @@
namespace pcc {
//============================================================================
// Quantise the geometry of a point cloud, retaining unique points only.
// Points in the @src point cloud are translated by @offset, then quantised
// by a multiplicitive @scaleFactor with rounding.
//
// The destination and source point clouds may be the same object.
//
// NB: attributes are not processed.
inline void
quantizePositionsUniq(
const float scaleFactor,
const PCCVector3D offset,
const PCCPointSet3& src,
PCCPointSet3* dst)
{
// Determine the set of unique quantised points
std::set<PCCVector3<int32_t>> uniquePoints;
int numSrcPoints = src.getPointCount();
for (int i = 0; i < numSrcPoints; ++i) {
const PCCVector3D point = (src[i] + offset) * scaleFactor;
PCCVector3<int32_t> quantizedPoint;
quantizedPoint[0] = int64_t(std::round(point[0]));
quantizedPoint[1] = int64_t(std::round(point[1]));
quantizedPoint[2] = int64_t(std::round(point[2]));
uniquePoints.insert(quantizedPoint);
}
// Populate output point cloud
if (&src != dst) {
dst->clear();
dst->addRemoveAttributes(src.hasColors(), src.hasReflectances());
}
dst->resize(uniquePoints.size());
int idx = 0;
for (const auto& point : uniquePoints) {
auto& dstPoint = (*dst)[idx++];
for (int k = 0; k < 3; ++k)
dstPoint[k] = double(point[k]);
}
}
//============================================================================
// Quantise the geometry of a point cloud, retaining duplicate points.
// Points in the @src point cloud are translated by @offset, then quantised
// by a multiplicitive @scaleFactor with rounding.
//
// The destination and source point clouds may be the same object.
//
// NB: attributes are preserved
inline void
quantizePositions(
const float scaleFactor,
const PCCVector3D offset,
const PCCPointSet3& src,
PCCPointSet3* dst)
{
int numSrcPoints = src.getPointCount();
// In case dst and src point clouds are the same, don't destroy src.
if (&src != dst) {
dst->clear();
dst->addRemoveAttributes(src.hasColors(), src.hasReflectances());
dst->resize(numSrcPoints);
}
for (int i = 0; i < numSrcPoints; ++i) {
const PCCVector3D point = (src[i] + offset) * scaleFactor;
auto& dstPoint = (*dst)[i];
for (int k = 0; k < 3; ++k)
dstPoint[k] = std::round(point[k]);
}
// don't copy attributes if dst already has them
if (&src == dst)
return;
if (src.hasColors()) {
for (int i = 0; i < numSrcPoints; ++i)
dst->setColor(i, src.getColor(i));
}
if (src.hasReflectances()) {
for (int i = 0; i < numSrcPoints; ++i)
dst->setReflectance(i, src.getReflectance(i));
}
}
//============================================================================
// Determine colour attribute values from a reference/source point cloud.
// Each point in @target is coloured by:
//
// - first projecting the attribute values of each point in @source to
// the corresponding nearest neighbour in @target. In case multiple
// source points map to a single target point, the mean value is used.
//
// - for any remaining uncoloured points, finding the corresponding nearest
// neighbour in @source.
//
// Differences in the scale and translation of the target and source point
// clouds, is handled according to:
// posInTgt = (posInSrc - targetToSourceOffset) * sourceToTargetScaleFactor
inline bool
PCCTransfertColors(const PCCPointSet3& source, PCCPointSet3& target)
PCCTransfertColors(
const PCCPointSet3& source,
double sourceToTargetScaleFactor,
PCCVector3D targetToSourceOffset,
PCCPointSet3& target)
{
double targetToSourceScaleFactor = 1.0 / sourceToTargetScaleFactor;
const size_t pointCountSource = source.getPointCount();
const size_t pointCountTarget = target.getPointCount();
if (!pointCountSource || !pointCountTarget || !source.hasColors()) {
......@@ -67,19 +183,30 @@ PCCTransfertColors(const PCCPointSet3& source, PCCPointSet3& target)
std::vector<size_t> indices(num_results);
std::vector<double> sqrDist(num_results);
nanoflann::KNNResultSet<double> resultSet(num_results);
for (size_t index = 0; index < pointCountTarget; ++index) {
resultSet.init(&indices[0], &sqrDist[0]);
PCCVector3D posInSrc =
target[index] * targetToSourceScaleFactor + targetToSourceOffset;
kdtreeSource.index->findNeighbors(
resultSet, &target[index][0], nanoflann::SearchParams(10));
resultSet, &posInSrc[0], nanoflann::SearchParams(10));
refinedColors1[index] = source.getColor(indices[0]);
}
for (size_t index = 0; index < pointCountSource; ++index) {
const PCCColor3B color = source.getColor(index);
resultSet.init(&indices[0], &sqrDist[0]);
PCCVector3D posInTgt =
(source[index] - targetToSourceOffset) * sourceToTargetScaleFactor;
kdtreeTarget.index->findNeighbors(
resultSet, &source[index][0], nanoflann::SearchParams(10));
resultSet, &posInTgt[0], nanoflann::SearchParams(10));
refinedColors2[indices[0]].push_back(color);
}
for (size_t index = 0; index < pointCountTarget; ++index) {
const PCCColor3B color1 = refinedColors1[index];
const std::vector<PCCColor3B>& colors2 = refinedColors2[index];
......@@ -103,9 +230,30 @@ PCCTransfertColors(const PCCPointSet3& source, PCCPointSet3& target)
return true;
}
//============================================================================
// Determine reflectance attribute values from a reference/source point cloud.
// Each point in @target is coloured by:
//
// - first projecting the attribute values of each point in @source to
// the corresponding nearest neighbour in @target. In case multiple
// source points map to a single target point, the mean value is used.
//
// - for any remaining uncoloured points, finding the corresponding nearest
// neighbour in @source.
//
// Differences in the scale and translation of the target and source point
// clouds, is handled according to:
// posInTgt = (posInSrc - targetToSourceOffset) * sourceToTargetScaleFactor
inline bool
PCCTransfertReflectances(const PCCPointSet3& source, PCCPointSet3& target)
PCCTransfertReflectances(
const PCCPointSet3& source,
double sourceToTargetScaleFactor,
PCCVector3D targetToSourceOffset,
PCCPointSet3& target)
{
double targetToSourceScaleFactor = 1.0 / sourceToTargetScaleFactor;
const size_t pointCountSource = source.getPointCount();
const size_t pointCountTarget = target.getPointCount();
if (!pointCountSource || !pointCountTarget || !source.hasReflectances()) {
......@@ -125,19 +273,30 @@ PCCTransfertReflectances(const PCCPointSet3& source, PCCPointSet3& target)
std::vector<size_t> indices(num_results);
std::vector<double> sqrDist(num_results);
nanoflann::KNNResultSet<double> resultSet(num_results);
for (size_t index = 0; index < pointCountTarget; ++index) {
resultSet.init(&indices[0], &sqrDist[0]);
PCCVector3D posInSrc =
target[index] * targetToSourceScaleFactor + targetToSourceOffset;
kdtreeSource.index->findNeighbors(
resultSet, &target[index][0], nanoflann::SearchParams(10));
resultSet, &posInSrc[0], nanoflann::SearchParams(10));
refined1[index] = source.getReflectance(indices[0]);
}
for (size_t index = 0; index < pointCountSource; ++index) {
const uint16_t reflectance = source.getReflectance(index);
resultSet.init(&indices[0], &sqrDist[0]);
PCCVector3D posInTgt =
(source[index] - targetToSourceOffset) * sourceToTargetScaleFactor;
kdtreeTarget.index->findNeighbors(
resultSet, &source[index][0], nanoflann::SearchParams(10));
resultSet, &posInTgt[0], nanoflann::SearchParams(10));
refined2[indices[0]].push_back(reflectance);
}
for (size_t index = 0; index < pointCountTarget; ++index) {
const uint16_t reflectance1 = refined1[index];
const std::vector<uint16_t>& reflectances2 = refined2[index];
......@@ -156,6 +315,46 @@ PCCTransfertReflectances(const PCCPointSet3& source, PCCPointSet3& target)
}
return true;
}
//============================================================================
// Recolour attributes based on a source/reference point cloud.
//
// Differences in the scale and translation of the target and source point
// clouds, is handled according to:
// posInTgt = (posInSrc - targetToSourceOffset) * sourceToTargetScaleFactor
inline int
recolour(
const PCCPointSet3& source,
float sourceToTargetScaleFactor,
PCCVector3D targetToSourceOffset,
PCCPointSet3* target)
{
if (source.hasColors()) {
bool ok = PCCTransfertColors(
source, sourceToTargetScaleFactor, targetToSourceOffset, *target);
if (!ok) {
std::cout << "Error: can't transfer colors!" << std::endl;
return -1;
}
}
if (source.hasReflectances()) {
bool ok = PCCTransfertReflectances(
source, sourceToTargetScaleFactor, targetToSourceOffset, *target);
if (!ok) {
std::cout << "Error: can't transfer reflectance!" << std::endl;
return -1;
}
}
return 0;
}
//============================================================================
}; // namespace pcc
#endif /* PCCPointSetProcessing_h */
......@@ -88,11 +88,10 @@ private:
void computeMinPositions(const PCCPointSet3& inputPointCloud);
int quantization(const PCCPointSet3& inputPointCloud);
int recolorTrisoup(const PCCPointSet3& inputPointCloud);
void quantization(const PCCPointSet3& inputPointCloud);
private:
// todo(df): minPositions is unscaled -- which isn't quite correct.
PCCVector3D minPositions;
PCCBox3<uint32_t> boundingBox;
PCCPointSet3 pointCloud;
......
......@@ -122,8 +122,9 @@ PCCTMC3Encoder3::compress(
// geometry compression consists of the following stages:
// - prefilter/quantize geometry (non-normative)
// - recolour
// - encode geometry
// - recolour
if (_gps->geom_codec_type == GeometryCodecType::kOctree) {
quantization(inputPointCloud);
}
......@@ -163,8 +164,6 @@ PCCTMC3Encoder3::compress(
bool hasReflectance = inputPointCloud.hasReflectances();
if (encodeTrisoup(hasColor, hasReflectance, &payload))
return -1;
if (recolorTrisoup(inputPointCloud))
return -1;
}
clock_user.stop();
......@@ -181,7 +180,17 @@ PCCTMC3Encoder3::compress(
outputFn(payload);
}
// attributeCoding
// recolouring
// NB: recolouring is required if points are added / removed
bool recolourNeeded = _gps->geom_unique_points_flag
|| _gps->geom_codec_type == GeometryCodecType::kTriSoup;
if (recolourNeeded) {
recolour(
inputPointCloud, _sps->seq_source_geom_scale_factor, minPositions,
&pointCloud);
}
// dump recoloured point cloud
if (!params->postRecolorPath.empty()) {
......@@ -190,6 +199,8 @@ PCCTMC3Encoder3::compress(
tempPointCloud.write(params->postRecolorPath);
}
// attributeCoding
// for each attribute
for (const auto& it : params->attributeIdxMap) {
int attrIdx = it.second;
......@@ -341,83 +352,23 @@ PCCTMC3Encoder3::computeMinPositions(const PCCPointSet3& inputPointCloud)
//----------------------------------------------------------------------------
int
void
PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud)
{
computeMinPositions(inputPointCloud);
bool hasColour = inputPointCloud.hasColors();
bool hasReflectance = inputPointCloud.hasReflectances();
pointCloud.resize(0);
pointCloud.addRemoveAttributes(hasColour, hasReflectance);
const size_t inputPointCount = inputPointCloud.getPointCount();
if (_gps->geom_unique_points_flag) { // retain unique quantized points
// filter points based on quantized positions
std::set<PCCVector3<int64_t>> retainedPoints;
for (size_t i = 0; i < inputPointCount; ++i) {
const PCCVector3D point = (inputPointCloud[i] - minPositions)
* _sps->seq_source_geom_scale_factor;
PCCVector3<int64_t> quantizedPoint;
quantizedPoint[0] = int64_t(std::round(point[0]));
quantizedPoint[1] = int64_t(std::round(point[1]));
quantizedPoint[2] = int64_t(std::round(point[2]));
const auto it = retainedPoints.find(quantizedPoint);
if (it == retainedPoints.end()) {
retainedPoints.insert(quantizedPoint);
}
}
// compute reconstructed point cloud
pointCloud.resize(retainedPoints.size());
const double invScale = 1.0 / _sps->seq_source_geom_scale_factor;
size_t pointCounter = 0;
for (const auto& quantizedPoint : retainedPoints) {
auto& point = pointCloud[pointCounter++];
for (size_t k = 0; k < 3; ++k) {
point[k] = double(quantizedPoint[k]) * invScale + minPositions[k];
}
}
if (hasColour) { // transfer colors
if (!PCCTransfertColors(inputPointCloud, pointCloud)) {
std::cout << "Error: can't transfer colors!" << std::endl;
return -1;
}
}
if (hasReflectance) { // transfer reflectances
if (!PCCTransfertReflectances(inputPointCloud, pointCloud)) {
std::cout << "Error: can't transfer reflectances!" << std::endl;
return -1;
}
}
// compute quantized coordinates
pointCounter = 0;
for (const auto& quantizedPoint : retainedPoints) {
auto& point = pointCloud[pointCounter++];
for (size_t k = 0; k < 3; ++k) {
point[k] = double(quantizedPoint[k]);
}
}
// todo(df): allow overriding of minposition from CLI
// todo(df): remove trisoup hack
minPositions = PCCVector3D{0.0};
if (_gps->geom_codec_type == GeometryCodecType::kOctree)
computeMinPositions(inputPointCloud);
if (_gps->geom_unique_points_flag) {
quantizePositionsUniq(
_sps->seq_source_geom_scale_factor, -minPositions, inputPointCloud,
&pointCloud);
} else {
// quantized point cloud
pointCloud.resize(inputPointCount);
for (size_t i = 0; i < inputPointCount; ++i) {
const PCCVector3D point = (inputPointCloud[i] - minPositions)
* _sps->seq_source_geom_scale_factor;
auto& quantizedPoint = pointCloud[i];
quantizedPoint[0] = std::round(point[0]);
quantizedPoint[1] = std::round(point[1]);
quantizedPoint[2] = std::round(point[2]);
if (hasColour) {
pointCloud.setColor(i, inputPointCloud.getColor(i));
}
if (hasReflectance) {
pointCloud.setReflectance(i, inputPointCloud.getReflectance(i));
}
}
quantizePositions(
_sps->seq_source_geom_scale_factor, -minPositions, inputPointCloud,
&pointCloud);
}
const size_t pointCount = pointCloud.getPointCount();
......@@ -432,43 +383,6 @@ PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud)
}
}
}
return 0;
}
//----------------------------------------------------------------------------
int
PCCTMC3Encoder3::recolorTrisoup(const PCCPointSet3& inputPointCloud)
{
// Transform from integer to original coordinates.
const size_t pointCount = pointCloud.getPointCount();
for (size_t pointIndex = 0; pointIndex < pointCount; ++pointIndex) {
pointCloud[pointIndex] *= _sps->donotuse_trisoup_int_to_orig_scale;
// todo(pchou): should add intToOrigTranslation here.
}
// Recolour attributes.
if (inputPointCloud.hasColors()) {
if (!PCCTransfertColors(inputPointCloud, pointCloud)) {
std::cout << "Error: can't transfer colors!" << std::endl;
return -1;
}
}
if (inputPointCloud.hasReflectances()) {
if (!PCCTransfertReflectances(inputPointCloud, pointCloud)) {
std::cout << "Error: can't transfer reflectance!" << std::endl;
return -1;
}
}
// Transform from original to integer coordinates.
for (size_t pointIndex = 0; pointIndex < pointCount; ++pointIndex) {
// todo(pchou): should subtract intToOrigTranslation here.
// todo(?): don't use division here
pointCloud[pointIndex] /= _sps->donotuse_trisoup_int_to_orig_scale;
}
return 0;
}
//============================================================================
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment