Commit bcf5394c by Yiting Shao Committed by David Flynn

### 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: std::swap(getReflectance(index1), getReflectance(index2)); } } Box3 computeBoundingBox() const { Box3 bbox = {std::numeric_limits::max(), ... ... @@ -446,6 +447,34 @@ public: } return bbox; } //-------------------------------------------------------------------------- // Determine the bounding box of the set of points given by the indicies // given by iterating over [begin, end) template Box3 computeBoundingBox(ForwardIt begin, ForwardIt end) const { Box3 bbox = {std::numeric_limits::max(), std::numeric_limits::lowest()}; for (auto it = begin; it != end; ++it) { int i = *it; const Vec3& 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) { int i = 0; ... ...
 ... ... @@ -39,6 +39,8 @@ #include #include #include #include #include #include "KDTreeVectorOfVectorsAdaptor.h" #include "PCCPointSet.h" ... ... @@ -80,10 +82,12 @@ quantizePositionsUniq( const Vec3 offset, const Box3 clamp, const PCCPointSet3& src, PCCPointSet3* dst) PCCPointSet3* dst, std::multimap, int32_t>& doubleQuantizedToOrigin) { // Determine the set of unique quantised points std::multimap, int32_t> intQuantizedToOrigin; std::set> uniquePoints; int numSrcPoints = src.getPointCount(); for (int i = 0; i < numSrcPoints; ++i) { ... ... @@ -96,6 +100,7 @@ quantizePositionsUniq( } uniquePoints.insert(quantizedPoint); intQuantizedToOrigin.insert(std::make_pair(quantizedPoint, i)); } // Populate output point cloud ... ... @@ -105,12 +110,18 @@ quantizePositionsUniq( dst->addRemoveAttributes(src.hasColors(), src.hasReflectances()); } dst->resize(uniquePoints.size()); doubleQuantizedToOrigin.clear(); int idx = 0; for (const auto& point : uniquePoints) { auto& dstPoint = (*dst)[idx++]; for (int k = 0; k < 3; ++k) dstPoint[k] = double(point[k]); std::multimap, 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 @@ #include "PCCPointSet.h" #include "PCCPointSetProcessing.h" #include "hls.h" #include "partitioning.h" 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 { // NB: this only makes sense for setting configurable parameters AttributeBrickHeader abh; ... ... @@ -88,14 +75,8 @@ struct EncoderParams { // Filename for saving recoloured point cloud. std::string postRecolorPath; // Method for partitioning the input cloud PartitionMethod partitionMethod; // Number of slices used by PartitionMethod::kUniformGeom int partitionNumUniformGeom; // Depth of octree used by PartitionMethod::kOctreeUniform int partitionOctreeDepth; // Parameters that control partitioning PartitionParams partition; // attribute recolouring parameters RecolourParams recolour; ... ... @@ -118,6 +99,7 @@ public: void compressPartition( const PCCPointSet3& inputPointCloud, const PCCPointSet3& originPartCloud, EncoderParams* params, std::function outputFn, PCCPointSet3* reconstructedCloud = nullptr); ... ... @@ -129,7 +111,12 @@ private: void encodeGeometryBrick(PayloadBuffer* buf); void quantization(const PCCPointSet3& inputPointCloud); PCCPointSet3 quantization(const PCCPointSet3& inputPointCloud); void getSrcPartition( const PCCPointSet3& inputPointCloud, PCCPointSet3& srcPartition, std::vector indexes); private: PCCPointSet3 pointCloud; ... ... @@ -150,6 +137,9 @@ private: // Identifies the current tile int _tileId; // Map quantized points to the original input points std::multimap, int32_t> quantizedToOrigin; }; //============================================================================ ... ...
 ... ... @@ -168,8 +168,8 @@ operator<<(std::ostream& out, const PartitionMethod& val) { switch (val) { case PartitionMethod::kNone: out << "0 (None)"; break; case PartitionMethod::kUniformGeom: out << "0 (UniformGeom)"; break; case PartitionMethod::kOctreeUniform: out << "0 (UniformOctree)"; break; case PartitionMethod::kUniformGeom: out << "2 (UniformGeom)"; break; case PartitionMethod::kOctreeUniform: out << "3 (UniformOctree)"; break; default: out << int(val) << " (Unknown)"; break; } return out; ... ... @@ -323,22 +323,28 @@ ParseParameters(int argc, char* argv[], Parameters& params) "Enables removal of duplicated points") ("partitionMethod", params.encoder.partitionMethod, PartitionMethod::kNone, params.encoder.partition.method, PartitionMethod::kUniformGeom, "Method used to partition input point cloud into slices/tiles:\n" " 0: none\n" " 1: none (deprecated)\n" " 2: n Uniform-Geometry partition bins along the longest edge\n" " 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", params.encoder.partitionOctreeDepth, 2, "Depth of octree partition for partitionMethod=3") params.encoder.partition.octreeDepth, 1, "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", params.encoder.sps.cabac_bypass_stream_enabled_flag, false, ... ... @@ -632,6 +638,12 @@ ParseParameters(int argc, char* argv[], Parameters& params) // 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.neighbour_avail_boundary_log2) err.error() << "Geometry intra prediction requires finite" ... ...
 ... ... @@ -94,48 +94,117 @@ PCCTMC3Encoder3::compress( // initial geometry IDs _tileId = 0; _sliceId = 0; _sliceOrigin = Vec3{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> tileMaps; if (params->partition.tileSize) { PCCPointSet3 inverseQuantizedCloud; Box3 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 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 (params->partitionMethod == PartitionMethod::kNone) { if (params->partition.method == PartitionMethod::kNone) { // todo(df): params->gps.geom_box_present_flag = false; _sliceOrigin = Vec3{0}; compressPartition(inputPointCloud, params, outputFn, reconstructedCloud); compressPartition( quantizedInputCloud, inputPointCloud, params, outputFn, reconstructedCloud); return 0; } // 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 // optional tile metadata for each partition. // - encode any tile metadata // todo(df): consider requiring partitioning function to sort the input // points and provide ranges rather than a set of indicies. PartitionSet partitions; do { PCCPointSet3 quantizedInputCloud; Box3 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) { // NB: this method is handled earlier case PartitionMethod::kNone: return 1; case PartitionMethod::kUniformGeom: partitions = partitionByUniformGeom( quantizedInputCloud, params->partitionNumUniformGeom); break; case PartitionMethod::kOctreeUniform: partitions = partitionByOctreeDepth( quantizedInputCloud, params->partitionOctreeDepth); break; for (int t = 0; t < tileMaps.size(); t++) { const auto& tile = tileMaps[t]; // Get the point cloud of current tile and compute their bounding boxes PCCPointSet3 tileCloud; getSrcPartition(quantizedInputCloud, tileCloud, tile); Box3 bbox = tileCloud.computeBoundingBox(); Vec3 tile_quantized_box_xyz0; for (int k = 0; k < 3; k++) { tile_quantized_box_xyz0[k] = int(bbox.min[k]); } // 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 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); 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)); } ... ... @@ -145,28 +214,28 @@ PCCTMC3Encoder3::compress( for (const auto& partition : partitions.slices) { // create partitioned point set PCCPointSet3 srcPartition; srcPartition.addRemoveAttributes( inputPointCloud.hasColors(), inputPointCloud.hasReflectances()); int partitionSize = partition.pointIndexes.size(); srcPartition.resize(partitionSize); for (int i = 0; i < partitionSize; i++) { int inputIdx = partition.pointIndexes[i]; srcPartition[i] = inputPointCloud[inputIdx]; if (inputPointCloud.hasColors()) srcPartition.setColor(i, inputPointCloud.getColor(inputIdx)); if (inputPointCloud.hasReflectances()) srcPartition.setReflectance( i, inputPointCloud.getReflectance(inputIdx)); getSrcPartition(quantizedInputCloud, srcPartition, partition.pointIndexes); // Get the original partial cloud corresponding to each slice for recolor std::vector partitionOriginIdxes; for (int i = 0; i < partition.pointIndexes.size(); i++) { const auto& point = srcPartition[i]; std::multimap, int32_t>::iterator pos; for (pos = quantizedToOrigin.lower_bound(point); pos != quantizedToOrigin.upper_bound(point); ++pos) { partitionOriginIdxes.push_back(pos->second); } } PCCPointSet3 partitionInOriginCloud; getSrcPartition( inputPointCloud, partitionInOriginCloud, partitionOriginIdxes); _sliceId = partition.sliceId; _tileId = partition.tileId; _sliceOrigin = partition.origin; compressPartition(srcPartition, params, outputFn, reconstructedCloud); compressPartition( srcPartition, partitionInOriginCloud, params, outputFn, reconstructedCloud); } return 0; ... ... @@ -240,6 +309,7 @@ PCCTMC3Encoder3::fixupParameterSets(EncoderParams* params) void PCCTMC3Encoder3::compressPartition( const PCCPointSet3& inputPointCloud, const PCCPointSet3& originPartCloud, EncoderParams* params, std::function outputFn, PCCPointSet3* reconstructedCloud) ... ... @@ -250,7 +320,28 @@ PCCTMC3Encoder3::compressPartition( // - recolour pointCloud.clear(); quantization(inputPointCloud); pointCloud = inputPointCloud; // Offset the point cloud to account for (preset) _sliceOrigin. Vec3 sliceOriginD{double(_sliceOrigin[0]), double(_sliceOrigin[1]), double(_sliceOrigin[2])}; // The new maximum bounds of the offset cloud Vec3 maxBound{0}; const size_t pointCount = pointCloud.getPointCount(); for (size_t i = 0; i < pointCount; ++i) { const Vec3 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 if (1) { ... ... @@ -278,12 +369,9 @@ PCCTMC3Encoder3::compressPartition( // recolouring // NB: recolouring is required if points are added / removed bool recolourNeeded = _gps->geom_unique_points_flag || _gps->trisoup_node_size_log2 > 0; if (recolourNeeded) { if (_gps->geom_unique_points_flag || _gps->trisoup_node_size_log2 > 0) { 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); } ... ... @@ -425,9 +513,13 @@ PCCTMC3Encoder3::appendReconstructedPoints(PCCPointSet3* reconstructedCloud) // translates and scales inputPointCloud, storing the result in // this->pointCloud for use by the encoding process. void PCCPointSet3 PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud) { PCCPointSet3 pointCloud0; pointCloud0.clear(); quantizedToOrigin.clear(); // Currently the sequence width/height/depth must be set assert(_sps->seq_bounding_box_whd != Vec3{0}); ... ... @@ -447,11 +539,11 @@ PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud) if (_gps->geom_unique_points_flag) { quantizePositionsUniq( _sps->seq_source_geom_scale_factor, _sps->seq_bounding_box_xyz0, clampBox, inputPointCloud, &pointCloud); clampBox, inputPointCloud, &pointCloud0, quantizedToOrigin); } else { quantizePositions( _sps->seq_source_geom_scale_factor, _sps->seq_bounding_box_xyz0, clampBox, inputPointCloud, &pointCloud); clampBox, inputPointCloud, &pointCloud0); } // Offset the point cloud to account for (preset) _sliceOrigin. ... ... @@ -461,9 +553,9 @@ PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud) // The new maximum bounds of the offset cloud Vec3 maxBound{0}; const size_t pointCount = pointCloud.getPointCount(); const size_t pointCount = pointCloud0.getPointCount(); for (size_t i = 0; i < pointCount; ++i) { const Vec3 point = (pointCloud[i] -= sliceOriginD); const Vec3 point = (pointCloud0[i] -= sliceOriginD); for (int k = 0; k < 3; ++k) { const int k_coord = int(point[k]); assert(k_coord >= 0); ... ... @@ -474,6 +566,36 @@ PCCTMC3Encoder3::quantization(const PCCPointSet3& inputPointCloud) // todo(df): don't update maxBound if something is forcing the value? _sliceBoxWhd = maxBound; return pointCloud0; } //---------------------------------------------------------------------------- // get the partial point cloud according to required point indexes void PCCTMC3Encoder3::getSrcPartition( const PCCPointSet3& inputPointCloud, PCCPointSet3& srcPartition, std::vector Indexes) { //PCCPointSet3 srcPartition; srcPartition.addRemoveAttributes( inputPointCloud.hasColors(), inputPointCloud.hasReflectances()); int partitionSize = Indexes.size(); srcPartition.resize(partitionSize); for (int i = 0; i < partitionSize; i++) { int inputIdx = Indexes[i]; srcPartition[i] = inputPointCloud[inputIdx]; if (inputPointCloud.hasColors()) srcPartition.setColor(i, inputPointCloud.getColor(inputIdx)); if (inputPointCloud.hasReflectances()) srcPartition.setReflectance(i, inputPointCloud.getReflectance(inputIdx)); } //return srcPartition; } //============================================================================ ... ...
 ... ... @@ -37,9 +37,26 @@ #include #include #include #include namespace pcc { //============================================================================ // Determine whether half of slices are smaller than maxPoints bool halfQualified(const std::vector slices, int maxPoints) { int Qualified = 0; for (int i = 0; i < slices.size(); i++) { if (slices[i].pointIndexes.size() < maxPoints) Qualified++; } return ((double)Qualified / (double)slices.size()) > 0.5; } //============================================================================ template ... ... @@ -82,10 +99,11 @@ shortestAxis(const Box3& curBox) // ratio of longest:shortest axis is used). // No tile metadata is generated. PartitionSet partitionByUniformGeom(const PCCPointSet3& cloud, int numPartitions) std::vector partitionByUniformGeom( const PartitionParams& params, const PCCPointSet3& cloud, int tileID) { PartitionSet partitions; std::vector slices; Box3 bbox = cloud.computeBoundingBox(); ... ... @@ -94,56 +112,63 @@ partitionByUniformGeom(const PCCPointSet3& cloud, int numPartitions) int minEdgeAxis = shortestAxis(bbox); int minEdge = bbox.max[minEdgeAxis] - bbox.min[minEdgeAxis]; int sliceNum; int sliceSize; if (!numPartitions) { sliceNum = maxEdge / minEdge; sliceSize = minEdge; } else { sliceNum = numPartitions; sliceSize = maxEdge / sliceNum; } partitions.slices.resize(sliceNum); int sliceNum = minEdge ? (maxEdge / minEdge) : 1; int sliceSize = minEdge ? minEdge : maxEdge; for (int i = 0; i < sliceNum; i++) { auto& slice = partitions.slices[i]; slice.sliceId = i; slice.tileId = -1; slice.origin = Vec3{0}; } while (1) { slices.clear(); slices.resize(sliceNum); for (int i = 0; i < sliceNum; i++) { auto& slice = slices[i]; slice.sliceId = i;