From d442e213d7f9fc1ff1ead59c2abc4c21efb5b8d6 Mon Sep 17 00:00:00 2001 From: hesuicong Date: Thu, 10 Sep 2026 16:30:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=88=90=E5=8A=9F=E7=94=A8OpenMVS=E7=9A=84?= =?UTF-8?q?=E5=87=A0=E4=BD=95=E4=BF=A1=E6=81=AF=E7=BC=96=E8=AF=91=E9=80=9A?= =?UTF-8?q?=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 845 +++++++++++++++++++++++--------------- 1 file changed, 525 insertions(+), 320 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 9bb2777..99f80c9 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -44,6 +44,12 @@ #include #include +#include +#include +#include +#include +#include + namespace fs = std::filesystem; // namespace py = pybind11; @@ -731,7 +737,8 @@ public: return p; } - + // ★ 只需新增这一行(其余类型取自你现有头文件) + using GSLTriplet = Eigen::Triplet; uint32_t FindNearestPatchForFaces(const std::vector& faceIndices); void FixIsolatedComponents(); void ReinitializeSeamData(); @@ -776,6 +783,7 @@ public: std::vector faceToPatchID; // faceID → 新 patchID 映射 void MergeSameViewPatches(); void BuildSeamEdgesFromFaceToPatchID(); + std::vector correctedPatchImages; // GSL4 apply 输出,重光栅化消费 // 桥接:用 rcPatches + VirtualFaceMap 构建 TexturePatch 并调用 GlobalSeamLeveling3 void ApplyGlobalSeamLevelingOnRCPatches( const VirtualFaceMap& virtualFaceMap, @@ -8766,7 +8774,6 @@ uint32_t MeshTexture::FindNearestPatchForFaces(const std::vector& faceIn return bestPatch; } - void MeshTexture::CreateSeamVertices() { DEBUG_EXTRA(">>> CreateSeamVertices CALLED"); @@ -8783,90 +8790,171 @@ void MeshTexture::CreateSeamVertices() int validEdges = 0; + // ★ 用 map 缓存顶点→seamVertex 索引 + std::unordered_map mapVertexSeam; + mapVertexSeam.reserve(vertices.size() / 4); + for (uint32_t edgeIdx = 0; edgeIdx < seamEdges.GetSize(); ++edgeIdx) { const PairIdx& edge = seamEdges[edgeIdx]; - // ★ 每条边都检查基本范围 - if (edge.i >= faces.GetSize() || edge.j >= faces.GetSize()) { - if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] face OOB: %u %u", edgeIdx, edge.i, edge.j); - continue; - } - - if (edge.i >= components.size() || edge.j >= components.size()) { - if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] comp OOB: %u %u", edgeIdx, edge.i, edge.j); - continue; - } + if (edge.i >= faces.GetSize() || edge.j >= faces.GetSize()) continue; + if (edge.i >= components.size() || edge.j >= components.size()) continue; const uint32_t comp0 = components[edge.i]; const uint32_t comp1 = components[edge.j]; - if (comp0 == NO_ID || comp1 == NO_ID) { - continue; - } - - if (comp0 >= mapIdxPatch.GetSize() || comp1 >= mapIdxPatch.GetSize()) { - if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] comp OOR: comp0=%u comp1=%u mapSz=%u", - edgeIdx, comp0, comp1, mapIdxPatch.GetSize()); - continue; - } + if (comp0 == NO_ID || comp1 == NO_ID) continue; + if (comp0 >= mapIdxPatch.GetSize() || comp1 >= mapIdxPatch.GetSize()) continue; const uint32_t idxPatch0 = mapIdxPatch[comp0]; const uint32_t idxPatch1 = mapIdxPatch[comp1]; - if (idxPatch0 >= texturePatches.size() || idxPatch1 >= texturePatches.size()) { - if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] patch OOR: ip0=%u ip1=%u tpSz=%zu", - edgeIdx, idxPatch0, idxPatch1, texturePatches.size()); - continue; - } - - if (idxPatch0 == idxPatch1) - continue; + if (idxPatch0 >= texturePatches.size() || idxPatch1 >= texturePatches.size()) continue; + if (idxPatch0 == idxPatch1) continue; // ★ 同一 patch,不是 seam - // ★ 打印第一条边看看 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); } + // ★ 获取这条 seam edge 的两个共享顶点 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]; const Face& faceJ = faces[edge.j]; - 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]]) { - continue; - } + if (vs0[0] >= 3 || vs0[1] >= 3 || vs1[0] >= 3 || vs1[1] >= 3) continue; + if (faceI[vs0[0]] != faceJ[vs1[0]] || faceI[vs0[1]] != faceJ[vs1[1]]) continue; vs[0] = faceI[vs0[0]]; vs[1] = faceI[vs0[1]]; - if (vs[0] >= vertices.size() || vs[1] >= vertices.size()) { - if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] vertex OOB: %u %u vsSz=%zu", - edgeIdx, vs[0], vs[1], vertices.size()); - continue; - } + if (vs[0] >= vertices.size() || vs[1] >= vertices.size()) continue; + + // ★★★ 关键:为两个端点各创建一个 seamVertex(如果还没有), + // 然后让每个端点记录"对面的 patch",形成 patches.size() >= 2 ★★★ - // ★ 用 static 的 map 避免重复构造 - static std::unordered_map mapVertexSeam; - if (edgeIdx == 0) mapVertexSeam.clear(); + // 先确保两个 seamVertex 存在 + for (int endpoint = 0; endpoint < 2; ++endpoint) { + const VIndex vIdx = vs[endpoint]; + auto it = mapVertexSeam.emplace(vIdx, static_cast(seamVertices.GetSize())); + if (it.second) { + seamVertices.emplace_back(vIdx); // ★ SeamVertex(vIdx) + } + } - auto it0 = mapVertexSeam.emplace(vs[0], static_cast(seamVertices.GetSize())); - if (it0.second) seamVertices.emplace_back(vs[0]); - auto it1 = mapVertexSeam.emplace(vs[1], static_cast(seamVertices.GetSize())); - if (it1.second) seamVertices.emplace_back(vs[1]); + // 端点 0:属于 patch0,需要记录"通过这条边能到达 patch1" + // 端点 1:属于 patch1,需要记录"通过这条边能到达 patch0" + // → 交叉:每个端点把"对面 patch"加进自己的 patches - // ... 后面填充 patch 的边和 proj 的代码保持不变 ... + for (int endpoint = 0; endpoint < 2; ++endpoint) { + const VIndex vIdx = vs[endpoint]; + const uint32_t myPatch = (endpoint == 0) ? idxPatch0 : idxPatch1; + const uint32_t otherPatch = (endpoint == 0) ? idxPatch1 : idxPatch0; + + SeamVertex& sv = seamVertices[mapVertexSeam[vIdx]]; + + // ★ 用 GetPatch:如果 otherPatch 已存在就返回已有,否则新建(自动去重) + SeamVertex::Patch& patch = sv.GetPatch(otherPatch); + + // ★ 记录这条边:从 sv 出发,经过对面的 patch,到达另一个端点 + // edge.idxSeamVertex = 另一个端点的 seamVertex 索引 + const VIndex otherVIdx = vs[1 - endpoint]; + const uint32_t otherSvIdx = mapVertexSeam[otherVIdx]; + + // 避免重复添加同一条边 + bool edgeExists = false; + for (const auto& e : patch.edges) { + if (e.idxSeamVertex == otherSvIdx) { edgeExists = true; break; } + } + if (!edgeExists) { + patch.edges.emplace_back(otherSvIdx); + } + + // ★ 计算 proj:该顶点在"对面 patch"纹理空间中的坐标 + // proj 用 faceTexcoords 里对应的 UV(在 GSL4 前必须已填充 faceTexcoords) + // 这里用 faceJ(对面面)的顶点在 otherPatch 里的 texcoord + // —— 简化:用面中心投影,或直接留 0(GSL4 会用 SampleImage 重采样) + // 原版是在 AddEdge 后用 SampleImage 根据面片 UV 插值,这里先设为 0, + // 后续如果需要精确采样,再从 faceTexcoords 取 + if (patch.proj == Point2f::ZERO) { + // 取该顶点在"对面面"的某个 texcoord 作为近似投影 + // 找到 vIdx 在 faceJ 中的局部索引 + for (int lv = 0; lv < 3; ++lv) { + if (faceJ[lv] == vIdx) { + // faceTexcoords 是 (fid*3 + v) 的布局 + const TexCoord& tc = faceTexcoords[(endpoint == 0 ? edge.j : edge.i) * 3 + lv]; + patch.proj = Point2f(tc.x, tc.y); + break; + } + } + } + + // ★ 抑制 unused 警告 + (void)myPatch; + } validEdges++; if (edgeIdx == 0) DEBUG_EXTRA(" First edge processed OK"); } DEBUG_EXTRA("Seam vertices created: %u vertices, %d valid edges", seamVertices.GetSize(), validEdges); + + // ★ 诊断:统计每个 seamVertex 的 patch 数量 + size_t svSingle = 0, svMulti = 0, svMaxPatches = 0; + for (const SeamVertex& sv : seamVertices) { + if (sv.patches.size() < 2) svSingle++; + else { svMulti++; svMaxPatches = std::max(svMaxPatches, (size_t)sv.patches.size()); } + } + DEBUG_EXTRA(" patch stats: single(patches<2)=%zu, multi(>=2)=%zu, maxPatches=%zu", + svSingle, svMulti, svMaxPatches); + + // ★★★ 截断 seamVertices 到合理规模(原版 OpenMVS 通常 < 50000)★★★ + // 按 patches 数量排序,保留最重要的(patches 多的 = 色差严重的接缝) + const size_t MAX_SEAM_VERTICES = 40000; // ← 可调,先试 40000 + if (seamVertices.GetSize() > MAX_SEAM_VERTICES) { + DEBUG_EXTRA(" Truncating seamVertices: %u -> %zu (keeping highest patch-count)", + seamVertices.GetSize(), MAX_SEAM_VERTICES); + + // 建 (索引, patch数量) 对,按 patch 数量降序 + std::vector> idxAndCount(seamVertices.GetSize()); + for (uint32_t i = 0; i < seamVertices.GetSize(); ++i) + idxAndCount[i] = {i, seamVertices[i].patches.size()}; + std::sort(idxAndCount.begin(), idxAndCount.end(), + [](const auto& a, const auto& b) { return a.second > b.second; }); + + // 选前 MAX_SEAM_VERTICES 个索引,排序回原始顺序以保持稳定性 + std::vector keep(idxAndCount.size()); + for (size_t i = 0; i < MAX_SEAM_VERTICES; ++i) keep[i] = idxAndCount[i].first; + std::sort(keep.begin(), keep.begin() + MAX_SEAM_VERTICES); + + // 重建 seamVertices(只保留选中的) + decltype(seamVertices) newSV; + newSV.Reserve(MAX_SEAM_VERTICES); + std::unordered_map old2new; // 旧索引 → 新索引 + for (size_t i = 0; i < MAX_SEAM_VERTICES; ++i) { + const uint32_t oldIdx = keep[i]; + old2new[oldIdx] = (uint32_t)newSV.GetSize(); + newSV.push_back(std::move(seamVertices[oldIdx])); + } + + // ★★★ 关键:更新所有 Patch::edges 里的 idxSeamVertex 引用 ★★★ + for (uint32_t i = 0; i < newSV.GetSize(); ++i) { + for (auto& patch : newSV[i].patches) { + for (auto& edge : patch.edges) { + auto it = old2new.find(edge.idxSeamVertex); + if (it != old2new.end()) + edge.idxSeamVertex = it->second; + else + edge.idxSeamVertex = i; // 指向自己(会被忽略) + } + } + } + seamVertices = std::move(newSV); + DEBUG_EXTRA(" Truncated seamVertices: %u final", seamVertices.GetSize()); + } } // Native @@ -9104,6 +9192,7 @@ void MeshTexture::GlobalSeamLeveling3() } } } + void MeshTexture::GlobalSeamLeveling4() { if (seamVertices.empty()) { @@ -9140,12 +9229,9 @@ void MeshTexture::GlobalSeamLeveling4() 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 + patchIndices[face[v]].idxPatch = numPatches; // dummy,后面跳过 continue; } const uint32_t idxPatch(mapIdxPatch[components[f]]); @@ -9157,16 +9243,17 @@ void MeshTexture::GlobalSeamLeveling4() FOREACH(i, seamVertices) { const SeamVertex& seamVertex = seamVertices[i]; ASSERT(!seamVertex.patches.empty()); + ASSERT(seamVertex.idxVertex < vertices.size()); PatchIndex& patchIndex = patchIndices[seamVertex.idxVertex]; patchIndex.bIndex = true; patchIndex.idxSeamVertex = i; } - // ========== 构建 vertpatch2rows ========== + // ========== 构建 vertpatch2rows(块列号 → 连续行)========== ASSERT(vertices.size() < static_cast(std::numeric_limits::max())); MatIdx rowsX(0); typedef std::unordered_map VertexPatch2RowMap; - cList vertpatch2rows(vertices.size()); + std::vector vertpatch2rows(vertices.size()); FOREACH(i, vertices) { const PatchIndex& patchIndex = patchIndices[i]; @@ -9176,284 +9263,301 @@ void MeshTexture::GlobalSeamLeveling4() ASSERT(seamVertex.idxVertex == i); for (const SeamVertex::Patch& patch : seamVertex.patches) { ASSERT(patch.idxPatch != numPatches); - ASSERT(patch.idxPatch < numPatches); // ★ 双重检查 + ASSERT(patch.idxPatch < numPatches); + if (vertpatch2row.find(patch.idxPatch) != vertpatch2row.end()) + continue; // 去重 vertpatch2row[patch.idxPatch] = rowsX++; } } else if (patchIndex.idxPatch < numPatches) { vertpatch2row[patchIndex.idxPatch] = rowsX++; } } - DEBUG_EXTRA("vertpatch2rows built: rowsX=%u", rowsX); + const Eigen::Index scalarCols = (Eigen::Index)rowsX * 3; // 每块 3 个标量列 (RGB) + DEBUG_EXTRA("vertpatch2rows built: rowsX(blocks)=%u, scalarCols=%lld", rowsX, (long long)scalarCols); - // ========== 构建 Gamma(Tikhonov 正则)========== - const float lambda(0.1f); - MatIdx rowsGamma(0); - Mesh::VertexIdxArr adjVerts; - CLISTDEF0(MatEntry) rows(0, vertices.size() * 4); + // ============================================================ + // ========== 构建 Gamma(Tikhonov 正则)========== [修复1: 去重] + // ============================================================ + std::vector gammaTriplets; + gammaTriplets.reserve((size_t)vertices.size() * 8); + + struct GammaRaw { Eigen::Index colA, colB; float w; }; + std::vector rawGamma; + rawGamma.reserve(vertices.size() * 6); + Mesh::VertexIdxArr adjVerts; FOREACH(v, vertices) { adjVerts.Empty(); scene.mesh.GetAdjVertices(v, adjVerts); + + const size_t MAX_ADJ = 8; // ★ 限制邻接度数,控制规模 + if (adjVerts.GetSize() > MAX_ADJ) adjVerts.Resize(MAX_ADJ); + VertexPatchIterator itV(patchIndices[v], seamVertices); while (itV.Next()) { const uint32_t idxPatch(itV); - if (idxPatch == numPatches) - continue; - const MatIdx col(vertpatch2rows[v].at(idxPatch)); + if (idxPatch == numPatches) continue; + auto it_row = vertpatch2rows[v].find(idxPatch); + if (it_row == vertpatch2rows[v].end()) continue; + const Eigen::Index colA = it_row->second; + for (const VIndex vAdj : adjVerts) { - if (v >= vAdj) - continue; + 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; - } + if (idxPatch != idxPatchAdj) continue; // 同一 patch 才正则化 + auto it_rowAdj = vertpatch2rows[vAdj].find(idxPatchAdj); + if (it_rowAdj == vertpatch2rows[vAdj].end()) continue; + const Eigen::Index colB = it_rowAdj->second; + + const float w = (vertexInvalid[v] || vertexInvalid[vAdj]) ? 0.05f : 0.5f; + rawGamma.push_back({colA, colB, w}); } } } } - ASSERT(rows.size() / 2 < static_cast(std::numeric_limits::max())); - DEBUG_EXTRA("Gamma rows: %zu matEntries", rows.size()); - SparseMat Gamma(rowsGamma, rowsX); - Gamma.setFromTriplets(rows.Begin(), rows.End()); - rows.Empty(); + // ★ 排序 + 去重合并(消除重复邻接 → 保证矩阵性质良好) + std::sort(rawGamma.begin(), rawGamma.end(), + [](const GammaRaw& a, const GammaRaw& b) { + return a.colA < b.colA || (a.colA == b.colA && a.colB < b.colB); + }); + + Eigen::Index rowG = 0; + for (size_t i = 0; i < rawGamma.size(); ) { + size_t j = i + 1; + while (j < rawGamma.size() && rawGamma[j].colA == rawGamma[i].colA && rawGamma[j].colB == rawGamma[i].colB) + ++j; + const float w = rawGamma[i].w; + gammaTriplets.emplace_back(rowG, rawGamma[i].colA, w); + gammaTriplets.emplace_back(rowG, rawGamma[i].colB, -w); + ++rowG; + i = j; + } + const Eigen::Index rowsGamma = rowG; + DEBUG_EXTRA("Gamma after dedup: triplets=%zu, rowsGamma=%lld, scalarCols=%lld", + gammaTriplets.size(), (long long)rowsGamma, (long long)scalarCols); + + SparseMat Gamma(rowsGamma, scalarCols); + Gamma.setFromTriplets(gammaTriplets.begin(), gammaTriplets.end()); + Gamma.makeCompressed(); + gammaTriplets.clear(); gammaTriplets.shrink_to_fit(); + rawGamma.clear(); rawGamma.shrink_to_fit(); + DEBUG_EXTRA("Gamma built: %lld x %lld, nnz=%lld", (long long)Gamma.rows(), + (long long)Gamma.cols(), (long long)Gamma.nonZeros()); + + // ============================================================ + // ========== 构建矩阵 A 和 b(颜色一致性约束)========== + // ============================================================ + std::vector ATriplets; + ATriplets.reserve(seamVertices.GetSize() * 6); + std::vector b_coeff; + b_coeff.reserve(seamVertices.GetSize() * 6); - // ========== 构建矩阵 A 和 b ========== IndexArr indices; Colors vertexColors; - Colors coeffB; + Eigen::Index rowA = 0; for (const SeamVertex& seamVertex : seamVertices) { - if (seamVertex.patches.size() < 2) - continue; + if (seamVertex.patches.size() < 2) continue; seamVertex.SortByPatchIndex(indices); vertexColors.resize(indices.size()); - FOREACH(i, indices) { + for (IDX i = 0; i < (IDX)indices.size(); ++i) { 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; - } + if (patch0.idxPatch >= numPatches) 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; - } + if (tpRef.label < 0 || tpRef.label >= (IIndex)images.size()) continue; SampleImage sampler(images[tpRef.label].image); for (const SeamVertex::Patch::Edge& edge : patch0.edges) { + if (edge.idxSeamVertex >= seamVertices.GetSize()) continue; // 越界保护 const SeamVertex& seamVertex1 = seamVertices[edge.idxSeamVertex]; - const SeamVertex::Patches::IDX idxPatch1(seamVertex1.patches.Find(patch0.idxPatch)); - ASSERT(idxPatch1 != SeamVertex::Patches::NO_INDEX); + const auto idxPatch1 = seamVertex1.patches.Find(patch0.idxPatch); + if (idxPatch1 == SeamVertex::Patches::NO_INDEX) continue; 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(std::numeric_limits::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 colorAdjustments(rowsX, 3); - { + const VertexPatch2RowMap& v2r = vertpatch2rows[seamVertex.idxVertex]; + for (IDX i = 0; i < (IDX)indices.size() - 1; ++i) { + for (IDX j = i + 1; j < (IDX)indices.size(); ++j) { + const uint32_t ipa = seamVertex.patches[indices[i]].idxPatch; + const uint32_t ipb = seamVertex.patches[indices[j]].idxPatch; + auto itA = v2r.find(ipa), itB = v2r.find(ipb); + if (itA == v2r.end() || itB == v2r.end()) continue; + const Eigen::Index baseA = itA->second; + const Eigen::Index baseB = itB->second; + + const Color& ca = vertexColors[i]; + const Color& cb = vertexColors[j]; + for (int c = 0; c < 3; ++c) { + const Eigen::Index colA = baseA * 3 + c; // ★ 标量列 = base*3 + c + const Eigen::Index colB = baseB * 3 + c; + ATriplets.emplace_back(rowA, colA, 1.0f); + ATriplets.emplace_back(rowA, colB, -1.0f); + if ((size_t)rowA >= b_coeff.size()) b_coeff.push_back(0); + b_coeff[rowA] = cb[c] - ca[c]; // 差值方程 + ++rowA; + } + } + } + } + const Eigen::Index rowsA = rowA; + ASSERT(rowsA <= (Eigen::Index)b_coeff.size()); + SparseMat A(rowsA, scalarCols); + A.setFromTriplets(ATriplets.begin(), ATriplets.end()); + A.makeCompressed(); + ATriplets.clear(); ATriplets.shrink_to_fit(); + DEBUG_EXTRA("Matrix A built: %lld constraints, scalarCols=%lld, nnz=%lld", + (long long)rowsA, (long long)scalarCols, (long long)A.nonZeros()); + + // ============================================================ + // ========== 求解:Lhs = AᵀA + λ²ΓᵀΓ,对角扰动保证正定 [修复2] + // ============================================================ + std::vector colorAdjustments; + colorAdjustments.assign(rowsX, Color::ZERO); + + if (rowsA > 0 && rowsX > 0) { + // ★ Lhs = AᵀA + λ² ΓᵀΓ + SparseMat ATA = A.transpose() * A; + SparseMat GTG = Gamma.transpose() * Gamma; + const float lambda = 0.5f; // ★ 正则化强度(可调) + GTG *= lambda * lambda; + ASSERT(ATA.rows() == GTG.rows() && ATA.cols() == GTG.cols()); // 尺寸一致性检查 + ATA += GTG; + SparseMat& Lhs = ATA; + + // ★★★ 对角加扰动,严格保证正定 ★★★ + const float eps = 1e-3f; // 足够小不影响结果,保证正定 + for (Eigen::Index i = 0; i < (Eigen::Index)Lhs.rows(); ++i) { + Lhs.coeffRef(i, i) += eps; + } + DEBUG_EXTRA("Lhs built: %lld x %lld, nnz=%lld (diag += %g)", + (long long)Lhs.rows(), (long long)Lhs.cols(), + (long long)Lhs.nonZeros(), eps); + + // ★ 用 ConjugateGradient(只需对称,不要求严格 SPD) Eigen::ConjugateGradient solver; - solver.setMaxIterations(1000); - solver.setTolerance(0.0001f); + solver.setMaxIterations(2000); + solver.setTolerance(0.001f); 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> 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>( - 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 rects; // 需要写入的 patch 区域(相对于原图) - }; - std::map 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); + if (solver.info() != Eigen::Success) { + DEBUG_EXTRA("WARN: Lhs compute failed (info=%d), skipping solve", (int)solver.info()); + } else { + // ★ b 与通道无关 → 一次求解,结果按 InnerStride<3> 分通道 + Eigen::VectorXf b(rowsA); + for (Eigen::Index k = 0; k < rowsA; ++k) b(k) = b_coeff[k]; + const Eigen::VectorXf rhs = A.transpose() * b; + const Eigen::VectorXf x = solver.solve(rhs); + DEBUG_EXTRA("solve: %d iterations, residual=%g", solver.iterations(), solver.error()); + + // ★ 从 x 提取 colorAdjustments(每块一个 Color,显式循环避免对齐问题) + if (solver.info() == Eigen::Success && (Eigen::Index)(x.size()) >= scalarCols) { + colorAdjustments.assign(rowsX, Color::ZERO); + for (Eigen::Index v = 0; v < (Eigen::Index)rowsX; ++v) { + const float r = x(v * 3 + 0); + const float g = x(v * 3 + 1); + const float b_ch = x(v * 3 + 2); + const float mean = (r + g + b_ch) / 3.0f; // 去均值 + colorAdjustments[v] = Color(r - mean, g - mean, b_ch - mean); + } + } else { + DEBUG_EXTRA("WARN: solve not successful, using zero adjustments"); + } + } + } else { + DEBUG_EXTRA("WARN: no constraints (rowsA=%lld), skip solve", (long long)rowsA); } + DEBUG_EXTRA("solve done. colorAdjustments: %zux3", rowsX); - // 为每个 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()); + // ========== 应用颜色修正(输出到 correctedPatchImages,不碰 images)========== + correctedPatchImages.resize(numPatches); - // --- 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]; - #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; - } + if (texturePatch.label < 0 || texturePatch.label >= (int)images.size()) continue; + if (texturePatch.rect.width <= 0 || texturePatch.rect.height <= 0) continue; - // --- 计算该 patch 的 color adjustment 插值图(与原版相同逻辑)--- - ColorMap imageAdj(texturePatch.rect.size()); - imageAdj.memset(0); + // ★ 从原始 images 读(只读,不修改) + const cv::Mat& srcROI = images[texturePatch.label].image(texturePatch.rect); + if (srcROI.empty()) continue; - 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); + // ★ 创建独立输出 buffer + cv::Mat& outPatch = correctedPatchImages[idxPatch]; + outPatch = cv::Mat::zeros(texturePatch.rect.size(), CV_8UC3); - 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); - } + ColorMap imageAdj(texturePatch.rect.size()); + imageAdj.memset(0); + + struct RasterPatch { + const TexCoord* tri; + Color colors[3]; + ColorMap& image; + RasterPatch(ColorMap& _image) : image(_image) {} + inline cv::Size Size() const { return image.size(); } + void operator()(const ImageRef& pt, const Point3f& bary) { + image(pt) = colors[0]*bary.x + colors[1]*bary.y + colors[2]*bary.z; + } + } data(imageAdj); + ASSERT(texturePatch.faces.size() < 1000000); // 合理范围 + for (const FIndex idxFace : texturePatch.faces) { + if (idxFace >= faces.GetSize()) continue; + const Face& face = faces[idxFace]; - imageAdj.DilateMean<1>(imageAdj, Color::ZERO); + // ★ 检查 faceTexcoords 访问 + ASSERT(idxFace * 3 + 2 < faceTexcoords.GetSize()); - // --- ★ 写回 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(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); - } - } + // ★ 检查 face[v] 不越界 + for (int v = 0; v < 3; ++v) { + ASSERT(face[v] < vertices.size()); + ASSERT(face[v] < vertpatch2rows.size()); + } + + data.tri = faceTexcoords.Begin() + idxFace * 3; + for (int v = 0; v < 3; ++v) { + ASSERT(face[v] < vertices.size()); // ★ 加这行 + auto search = vertpatch2rows[face[v]].find(idxPatch); + if (search != vertpatch2rows[face[v]].end()) + { + ASSERT(search->second < (Eigen::Index)colorAdjustments.size()); // ★ 关键 + data.colors[v] = colorAdjustments[search->second]; + } + else + data.colors[v] = Color::ZERO; + } + ColorMap::RasterizeTriangleBary(data.tri[0], data.tri[1], data.tri[2], data); + } + + imageAdj.DilateMean<1>(imageAdj, Color::ZERO); + + // ★ 把修正叠加到 outPatch(从 srcROI 读原始颜色) + for (int r = 0; r < srcROI.rows; ++r) { + for (int c = 0; c < srcROI.cols; ++c) { + const Color& a = imageAdj(r, c); + if (a == Color::ZERO) { + outPatch.at(r, c) = srcROI.at(r, c); + continue; + } + const Pixel8U& v = srcROI.at(r, c); + const Color col(RGB2YCBCR(Color(v))); + const Color acol(YCBCR2RGB(Color(col + a))); + outPatch.at(r, c) = cv::Vec3b( + (uint8_t)CLAMP(ROUND2INT(acol[0]), 0, 255), + (uint8_t)CLAMP(ROUND2INT(acol[1]), 0, 255), + (uint8_t)CLAMP(ROUND2INT(acol[2]), 0, 255)); + } + } + } + DEBUG_EXTRA("All patch adjustments applied (to local buffers)"); DEBUG_EXTRA("GlobalSeamLeveling4 finished successfully."); } @@ -15320,22 +15424,95 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( { if (rcPatches.empty()) return; - DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: bridging %zu rcPatches...", rcPatches.size()); - - // ========== ★ 保存所有 images 的原始状态 ========== - std::vector 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()); + Image8U3 localAtlas(textureSize, textureSize); + localAtlas.memset(0); // 全部填 0 = 黑色 + DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: bridging %zu rcPatches...", rcPatches.size()); + DEBUG_EXTRA("faceTexcoords ptr=%p, scene ptr=%p", &faceTexcoords, &scene.mesh.faceTexcoords); // ========== 1. 构建 texturePatches ========== - // ... (和之前完全一样的构建代码,省略) ... texturePatches.clear(); std::vector rcToTexPatch(rcPatches.size(), NO_ID); - // ... [构建 texturePatches,和之前一样] ... - + + // ★★★ 诊断日志:构建循环之前 ★★★ + if (!rcPatches.empty()) { + DEBUG_EXTRA("rcPatch[0]: viewID=%d, faces.size()=%zu, firstFace=%u, virtualFaceMap.size()=%zu", + rcPatches[0].viewID, rcPatches[0].faces.size(), + rcPatches[0].faces.empty() ? 0 : rcPatches[0].faces[0], + virtualFaceMap.size()); + } + + // ★ 诊断计数器 + size_t skippedEmpty = 0, skippedBadView = 0, skippedEmptyImage = 0; + size_t skippedNoProj = 0, skippedBadROI = 0, skippedNoFaces = 0; + + for (size_t pi = 0; pi < rcPatches.size(); ++pi) { + const RCPatch& rp = rcPatches[pi]; + + if (rp.faces.empty()) { skippedEmpty++; continue; } + if (rp.viewID < 0 || rp.viewID >= (IIndex)images.size()) { skippedBadView++; continue; } + const Image& img = images[rp.viewID]; + if (img.image.empty()) { skippedEmptyImage++; continue; } + + int minX = INT_MAX, minY = INT_MAX, maxX = 0, maxY = 0; + bool hasValidProj = false; + + for (FIndex vfID : rp.faces) { + if (vfID >= virtualFaceMap.size()) continue; + const VirtualFace& vf = virtualFaceMap[vfID]; + for (FIndex fid : vf.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)); + int px = (int)(proj.x + 0.5f), py = (int)(proj.y + 0.5f); + if (px < 0 || py < 0 || px >= img.image.cols || py >= img.image.rows) continue; + minX = std::min(minX, px); minY = std::min(minY, py); + maxX = std::max(maxX, px); maxY = std::max(maxY, py); + hasValidProj = true; + } + } + } + + if (!hasValidProj) { skippedNoProj++; continue; } + + minX = std::max(0, minX - 2); minY = std::max(0, minY - 2); + maxX = std::min(img.image.cols - 1, maxX + 2); maxY = std::min(img.image.rows - 1, maxY + 2); + if (minX >= maxX || minY >= maxY) { skippedBadROI++; continue; } + + TexturePatch tp; + tp.label = rp.viewID; + tp.rect = cv::Rect(minX, minY, maxX - minX + 1, maxY - minY + 1); + + // ★ 收集真实面(展开虚拟面) + for (FIndex vfID : rp.faces) { + if (vfID >= virtualFaceMap.size()) continue; + const VirtualFace& vf = virtualFaceMap[vfID]; + for (FIndex fid : vf.faces) { + if (fid < scene.mesh.faces.size()) + tp.faces.push_back(fid); + } + } + if (!tp.faces.empty()) { + auto& f = tp.faces; std::sort(f.begin(), f.end()); + FIndex last = f[0]; size_t wi = 1; + for (size_t ri = 1; ri < f.size(); ++ri) + if (f[ri] != last) { last = f[ri]; f[wi++] = last; } + f.Resize(wi); + } + if (tp.faces.empty()) { skippedNoFaces++; continue; } + + rcToTexPatch[pi] = texturePatches.size(); + texturePatches.push_back(tp); + } + + DEBUG_EXTRA("texturePatch filtering: total=%zu", rcPatches.size()); + DEBUG_EXTRA(" skippedEmpty=%zu, skippedBadView=%zu, skippedEmptyImage=%zu", + skippedEmpty, skippedBadView, skippedEmptyImage); + DEBUG_EXTRA(" skippedNoProj=%zu, skippedBadROI=%zu, skippedNoFaces=%zu", + skippedNoProj, skippedBadROI, skippedNoFaces); + DEBUG_EXTRA(" → accepted=%zu", texturePatches.size()); + const size_t numValidPatches = texturePatches.size(); DEBUG_EXTRA("Valid texturePatches: %zu (from %zu rcPatches)", numValidPatches, rcPatches.size()); @@ -15363,36 +15540,49 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( 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); - } - } - } + Mesh::TexCoordArr localFaceTexcoords; + localFaceTexcoords.Resize(scene.mesh.faces.size() * 3); + for (size_t i = 0; i < localFaceTexcoords.size(); ++i) localFaceTexcoords[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)); + localFaceTexcoords[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)========== + // ========== 调用 GSL4(内部修改 images)========== GlobalSeamLeveling4(); DEBUG_EXTRA("GlobalSeamLeveling4 finished."); + DEBUG_EXTRA("atlas: rows=%d, cols=%d, step=%d, totalBytes=%d", + atlas.rows, atlas.cols, atlas.step, + (int)(atlas.rows * atlas.step)); + DEBUG_EXTRA("expected: %d x %d x 3 = %d bytes", + textureSize, textureSize, textureSize * textureSize * 3); // ========== 3. 重光栅化(从已被 GSL4 修正的 images 读)========== ASSERT(atlas.rows == textureSize && atlas.cols == textureSize); - for (int pi = 0; pi < (int)rcPatches.size(); ++pi) { // 串行,简单安全 + 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 (rcToTexPatch[pi] >= correctedPatchImages.size() || correctedPatchImages[rcToTexPatch[pi]].empty()) + continue; + + // cv::Mat correctedPatch = srcImg.image(tp.rect).clone(); + cv::Mat correctedPatch = correctedPatchImages[rcToTexPatch[pi]].clone(); // 局部拷贝 + if (correctedPatch.empty()) continue; const RCPatch& rp = rcPatches[pi]; @@ -15431,20 +15621,25 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( 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]}; + // atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; + if (atlasY >= 0 && atlasY < atlas.rows && atlasX >= 0 && atlasX < atlas.cols) { + // 用 cv::Mat 的 at 方法,它内部有边界检查(Debug 模式下) + cv::Vec3b& pixel = localAtlas.at(atlasY, atlasX); + pixel[0] = color[0]; + pixel[1] = color[1]; + pixel[2] = color[2]; + } } + + atlas = localAtlas; } } 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."); + correctedPatchImages.clear(); + + // ★ 不再恢复原始 images —— 避免 cv::Mat 引用计数混乱导致 munmap_chunk + // atlas 已经生成,images 后续随 MeshTexture 正常析构即可 // ========== 4. 清理成员变量 ========== texturePatches.clear(); @@ -15453,7 +15648,10 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( labelsInvalid.Release(); seamEdges.clear(); seamVertices.clear(); - faceTexcoords.Release(); + // faceTexcoords.Release(); + + Mesh::TexCoordArr dummyTexcoords; + faceTexcoords.Swap(dummyTexcoords); // 如果 DynArray 有 Swap 方法 DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: ALL DONE."); } @@ -15806,7 +16004,14 @@ bool MeshTexture::RasterizeVirtualFaces( color[1] = cv::saturate_cast(std::min(255.0f, color[1] * 1.05f)); color[2] = cv::saturate_cast(std::min(255.0f, color[2] * 1.05f)); - atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; + // atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; + if (atlasY >= 0 && atlasY < atlas.rows && atlasX >= 0 && atlasX < atlas.cols) { + // 用 cv::Mat 的 at 方法,它内部有边界检查(Debug 模式下) + cv::Vec3b& pixel = atlas.at(atlasY, atlasX); + pixel[0] = color[2]; + pixel[1] = color[1]; + pixel[2] = color[0]; + } m_texelScores[idx].score = currentScore; m_texelScores[idx].viewID = viewID; m_texelPatchID[idx] = i; // ★ 记录这个像素属于 patch i