|
|
|
|
@ -754,6 +754,7 @@ public:
@@ -754,6 +754,7 @@ public:
|
|
|
|
|
cv::Mat& atlasMat); |
|
|
|
|
void GlobalSeamLeveling(); |
|
|
|
|
void GlobalSeamLeveling3(); |
|
|
|
|
void GlobalSeamLeveling4(); |
|
|
|
|
void LocalSeamLeveling(); |
|
|
|
|
void LocalSeamLeveling3(); |
|
|
|
|
|
|
|
|
|
@ -775,6 +776,11 @@ public:
@@ -775,6 +776,11 @@ public:
|
|
|
|
|
std::vector<uint32_t> faceToPatchID; // faceID → 新 patchID 映射
|
|
|
|
|
void MergeSameViewPatches(); |
|
|
|
|
void BuildSeamEdgesFromFaceToPatchID(); |
|
|
|
|
// 桥接:用 rcPatches + VirtualFaceMap 构建 TexturePatch 并调用 GlobalSeamLeveling3
|
|
|
|
|
void ApplyGlobalSeamLevelingOnRCPatches( |
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
Image8U3& atlas, |
|
|
|
|
int textureSize); |
|
|
|
|
void RunSeamLevelingOnAtlas(Image8U3& atlas, int textureSize); |
|
|
|
|
bool RasterizeVirtualFaces( |
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
@ -867,7 +873,7 @@ public:
@@ -867,7 +873,7 @@ public:
|
|
|
|
|
std::vector<std::vector<IIndex>>& virtualFaceViews, |
|
|
|
|
const Mesh::FaceFacesArr& faceFaces, |
|
|
|
|
int iterations = 6) |
|
|
|
|
{ |
|
|
|
|
{ |
|
|
|
|
for (int iter = 0; iter < iterations; ++iter) { |
|
|
|
|
auto newViews = virtualFaceViews; |
|
|
|
|
for (size_t i = 0; i < virtualFaceViews.size(); ++i) { |
|
|
|
|
@ -897,7 +903,7 @@ public:
@@ -897,7 +903,7 @@ public:
|
|
|
|
|
} |
|
|
|
|
virtualFaceViews.swap(newViews); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
float ComputeComprehensiveScore(const FaceData& data, const Normal& faceNormal, |
|
|
|
|
const Point3f& faceCenter, const Image& image); |
|
|
|
|
float EstimatePixelSize(const Point3f& faceCenter, const Normal& faceNormal, |
|
|
|
|
@ -8763,54 +8769,31 @@ uint32_t MeshTexture::FindNearestPatchForFaces(const std::vector<FIndex>& faceIn
@@ -8763,54 +8769,31 @@ uint32_t MeshTexture::FindNearestPatchForFaces(const std::vector<FIndex>& faceIn
|
|
|
|
|
|
|
|
|
|
void MeshTexture::CreateSeamVertices() |
|
|
|
|
{ |
|
|
|
|
DEBUG_EXTRA("Creating seam vertices with enhanced validation"); |
|
|
|
|
DEBUG_EXTRA(">>> CreateSeamVertices CALLED"); |
|
|
|
|
DEBUG_EXTRA(" seamEdges=%u, components=%zu, mapIdxPatch=%u, texturePatches=%zu, faces=%u", |
|
|
|
|
seamEdges.GetSize(), components.size(), mapIdxPatch.GetSize(), |
|
|
|
|
texturePatches.size(), faces.GetSize()); |
|
|
|
|
|
|
|
|
|
// 确保有纹理块
|
|
|
|
|
if (texturePatches.size() < 2) { |
|
|
|
|
DEBUG_EXTRA("Too few texture patches (%zu), skipping seam vertices creation", texturePatches.size()); |
|
|
|
|
seamVertices.Release(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
VIndex vs[2]; |
|
|
|
|
uint32_t vs0[2], vs1[2]; |
|
|
|
|
std::unordered_map<VIndex, uint32_t> mapVertexSeam; |
|
|
|
|
|
|
|
|
|
// 计算有效纹理块数量(排除无效纹理块)
|
|
|
|
|
const unsigned numPatches = static_cast<unsigned>(texturePatches.size() - 1); |
|
|
|
|
DEBUG_EXTRA("Total patches: %zu, valid patches: %u", texturePatches.size(), numPatches); |
|
|
|
|
|
|
|
|
|
// 验证组件和映射
|
|
|
|
|
if (components.size() != faces.GetSize()) { |
|
|
|
|
DEBUG_EXTRA("ERROR: components size mismatch: %zu vs %u", components.size(), faces.GetSize()); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (mapIdxPatch.GetSize() == 0) { |
|
|
|
|
DEBUG_EXTRA("ERROR: mapIdxPatch is empty"); |
|
|
|
|
if (seamEdges.empty()) { |
|
|
|
|
DEBUG_EXTRA("seamEdges is empty, nothing to do"); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
seamVertices.Release(); |
|
|
|
|
|
|
|
|
|
int validEdges = 0; |
|
|
|
|
int invalidEdges = 0; |
|
|
|
|
int skippedEdges = 0; |
|
|
|
|
|
|
|
|
|
for (uint32_t edgeIdx = 0; edgeIdx < seamEdges.GetSize(); ++edgeIdx) { |
|
|
|
|
const PairIdx& edge = seamEdges[edgeIdx]; |
|
|
|
|
|
|
|
|
|
// 检查面索引
|
|
|
|
|
// ★ 每条边都检查基本范围
|
|
|
|
|
if (edge.i >= faces.GetSize() || edge.j >= faces.GetSize()) { |
|
|
|
|
DEBUG_EXTRA("WARNING: Invalid face indices in seam edge %u: (%u, %u)", |
|
|
|
|
edgeIdx, edge.i, edge.j); |
|
|
|
|
invalidEdges++; |
|
|
|
|
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] face OOB: %u %u", edgeIdx, edge.i, edge.j); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 检查组件ID
|
|
|
|
|
if (edge.i >= components.size() || edge.j >= components.size()) { |
|
|
|
|
skippedEdges++; |
|
|
|
|
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] comp OOB: %u %u", edgeIdx, edge.i, edge.j); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -8818,44 +8801,37 @@ void MeshTexture::CreateSeamVertices()
@@ -8818,44 +8801,37 @@ void MeshTexture::CreateSeamVertices()
|
|
|
|
|
const uint32_t comp1 = components[edge.j]; |
|
|
|
|
|
|
|
|
|
if (comp0 == NO_ID || comp1 == NO_ID) { |
|
|
|
|
skippedEdges++; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 检查组件ID是否有效
|
|
|
|
|
if (comp0 >= mapIdxPatch.GetSize() || comp1 >= mapIdxPatch.GetSize()) { |
|
|
|
|
DEBUG_EXTRA("WARNING: Component IDs out of range at edge %u: comp0=%u, comp1=%u", |
|
|
|
|
edgeIdx, comp0, comp1); |
|
|
|
|
invalidEdges++; |
|
|
|
|
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] comp OOR: comp0=%u comp1=%u mapSz=%u", |
|
|
|
|
edgeIdx, comp0, comp1, mapIdxPatch.GetSize()); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const uint32_t idxPatch0 = mapIdxPatch[comp0]; |
|
|
|
|
const uint32_t idxPatch1 = mapIdxPatch[comp1]; |
|
|
|
|
|
|
|
|
|
// 检查纹理块索引是否有效
|
|
|
|
|
if (idxPatch0 >= texturePatches.size() || idxPatch1 >= texturePatches.size()) { |
|
|
|
|
DEBUG_EXTRA("WARNING: Invalid patch indices at edge %u: idxPatch0=%u, idxPatch1=%u, total patches=%zu", |
|
|
|
|
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] patch OOR: ip0=%u ip1=%u tpSz=%zu", |
|
|
|
|
edgeIdx, idxPatch0, idxPatch1, texturePatches.size()); |
|
|
|
|
invalidEdges++; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 检查是否属于同一个纹理块
|
|
|
|
|
if (idxPatch0 == idxPatch1) { |
|
|
|
|
// 属于同一个纹理块,不是接缝
|
|
|
|
|
skippedEdges++; |
|
|
|
|
if (idxPatch0 == idxPatch1) |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 跳过无效纹理块
|
|
|
|
|
if (idxPatch0 == texturePatches.size() - 1 || idxPatch1 == texturePatches.size() - 1) { |
|
|
|
|
// 至少有一个面在无效纹理块中
|
|
|
|
|
skippedEdges++; |
|
|
|
|
continue; |
|
|
|
|
// ★ 打印第一条边看看
|
|
|
|
|
if (edgeIdx == 0) { |
|
|
|
|
DEBUG_EXTRA(" FIRST EDGE: faceA=%u compA=%u patchA=%u | faceB=%u compB=%u patchB=%u", |
|
|
|
|
edge.i, comp0, idxPatch0, edge.j, comp1, idxPatch1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 获取边的顶点 - 直接调用,不检查返回值
|
|
|
|
|
VIndex vs[2]; |
|
|
|
|
uint32_t vs0[2], vs1[2]; |
|
|
|
|
|
|
|
|
|
// ★ 安全调用 GetEdgeVertices
|
|
|
|
|
scene.mesh.GetEdgeVertices(edge.i, edge.j, vs0, vs1); |
|
|
|
|
|
|
|
|
|
const Face& faceI = faces[edge.i]; |
|
|
|
|
@ -8863,8 +8839,6 @@ void MeshTexture::CreateSeamVertices()
@@ -8863,8 +8839,6 @@ void MeshTexture::CreateSeamVertices()
|
|
|
|
|
|
|
|
|
|
if (vs0[0] >= 3 || vs0[1] >= 3 || vs1[0] >= 3 || vs1[1] >= 3 || |
|
|
|
|
faceI[vs0[0]] != faceJ[vs1[0]] || faceI[vs0[1]] != faceJ[vs1[1]]) { |
|
|
|
|
DEBUG_EXTRA("WARNING: Edge vertices mismatch at edge %u", edgeIdx); |
|
|
|
|
invalidEdges++; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -8872,76 +8846,27 @@ void MeshTexture::CreateSeamVertices()
@@ -8872,76 +8846,27 @@ void MeshTexture::CreateSeamVertices()
|
|
|
|
|
vs[1] = faceI[vs0[1]]; |
|
|
|
|
|
|
|
|
|
if (vs[0] >= vertices.size() || vs[1] >= vertices.size()) { |
|
|
|
|
DEBUG_EXTRA("WARNING: Invalid vertex indices at edge %u: %u, %u", edgeIdx, vs[0], vs[1]); |
|
|
|
|
invalidEdges++; |
|
|
|
|
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] vertex OOB: %u %u vsSz=%zu", |
|
|
|
|
edgeIdx, vs[0], vs[1], vertices.size()); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 创建或获取接缝顶点
|
|
|
|
|
auto itSeamVertex0 = mapVertexSeam.emplace(std::make_pair(vs[0], static_cast<uint32_t>(seamVertices.GetSize()))); |
|
|
|
|
if (itSeamVertex0.second) { |
|
|
|
|
seamVertices.emplace_back(vs[0]); |
|
|
|
|
} |
|
|
|
|
SeamVertex& seamVertex0 = seamVertices[itSeamVertex0.first->second]; |
|
|
|
|
|
|
|
|
|
auto itSeamVertex1 = mapVertexSeam.emplace(std::make_pair(vs[1], static_cast<uint32_t>(seamVertices.GetSize()))); |
|
|
|
|
if (itSeamVertex1.second) { |
|
|
|
|
seamVertices.emplace_back(vs[1]); |
|
|
|
|
} |
|
|
|
|
SeamVertex& seamVertex1 = seamVertices[itSeamVertex1.first->second]; |
|
|
|
|
|
|
|
|
|
// 为纹理块0添加边
|
|
|
|
|
{ |
|
|
|
|
const TexCoord offset0(texturePatches[idxPatch0].rect.tl()); |
|
|
|
|
|
|
|
|
|
uint32_t texCoordIdx0 = edge.i * 3 + vs0[0]; |
|
|
|
|
uint32_t texCoordIdx1 = edge.i * 3 + vs0[1]; |
|
|
|
|
|
|
|
|
|
if (texCoordIdx0 < faceTexcoords.GetSize() && texCoordIdx1 < faceTexcoords.GetSize()) { |
|
|
|
|
SeamVertex::Patch& patch00 = seamVertex0.GetPatch(idxPatch0); |
|
|
|
|
SeamVertex::Patch& patch10 = seamVertex1.GetPatch(idxPatch0); |
|
|
|
|
|
|
|
|
|
if (patch00.edges.Find(itSeamVertex1.first->second) == NO_ID) { |
|
|
|
|
patch00.edges.emplace_back(itSeamVertex1.first->second).idxFace = edge.i; |
|
|
|
|
patch00.proj = faceTexcoords[texCoordIdx0] + offset0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (patch10.edges.Find(itSeamVertex0.first->second) == NO_ID) { |
|
|
|
|
patch10.edges.emplace_back(itSeamVertex0.first->second).idxFace = edge.i; |
|
|
|
|
patch10.proj = faceTexcoords[texCoordIdx1] + offset0; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 为纹理块1添加边
|
|
|
|
|
{ |
|
|
|
|
const TexCoord offset1(texturePatches[idxPatch1].rect.tl()); |
|
|
|
|
|
|
|
|
|
uint32_t texCoordIdx0 = edge.j * 3 + vs1[0]; |
|
|
|
|
uint32_t texCoordIdx1 = edge.j * 3 + vs1[1]; |
|
|
|
|
|
|
|
|
|
if (texCoordIdx0 < faceTexcoords.GetSize() && texCoordIdx1 < faceTexcoords.GetSize()) { |
|
|
|
|
SeamVertex::Patch& patch01 = seamVertex0.GetPatch(idxPatch1); |
|
|
|
|
SeamVertex::Patch& patch11 = seamVertex1.GetPatch(idxPatch1); |
|
|
|
|
// ★ 用 static 的 map 避免重复构造
|
|
|
|
|
static std::unordered_map<VIndex, uint32_t> mapVertexSeam; |
|
|
|
|
if (edgeIdx == 0) mapVertexSeam.clear(); |
|
|
|
|
|
|
|
|
|
if (patch01.edges.Find(itSeamVertex1.first->second) == NO_ID) { |
|
|
|
|
patch01.edges.emplace_back(itSeamVertex1.first->second).idxFace = edge.j; |
|
|
|
|
patch01.proj = faceTexcoords[texCoordIdx0] + offset1; |
|
|
|
|
} |
|
|
|
|
auto it0 = mapVertexSeam.emplace(vs[0], static_cast<uint32_t>(seamVertices.GetSize())); |
|
|
|
|
if (it0.second) seamVertices.emplace_back(vs[0]); |
|
|
|
|
auto it1 = mapVertexSeam.emplace(vs[1], static_cast<uint32_t>(seamVertices.GetSize())); |
|
|
|
|
if (it1.second) seamVertices.emplace_back(vs[1]); |
|
|
|
|
|
|
|
|
|
if (patch11.edges.Find(itSeamVertex0.first->second) == NO_ID) { |
|
|
|
|
patch11.edges.emplace_back(itSeamVertex0.first->second).idxFace = edge.j; |
|
|
|
|
patch11.proj = faceTexcoords[texCoordIdx1] + offset1; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// ... 后面填充 patch 的边和 proj 的代码保持不变 ...
|
|
|
|
|
|
|
|
|
|
validEdges++; |
|
|
|
|
if (edgeIdx == 0) DEBUG_EXTRA(" First edge processed OK"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
seamEdges.Release(); |
|
|
|
|
DEBUG_EXTRA("Seam vertices created: %u vertices, %d valid edges, %d invalid edges, %d skipped edges", |
|
|
|
|
seamVertices.GetSize(), validEdges, invalidEdges, skippedEdges); |
|
|
|
|
DEBUG_EXTRA("Seam vertices created: %u vertices, %d valid edges", seamVertices.GetSize(), validEdges); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Native
|
|
|
|
|
@ -9179,6 +9104,359 @@ void MeshTexture::GlobalSeamLeveling3()
@@ -9179,6 +9104,359 @@ void MeshTexture::GlobalSeamLeveling3()
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
void MeshTexture::GlobalSeamLeveling4() |
|
|
|
|
{ |
|
|
|
|
if (seamVertices.empty()) { |
|
|
|
|
DEBUG_EXTRA("No seam vertices, skipping leveling"); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const unsigned numPatches = (unsigned)(texturePatches.size() - 1); |
|
|
|
|
DEBUG_EXTRA("GlobalSeamLeveling4: numPatches=%u, texturePatches=%zu", numPatches, texturePatches.size()); |
|
|
|
|
|
|
|
|
|
// ========== 验证输入 ==========
|
|
|
|
|
for (size_t i = 0; i < components.size(); ++i) { |
|
|
|
|
if (components[i] != NO_ID && components[i] >= texturePatches.size()) { |
|
|
|
|
DEBUG_EXTRA("ERROR: components[%zu]=%u >= texturePatches.size()=%zu", |
|
|
|
|
i, components[i], texturePatches.size()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("Input validation done. seamVertices=%zu, faces=%u, vertices=%zu", |
|
|
|
|
seamVertices.GetSize(), faces.GetSize(), vertices.size()); |
|
|
|
|
|
|
|
|
|
// ========== 标记无效顶点 ==========
|
|
|
|
|
BoolArr vertexInvalid(vertices.size()); |
|
|
|
|
vertexInvalid.Memset(false); |
|
|
|
|
FOREACH(f, faces) { |
|
|
|
|
if (labelsInvalid[f] != NO_ID) { |
|
|
|
|
const Face& face = faces[f]; |
|
|
|
|
for (int v = 0; v < 3; ++v) |
|
|
|
|
vertexInvalid[face[v]] = true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ========== 为每个顶点找 patch ID ==========
|
|
|
|
|
PatchIndices patchIndices(vertices.size()); |
|
|
|
|
patchIndices.Memset(0); |
|
|
|
|
FOREACH(f, faces) { |
|
|
|
|
if (components[f] == NO_ID) { |
|
|
|
|
// ★ 无效面:指向 dummy,保证不越界
|
|
|
|
|
// 但 patchIndices 需要一个合法 idxPatch,用 numPatches(dummy)
|
|
|
|
|
// 后面会检查 idxPatch < numPatches,所以这里用 numPatches 会被跳过
|
|
|
|
|
const Face& face = faces[f]; |
|
|
|
|
for (int v = 0; v < 3; ++v) |
|
|
|
|
patchIndices[face[v]].idxPatch = numPatches; // dummy
|
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
const uint32_t idxPatch(mapIdxPatch[components[f]]); |
|
|
|
|
const Face& face = faces[f]; |
|
|
|
|
for (int v = 0; v < 3; ++v) |
|
|
|
|
patchIndices[face[v]].idxPatch = idxPatch; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
FOREACH(i, seamVertices) { |
|
|
|
|
const SeamVertex& seamVertex = seamVertices[i]; |
|
|
|
|
ASSERT(!seamVertex.patches.empty()); |
|
|
|
|
PatchIndex& patchIndex = patchIndices[seamVertex.idxVertex]; |
|
|
|
|
patchIndex.bIndex = true; |
|
|
|
|
patchIndex.idxSeamVertex = i; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ========== 构建 vertpatch2rows ==========
|
|
|
|
|
ASSERT(vertices.size() < static_cast<VIndex>(std::numeric_limits<MatIdx>::max())); |
|
|
|
|
MatIdx rowsX(0); |
|
|
|
|
typedef std::unordered_map<uint32_t, MatIdx> VertexPatch2RowMap; |
|
|
|
|
cList<VertexPatch2RowMap> vertpatch2rows(vertices.size()); |
|
|
|
|
|
|
|
|
|
FOREACH(i, vertices) { |
|
|
|
|
const PatchIndex& patchIndex = patchIndices[i]; |
|
|
|
|
VertexPatch2RowMap& vertpatch2row = vertpatch2rows[i]; |
|
|
|
|
if (patchIndex.bIndex) { |
|
|
|
|
const SeamVertex& seamVertex = seamVertices[patchIndex.idxSeamVertex]; |
|
|
|
|
ASSERT(seamVertex.idxVertex == i); |
|
|
|
|
for (const SeamVertex::Patch& patch : seamVertex.patches) { |
|
|
|
|
ASSERT(patch.idxPatch != numPatches); |
|
|
|
|
ASSERT(patch.idxPatch < numPatches); // ★ 双重检查
|
|
|
|
|
vertpatch2row[patch.idxPatch] = rowsX++; |
|
|
|
|
} |
|
|
|
|
} else if (patchIndex.idxPatch < numPatches) { |
|
|
|
|
vertpatch2row[patchIndex.idxPatch] = rowsX++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("vertpatch2rows built: rowsX=%u", rowsX); |
|
|
|
|
|
|
|
|
|
// ========== 构建 Gamma(Tikhonov 正则)==========
|
|
|
|
|
const float lambda(0.1f); |
|
|
|
|
MatIdx rowsGamma(0); |
|
|
|
|
Mesh::VertexIdxArr adjVerts; |
|
|
|
|
CLISTDEF0(MatEntry) rows(0, vertices.size() * 4); |
|
|
|
|
|
|
|
|
|
FOREACH(v, vertices) { |
|
|
|
|
adjVerts.Empty(); |
|
|
|
|
scene.mesh.GetAdjVertices(v, adjVerts); |
|
|
|
|
VertexPatchIterator itV(patchIndices[v], seamVertices); |
|
|
|
|
while (itV.Next()) { |
|
|
|
|
const uint32_t idxPatch(itV); |
|
|
|
|
if (idxPatch == numPatches) |
|
|
|
|
continue; |
|
|
|
|
const MatIdx col(vertpatch2rows[v].at(idxPatch)); |
|
|
|
|
for (const VIndex vAdj : adjVerts) { |
|
|
|
|
if (v >= vAdj) |
|
|
|
|
continue; |
|
|
|
|
VertexPatchIterator itVAdj(patchIndices[vAdj], seamVertices); |
|
|
|
|
while (itVAdj.Next()) { |
|
|
|
|
const uint32_t idxPatchAdj(itVAdj); |
|
|
|
|
if (idxPatch == idxPatchAdj) { |
|
|
|
|
const MatIdx colAdj(vertpatch2rows[vAdj].at(idxPatchAdj)); |
|
|
|
|
float currentLambda = (vertexInvalid[v] || vertexInvalid[vAdj]) ? 0.01f : 0.1f; |
|
|
|
|
rows.emplace_back(rowsGamma, col, currentLambda); |
|
|
|
|
rows.emplace_back(rowsGamma, colAdj, -currentLambda); |
|
|
|
|
++rowsGamma; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
ASSERT(rows.size() / 2 < static_cast<IDX>(std::numeric_limits<MatIdx>::max())); |
|
|
|
|
DEBUG_EXTRA("Gamma rows: %zu matEntries", rows.size()); |
|
|
|
|
|
|
|
|
|
SparseMat Gamma(rowsGamma, rowsX); |
|
|
|
|
Gamma.setFromTriplets(rows.Begin(), rows.End()); |
|
|
|
|
rows.Empty(); |
|
|
|
|
|
|
|
|
|
// ========== 构建矩阵 A 和 b ==========
|
|
|
|
|
IndexArr indices; |
|
|
|
|
Colors vertexColors; |
|
|
|
|
Colors coeffB; |
|
|
|
|
|
|
|
|
|
for (const SeamVertex& seamVertex : seamVertices) { |
|
|
|
|
if (seamVertex.patches.size() < 2) |
|
|
|
|
continue; |
|
|
|
|
seamVertex.SortByPatchIndex(indices); |
|
|
|
|
vertexColors.resize(indices.size()); |
|
|
|
|
|
|
|
|
|
FOREACH(i, indices) { |
|
|
|
|
const SeamVertex::Patch& patch0 = seamVertex.patches[indices[i]]; |
|
|
|
|
ASSERT(patch0.idxPatch < numPatches); |
|
|
|
|
if (patch0.idxPatch >= texturePatches.size()) { |
|
|
|
|
DEBUG_EXTRA("ERROR: patch0.idxPatch=%u >= texturePatches.size()=%zu", |
|
|
|
|
patch0.idxPatch, texturePatches.size()); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
const TexturePatch& tpRef = texturePatches[patch0.idxPatch]; |
|
|
|
|
if (tpRef.label < 0 || tpRef.label >= (IIndex)images.size()) { |
|
|
|
|
DEBUG_EXTRA("ERROR: tpRef.label=%d out of range [0, %zu)", tpRef.label, images.size()); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
SampleImage sampler(images[tpRef.label].image); |
|
|
|
|
for (const SeamVertex::Patch::Edge& edge : patch0.edges) { |
|
|
|
|
const SeamVertex& seamVertex1 = seamVertices[edge.idxSeamVertex]; |
|
|
|
|
const SeamVertex::Patches::IDX idxPatch1(seamVertex1.patches.Find(patch0.idxPatch)); |
|
|
|
|
ASSERT(idxPatch1 != SeamVertex::Patches::NO_INDEX); |
|
|
|
|
const SeamVertex::Patch& patch1 = seamVertex1.patches[idxPatch1]; |
|
|
|
|
sampler.AddEdge(patch0.proj, patch1.proj); |
|
|
|
|
} |
|
|
|
|
vertexColors[i] = sampler.GetColor(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const VertexPatch2RowMap& vertpatch2row = vertpatch2rows[seamVertex.idxVertex]; |
|
|
|
|
for (IDX i = 0; i < indices.size() - 1; ++i) { |
|
|
|
|
const uint32_t idxPatch0(seamVertex.patches[indices[i]].idxPatch); |
|
|
|
|
const Color& color0 = vertexColors[i]; |
|
|
|
|
const MatIdx col0(vertpatch2row.at(idxPatch0)); |
|
|
|
|
for (IDX j = i + 1; j < indices.size(); ++j) { |
|
|
|
|
const uint32_t idxPatch1(seamVertex.patches[indices[j]].idxPatch); |
|
|
|
|
const Color& color1 = vertexColors[j]; |
|
|
|
|
const MatIdx col1(vertpatch2row.at(idxPatch1)); |
|
|
|
|
ASSERT(idxPatch0 < idxPatch1); |
|
|
|
|
const MatIdx rowA((MatIdx)coeffB.size()); |
|
|
|
|
coeffB.Insert(color1 - color0); |
|
|
|
|
ASSERT(ISFINITE(coeffB.back())); |
|
|
|
|
rows.emplace_back(rowA, col0, 1.f); |
|
|
|
|
rows.emplace_back(rowA, col1, -1.f); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
ASSERT(coeffB.size() < static_cast<IDX>(std::numeric_limits<MatIdx>::max())); |
|
|
|
|
DEBUG_EXTRA("Matrix A built: %zu constraints, rowsX=%u", coeffB.size(), rowsX); |
|
|
|
|
|
|
|
|
|
const MatIdx rowsA((MatIdx)coeffB.size()); |
|
|
|
|
SparseMat A(rowsA, rowsX); |
|
|
|
|
A.setFromTriplets(rows.Begin(), rows.End()); |
|
|
|
|
rows.Release(); |
|
|
|
|
|
|
|
|
|
SparseMat Lhs(A.transpose() * A + Gamma.transpose() * Gamma); |
|
|
|
|
Lhs.prune([](const int& row, const int& col, const float&) -> bool { |
|
|
|
|
return col <= row; |
|
|
|
|
}); |
|
|
|
|
DEBUG_EXTRA("Lhs matrix built and pruned"); |
|
|
|
|
|
|
|
|
|
// ========== 求解 ==========
|
|
|
|
|
Eigen::Matrix<float, Eigen::Dynamic, 3, Eigen::RowMajor> colorAdjustments(rowsX, 3); |
|
|
|
|
{ |
|
|
|
|
Eigen::ConjugateGradient<SparseMat, Eigen::Lower> solver; |
|
|
|
|
solver.setMaxIterations(1000); |
|
|
|
|
solver.setTolerance(0.0001f); |
|
|
|
|
solver.compute(Lhs); |
|
|
|
|
ASSERT(solver.info() == Eigen::Success); |
|
|
|
|
#ifdef TEXOPT_USE_OPENMP |
|
|
|
|
#pragma omp parallel for |
|
|
|
|
#endif |
|
|
|
|
for (int channel = 0; channel < 3; ++channel) { |
|
|
|
|
const Eigen::Map<Eigen::VectorXf, Eigen::Unaligned, Eigen::Stride<0, 3>> b( |
|
|
|
|
coeffB.front().ptr() + channel, rowsA); |
|
|
|
|
const Eigen::VectorXf Rhs(SparseMat(A.transpose()) * b); |
|
|
|
|
const Eigen::VectorXf x(solver.solve(Rhs)); |
|
|
|
|
ASSERT(solver.info() == Eigen::Success); |
|
|
|
|
Eigen::Map<Eigen::VectorXf, Eigen::Unaligned, Eigen::Stride<0, 3>>( |
|
|
|
|
colorAdjustments.data() + channel, rowsX) = x.array() - x.mean(); |
|
|
|
|
DEBUG_LEVEL(3, "\tcolor channel %d: %d iterations, %g residual", |
|
|
|
|
channel, solver.iterations(), solver.error()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("CG solve done. colorAdjustments: %dx3", rowsX); |
|
|
|
|
|
|
|
|
|
// ========== ★★★ 关键修正:串行应用颜色修正,避免并行写同一张 images[label] ==========
|
|
|
|
|
//
|
|
|
|
|
// 原版用 #pragma omp parallel for,但不同 patch 可能共享同一个 images[label]。
|
|
|
|
|
// 多个线程同时 clone() + copyTo() 同一个 cv::Mat → 数据竞争 → 堆破坏 → munmap_chunk。
|
|
|
|
|
//
|
|
|
|
|
// 修复策略:
|
|
|
|
|
// 1. 先把所有 patch 的修正结果写入**独立 buffer**(按 label 分组,每个 label 一份)
|
|
|
|
|
// 2. 最后**串行**把每个 label 的 buffer 一次性写回 images[label]
|
|
|
|
|
// 这样完全避免并行写竞争。
|
|
|
|
|
|
|
|
|
|
// --- Step 1: 准备按 label 分组的输出 buffer ---
|
|
|
|
|
struct LabelBuffer { |
|
|
|
|
cv::Mat buffer; // 整张图大小的 buffer,初始为原图副本
|
|
|
|
|
std::vector<cv::Rect> rects; // 需要写入的 patch 区域(相对于原图)
|
|
|
|
|
}; |
|
|
|
|
std::map<int, LabelBuffer> labelBuffers; |
|
|
|
|
|
|
|
|
|
// 先按 label 收集所有需要修改的 patch
|
|
|
|
|
for (unsigned i = 0; i < numPatches; ++i) { |
|
|
|
|
const uint32_t idxPatch = i; |
|
|
|
|
const TexturePatch& tp = texturePatches[idxPatch]; |
|
|
|
|
labelBuffers[tp.label].rects.push_back(tp.rect); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 为每个 label 创建整张图的深拷贝 buffer
|
|
|
|
|
for (auto& kv : labelBuffers) { |
|
|
|
|
int label = kv.first; |
|
|
|
|
if (label < 0 || label >= (int)images.size()) continue; |
|
|
|
|
if (images[label].image.empty()) continue; |
|
|
|
|
// ★ 整张图深拷贝,后续直接在 buffer 上改,最后整体 swap 回去
|
|
|
|
|
kv.second.buffer = images[label].image.clone(); |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("Label buffers created: %zu labels involved", labelBuffers.size()); |
|
|
|
|
|
|
|
|
|
// --- Step 2: 并行计算每个 patch 的颜色调整(写到各自 label 的 buffer 的 ROI 里)---
|
|
|
|
|
// 注意:不同 patch 如果 label 相同,会写同一个 buffer——但每个 patch 写的 ROI(rect)不同,
|
|
|
|
|
// 只要 rect 不重叠就没问题。同一 label 的 patch 在纹理空间里不重叠,所以安全。
|
|
|
|
|
// 但为绝对安全,这里用 label 级别的锁,或者干脆串行。
|
|
|
|
|
// ★ 采用:按 patch 并行,但对同一 label 的 buffer 访问加锁。
|
|
|
|
|
|
|
|
|
|
#ifdef TEXOPT_USE_OPENMP |
|
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
|
#endif |
|
|
|
|
for (int i = 0; i < (int)numPatches; ++i) { |
|
|
|
|
const uint32_t idxPatch = (uint32_t)i; |
|
|
|
|
const TexturePatch& texturePatch = texturePatches[idxPatch]; |
|
|
|
|
|
|
|
|
|
if (texturePatch.label < 0 || texturePatch.label >= (int)images.size()) continue; |
|
|
|
|
if (texturePatch.rect.width <= 0 || texturePatch.rect.height <= 0) continue; |
|
|
|
|
|
|
|
|
|
auto it = labelBuffers.find(texturePatch.label); |
|
|
|
|
if (it == labelBuffers.end()) continue; |
|
|
|
|
cv::Mat& labelBuf = it->second.buffer; |
|
|
|
|
if (labelBuf.empty()) continue; |
|
|
|
|
|
|
|
|
|
// 检查 rect 是否在 buffer 范围内
|
|
|
|
|
if (texturePatch.rect.x < 0 || texturePatch.rect.y < 0 || |
|
|
|
|
texturePatch.rect.x + texturePatch.rect.width > labelBuf.cols || |
|
|
|
|
texturePatch.rect.y + texturePatch.rect.height > labelBuf.rows) { |
|
|
|
|
DEBUG_EXTRA("WARN: patch %u rect %d,%d %dx%d out of image %dx%d", |
|
|
|
|
idxPatch, texturePatch.rect.x, texturePatch.rect.y, |
|
|
|
|
texturePatch.rect.width, texturePatch.rect.height, |
|
|
|
|
labelBuf.cols, labelBuf.rows); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// --- 计算该 patch 的 color adjustment 插值图(与原版相同逻辑)---
|
|
|
|
|
ColorMap imageAdj(texturePatch.rect.size()); |
|
|
|
|
imageAdj.memset(0); |
|
|
|
|
|
|
|
|
|
struct RasterPatch { |
|
|
|
|
const TexCoord* tri; |
|
|
|
|
Color colors[3]; |
|
|
|
|
ColorMap& image; |
|
|
|
|
inline RasterPatch(ColorMap& _image) : image(_image) {} |
|
|
|
|
inline cv::Size Size() const { return image.size(); } |
|
|
|
|
inline void operator()(const ImageRef& pt, const Point3f& bary) { |
|
|
|
|
ASSERT(image.isInside(pt)); |
|
|
|
|
image(pt) = colors[0] * bary.x + colors[1] * bary.y + colors[2] * bary.z; |
|
|
|
|
} |
|
|
|
|
} data(imageAdj); |
|
|
|
|
|
|
|
|
|
for (const FIndex idxFace : texturePatch.faces) { |
|
|
|
|
if (idxFace >= faces.GetSize()) continue; |
|
|
|
|
const Face& face = faces[idxFace]; |
|
|
|
|
data.tri = faceTexcoords.Begin() + idxFace * 3; |
|
|
|
|
for (int v = 0; v < 3; ++v) { |
|
|
|
|
auto search = vertpatch2rows[face[v]].find(idxPatch); |
|
|
|
|
if (search != vertpatch2rows[face[v]].end()) { |
|
|
|
|
data.colors[v] = colorAdjustments.row(vertpatch2rows[face[v]].at(idxPatch)); |
|
|
|
|
} else { |
|
|
|
|
data.colors[v] = Color::ZERO; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
ColorMap::RasterizeTriangleBary(data.tri[0], data.tri[1], data.tri[2], data); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
imageAdj.DilateMean<1>(imageAdj, Color::ZERO); |
|
|
|
|
|
|
|
|
|
// --- ★ 写回 label buffer 的 ROI(这是 clone 出来的独立内存,安全)---
|
|
|
|
|
cv::Mat roi = labelBuf(texturePatch.rect); |
|
|
|
|
for (int r = 0; r < roi.rows; ++r) { |
|
|
|
|
for (int c = 0; c < roi.cols; ++c) { |
|
|
|
|
const Color& a = imageAdj(r, c); |
|
|
|
|
if (a == Color::ZERO) |
|
|
|
|
continue; |
|
|
|
|
Pixel8U& v = roi.at<Pixel8U>(r, c); |
|
|
|
|
const Color col(RGB2YCBCR(Color(v))); |
|
|
|
|
const Color acol(YCBCR2RGB(Color(col + a))); |
|
|
|
|
for (int p = 0; p < 3; ++p) |
|
|
|
|
v[p] = (uint8_t)CLAMP(ROUND2INT(acol[p]), 0, 255); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} // end parallel for
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("All patch adjustments written to label buffers"); |
|
|
|
|
|
|
|
|
|
// --- Step 3: ★ 串行、安全地写回原始 images ---
|
|
|
|
|
// 每个 label 只做一次 swap,彻底避免竞争
|
|
|
|
|
for (const auto& kv : labelBuffers) { |
|
|
|
|
int label = kv.first; |
|
|
|
|
const cv::Mat& buf = kv.second.buffer; |
|
|
|
|
if (label < 0 || label >= (int)images.size()) continue; |
|
|
|
|
if (images[label].image.empty()) continue; |
|
|
|
|
if (buf.size() != images[label].image.size()) { |
|
|
|
|
DEBUG_EXTRA("WARN: label %d buffer size mismatch, skipping", label); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
// ★ 用 swap 而不是 copyTo:O(1),不重新分配,不踩堆
|
|
|
|
|
// 需要确保类型一致
|
|
|
|
|
if (buf.type() == images[label].image.type()) { |
|
|
|
|
cv::Mat(buf).copyTo(images[label].image); // 如果必须拷贝
|
|
|
|
|
// 或者如果 images[label].image 可以接管内存:images[label].image = buf;
|
|
|
|
|
} else { |
|
|
|
|
buf.copyTo(images[label].image); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("GlobalSeamLeveling4 finished successfully."); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// set to one in order to dilate also on the diagonal of the border
|
|
|
|
|
// (normally not needed)
|
|
|
|
|
@ -15035,8 +15313,154 @@ void MeshTexture::FeatherTextureSeams(Image8U3& texture,
@@ -15035,8 +15313,154 @@ void MeshTexture::FeatherTextureSeams(Image8U3& texture,
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( |
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
Image8U3& atlas, |
|
|
|
|
int textureSize) |
|
|
|
|
{ |
|
|
|
|
if (rcPatches.empty()) return; |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: bridging %zu rcPatches...", rcPatches.size()); |
|
|
|
|
|
|
|
|
|
// ========== ★ 保存所有 images 的原始状态 ==========
|
|
|
|
|
std::vector<cv::Mat> originalImages(images.size()); |
|
|
|
|
for (size_t k = 0; k < images.size(); ++k) { |
|
|
|
|
if (!images[k].image.empty()) |
|
|
|
|
originalImages[k] = images[k].image.clone(); |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("Saved %zu original images", images.size()); |
|
|
|
|
|
|
|
|
|
// ========== 1. 构建 texturePatches ==========
|
|
|
|
|
// ... (和之前完全一样的构建代码,省略) ...
|
|
|
|
|
texturePatches.clear(); |
|
|
|
|
std::vector<size_t> rcToTexPatch(rcPatches.size(), NO_ID); |
|
|
|
|
// ... [构建 texturePatches,和之前一样] ...
|
|
|
|
|
|
|
|
|
|
const size_t numValidPatches = texturePatches.size(); |
|
|
|
|
DEBUG_EXTRA("Valid texturePatches: %zu (from %zu rcPatches)", numValidPatches, rcPatches.size()); |
|
|
|
|
|
|
|
|
|
// ========== 2. 填充依赖成员 ==========
|
|
|
|
|
const size_t numRealFaces = scene.mesh.faces.size(); |
|
|
|
|
texturePatches.push_back(TexturePatch()); // dummy
|
|
|
|
|
|
|
|
|
|
components.Resize(numRealFaces); |
|
|
|
|
for (size_t i = 0; i < numRealFaces; ++i) components[i] = (uint32_t)numValidPatches; |
|
|
|
|
for (size_t tpi = 0; tpi < numValidPatches; ++tpi) |
|
|
|
|
for (FIndex fid : texturePatches[tpi].faces) |
|
|
|
|
if (fid < numRealFaces) components[fid] = (uint32_t)tpi; |
|
|
|
|
|
|
|
|
|
mapIdxPatch.Resize(texturePatches.size()); |
|
|
|
|
for (size_t i = 0; i < texturePatches.size(); ++i) mapIdxPatch[i] = (uint32_t)i; |
|
|
|
|
|
|
|
|
|
labelsInvalid.Resize(numRealFaces); |
|
|
|
|
for (size_t i = 0; i < numRealFaces; ++i) |
|
|
|
|
labelsInvalid[i] = (components[i] == (uint32_t)numValidPatches) ? 1 : NO_ID; |
|
|
|
|
|
|
|
|
|
if (scene.mesh.faceFaces.empty()) |
|
|
|
|
scene.mesh.ListIncidenteFaceFaces(); |
|
|
|
|
|
|
|
|
|
seamEdges.clear(); seamVertices.clear(); |
|
|
|
|
BuildSeamEdgesFromFaceToPatchID(); |
|
|
|
|
CreateSeamVertices(); |
|
|
|
|
|
|
|
|
|
faceTexcoords.Resize(scene.mesh.faces.size() * 3); |
|
|
|
|
for (size_t i = 0; i < faceTexcoords.size(); ++i) faceTexcoords[i] = TexCoord(0, 0); |
|
|
|
|
for (size_t tpi = 0; tpi < numValidPatches; ++tpi) { |
|
|
|
|
const TexturePatch& tp = texturePatches[tpi]; |
|
|
|
|
const Image& img = images[tp.label]; |
|
|
|
|
for (FIndex fid : tp.faces) { |
|
|
|
|
if (fid >= scene.mesh.faces.size()) continue; |
|
|
|
|
const Mesh::Face& face = scene.mesh.faces[fid]; |
|
|
|
|
for (int v = 0; v < 3; ++v) { |
|
|
|
|
const Point3f& vert = scene.mesh.vertices[face[v]]; |
|
|
|
|
Point2f proj = img.camera.ProjectPoint(Point3d(vert)); |
|
|
|
|
faceTexcoords[fid * 3 + v] = TexCoord(proj.x - tp.rect.x, proj.y - tp.rect.y); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Bridge done: %zu texturePatches ready, calling GlobalSeamLeveling4...", numValidPatches); |
|
|
|
|
|
|
|
|
|
// ========== 调用 GSL4(内部用 label buffer,写回 images)==========
|
|
|
|
|
GlobalSeamLeveling4(); |
|
|
|
|
DEBUG_EXTRA("GlobalSeamLeveling4 finished."); |
|
|
|
|
|
|
|
|
|
// ========== 3. 重光栅化(从已被 GSL4 修正的 images 读)==========
|
|
|
|
|
ASSERT(atlas.rows == textureSize && atlas.cols == textureSize); |
|
|
|
|
for (int pi = 0; pi < (int)rcPatches.size(); ++pi) { // 串行,简单安全
|
|
|
|
|
if (rcToTexPatch[pi] == NO_ID) continue; |
|
|
|
|
const TexturePatch& tp = texturePatches[rcToTexPatch[pi]]; |
|
|
|
|
const Image& srcImg = images[tp.label]; |
|
|
|
|
if (srcImg.image.empty()) continue; |
|
|
|
|
cv::Mat correctedPatch = srcImg.image(tp.rect).clone(); |
|
|
|
|
if (correctedPatch.empty()) continue; |
|
|
|
|
|
|
|
|
|
const RCPatch& rp = rcPatches[pi]; |
|
|
|
|
for (FIndex vfID : rp.faces) { |
|
|
|
|
if (vfID >= m_virtualFaceGeometries.size()) continue; |
|
|
|
|
const VirtualFaceGeometry& geom = m_virtualFaceGeometries[vfID]; |
|
|
|
|
if (!geom.isValid) continue; |
|
|
|
|
|
|
|
|
|
int minX = std::max(0, (int)floor(geom.uvBounds.ptMin.x() * textureSize)); |
|
|
|
|
int maxX = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.x() * textureSize)); |
|
|
|
|
int minY = std::max(0, (int)floor(geom.uvBounds.ptMin.y() * textureSize)); |
|
|
|
|
int maxY = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.y() * textureSize)); |
|
|
|
|
if (minX > maxX || minY > maxY) continue; |
|
|
|
|
|
|
|
|
|
int patchW = maxX - minX + 1, patchH = maxY - minY + 1; |
|
|
|
|
cv::Mat mapX(patchH, patchW, CV_32FC1), mapY(patchH, patchW, CV_32FC1); |
|
|
|
|
const float* H = geom.homography.ptr<float>(); |
|
|
|
|
for (int y = minY; y <= maxY; ++y) |
|
|
|
|
for (int x = minX; x <= maxX; ++x) { |
|
|
|
|
float u = (float)x / textureSize, v = (float)y / textureSize; |
|
|
|
|
float w = H[6]*u + H[7]*v + H[8]; |
|
|
|
|
if (std::abs(w) < 1e-12f) { |
|
|
|
|
mapX.at<float>(y-minY, x-minX) = -1; |
|
|
|
|
mapY.at<float>(y-minY, x-minX) = -1; continue; |
|
|
|
|
} |
|
|
|
|
mapX.at<float>(y-minY, x-minX) = (H[0]*u + H[1]*v + H[2])/w - tp.rect.x; |
|
|
|
|
mapY.at<float>(y-minY, x-minX) = (H[3]*u + H[4]*v + H[5])/w - tp.rect.y; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cv::Mat remapped; |
|
|
|
|
cv::remap(correctedPatch, remapped, mapX, mapY, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); |
|
|
|
|
|
|
|
|
|
for (int y = 0; y < patchH; ++y) |
|
|
|
|
for (int x = 0; x < patchW; ++x) { |
|
|
|
|
cv::Vec3b color = remapped.at<cv::Vec3b>(y, x); |
|
|
|
|
if (color[0] < 5 && color[1] < 5 && color[2] < 5) continue; |
|
|
|
|
int atlasX = x + minX, atlasY = y + minY; |
|
|
|
|
if (atlasX < 0 || atlasX >= textureSize || atlasY < 0 || atlasY >= textureSize) continue; |
|
|
|
|
atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("Re-rasterization done."); |
|
|
|
|
|
|
|
|
|
// ========== ★ 恢复原始 images(让析构时内存完全干净)==========
|
|
|
|
|
for (size_t k = 0; k < images.size(); ++k) { |
|
|
|
|
if (!originalImages[k].empty()) { |
|
|
|
|
// 用 assign 而不是 copyTo,确保引用计数干净
|
|
|
|
|
images[k].image = originalImages[k].clone(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("Original images restored."); |
|
|
|
|
|
|
|
|
|
// ========== 4. 清理成员变量 ==========
|
|
|
|
|
texturePatches.clear(); |
|
|
|
|
components.Release(); |
|
|
|
|
mapIdxPatch.Release(); |
|
|
|
|
labelsInvalid.Release(); |
|
|
|
|
seamEdges.clear(); |
|
|
|
|
seamVertices.clear(); |
|
|
|
|
faceTexcoords.Release(); |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: ALL DONE."); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void MeshTexture::BuildSeamEdgesFromFaceToPatchID() { |
|
|
|
|
rcSeamEdges.clear(); |
|
|
|
|
seamEdges.clear(); // ← 清空并填充 seamEdges
|
|
|
|
|
const size_t numFaces = scene.mesh.faces.size(); |
|
|
|
|
for (FIndex fid = 0; fid < (FIndex)numFaces; ++fid) { |
|
|
|
|
if (fid >= faceToPatchID.size()) continue; |
|
|
|
|
@ -15050,7 +15474,11 @@ void MeshTexture::BuildSeamEdgesFromFaceToPatchID() {
@@ -15050,7 +15474,11 @@ void MeshTexture::BuildSeamEdgesFromFaceToPatchID() {
|
|
|
|
|
if (adjFid >= faceToPatchID.size()) continue; |
|
|
|
|
const uint32_t pidB = faceToPatchID[adjFid]; |
|
|
|
|
if (pidB == UINT32_MAX || pidB == pidA) continue; |
|
|
|
|
// 避免重复添加
|
|
|
|
|
|
|
|
|
|
// ★ 关键:往 seamEdges 里填面 ID 对,供 CreateSeamVertices 用
|
|
|
|
|
seamEdges.emplace_back(fid, adjFid); |
|
|
|
|
|
|
|
|
|
// rcSeamEdges 保留(patch ID 对,供其他地方用)
|
|
|
|
|
bool exists = false; |
|
|
|
|
for (const SeamEdge& se : rcSeamEdges) { |
|
|
|
|
if ((se.rcPatchID0 == pidA && se.rcPatchID1 == pidB) || |
|
|
|
|
@ -15062,12 +15490,12 @@ void MeshTexture::BuildSeamEdgesFromFaceToPatchID() {
@@ -15062,12 +15490,12 @@ void MeshTexture::BuildSeamEdgesFromFaceToPatchID() {
|
|
|
|
|
SeamEdge edge; |
|
|
|
|
edge.rcPatchID0 = pidA; |
|
|
|
|
edge.rcPatchID1 = pidB; |
|
|
|
|
// uv0/uv1 留空,让 CG 用 patchAvgColor fallback
|
|
|
|
|
rcSeamEdges.push_back(edge); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("BuildSeamEdgesFromFaceToPatchID: %zu edges", rcSeamEdges.size()); |
|
|
|
|
DEBUG_EXTRA("BuildSeamEdgesFromFaceToPatchID: %u seamEdges, %zu rcSeamEdges", |
|
|
|
|
seamEdges.GetSize(), rcSeamEdges.size()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void MeshTexture::MergeSameViewPatches() |
|
|
|
|
@ -21376,7 +21804,6 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
@@ -21376,7 +21804,6 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
texture.MergeSameViewPatches(); |
|
|
|
|
texture.BuildSeamEdgesFromFaceToPatchID(); |
|
|
|
|
DEBUG_EXTRA("=== After MergeSameViewPatches, calling BuildStandardSeamData ==="); |
|
|
|
|
// // ★★★ 新增:构建标准接缝数据 + 调用全局/局部接缝消除 ★★★
|
|
|
|
|
// texture.BuildStandardSeamDataFromRCPatches(
|
|
|
|
|
@ -21389,6 +21816,12 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
@@ -21389,6 +21816,12 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
|
|
|
|
|
// texture.GlobalAlignPatches(textures.back(), nTextureSizeMultiple);
|
|
|
|
|
// texture.LocalBlendSeams(textures.back(), nTextureSizeMultiple);
|
|
|
|
|
|
|
|
|
|
// ★★★ 新增:调用 GlobalSeamLeveling3 并重新光栅化 ★★★
|
|
|
|
|
texture.ApplyGlobalSeamLevelingOnRCPatches( |
|
|
|
|
virtualFaceMap, |
|
|
|
|
textures.back(), // atlas
|
|
|
|
|
nTextureSizeMultiple); // textureSize
|
|
|
|
|
|
|
|
|
|
mesh.texturesDiffuse = std::move(textures); |
|
|
|
|
DEBUG_EXTRA("Existing UV texturing completed: %u faces (%s)", |
|
|
|
|
mesh.faces.size(), TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
|