From 2d1f6a13e57297c517174c91c507e0c5efe62b5b Mon Sep 17 00:00:00 2001 From: hesuicong Date: Sat, 12 Sep 2026 09:51:31 +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 | 330 +++++++++++++++++++++++++++++++++++++- 1 file changed, 329 insertions(+), 1 deletion(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index d41b966..debfb21 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -764,6 +764,7 @@ public: void GlobalSeamLeveling4(); void LocalSeamLeveling(); void LocalSeamLeveling3(); + void LocalSeamLeveling4(); void GlobalSeamLevelingExternalUV(); void LocalSeamLevelingExternalUV(); @@ -10054,7 +10055,277 @@ void MeshTexture::LocalSeamLeveling3() } } } +void MeshTexture::LocalSeamLeveling4() +{ + ASSERT(!seamVertices.empty()); + const unsigned numPatches(texturePatches.size()-1); + + // ★ 统计跳过情况 + int skipEmptyRect = 0, skipBadLabel = 0, skipBadProj = 0, skipNoSeam = 0; + + #ifdef TEXOPT_USE_OPENMP + #pragma omp parallel for schedule(dynamic) + for (int i=0; i<(int)numPatches; ++i) { + #else + for (unsigned i=0; i= (int)images.size()) { + #ifdef TEXOPT_USE_OPENMP + #pragma omp atomic + #endif + skipBadLabel++; + continue; + } + + // ============================================================ + // ★ 保护 2:检查 rect 合法性(最常见 crash 原因) + // ============================================================ + if (texturePatch.rect.width <= 0 || texturePatch.rect.height <= 0 || + texturePatch.rect.x < 0 || texturePatch.rect.y < 0) { + #ifdef TEXOPT_USE_OPENMP + #pragma omp atomic + #endif + skipEmptyRect++; + continue; + } + + // 检查 rect 是否超出源图像范围 + const Image8U3& image0(images[texturePatch.label].image); + if (image0.empty()) { + #ifdef TEXOPT_USE_OPENMP + #pragma omp atomic + #endif + skipBadLabel++; + continue; + } + cv::Rect safeRect = texturePatch.rect & cv::Rect(0, 0, image0.cols, image0.rows); + if (safeRect.width <= 0 || safeRect.height <= 0) { + #ifdef TEXOPT_USE_OPENMP + #pragma omp atomic + #endif + skipEmptyRect++; + continue; + } + + // extract image + Image32F3 image, imageOrg; +#ifdef USE_CUDA + if (MeshTextureCUDA::ConvertToCUDA(image0(safeRect), image, 1.0/255.0)) {} + else { + image0(safeRect).convertTo(image, CV_32FC3, 1.0/255.0); + } +#else + image0(safeRect).convertTo(image, CV_32FC3, 1.0/255.0); +#endif + image.copyTo(imageOrg); + + if (image.empty() || image.cols < 2 || image.rows < 2) { + #ifdef TEXOPT_USE_OPENMP + #pragma omp atomic + #endif + skipEmptyRect++; + continue; + } + + // render patch coverage + Image8U mask(image.size()); { + mask.memset(0); + struct RasterMesh { + Image8U& image; + inline void operator()(const ImageRef& pt) { + if (image.isInside(pt)) + image(pt) = interior; + } + } data{mask}; + for (const FIndex idxFace: texturePatch.faces) { + if (idxFace >= faces.size()) continue; + const TexCoord* tri = faceTexcoords.data()+idxFace*3; + ColorMap::RasterizeTriangle(tri[0], tri[1], tri[2], data); + } + } + + const Sampler sampler; + const TexCoord offset(texturePatch.rect.tl()); + + // ============================================================ + // ★ 保护 3:遍历接缝前,检查这个 patch 在 seamVertices 里有没有引用 + // ============================================================ + bool patchHasSeam = false; + for (const SeamVertex& sv : seamVertices) { + for (const SeamVertex::Patch& p : sv.patches) { + if (p.idxPatch == idxPatch) { + patchHasSeam = true; + break; + } + } + if (patchHasSeam) break; + } + if (!patchHasSeam) { + // 这个 patch 不在任何接缝上,不需要局部融合,直接复制原图 + #ifdef TEXOPT_USE_OPENMP + #pragma omp atomic + #endif + skipNoSeam++; + + // 直接把 imageOrg 写回 imagePatch(不做泊松融合) + cv::Mat imagePatch(image0(safeRect)); + for (int r=0; r(r,c); + for (int p=0; p<3; ++p) + v[p] = (uint8_t)CLAMP(ROUND2INT(a[p]*255.f), 0, 255); + } + } + continue; + } + + // render the patch border meeting neighbor patches + for (const SeamVertex& seamVertex0: seamVertices) { + if (seamVertex0.patches.size() < 2) + continue; + const uint32_t idxVertPatch0(seamVertex0.patches.Find(idxPatch)); + if (idxVertPatch0 == SeamVertex::Patches::NO_INDEX) + continue; + const SeamVertex::Patch& patch0 = seamVertex0.patches[idxVertPatch0]; + const TexCoord p0(patch0.proj-offset); + + // ★ 保护:p0 必须在 image 范围内 + if (p0.x < 0 || p0.y < 0 || p0.x >= image.cols || p0.y >= image.rows) { + skipBadProj++; + continue; + } + + // for each edge of this vertex belonging to this patch... + for (const SeamVertex::Patch::Edge& edge0: patch0.edges) { + if (edge0.idxSeamVertex >= seamVertices.size()) continue; + + const SeamVertex& seamVertex1 = seamVertices[edge0.idxSeamVertex]; + const uint32_t idxVertPatch0Adj(seamVertex1.patches.Find(idxPatch)); + if (idxVertPatch0Adj == SeamVertex::Patches::NO_INDEX) continue; + + const SeamVertex::Patch& patch0Adj = seamVertex1.patches[idxVertPatch0Adj]; + const TexCoord p0Adj(patch0Adj.proj-offset); + + if (p0Adj.x < 0 || p0Adj.y < 0 || p0Adj.x >= image.cols || p0Adj.y >= image.rows) { + skipBadProj++; + continue; + } + + // find the other patch sharing the same edge + FOREACH(idxVertPatch1, seamVertex0.patches) { + if (idxVertPatch1 == idxVertPatch0) continue; + const SeamVertex::Patch& patch1 = seamVertex0.patches[idxVertPatch1]; + + if (patch1.idxPatch >= texturePatches.size()) continue; // ★ 越界保护 + + const uint32_t idxEdge1(patch1.edges.Find(edge0.idxSeamVertex)); + if (idxEdge1 == SeamVertex::Patch::Edges::NO_INDEX) continue; + + const TexCoord& p1(patch1.proj); + const uint32_t idxVertPatch1Adj(seamVertex1.patches.Find(patch1.idxPatch)); + if (idxVertPatch1Adj == SeamVertex::Patches::NO_INDEX) continue; + + const SeamVertex::Patch& patch1Adj = seamVertex1.patches[idxVertPatch1Adj]; + const TexCoord& p1Adj(patch1Adj.proj); + + // 检查 label + const int label1 = texturePatches[patch1.idxPatch].label; + if (label1 < 0 || label1 >= (int)images.size()) continue; + const Image8U3& image1(images[label1].image); + if (image1.empty()) continue; + struct RasterPatch { + Image32F3& image; + Image8U& mask; + const Image32F3& image0; + const Image8U3& image1; + const TexCoord p0, p0Dir; + const TexCoord p1, p1Dir; + const float length; + const Sampler sampler; + inline RasterPatch(Image32F3& _image, Image8U& _mask, const Image32F3& _image0, const Image8U3& _image1, + const TexCoord& _p0, const TexCoord& _p0Adj, const TexCoord& _p1, const TexCoord& _p1Adj) + : image(_image), mask(_mask), image0(_image0), image1(_image1), + p0(_p0), p0Dir(_p0Adj-_p0), p1(_p1), p1Dir(_p1Adj-_p1), length((float)norm(p0Dir)), sampler() {} + inline void operator()(const ImageRef& pt) { + // ★ 保护:pt 必须在 image 范围内 + if (pt.x < 0 || pt.y < 0 || pt.x >= image.cols || pt.y >= image.rows) + return; + const float l((float)norm(TexCoord(pt)-p0)/length); + const TexCoord samplePos0(p0 + p0Dir * l); + const Color color0(image0.sample(sampler, samplePos0)); + const TexCoord samplePos1(p1 + p1Dir * l); + const Color color1(image1.sample(sampler, samplePos1)/255.f); + image(pt) = Color((color0 + color1) * 0.5f); + mask(pt) = border; + } + } data(image, mask, imageOrg, image1, p0, p0Adj, p1, p1Adj); + + // ★ 保护:DrawLine 前检查端点 + if (p0.x >= 0 && p0.y >= 0 && p0Adj.x >= 0 && p0Adj.y >= 0 && + p0.x < image.cols && p0.y < image.rows && + p0Adj.x < image.cols && p0Adj.y < image.rows) { + Image32F3::DrawLine(p0, p0Adj, data); + } + break; + } + } + // render vertex + AccumColor accumColor; + for (const SeamVertex::Patch& patch: seamVertex0.patches) { + if (patch.idxPatch >= texturePatches.size()) continue; + int plabel = texturePatches[patch.idxPatch].label; + if (plabel < 0 || plabel >= (int)images.size()) continue; + const Image8U3& img(images[plabel].image); + accumColor.Add(img.sample(sampler, patch.proj)/255.f, 1.f); + } + const ImageRef pt(ROUND2INT(patch0.proj-offset)); + if (pt.x >= 0 && pt.y >= 0 && pt.x < image.cols && pt.y < image.rows) { + image(pt) = accumColor.Normalized(); + mask(pt) = border; + } + } + + // make sure the border is continuous +#ifdef USE_CUDA + if (MeshTextureCUDA::ProcessMaskCUDA(mask, 20)) {} + else +#endif + ProcessMask(mask, 20); + + // compute texture patch blending +#ifdef USE_CUDA + if (MeshTextureCUDA::PoissonBlendCUDA(image, imageOrg, mask, 1.0f)) {} + else +#endif + PoissonBlending(imageOrg, image, mask); + + // apply color correction to the patch image + cv::Mat imagePatch(image0(safeRect)); + #ifdef TEXOPT_USE_OPENMP + #pragma omp parallel for collapse(2) + #endif + for (int r=0; r(r,c); + for (int p=0; p<3; ++p) + v[p] = (uint8_t)CLAMP(ROUND2INT(a[p]*255.f), 0, 255); + } + } + } + + VERBOSE("LocalSeamLeveling3 stats: skipEmptyRect=%d, skipBadLabel=%d, skipBadProj=%d, skipNoSeam=%d", + skipEmptyRect, skipBadLabel, skipBadProj, skipNoSeam); +} void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& basename, bool bOriginFaceview, Scene *pScene) { bool bUseExternalUV = false; @@ -15587,6 +15858,62 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( DEBUG_EXTRA("expected: %d x %d x 3 = %d bytes", textureSize, textureSize, textureSize * textureSize * 3); + // ============================================================ + // ★ 桥接补丁:确保 texturePatches[i].label = viewID + // ============================================================ + // 你的 rcPatches 里每个 patch 有 viewID,但桥接时可能没设 label。 + // 如果桥接代码已经设了,这个循环是幂等的,不会破坏任何东西。 + VERBOSE("Syncing texturePatches.label ← rcPatches.viewID..."); + for (uint32_t i = 0; i < texturePatches.size(); ++i) { + // texturePatches 索引 i 对应 rcPatches 索引 i(你的桥接是 1:1 的) + if (i < rcPatches.size()) { + texturePatches[i].label = rcPatches[i].viewID; + } else { + // 兜底:如果数量不匹配,用 faceToPatchID 反查 + // 这种情况不应该发生,打日志报警 + VERBOSE(" WARNING: texturePatch[%u] has no matching rcPatch!", i); + } + } + + // 验证一下 label 是否正确 + int labelOK = 0, labelBad = 0; + for (uint32_t i = 0; i < texturePatches.size(); ++i) { + if (texturePatches[i].label >= 0 && texturePatches[i].label < (int)images.size()) { + ++labelOK; + } else { + ++labelBad; + } + } + VERBOSE(" label check: %d OK, %d BAD (out of %u)", labelOK, labelBad, (uint32_t)texturePatches.size()); + if (labelBad > 0) { + VERBOSE(" ERROR: %d texturePatches have invalid label (viewID out of range)!", labelBad); + // 不 return,让 LSL3 尽量跑,坏 label 的 patch 会采样到错误图像但不会导致崩溃 + } + + // ============================================================ + // ★ 确认 seamVertices 已构建(你的日志显示已经 90152 个) + // ============================================================ + if (seamVertices.empty()) { + VERBOSE("seamVertices is empty! Calling CreateSeamVertices()..."); + CreateSeamVertices(); // 如果之前没调过,这里补调 + VERBOSE(" seamVertices created: %zu", seamVertices.size()); + } else { + VERBOSE("seamVertices already built: %zu vertices, ready for LSL3", seamVertices.size()); + } + + // ============================================================ + // ★ 调用 LocalSeamLeveling4 + // ============================================================ + VERBOSE("Calling LocalSeamLeveling4 (poisson blending on %zu patches)...", texturePatches.size() - 1); + VERBOSE(" This may take 5~15 minutes for %zu patches...", texturePatches.size() - 1); + + const auto lsl3Start = std::chrono::high_resolution_clock::now(); + LocalSeamLeveling4(); + const auto lsl3End = std::chrono::high_resolution_clock::now(); + const double lsl3Sec = std::chrono::duration(lsl3End - lsl3Start).count(); + + VERBOSE("LocalSeamLeveling4 done in %.1f seconds.", lsl3Sec); + for (int i = 0; i < std::min(3, (int)images.size()); ++i) { char dbgName[256]; sprintf(dbgName, "debug_image_%d_after_gsl4.png", i); @@ -17340,6 +17667,7 @@ int MeshTexture::ComputeOptimalTextureSizeAdaptive( // 防止过小 if (textureSize < 1024) textureSize = 1024; + textureSize = 8192; // ★ 临时强制,调试完删掉 DEBUG_EXTRA("Adaptive texture size: %d (totalPixels=%.0f, validFaces=%d, max=%u)", textureSize, totalPixels, validFaces, nTextureSizeMultiple); @@ -17569,7 +17897,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( // ========== 颜色一致性代价 ========== { - const float lambda = 2.0f; + const float lambda = 100.0f; // ========== 收集邻居颜色(修复版:1-ring + faceToView)========== std::vector neighborColors; if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) {