From 7ff01eafdb55b13820a870b63653cc6d19b9951f Mon Sep 17 00:00:00 2001 From: hesuicong Date: Fri, 28 Aug 2026 18:02:52 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=9B=E4=B8=80=E6=AD=A5=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 130 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 8b9ba06..189ac24 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -766,6 +766,8 @@ public: unsigned nTextureSizeMultiple, Pixel8U colEmpty, Mesh::Image8U3Arr& outTextures); + void GlobalPatchColorAlignment(Image8U3& atlas, int textureSize); + // ========== 辅助函数 ========== // 双线性采样(适配 SEACAVE::TImage) @@ -14370,6 +14372,132 @@ void MeshTexture::FillTextureHoles(std::vector& textures, Pixel8U colE DEBUG_EXTRA("Hole filling completed"); } +// ===== 全局 Patch 颜色对齐 ===== +// 在 seam edge 上采样两侧 patch 的颜色,求解每个 patch 的偏移量 +void MeshTexture::GlobalPatchColorAlignment(Image8U3& atlas, int textureSize) +{ + if (rcSeamEdges.empty() || rcPatches.empty()) return; + + DEBUG_EXTRA("Global patch color alignment..."); + TD_TIMER_START(); + + const int NP = (int)rcPatches.size(); + + // 收集约束:每条 seam edge 两侧 patch 在边界上的颜色差 + struct Constraint { int pa, pb; Color diff; }; + std::vector constraints; + constraints.reserve(rcSeamEdges.size()); + + for (const auto& e : rcSeamEdges) { + if (e.rcPatchID0 >= NP || e.rcPatchID1 >= NP) continue; + const RCPatch& patchA = rcPatches[e.rcPatchID0]; + const RCPatch& patchB = rcPatches[e.rcPatchID1]; + + // 在重叠区域采样 + cv::Rect overlap = patchA.rect & patchB.rect; + if (overlap.width < 2 || overlap.height < 2) continue; + + int samples = 0; + Color sumA(0,0,0), sumB(0,0,0); + + for (int y = overlap.y; y < overlap.y + overlap.height && samples < 100; ++y) { + for (int x = overlap.x; x < overlap.x + overlap.width && samples < 100; ++x) { + if (x < 0 || x >= textureSize || y < 0 || y >= textureSize) continue; + const Pixel8U& pxA = atlas(y, x); + if (pxA[0]==0 && pxA[1]==0 && pxA[2]==0) continue; + sumA[0] += pxA[2]; sumA[1] += pxA[1]; sumA[2] += pxA[0]; // RGB + // 从 patchB 的对应位置采样 + const Pixel8U& pxB = atlas(y, x); + sumB[0] += pxB[2]; sumB[1] += pxB[1]; sumB[2] += pxB[0]; + samples++; + } + } + if (samples < 5) continue; + + Color avgA = sumA / (float)samples; + Color avgB = sumB / (float)samples; + constraints.push_back(Constraint{e.rcPatchID0, e.rcPatchID1, avgB - avgA}); + } + + if (constraints.empty()) { + DEBUG_EXTRA("GlobalPatchColorAlignment: no constraints, skip"); + return; + } + + const int rows = (int)constraints.size(); + + // 构建稀疏矩阵 A 和 b + std::vector> triplets; + triplets.reserve(rows * 2 + NP); // 约束 + 正则化 + Eigen::VectorXf bR(rows), bG(rows), bB(rows); + + for (int i = 0; i < rows; ++i) { + triplets.emplace_back(i, constraints[i].pa, -1.0f); + triplets.emplace_back(i, constraints[i].pb, 1.0f); + bR(i) = constraints[i].diff[0]; // R + bG(i) = constraints[i].diff[1]; // G + bB(i) = constraints[i].diff[2]; // B + } + + // 正则化:所有 patch 的偏移量尽量小(Tikhonov) + const float lambda = 0.1f; + for (int i = 0; i < NP; ++i) { + triplets.emplace_back(rows + i, i, lambda); + } + + Eigen::SparseMatrix A(rows + NP, NP); + A.setFromTriplets(triplets.begin(), triplets.end()); + + // 求解 + Eigen::VectorXf xR, xG, xB; + { + // ★ 转稠密,避免 Sparse LDLT 的 lpNorm bug ★ + Eigen::MatrixXf A_dense = A; // Sparse → Dense + Eigen::MatrixXf AtA = A_dense.transpose() * A_dense; + + // 加对角阻尼(1e-6),保证正定可逆 + const int N = A_dense.cols(); + for (int i = 0; i < N; ++i) AtA(i, i) += 1e-6f; + + Eigen::VectorXf AtbR = A_dense.transpose() * bR; + Eigen::VectorXf AtbG = A_dense.transpose() * bG; + Eigen::VectorXf AtbB = A_dense.transpose() * bB; + + Eigen::LDLT ldlt(AtA); + if (ldlt.info() != Eigen::Success) { + DEBUG_EXTRA("GlobalPatchColorAlignment: LDLT failed"); + return; + } + xR = ldlt.solve(AtbR); + xG = ldlt.solve(AtbG); + xB = ldlt.solve(AtbB); + } + + // 应用:对每个 patch 的像素加上偏移(加性,限制幅度) + const float maxAdj = 30.0f; // 8-bit 空间最大偏移 + #pragma omp parallel for + for (int i = 0; i < NP; ++i) { + const RCPatch& patch = rcPatches[i]; + float adjR = std::max(-maxAdj, std::min(maxAdj, xR(i))); + float adjG = std::max(-maxAdj, std::min(maxAdj, xG(i))); + float adjB = std::max(-maxAdj, std::min(maxAdj, xB(i))); + + for (int y = patch.rect.y; y < patch.rect.y + patch.rect.height; ++y) { + for (int x = patch.rect.x; x < patch.rect.x + patch.rect.width; ++x) { + if (x < 0 || x >= textureSize || y < 0 || y >= textureSize) continue; + Pixel8U& px = atlas(y, x); + if (px[0]==0 && px[1]==0 && px[2]==0) continue; + // BGR 存储顺序 +px[2] = (uint8_t)CLAMP(px[2] + adjR, 0.f, 255.f); // R +px[1] = (uint8_t)CLAMP(px[1] + adjG, 0.f, 255.f); // G +px[0] = (uint8_t)CLAMP(px[0] + adjB, 0.f, 255.f); // B + } + } + } + + DEBUG_EXTRA("Global alignment done: %d constraints (%s)", rows, TD_TIMER_GET_FMT().c_str()); +} + // ============================================================ // 3. RC 风格光栅化主函数(含接缝优化) // ============================================================ @@ -14504,6 +14632,8 @@ bool MeshTexture::RasterizeVirtualFaces( } DEBUG_EXTRA("Created %zu RC patches", rcPatches.size()); + GlobalPatchColorAlignment(atlas, textureSize); + // 5/6/7. 接缝 BuildSeamEdgesFromRCPatches(); if (!seamEdges.empty()) {