uvgVPCCenc 1.0.0
uvgVPCCenc is an open-source real-time V-PCC encoder library written in C++ from scratch.
Loading...
Searching...
No Matches
utilsPatchGeneration.hpp
Go to the documentation of this file.
1/*****************************************************************************
2 * This file is part of uvgVPCCenc V-PCC encoder.
3 *
4 * Copyright (c) 2024, Tampere University, ITU/ISO/IEC, project contributors
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without modification,
8 * are permitted provided that the following conditions are met:
9 *
10 * * Redistributions of source code must retain the above copyright notice, this
11 * list of conditions and the following disclaimer.
12 *
13 * * Redistributions in binary form must reproduce the above copyright notice, this
14 * list of conditions and the following disclaimer in the documentation and/or
15 * other materials provided with the distribution.
16 *
17 * * Neither the name of the Tampere University or ITU/ISO/IEC nor the names of its
18 * contributors may be used to endorse or promote products derived from
19 * this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
25 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 * INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION HOWEVER CAUSED AND ON
28 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 * INCLUDING NEGLIGENCE OR OTHERWISE ARISING IN ANY WAY OUT OF THE USE OF THIS
31 ****************************************************************************/
32
34
35#pragma once
36
37#include "utils/utils.hpp"
38#include "uvgvpcc/log.hpp"
39#include <fstream>
40#include <vector>
41#include <unordered_map>
42
43using namespace uvgvpcc_enc;
44
45
46// TODO(lf): how to handle this type of lut table variable ?
47// NOLINTNEXTLINE(cert-err58-cpp)
48static const std::array<std::vector<Vector3<int32_t>>, 9> adjacentPointsSearch = {{
49 // Adjacent shift for squared distance 1
50 {{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}},
51 // Adjacent shift for squared distance 2
52 {{1, 1, 0},
53 {1, -1, 0},
54 {-1, 1, 0},
55 {-1, -1, 0},
56 {0, 1, 1},
57 {0, 1, -1},
58 {0, -1, 1},
59 {0, -1, -1},
60 {1, 0, 1},
61 {-1, 0, 1},
62 {1, 0, -1},
63 {-1, 0, -1}},
64 // Adjacent shift for squared distance 3
65 {{1, 1, 1}, {1, 1, -1}, {1, -1, 1}, {1, -1, -1}, {-1, 1, 1}, {-1, 1, -1}, {-1, -1, 1}, {-1, -1, -1}},
66 // Adjacent shift for squared distance 4
67 {{2, 0, 0}, {-2, 0, 0}, {0, 2, 0}, {0, -2, 0}, {0, 0, 2}, {0, 0, -2}},
68 // Adjacent shift for squared distance 5
69 {{2, 1, 0}, {2, -1, 0}, {1, 2, 0}, {1, -2, 0}, {-1, 2, 0}, {-1, -2, 0}, {-2, 1, 0}, {-2, -1, 0},
70 {0, 2, 1}, {0, 2, -1}, {0, 1, 2}, {0, 1, -2}, {0, -1, 2}, {0, -1, -2}, {0, -2, 1}, {0, -2, -1},
71 {1, 0, 2}, {-1, 0, 2}, {2, 0, 1}, {-2, 0, 1}, {2, 0, -1}, {-2, 0, -1}, {1, 0, -2}, {-1, 0, -2}},
72 // Adjacent shift for squared distance 6
73 {{2, 1, 1}, {2, 1, -1}, {2, -1, 1}, {2, -1, -1}, {1, 2, 1}, {1, 2, -1}, {1, 1, 2}, {1, 1, -2},
74 {1, -1, 2}, {1, -1, -2}, {1, -2, 1}, {1, -2, -1}, {-1, 2, 1}, {-1, 2, -1}, {-1, 1, 2}, {-1, 1, -2},
75 {-1, -1, 2}, {-1, -1, -2}, {-1, -2, 1}, {-1, -2, -1}, {-2, 1, 1}, {-2, 1, -1}, {-2, -1, 1}, {-2, -1, -1}},
76 // Adjacent shift for squared distance 7 (does not exist in an integer grid)
77 {},
78 // Adjacent shift for squared distance 8
79 {{2, 2, 0},
80 {2, -2, 0},
81 {-2, 2, 0},
82 {-2, -2, 0},
83 {0, 2, 2},
84 {0, 2, -2},
85 {0, -2, 2},
86 {0, -2, -2},
87 {2, 0, 2},
88 {-2, 0, 2},
89 {2, 0, -2},
90 {-2, 0, -2}},
91 // Adjacent shift for squared distance 9
92 {{3, 0, 0}, {-3, 0, 0}, {0, 3, 0}, {0, -3, 0}, {0, 0, 3}, {0, 0, -3}, {2, 2, 1}, {2, 2, -1}, {2, 1, 2}, {2, 1, -2},
93 {2, -1, 2}, {2, -1, -2}, {2, -2, 1}, {2, -2, -1}, {1, 2, 2}, {1, 2, -2}, {1, -2, 2}, {1, -2, -2}, {-1, 2, 2}, {-1, 2, -2},
94 {-1, -2, 2}, {-1, -2, -2}, {-2, 2, 1}, {-2, 2, -1}, {-2, 1, 2}, {-2, 1, -2}, {-2, -1, 2}, {-2, -1, -2}, {-2, -2, 1}, {-2, -2, -1}},
95}};
96
97const std::array<Vector3<uint8_t>, 6> ppiColors = {{
98 {51, 51, 51}, // Charcoal Gray
99 {0, 102, 51}, // Forest Green
100 {153, 0, 0}, // Rich Crimson
101 {0, 51, 102}, // Deep Blue
102 {255, 204, 0}, // Golden Yellow
103 {102, 204, 204} // Muted Cyan
104}};
105
106const std::array<Vector3<uint8_t>, 114> patchColors = {{
107 // Red color is for points not being part of a patch before the 2D projection.
108{139,0,0},{165,42,42},{178,34,34},{220,20,60},{255,99,71},{255,127,80},{205,92,92},{240,128,128},{233,150,122},{250,128,114},{255,160,122},{255,69,0},{255,140,0},{255,165,0},{255,215,0},{184,134,11},{218,165,32},{238,232,170},{189,183,107},{240,230,140},{255,255,0},{32,178,170},{0,128,128},{0,139,139},{0,255,255},{0,255,255},{224,255,255},{0,206,209},{72,209,204},{175,238,238},{176,224,230},{95,158,160},{70,130,180},{100,149,237},{0,191,255},{30,144,255},{173,216,230},{135,206,235},{135,206,250},{25,25,112},{0,0,128},{0,0,139},{0,0,205},{0,0,255},{65,105,225},{138,43,226},{75,0,130},{72,61,139},{106,90,205},{123,104,238},{147,112,219},{139,0,139},{148,0,211},{153,50,204},{186,85,211},{128,0,128},{216,191,216},{221,160,221},{238,130,238},{255,0,255},{218,112,214},{199,21,133},{219,112,147},{255,20,147},{255,105,180},{255,182,193},{255,192,203},{250,235,215},{245,245,220},{255,228,196},{255,235,205},{245,222,179},{255,248,220},{255,250,205},{250,250,210},{255,255,224},{139,69,19},{160,82,45},{210,105,30},{205,133,63},{244,164,96},{222,184,135},{210,180,140},{188,143,143},{255,228,181},{255,222,173},{255,218,185},{255,228,225},{255,240,245},{250,240,230},{253,245,230},{255,239,213},{255,245,238},{245,255,250},{112,128,144},{119,136,153},{176,196,222},{230,230,250},{255,250,240},{240,248,255},{248,248,255},{240,255,240},{255,255,240},{240,255,255},{255,250,250},{0,0,0},{105,105,105},{128,128,128},{169,169,169},{192,192,192},{211,211,211},{220,220,220},{245,245,245},{255,255,255},
109}};
110
111template <typename T, typename TT>
112inline double dotProduct(const std::array<T, 3>& arr1, const std::array<TT, 3>& arr2) {
113 return arr1[0] * arr2[0] + arr1[1] * arr2[1] + arr1[2] * arr2[2];
114}
115
116inline void exportPointCloud(const std::string& plyFilePath, const std::vector<Vector3<typeGeometryInput>>& geometries,
117 const std::vector<Vector3<uint8_t>>& attributes,
118 const std::vector<Vector3<double>>& normals = {}) {
119 const bool hasNormals = !normals.empty();
120
121 std::ofstream fout(plyFilePath, std::ofstream::out | std::ofstream::trunc);
122 if (!fout.is_open()) {
123 throw std::runtime_error("Error : can't create a stream from : " + plyFilePath);
124 }
125
126 fout << "ply";
127 fout << "\nformat ascii 1.0";
128 fout << "\nelement vertex " << geometries.size();
129 fout << "\nproperty int x";
130 fout << "\nproperty int y";
131 fout << "\nproperty int z";
132 if (hasNormals) {
133 fout << "\nproperty double nx";
134 fout << "\nproperty double ny";
135 fout << "\nproperty double nz";
136 }
137 fout << "\nproperty uchar red";
138 fout << "\nproperty uchar green";
139 fout << "\nproperty uchar blue";
140 fout << "\nend_header\n";
141
142 fout << std::setprecision(std::numeric_limits<double>::max_digits10);
143 for (size_t i = 0; i < geometries.size(); ++i) {
144 fout << geometries[i][0] << " " << geometries[i][1] << " " << geometries[i][2];
145 if (hasNormals) {
146 fout << " " << normals[i][0] << " " << normals[i][1] << " " << normals[i][2];
147 }
148 fout << " " << static_cast<int>(attributes[i][0]) << " " << static_cast<int>(attributes[i][1]) << " "
149 << static_cast<int>(attributes[i][2]);
150 fout << std::endl;
151 }
152 fout.close();
153}
154
155// Hash function for vector3
156template <typename T>
158 size_t operator()(const Vector3<T>& vector) const {
159 std::hash<T> hasher;
160 size_t hash = 0;
161 for (const auto& elem : vector) {
162 // Use a simple hash combining algorithm
163 hash ^= hasher(elem) + 0x9e3779b9 + (hash << 6U) + (hash >> 2U);
164 }
165 return hash;
166 }
167};
168
169
170// The voxelization is done so to have those three vectors sharing the same order (that is sharing the same voxel index) :
171// -> A list of voxel (vector of coordinates)
172// -> A list of list of points (vector of vector of point index). This associate a voxel with the points inside it.
173// -> A list of voxel PPI (vector of PPI (int))
174//
175// Example with a voxel index i:
176// voxelizedPointsGeometry[i] stores the coordinate of the voxel i
177// voxelIdToPointsId[i] stores the index of the points inside the voxel i
178// voxelsPPIs[i] stores the PPI of the voxel i
179//
180// The use of a map is needed as several points can be inside one voxel. We want to find in a fast way if the current point resides in a voxel
181// that has already been created. For an input point j: voxelCoordToVoxelIndex[ voxelCoordsOf(j) ] = i
182//
183// This structure makes computation easier for the patch generation and for applying voxel data to points data.
184// To achieve this, we need a temporary map that interfaces the voxel coordinates with the voxel index in those three list.
185
186// voxelizedPointsGeometry -> list of the voxels. That is, a list of the coordinates of the voxels.
187// voxelIdToPointsId -> For each voxel, stores the index of the points inside. A voxel is defined by a voxel index, that is given by the
188// filling order of the vector. Thus, the third voxel to be added to this structure will have a voxel index of 2.
189// voxelCoordToVoxelIndex -> Temporary structure. Associate a voxel to its index in 'voxelIdToPointsId' (its voxel index). This structure is a
190// map whose key is a voxel coordinate and the value is a voxel index.
191//
192// For each point if the input point cloud :
193// Compute the coordinates of the voxel inside which the current point should be.
194// If those voxel coordinates are already in the map 'voxelCoordToVoxelIndex':
195// Add the current input point index to the list of points index of the found voxel.
196// That is, add the current input point index to the list of points of the voxel in 'voxelIdToPointsId'
197// If those voxel coordinates are not in the map 'voxelCoordToVoxelIndex':
198// Create a new item in the map 'voxelCoordToVoxelIndex'. This associates the voxel coordinates to its voxel index (the current size
199// of the list of voxel === the index of this voxel in the list of voxel). Add a new voxel to 'voxelIdToPointsId'. This associates the
200// voxel index (the current size of the list of voxel === the index of this voxel in the list of voxel) to an empty list of input
201// point index. Create a new voxel by adding the voxel coordinates in the voxel list 'voxelizedPointsGeometry'. Add the current input
202// point index to the list of points index of the new voxel. That is, add the current input point index to the list of points of the
203// voxel in 'voxelIdToPointsId'
204
205
206// Notice that in the refined segmentation, the inputPointsGeometry can be the voxelizedPointsGeometry. Indeed, when the voxelization is
207// activated, the refined segmentation is applying a second voxelization step. The created voxelized point cloud is then the result of two
208// voxelizations.
209inline void voxelization(const std::vector<Vector3<typeGeometryInput>>& inputPointsGeometry,
210 std::vector<Vector3<typeGeometryInput>>& voxelizedPointsGeometry,
211 std::vector<std::vector<size_t>>& voxelIdToPointsId, const size_t inputBitResolution,
212 const size_t outputBitResolution) {
214 LogLevel::TRACE, "PATCH GENERATION",
215 "Voxelization from " + std::to_string(inputBitResolution) + " to " + std::to_string(outputBitResolution) + " bits of resolution.\n");
216
217 std::unordered_map<Vector3<typeGeometryInput>, size_t, vector3Hash<typeGeometryInput>>
218 voxelCoordToVoxelIndex;
219
220 // Maximum number of voxel is : (1 << outputBitResolution)^3 (aka number of cell in the grid)
221 // approximateVoxelCount is : (1 << outputBitResolution)^2 = (1 << outputBitResolution x 2)
222 // TODO(lf) : check if it is more clever to divide the inputPointsGeometry.size() by the bit resolution difference
223 const size_t approximateVoxelCount = 1U << (outputBitResolution * 2U); // Rough approximation
224 voxelizedPointsGeometry.reserve(approximateVoxelCount);
225 voxelIdToPointsId.reserve(approximateVoxelCount);
226 voxelCoordToVoxelIndex.reserve(approximateVoxelCount);
227
228 // Maximum number of points in one voxel : (inputBitResolution - outputBitResolution)^3
229 // approximatePointsCountInOneVoxel : (inputBitResolution - outputBitResolution)^2
230 const size_t approximatePointsCountInOneVoxel =
231 (inputBitResolution - outputBitResolution) * (inputBitResolution - outputBitResolution);
232 const size_t voxelizationShift = inputBitResolution - outputBitResolution;
233
234 // Iteration over all input points //
235 for (size_t inputPointIndex = 0; inputPointIndex < inputPointsGeometry.size(); ++inputPointIndex) {
236 const Vector3<typeGeometryInput>& inputPoint = inputPointsGeometry[inputPointIndex];
237
238 // TODO(lf): Discuss it together. The +halfVoxelSize seems natural (is done in TMC2). However, it is repsonsible for a "bug" in
239 // ready_for_winter_9 (frame number 5), where the bottom of the shoes is at the very top of the bounding box in the decoded point
240 // cloud (congruence)
241 const Vector3<typeGeometryInput> voxCoord{
242 static_cast<typeGeometryInput>(inputPoint[0] >> voxelizationShift),
243 static_cast<typeGeometryInput>(inputPoint[1] >> voxelizationShift),
244 static_cast<typeGeometryInput>(inputPoint[2] >> voxelizationShift)
245 };
246
247 // Check if the voxel corresponding to the current input point was already created.
248 auto mapItem = voxelCoordToVoxelIndex.find(voxCoord);
249 if (mapItem == voxelCoordToVoxelIndex.end()) {
250 // The current point belongs to a voxel that has not been created yet //
251
252 // The voxel index corresponds to its index in the list of voxel. That is, to its index in 'voxelizedPointsGeometry'. This
253 // corresponds to the size of 'voxelizedPointsGeometry' before to add the voxel. Indeed and for example, the 4th voxel has an
254 // index of 3. Notice that voxelIndex = voxelizedPointsGeometry.size() = voxelIdToPointsId.size() = voxelCoordToVoxelIndex.size()
255 const size_t voxelIndex = voxelizedPointsGeometry.size();
256
257 // Add an item to the map 'voxelCoordToVoxelIndex', that associate the voxel coordinates to its voxel index.
258 // Get a pointer to this new item.
259 mapItem = voxelCoordToVoxelIndex.emplace(voxCoord, voxelIndex).first;
260
261 // Add the voxel to the voxel list
262 voxelizedPointsGeometry.push_back(voxCoord);
263
264 // Create a new empty input point index list at the corresponding voxel index (that is, at the end of the ('voxelIdToPointsId')
265 voxelIdToPointsId.emplace_back();
266 voxelIdToPointsId.back().reserve(approximatePointsCountInOneVoxel);
267 }
268
269 // Associate the current point with the voxel it belongs to //
270 // mapItem->second => voxel index
271 voxelIdToPointsId[mapItem->second].push_back(inputPointIndex);
272 }
273}
static void log(LogLevel level, const std::string &context, const std::string &message)
Definition log.cpp:84
Definition utils.hpp:59
Definition log.hpp:43
uint16_t typeGeometryInput
Definition utils.hpp:49
Definition utilsPatchGeneration.hpp:157
size_t operator()(const Vector3< T > &vector) const
Definition utilsPatchGeneration.hpp:158
void voxelization(const std::vector< Vector3< typeGeometryInput > > &inputPointsGeometry, std::vector< Vector3< typeGeometryInput > > &voxelizedPointsGeometry, std::vector< std::vector< size_t > > &voxelIdToPointsId, const size_t inputBitResolution, const size_t outputBitResolution)
Definition utilsPatchGeneration.hpp:209
double dotProduct(const std::array< T, 3 > &arr1, const std::array< TT, 3 > &arr2)
Definition utilsPatchGeneration.hpp:112
const std::array< Vector3< uint8_t >, 114 > patchColors
Definition utilsPatchGeneration.hpp:106
void exportPointCloud(const std::string &plyFilePath, const std::vector< Vector3< typeGeometryInput > > &geometries, const std::vector< Vector3< uint8_t > > &attributes, const std::vector< Vector3< double > > &normals={})
Definition utilsPatchGeneration.hpp:116
const std::array< Vector3< uint8_t >, 6 > ppiColors
Definition utilsPatchGeneration.hpp:97