diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 58c66df..47e3af6 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -18043,65 +18043,57 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( (int)faceNeighbors.empty()); DEBUG_EXTRA("Selecting best views for %zu virtual faces", virtualFaceMap.size()); - - // ---------- 准备禁用视图列表(禁用最后一个单独数字 = 最大值的【所有】相机) ---------- - 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; + // ==================================================================== + // 优化1:提前禁用视图筛选(不变,但移到前面) + // ==================================================================== + std::unordered_set disabledViews; + { + struct ViewDigitPair { IIndex idx; int digit; }; + std::vector viewDigits; - // 提取文件名(不含路径和扩展名) - 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); + for (IIndex idx = 0; idx < (IIndex)images.size(); ++idx) { + const Image& img = images[idx]; + if (!img.IsValid()) continue; - // 提取最后一个 _ 前面的最后一个数字字符 - 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; - } - } + 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); - if (digit >= 0) { - viewDigits.push_back({idx, digit}); - } - } + 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}); + } - // ★ 找到最大的 digit 值 - int maxDigit = -1; - for (const auto& vd : viewDigits) { - if (vd.digit > maxDigit) maxDigit = vd.digit; - } + int maxDigit = -1; + for (const auto& vd : viewDigits) + if (vd.digit > maxDigit) maxDigit = vd.digit; - // ★ 禁用【所有】digit == maxDigit 的视图(不再只禁前 N 个) - for (const auto& vd : viewDigits) { - if (vd.digit == maxDigit) { - disabledViews.insert(vd.idx); - DEBUG_EXTRA("[DisabledView] idx=%d name=%s digit=%d (maxDigit=%d)", - vd.idx, images[vd.idx].name.c_str(), vd.digit, maxDigit); - } - } - DEBUG_EXTRA("[DisabledView] %zu views disabled (all with digit=%d)", - disabledViews.size(), maxDigit); - } + 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(); @@ -18109,26 +18101,13 @@ 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; - } } - std::vector faceToView(scene.mesh.faces.size(), NO_ID); - std::vector faceScores(scene.mesh.faces.size(), -FLT_MAX); - - if (scene.mesh.faceFaces.empty()) { + 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) { @@ -18138,20 +18117,56 @@ 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); } - // ---------- 1. 初始视图分配 ---------- + 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 调用次数 + // ==================================================================== size_t emptyCandidateViews = 0; size_t fallbackByCenterFace = 0; size_t successVF = 0; @@ -18163,38 +18178,34 @@ 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) { - // ★ 排除禁用视图(优先选择小数字的视图) - if (disabledViews.count(vid) == 0) { - candidateViews.insert(vid); - } - } - } - } + // --- 收集候选视图 --- + 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) + 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) { - candidateViews.insert(vid); - } - } - } - // DEBUG_EXTRA("[Fallback] pi=%zu: allowed disabled views, got %zu candidates", - // i, candidateViews.size()); - } + 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); + } + } + } if (candidateViews.empty()) { ++emptyCandidateViews; IIndex forcedView = (!views.empty()) ? views[0] : (!images.empty()) ? 0 : NO_ID; - if (forcedView != NO_ID) { + if (forcedView != NO_ID && forcedView < viewCache.size() && viewCache[forcedView].valid) { faceViews[i].push_back(forcedView); faceViewWeights[i].push_back(1.0f); for (FIndex fid : vf.faces) { @@ -18208,112 +18219,96 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( continue; } - 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; + // --- 使用预计算的中心和法线 --- + const Point3f& faceCenter = precomputedCenters[faceID]; + const Point3f& faceNormal = precomputedNormals[faceID]; - 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; + // ★ 优化:先计算所有候选视图的几何得分,选出 Top-K,再算颜色 + struct GeoScore { + IIndex vid; + float score; + }; + std::vector geoResults; + geoResults.reserve(candidateViews.size()); for (IIndex vid : candidateViews) { if (vid >= (IIndex)images.size()) continue; - - const Image& img = images[vid]; - if (img.image.empty() || img.image.cols < 2 || img.image.rows < 2) - continue; - + const Image& img = *viewCache[vid].img_ptr; 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, v0, v1, v2, img.image.cols, img.image.rows); + cam, scene.mesh.vertices[face[0]], scene.mesh.vertices[face[1]], + scene.mesh.vertices[face[2]], img.image.cols, img.image.rows); - 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); + 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); - float rawScore = 3.0f * s_normal - + 0.5f * s_resolution - - 0.1f * s_occlusion; + 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 score = rawScore; - // ========== 颜色一致性代价(局部采样版)========== - { - const float lambda = 8.0f; // 从 100 降到 0.5 + // --- 颜色代价(只对 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; - // 采样当前候选视图下面中心的颜色 - 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); - } + 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; - 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; + cv::Vec3f nbColor = SampleFaceCenterColor(nbView, precomputedCenters[nb]); + 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); + 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); } } - // ========== 颜色代价结束 ========== if (score > bestScore) { bestScore = score; @@ -18334,8 +18329,10 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( } } - // ---------- 2. 改进的 Patch 一致性传播 ---------- - const int PROPAGATION_ITER = 6; + // ==================================================================== + // 优化6:一致性传播(减少轮数,改为加权投票) + // ==================================================================== + const int PROPAGATION_ITER = 3; // 从6降到3 for (int iter = 0; iter < PROPAGATION_ITER; ++iter) { std::vector newFaceToView = faceToView; std::vector newFaceScores = faceScores; @@ -18347,66 +18344,49 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[fid]; float currentScore = faceScores[fid]; - if (currentScore > 0.95f) continue; + if (currentScore > 0.90f) continue; + + // ★ 加权投票:按分数加权 + std::map weightedVote; + weightedVote[faceToView[fid]] = currentScore; - 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]]++; - } + if (nb < (FIndex)faceToView.size() && faceToView[nb] != NO_ID) + weightedVote[faceToView[nb]] += faceScores[nb]; } - IIndex majorityView = faceToView[fid]; - int maxVote = 0; - for (const auto& v : vote) { - if (v.second > maxVote) { - maxVote = v.second; - majorityView = v.first; - } - } + auto best = std::max_element(weightedVote.begin(), weightedVote.end(), + [](const std::pair& a, const std::pair& b) { + return a.second < b.second; }); - 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; - } + // ★ 需要显著更好才切换 + if (best->first != faceToView[fid] && best->second > currentScore * 1.5f) { + newFaceToView[fid] = best->first; + newFaceScores[fid] = best->second / 4.0f; // 除以邻居数 } } faceToView.swap(newFaceToView); faceScores.swap(newFaceScores); } - // ---------- 3. 写回 ---------- + // ==================================================================== + // 优化7:写回 + 后处理(减少轮数) + // ==================================================================== 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) { - IIndex consistentView = faceToView[firstFace]; - float consistentScore = faceScores[firstFace]; - faceViews[i] = {consistentView}; - faceViewWeights[i] = {consistentScore}; + faceViews[i] = {faceToView[firstFace]}; + faceViewWeights[i] = {faceScores[firstFace]}; } } - // 后处理:再跑几轮简单投票 - for (int iter = 0; iter < 3; ++iter) { + // 后处理:1轮足够 + for (int iter = 0; iter < 1; ++iter) { auto newFaceToView = faceToView; for (size_t i = 0; i < faceToView.size(); ++i) { if (faceToView[i] == NO_ID) continue;