From 73b8d77980df5a8f0cb564cab3bc2303c1f4c2bc Mon Sep 17 00:00:00 2001 From: hesuicong Date: Fri, 7 Aug 2026 17:35:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=A7=86=E5=9B=BE=E7=BB=BC?= =?UTF-8?q?=E5=90=88=E8=AF=84=E5=88=86=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 164 +++++++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index ac7282d..6823adc 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -630,6 +630,17 @@ public: unsigned minCommonCameras, float fOutlierThreshold, float fRatioDataSmoothness, int nIgnoreMaskLabel, const IIndexArr& views); + float ComputeViewNormalScore(const Mesh::Normal& faceNormal, const Camera& camera, const Point3f& faceCenter); + float ComputeResolutionScore( + const Camera& camera, + const Point3f& v0, + const Point3f& v1, + const Point3f& v2, + int imageWidth, + int imageHeight + ); + float ComputeOcclusionPenalty(const cv::Point2f& p0, const cv::Point2f& p1, const cv::Point2f& p2, + int imageWidth, int imageHeight, int border = 8); bool ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMap); @@ -14606,6 +14617,90 @@ bool MeshTexture::GenerateTextureWithVirtualFaces(bool bGlobalSeamLeveling, bool } } +float MeshTexture::ComputeViewNormalScore( + const Mesh::Normal& faceNormal, + const Camera& camera, + const Point3f& faceCenter) +{ + // ✅ 转换为 double 避免 float/double 混用 + Point3d C(camera.C); + Point3d FC(faceCenter); + + // 视线方向:相机 → 面中心(手动计算) + Point3d viewDir( + C.x - FC.x, + C.y - FC.y, + C.z - FC.z + ); + + float vlen = std::sqrt(viewDir.x*viewDir.x + + viewDir.y*viewDir.y + + viewDir.z*viewDir.z); + if (vlen < 1e-8f) return 0.0f; + viewDir /= vlen; + + // 面法线 + Point3f normal = faceNormal; + float nlen = std::sqrt(normal.x*normal.x + + normal.y*normal.y + + normal.z*normal.z); + if (nlen < 1e-8f) return 0.0f; + normal /= nlen; + + float cosTheta = viewDir.x*normal.x + + viewDir.y*normal.y + + viewDir.z*normal.z; + return (cosTheta > 0.0f) ? cosTheta : 0.0f; +} + +float MeshTexture::ComputeResolutionScore( + const Camera& camera, + const Point3f& v0, + const Point3f& v1, + const Point3f& v2, + int imageWidth, + int imageHeight) +{ + Point2d p0 = camera.ProjectPoint(Point3d(v0)); + Point2d p1 = camera.ProjectPoint(Point3d(v1)); + Point2d p2 = camera.ProjectPoint(Point3d(v2)); + + auto inImage = [&](const Point2f& p) { + return p.x >= 0 && p.x < imageWidth && + p.y >= 0 && p.y < imageHeight; + }; + + if (!inImage(p0) || !inImage(p1) || !inImage(p2)) + return 0.0f; + + float area = + std::abs((p1.x - p0.x) * (p2.y - p0.y) - + (p2.x - p0.x) * (p1.y - p0.y)) * 0.5f; + + if (area < 1.0f) return 0.0f; + return std::log2(area + 1.0f); +} + +float MeshTexture::ComputeOcclusionPenalty( + const cv::Point2f& p0, + const cv::Point2f& p1, + const cv::Point2f& p2, + int imageWidth, + int imageHeight, + int border) +{ + auto inside = [&](const cv::Point2f& p) { + return p.x >= border && p.x < imageWidth - border && + p.y >= border && p.y < imageHeight - border; + }; + + int count = 0; + if (!inside(p0)) count++; + if (!inside(p1)) count++; + if (!inside(p2)) count++; + + return float(count) / 3.0f; // [0, 1] +} // ============================================================ // 5. SelectBestViewsForVirtualFaces(带 patch 一致性传播) // ============================================================ @@ -14674,8 +14769,75 @@ 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; + + 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 = *candidateViews.begin(); + // IIndex bestView = *candidateViews.begin(); + IIndex bestView = NO_ID; + float bestScore = -FLT_MAX; + + 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 Camera& cam = img.camera; + + // 1. 正面度评分 + float s_normal = ComputeViewNormalScore(faceNormal, cam, faceCenter); + if (s_normal <= 0.0f) continue; + + // 2. 分辨率评分 + float s_resolution = ComputeResolutionScore( + cam, v0, v1, v2, + img.image.cols, img.image.rows + ); + + // 3. 遮挡惩罚 + 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 // border pixels + ); + + // 综合评分 + float score = 1.0f * s_normal + + 0.7f * s_resolution + - 0.3f * s_occlusion; + + if (score > bestScore) { + bestScore = score; + bestView = vid; + } + } + faceViews[i].push_back(bestView); faceViewWeights[i].push_back(1.0f);