Commit bcf5394c authored by Yiting Shao's avatar Yiting Shao Committed by David Flynn
Browse files

slices/m48892: perform geometry quantisation prior to slice partitioning

Adjust the operation sequence of slice partition and geometry quantization. We
do geometry quantization first and then do the slice partition, so that the
point number of each slice would be kept in the predefined range under
lossless/lossy geometry coding conditions.
parent e72b9af7
...@@ -428,6 +428,7 @@ public: ...@@ -428,6 +428,7 @@ public:
std::swap(getReflectance(index1), getReflectance(index2)); std::swap(getReflectance(index1), getReflectance(index2));
} }
} }
Box3<double> computeBoundingBox() const Box3<double> computeBoundingBox() const
{ {
Box3<double> bbox = {std::numeric_limits<double>::max(), Box3<double> bbox = {std::numeric_limits<double>::max(),
...@@ -446,6 +447,34 @@ public: ...@@ -446,6 +447,34 @@ public:
} }
return bbox; return bbox;
} }
//--------------------------------------------------------------------------
// Determine the bounding box of the set of points given by the indicies
// given by iterating over [begin, end)
template<typename ForwardIt>
Box3<double> computeBoundingBox(ForwardIt begin, ForwardIt end) const
{
Box3<double> bbox = {std::numeric_limits<double>::max(),
std::numeric_limits<double>::lowest()};
for (auto it = begin; it != end; ++it) {
int i = *it;
const Vec3<double>& pt = (*this)[i];
for (int k = 0; k < 3; ++k) {
if (pt[k] > bbox.max[k]) {
bbox.max[k] = pt[k];
}
if (pt[k] < bbox.min[k]) {
bbox.min[k] = pt[k];
}
}
}
return bbox;
}
//--------------------------------------------------------------------------
static bool compareSeparators(char aChar, const char* const sep) static bool compareSeparators(char aChar, const char* const sep)
{ {
int i = 0; int i = 0;
......
...@@ -39,6 +39,8 @@ ...@@ -39,6 +39,8 @@
#include <cstddef> #include <cstddef>
#include <set> #include <set>
#include <vector> #include <vector>
#include <utility>
#include <map>
#include "KDTreeVectorOfVectorsAdaptor.h" #include "KDTreeVectorOfVectorsAdaptor.h"
#include "PCCPointSet.h" #include "PCCPointSet.h"
...@@ -80,10 +82,12 @@ quantizePositionsUniq( ...@@ -80,10 +82,12 @@ quantizePositionsUniq(
const Vec3<int> offset, const Vec3<int> offset,
const Box3<int> clamp, const Box3<int> clamp,
const PCCPointSet3& src, const PCCPointSet3& src,
PCCPointSet3* dst) PCCPointSet3* dst,
std::multimap<Vec3<double>, int32_t>& doubleQuantizedToOrigin)
{ {
// Determine the set of unique quantised points // Determine the set of unique quantised points
std::multimap<Vec3<int32_t>, int32_t> intQuantizedToOrigin;
std::set<Vec3<int32_t>> uniquePoints; std::set<Vec3<int32_t>> uniquePoints;
int numSrcPoints = src.getPointCount(); int numSrcPoints = src.getPointCount();
for (int i = 0; i < numSrcPoints; ++i) { for (int i = 0; i < numSrcPoints; ++i) {
...@@ -96,6 +100,7 @@ quantizePositionsUniq( ...@@ -96,6 +100,7 @@ quantizePositionsUniq(
} }
uniquePoints.insert(quantizedPoint); uniquePoints.insert(quantizedPoint);
intQuantizedToOrigin.insert(std::make_pair(quantizedPoint, i));
} }
// Populate output point cloud // Populate output point cloud
...@@ -105,12 +110,18 @@ quantizePositionsUniq( ...@@ -105,12 +110,18 @@ quantizePositionsUniq(
dst->addRemoveAttributes(src.hasColors(), src.hasReflectances()); dst->addRemoveAttributes(src.hasColors(), src.hasReflectances());
} }
dst->resize(uniquePoints.size()); dst->resize(uniquePoints.size());
doubleQuantizedToOrigin.clear();
int idx = 0; int idx = 0;
for (const auto& point : uniquePoints) { for (const auto& point : uniquePoints) {
auto& dstPoint = (*dst)[idx++]; auto& dstPoint = (*dst)[idx++];
for (int k = 0; k < 3; ++k) for (int k = 0; k < 3; ++k)
dstPoint[k] = double(point[k]); dstPoint[k] = double(point[k]);
std::multimap<Vec3<int32_t>, int32_t>::iterator pos;
for (pos = intQuantizedToOrigin.lower_bound(point);
pos != intQuantizedToOrigin.upper_bound(point); ++pos) {
doubleQuantizedToOrigin.insert(std::make_pair(dstPoint, pos->second));
}
} }
} }
......
...@@ -45,25 +45,12 @@ ...@@ -45,25 +45,12 @@
#include "PCCPointSet.h" #include "PCCPointSet.h"
#include "PCCPointSetProcessing.h" #include "PCCPointSetProcessing.h"
#include "hls.h" #include "hls.h"
#include "partitioning.h"
namespace pcc { namespace pcc {
//============================================================================ //============================================================================
enum class PartitionMethod
{
// Don't partition input
kNone = 0,
// Partition according to uniform geometry
kUniformGeom = 2,
// Partition according to the depth of octree
kOctreeUniform = 3,
};
//============================================================================
struct EncoderAttributeParams { struct EncoderAttributeParams {
// NB: this only makes sense for setting configurable parameters // NB: this only makes sense for setting configurable parameters
AttributeBrickHeader abh; AttributeBrickHeader abh;
...@@ -88,14 +75,8 @@ struct EncoderParams { ...@@ -88,14 +75,8 @@ struct EncoderParams {
// Filename for saving recoloured point cloud. // Filename for saving recoloured point cloud.
std::string postRecolorPath; std::string postRecolorPath;
// Method for partitioning the input cloud // Parameters that control partitioning
PartitionMethod partitionMethod; PartitionParams partition;
// Number of slices used by PartitionMethod::kUniformGeom
int partitionNumUniformGeom;
// Depth of octree used by PartitionMethod::kOctreeUniform
int partitionOctreeDepth;
// attribute recolouring parameters // attribute recolouring parameters
RecolourParams recolour; RecolourParams recolour;
...@@ -118,6 +99,7 @@ public: ...@@ -118,6 +99,7 @@ public:
void compressPartition( void compressPartition(
const PCCPointSet3& inputPointCloud, const PCCPointSet3& inputPointCloud,
const PCCPointSet3& originPartCloud,
EncoderParams* params, EncoderParams* params,
std::function<void(const PayloadBuffer&)> outputFn, std::function<void(const PayloadBuffer&)> outputFn,
PCCPointSet3* reconstructedCloud = nullptr); PCCPointSet3* reconstructedCloud = nullptr);
...@@ -129,7 +111,12 @@ private: ...@@ -129,7 +111,12 @@ private:
void encodeGeometryBrick(PayloadBuffer* buf); void encodeGeometryBrick(PayloadBuffer* buf);
void quantization(const PCCPointSet3& inputPointCloud); PCCPointSet3 quantization(const PCCPointSet3& inputPointCloud);
void getSrcPartition(
const PCCPointSet3& inputPointCloud,
PCCPointSet3& srcPartition,
std::vector<int32_t> indexes);
private: private:
PCCPointSet3 pointCloud; PCCPointSet3 pointCloud;
...@@ -150,6 +137,9 @@ private: ...@@ -150,6 +137,9 @@ private:
// Identifies the current tile // Identifies the current tile
int _tileId; int _tileId;
// Map quantized points to the original input points
std::multimap<Vec3<double>, int32_t> quantizedToOrigin;
}; };
//============================================================================ //============================================================================
......
...@@ -168,8 +168,8 @@ operator<<(std::ostream& out, const PartitionMethod& val) ...@@ -168,8 +168,8 @@ operator<<(std::ostream& out, const PartitionMethod& val)
{ {
switch (val) { switch (val) {
case PartitionMethod::kNone: out << "0 (None)"; break; case PartitionMethod::kNone: out << "0 (None)"; break;
case PartitionMethod::kUniformGeom: out << "0 (UniformGeom)"; break; case PartitionMethod::kUniformGeom: out << "2 (UniformGeom)"; break;
case PartitionMethod::kOctreeUniform: out << "0 (UniformOctree)"; break; case PartitionMethod::kOctreeUniform: out << "3 (UniformOctree)"; break;
default: out << int(val) << " (Unknown)"; break; default: out << int(val) << " (Unknown)"; break;
} }
return out; return out;
...@@ -323,22 +323,28 @@ ParseParameters(int argc, char* argv[], Parameters& params) ...@@ -323,22 +323,28 @@ ParseParameters(int argc, char* argv[], Parameters& params)
"Enables removal of duplicated points") "Enables removal of duplicated points")
("partitionMethod", ("partitionMethod",
params.encoder.partitionMethod, PartitionMethod::kNone, params.encoder.partition.method, PartitionMethod::kUniformGeom,
"Method used to partition input point cloud into slices/tiles:\n" "Method used to partition input point cloud into slices/tiles:\n"
" 0: none\n" " 0: none\n"
" 1: none (deprecated)\n" " 1: none (deprecated)\n"
" 2: n Uniform-Geometry partition bins along the longest edge\n" " 2: n Uniform-Geometry partition bins along the longest edge\n"
" 3: Uniform Geometry partition at n octree depth") " 3: Uniform Geometry partition at n octree depth")
("partitionNumUniformGeom",
params.encoder.partitionNumUniformGeom, 0,
"Number of bins for partitionMethod=2:\n"
" 0: slice partition with adaptive-defined bins\n"
" >=1: slice partition with user-defined bins\n")
("partitionOctreeDepth", ("partitionOctreeDepth",
params.encoder.partitionOctreeDepth, 2, params.encoder.partition.octreeDepth, 1,
"Depth of octree partition for partitionMethod=3") "Depth of octree partition for partitionMethod=4")
("sliceMaxPoints",
params.encoder.partition.sliceMaxPoints, 1100000,
"Maximum number of points per slice")
("sliceMinPoints",
params.encoder.partition.sliceMinPoints, 550000,
"Minimum number of points per slice (soft limit)")
("tileSize",
params.encoder.partition.tileSize, 0,
"Partition input into cubic tiles of given size")
("cabac_bypass_stream_enabled_flag", ("cabac_bypass_stream_enabled_flag",
params.encoder.sps.cabac_bypass_stream_enabled_flag, false, params.encoder.sps.cabac_bypass_stream_enabled_flag, false,
...@@ -632,6 +638,12 @@ ParseParameters(int argc, char* argv[], Parameters& params) ...@@ -632,6 +638,12 @@ ParseParameters(int argc, char* argv[], Parameters& params)
// sanity checks // sanity checks
if (
params.encoder.partition.sliceMaxPoints
< params.encoder.partition.sliceMinPoints)
err.error()
<< "sliceMaxPoints must be greater than or equal to sliceMinPoints\n";
if (params.encoder.gps.intra_pred_max_node_size_log2) if (params.encoder.gps.intra_pred_max_node_size_log2)
if (!params.encoder.gps.neighbour_avail_boundary_log2) if (!params.encoder.gps.neighbour_avail_boundary_log2)
err.error() << "Geometry intra prediction requires finite" err.error() << "Geometry intra prediction requires finite"
......
...@@ -94,48 +94,117 @@ PCCTMC3Encoder3::compress( ...@@ -94,48 +94,117 @@ PCCTMC3Encoder3::compress(
// initial geometry IDs // initial geometry IDs
_tileId = 0; _tileId = 0;
_sliceId = 0; _sliceId = 0;
_sliceOrigin = Vec3<int>{0};
// Partition the input point cloud into tiles
// - quantize the input point cloud (without duplicate point removal)
// - inverse quantize the cloud above to get the initial-sized cloud
// - if tile partitioning is enabled,partitioning function produces
// vectors tileMaps which map tileIDs to point indexes.
// Compute the tile metadata for each partition.
// - if not, regard the whole input cloud as a single tile to facilitate
// slice partitioning subsequent
// todo(df):
PartitionSet partitions;
PCCPointSet3 quantizedInputCloud;
quantizedInputCloud = quantization(inputPointCloud);
std::vector<std::vector<int32_t>> tileMaps;
if (params->partition.tileSize) {
PCCPointSet3 inverseQuantizedCloud;
Box3<int32_t> clampBox{{0, 0, 0}, {INT32_MAX, INT32_MAX, INT32_MAX}};
quantizePositions(
1.0 / _sps->seq_source_geom_scale_factor, 0, clampBox,
quantizedInputCloud, &inverseQuantizedCloud);
tileMaps = tilePartition(params->partition, inverseQuantizedCloud);
// Get the bounding box of current tile and write it into tileInventory
partitions.tileInventory.tiles.resize(tileMaps.size());
for (int t = 0; t < tileMaps.size(); t++) {
Box3<double> bbox = inverseQuantizedCloud.computeBoundingBox(
tileMaps[t].begin(), tileMaps[t].end());
auto& tileIvt = partitions.tileInventory.tiles[t];
for (int k = 0; k < 3; k++) {
tileIvt.tile_bounding_box_whd[k] = bbox.max[k] - bbox.min[k];
tileIvt.tile_bounding_box_xyz0[k] =
bbox.min[k] - _sps->seq_bounding_box_xyz0[k];
}
}
} else {
tileMaps.emplace_back();
auto& tile = tileMaps.back();
for (int i = 0; i < quantizedInputCloud.getPointCount(); i++)
tile.push_back(i);
}
// If partitioning is not enabled, encode input as a single "partition" // If partitioning is not enabled, encode input as a single "partition"
if (params->partitionMethod == PartitionMethod::kNone) { if (params->partition.method == PartitionMethod::kNone) {
// todo(df): params->gps.geom_box_present_flag = false; // todo(df): params->gps.geom_box_present_flag = false;
_sliceOrigin = Vec3<int>{0}; _sliceOrigin = Vec3<int>{0};
compressPartition(inputPointCloud, params, outputFn, reconstructedCloud); compressPartition(
quantizedInputCloud, inputPointCloud, params, outputFn,
reconstructedCloud);
return 0; return 0;
} }
// Partition the input point cloud // Partition the input point cloud
// - quantise the input point cloud (without duplicate point removal) // - get the partitial cloud of each tile
// - partitioning function produces a list of point indexes, origin and // - partitioning function produces a list of point indexes, origin and
// optional tile metadata for each partition. // optional tile metadata for each partition.
// - encode any tile metadata // - encode any tile metadata
// todo(df): consider requiring partitioning function to sort the input // todo(df): consider requiring partitioning function to sort the input
// points and provide ranges rather than a set of indicies. // points and provide ranges rather than a set of indicies.
PartitionSet partitions;
do { do {
PCCPointSet3 quantizedInputCloud;
Box3<int32_t> clampBox{{0, 0, 0}, {INT32_MAX, INT32_MAX, INT32_MAX}}; Box3<int32_t> clampBox{{0, 0, 0}, {INT32_MAX, INT32_MAX, INT32_MAX}};
quantizePositions(
_sps->seq_source_geom_scale_factor, _sps->seq_bounding_box_xyz0,
clampBox, inputPointCloud, &quantizedInputCloud);
switch (params->partitionMethod) { for (int t = 0; t < tileMaps.size(); t++) {
// NB: this method is handled earlier const auto& tile = tileMaps[t];
case PartitionMethod::kNone: return 1;
// Get the point cloud of current tile and compute their bounding boxes
case PartitionMethod::kUniformGeom: PCCPointSet3 tileCloud;
partitions = partitionByUniformGeom( getSrcPartition(quantizedInputCloud, tileCloud, tile);
quantizedInputCloud, params->partitionNumUniformGeom); Box3<double> bbox = tileCloud.computeBoundingBox();
break; Vec3<int> tile_quantized_box_xyz0;
for (int k = 0; k < 3; k++) {
case PartitionMethod::kOctreeUniform: tile_quantized_box_xyz0[k] = int(bbox.min[k]);
partitions = partitionByOctreeDepth( }
quantizedInputCloud, params->partitionOctreeDepth);
break; // Move the tile cloud to coodinate origin
// for the convenience of slice partitioning
quantizePositions(
1, tile_quantized_box_xyz0, clampBox, tileCloud, &tileCloud);
//Slice partition of current tile
std::vector<Partition> curSlices;
switch (params->partition.method) {
// NB: this method is handled earlier
case PartitionMethod::kNone: return 1;
case PartitionMethod::kUniformGeom:
curSlices = partitionByUniformGeom(params->partition, tileCloud, t);
break;
case PartitionMethod::kOctreeUniform:
curSlices = partitionByOctreeDepth(params->partition, tileCloud, t);
break;
}
// Map slice indexes to tile indexes(the original indexes)
for (int i = 0; i < curSlices.size(); i++) {
for (int p = 0; p < curSlices[i].pointIndexes.size(); p++) {
curSlices[i].pointIndexes[p] = tile[curSlices[i].pointIndexes[p]];
}
}
partitions.slices.insert(
partitions.slices.end(), curSlices.begin(), curSlices.end());
} }
std::cout << "Slice number: " << partitions.slices.size() << std::endl;
} while (0); } while (0);
if (!partitions.tileInventory.tiles.empty()) { if (partitions.tileInventory.tiles.size() > 1) {
assert(partitions.tileInventory.tiles.size() == tileMaps.size());
std::cout << "Tile number: " << tileMaps.size() << std::endl;
outputFn(write(partitions.tileInventory)); outputFn(write(partitions.tileInventory));
} }
...@@ -145,28 +214,28 @@ PCCTMC3Encoder3::compress( ...@@ -145,28 +214,28 @@ PCCTMC3Encoder3::compress(
for (const auto& partition : partitions.slices) { for (const auto& partition : partitions.slices) {
// create partitioned point set // create partitioned point set
PCCPointSet3 srcPartition; PCCPointSet3 srcPartition;
srcPartition.addRemoveAttributes( getSrcPartition(quantizedInputCloud, srcPartition, partition.pointIndexes);
inputPointCloud.hasColors(), inputPointCloud.hasReflectances());
// Get the original partial cloud corresponding to each slice for recolor
int partitionSize = partition.pointIndexes.size(); std::vector<int32_t> partitionOriginIdxes;
srcPartition.resize(partitionSize); for (int i = 0; i < partition.pointIndexes.size(); i++) {
const auto& point = srcPartition[i];
for (int i = 0; i < partitionSize; i++) { std::multimap<Vec3<double>, int32_t>::iterator pos;
int inputIdx = partition.pointIndexes[i]; for (pos = quantizedToOrigin.lower_bound(point);
srcPartition[i] = inputPointCloud[inputIdx]; pos != quantizedToOrigin.upper_bound(point); ++pos) {
partitionOriginIdxes.push_back(pos->second);
if (inputPointCloud.hasColors()) }
srcPartition.setColor(i, inputPointCloud.getColor(inputIdx));
if (inputPointCloud.hasReflectances())
srcPartition.setReflectance(
i, inputPointCloud.getReflectance(inputIdx));
} }
PCCPointSet3 partitionInOriginCloud;
getSrcPartition(
inputPointCloud, partitionInOriginCloud, partitionOriginIdxes);
_sliceId = partition.sliceId; _sliceId = partition.sliceId;
_tileId = partition.tileId; _tileId = partition.tileId;
_sliceOrigin = partition.origin; _sliceOrigin = partition.origin;
compressPartition(srcPartition, params, outputFn, reconstructedCloud); compressPartition(
srcPartition, partitionInOriginCloud, params, outputFn,
reconstructedCloud);
} }
return 0; return 0;
...@@ -240,6 +309,7 @@ PCCTMC3Encoder3::fixupParameterSets(EncoderParams* params) ...@@ -240,6 +309,7 @@ PCCTMC3Encoder3::fixupParameterSets(EncoderParams* params)
void void
PCCTMC3Encoder3::compressPartition( PCCTMC3Encoder3::compressPartition(
const PCCPointSet3& inputPointCloud, const PCCPointSet3& inputPointCloud,
const PCCPointSet3& originPartCloud,
EncoderParams* params, EncoderParams* params,
std::function<void(const PayloadBuffer&)> outputFn, std::function<void(const PayloadBuffer&)> outputFn,
PCCPointSet3* reconstructedCloud) PCCPointSet3* reconstructedCloud)
...@@ -250,7 +320,28 @@ PCCTMC3Encoder3::compressPartition( ...@@ -250,7 +320,28 @@ PCCTMC3Encoder3::compressPartition(
// - recolour // - recolour
pointCloud.clear(); pointCloud.clear();
quantization(inputPointCloud); pointCloud = inputPointCloud;
// Offset the point cloud to account for (preset) _sliceOrigin.
Vec3<double> sliceOriginD{double(_sliceOrigin[0]), double(_sliceOrigin[1]),
double(_sliceOrigin[2])};
// The new maximum bounds of the offset cloud
Vec3<int> maxBound{0};
const size_t pointCount = pointCloud.getPointCount();
for (size_t i = 0; i < pointCount; ++i) {
const Vec3<double> point = (pointCloud[i] -= sliceOriginD);
for (int k = 0; k < 3; ++k) {
const int k_coord = int(point[k]);
assert(k_coord >= 0);
if (maxBound[k] < k_coord)
maxBound[k] = k_coord;
}
}
// todo(df): don't update maxBound if something is forcing the value?
_sliceBoxWhd = maxBound;
// geometry encoding // geometry encoding
if (1) { if (1) {
...@@ -278,12 +369,9 @@ PCCTMC3Encoder3::compressPartition( ...@@ -278,12 +369,9 @@ PCCTMC3Encoder3::compressPartition(
// recolouring // recolouring
// NB: recolouring is required if points are added / removed // NB: recolouring is required if points are added / removed
bool recolourNeeded = if (_gps->geom_unique_points_flag || _gps->trisoup_node_size_log2 > 0) {
_gps->geom_unique_points_flag || _gps->trisoup_node_size_log2 > 0;
if (recolourNeeded) {
recolour( recolour(
params->recolour, inputPointCloud, _sps->seq_source_geom_scale_factor, params->recolour, originPartCloud, _sps->seq_source_geom_scale_factor,
_sps->seq_bounding_box_xyz0, _sliceOrigin, &pointCloud); _sps->seq_bounding_box_xyz0, _sliceOrigin, &pointCloud);
} }
...@@ -425,9 +513,13 @@ PCCTMC3Encoder3::appendReconstructedPoints(PCCPointSet3* reconstructedCloud) ...@@ -425,9 +513,13 @@ PCCTMC3Encoder3::appendReconstructedPoints(PCCPointSet3* reconstructedCloud)
// translates and scales inputPointCloud, storing the result in // translates and scales inputPointCloud, storing the result in
// this->pointCloud for use by the encoding process. // this->pointCloud for use by the encoding process.
void PCCPointSet3
PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud) PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud)
{ {
PCCPointSet3 pointCloud0;
pointCloud0.clear();
quantizedToOrigin.clear();
// Currently the sequence width/height/depth must be set // Currently the sequence width/height/depth must be set
assert(_sps->seq_bounding_box_whd != Vec3<int>{0}); assert(_sps->seq_bounding_box_whd != Vec3<int>{0});
...@@ -447,11 +539,11 @@ PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud) ...@@ -447,11 +539,11 @@ PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud)
if (_gps->geom_unique_points_flag) { if (_gps->geom_unique_points_flag) {
quantizePositionsUniq( quantizePositionsUniq(
_sps->seq_source_geom_scale_factor, _sps->seq_bounding_box_xyz0, _sps->seq_source_geom_scale_factor, _sps->seq_bounding_box_xyz0,
clampBox, inputPointCloud, &pointCloud); clampBox, inputPointCloud, &pointCloud0, quantizedToOrigin);
} else {