From 990fff69c5c5a5e5d931954b1a555ed68cd3a0e8 Mon Sep 17 00:00:00 2001 From: hesuicong Date: Sun, 27 Sep 2026 11:45:49 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9B=AE=E5=89=8D=E6=95=88=E6=9E=9C=E6=AF=94?= =?UTF-8?q?=E8=BE=83=E5=A5=BD=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 861 +++++++------------------------------- 1 file changed, 149 insertions(+), 712 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index fa5665f..58c66df 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -584,16 +584,6 @@ struct MeshTexture { */ std::vector m_virtualFaceGeometries; - // ============================================================ - // 面片级全局增益(Global Gain)数据结构 - // ============================================================ - struct FaceGainInfo { - float gain; // 亮度增益 - bool valid; // 是否有效(有相邻视角对比) - }; - // 面片级增益数组(索引对应 face ID) - std::vector m_faceGains; - public: MeshTexture(Scene& _scene, unsigned _nResolutionLevel=0, unsigned _nMinResolution=640); ~MeshTexture(); @@ -728,25 +718,6 @@ public: const VirtualFace& vf, IIndex viewID, VirtualFaceGeometry& geom); - // ============================================================ - // ComputeGlobalGain - 计算面片级全局增益 - // 功能:对每个面片,比较其主视角与备选视角的平均亮度, - // 计算增益使两者对齐。仅调整明暗,不改变色相。 - // 参数:virtualFaceViews 即光栅化循环中的 virtualFaceViews - // ============================================================ - void ComputeGlobalGain( - const Scene& scene, - const Mesh& mesh, - const std::vector>& virtualFaceViews); - float SampleLuminanceBilinear(const cv::Mat& img, float x, float y); - // ============================================================ - // GetFaceMeanLuminance - 获取面片在指定视角下的平均亮度 - // ============================================================ - float GetFaceMeanLuminance( - const Scene& scene, - const Mesh& mesh, - int faceID, - int viewID) const; std::vector> faceViews; std::vector> faceViewWeights; @@ -16240,7 +16211,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( // ★ 强制原样写入(正确图的效果) 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 @@ -16256,7 +16227,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( } else { localAtlas.at(atlasY, atlasX) = color; } - //*/ + */ } } } @@ -16342,7 +16313,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( // 对标记为接缝的像素做高斯模糊 cv::Mat blurred; - cv::GaussianBlur(atlas, blurred, cv::Size(5, 5), 1.5); // 更大核 + cv::GaussianBlur(atlas, blurred, cv::Size(3, 3), 0.5); // 只替换接缝像素 for (int y = 0; y < textureSize; ++y) { @@ -16635,17 +16606,6 @@ bool MeshTexture::RasterizeVirtualFaces( if (!m_gainsEstimated) EstimateGlobalPhotometricCorrection(); - // ===== 面片级全局增益(Global Gain)===== - if (m_faceGains.empty()) { - VERBOSE("Computing global gains for %zu faces...", virtualFaceViews.size()); - ComputeGlobalGain(scene, scene.mesh, virtualFaceViews); - VERBOSE("Global gains computed: %zu valid, %zu invalid", - std::count_if(m_faceGains.begin(), m_faceGains.end(), - [](const FaceGainInfo& fg) { return fg.valid; }), - std::count_if(m_faceGains.begin(), m_faceGains.end(), - [](const FaceGainInfo& fg) { return !fg.valid; })); - } - VERBOSE("[Raster] Step 2: starting patch loop, total faces=%zu", virtualFaceMap.size()); // 3. 逐三角形光栅化 @@ -16714,7 +16674,7 @@ bool MeshTexture::RasterizeVirtualFaces( // GSL4 会统一做全局颜色优化,这里乘了会导致双重校正 // 如果后续不走 GSL4 流程,可以把下面 false 改为 true 恢复 // ============================================================ -#if 1 // ← 改为 1 可临时恢复光栅化时乘 gain +#if 0 // ← 改为 1 可临时恢复光栅化时乘 gain // 应用光度校正 const cv::Vec3f& gain = (viewID < (IIndex)m_imageGains.size()) ? m_imageGains[viewID] : cv::Vec3f(1,1,1); const bool applyGain = (gain != cv::Vec3f(1,1,1)); @@ -16729,43 +16689,45 @@ bool MeshTexture::RasterizeVirtualFaces( } } } -#endif +#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) { - // ★ 应用面片级全局增益(仅调整亮度,不改变色相) - float gain = 1.0f; - if (i >= 0 && i < (int)m_faceGains.size() && m_faceGains[i].valid) { - gain = m_faceGains[i].gain; + // ★ 已删除错误放在这里的 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[0] = color[2]; + // pixel[1] = color[1]; + // pixel[2] = color[0]; + + pixel = color; // 原样写入,不再交换 } - - if (gain != 1.0f) { - color[0] = cv::saturate_cast(color[0] * gain); // B - color[1] = cv::saturate_cast(color[1] * gain); // G - color[2] = cv::saturate_cast(color[2] * gain); // R - } - - // 写入图集 - atlas.at(atlasY, atlasX) = color; - 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..."); @@ -16855,98 +16817,6 @@ bool MeshTexture::RasterizeVirtualFaces( // AlignPatchColors(atlas, m_texelPatchID, textureSize); // FeatherTextureSeams(atlas, m_texelPatchID, textureSize, 3); - // ★★★ 后处理:接缝平滑 ★★★ - { - Image8U3 smoothSrc = atlas.clone(); - int seamPixelCount = 0; - - for (int y = 1; y < textureSize - 1; ++y) { - for (int x = 1; x < textureSize - 1; ++x) { - size_t centerIdx = y * textureSize + x; - int centerPatch = m_texelPatchID[centerIdx]; - if (centerPatch == NO_ID) continue; - - // 检查 4 邻域是否有不同 patch - bool isSeam = false; - int neighbors[4] = { - m_texelPatchID[(y-1) * textureSize + x], - m_texelPatchID[(y+1) * textureSize + x], - m_texelPatchID[y * textureSize + (x-1)], - m_texelPatchID[y * textureSize + (x+1)] - }; - - for (int k = 0; k < 4; ++k) { - if (neighbors[k] != NO_ID && neighbors[k] != centerPatch) { - isSeam = true; - break; - } - } - - if (isSeam) { - cv::Vec3i sum(0, 0, 0); - int count = 0; - - for (int dy = -1; dy <= 1; ++dy) { - for (int dx = -1; dx <= 1; ++dx) { - int nx = x + dx, ny = y + dy; - if (nx < 0 || nx >= textureSize || ny < 0 || ny >= textureSize) continue; - - size_t nidx = ny * textureSize + nx; - int nPatch = m_texelPatchID[nidx]; - - if (nPatch != NO_ID && nPatch != centerPatch) { - cv::Vec3b nc = smoothSrc.at(ny, nx); - sum[0] += nc[0]; - sum[1] += nc[1]; - sum[2] += nc[2]; - count++; - } - } - } - - if (count > 0) { - cv::Vec3b avg( - cv::saturate_cast(sum[0] / count), - cv::saturate_cast(sum[1] / count), - cv::saturate_cast(sum[2] / count) - ); - - cv::Vec3b original = smoothSrc.at(y, x); - - // 计算到本patch内部像素的最小欧氏距离 - float minDistSq = 1e10f; - for (int dy = -5; dy <= 5; ++dy) { - for (int dx = -5; dx <= 5; ++dx) { - int nx = x + dx, ny = y + dy; - if (nx < 0 || nx >= textureSize || ny < 0 || ny >= textureSize) continue; - - size_t nidx = ny * textureSize + nx; - if (m_texelPatchID[nidx] == centerPatch) { - float distSq = (float)(dx*dx + dy*dy); - minDistSq = std::min(minDistSq, distSq); - } - } - } - - float minDist = std::sqrt(minDistSq); - float sigma = 2.0f; - float weight = std::exp(-minDist * minDist / (2.0f * sigma * sigma)); - weight = 1.0f - weight; - weight = std::max(0.1f, std::min(0.5f, weight)); - - // 应用混合 - atlas.at(y, x) = cv::Vec3b( - cv::saturate_cast(original[0] * (1-weight) + avg[0] * weight), - cv::saturate_cast(original[1] * (1-weight) + avg[1] * weight), - cv::saturate_cast(original[2] * (1-weight) + avg[2] * weight) - ); - seamPixelCount++; - } - } - } - } - DEBUG_EXTRA("Post-process seam smoothing done: %d seam pixels processed.", seamPixelCount); - } return true; } @@ -18143,57 +18013,20 @@ int MeshTexture::ComputeOptimalTextureSizeAdaptive( cv::Vec3f MeshTexture::SampleFaceCenterColor(IIndex vid, const Point3f& faceCenter) { const Image& img = images[vid]; if (img.image.empty()) return cv::Vec3f(-1, -1, -1); - Point2f proj = img.camera.ProjectPoint(Point3d(faceCenter)); - int cx = (int)(proj.x + 0.5f), cy = (int)(proj.y + 0.5f); - - // ★★★ 3x3 区域平均采样 ★★★ - cv::Vec3f sum(0, 0, 0); - int validCount = 0; - - for (int dy = -1; dy <= 1; ++dy) { - for (int dx = -1; dx <= 1; ++dx) { - int sx = cx + dx; - int sy = cy + dy; - - // 边界检查 - if (sx < 0 || sx >= img.image.cols || sy < 0 || sy >= img.image.rows) - continue; - - const cv::Vec3b& c = img.image.at(sy, sx); - - // 跳过黑色/无效像素(假设纯黑(0,0,0)为无效) - if (c[0] == 0 && c[1] == 0 && c[2] == 0) - continue; - - sum[0] += c[2]; // B→R - sum[1] += c[1]; // G→G - sum[2] += c[0]; // R→B - validCount++; - } - } - - // 如果 3x3 区域内全部无效,退回单点采样 - if (validCount == 0) { - if (cx < 0 || cy < 0 || cx >= img.image.cols || cy >= img.image.rows) - return cv::Vec3f(-1, -1, -1); - const cv::Vec3b& c = img.image.at(cy, cx); - if (c[0] == 0 && c[1] == 0 && c[2] == 0) - return cv::Vec3f(-1, -1, -1); - return cv::Vec3f(c[2]/255.0f, c[1]/255.0f, c[0]/255.0f); - } - - // 返回归一化的平均值 - return cv::Vec3f( - sum[0] / (validCount * 255.0f), - sum[1] / (validCount * 255.0f), - sum[2] / (validCount * 255.0f) - ); + int px = (int)(proj.x + 0.5f), py = (int)(proj.y + 0.5f); + if (px < 0 || py < 0 || px >= img.image.cols || py >= img.image.rows) + return cv::Vec3f(-1, -1, -1); + const cv::Vec3b& c = img.image.at(py, px); + return cv::Vec3f(c[2]/255.0f, c[1]/255.0f, c[0]/255.0f); // BGR→RGB, normalize } // ============================================================ // 5. SelectBestViewsForVirtualFaces(修复版) // ============================================================ +// ============================================================ +// 5. SelectBestViewsForVirtualFaces(局部采样颜色版) +// ============================================================ // 前置条件:MeshTexture 类中需声明并定义 SampleFaceCenterColor: // cv::Vec3f SampleFaceCenterColor(IIndex vid, const Point3f& faceCenter) const; // 实现见下方附录 @@ -18420,80 +18253,67 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( - 0.1f * s_occlusion; float score = rawScore; -// ========== 颜色一致性代价(动态阈值版)========== -{ - // ★ 判断是否为头发区域(法线朝下 + 位置偏高) - bool isHair = (faceNormal.y < -0.3f); // 法线向下,典型头发区域 - // 可根据模型调整:也可加 faceCenter.y > 某个高度阈值 - - // ★ 动态参数:头发区域放宽限制 - float dynamicLambda = isHair ? 60.0f : 120.0f; - float dynamicMaxDiff = isHair ? 40.0f : 18.0f; // 头发放宽到 40 + // ========== 颜色一致性代价(局部采样版)========== + { + const float lambda = 8.0f; // 从 100 降到 0.5 - // 采样当前候选视图下面中心的颜色(3x3 平均已在 SampleFaceCenterColor 中实现) - cv::Vec3f candidateColor = SampleFaceCenterColor(vid, faceCenter); - if (candidateColor[0] < 0) { - // 投影失败,跳过颜色代价 - } 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; + // 采样当前候选视图下面中心的颜色 + 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]; + 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); + } + } + } + + 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 (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); + // ---- 调试日志(前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); } } - } - - float colorDiff = 0.0f; - if (!neighborColors.empty()) { - // ★ 使用中位数代替平均,抗高光干扰 - std::vector diffs; - for (const auto& nc : neighborColors) { - cv::Vec3f d = candidateColor - nc; - diffs.push_back(std::sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2])); - } - std::sort(diffs.begin(), diffs.end()); - colorDiff = diffs[diffs.size() / 2]; // 中位数 - - // ★ 平滑惩罚:平方衰减,小差异不罚,大差异渐重 - float normDiff = std::min(colorDiff / dynamicMaxDiff, 1.0f); - score -= dynamicLambda * normDiff * normDiff; - - // ★ 极端的才重罚(避免硬截断导致碎片) - if (colorDiff > dynamicMaxDiff * 1.8f) { - score -= 300.0f; - } - } - - // ---- 调试日志 ---- - static int g_dbg = 0; - g_dbg++; - if (g_dbg <= 20 || faceID % 50000 == 0) { - VERBOSE("[Score] face=%d view=%d rawScore=%.3f finalScore=%.3f colorDiff=%.1f isHair=%d", - faceID, vid, rawScore, score, colorDiff, (int)isHair); - } - } -} -// ========== 颜色代价结束 ========== + // ========== 颜色代价结束 ========== if (score > bestScore) { bestScore = score; @@ -18514,64 +18334,62 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( } } - // ---------- 2. 改进的 Patch 一致性传播(削弱版)---------- - const int PROPAGATION_ITER = 3; // 从 6 降为 3,减少过度平滑 - for (int iter = 0; iter < PROPAGATION_ITER; ++iter) { - std::vector newFaceToView = faceToView; - std::vector newFaceScores = faceScores; + // ---------- 2. 改进的 Patch 一致性传播 ---------- + const int PROPAGATION_ITER = 6; + for (int iter = 0; iter < PROPAGATION_ITER; ++iter) { + std::vector newFaceToView = faceToView; + std::vector newFaceScores = faceScores; - for (FIndex fid = 0; fid < (FIndex)faceToView.size(); ++fid) { - if (faceToView[fid] == NO_ID) continue; - if (fid >= (FIndex)scene.mesh.faceFaces.size()) continue; + for (FIndex fid = 0; fid < (FIndex)faceToView.size(); ++fid) { + if (faceToView[fid] == NO_ID) continue; + if (fid >= (FIndex)scene.mesh.faceFaces.size()) continue; - const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[fid]; - float currentScore = faceScores[fid]; + const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[fid]; + float currentScore = faceScores[fid]; - // ★ 提高跳过门槛:高分面片不参与传播 - if (currentScore > 0.85f) continue; // 原 0.95,降低到 0.85 + 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) { - vote[faceToView[nb]]++; - } - } + 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) { + vote[faceToView[nb]]++; + } + } - IIndex majorityView = faceToView[fid]; - int maxVote = 0; - for (const auto& v : vote) { - if (v.second > maxVote) { - maxVote = v.second; - majorityView = v.first; - } - } + IIndex majorityView = faceToView[fid]; + int maxVote = 0; + for (const auto& v : vote) { + if (v.second > maxVote) { + maxVote = v.second; + majorityView = v.first; + } + } - 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 (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.2f && majorityAvgScore > 0.75f) { - newFaceToView[fid] = majorityView; - newFaceScores[fid] = majorityAvgScore; - } - } - } - faceToView.swap(newFaceToView); - faceScores.swap(newFaceScores); - } + if (majorityAvgScore > currentScore - 0.5f) { + newFaceToView[fid] = majorityView; + newFaceScores[fid] = majorityAvgScore; + } + } + } + faceToView.swap(newFaceToView); + faceScores.swap(newFaceScores); + } // ---------- 3. 写回 ---------- for (size_t i = 0; i < virtualFaceMap.size(); ++i) { @@ -18616,176 +18434,6 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( DEBUG_EXTRA("Fallback by center face : %zu", fallbackByCenterFace); DEBUG_EXTRA("================================================="); - // ================================================================ -// ★★★ 连通域强制统一视角(消除色差碎片)★★★ -// ================================================================ -{ - TD_TIMER_START(); - - const FIndex NF = (FIndex)faceToView.size(); - if (NF == 0) return true; - - // 1. 构建面片邻接关系(只考虑有效面片) - std::vector> adjList(NF); - for (FIndex fid = 0; fid < NF; ++fid) { - if (faceToView[fid] == NO_ID) continue; - if (fid >= (FIndex)scene.mesh.faceFaces.size()) continue; - - const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[fid]; - for (int k = 0; k < 3; ++k) { - FIndex nb = neighbors[k]; - if (nb == NO_ID || nb >= NF) continue; - if (faceToView[nb] == NO_ID) continue; - adjList[fid].push_back(nb); - } - } - - // 2. 连通域标记(BFS) - std::vector component(NF, -1); - int numComponents = 0; - - for (FIndex fid = 0; fid < NF; ++fid) { - if (faceToView[fid] == NO_ID) continue; - if (component[fid] >= 0) continue; - - std::queue q; - q.push(fid); - component[fid] = numComponents; - - while (!q.empty()) { - FIndex cur = q.front(); q.pop(); - for (FIndex nb : adjList[cur]) { - if (component[nb] < 0) { - component[nb] = numComponents; - q.push(nb); - } - } - } - numComponents++; - } - - DEBUG_EXTRA("Connected components: %d components from %d faces", numComponents, NF); - - // 3. 统计每个连通域的视角分布 - struct CompInfo { - std::vector faces; - std::map viewVotes; - std::map viewScoreSum; - std::map viewAvgScore; - }; - std::vector comps(numComponents); - - for (FIndex fid = 0; fid < NF; ++fid) { - if (faceToView[fid] == NO_ID) continue; - int c = component[fid]; - if (c < 0) continue; - - IIndex vid = faceToView[fid]; - float score = faceScores[fid]; - - comps[c].faces.push_back(fid); - comps[c].viewVotes[vid]++; - comps[c].viewScoreSum[vid] += score; - } - - for (int c = 0; c < numComponents; ++c) { - for (auto& vs : comps[c].viewScoreSum) { - IIndex vid = vs.first; - int votes = comps[c].viewVotes[vid]; - comps[c].viewAvgScore[vid] = vs.second / votes; - } - } - - // 4. 每个连通域选最佳视角 - int changedFaces = 0; - int smallComponents = 0; - - for (int c = 0; c < numComponents; ++c) { - auto& comp = comps[c]; - if (comp.faces.empty()) continue; - - // 小连通域合并 - if ((int)comp.faces.size() < 5) { - smallComponents++; - - std::map neighborCompVotes; - for (FIndex fid : comp.faces) { - for (FIndex nb : adjList[fid]) { - int nc = component[nb]; - if (nc >= 0 && nc != c) { - neighborCompVotes[nc]++; - } - } - } - - if (!neighborCompVotes.empty()) { - auto bestNb = std::max_element(neighborCompVotes.begin(), neighborCompVotes.end(), - [](const auto& a, const auto& b) { return a.second < b.second; }); - - int targetComp = bestNb->first; - IIndex targetView = NO_ID; - int maxVotes = 0; - for (const auto& vv : comps[targetComp].viewVotes) { - if (vv.second > maxVotes) { - maxVotes = vv.second; - targetView = vv.first; - } - } - - if (targetView != NO_ID) { - for (FIndex fid : comp.faces) { - if (faceToView[fid] != targetView) { - faceToView[fid] = targetView; - faceScores[fid] = comps[targetComp].viewAvgScore[targetView]; - changedFaces++; - } - } - } - continue; - } - } - - // 正常连通域选视角 - IIndex bestView = NO_ID; - float bestWeightedScore = -FLT_MAX; - int totalFaces = (int)comp.faces.size(); - - for (const auto& vv : comp.viewVotes) { - IIndex vid = vv.first; - int votes = vv.second; - float avgScore = comp.viewAvgScore[vid]; - - float voteRatio = (float)votes / totalFaces; - float normalizedScore = std::max(0.0f, std::min(1.0f, avgScore)); - float weightedScore = 0.6f * voteRatio + 0.4f * normalizedScore; - - if (weightedScore > bestWeightedScore) { - bestWeightedScore = weightedScore; - bestView = vid; - } - } - - if (bestView == NO_ID) continue; - - for (FIndex fid : comp.faces) { - if (faceToView[fid] != bestView) { - faceToView[fid] = bestView; - faceScores[fid] = comp.viewAvgScore[bestView]; - changedFaces++; - } - } - } - - DEBUG_EXTRA("Component unification: %d faces changed, %d small components merged (%s)", - changedFaces, smallComponents, TD_TIMER_GET_FMT().c_str()); - - // ★★★ 注意:不要在这里更新 faceViews/faceViewWeights! - // faceViews 会在视图选择阶段的末尾,由 SelectBestViewsForVirtualFaces 根据 faceToView 重建 - // 如果在这里强行同步,会破坏虚拟面片与视图的映射关系,导致采样坐标错误 - - DEBUG_EXTRA("Unification complete: %d components processed.", numComponents); -} - return true; } @@ -19229,69 +18877,6 @@ float MeshTexture::PointToLineDistance(const Point2f& p, const Point2f& a, const float disty = p.y - projy; return std::sqrt(distx*distx + disty*disty); } -// ============================================================ -// GetFaceMeanLuminance - 获取面片在指定视角下的平均亮度 -// ============================================================ -float MeshTexture::GetFaceMeanLuminance( - const Scene& scene, - const Mesh& mesh, - int faceID, - int viewID) const -{ - if (viewID < 0 || viewID >= (int)scene.images.size()) return 0.0f; - - const Image8U3& image = scene.images[viewID].image; - if (image.empty()) return 0.0f; - - // 获取面片顶点(直接索引) - Point3f v0 = mesh.vertices[faceID * 3]; - Point3f v1 = mesh.vertices[faceID * 3 + 1]; - Point3f v2 = mesh.vertices[faceID * 3 + 2]; - - // 获取相机参数 - const Camera& camera = scene.images[viewID].camera; - - // ★ 修复:显式转为 Point3d(double)再投影 - // 世界坐标 → 相机坐标 - Point3d c0 = camera.TransformPointW2C(Point3d(v0)); - Point3d c1 = camera.TransformPointW2C(Point3d(v1)); - Point3d c2 = camera.TransformPointW2C(Point3d(v2)); - - // 检查是否在相机前方 - if (c0.z <= 0 || c1.z <= 0 || c2.z <= 0) return 0.0f; - - // 相机坐标 → 图像坐标(归一化) - Point2f p0 = camera.TransformPointC2I(c0); - Point2f p1 = camera.TransformPointC2I(c1); - Point2f p2 = camera.TransformPointC2I(c2); - - // 计算面片在图像上的包围盒 - int minX = std::max(0, (int)std::floor(std::min({p0.x, p1.x, p2.x}))); - int maxX = std::min(image.width() - 1, (int)std::ceil(std::max({p0.x, p1.x, p2.x}))); - int minY = std::max(0, (int)std::floor(std::min({p0.y, p1.y, p2.y}))); - int maxY = std::min(image.height() - 1, (int)std::ceil(std::max({p0.y, p1.y, p2.y}))); - - if (minX >= maxX || minY >= maxY) return 0.0f; - - // 采样包围盒内的像素,计算平均亮度 - float totalLum = 0.0f; - int count = 0; - - for (int y = minY; y <= maxY; ++y) { - for (int x = minX; x <= maxX; ++x) { - const Pixel8U& pixel = image(y, x); - // 跳过纯黑(无效区域) - if (pixel.r == 0 && pixel.g == 0 && pixel.b == 0) continue; - - // 计算亮度(BT.709 标准) - float lum = 0.2126f * pixel.r + 0.7152f * pixel.g + 0.0722f * pixel.b; - totalLum += lum; - count++; - } - } - - return (count > 0) ? (totalLum / count) : 0.0f; -} // ============================================================ // 2. 核心:计算单应矩阵 / 仿射矩阵 @@ -19401,154 +18986,6 @@ bool MeshTexture::ComputeHomographyForVirtualFace( return geom.isValid; } -// 辅助函数:双线性插值采样亮度 -float MeshTexture::SampleLuminanceBilinear(const cv::Mat& img, float x, float y) { - int ix = (int)x; - int iy = (int)y; - float fx = x - ix; - float fy = y - iy; - - const uchar* p00 = img.ptr(iy, ix); - const uchar* p10 = img.ptr(iy, ix + 1); - const uchar* p01 = img.ptr(iy + 1, ix); - const uchar* p11 = img.ptr(iy + 1, ix + 1); - - float c00 = 0.299f * p00[2] + 0.587f * p00[1] + 0.114f * p00[0]; - float c10 = 0.299f * p10[2] + 0.587f * p10[1] + 0.114f * p10[0]; - float c01 = 0.299f * p01[2] + 0.587f * p01[1] + 0.114f * p01[0]; - float c11 = 0.299f * p11[2] + 0.587f * p11[1] + 0.114f * p11[0]; - - float c0 = c00 * (1-fx) + c10 * fx; - float c1 = c01 * (1-fx) + c11 * fx; - - return c0 * (1-fy) + c1 * fy; -} - -// ============================================================ -// ComputeGlobalGain - 计算面片级全局增益 -// 功能:对每个面片,比较其主视角与备选视角的平均亮度, -// 计算增益使两者对齐。仅调整明暗,不改变色相。 -// 参数:virtualFaceViews 即光栅化循环中的 virtualFaceViews -// ============================================================ -void MeshTexture::ComputeGlobalGain( - const Scene& scene, - const Mesh& mesh, - const std::vector>& virtualFaceViews) -{ - VERBOSE("[GlobalGain] Starting fast computation for %zu faces...", virtualFaceViews.size()); - - const size_t numFaces = virtualFaceViews.size(); - m_faceGains.resize(numFaces); - - int progressInterval = std::max(1, (int)numFaces / 100); - - #pragma omp parallel for schedule(dynamic, 1000) - for (int faceID = 0; faceID < (int)numFaces; ++faceID) { - if (faceID % progressInterval == 0) { - #pragma omp critical - VERBOSE("[GlobalGain] Progress: %d/%zu (%.1f%%)", - faceID, numFaces, 100.0 * faceID / numFaces); - } - - const auto& views = virtualFaceViews[faceID]; - if (views.size() < 2) { - m_faceGains[faceID].valid = false; - continue; - } - - // 只采样三角形三个顶点和中心共4个点 - float lumMain = 0.0f; - float lumAlt = 0.0f; - int sampleCount = 0; - - // 获取三角形的三个顶点 - const Point3f& v0f = mesh.vertices[faceID * 3]; - const Point3f& v1f = mesh.vertices[faceID * 3 + 1]; - const Point3f& v2f = mesh.vertices[faceID * 3 + 2]; - - // 转为 Point3d(因为 Camera::TransformPointW2C 内部用 double) - Point3d v0(v0f.x, v0f.y, v0f.z); - Point3d v1(v1f.x, v1f.y, v1f.z); - Point3d v2(v2f.x, v2f.y, v2f.z); - - // 采样点:三个顶点 + 中心(都用 Point3d) - Point3d samplePoints[4] = { - v0, v1, v2, - Point3d((v0.x + v1.x + v2.x) / 3.0, - (v0.y + v1.y + v2.y) / 3.0, - (v0.z + v1.z + v2.z) / 3.0) - }; - - for (int s = 0; s < 4; ++s) { - const Point3d& pt = samplePoints[s]; - - // 主视角采样 - int mainViewID = views[0]; - const Camera& mainCam = scene.images[mainViewID].camera; - - Point3d ptCam = mainCam.TransformPointW2C(pt); - if (ptCam.z <= 0) continue; - - Point2f ptImg = mainCam.TransformPointC2I(ptCam); - if (ptImg.x < 0 || ptImg.x >= scene.images[mainViewID].image.cols - 1 || - ptImg.y < 0 || ptImg.y >= scene.images[mainViewID].image.rows - 1) continue; - - // 双线性插值采样主视角亮度 - float lum = SampleLuminanceBilinear(scene.images[mainViewID].image, ptImg.x, ptImg.y); - if (lum < 5.0f) continue; // 跳过纯黑 - - // 备选视角采样(取第一个有效的) - float lumAltSample = 0.0f; - bool foundAlt = false; - for (size_t v = 1; v < views.size(); ++v) { - int altViewID = views[v]; - const Camera& altCam = scene.images[altViewID].camera; - - Point3d altPtCam = altCam.TransformPointW2C(pt); - if (altPtCam.z <= 0) continue; - - Point2f altPtImg = altCam.TransformPointC2I(altPtCam); - if (altPtImg.x < 0 || altPtImg.x >= scene.images[altViewID].image.cols - 1 || - altPtImg.y < 0 || altPtImg.y >= scene.images[altViewID].image.rows - 1) continue; - - lumAltSample = SampleLuminanceBilinear(scene.images[altViewID].image, altPtImg.x, altPtImg.y); - if (lumAltSample > 5.0f) { - foundAlt = true; - break; - } - } - - if (foundAlt) { - lumMain += lum; - lumAlt += lumAltSample; - sampleCount++; - } - } - - if (sampleCount > 0) { - float avgMain = lumMain / sampleCount; - float avgAlt = lumAlt / sampleCount; - - if (avgMain > 1.0f && avgAlt > 1.0f) { - float gain = avgAlt / avgMain; - m_faceGains[faceID].gain = std::max(0.25f, std::min(4.0f, gain)); - m_faceGains[faceID].valid = true; - } else { - m_faceGains[faceID].valid = false; - } - } else { - m_faceGains[faceID].valid = false; - } - } - - int validCount = 0; - for (const auto& fg : m_faceGains) { - if (fg.valid) validCount++; - } - - VERBOSE("[GlobalGain] Done. Valid: %d/%zu", validCount, numFaces); -} - bool MeshTexture::GenerateTextureWithVirtualFacesInternal(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize,