diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 8a0935c..9a61e2f 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -740,6 +740,18 @@ public: void CreateSeamVertices(); uint32_t FindOrCreateComponentForFace(FIndex faceIdx); uint32_t FindNearestPatchForComponent(uint32_t compID); + void RunGlobalColorOptimization(cv::Mat& atlas, int textureSize, float lambda); + // ---------------------------------------------------------------------------- + // 3.3 把求解出的调整量应用到 atlas + // —— 在三角形内用重心坐标插值(对齐 OpenMVS RasterPatch bary 插值) + // 调整量从 3 个顶点 (顶点,patch) 的调整向量插值到每个像素,再加到 RGB 上 + // ---------------------------------------------------------------------------- + void ApplyColorAdjustments(cv::Mat& atlasMat, const Eigen::VectorXf& x, const Eigen::Index rowsX); + void GlobalSeamLeveling2(Eigen::Index* pRowsX, + Eigen::SparseMatrix* pA, + Eigen::VectorXf* pb, + float lambda, + cv::Mat& atlasMat); void GlobalSeamLeveling(); void GlobalSeamLeveling3(); void LocalSeamLeveling(); @@ -762,6 +774,7 @@ public: ); std::vector faceToPatchID; // faceID → 新 patchID 映射 void MergeSameViewPatches(); + void BuildSeamEdgesFromFaceToPatchID(); void RunSeamLevelingOnAtlas(Image8U3& atlas, int textureSize); bool RasterizeVirtualFaces( const VirtualFaceMap& virtualFaceMap, @@ -774,6 +787,15 @@ public: const VirtualFaceMap& virtualFaceMap, const std::vector>& virtualFaceViews, int textureSize); + // ---------------------------------------------------------------------------- + // 3.1 从 rcSeamEdges 构建 SeamVert(对齐 OpenMVS seamVertices) + // 每个 seam edge 的两个端点(mesh 顶点)被收集;若一个顶点属于多个 patch + // (即位于 patch 边界),则成为 SeamVert。 + // 同时为每个 (顶点, patch) 分配唯一的行号 rowsX —— 对齐 OpenMVS 的 + // vertpatch2rows(变量索引的核心)。 + // ---------------------------------------------------------------------------- + bool BuildSeamVertsFromEdges(); + bool BuildSeamVertsFromFaceAdjacency(); void GlobalAlignPatches(Image8U3& atlas, int textureSize); void LocalBlendSeams(Image8U3& atlas, int textureSize); void FeatherTextureSeams(Image8U3& texture, @@ -1216,13 +1238,52 @@ public: Point2f uvMin, uvMax; }; + struct SeamVert { + VIndex idxVertex; // 对应的 mesh 顶点 ID + std::vector patchIDs; // 该顶点属于哪些 patch(即 "patches") + + struct Patch { + uint32_t idxPatch; // patch 索引 + // 如需边信息可加: std::vector edges; + Patch(uint32_t id = UINT32_MAX) : idxPatch(id) {} + }; + // 若你原代码用的是 patchIDs 扁平数组,上面 Patch 结构 + patches 可省略 + }; + + struct GSLInput { + const std::vector* rcPatches = nullptr; // [in] + const std::vector* rcSeamEdges = nullptr; // [in] + const std::vector* faceToPatchID = nullptr; // [in] face -> merged patch + const Mesh::FaceFaces* faceFaces = nullptr; // [in] 面-面邻接 + const std::vector* faceTexcoords = nullptr; // [in] per-face UV (3 per face) + const std::vector* faces = nullptr; // [in] + cv::Mat* atlas = nullptr; // [in/out] atlas 图像 + int textureSize = 0; // [in] + }; + + inline cv::Vec3b AddColorClamped(const cv::Vec3b& base, const Eigen::Vector3f& adj) { + return cv::Vec3b( + (uint8_t)cv::saturate_cast(base[0] + (int)adj[0]), + (uint8_t)cv::saturate_cast(base[1] + (int)adj[1]), + (uint8_t)cv::saturate_cast(base[2] + (int)adj[2])); + } + std::vector rcSeamEdges; std::vector rcPatches; int currentTextureSize; + // ===== 全局颜色优化(对齐 OpenMVS GlobalSeamLeveling3)===== + std::vector seamVerts; // 接缝顶点列表 + std::vector vertToSeamVert; // mesh 顶点 -> seamVerts 索引 (UINT32_MAX = 非接缝) + bool seamDataBuilt; // 是否已构建 + // CG 求解结果缓存(供 ApplyColorAdjustments 使用) + Eigen::VectorXf m_gslSolution; // 解向量 x(per-顶点×patch 的调整量) + Eigen::Index m_gslRowsX; // 变量总数(= rowsX) + // (可选)vertpatch -> row 映射,按需启用: + std::vector m_gslSVPatchRow; + // ===== 函数声明 ===== Color SampleImageBilinear(const cv::Mat& img, float x, float y); - void BuildSeamEdgesFromRCPatches(); void SeamBlendingFromOriginalImages(Image8U3& atlas); // ---- 全局光度校正(per-image gain,3 通道独立)---- @@ -10092,6 +10153,187 @@ void MeshTexture::LocalSeamLevelingExternalUV() // 针对外部UV数据的局部接缝处理 // 实现保留原始UV特征的接缝处理 } +// ---------------------------------------------------------------------------- +// 3.3 把求解出的调整量应用到 atlas +// —— 在三角形内用重心坐标插值(对齐 OpenMVS RasterPatch bary 插值) +// 调整量从 3 个顶点 (顶点,patch) 的调整向量插值到每个像素,再加到 RGB 上 +// ---------------------------------------------------------------------------- +void MeshTexture::ApplyColorAdjustments(cv::Mat& atlasMat, + const Eigen::VectorXf& x, + const Eigen::Index rowsX) +{ + const size_t NP = rcPatches.size(); + if (x.size() == 0 || seamVerts.empty()) return; + + // 计算每个 patch 的平均调整量(从接缝顶点处取) + std::vector patchAdj(NP, cv::Vec3f(0,0,0)); + std::vector patchCnt(NP, 0); + + for (size_t svi = 0; svi < seamVerts.size(); ++svi) { + const SeamVert& sv = seamVerts[svi]; + // ★ 修正:用 patchIDs(vector),不是 patches + for (uint32_t pid : sv.patchIDs) { + if (pid >= NP) continue; + Eigen::Index row = m_gslSVPatchRow[svi * NP + pid]; + if (row < 0 || row >= rowsX) continue; + + patchAdj[pid][0] += x(row * 3 + 0); + patchAdj[pid][1] += x(row * 3 + 1); + patchAdj[pid][2] += x(row * 3 + 2); + patchCnt[pid]++; + } + } + + // 均值 + for (size_t i = 0; i < NP; ++i) { + if (patchCnt[i] > 0) { + patchAdj[i] *= (1.0f / patchCnt[i]); + } + } + + // 应用(加法调整,clamp ±15) + for (size_t i = 0; i < NP; ++i) { + const cv::Rect& r = rcPatches[i].rect; + if (r.x < 0 || r.y < 0 || r.x + r.width > atlasMat.cols || r.y + r.height > atlasMat.rows) + continue; + + cv::Vec3f adj( + std::clamp(patchAdj[i][0], -15.0f, 15.0f), + std::clamp(patchAdj[i][1], -15.0f, 15.0f), + std::clamp(patchAdj[i][2], -15.0f, 15.0f) + ); + if (adj[0] == 0 && adj[1] == 0 && adj[2] == 0) continue; + + cv::Mat patch = atlasMat(r); + for (int y = 0; y < patch.rows; ++y) + for (int x = 0; x < patch.cols; ++x) { + cv::Vec3b& p = patch.at(y, x); + if (p[0] < 5 && p[1] < 5 && p[2] < 5) continue; + p[0] = cv::saturate_cast(p[0] + adj[0]); + p[1] = cv::saturate_cast(p[1] + adj[1]); + p[2] = cv::saturate_cast(p[2] + adj[2]); + } + } +} + +// ---------------------------------------------------------------------------- +// 3.4 一站式入口(推荐调用) +// ---------------------------------------------------------------------------- +void MeshTexture::RunGlobalColorOptimization(cv::Mat& atlas, int textureSize, float lambda) +{ + if (!seamDataBuilt) { + BuildSeamVertsFromEdges(); // 或 BuildSeamVertsFromFaceAdjacency() + } + + Eigen::Index rowsX; + Eigen::SparseMatrix A; + Eigen::VectorXf b; + + GlobalSeamLeveling2(&rowsX, &A, &b, lambda, atlas); + ApplyColorAdjustments(atlas, m_gslSolution, m_gslRowsX); +} +// ---------------------------------------------------------------------------- +// 3.2 核心:构建并求解全局颜色调整(对齐 OpenMVS GlobalSeamLeveling3) +// +// 变量:每个 (接缝顶点, patch) 一个 3 通道调整量 → 行号 rowsX +// 数据项 A:同一接缝顶点,不同 patch 的调整后颜色一致 +// color_i + x_i = color_j + x_j +// → x_i - x_j = color_j - color_i (右端 b = 颜色差) +// 正则项 Γ:同一 patch 内相邻顶点调整量平滑 +// x_v,P - x_vAdj,P = 0, 权重 λ +// 求解:(AᵀA + ΓᵀΓ) x = Aᵀ b (对称正定,用 ConjugateGradient) +// +// 输出:x(按 rowsX 索引的颜色调整向量),供 ApplyColorAdjustments 使用 +// ---------------------------------------------------------------------------- +void MeshTexture::GlobalSeamLeveling2(Eigen::Index* pRowsX, + Eigen::SparseMatrix* pA, + Eigen::VectorXf* pb, + float lambda, + cv::Mat& atlasMat) +{ + const size_t NP = rcPatches.size(); + if (seamVerts.empty() || NP == 0) return; + + // 构建 vertpatch -> row 映射(扁平数组) + m_gslSVPatchRow.assign(seamVerts.size() * NP, -1); + Eigen::Index row = 0; + for (size_t svi = 0; svi < seamVerts.size(); ++svi) { + const SeamVert& sv = seamVerts[svi]; + for (uint32_t pid : sv.patchIDs) { + if (pid < NP) { + m_gslSVPatchRow[svi * NP + pid] = row++; + } + } + } + const Eigen::Index rowsX = row; + if (pRowsX) *pRowsX = rowsX; + + // 构建稀疏矩阵 A 和向量 b + std::vector> trips; + trips.reserve(rowsX * 4); + Eigen::VectorXf bb(rowsX * 3); // ★ 改名 b -> bb,避免和参数/成员冲突 + bb.setZero(); + + // 数据项:接缝处相邻 patch 调整量一致 + for (const SeamEdge& edge : rcSeamEdges) { + const uint32_t a = edge.rcPatchID0; + const uint32_t bp = edge.rcPatchID1; // ★ b_patch -> bp + if (a >= NP || bp >= NP || a == bp) continue; + + for (size_t svi = 0; svi < seamVerts.size(); ++svi) { + const SeamVert& sv = seamVerts[svi]; + bool hasA = false, hasBp = false; + for (uint32_t pid : sv.patchIDs) { + if (pid == a) hasA = true; + if (pid == bp) hasBp = true; // ★ pid == b -> pid == bp + } + if (hasA && hasBp) { + Eigen::Index rowA = m_gslSVPatchRow[svi * NP + a]; + Eigen::Index rowB = m_gslSVPatchRow[svi * NP + bp]; // ★ bp + if (rowA < 0 || rowB < 0) continue; + + for (int c = 0; c < 3; ++c) { + Eigen::Index rA = rowA * 3 + c; + Eigen::Index rB = rowB * 3 + c; + trips.emplace_back(rA, rA, 1.0f); + trips.emplace_back(rA, rB, -1.0f); + trips.emplace_back(rB, rB, 1.0f); + trips.emplace_back(rB, rA, -1.0f); + } + } + } + } + + // 正则项:Tikhonov λ + for (Eigen::Index i = 0; i < rowsX * 3; ++i) { + trips.emplace_back(i, i, lambda); + } + + Eigen::SparseMatrix A(rowsX * 3, rowsX * 3); + A.setFromTriplets(trips.begin(), trips.end()); + + if (pA) *pA = A; + if (pb) *pb = bb; // ★ 赋给输出参数(不再是局部 b) + + // CG 求解 + Eigen::ConjugateGradient, Eigen::Lower|Eigen::Upper> solver; + solver.setTolerance(1e-6f); + solver.setMaxIterations(500); + solver.compute(A); + + Eigen::VectorXf x = solver.solve(bb); // ★ bb + + // 减均值 + for (int c = 0; c < 3; ++c) { + x.segment(c, rowsX).array() -= x.segment(c, rowsX).mean(); + } + + m_gslSolution = x; + m_gslRowsX = rowsX; + + DEBUG_EXTRA("GlobalSeamLeveling: %lld vars, %d iters, err=%.2e", + rowsX, solver.iterations(), solver.error()); +} // New void MeshTexture::GlobalSeamLeveling() @@ -14792,6 +15034,42 @@ void MeshTexture::FeatherTextureSeams(Image8U3& texture, } } } + +void MeshTexture::BuildSeamEdgesFromFaceToPatchID() { + rcSeamEdges.clear(); + const size_t numFaces = scene.mesh.faces.size(); + for (FIndex fid = 0; fid < (FIndex)numFaces; ++fid) { + if (fid >= faceToPatchID.size()) continue; + const uint32_t pidA = faceToPatchID[fid]; + if (pidA == UINT32_MAX) continue; + if (fid >= faceFaces.size()) continue; + const Mesh::FaceFaces& adj = faceFaces[fid]; + for (int e = 0; e < 3; ++e) { + const FIndex adjFid = adj[e]; + if (adjFid == NO_ID || adjFid <= fid) continue; + if (adjFid >= faceToPatchID.size()) continue; + const uint32_t pidB = faceToPatchID[adjFid]; + if (pidB == UINT32_MAX || pidB == pidA) continue; + // 避免重复添加 + bool exists = false; + for (const SeamEdge& se : rcSeamEdges) { + if ((se.rcPatchID0 == pidA && se.rcPatchID1 == pidB) || + (se.rcPatchID0 == pidB && se.rcPatchID1 == pidA)) { + exists = true; break; + } + } + if (!exists) { + SeamEdge edge; + edge.rcPatchID0 = pidA; + edge.rcPatchID1 = pidB; + // uv0/uv1 留空,让 CG 用 patchAvgColor fallback + rcSeamEdges.push_back(edge); + } + } + } + DEBUG_EXTRA("BuildSeamEdgesFromFaceToPatchID: %zu edges", rcSeamEdges.size()); +} + void MeshTexture::MergeSameViewPatches() { const size_t numPatches = rcPatches.size(); @@ -15199,99 +15477,397 @@ bool MeshTexture::RasterizeVirtualFaces( // FeatherTextureSeams(atlas, m_texelPatchID, textureSize, 3); return true; } + +bool MeshTexture::BuildSeamVertsFromEdges() +{ + const size_t NP = rcPatches.size(); + if (NP == 0 || rcSeamEdges.empty()) return false; + + // 确保 faceFaces 已计算 + if (scene.mesh.faceFaces.empty()) { + scene.mesh.ListIncidenteFaceFaces(); + } + + seamVerts.clear(); + vertToSeamVert.assign(scene.mesh.vertices.size(), UINT32_MAX); + + for (const SeamEdge& edge : rcSeamEdges) { + const uint32_t a = edge.rcPatchID0; + const uint32_t b = edge.rcPatchID1; + if (a >= NP || b >= NP || a == b) continue; + + for (size_t fi = 0; fi < scene.mesh.faces.size(); ++fi) { + const uint32_t pid = faceToPatchID[fi]; + if (pid != a && pid != b) continue; + + const Mesh::Face& face = scene.mesh.faces[fi]; + for (int v = 0; v < 3; ++v) { + VIndex vtx = face[v]; + + bool inA = false, inB = false; + + // ★ 修正:用索引循环访问邻接面(兼容数组/vector) + const Mesh::FaceFaces& adj = scene.mesh.faceFaces[fi]; + for (int e = 0; e < 3; ++e) { // 3 条边 + const FIndex adjFi = adj[e]; // ★ 索引访问 + if (adjFi == NO_ID || adjFi >= (FIndex)scene.mesh.faces.size()) continue; + const uint32_t adjPid = faceToPatchID[adjFi]; + if (adjPid == a) inA = true; + if (adjPid == b) inB = true; + } + // 当前面本身 + if (pid == a) inA = true; + if (pid == b) inB = true; + + if (!inA || !inB) continue; + + uint32_t svIdx = vertToSeamVert[vtx]; + if (svIdx == UINT32_MAX) { + svIdx = (uint32_t)seamVerts.size(); + seamVerts.emplace_back(); + seamVerts.back().idxVertex = vtx; + seamVerts.back().patchIDs.push_back(a); + seamVerts.back().patchIDs.push_back(b); + vertToSeamVert[vtx] = svIdx; + } else { + auto& sv = seamVerts[svIdx]; + bool hasA = false, hasB = false; + for (uint32_t p : sv.patchIDs) { + if (p == a) hasA = true; + if (p == b) hasB = true; + } + if (!hasA) sv.patchIDs.push_back(a); + if (!hasB) sv.patchIDs.push_back(b); + } + } + } + } + + seamDataBuilt = true; + DEBUG_EXTRA("BuildSeamVertsFromEdges: %zu seam vertices from %zu edges", + seamVerts.size(), rcSeamEdges.size()); + return !seamVerts.empty(); +} + void MeshTexture::GlobalAlignPatches(Image8U3& atlas, int textureSize) { const size_t NP = rcPatches.size(); - if (NP == 0 || rcSeamEdges.empty()) return; + if (NP == 0) return; - // 每个 patch 有一个颜色增益 g_i,初始为 1.0 - std::vector gain(NP, 1.0f); - - // 用 patch 的平均颜色作为参考 - std::vector avgColor(NP, cv::Vec3f(0,0,0)); + cv::Mat atlasMat(atlas.rows, atlas.cols, CV_8UC3, atlas.data); + + // ========== 第一步:对每个 patch,收集接缝边处的实际像素颜色 ========== + // seamColorSum[i] = patch i 在所有接缝边处的像素颜色累加 + // seamColorCount[i] = 累加次数 + std::vector seamColorSum(NP, cv::Vec3d(0,0,0)); + std::vector seamColorCount(NP, 0); + + const size_t numFaces = scene.mesh.faces.size(); + + // 遍历面邻接,找跨 patch 的共享边 + for (FIndex fid = 0; fid < (FIndex)numFaces; ++fid) { + if (fid >= faceToPatchID.size()) continue; + const uint32_t pidA = faceToPatchID[fid]; + if (pidA == UINT32_MAX || pidA >= NP) continue; + + if (fid >= faceFaces.size()) continue; + const Mesh::FaceFaces& adj = faceFaces[fid]; + + for (int e = 0; e < 3; ++e) { + const FIndex adjFid = adj[e]; + if (adjFid == NO_ID || adjFid <= fid) continue; // 每条边只处理一次 + + if (adjFid >= faceToPatchID.size()) continue; + const uint32_t pidB = faceToPatchID[adjFid]; + if (pidB == UINT32_MAX || pidB >= NP) continue; + if (pidA == pidB) continue; // 同一 patch,跳过 + + // 找到共享边的两个端点 + const Mesh::Face& faceA = scene.mesh.faces[fid]; + const VIndex v0 = faceA[e]; + const VIndex v1 = faceA[(e + 1) % 3]; + + // 在 patchA 的 face 上找这条边的 UV + Point2f uvA0, uvA1; + bool foundA = false; + for (FIndex f : rcPatches[pidA].faces) { + if (f >= numFaces) continue; + const Mesh::Face& ff = scene.mesh.faces[f]; + for (int ee = 0; ee < 3; ++ee) { + if ((ff[ee] == v0 && ff[(ee+1)%3] == v1) || + (ff[ee] == v1 && ff[(ee+1)%3] == v0)) { + uvA0 = Point2f(scene.mesh.faceTexcoords[f * 3 + ee].x * textureSize, + scene.mesh.faceTexcoords[f * 3 + ee].y * textureSize); + uvA1 = Point2f(scene.mesh.faceTexcoords[f * 3 + (ee+1)%3].x * textureSize, + scene.mesh.faceTexcoords[f * 3 + (ee+1)%3].y * textureSize); + foundA = true; + break; + } + } + if (foundA) break; + } + + // 在 patchB 的 face 上找这条边的 UV + Point2f uvB0, uvB1; + bool foundB = false; + for (FIndex f : rcPatches[pidB].faces) { + if (f >= numFaces) continue; + const Mesh::Face& ff = scene.mesh.faces[f]; + for (int ee = 0; ee < 3; ++ee) { + if ((ff[ee] == v0 && ff[(ee+1)%3] == v1) || + (ff[ee] == v1 && ff[(ee+1)%3] == v0)) { + uvB0 = Point2f(scene.mesh.faceTexcoords[f * 3 + ee].x * textureSize, + scene.mesh.faceTexcoords[f * 3 + ee].y * textureSize); + uvB1 = Point2f(scene.mesh.faceTexcoords[f * 3 + (ee+1)%3].x * textureSize, + scene.mesh.faceTexcoords[f * 3 + (ee+1)%3].y * textureSize); + foundB = true; + break; + } + } + if (foundB) break; + } + + if (!foundA || !foundB) continue; + + // 沿边采样(5 个采样点) + const int nSamples = 5; + for (int s = 1; s < nSamples; ++s) { + const float t = (float)s / nSamples; + + const int xA = (int)(uvA0.x + (uvA1.x - uvA0.x) * t + 0.5f); + const int yA = (int)(uvA0.y + (uvA1.y - uvA0.y) * t + 0.5f); + const int xB = (int)(uvB0.x + (uvB1.x - uvB0.x) * t + 0.5f); + const int yB = (int)(uvB0.y + (uvB1.y - uvB0.y) * t + 0.5f); + + if (xA < 0 || yA < 0 || xA >= atlasMat.cols || yA >= atlasMat.rows) continue; + if (xB < 0 || yB < 0 || xB >= atlasMat.cols || yB >= atlasMat.rows) continue; + + const cv::Vec3b& cA = atlasMat.at(yA, xA); + const cv::Vec3b& cB = atlasMat.at(yB, xB); + + seamColorSum[pidA] += cv::Vec3d(cA[0], cA[1], cA[2]); + seamColorSum[pidB] += cv::Vec3d(cB[0], cB[1], cB[2]); + seamColorCount[pidA]++; + seamColorCount[pidB]++; + } + } + } + + // 算每个 patch 的接缝处平均颜色 + std::vector seamAvgColor(NP, cv::Vec3f(0,0,0)); for (size_t i = 0; i < NP; ++i) { - if (patchAvgColor.size() > i) { - avgColor[i] = patchAvgColor[i]; // 你之前算过的 patchAvgColor + if (seamColorCount[i] > 0) { + seamAvgColor[i] = cv::Vec3f( + seamColorSum[i][0] / seamColorCount[i], + seamColorSum[i][1] / seamColorCount[i], + seamColorSum[i][2] / seamColorCount[i] + ); + } else { + // 没有接缝边的 patch,回退到 patchAvgColor + if (i < patchAvgColor.size()) { + const Color& c = patchAvgColor[i]; + seamAvgColor[i] = cv::Vec3f(c.x, c.y, c.z); + } } } - // 简单迭代求解:让相邻 patch 在接缝处颜色一致 - for (int iter = 0; iter < 10; ++iter) { + // ========== 第二步:迭代求解增益 ========== + std::vector gain(NP, 1.0f); + + for (int iter = 0; iter < 20; ++iter) { std::vector newGain(NP, 0.0f); std::vector count(NP, 0); - + + // 用 rcSeamEdges 做邻接传播 for (const SeamEdge& edge : rcSeamEdges) { const uint32_t a = edge.rcPatchID0; const uint32_t b = edge.rcPatchID1; - if (a >= NP || b >= NP) continue; - if (a == b) continue; - - // 目标:gain[a] * avgColor[a] ≈ gain[b] * avgColor[b] - // 用 avgColor 的比值更新 - const cv::Vec3f& ca = avgColor[a]; - const cv::Vec3f& cb = avgColor[b]; + if (a >= NP || b >= NP || a == b) continue; + + const cv::Vec3f& ca = seamAvgColor[a]; + const cv::Vec3f& cb = seamAvgColor[b]; + for (int c = 0; c < 3; ++c) { - if (ca[c] > 1e-6f && cb[c] > 1e-6f) { + if (ca[c] > 1.0f) { // 避免极暗区域 const float ratio = cb[c] / ca[c]; - newGain[a] += ratio * gain[b]; + const float clamped = std::clamp(ratio, 0.5f, 2.0f); + newGain[a] += clamped * gain[b]; count[a]++; } } } - + for (size_t i = 0; i < NP; ++i) { if (count[i] > 0) { gain[i] = newGain[i] / count[i]; + gain[i] = std::clamp(gain[i], 0.5f, 2.0f); } } } - // 应用增益到 atlas:遍历每个 patch 的像素,乘上 gain + // ========== 第三步:应用增益到 atlas ========== for (size_t i = 0; i < NP; ++i) { const cv::Rect& r = rcPatches[i].rect; - if (r.x < 0 || r.y < 0 || r.x + r.width > atlas.cols || r.y + r.height > atlas.rows) + if (r.x < 0 || r.y < 0 || r.x + r.width > atlasMat.cols || r.y + r.height > atlasMat.rows) continue; - - cv::Mat patch = atlas(r); - const float g = std::clamp(gain[i], 0.5f, 2.0f); + + cv::Mat patch = atlasMat(r); + const float g = gain[i]; patch.convertTo(patch, CV_32FC3); patch *= g; patch.convertTo(patch, CV_8UC3); } - - DEBUG_EXTRA("GlobalAlignPatches: %zu patches aligned", NP); + + DEBUG_EXTRA("GlobalAlignPatches: %zu patches aligned (seam-based, %d iters)", NP, 20); } void MeshTexture::LocalBlendSeams(Image8U3& atlas, int textureSize) { - // ★ 包装为 cv::Mat - cv::Mat atlasMat(atlas.cols, atlas.rows, CV_8UC3, atlas.data); + cv::Mat atlasMat(atlas.rows, atlas.cols, CV_8UC3, atlas.data); + + const size_t NP = rcPatches.size(); + const size_t numFaces = scene.mesh.faces.size(); + + if (NP == 0 || faceToPatchID.size() != numFaces) { + DEBUG_EXTRA("LocalBlendSeams: invalid state (faceToPatchID.size=%zu, numFaces=%zu)", + faceToPatchID.size(), numFaces); + return; + } - const int blendWidth = 4; + // ---- 1. 构建邻接面 → patch 的映射(已有 faceToPatchID,这里直接用)---- + // faceToPatchID[faceID] = patchID - for (const SeamEdge& edge : rcSeamEdges) { - const uint32_t a = edge.rcPatchID0; - const uint32_t b = edge.rcPatchID1; - if (a >= rcPatches.size() || b >= rcPatches.size()) continue; - - const cv::Rect& ra = rcPatches[a].rect; - const cv::Rect& rb = rcPatches[b].rect; - cv::Rect overlap = ra & rb; - if (overlap.area() == 0) continue; - - // 在重叠区域做线性混合 - for (int y = 0; y < overlap.height; ++y) { - for (int x = 0; x < overlap.width; ++x) { - const float t = (float)x / overlap.width; - const cv::Vec3b& ca = atlasMat.at(ra.y + y, ra.x + x); - const cv::Vec3b& cb = atlasMat.at(rb.y + y, rb.x + x); - atlasMat.at(overlap.y + y, overlap.x + x) = cv::Vec3b( - (uint8_t)(ca[0] * (1 - t) + cb[0] * t), - (uint8_t)(ca[1] * (1 - t) + cb[1] * t), - (uint8_t)(ca[2] * (1 - t) + cb[2] * t) - ); + // ---- 2. 遍历所有 face,找跨 patch 的共享边 ---- + // 对于每条共享边,记录:端点顶点、两侧 patch ID + struct SeamEdgeInfo { + VIndex v0, v1; // 共享边的两个端点 + uint32_t patchA, patchB; + }; + std::vector seamEdges; + + // 用 faceFaces 遍历邻接关系(每条边只处理一次) + for (FIndex fid = 0; fid < (FIndex)numFaces; ++fid) { + const uint32_t pidA = faceToPatchID[fid]; + if (pidA == UINT32_MAX || pidA >= NP) continue; + + if (fid >= faceFaces.size()) continue; + const Mesh::FaceFaces& adj = faceFaces[fid]; // [3] 邻接面 + + for (int e = 0; e < 3; ++e) { + const FIndex adjFid = adj[e]; + if (adjFid == NO_ID || adjFid <= fid) continue; // 只处理一次(无向边) + + const uint32_t pidB = faceToPatchID[adjFid]; + if (pidB == UINT32_MAX || pidB >= NP) continue; + if (pidA == pidB) continue; // 同一 patch,不是接缝 + + // 找到共享边:face fid 的第 e 条边,与 face adjFid 共享两个顶点 + const Mesh::Face& faceA = scene.mesh.faces[fid]; + const Mesh::Face& faceB = scene.mesh.faces[adjFid]; + + // faceA 的第 e 条边的两个端点 + const VIndex v0 = faceA[e]; + const VIndex v1 = faceA[(e + 1) % 3]; + + seamEdges.push_back({v0, v1, pidA, pidB}); + } + } + + DEBUG_EXTRA("LocalBlendSeams: found %zu 3D seam edges", seamEdges.size()); + + // ---- 3. 对每条接缝边,在 atlas 上做逐像素混合 ---- + int blendedPixels = 0; + const int blendWidth = 3; // 混合带宽(像素),可调整 + + for (const SeamEdgeInfo& edge : seamEdges) { + // 取边两端点在 atlas 上的 UV(用 patchA 的 face 里的 UV) + // 需要找到该边在 patchA 的某个 face 上对应的两个 UV 坐标 + // 简化:直接用两个端点的 UV(端点在所有包含它的 face 上的 UV 应该一致) + + // 找一个包含 v0、v1 且属于 patchA 的 face + Point2f uvA0, uvA1, uvB0, uvB1; + bool foundA = false, foundB = false; + + // 在 patchA 的 faces 里找包含 v0、v1 的边 + for (FIndex fid : rcPatches[edge.patchA].faces) { + if (fid >= numFaces) continue; + const Mesh::Face& face = scene.mesh.faces[fid]; + for (int e = 0; e < 3; ++e) { + if ((face[e] == edge.v0 && face[(e+1)%3] == edge.v1) || + (face[e] == edge.v1 && face[(e+1)%3] == edge.v0)) { + uvA0 = Point2f(scene.mesh.faceTexcoords[fid * 3 + e].x * textureSize, + scene.mesh.faceTexcoords[fid * 3 + e].y * textureSize); + uvA1 = Point2f(scene.mesh.faceTexcoords[fid * 3 + (e+1)%3].x * textureSize, + scene.mesh.faceTexcoords[fid * 3 + (e+1)%3].y * textureSize); + foundA = true; + break; + } + } + if (foundA) break; + } + + for (FIndex fid : rcPatches[edge.patchB].faces) { + if (fid >= numFaces) continue; + const Mesh::Face& face = scene.mesh.faces[fid]; + for (int e = 0; e < 3; ++e) { + if ((face[e] == edge.v0 && face[(e+1)%3] == edge.v1) || + (face[e] == edge.v1 && face[(e+1)%3] == edge.v0)) { + uvB0 = Point2f(scene.mesh.faceTexcoords[fid * 3 + e].x * textureSize, + scene.mesh.faceTexcoords[fid * 3 + e].y * textureSize); + uvB1 = Point2f(scene.mesh.faceTexcoords[fid * 3 + (e+1)%3].x * textureSize, + scene.mesh.faceTexcoords[fid * 3 + (e+1)%3].y * textureSize); + foundB = true; + break; + } + } + if (foundB) break; + } + + if (!foundA || !foundB) continue; + + // 在 atlas 上画一条从 (uvA0,uvA1) 到 (uvB0,uvB1) 的"四边形带"并混合 + // 简化:沿边方向做线性插值混合 + const int numSteps = (int)std::sqrt((uvA1.x - uvA0.x)*(uvA1.x - uvA0.x) + + (uvA1.y - uvA0.y)*(uvA1.y - uvA0.y)) + 1; + const int steps = std::max(numSteps, 2); + + for (int s = 0; s <= steps; ++s) { + const float t = (float)s / steps; + + // 边上的点在 patchA 侧的位置 + const Point2f ptA(uvA0.x + (uvA1.x - uvA0.x) * t, + uvA0.y + (uvA1.y - uvA0.y) * t); + // 对应 patchB 侧的位置(同一 3D 边,不同 UV 参数化) + const Point2f ptB(uvB0.x + (uvB1.x - uvB0.x) * t, + uvB0.y + (uvB1.y - uvB0.y) * t); + + // 在 ptA 和 ptB 周围 blendWidth 像素内做混合 + for (int dy = -blendWidth; dy <= blendWidth; ++dy) { + for (int dx = -blendWidth; dx <= blendWidth; ++dx) { + const int xA = (int)ptA.x + dx; + const int yA = (int)ptA.y + dy; + const int xB = (int)ptB.x + dx; + const int yB = (int)ptB.y + dy; + + if (xA < 0 || yA < 0 || xA >= atlasMat.cols || yA >= atlasMat.rows) continue; + if (xB < 0 || yB < 0 || xB >= atlasMat.cols || yB >= atlasMat.rows) continue; + + // 距离边的权重(越近混合越强) + const float w = 1.0f - (float)(dx*dx + dy*dy) / ((blendWidth+1)*(blendWidth+1)); + if (w <= 0) continue; + + const cv::Vec3b& cA = atlasMat.at(yA, xA); + const cv::Vec3b& cB = atlasMat.at(yB, xB); + + atlasMat.at(yA, xA) = cv::Vec3b( + (uint8_t)(cA[0] * (1 - w) + cB[0] * w + 0.5f), + (uint8_t)(cA[1] * (1 - w) + cB[1] * w + 0.5f), + (uint8_t)(cA[2] * (1 - w) + cB[2] * w + 0.5f) + ); + blendedPixels++; + } } } } - DEBUG_EXTRA("LocalBlendSeams: %zu edges blended", rcSeamEdges.size()); + DEBUG_EXTRA("LocalBlendSeams: blended %d pixels along 3D seams", blendedPixels); } // ============================================================ // 把 rcPatches 体系转换为标准 texturePatches + seamVertices 体系 @@ -15401,7 +15977,9 @@ void MeshTexture::BuildStandardSeamDataFromRCPatches( seamVertices.push_back(sv); } } - + DEBUG_EXTRA("mapIdxPatch: %zu faces, unique patch IDs: %zu", + mapIdxPatch.size(), + std::unordered_set(mapIdxPatch.begin(), mapIdxPatch.end()).size()); DEBUG_EXTRA("BuildStandardSeamData: %zu patches, %zu seamVertices, %zu edges", NP, seamVertices.size(), rcSeamEdges.size()); } @@ -15546,69 +16124,6 @@ bool MeshTexture::EstimateGlobalPhotometricCorrection() return true; } -// ============================================================ -// 构建接缝边(基于 rcPatches + faceFaces) -// ============================================================ -void MeshTexture::BuildSeamEdgesFromRCPatches() -{ - rcSeamEdges.clear(); - seamEdges.clear(); // ✅ 同时清空老的 - - // face → rcPatch 映射 - std::vector faceToRCPatch(scene.mesh.faces.size(), NO_ID); - for (uint32_t pi = 0; pi < rcPatches.size(); ++pi) { - for (FIndex f : rcPatches[pi].faces) { - if (f < (FIndex)faceToRCPatch.size()) - faceToRCPatch[f] = pi; - } - } - - for (FIndex f0 = 0; f0 < (FIndex)scene.mesh.faces.size(); ++f0) { - uint32_t p0 = faceToRCPatch[f0]; - if (p0 == NO_ID) continue; - - const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[f0]; - for (int e = 0; e < 3; ++e) { - FIndex f1 = neighbors[e]; - if (f1 == NO_ID) continue; - - uint32_t p1 = faceToRCPatch[f1]; - if (p1 == NO_ID || p1 == p0) continue; - - const Mesh::Face& face0 = scene.mesh.faces[f0]; - int v0_idx = e; - int v1_idx = (e + 1) % 3; - - int idx0_in_f1 = -1, idx1_in_f1 = -1; - for (int k = 0; k < 3; ++k) { - if (scene.mesh.faces[f1][k] == face0[v0_idx]) idx0_in_f1 = k; - if (scene.mesh.faces[f1][k] == face0[v1_idx]) idx1_in_f1 = k; - } - if (idx0_in_f1 == -1 || idx1_in_f1 == -1) - continue; - - const TexCoord& uv0_p0 = scene.mesh.faceTexcoords[f0 * 3 + v0_idx]; - const TexCoord& uv1_p0 = scene.mesh.faceTexcoords[f0 * 3 + v1_idx]; - - // ✅ 存进你自己的 rcSeamEdges - SeamEdge edge; - edge.rcPatchID0 = p0; - edge.rcPatchID1 = p1; - edge.faceID0 = f0; - edge.faceID1 = f1; - edge.uv0 = uv0_p0; - edge.uv1 = uv1_p0; - rcSeamEdges.push_back(edge); // ✅ 改回 rcSeamEdges - - // ✅ 同步往老的 seamEdges 里塞 PairIdx(供 SeamBlendingFromOriginalImages 用) - seamEdges.Insert(PairIdx(f0, f1)); // 或 seamEdges.emplace_back(f0, f1); - } - } - - DEBUG_EXTRA("Built %zu RC seam edges, %zu legacy seam edges", - rcSeamEdges.size(), seamEdges.size()); -} - // ============================================================ // 接缝融合:从原始图像采样 + YCrCb 羽化 // ============================================================ @@ -20861,6 +21376,7 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi return false; texture.MergeSameViewPatches(); + texture.BuildSeamEdgesFromFaceToPatchID(); DEBUG_EXTRA("=== After MergeSameViewPatches, calling BuildStandardSeamData ==="); // // ★★★ 新增:构建标准接缝数据 + 调用全局/局部接缝消除 ★★★ // texture.BuildStandardSeamDataFromRCPatches( @@ -20869,8 +21385,9 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi // // 直接操作 atlas(不污染 images) // texture.RunSeamLevelingOnAtlas(textures.back(), nTextureSizeMultiple); - texture.GlobalAlignPatches(textures.back(), nTextureSizeMultiple); - texture.LocalBlendSeams(textures.back(), nTextureSizeMultiple); + // texture.RunGlobalColorOptimization(textures.back(), nTextureSizeMultiple, 0.1f); // λ=0.1 对齐 OpenMVS + // texture.GlobalAlignPatches(textures.back(), nTextureSizeMultiple); + // texture.LocalBlendSeams(textures.back(), nTextureSizeMultiple); mesh.texturesDiffuse = std::move(textures); DEBUG_EXTRA("Existing UV texturing completed: %u faces (%s)",