diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index f40993d..f7253ef 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -791,14 +791,6 @@ public: const VirtualFaceMap& virtualFaceMap, Image8U3& atlas, int textureSize); - - // 在 class MeshTexture 的 public 或 private 区域添加 - struct AffineCorrection { - float kr, kg, kb; // gain per channel - float br, bg, bb; // bias per channel - }; - std::vector patchAffineByTexPatch; // index = texturePatch ID - void RunSeamLevelingOnAtlas(Image8U3& atlas, int textureSize); bool RasterizeVirtualFaces( const VirtualFaceMap& virtualFaceMap, @@ -836,169 +828,6 @@ public: std::vector patchPixelCount; // ========== 辅助函数 ========== - - // 在 MeshTexture 类的 public 区域添加 - - // 每个图像的 Lab 颜色统计 - struct LabStats { - cv::Vec3f mean; // L, a, b 均值 - cv::Vec3f stddev; // L, a, b 标准差 - bool valid = false; - }; - std::vector m_labStats; - IIndex m_refViewID = NO_ID; - - // 计算所有图像的 Lab 统计 - inline void ComputeLabStats() { - TD_TIMER_START(); - - m_labStats.resize(images.size()); - m_refViewID = NO_ID; - - // 第一遍:计算每个图像的统计 - float bestExposure = FLT_MAX; - - for (size_t vid = 0; vid < images.size(); ++vid) { - const Image& img = images[vid]; - if (!img.IsValid() || img.image.empty()) { - m_labStats[vid].valid = false; - continue; - } - - // 转 Lab 空间 - cv::Mat lab; - cv::cvtColor(img.image, lab, cv::COLOR_BGR2Lab); - lab.convertTo(lab, CV_32FC3); - - // 计算均值和标准差 - cv::Scalar mean, stddev; - cv::meanStdDev(lab, mean, stddev); - - m_labStats[vid].mean = cv::Vec3f(mean[0], mean[1], mean[2]); - m_labStats[vid].stddev = cv::Vec3f(stddev[0], stddev[1], stddev[2]); - m_labStats[vid].valid = true; - - // 选择 L 均值最接近 50 的作为参考视图 - float exposure = std::abs(mean[0] - 50.0f); - if (exposure < bestExposure) { - bestExposure = exposure; - m_refViewID = (IIndex)vid; - } - - DEBUG_EXTRA("[LabStats] View %d: L=%.1f±%.1f, a=%.1f±%.1f, b=%.1f±%.1f", - (int)vid, - mean[0], stddev[0], - mean[1], stddev[1], - mean[2], stddev[2]); - } - - DEBUG_EXTRA("[LabStats] Computed for %zu images, reference view = %d (%s)", - m_labStats.size(), - (int)m_refViewID, - m_refViewID != NO_ID ? images[m_refViewID].name.c_str() : "none"); - - DEBUG_EXTRA("[LabStats] Time: %s", TD_TIMER_GET_FMT().c_str()); - } - - // 从原始图像采样某点的颜色(BGR,8位) - inline cv::Vec3b SampleRawColor(IIndex vid, const Point3f& pt) const { - if (vid >= (IIndex)images.size()) { - return cv::Vec3b(0, 0, 0); - } - - const Image& img = images[vid]; - if (img.image.empty()) { - return cv::Vec3b(0, 0, 0); - } - - const Camera& cam = img.camera; - - // 投影到图像坐标 - Point2f imgPt = cam.ProjectPoint(Point3d(pt)); - - // 边界检查 - if (imgPt.x < 0 || imgPt.x >= img.image.cols - 1 || - imgPt.y < 0 || imgPt.y >= img.image.rows - 1) { - return cv::Vec3b(0, 0, 0); - } - - // 双线性插值采样 - int x = (int)imgPt.x; - int y = (int)imgPt.y; - float fx = imgPt.x - x; - float fy = imgPt.y - y; - - cv::Vec3b c00 = img.image.at(y, x); - cv::Vec3b c10 = img.image.at(y, x + 1); - cv::Vec3b c01 = img.image.at(y + 1, x); - cv::Vec3b c11 = img.image.at(y + 1, x + 1); - - cv::Vec3b result; - for (int ch = 0; ch < 3; ++ch) { - float val = (c00[ch] * (1-fx) + c10[ch] * fx) * (1-fy) - + (c01[ch] * (1-fx) + c11[ ch] * fx) * fy; - result[ch] = cv::saturate_cast(val); - } - - return result; - } - - // 颜色迁移函数:将像素从源视图迁移到参考视图的颜色空间 - inline cv::Vec3b TransferPixelColorSafe(const cv::Vec3b& srcPixel, IIndex srcViewID, IIndex refViewID, - const LabStats& srcStats, const LabStats& refStats, - float strength = 0.6f) { - if (srcViewID == refViewID || !srcStats.valid || !refStats.valid) - return srcPixel; - - // 1. 转 Lab (32位浮点) - cv::Mat pixelMat(1, 1, CV_8UC3); - pixelMat.at(0,0) = srcPixel; - cv::Mat labMat; - cv::cvtColor(pixelMat, labMat, cv::COLOR_BGR2Lab); - labMat.convertTo(labMat, CV_32FC3); - cv::Vec3f labVal = labMat.at(0,0); - cv::Vec3f transferred = labVal; - - // 保护:标准差过小会导致除零或噪声放大 - auto safeStd = [](float s) { return std::max(s, 2.0f); }; // Lab空间标准差阈值放宽 - - // 2. L 通道:对齐亮度(带强度因子,防止过暗) - float srcLMean = srcStats.mean[0], refLMean = refStats.mean[0]; - float srcLStd = safeStd(srcStats.stddev[0]), refLStd = safeStd(refStats.stddev[0]); - - float zL = (labVal[0] - srcLMean) / srcLStd; - zL = std::max(-2.0f, std::min(2.0f, zL)); // 截断极端值 - float newL = zL * refLStd + refLMean; - - // 混合原图L与迁移L(strength=0.6 表示60%迁移,保留40%原貌) - transferred[0] = labVal[0] * (1.0f - strength) + newL * strength; - transferred[0] = std::max(0.0f, std::min(100.0f, transferred[0])); // 32位Lab L范围0-100 - - // 3. a/b 通道:仅做轻微缩放(不做均值平移,防止变灰!) - for (int ch = 1; ch <= 2; ++ch) { - float srcMean = srcStats.mean[ch]; - float refMean = refStats.mean[ch]; // 通常接近128(8位)或0(32位) - float srcStd = safeStd(srcStats.stddev[ch]); - float refStd = safeStd(refStats.stddev[ch]); - - float z = (labVal[ch] - srcMean) / srcStd; - z = std::max(-2.0f, std::min(2.0f, z)); - - // 仅缩放标准差,均值保持原图均值(保留色相) - float newAB = srcMean + z * (refStd / srcStd) * strength; - transferred[ch] = labVal[ch] * (1.0f - strength) + newAB * strength; - transferred[ch] = std::max(-127.0f, std::min(127.0f, transferred[ch])); - } - - // 4. 转回 BGR (确保流程:32F -> 8U -> BGR) - labMat.at(0,0) = transferred; - labMat.convertTo(labMat, CV_8UC3); - cv::Mat bgrMat; - cv::cvtColor(labMat, bgrMat, cv::COLOR_Lab2BGR); - - return bgrMat.at(0,0); - } - // 双线性采样(适配 SEACAVE::TImage) static inline Pixel8U SampleBilinear(const Image8U3& img, const Point2f& pt) { float x = pt.x * img.width() - 0.5f; @@ -9376,7 +9205,15 @@ void MeshTexture::GlobalSeamLeveling4() } const unsigned numPatches = (unsigned)(texturePatches.size() - 1); - DEBUG_EXTRA("GlobalSeamLeveling4 (Affine): numPatches=%u", numPatches); + 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()); + } + } // ========== 标记无效顶点 ========== BoolArr vertexInvalid(vertices.size()); @@ -9407,13 +9244,15 @@ 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 ========== - // ★ 每个 vertpatch 有 6 个变量: scale_R, scale_G, scale_B, offset_R, offset_G, offset_B + ASSERT(vertices.size() < static_cast(std::numeric_limits::max())); MatIdx rowsX(0); typedef std::unordered_map VertexPatch2RowMap; std::vector vertpatch2rows(vertices.size()); @@ -9423,31 +9262,38 @@ void MeshTexture::GlobalSeamLeveling4() VertexPatch2RowMap& vertpatch2row = vertpatch2rows[i]; if (patchIndex.bIndex) { const SeamVertex& seamVertex = seamVertices[patchIndex.idxSeamVertex]; + ASSERT(seamVertex.idxVertex == i); for (const SeamVertex::Patch& patch : seamVertex.patches) { - if (patch.idxPatch >= numPatches) continue; - if (vertpatch2row.find(patch.idxPatch) != vertpatch2row.end()) continue; + 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++; } } - const Eigen::Index scalarCols = (Eigen::Index)rowsX * 6; // ★ 6 variables per vertpatch + const Eigen::Index scalarCols = (Eigen::Index)rowsX * 3; DEBUG_EXTRA("vertpatch2rows built: rowsX=%u, scalarCols=%lld", rowsX, (long long)scalarCols); - // ========== 构建 Gamma(平滑正则)========== + // ============================================================ + // ========== 构建 Gamma(Tikhonov 正则)========== + // ============================================================ std::vector gammaTriplets; - gammaTriplets.reserve((size_t)vertices.size() * 12); + gammaTriplets.reserve((size_t)vertices.size() * 8); struct GammaRaw { Eigen::Index colA, colB; float w; }; std::vector rawGamma; - rawGamma.reserve(vertices.size() * 8); + rawGamma.reserve(vertices.size() * 6); Mesh::VertexIdxArr adjVerts; FOREACH(v, vertices) { adjVerts.Empty(); scene.mesh.GetAdjVertices(v, adjVerts); - if (adjVerts.GetSize() > 8) adjVerts.Resize(8); + + const size_t MAX_ADJ = 8; + if (adjVerts.GetSize() > MAX_ADJ) adjVerts.Resize(MAX_ADJ); VertexPatchIterator itV(patchIndices[v], seamVertices); while (itV.Next()) { @@ -9455,7 +9301,7 @@ void MeshTexture::GlobalSeamLeveling4() if (idxPatch == numPatches) continue; auto it_row = vertpatch2rows[v].find(idxPatch); if (it_row == vertpatch2rows[v].end()) continue; - const Eigen::Index baseA = it_row->second * 6; // ★ base * 6 + const Eigen::Index colA = it_row->second; for (const VIndex vAdj : adjVerts) { if (v >= vAdj) continue; @@ -9465,13 +9311,10 @@ void MeshTexture::GlobalSeamLeveling4() if (idxPatch != idxPatchAdj) continue; auto it_rowAdj = vertpatch2rows[vAdj].find(idxPatchAdj); if (it_rowAdj == vertpatch2rows[vAdj].end()) continue; - const Eigen::Index baseB = it_rowAdj->second * 6; + const Eigen::Index colB = it_rowAdj->second; - const float w = (vertexInvalid[v] || vertexInvalid[vAdj]) ? 0.02f : 0.3f; - // scale 和 offset 都加平滑 - for (int k = 0; k < 6; ++k) { - rawGamma.push_back({baseA + k, baseB + k, w}); - } + const float w = (vertexInvalid[v] || vertexInvalid[vAdj]) ? 0.05f : 0.5f; + rawGamma.push_back({colA, colB, w}); } } } @@ -9487,23 +9330,27 @@ void MeshTexture::GlobalSeamLeveling4() size_t j = i + 1; while (j < rawGamma.size() && rawGamma[j].colA == rawGamma[i].colA && rawGamma[j].colB == rawGamma[i].colB) ++j; - gammaTriplets.emplace_back(rowG, rawGamma[i].colA, rawGamma[i].w); - gammaTriplets.emplace_back(rowG, rawGamma[i].colB, -rawGamma[i].w); + 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; - SparseMat Gamma(rowG, 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(); - // ========== 构建约束 A * x = b ========== + // ============================================================ + // ========== 构建矩阵 A 和 b(颜色一致性约束)========== + // ============================================================ std::vector ATriplets; - ATriplets.reserve(seamVertices.GetSize() * 12); + ATriplets.reserve(seamVertices.GetSize() * 6); std::vector b_coeff; - b_coeff.reserve(seamVertices.GetSize() * 12); + b_coeff.reserve(seamVertices.GetSize() * 6); IndexArr indices; Colors vertexColors; @@ -9520,7 +9367,9 @@ void MeshTexture::GlobalSeamLeveling4() const TexturePatch& tpRef = texturePatches[patch0.idxPatch]; if (tpRef.label < 0 || tpRef.label >= (IIndex)images.size()) continue; - if (std::isnan(patch0.proj.x) || std::isnan(patch0.proj.y)) { + // ★FIX: 检查 patch0.proj 是否为 NaN/Inf(崩溃根源) + if (std::isnan(patch0.proj.x) || std::isnan(patch0.proj.y) || + std::isinf(patch0.proj.x) || std::isinf(patch0.proj.y)) { vertexColors[i] = Color::ZERO; continue; } @@ -9532,7 +9381,12 @@ void MeshTexture::GlobalSeamLeveling4() const auto idxPatch1 = seamVertex1.patches.Find(patch0.idxPatch); if (idxPatch1 == SeamVertex::Patches::NO_INDEX) continue; const SeamVertex::Patch& patch1 = seamVertex1.patches[idxPatch1]; - if (std::isnan(patch1.proj.x) || std::isnan(patch1.proj.y)) continue; + + // ★FIX: 检查 patch1.proj 是否为 NaN/Inf,防止 AddEdge 崩溃 + if (std::isnan(patch1.proj.x) || std::isnan(patch1.proj.y) || + std::isinf(patch1.proj.x) || std::isinf(patch1.proj.y)) + continue; + sampler.AddEdge(patch0.proj, patch1.proj); } vertexColors[i] = sampler.GetColor(); @@ -9545,129 +9399,75 @@ void MeshTexture::GlobalSeamLeveling4() 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 * 6; // ★ ×6 - const Eigen::Index baseB = itB->second * 6; + const Eigen::Index baseA = itA->second; + const Eigen::Index baseB = itB->second; const Color& ca = vertexColors[i]; const Color& cb = vertexColors[j]; - - // ★ 仿射约束: scale_A * ca + offset_A = scale_B * cb + offset_B - // 即: scale_A * ca - scale_B * cb + offset_A - offset_B = 0 - for (int c = 0; c < 3; ++c) { - const float norm_ca = ca[c] / 255.0f; // ★ 归一化 - const float norm_cb = cb[c] / 255.0f; // ★ 归一化 - - const Eigen::Index colA_scale = baseA + c; - const Eigen::Index colA_offset = baseA + 3 + c; - const Eigen::Index colB_scale = baseB + c; - const Eigen::Index colB_offset = baseB + 3 + c; - - ATriplets.emplace_back(rowA, colA_scale, norm_ca); - ATriplets.emplace_back(rowA, colB_scale, -norm_cb); - ATriplets.emplace_back(rowA, colA_offset, 1.0f); - ATriplets.emplace_back(rowA, colB_offset, -1.0f); - - if ((size_t)rowA >= b_coeff.size()) b_coeff.push_back(0); - b_coeff[rowA] = 1.0f; - ++rowA; - } + for (int c = 0; c < 3; ++c) { + const Eigen::Index colA = baseA * 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; SparseMat A(rowsA, scalarCols); A.setFromTriplets(ATriplets.begin(), ATriplets.end()); A.makeCompressed(); ATriplets.clear(); ATriplets.shrink_to_fit(); - // ========== 求解 ========== - struct AffineParam { float scale[3]; float offset[3]; }; - std::vector patchAffine; - patchAffine.assign(rowsX, {{1,1,1}, {0,0,0}}); + // ============================================================ + // ========== 求解 ========== + // ============================================================ + std::vector colorAdjustments; + colorAdjustments.assign(rowsX, Color::ZERO); - if (rowsA > 0 && rowsX > 0) { - SparseMat ATA = A.transpose() * A; - SparseMat GTG = Gamma.transpose() * Gamma; - const float lambda = 20.0f; - GTG *= lambda * lambda; - ATA += GTG; - SparseMat& Lhs = ATA; + if (rowsA > 0 && rowsX > 0) { + SparseMat ATA = A.transpose() * A; + SparseMat GTG = Gamma.transpose() * Gamma; + const float lambda = 0.1f; + GTG *= lambda * lambda; + ATA += GTG; + SparseMat& Lhs = ATA; - // ★ 正确构建 rhs = A^T * b - Eigen::VectorXf rhs = A.transpose() * Eigen::Map(b_coeff.data(), rowsA); + const float eps = 1e-3f; + for (Eigen::Index i = 0; i < (Eigen::Index)Lhs.rows(); ++i) { + Lhs.coeffRef(i, i) += eps; + } - // ★ 正则化:同时加 Lhs 和 rhs - const float lambda_identity = 20.0f; // ★ 增大 - for (Eigen::Index v = 0; v < (Eigen::Index)rowsX; ++v) { - for (int c = 0; c < 3; ++c) { - const Eigen::Index col_scale = v * 6 + c; - const Eigen::Index col_offset = v * 6 + 3 + c; - - Lhs.coeffRef(col_scale, col_scale) += lambda_identity; - Lhs.coeffRef(col_offset, col_offset) += lambda_identity * 0.5f; - - rhs(col_scale) += lambda_identity * 1.0f; - // rhs(col_offset) += 0 (target = 0) - } - } - - const float eps = 1e-4f; - for (Eigen::Index i = 0; i < (Eigen::Index)Lhs.rows(); ++i) - Lhs.coeffRef(i, i) += eps; - - Eigen::ConjugateGradient solver; - solver.setMaxIterations(3000); - solver.setTolerance(0.001f); - solver.compute(Lhs); - if (solver.info() == Eigen::Success) { - const Eigen::VectorXf x = solver.solve(rhs); - - // ★★★ 放在这里 ★★★ - DEBUG_EXTRA("Solver: info=%d iterations=%d error=%.6f", - (int)solver.info(), solver.iterations(), solver.error()); - - printf(">>> MY_NEW_CODE_IS_RUNNING <<<\n"); - fflush(stdout); - - // ★ 同时把 RAW x stats 也放这里 - if ((Eigen::Index)(x.size()) >= scalarCols) { - float x_min = x.minCoeff(), x_max = x.maxCoeff(); - float x_mean = x.mean(); - DEBUG_EXTRA("RAW x stats: min=%.4f max=%.4f mean=%.4f", x_min, x_max, x_mean); - } + Eigen::ConjugateGradient solver; + solver.setMaxIterations(2000); + solver.setTolerance(0.001f); + solver.compute(Lhs); + if (solver.info() == Eigen::Success) { + 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); 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) { - AffineParam& param = patchAffine[v]; - for (int c = 0; c < 3; ++c) { - param.scale[c] = std::max(-2.0f, std::min(4.0f, x(v * 6 + c))); - param.offset[c] = std::max(-1.0f, std::min(1.0f, x(v * 6 + 3 + c))); - } + 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); } } - } - } + } + } b_coeff.clear(); b_coeff.shrink_to_fit(); - // ========== 统计 ========== - { - float s_sum=0, o_sum=0, s_mn=1e9, s_mx=-1e9, o_mn=1e9, o_mx=-1e9; - int cnt=0; - for (const auto& p : patchAffine) { - for (int c=0; c<3; ++c) { - s_sum += p.scale[c]; o_sum += p.offset[c]; - s_mn = std::min(s_mn, p.scale[c]); s_mx = std::max(s_mx, p.scale[c]); - o_mn = std::min(o_mn, p.offset[c]); o_mx = std::max(o_mx, p.offset[c]); - ++cnt; - } - } - DEBUG_EXTRA("Affine stats: scale min=%.3f max=%.3f mean=%.3f | offset min=%.1f max=%.1f mean=%.1f", - s_mn, s_mx, s_sum/cnt, o_mn, o_mx, o_sum/(cnt*3)); - } - - // ========== 应用颜色修正 ========== + // ========== 应用颜色修正 ========== correctedPatchImages.resize(numPatches); #ifdef TEXOPT_USE_OPENMP @@ -9686,176 +9486,68 @@ void MeshTexture::GlobalSeamLeveling4() cv::Mat& outPatch = correctedPatchImages[idxPatch]; outPatch = cv::Mat::zeros(texturePatch.rect.size(), CV_8UC3); - ColorMap imageAffine(texturePatch.rect.size()); - // 初始化为 identity (scale=1, offset=0) - for (int _r = 0; _r < imageAffine.rows; ++_r) - for (int _c = 0; _c < imageAffine.cols; ++_c) - imageAffine(_r, _c) = Color(1.0f, 1.0f, 1.0f); + ColorMap imageAdj(texturePatch.rect.size()); + imageAdj.memset(0); - // ★ 用两个 ColorMap 分别存 scale 和 offset - ColorMap imageOffset(texturePatch.rect.size()); - for (int _r = 0; _r < imageOffset.rows; ++_r) - for (int _c = 0; _c < imageOffset.cols; ++_c) - imageOffset(_r, _c) = Color(0.0f, 0.0f, 0.0f); - - // ★ 修改后的 RasterPatchAffine,带接缝软过渡 - struct RasterPatchAffine { + struct RasterPatch { const TexCoord* tri; - Color colors[3]; // scale - Color colorsOffset[3]; // offset - ColorMap& imageScale; - ColorMap& imageOffset; - const SeamVertex* seamVerticesPtr; - int numSeamVertices; - VIndex vertexIdx[3]; - - RasterPatchAffine(ColorMap& s, ColorMap& o, - const SeamVertex* sv, int nsv) - : imageScale(s), imageOffset(o), - seamVerticesPtr(sv), numSeamVertices(nsv) {} - - inline cv::Size Size() const { return imageScale.size(); } - - void operator()(const ImageRef& pt, const Point3f& bary) { - Color s = colors[0]*bary.x + colors[1]*bary.y + colors[2]*bary.z; - Color o = colorsOffset[0]*bary.x + colorsOffset[1]*bary.y + colorsOffset[2]*bary.z; - if (std::isnan(s[0]) || std::isinf(s[0])) return; - - // ★ 计算到最近接缝的距离,用于软过渡 - float minDistToSeam = 1.0f; - for (int vi = 0; vi < 3; ++vi) { - VIndex vidx = vertexIdx[vi]; - for (int si = 0; si < numSeamVertices; ++si) { - if (seamVerticesPtr[si].idxVertex == vidx) { - float dist = 1.0f - (vi == 0 ? bary.x : (vi == 1 ? bary.y : bary.z)); - minDistToSeam = std::min(minDistToSeam, dist); - } - } - } - - // ★ 如果靠近接缝(距离 < 0.1),减弱修正强度 - if (minDistToSeam < 0.1f) { - float blend = minDistToSeam / 0.1f; - s = Color(1.0f, 1.0f, 1.0f) * (1.0f - blend) + s * blend; - o = Color(0.0f, 0.0f, 0.0f) * (1.0f - blend) + o * blend; - } - - imageScale(pt) = s; - imageOffset(pt) = o; - } - } data(imageAffine, imageOffset, - seamVertices.Begin(), seamVertices.GetSize()); + 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) { + Color c = colors[0]*bary.x + colors[1]*bary.y + colors[2]*bary.z; + // ★ 过滤 NaN/Inf 调整值 + if (std::isnan(c[0]) || std::isnan(c[1]) || std::isnan(c[2]) || + std::isinf(c[0]) || std::isinf(c[1]) || std::isinf(c[2])) { + return; + } + image(pt) = c; + } + } data(imageAdj); for (const FIndex idxFace : texturePatch.faces) { if (idxFace >= faces.GetSize()) continue; const Face& face = faces[idxFace]; data.tri = faceTexcoords.Begin() + idxFace * 3; - - // ★ 设置当前三角形的三个顶点索引 - data.vertexIdx[0] = face[0]; - data.vertexIdx[1] = face[1]; - data.vertexIdx[2] = face[2]; 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()) { - const AffineParam& param = patchAffine[search->second]; - data.colors[v] = Color(param.scale[0], param.scale[1], param.scale[2]); - data.colorsOffset[v] = Color(param.offset[0], param.offset[1], param.offset[2]); + ASSERT(search->second < (Eigen::Index)colorAdjustments.size()); + data.colors[v] = colorAdjustments[search->second]; } else { - data.colors[v] = Color(1.0f, 1.0f, 1.0f); - data.colorsOffset[v] = Color(0.0f, 0.0f, 0.0f); + data.colors[v] = Color::ZERO; } } ColorMap::RasterizeTriangleBary(data.tri[0], data.tri[1], data.tri[2], data); } - imageAffine.DilateMean<1>(imageAffine, Color(1.0f, 1.0f, 1.0f)); - imageOffset.DilateMean<1>(imageOffset, Color(0.0f, 0.0f, 0.0f)); + // ★FIX: 改回 DilateMean<1>(和原版一致,之前 <5> 过度模糊) + imageAdj.DilateMean<1>(imageAdj, Color::ZERO); - // 应用: out = src * scale + offset * 255 + // ★FIX: 去掉 GAIN=10,Y/Cb/Cr 全部 1:1 微调(和原版 GSL3 一致) for (int r = 0; r < srcROI.rows; ++r) { for (int c = 0; c < srcROI.cols; ++c) { - const Color& scale = imageAffine(r, c); - const Color& offset = imageOffset(r, c); - const cv::Vec3b& src = srcROI.at(r, 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(src[0] * scale[0] + offset[0] * 255.0f), 0, 255), - (uint8_t)CLAMP(ROUND2INT(src[1] * scale[1] + offset[1] * 255.0f), 0, 255), - (uint8_t)CLAMP(ROUND2INT(src[2] * scale[2] + offset[2] * 255.0f), 0, 255) - ); + (uint8_t)CLAMP(ROUND2INT(acol[0]), 0, 255), + (uint8_t)CLAMP(ROUND2INT(acol[1]), 0, 255), + (uint8_t)CLAMP(ROUND2INT(acol[2]), 0, 255)); } } } - // ========== 映射到按 texturePatch 索引的数组 ========== - patchAffineByTexPatch.clear(); - patchAffineByTexPatch.resize(images.size(), {1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f}); - - // 对于每个 texturePatch,取其第一个顶点的仿射参数作为代表 - for (size_t tpi = 0; tpi < numPatches; ++tpi) { - const TexturePatch& tp = texturePatches[tpi]; - if (tp.label < 0 || tp.label >= (int)images.size()) continue; - - // 取该 patch 的第一个有效 face 的第一个顶点 - bool found = false; - for (FIndex fid : tp.faces) { - if (fid >= faces.GetSize()) continue; - const Face& face = faces[fid]; - for (int v = 0; v < 3; ++v) { - auto search = vertpatch2rows[face[v]].find((uint32_t)tpi); - if (search != vertpatch2rows[face[v]].end()) { - const AffineParam& param = patchAffine[search->second]; - AffineCorrection& ac = patchAffineByTexPatch[tp.label]; - ac.kr = param.scale[0]; - ac.kg = param.scale[1]; - ac.kb = param.scale[2]; - ac.br = param.offset[0]; - ac.bg = param.offset[1]; - ac.bb = param.offset[2]; - found = true; - break; - } - } - if (found) break; - } - } - - patchAffineByTexPatch.clear(); - patchAffineByTexPatch.resize(numPatches, {1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f}); - - printf("[DEBUG] Filling patchAffineByTexPatch, numPatches=%zu\n", numPatches); - - for (size_t tpi = 0; tpi < numPatches; ++tpi) { - const TexturePatch& tp = texturePatches[tpi]; - bool found = false; - for (FIndex fid : tp.faces) { - if (fid >= faces.GetSize()) continue; - const Face& face = faces[fid]; - for (int v = 0; v < 3; ++v) { - auto search = vertpatch2rows[face[v]].find((uint32_t)tpi); - if (search != vertpatch2rows[face[v]].end()) { - const AffineParam& param = patchAffine[search->second]; - AffineCorrection& ac = patchAffineByTexPatch[tpi]; - ac.kr = param.scale[0]; - ac.kg = param.scale[1]; - ac.kb = param.scale[2]; - ac.br = param.offset[0]; - ac.bg = param.offset[1]; - ac.bb = param.offset[2]; - found = true; - break; - } - } - if (found) break; - } - } - - printf("[DEBUG] patchAffineByTexPatch filled, size=%zu\n", patchAffineByTexPatch.size()); - - DEBUG_EXTRA("GlobalSeamLeveling4 (Affine) finished successfully."); + DEBUG_EXTRA("GlobalSeamLeveling4 finished successfully."); } // set to one in order to dilate also on the diagonal of the border @@ -15919,7 +15611,7 @@ void MeshTexture::AlignPatchColors(Image8U3& atlas, for (int c = 0; c < 3; ++c) { if (selfMean[c] > 1.0f) { float g = (float)(nbMean[c] / selfMean[c]); - gain[i][c] = std::max(0.5f, std::min(2.0f, g)); // 保守范围 + gain[i][c] = std::max(0.7f, std::min(1.4f, g)); // 保守范围 } } } @@ -15990,16 +15682,16 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( { if (rcPatches.empty()) return; - Image8U3 localAtlas(textureSize, textureSize); - localAtlas.memset(0); // 全部填 0 = 黑色 + 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); - + DEBUG_EXTRA("faceTexcoords ptr=%p, scene ptr=%p", &faceTexcoords, &scene.mesh.faceTexcoords); // ========== 1. 构建 texturePatches ========== texturePatches.clear(); std::vector rcToTexPatch(rcPatches.size(), NO_ID); + // ★★★ 诊断日志:构建循环之前 ★★★ if (!rcPatches.empty()) { DEBUG_EXTRA("rcPatch[0]: viewID=%d, faces.size()=%zu, firstFace=%u, virtualFaceMap.size()=%zu", rcPatches[0].viewID, rcPatches[0].faces.size(), @@ -16007,6 +15699,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( virtualFaceMap.size()); } + // ★ 诊断计数器 size_t skippedEmpty = 0, skippedBadView = 0, skippedEmptyImage = 0; size_t skippedNoProj = 0, skippedBadROI = 0, skippedNoFaces = 0; @@ -16049,6 +15742,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( 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]; @@ -16102,134 +15796,146 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( seamEdges.clear(); seamVertices.clear(); + // // 填充 faceToPatchID + // faceToPatchID.assign(scene.mesh.faces.size(), NO_ID); + // for (size_t pi = 0; pi < rcPatches.size(); ++pi) { + // if (rcToTexPatch[pi] == NO_ID) continue; + // uint32_t texPatchID = rcToTexPatch[pi]; + // for (FIndex vfID : rcPatches[pi].faces) { + // if (vfID >= virtualFaceMap.size()) continue; + // for (FIndex fid : virtualFaceMap[vfID].faces) { + // if (fid < faceToPatchID.size()) + // faceToPatchID[fid] = texPatchID; + // } + // } + // } + BuildSeamEdgesFromFaceToPatchID(); CreateSeamVertices(); Mesh::TexCoordArr localFaceTexcoords; - localFaceTexcoords.Resize(scene.mesh.faces.size() * 3); + 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); - } - } - } + 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); + } + } + } + // faceTexcoords = localFaceTexcoords; DEBUG_EXTRA("Bridge done: %zu texturePatches ready, calling GlobalSeamLeveling4...", numValidPatches); - // ========== 调用 GSL4 ========== + // ========== 调用 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); + 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); - // ========== 桥接补丁 ========== - VERBOSE("Syncing texturePatches.label ← rcPatches.viewID..."); - for (uint32_t i = 0; i < texturePatches.size(); ++i) { - if (i < rcPatches.size()) { - texturePatches[i].label = rcPatches[i].viewID; - } else { - VERBOSE(" WARNING: texturePatch[%u] has no matching rcPatch!", i); - } - } + // ============================================================ + // ★ 桥接补丁:确保 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); + } + } - 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); - } + // 验证一下 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 检查 ========== - 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()); - } + // ============================================================ + // ★ 确认 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); + // ============================================================ + // ★ 调用 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(); + 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); + 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); - cv::imwrite(dbgName, images[i].image); - } - DEBUG_EXTRA("Exported debug images after GSL4"); - - // ========== 3. 重光栅化(自动检测H方向 + ROI + 仿射校正)========== - - if (!images.empty() && !images[0].image.empty()) { - const auto& p = images[0].image.at(images[0].image.rows/2, images[0].image.cols/2); - LOG("center pixel B=%d G=%d R=%d\n", p[0], p[1], p[2]); - } + for (int i = 0; i < std::min(3, (int)images.size()); ++i) { + char dbgName[256]; + sprintf(dbgName, "debug_image_%d_after_gsl4.png", i); + cv::imwrite(dbgName, images[i].image); // ← 用 .image + } + DEBUG_EXTRA("Exported debug images after GSL4"); + // ========== 3. 重光栅化(从已被 GSL4 修正的 images 读)========== ASSERT(atlas.rows == textureSize && atlas.cols == textureSize); - localAtlas.setTo(cv::Scalar(0,0,0)); - - bool firstParamPrinted = false; - bool savedDebugRemapped = false; - for (int pi = 0; pi < (int)rcPatches.size(); ++pi) { - if (pi % 500 == 0 || pi == (int)rcPatches.size() - 1) { - printf("[Reraster] patch %d / %zu\n", pi, rcPatches.size()); - fflush(stdout); - } - if (rcToTexPatch[pi] == NO_ID) continue; const TexturePatch& tp = texturePatches[rcToTexPatch[pi]]; const Image& srcImg = images[tp.label]; + if (srcImg.image.empty()) continue; - int texPatchIdx = rcToTexPatch[pi]; - const AffineCorrection* acPtr = nullptr; - if (texPatchIdx >= 0 && texPatchIdx < (int)patchAffineByTexPatch.size()) { - acPtr = &patchAffineByTexPatch[texPatchIdx]; - } + if (rcToTexPatch[pi] >= correctedPatchImages.size() || correctedPatchImages[rcToTexPatch[pi]].empty()) + continue; - if (!firstParamPrinted && acPtr) { - printf("[DEBUG] pi=%d texPatchIdx=%d kr=%.4f kg=%.4f kb=%.4f br=%.4f bg=%.4f bb=%.4f\n", - pi, texPatchIdx, acPtr->kr, acPtr->kg, acPtr->kb, acPtr->br, acPtr->bg, acPtr->bb); - firstParamPrinted = true; - } + // cv::Mat correctedPatch = srcImg.image(tp.rect).clone(); + cv::Mat correctedPatch = correctedPatchImages[rcToTexPatch[pi]].clone(); // 局部拷贝 - // 从原图裁出 ROI - cv::Mat patchImage = srcImg.image(tp.rect).clone(); + // 加上严格检查 + static int cnt = 0; + if (cnt < 5) { + DEBUG_EXTRA("Patch %d: rcToTexPatch=%d, correctedPatch empty=%d, size=(%dx%d), type=%d", + cnt, rcToTexPatch[pi], correctedPatch.empty(), + correctedPatch.cols, correctedPatch.rows, correctedPatch.type()); + + if (!correctedPatch.empty()) { + char nm[256]; sprintf(nm, "debug_corrected_patch_%d.png", cnt); + cv::imwrite(nm, correctedPatch); + } + cnt++; + } - // // 保存 ROI 诊断图 - // if (pi == 0) { - // cv::imwrite("/home/algo/Documents/openMVS/data/546893/out.546893/debug_patch0_roi.png", patchImage); - // printf("[DIAG] patchImage size=%dx%d, nonZero=%d\n", - // patchImage.cols, patchImage.rows, - // cv::countNonZero(patchImage.reshape(1))); - // } + if (correctedPatch.empty()) continue; const RCPatch& rp = rcPatches[pi]; for (FIndex vfID : rp.faces) { @@ -16237,260 +15943,64 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( const VirtualFaceGeometry& geom = m_virtualFaceGeometries[vfID]; if (!geom.isValid) continue; - // 保持原来的包围盒外扩逻辑不变 - int pad = 4; - int minX = std::max(0, (int)floor(geom.uvBounds.ptMin.x() * textureSize) - pad); - int maxX = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.x() * textureSize) + pad); - int minY = std::max(0, (int)floor(geom.uvBounds.ptMin.y() * textureSize) - pad); - int maxY = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.y() * textureSize) + pad); - + 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; - - // ★ 防止 patch 过大导致 remap 卡死 - if (patchW > textureSize/2 || patchH > textureSize/2) { - printf("[WARN] Skipping oversized patch at pi=%d: %dx%d\n", pi, patchW, patchH); - continue; - } - cv::Mat mapX(patchH, patchW, CV_32FC1), mapY(patchH, patchW, CV_32FC1); - - // ★ 策略1:先尝试求逆(假设 H 是 image→atlas) - cv::Mat H_atlas2img; - cv::invert(geom.homography, H_atlas2img); - const float* H = H_atlas2img.ptr(); - - bool allOutOfRange = true; - - for (int y = minY; y <= maxY; ++y) { + const float* H = geom.homography.ptr(); + 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(y-minY, x-minX) = -1; - mapY.at(y-minY, x-minX) = -1; - continue; - } - - // 映射到原图坐标 - float srcX = (H[0]*u + H[1]*v + H[2])/w; - float srcY = (H[3]*u + H[4]*v + H[5])/w; - - // 减去 rect 偏移得到 ROI 内坐标 - srcX -= tp.rect.x; - srcY -= tp.rect.y; - - mapX.at(y-minY, x-minX) = srcX; - mapY.at(y-minY, x-minX) = srcY; - - if (srcX >= 0 && srcX < patchImage.cols && - srcY >= 0 && srcY < patchImage.rows) { - allOutOfRange = false; - } - - // 诊断:第一个 patch 的第一个像素 - if (pi == 0 && vfID == rp.faces[0] && y == minY && x == minX) { - printf("[DIAG] INVERT: uv=(%.4f, %.4f) -> src=(%.2f, %.2f) rect=(%d,%d,%d,%d) w=%.6f\n", - u, v, srcX, srcY, tp.rect.x, tp.rect.y, tp.rect.width, tp.rect.height); - printf("[DIAG] H_inv=[%.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f]\n", - H[0], H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]); + mapY.at(y-minY, x-minX) = -1; continue; } + mapX.at(y-minY, x-minX) = (H[0]*u + H[1]*v + H[2])/w - tp.rect.x; + mapY.at(y-minY, x-minX) = (H[3]*u + H[4]*v + H[5])/w - tp.rect.y; } - } - - // ★ 如果求逆策略全部越界,尝试直接用 H(不逆) - if (allOutOfRange) { - // printf("[DIAG] Invert strategy failed for pi=%d, trying direct H...\n", pi); - - const float* H_direct = geom.homography.ptr(); - allOutOfRange = true; - - 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_direct[6]*u + H_direct[7]*v + H_direct[8]; - if (std::abs(w) < 1e-12f) { - mapX.at(y-minY, x-minX) = -1; - mapY.at(y-minY, x-minX) = -1; - continue; - } - - float srcX = (H_direct[0]*u + H_direct[1]*v + H_direct[2])/w; - float srcY = (H_direct[3]*u + H_direct[4]*v + H_direct[5])/w; - - // 不减去 rect(假设 H 已经在 ROI 坐标系) - // srcX -= tp.rect.x; - // srcY -= tp.rect.y; - - mapX.at(y-minY, x-minX) = srcX; - mapY.at(y-minY, x-minX) = srcY; - - if (srcX >= 0 && srcX < patchImage.cols && - srcY >= 0 && srcY < patchImage.rows) { - allOutOfRange = false; - } - - if (pi == 0 && vfID == rp.faces[0] && y == minY && x == minX) { - printf("[DIAG] DIRECT: uv=(%.4f, %.4f) -> src=(%.2f, %.2f) w=%.6f\n", - u, v, srcX, srcY, w); - printf("[DIAG] H_direct=[%.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f]\n", - H_direct[0], H_direct[1], H_direct[2], - H_direct[3], H_direct[4], H_direct[5], - H_direct[6], H_direct[7], H_direct[8]); - } - } - } - } - - // 如果两种策略都失败,跳过这个虚拟面 - if (allOutOfRange) { - // printf("[WARN] Both mapping strategies failed for pi=%d, vfID=%u\n", pi, vfID); - continue; - } cv::Mat remapped; - cv::remap(patchImage, remapped, mapX, mapY, - cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); + cv::remap(correctedPatch, remapped, mapX, mapY, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); - // // 保存 remapped 诊断图 - // if (pi == 0 && !savedDebugRemapped) { - // cv::imwrite("/home/algo/Documents/openMVS/data/546893/out.546893/debug_patch0_remapped.png", remapped); - // savedDebugRemapped = true; - // printf("[DIAG] Saved debug_patch0_remapped.png, nonZero=%d\n", - // cv::countNonZero(remapped.reshape(1))); - // } - - // 写入 Atlas:【临时关闭仿射校正,解决偏蓝/反色】 - for (int y = 0; y < patchH; ++y) { - for (int x = 0; x < patchW; ++x) { - cv::Vec3b color = remapped.at(y, x); - // 严格跳过黑色(背景/无效区),防止橙色污染 - if (color[0] == 0 && color[1] == 0 && color[2] == 0) continue; - - int atlasX = x + minX, atlasY = y + minY; - if (atlasX < 0 || atlasX >= textureSize || atlasY < 0 || atlasY >= textureSize) continue; - - // ★ 强制原样写入(正确图的效果) - localAtlas.at(atlasY, atlasX) = color; - - //* - // ★ 若后续需恢复仿射,请先打印参数确认: - // printf("ac: kb=%.2f kg=%.2f kr=%.2f bb=%.2f bg=%.2f br=%.2f\n", ac.kb, ac.kg, ac.kr, ac.bb, ac.bg, ac.br); - // 注意:OpenCV 是 BGR,确保 k/bb 对应 BGR 而非 RGB - if (acPtr) { - const AffineCorrection& ac = *acPtr; - cv::Vec3f c(color[0]/255.f, color[1]/255.f, color[2]/255.f); - c[0] = std::max(0.f, std::min(1.f, c[0] * ac.kb + ac.bb)); - c[1] = std::max(0.f, std::min(1.f, c[1] * ac.kg + ac.bg)); - c[2] = std::max(0.f, std::min(1.f, c[2] * ac.kr + ac.br)); - localAtlas.at(atlasY, atlasX) = cv::Vec3b( - (uchar)(c[0]*255.f+0.5f), (uchar)(c[1]*255.f+0.5f), (uchar)(c[2]*255.f+0.5f) - ); - } else { - localAtlas.at(atlasY, atlasX) = color; + for (int y = 0; y < patchH; ++y) + { + for (int x = 0; x < patchW; ++x) { + cv::Vec3b color = remapped.at(y, x); + if (color[0] == 0 && color[1] == 0 && color[2] == 0) 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]}; + 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 + // static int dbgCount = 0; + // char dbgName[256]; + // sprintf(dbgName, "debug_atlas_after_gsl4_%d.png", dbgCount++); + // cv::imwrite(dbgName, atlas); + // DEBUG_EXTRA("Exported %s", dbgName); } + atlas = localAtlas; } + DEBUG_EXTRA("Re-rasterization done."); - // 将 localAtlas 合并到 atlas - for (int y = 0; y < textureSize; ++y) - for (int x = 0; x < textureSize; ++x) - if (localAtlas.at(y, x) != cv::Vec3b(0,0,0)) - atlas.at(y, x) = localAtlas.at(y, x); + correctedPatchImages.clear(); - // ========== 写入空白三角形到文件 ========== - if (false) - { - // 收集所有没有被任何 rcPatch 覆盖的面片 - std::set coveredFaces; - for (size_t pi = 0; pi < rcPatches.size(); ++pi) { - if (rcToTexPatch[pi] == NO_ID) continue; - const TexturePatch& tp = texturePatches[rcToTexPatch[pi]]; - for (FIndex fid : tp.faces) { - coveredFaces.insert(fid); - } - } - - // 找出未被覆盖的面片(空白三角形) - std::vector emptyFaces; - for (FIndex fid = 0; fid < (FIndex)scene.mesh.faces.size(); ++fid) { - if (coveredFaces.find(fid) == coveredFaces.end()) { - emptyFaces.push_back(fid); - } - } - - // 写入文件 - if (!emptyFaces.empty()) { - std::string filename = "/home/algo/Documents/openMVS/data/546893/out.546893/mesh_empty_color_triangles.txt"; - std::ofstream out(filename); - if (out.is_open()) { - for (FIndex fid : emptyFaces) { - out << fid << "\n"; - } - out.close(); - DEBUG_EXTRA("Written %zu empty color triangles to %s", emptyFaces.size(), filename.c_str()); - } else { - DEBUG_EXTRA("Failed to open %s for writing empty triangles", filename.c_str()); - } - } else { - DEBUG_EXTRA("No empty color triangles found"); - } - } - - DEBUG_EXTRA("Re-rasterization done (auto-detect H direction + ROI + affine correction)."); - - // ★★★★★ 在这里插入接缝模糊 ★★★★★ - { - // 找到所有接缝位置的像素(相邻 patch 交界处) - cv::Mat seamMask = cv::Mat::zeros(textureSize, textureSize, CV_8UC1); - - // 遍历 atlas,标记接缝像素 - for (int y = 1; y < textureSize - 1; ++y) { - for (int x = 1; x < textureSize - 1; ++x) { - cv::Vec3b center = atlas.at(y, x); - if (center == cv::Vec3b(0,0,0)) continue; - - // 检查 4 邻域是否有显著颜色差异 - for (int dy = -1; dy <= 1; ++dy) { - for (int dx = -1; dx <= 1; ++dx) { - if (dx == 0 && dy == 0) continue; - cv::Vec3b neighbor = atlas.at(y+dy, x+dx); - if (neighbor == cv::Vec3b(0,0,0)) continue; - - // 计算色差 - float diff = std::abs(center[0] - neighbor[0]) + - std::abs(center[1] - neighbor[1]) + - std::abs(center[2] - neighbor[2]); - if (diff > 80) { // 色差阈值(稍微提高避免误判) - seamMask.at(y, x) = 255; - break; - } - } - } - } - } - - // 对标记为接缝的像素做高斯模糊 - cv::Mat blurred; - cv::GaussianBlur(atlas, blurred, cv::Size(3, 3), 0.5); - - // 只替换接缝像素 - for (int y = 0; y < textureSize; ++y) { - for (int x = 0; x < textureSize; ++x) { - if (seamMask.at(y, x)) { - atlas.at(y, x) = blurred.at(y, x); - } - } - } - - DEBUG_EXTRA("Seam blur applied: masked %d pixels", cv::countNonZero(seamMask)); - } - // ★★★★★ 接缝模糊结束 ★★★★★ - correctedPatchImages.clear(); + // ★ 不再恢复原始 images —— 避免 cv::Mat 引用计数混乱导致 munmap_chunk + // atlas 已经生成,images 后续随 MeshTexture 正常析构即可 // ========== 4. 清理成员变量 ========== texturePatches.clear(); @@ -16499,9 +16009,10 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( labelsInvalid.Release(); seamEdges.clear(); seamVertices.clear(); + // faceTexcoords.Release(); - Mesh::TexCoordArr dummyTexcoords; - faceTexcoords.Swap(dummyTexcoords); + Mesh::TexCoordArr dummyTexcoords; + faceTexcoords.Swap(dummyTexcoords); // 如果 DynArray 有 Swap 方法 DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: ALL DONE."); } @@ -16736,11 +16247,6 @@ bool MeshTexture::RasterizeVirtualFaces( DEBUG_EXTRA("RC-style Rasterization Engine (simplified + photometric): Starting..."); TD_TIMER_START(); - // ★ 新增:计算 Lab 统计(如果还没算过) - if (m_labStats.empty()) { - ComputeLabStats(); - } - if (virtualFaceMap.empty() || virtualFaceViews.size() != virtualFaceMap.size()) return false; @@ -16796,15 +16302,10 @@ bool MeshTexture::RasterizeVirtualFaces( float currentScore = virtualFaceViewWeights[i].empty() ? -1.0f : virtualFaceViewWeights[i][0]; - const float invSize = 1.0f / textureSize; - - // 保持原来的包围盒外扩逻辑不变 - int pad = 4; - int minX = std::max(0, (int)floor(geom.uvBounds.ptMin.x() * textureSize) - pad); - int maxX = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.x() * textureSize) + pad); - int minY = std::max(0, (int)floor(geom.uvBounds.ptMin.y() * textureSize) - pad); - int maxY = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.y() * textureSize) + pad); - + 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; @@ -16819,19 +16320,11 @@ bool MeshTexture::RasterizeVirtualFaces( const float* H = geom.homography.ptr(); for (int y = minY; y <= maxY; ++y) { for (int x = minX; x <= maxX; ++x) { - - float u = (float)x * invSize; - float v = (float)y * invSize; - - // ★ 简单外扩映射(保持 H,仅 UV 微移) - float w = H[6]*u + H[7]*v + H[8]; - if (std::abs(w) < 1e-12f) { - mapX.at(y-minY,x-minX) = -1; - mapY.at(y-minY,x-minX) = -1; - continue; - } - mapX.at(y-minY,x-minX) = (H[0]*u + H[1]*v + H[2])/w; - mapY.at(y-minY,x-minX) = (H[3]*u + H[4]*v + H[5])/w; + float u = (float)x / (float)textureSize, v = (float)y / (float)textureSize; + float w = H[6]*u + H[7]*v + H[8]; + if (std::abs(w) < 1e-12f) { mapX.at(y-minY,x-minX)=-1; mapY.at(y-minY,x-minX)=-1; continue; } + mapX.at(y-minY,x-minX) = (H[0]*u+H[1]*v+H[2])/w; + mapY.at(y-minY,x-minX) = (H[3]*u+H[4]*v+H[5])/w; } } cv::Mat patch; @@ -16859,33 +16352,41 @@ bool MeshTexture::RasterizeVirtualFaces( } #endif - // 写入 atlas(最高分胜出) - #pragma omp critical (atlas_write) - { - for (int y = 0; y < patchH; ++y) { - for (int x = 0; x < patchW; ++x) { - cv::Vec3b color = patch.at(y, x); + // 写入 atlas(最高分胜出) + #pragma omp critical (atlas_write) + { + for (int y = 0; y < patchH; ++y) { + for (int x = 0; x < patchW; ++x) { + cv::Vec3b color = patch.at(y, x); - if (color[0] == 0 && color[1] == 0 && color[2] == 0) continue; + if (color[0] == 0 && color[1] == 0 && color[2] == 0) continue; - int atlasX = x + minX, atlasY = y + minY; - if (atlasX<0||atlasX>=textureSize||atlasY<0||atlasY>=textureSize) continue; - size_t idx = atlasY * textureSize + atlasX; + int atlasX = x + minX, atlasY = y + minY; + if (atlasX<0||atlasX>=textureSize||atlasY<0||atlasY>=textureSize) continue; + size_t idx = atlasY * textureSize + atlasX; - if (currentScore > m_texelScores[idx].score) { - // ★ 直接写入原始颜色,不做迁移 + // ★ 已删除错误放在这里的 m_texelPatchID.assign(...) + + if (currentScore > m_texelScores[idx].score) { + color[0] = cv::saturate_cast(std::min(255.0f, color[0] * 1.0f)); + color[1] = cv::saturate_cast(std::min(255.0f, color[1] * 1.0f)); + color[2] = cv::saturate_cast(std::min(255.0f, color[2] * 1.0f)); + + // 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 = color; // 直接用 patch 的颜色 + 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; - } - } - } - } + m_texelScores[idx].score = currentScore; + m_texelScores[idx].viewID = viewID; + m_texelPatchID[idx] = i; // ★ 记录这个像素属于 patch i + } + } + } + } } VERBOSE("[Raster] Step 3: rasterization done, building RC patches..."); @@ -16927,9 +16428,9 @@ bool MeshTexture::RasterizeVirtualFaces( if (x < 0 || x >= textureSize || y < 0 || y >= textureSize) continue; const Pixel8U& px = atlas(y, x); if (px[0]==0 && px[1]==0 && px[2]==0) continue; - patchAvgColor[i][0] += px[0]; + patchAvgColor[i][0] += px[2]; patchAvgColor[i][1] += px[1]; - patchAvgColor[i][2] += px[2]; + patchAvgColor[i][2] += px[0]; pixelCount[i]++; } } @@ -16945,8 +16446,8 @@ bool MeshTexture::RasterizeVirtualFaces( NP, NP - std::count(pixelCount.begin(), pixelCount.end(), 0)); - for (int iter = 0; iter < 3; ++iter) { - // for (int iter = 0; iter < 1; ++iter) { + // for (int iter = 0; iter < 3; ++iter) { + for (int iter = 0; iter < 1; ++iter) { AlignPatchColors(atlas, m_texelPatchID, textureSize); } @@ -18202,56 +17703,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( DEBUG_EXTRA("Selecting best views for %zu virtual faces", virtualFaceMap.size()); - // ==================================================================== - // 优化1:提前禁用视图筛选(不变,但移到前面) - // ==================================================================== - std::unordered_set disabledViews; - { - struct ViewDigitPair { IIndex idx; int digit; }; - std::vector viewDigits; - - for (IIndex idx = 0; idx < (IIndex)images.size(); ++idx) { - const Image& img = images[idx]; - if (!img.IsValid()) continue; - - std::string path = img.name; - size_t lastSlash = path.find_last_of("/\\"); - size_t start = (lastSlash == std::string::npos) ? 0 : lastSlash + 1; - size_t lastDot = path.find_last_of('.'); - if (lastDot == std::string::npos) lastDot = path.size(); - std::string strName = path.substr(start, lastDot - start); - - int digit = -1; - size_t lastUnderscore = strName.find_last_of('_'); - if (lastUnderscore != std::string::npos && lastUnderscore > 0) { - for (size_t pos = lastUnderscore - 1; ; --pos) { - if (std::isdigit(strName[pos])) { - digit = strName[pos] - '0'; - break; - } - if (pos == 0) break; - } - } - if (digit >= 0) viewDigits.push_back({idx, digit}); - } - - int maxDigit = -1; - for (const auto& vd : viewDigits) - if (vd.digit > maxDigit) maxDigit = vd.digit; - - for (const auto& vd : viewDigits) { - if (vd.digit == maxDigit) { - disabledViews.insert(vd.idx); - DEBUG_EXTRA("[DisabledView] idx=%d name=%s digit=%d", - vd.idx, images[vd.idx].name.c_str(), vd.digit); - } - } - DEBUG_EXTRA("[DisabledView] %zu views disabled", disabledViews.size()); - } - - // ==================================================================== - // 优化2:初始化 + 构建 faceNeighbors - // ==================================================================== + // ---------- 初始化 ---------- faceViews.resize(virtualFaceMap.size()); faceViewWeights.resize(virtualFaceMap.size()); for (auto& v : faceViews) v.clear(); @@ -18259,13 +17711,26 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( if (faceNeighbors.empty()) { DEBUG_EXTRA("faceNeighbors empty, running ComputePureFaceVisibility first"); - if (!ComputePureFaceVisibility(fOutlierThreshold, nIgnoreMaskLabel, views)) + if (!ComputePureFaceVisibility(fOutlierThreshold, nIgnoreMaskLabel, views)) { return false; + } } - if (scene.mesh.faceFaces.empty()) - scene.mesh.ListIncidenteFaceFaces(); + std::vector faceToView(scene.mesh.faces.size(), NO_ID); + std::vector faceScores(scene.mesh.faces.size(), -FLT_MAX); + if (scene.mesh.faceFaces.empty()) { + scene.mesh.ListIncidenteFaceFaces(); + } + + VERBOSE("5[AfterVis] faceFaces.empty=%d faceFaces.size=%d faceFaces[0]={%u,%u,%u}", + (int)scene.mesh.faceFaces.empty(), + (int)scene.mesh.faceFaces.size(), + scene.mesh.faceFaces.empty() ? 0xFFFFFFFF : scene.mesh.faceFaces[0][0], + scene.mesh.faceFaces.empty() ? 0xFFFFFFFF : scene.mesh.faceFaces[0][1], + scene.mesh.faceFaces.empty() ? 0xFFFFFFFF : scene.mesh.faceFaces[0][2]); + + // ★★★ 从 faceFaces 构建 faceNeighbors(virtual face 邻接)★★★ if (faceNeighbors.empty()) { faceNeighbors.resize(virtualFaceMap.size()); for (size_t fid = 0; fid < virtualFaceMap.size(); ++fid) { @@ -18275,56 +17740,20 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( FIndex nb = ff[k]; if (nb == NO_ID || nb >= (FIndex)virtualFaceMap.size()) continue; bool exists = false; - for (IIndex existing : faceNeighbors[fid]) + for (IIndex existing : faceNeighbors[fid]) { if ((FIndex)existing == nb) { exists = true; break; } + } if (!exists) faceNeighbors[fid].push_back((IIndex)nb); } } + DEBUG_EXTRA("[SelectBest] faceNeighbors built: size=%zu, avg=%f", + faceNeighbors.size(), + faceNeighbors.size() > 0 ? + (double)std::accumulate(faceNeighbors.begin(), faceNeighbors.end(), 0ull, + [](size_t s, const auto& v) { return s + v.size(); }) / faceNeighbors.size() : 0); } - std::vector faceToView(scene.mesh.faces.size(), NO_ID); - std::vector faceScores(scene.mesh.faces.size(), -FLT_MAX); - - // ==================================================================== - // 优化3:预计算所有面的中心和法线(避免重复计算) - // ==================================================================== - std::vector precomputedCenters(scene.mesh.faces.size()); - std::vector precomputedNormals(scene.mesh.faces.size()); - for (size_t fid = 0; fid < scene.mesh.faces.size(); ++fid) { - const Mesh::Face& face = scene.mesh.faces[fid]; - const Point3f& v0 = scene.mesh.vertices[face[0]]; - const Point3f& v1 = scene.mesh.vertices[face[1]]; - const Point3f& v2 = scene.mesh.vertices[face[2]]; - precomputedCenters[fid] = (v0 + v1 + v2) / 3.0f; - - Point3f normal = scene.mesh.faceNormals[fid]; - float norm = std::sqrt(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z); - if (norm > FLT_EPSILON) { - normal.x /= norm; normal.y /= norm; normal.z /= norm; - } else { - normal = Point3f(0, 0, 1); - } - precomputedNormals[fid] = normal; - } - - // ==================================================================== - // 优化4:预计算每个候选视图的投影参数(避免重复创建Camera对象) - // ==================================================================== - struct ViewCache { - bool valid; - float s_normal_cache; // 这里存不下,因为依赖面法线,所以只存图像指针 - const Image* img_ptr; - }; - std::vector viewCache(images.size()); - for (IIndex vid = 0; vid < (IIndex)images.size(); ++vid) { - const Image& img = images[vid]; - viewCache[vid].valid = img.IsValid() && !img.image.empty() && img.image.cols >= 2 && img.image.rows >= 2; - viewCache[vid].img_ptr = &img; - } - - // ==================================================================== - // 优化5:主循环 - 减少 SampleFaceCenterColor 调用次数 - // ==================================================================== + // ---------- 1. 初始视图分配 ---------- size_t emptyCandidateViews = 0; size_t fallbackByCenterFace = 0; size_t successVF = 0; @@ -18336,25 +17765,12 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( FIndex faceID = vf.faces[0]; if (faceID >= faceNeighbors.size()) continue; - // --- 收集候选视图 --- std::unordered_set candidateViews; for (FIndex fid : vf.faces) { if (fid >= faceNeighbors.size()) continue; for (IIndex vid : faceNeighbors[fid]) { - if ((views.empty() || views.FindFirst(vid) != NO_ID) && - !viewCache[vid].valid) continue; - if (disabledViews.count(vid) == 0) + if (views.empty() || views.FindFirst(vid) != NO_ID) { candidateViews.insert(vid); - } - } - - if (candidateViews.empty()) { - for (FIndex fid : vf.faces) { - if (fid >= faceNeighbors.size()) continue; - for (IIndex vid : faceNeighbors[fid]) { - if ((views.empty() || views.FindFirst(vid) != NO_ID) && - viewCache[vid].valid) - candidateViews.insert(vid); } } } @@ -18363,7 +17779,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( ++emptyCandidateViews; IIndex forcedView = (!views.empty()) ? views[0] : (!images.empty()) ? 0 : NO_ID; - if (forcedView != NO_ID && forcedView < viewCache.size() && viewCache[forcedView].valid) { + if (forcedView != NO_ID) { faceViews[i].push_back(forcedView); faceViewWeights[i].push_back(1.0f); for (FIndex fid : vf.faces) { @@ -18377,96 +17793,112 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( continue; } - // --- 使用预计算的中心和法线 --- - const Point3f& faceCenter = precomputedCenters[faceID]; - const Point3f& faceNormal = precomputedNormals[faceID]; + const Mesh::Face& face = scene.mesh.faces[faceID]; + const Point3f& v0 = scene.mesh.vertices[face[0]]; + const Point3f& v1 = scene.mesh.vertices[face[1]]; + const Point3f& v2 = scene.mesh.vertices[face[2]]; + Point3f faceCenter = (v0 + v1 + v2) / 3.0f; - // ★ 优化:先计算所有候选视图的几何得分,选出 Top-K,再算颜色 - struct GeoScore { - IIndex vid; - float score; - }; - std::vector geoResults; - geoResults.reserve(candidateViews.size()); + Point3f faceNormal = scene.mesh.faceNormals[faceID]; + float norm = std::sqrt(faceNormal.x * faceNormal.x + + faceNormal.y * faceNormal.y + + faceNormal.z * faceNormal.z); + if (norm > FLT_EPSILON) { + faceNormal.x /= norm; faceNormal.y /= norm; faceNormal.z /= norm; + } else { + faceNormal = Point3f(0, 0, 1); + } + + IIndex bestView = NO_ID; + float bestScore = -FLT_MAX; for (IIndex vid : candidateViews) { if (vid >= (IIndex)images.size()) continue; - const Image& img = *viewCache[vid].img_ptr; + + const Image& img = images[vid]; + if (img.image.empty() || img.image.cols < 2 || img.image.rows < 2) + continue; + const Camera& cam = img.camera; float s_normal = ComputeViewNormalScore(faceNormal, cam, faceCenter); if (s_normal <= 0.0f) continue; - const Mesh::Face& face = scene.mesh.faces[faceID]; float s_resolution = ComputeResolutionScore( - cam, scene.mesh.vertices[face[0]], scene.mesh.vertices[face[1]], - scene.mesh.vertices[face[2]], img.image.cols, img.image.rows); + cam, v0, v1, v2, img.image.cols, img.image.rows); - Point2f p0 = cam.ProjectPoint(Point3d(scene.mesh.vertices[face[0]])); - Point2f p1 = cam.ProjectPoint(Point3d(scene.mesh.vertices[face[1]])); - Point2f p2 = cam.ProjectPoint(Point3d(scene.mesh.vertices[face[2]])); - float s_occlusion = ComputeOcclusionPenalty(p0, p1, p2, img.image.cols, img.image.rows, 8); + Point2f p0 = cam.ProjectPoint(Point3d(v0)); + Point2f p1 = cam.ProjectPoint(Point3d(v1)); + Point2f p2 = cam.ProjectPoint(Point3d(v2)); + float s_occlusion = ComputeOcclusionPenalty( + p0, p1, p2, img.image.cols, img.image.rows, 8); - float score = 3.0f * s_normal + 0.5f * s_resolution - 0.1f * s_occlusion; - geoResults.push_back({vid, score}); - } - - if (geoResults.empty()) continue; - - // ★ 排序,只取 Top-5 算颜色代价 - std::sort(geoResults.begin(), geoResults.end(), - [](const GeoScore& a, const GeoScore& b) { return a.score > b.score; }); - - const size_t TOP_K = 5; - size_t numToCheck = std::min(TOP_K, geoResults.size()); - - IIndex bestView = NO_ID; - float bestScore = -FLT_MAX; - - for (size_t gi = 0; gi < numToCheck; ++gi) { - IIndex vid = geoResults[gi].vid; - float rawScore = geoResults[gi].score; + float rawScore = 3.0f * s_normal + + 0.5f * s_resolution + - 0.1f * s_occlusion; float score = rawScore; - // --- 颜色代价(只对 Top-5 计算)--- - const float lambda = 15.0f; // 从8降到3 - cv::Vec3f candidateColor = SampleFaceCenterColor(vid, faceCenter); - if (candidateColor[0] >= 0) { - std::vector neighborColors; - if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) { - const Mesh::FaceFaces& topoNeighbors = scene.mesh.faceFaces[faceID]; - for (int k = 0; k < 3; ++k) { - FIndex nb = topoNeighbors[k]; - if (nb == NO_ID || nb >= (FIndex)faceToView.size()) continue; + // ========== 颜色一致性代价(局部采样版)========== + { + const float lambda = 0.5f; // 从 100 降到 0.5 - IIndex nbView = faceToView[nb]; - if (nbView == NO_ID) { - if (nb < (FIndex)faceNeighbors.size() && !faceNeighbors[nb].empty()) - nbView = faceNeighbors[nb][0]; + // 采样当前候选视图下面中心的颜色 + cv::Vec3f candidateColor = SampleFaceCenterColor(vid, faceCenter); + if (candidateColor[0] < 0) { + // 投影失败,跳过颜色代价但不 return + // 保持 score 不变 + } else { + // 收集邻居颜色:已分配的面,采样邻居面中心在邻居视图下的颜色 + std::vector neighborColors; + if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) { + const Mesh::FaceFaces& topoNeighbors = scene.mesh.faceFaces[faceID]; + for (int k = 0; k < 3; ++k) { + FIndex nb = topoNeighbors[k]; + if (nb == NO_ID || nb >= (FIndex)faceToView.size()) continue; + + IIndex nbView = faceToView[nb]; + if (nbView == NO_ID) { + if (nb < (FIndex)faceNeighbors.size() && !faceNeighbors[nb].empty()) { + nbView = faceNeighbors[nb][0]; + } + } + if (nbView == NO_ID || nbView >= (IIndex)images.size()) continue; + + // 计算邻居面中心 + const Mesh::Face& nbFace = scene.mesh.faces[nb]; + Point3f nbCenter = (scene.mesh.vertices[nbFace[0]] + + scene.mesh.vertices[nbFace[1]] + + scene.mesh.vertices[nbFace[2]]) / 3.0f; + + cv::Vec3f nbColor = SampleFaceCenterColor(nbView, nbCenter); + if (nbColor[0] >= 0) { + neighborColors.push_back(nbColor); + } } - if (nbView == NO_ID || nbView >= (IIndex)images.size()) continue; + } - cv::Vec3f nbColor = SampleFaceCenterColor(nbView, precomputedCenters[nb]); - if (nbColor[0] >= 0) - neighborColors.push_back(nbColor); + float colorDiff = 0.0f; + if (!neighborColors.empty()) { + for (const auto& nc : neighborColors) { + cv::Vec3f d = candidateColor - nc; + colorDiff += std::sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2]); + } + colorDiff = colorDiff / neighborColors.size(); + colorDiff = std::min(colorDiff, 1.0f); + + score -= lambda * colorDiff; } } - if (!neighborColors.empty()) { - float colorDiff = 0.0f; - for (const auto& nc : neighborColors) { - cv::Vec3f d = candidateColor - nc; - colorDiff += std::sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2]); - } - colorDiff = colorDiff / neighborColors.size(); - colorDiff = std::min(colorDiff, 1.0f); - - // ★ 加入阈值,小色差不惩罚 - const float thresh = 0.05f; - if (colorDiff > thresh) - score -= lambda * (colorDiff - thresh); + // ---- 调试日志(前20个面 + 每5万面)---- + static int g_dbg = 0; + g_dbg++; + if (g_dbg <= 20 || faceID % 50000 == 0) { + VERBOSE("[Score] face=%d view=%d rawScore=%.3f finalScore=%.3f", + faceID, vid, rawScore, score); } } + // ========== 颜色代价结束 ========== if (score > bestScore) { bestScore = score; @@ -18487,10 +17919,8 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( } } - // ==================================================================== - // 优化6:一致性传播(减少轮数,改为加权投票) - // ==================================================================== - const int PROPAGATION_ITER = 3; // 从6降到3 + // ---------- 2. 改进的 Patch 一致性传播 ---------- + const int PROPAGATION_ITER = 6; for (int iter = 0; iter < PROPAGATION_ITER; ++iter) { std::vector newFaceToView = faceToView; std::vector newFaceScores = faceScores; @@ -18502,49 +17932,66 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[fid]; float currentScore = faceScores[fid]; - if (currentScore > 0.90f) continue; - - // ★ 加权投票:按分数加权 - std::map weightedVote; - weightedVote[faceToView[fid]] = currentScore; + if (currentScore > 0.95f) continue; + std::unordered_map vote; + vote[faceToView[fid]] = 1; for (int k = 0; k < 3; ++k) { FIndex nb = neighbors[k]; if (nb == NO_ID) continue; - if (nb < (FIndex)faceToView.size() && faceToView[nb] != NO_ID) - weightedVote[faceToView[nb]] += faceScores[nb]; + if (nb < (FIndex)faceToView.size() && faceToView[nb] != NO_ID) { + vote[faceToView[nb]]++; + } } - auto best = std::max_element(weightedVote.begin(), weightedVote.end(), - [](const std::pair& a, const std::pair& b) { - return a.second < b.second; }); + IIndex majorityView = faceToView[fid]; + int maxVote = 0; + for (const auto& v : vote) { + if (v.second > maxVote) { + maxVote = v.second; + majorityView = v.first; + } + } - // ★ 需要显著更好才切换 - if (best->first != faceToView[fid] && best->second > currentScore * 1.5f) { - newFaceToView[fid] = best->first; - newFaceScores[fid] = best->second / 4.0f; // 除以邻居数 + if (maxVote >= 2 && majorityView != faceToView[fid]) { + float majorityAvgScore = 0.0f; + int count = 0; + for (int k = 0; k < 3; ++k) { + FIndex nb = neighbors[k]; + if (nb == NO_ID) continue; + if (nb < (FIndex)faceToView.size() && faceToView[nb] == majorityView) { + majorityAvgScore += faceScores[nb]; + count++; + } + } + if (count > 0) majorityAvgScore /= count; + + if (majorityAvgScore > currentScore - 0.5f) { + newFaceToView[fid] = majorityView; + newFaceScores[fid] = majorityAvgScore; + } } } faceToView.swap(newFaceToView); faceScores.swap(newFaceScores); } - // ==================================================================== - // 优化7:写回 + 后处理(减少轮数) - // ==================================================================== + // ---------- 3. 写回 ---------- for (size_t i = 0; i < virtualFaceMap.size(); ++i) { const VirtualFace& vf = virtualFaceMap[i]; if (vf.faces.empty() || faceViews[i].empty()) continue; FIndex firstFace = vf.faces[0]; if (firstFace < faceToView.size() && faceToView[firstFace] != NO_ID) { - faceViews[i] = {faceToView[firstFace]}; - faceViewWeights[i] = {faceScores[firstFace]}; + IIndex consistentView = faceToView[firstFace]; + float consistentScore = faceScores[firstFace]; + faceViews[i] = {consistentView}; + faceViewWeights[i] = {consistentScore}; } } - // 后处理:1轮足够 - for (int iter = 0; iter < 1; ++iter) { + // 后处理:再跑几轮简单投票 + for (int iter = 0; iter < 3; ++iter) { auto newFaceToView = faceToView; for (size_t i = 0; i < faceToView.size(); ++i) { if (faceToView[i] == NO_ID) continue;