From c5dc504b1f6c8a8d0e023bfe885e0704d19862d3 Mon Sep 17 00:00:00 2001 From: hesuicong Date: Thu, 24 Sep 2026 11:04:31 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=9B=E4=B8=80=E6=AD=A5=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 294 +++++++++++++++++++++++++++++++++++++- 1 file changed, 289 insertions(+), 5 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 669c29e..fa5665f 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -584,6 +584,16 @@ 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(); @@ -718,6 +728,25 @@ 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; @@ -16606,6 +16635,17 @@ 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. 逐三角形光栅化 @@ -16705,7 +16745,19 @@ bool MeshTexture::RasterizeVirtualFaces( 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; + } + + 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; @@ -16861,11 +16913,32 @@ bool MeshTexture::RasterizeVirtualFaces( cv::Vec3b original = smoothSrc.at(y, x); - // 混合:50% 原始 + 50% 邻居平均 + // 计算到本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] * 0.5f + avg[0] * 0.5f), - cv::saturate_cast(original[1] * 0.5f + avg[1] * 0.5f), - cv::saturate_cast(original[2] * 0.5f + avg[2] * 0.5f) + 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++; } @@ -19156,6 +19229,69 @@ 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. 核心:计算单应矩阵 / 仿射矩阵 @@ -19265,6 +19401,154 @@ 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,