From 4d5f371e99a009b38d990be950090bc462cfa839 Mon Sep 17 00:00:00 2001 From: hesuicong Date: Tue, 13 Oct 2026 09:43:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=86=E8=A7=A3=E9=AB=98=E4=BD=8E=E9=A2=91?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E5=AF=B9=E4=BD=8E=E9=A2=91=E8=9E=8D=E5=90=88?= =?UTF-8?q?=EF=BC=8C=E9=AB=98=E9=A2=91=E5=8D=95=E8=A7=86=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 437 ++++++++++++++++++++++++++++---------- 1 file changed, 328 insertions(+), 109 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index f7253ef..ecbfeb9 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -552,11 +552,14 @@ struct MeshTexture { Point3f center; // 面片中心(世界坐标) Point3f normal; // 面片法线 AABB2f uvBounds; // UV 包围盒 - cv::Mat1f homography; // 3x3 单应矩阵(从 UV 到视图) + cv::Mat1f homography; // 3x3 单应矩阵(从 UV 到视图),主视图 homography bool isValid = false; std::vector neighborFaces; // 邻接面ID std::vector sharedEdgeIdx; // 共享边索引 (0,1,2) + + // ===== 新增:存储所有视图的 homography ===== + std::unordered_map viewHomographies; }; struct SeamBlendParams { @@ -726,6 +729,16 @@ public: std::vector> faceNeighbors; // VirtualFaceGeometryArr m_virtualFaceGeometries; + // 高频视图:每个面一个主视图(用于提取细节) + std::vector faceViewsHighFreq; + + // 低频视图:每个面 2-3 个辅助视图 + 权重(用于颜色融合) + struct LowFreqView { + IIndex viewId; + float weight; + }; + std::vector> faceViewsLowFreq; + inline Point3f NormalizePoint3(Point3f& p) { const float l = std::sqrt( @@ -799,6 +812,9 @@ public: unsigned nTextureSizeMultiple, Pixel8U colEmpty, Mesh::Image8U3Arr& outTextures); + + cv::Mat GetHomographyForView(int virtualFaceIdx, IIndex viewId) const; + void BuildStandardSeamDataFromRCPatches( const VirtualFaceMap& virtualFaceMap, const std::vector>& virtualFaceViews, @@ -828,6 +844,32 @@ public: std::vector patchPixelCount; // ========== 辅助函数 ========== + // 1. 构建高斯金字塔(逐层降采样模糊) + static inline std::vector buildGaussianPyramid(const cv::Mat& img, int levels) { + std::vector pyramid; + pyramid.push_back(img); + for (int i = 1; i < levels; i++) { + cv::Mat blurred, down; + cv::GaussianBlur(pyramid[i-1], blurred, cv::Size(5,5), 1.0); + cv::resize(blurred, down, cv::Size(), 0.5, 0.5); + pyramid.push_back(down); + } + return pyramid; + } + + // 2. 构建拉普拉斯金字塔(每层 = 当前层 - 上一层上采样) + static inline std::vector buildLaplacianPyramid(const cv::Mat& img, int levels) { + auto gp = buildGaussianPyramid(img, levels); + std::vector lp; + for (int i = 0; i < levels - 1; i++) { + cv::Mat up; + cv::resize(gp[i+1], up, gp[i].size()); + lp.push_back(gp[i] - up); // 高频细节 + } + lp.push_back(gp.back()); // 最低频(残差) + return lp; + } + // 双线性采样(适配 SEACAVE::TImage) static inline Pixel8U SampleBilinear(const Image8U3& img, const Point2f& pt) { float x = pt.x * img.width() - 0.5f; @@ -16233,9 +16275,20 @@ void MeshTexture::RunSeamLevelingOnAtlas(Image8U3& atlas, int textureSize) images.pop_back(); } -// ============================================================ -// 3. RC 风格光栅化主函数(含接缝优化) -// ============================================================ +cv::Mat MeshTexture::GetHomographyForView(int virtualFaceIdx, IIndex viewId) const +{ + if (virtualFaceIdx < 0 || virtualFaceIdx >= (int)m_virtualFaceGeometries.size()) + return cv::Mat(); + + const auto& geom = m_virtualFaceGeometries[virtualFaceIdx]; + auto it = geom.viewHomographies.find(viewId); + if (it != geom.viewHomographies.end()) { + return it->second; + } + + return cv::Mat(); +} + bool MeshTexture::RasterizeVirtualFaces( const VirtualFaceMap& virtualFaceMap, const std::vector>& virtualFaceViews, @@ -16259,15 +16312,13 @@ bool MeshTexture::RasterizeVirtualFaces( int textureSize = ComputeOptimalTextureSizeAdaptive(virtualFaceMap, virtualFaceViews, nTextureSizeMultiple); if (textureSize < 1024) textureSize = 1024; if (textureSize > 16384) textureSize = 16384; - textureSize = std::max(textureSize, 4096); // 至少 4096 + textureSize = std::max(textureSize, 4096); // 2. atlas + score outTextures.emplace_back(textureSize, textureSize); Image8U3& atlas = outTextures.back(); atlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); m_texelScores.assign(textureSize * textureSize, TexelScore{}); - - // ★ 修复:m_texelPatchID 只初始化一次,移到循环外面 m_texelPatchID.assign(textureSize * textureSize, NO_ID); VERBOSE("[Raster] Step 1: initializing buffers... textureSize=%d, scores=%zu, patchID=%zu", @@ -16282,7 +16333,15 @@ bool MeshTexture::RasterizeVirtualFaces( VERBOSE("[Raster] Step 2: starting patch loop, total faces=%zu", virtualFaceMap.size()); - // 3. 逐三角形光栅化 + // ========== 新增:预加载所有图像到浮点缓存(加速多次采样)========== + std::vector imagesFloat(images.size()); + for (size_t vi = 0; vi < images.size(); vi++) { + if (!images[vi].image.empty()) { + images[vi].image.convertTo(imagesFloat[vi], CV_32FC3, 1.0f / 255.0f); + } + } + + // 3. 逐三角形光栅化(分频带合成版) #ifdef _USE_OPENMP #pragma omp parallel for schedule(dynamic) #endif @@ -16295,10 +16354,21 @@ bool MeshTexture::RasterizeVirtualFaces( const VirtualFaceGeometry& geom = m_virtualFaceGeometries[i]; if (!geom.isValid || vf.faces.empty() || virtualFaceViews[i].empty()) continue; - IIndex viewID = virtualFaceViews[i][0]; - if (viewID >= (IIndex)images.size()) continue; - const Image& srcImg = images[viewID]; - if (srcImg.image.empty() || srcImg.image.cols < 2 || srcImg.image.rows < 2) continue; + // ========== 分频带视图获取 ========== + IIndex mainView = faceViewsHighFreq[i]; // 高频视图(主视图) + const auto& lowFreqViews = faceViewsLowFreq[i]; // 低频视图列表 + + if (mainView == NO_ID || mainView >= (IIndex)images.size()) continue; + if (images[mainView].image.empty()) continue; + + // 确保低频视图也有效 + bool hasLowFreqViews = false; + for (const auto& lfv : lowFreqViews) { + if (lfv.viewId < (IIndex)images.size() && !images[lfv.viewId].image.empty()) { + hasLowFreqViews = true; + break; + } + } float currentScore = virtualFaceViewWeights[i].empty() ? -1.0f : virtualFaceViewWeights[i][0]; @@ -16309,13 +16379,13 @@ bool MeshTexture::RasterizeVirtualFaces( if (minX > maxX || minY > maxY) continue; int patchW = maxX - minX + 1, patchH = maxY - minY + 1; - // ★ Guard:跳过异常巨大的 patch if (patchW > textureSize || patchH > textureSize || (int64_t)patchW * patchH > (int64_t)textureSize * textureSize / 5) { DEBUG_EXTRA("[Raster] face %d has HUGE rect: %dx%d, skipping", i, patchW, patchH); continue; } + // ========== 构建主视图的映射 ========== cv::Mat mapX(patchH, patchW, CV_32FC1), mapY(patchH, patchW, CV_32FC1); const float* H = geom.homography.ptr(); for (int y = minY; y <= maxY; ++y) { @@ -16327,62 +16397,110 @@ bool MeshTexture::RasterizeVirtualFaces( mapY.at(y-minY,x-minX) = (H[3]*u+H[4]*v+H[5])/w; } } - cv::Mat patch; - cv::remap(srcImg.image, patch, mapX, mapY, cv::INTER_NEAREST, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); -// ============================================================ - // ★ 光度校正:光栅化阶段不再应用 gain - // GSL4 会统一做全局颜色优化,这里乘了会导致双重校正 - // 如果后续不走 GSL4 流程,可以把下面 false 改为 true 恢复 -// ============================================================ -#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)); - if (applyGain) { - for (int y = 0; y < patchH; ++y) { - for (int x = 0; x < patchW; ++x) { - cv::Vec3b& p = patch.at(y, x); - if (p[0]==0 && p[1]==0 && p[2]==0) continue; - p[0] = cv::saturate_cast(p[0]*gain[0]); - p[1] = cv::saturate_cast(p[1]*gain[1]); - p[2] = cv::saturate_cast(p[2]*gain[2]); - } - } - } -#endif + // ========== 从主视图采样(全分辨率)========== + cv::Mat mainPatch; + cv::remap(images[mainView].image, mainPatch, mapX, mapY, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); - // 写入 atlas(最高分胜出) + // ========== 从辅助视图采样(各自用自己的 homography)========== + cv::Mat lowFreqPatch(patchH, patchW, CV_32FC3, cv::Scalar(0,0,0)); + if (hasLowFreqViews) { + float totalWeight = 0; + for (const auto& lfv : lowFreqViews) { + if (lfv.viewId >= (IIndex)images.size() || images[lfv.viewId].image.empty()) continue; + + // ===== 获取该视图自己的 homography ===== + const auto& geom = m_virtualFaceGeometries[i]; + auto it = geom.viewHomographies.find(lfv.viewId); + if (it == geom.viewHomographies.end()) continue; + const cv::Mat& H_aux = it->second; + + // ===== 用该视图的 homography 构建映射 ===== + cv::Mat auxMapX(patchH, patchW, CV_32FC1), auxMapY(patchH, patchW, CV_32FC1); + const float* H = H_aux.ptr(); + for (int y = minY; y <= maxY; ++y) { + for (int x = minX; x <= maxX; ++x) { + 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) { + auxMapX.at(y-minY,x-minX) = -1; + auxMapY.at(y-minY,x-minX) = -1; + continue; + } + auxMapX.at(y-minY,x-minX) = (H[0]*u+H[1]*v+H[2])/w; + auxMapY.at(y-minY,x-minX) = (H[3]*u+H[4]*v+H[5])/w; + } + } + + cv::Mat auxPatch; + cv::remap(images[lfv.viewId].image, auxPatch, auxMapX, auxMapY, + cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); + + cv::Mat auxFloat; + auxPatch.convertTo(auxFloat, CV_32FC3, 1.0f / 255.0f); + + lowFreqPatch += auxFloat * lfv.weight; + totalWeight += lfv.weight; + } + if (totalWeight > 0) { + lowFreqPatch /= totalWeight; + } else { + cv::Mat mainFloat; + mainPatch.convertTo(mainFloat, CV_32FC3, 1.0f / 255.0f); + lowFreqPatch = mainFloat.clone(); + } + } else { + cv::Mat mainFloat; + mainPatch.convertTo(mainFloat, CV_32FC3, 1.0f / 255.0f); + lowFreqPatch = mainFloat.clone(); + } + + // ========== 从主视图提取高频 ========== + // 对主视图做高斯模糊得到低频,然后相减得高频 + cv::Mat mainFloat; + mainPatch.convertTo(mainFloat, CV_32FC3, 1.0f / 255.0f); + + cv::Mat mainLowFreq; + cv::GaussianBlur(mainFloat, mainLowFreq, cv::Size(31, 31), 2.0); // σ=10 + + cv::Mat highFreq = mainFloat - mainLowFreq; // 高频 = 原图 - 低频 + + // ========== 合成:低频用多视图融合,高频用主视图细节 ========== + cv::Mat finalPatch = lowFreqPatch + highFreq; + // cv::Mat finalPatch = lowFreqPatch.clone(); + + // 钳制到 [0, 1] + cv::Mat clampedPatch; + cv::max(finalPatch, 0.0f, clampedPatch); + cv::min(clampedPatch, 1.0f, clampedPatch); + + // 转回 8UC3 + cv::Mat finalPatch8U; + clampedPatch.convertTo(finalPatch8U, CV_8UC3, 255.0f); + + // ========== 写入 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); - + cv::Vec3b color = finalPatch8U.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; size_t idx = atlasY * textureSize + atlasX; - // ★ 已删除错误放在这里的 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]; - } + if (atlasY >= 0 && atlasY < atlas.rows && atlasX >= 0 && atlasX < atlas.cols) { + cv::Vec3b& pixel = atlas.at(atlasY, atlasX); + 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; // ★ 记录这个像素属于 patch i + m_texelScores[idx].viewID = mainView; + m_texelPatchID[idx] = i; } } } @@ -16444,38 +16562,16 @@ bool MeshTexture::RasterizeVirtualFaces( DEBUG_EXTRA("Patch avg color computed: %d patches, %d with pixels", NP, NP - std::count(pixelCount.begin(), pixelCount.end(), 0)); - - - // for (int iter = 0; iter < 3; ++iter) { - for (int iter = 0; iter < 1; ++iter) { - AlignPatchColors(atlas, m_texelPatchID, textureSize); - } + + for (int iter = 0; iter < 1; ++iter) { + AlignPatchColors(atlas, m_texelPatchID, textureSize); + } - // // 5/6/7. 接缝 - // BuildSeamEdgesFromRCPatches(); - // if (!rcSeamEdges.empty()) { - // DEBUG_EXTRA("Before GlobalAlign: rcSeamEdges=%zu, patchAvgColor=%zu", - // rcSeamEdges.size(), patchAvgColor.size()); - // GlobalPatchColorAlignment(atlas, textureSize); - // LocalSeamBlending(atlas, textureSize); - // } + int validPixels = 0; + for (size_t i = 0; i < m_texelPatchID.size(); ++i) + if (m_texelPatchID[i] != NO_ID) validPixels++; + DEBUG_EXTRA("m_texelPatchID: %d valid pixels out of %zu", validPixels, m_texelPatchID.size()); - // if (!seamEdges.empty()) { - // TD_TIMER_START(); - // SeamBlendingFromOriginalImages(atlas); - // DEBUG_EXTRA("Seam blending completed: %zu edges (%s)", seamEdges.size(), TD_TIMER_GET_FMT().c_str()); - // } - - // 在 rasterization 循环结束后、SmoothAtlasPatchBoundaries 之前加: - int validPixels = 0; - for (size_t i = 0; i < m_texelPatchID.size(); ++i) - if (m_texelPatchID[i] != NO_ID) validPixels++; - DEBUG_EXTRA("m_texelPatchID: %d valid pixels out of %zu", validPixels, m_texelPatchID.size()); - - // SmoothAtlasPatchBoundaries(atlas, textureSize); - - // AlignPatchColors(atlas, m_texelPatchID, textureSize); - // FeatherTextureSeams(atlas, m_texelPatchID, textureSize, 3); return true; } @@ -17681,14 +17777,7 @@ cv::Vec3f MeshTexture::SampleFaceCenterColor(IIndex vid, const Point3f& faceCent } // ============================================================ -// 5. SelectBestViewsForVirtualFaces(修复版) -// ============================================================ -// ============================================================ -// 5. SelectBestViewsForVirtualFaces(局部采样颜色版) -// ============================================================ -// 前置条件:MeshTexture 类中需声明并定义 SampleFaceCenterColor: -// cv::Vec3f SampleFaceCenterColor(IIndex vid, const Point3f& faceCenter) const; -// 实现见下方附录 +// 5. SelectBestViewsForVirtualFaces(局部采样颜色版 + Top-K视图) // ============================================================ bool MeshTexture::SelectBestViewsForVirtualFaces( VirtualFaceMap& virtualFaceMap, @@ -17706,8 +17795,11 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( // ---------- 初始化 ---------- faceViews.resize(virtualFaceMap.size()); faceViewWeights.resize(virtualFaceMap.size()); + faceViewsHighFreq.resize(virtualFaceMap.size(), NO_ID); + faceViewsLowFreq.resize(virtualFaceMap.size()); for (auto& v : faceViews) v.clear(); for (auto& w : faceViewWeights) w.clear(); + for (auto& v : faceViewsLowFreq) v.clear(); if (faceNeighbors.empty()) { DEBUG_EXTRA("faceNeighbors empty, running ComputePureFaceVisibility first"); @@ -17753,7 +17845,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( [](size_t s, const auto& v) { return s + v.size(); }) / faceNeighbors.size() : 0); } - // ---------- 1. 初始视图分配 ---------- + // ---------- 1. 初始视图分配(带 Top-K 存储)---------- size_t emptyCandidateViews = 0; size_t fallbackByCenterFace = 0; size_t successVF = 0; @@ -17809,6 +17901,13 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( faceNormal = Point3f(0, 0, 1); } + // ★★★ 存储所有候选视图的分数(用于 Top-K 选取)★★★ + struct ViewScore { + IIndex viewId; + float score; + }; + std::vector allScores; + IIndex bestView = NO_ID; float bestScore = -FLT_MAX; @@ -17840,15 +17939,10 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( // ========== 颜色一致性代价(局部采样版)========== { - const float lambda = 0.5f; // 从 100 降到 0.5 + const float lambda = 0.5f; - // 采样当前候选视图下面中心的颜色 cv::Vec3f candidateColor = SampleFaceCenterColor(vid, faceCenter); - if (candidateColor[0] < 0) { - // 投影失败,跳过颜色代价但不 return - // 保持 score 不变 - } else { - // 收集邻居颜色:已分配的面,采样邻居面中心在邻居视图下的颜色 + 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]; @@ -17864,7 +17958,6 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( } 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]] + @@ -17900,6 +17993,9 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( } // ========== 颜色代价结束 ========== + // ★★★ 存储所有候选视图的分数 ★★★ + allScores.push_back({vid, score}); + if (score > bestScore) { bestScore = score; bestView = vid; @@ -17907,8 +18003,18 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( } if (bestView != NO_ID) { - faceViews[i].push_back(bestView); - faceViewWeights[i].push_back(bestScore); + // ★★★ 改为存储 Top-K 视图(最多 4 个)★★★ + const int TOP_K = 4; + std::sort(allScores.begin(), allScores.end(), + [](const ViewScore& a, const ViewScore& b) { return a.score > b.score; }); + + int numTop = std::min(TOP_K, (int)allScores.size()); + for (int k = 0; k < numTop; ++k) { + faceViews[i].push_back(allScores[k].viewId); + faceViewWeights[i].push_back(allScores[k].score); + } + + // 只把最佳视图写入 faceToView(用于一致性传播) for (FIndex fid : vf.faces) { if (fid < faceToView.size()) { faceToView[fid] = bestView; @@ -17976,7 +18082,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( faceScores.swap(newFaceScores); } - // ---------- 3. 写回 ---------- + // ---------- 3. 写回(只更新 faceToView 中的主视图)---------- for (size_t i = 0; i < virtualFaceMap.size(); ++i) { const VirtualFace& vf = virtualFaceMap[i]; if (vf.faces.empty() || faceViews[i].empty()) continue; @@ -17985,8 +18091,24 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( if (firstFace < faceToView.size() && faceToView[firstFace] != NO_ID) { IIndex consistentView = faceToView[firstFace]; float consistentScore = faceScores[firstFace]; - faceViews[i] = {consistentView}; - faceViewWeights[i] = {consistentScore}; + + // ★★★ 更新 Top-K 中的第一个为主视图 ★★★ + // 检查 consistentView 是否在 faceViews[i] 中 + bool found = false; + for (size_t k = 0; k < faceViews[i].size(); ++k) { + if (faceViews[i][k] == consistentView) { + // 把它移到最前面 + std::swap(faceViews[i][0], faceViews[i][k]); + std::swap(faceViewWeights[i][0], faceViewWeights[i][k]); + found = true; + break; + } + } + if (!found) { + // 如果不在列表中,替换第一个 + faceViews[i][0] = consistentView; + faceViewWeights[i][0] = consistentScore; + } } } @@ -18019,6 +18141,83 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( DEBUG_EXTRA("Fallback by center face : %zu", fallbackByCenterFace); DEBUG_EXTRA("================================================="); + // ========== 分频带视图赋值 ========== + faceViewsHighFreq.resize(virtualFaceMap.size(), NO_ID); + faceViewsLowFreq.resize(virtualFaceMap.size()); + + for (size_t i = 0; i < virtualFaceMap.size(); ++i) { + if (faceViews[i].empty()) { + faceViewsHighFreq[i] = NO_ID; + faceViewsLowFreq[i].clear(); + continue; + } + + // 高频视图 = 主视图(得分最高的视图) + faceViewsHighFreq[i] = faceViews[i][0]; + + // 低频视图 = 辅助视图(从 faceViews 中取第 2-4 名) + faceViewsLowFreq[i].clear(); + if (faceViews[i].size() >= 2) { + // ★★★ 加入颜色相似度过滤 ★★★ + // 先采样主视图的面中心颜色 + FIndex faceID = virtualFaceMap[i].faces[0]; + const Mesh::Face& face = scene.mesh.faces[faceID]; + Point3f faceCenter = (scene.mesh.vertices[face[0]] + + scene.mesh.vertices[face[1]] + + scene.mesh.vertices[face[2]]) / 3.0f; + cv::Vec3f mainColor = SampleFaceCenterColor(faceViewsHighFreq[i], faceCenter); + + const float colorThreshold = 0.15f; // 颜色差异阈值,可调 + + // 最多取 3 个辅助视图 + int numAux = std::min(3, (int)faceViews[i].size() - 1); + for (int j = 0; j < numAux; j++) { + IIndex auxView = faceViews[i][j + 1]; // 第 2、3、4 名 + + // 检查颜色相似度 + cv::Vec3f auxColor = SampleFaceCenterColor(auxView, faceCenter); + if (auxColor[0] >= 0) { + float colorDiff = std::sqrt( + (mainColor[0]-auxColor[0])*(mainColor[0]-auxColor[0]) + + (mainColor[1]-auxColor[1])*(mainColor[1]-auxColor[1]) + + (mainColor[2]-auxColor[2])*(mainColor[2]-auxColor[2])); + + // 颜色差异太大则跳过 + if (colorDiff > colorThreshold) { + continue; + } + } + + // 计算权重:基于得分比例 + float weight = faceViewWeights[i][j + 1]; + if (faceViewWeights[i][0] > 0) { + weight /= faceViewWeights[i][0]; // 相对主视图的权重 + } + weight = std::max(0.1f, std::min(1.0f, weight)); // 钳制到 [0.1, 1.0] + + faceViewsLowFreq[i].push_back({auxView, weight}); + } + + // 归一化权重 + float totalWeight = 0; + for (const auto& lfv : faceViewsLowFreq[i]) { + totalWeight += lfv.weight; + } + if (totalWeight > 0) { + for (auto& lfv : faceViewsLowFreq[i]) { + lfv.weight /= totalWeight; + } + } + } + } + + DEBUG_EXTRA("Frequency-band views assigned: highFreq=%zu, lowFreq=%.2f avg per face", + faceViewsHighFreq.size(), + faceViewsLowFreq.empty() ? 0.0f : + (float)std::accumulate(faceViewsLowFreq.begin(), faceViewsLowFreq.end(), 0, + [](int sum, const auto& v) { return sum + v.size(); }) / faceViewsLowFreq.size()); + // ========== 分频带视图赋值结束 ========== + return true; } @@ -18061,8 +18260,28 @@ bool MeshTexture::ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMa } } - IIndex viewID = faceViews[i][0]; - geom.isValid = ComputeHomographyForVirtualFace(vf, viewID, geom); + // ===== 主视图 homography(已有)===== + IIndex mainViewID = faceViews[i][0]; + geom.isValid = ComputeHomographyForVirtualFace(vf, mainViewID, geom); + if (!geom.isValid) continue; + + // ===== 新增:将主视图 homography 存入 map ===== + geom.viewHomographies[mainViewID] = geom.homography.clone(); + + // ===== 新增:为低频视图计算 homography ===== + if (i < (int)faceViewsLowFreq.size()) { + for (const auto& lfv : faceViewsLowFreq[i]) { + IIndex auxViewID = lfv.viewId; + if (auxViewID == mainViewID) continue; // 主视图已算过 + if (auxViewID >= (IIndex)images.size() || images[auxViewID].image.empty()) continue; + + // 复用已有的 ComputeHomographyForVirtualFace + VirtualFaceGeometry tmpGeom; + if (ComputeHomographyForVirtualFace(vf, auxViewID, tmpGeom)) { + geom.viewHomographies[auxViewID] = tmpGeom.homography.clone(); + } + } + } } return true;