From 5cf68ce6343d05c30b1aaa32bb6679d4e521c940 Mon Sep 17 00:00:00 2001 From: hesuicong Date: Wed, 12 Aug 2026 10:51:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E7=BC=9D=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 501 +++++++++++++++++++++++++++++++++++++- 1 file changed, 498 insertions(+), 3 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 9c2a736..1cbda9b 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -545,6 +545,21 @@ struct MeshTexture { AABB2f uvBounds; // UV 包围盒 cv::Mat1f homography; // 3x3 单应矩阵(从 UV 到视图) bool isValid = false; + + std::vector neighborFaces; // 邻接面ID + std::vector sharedEdgeIdx; // 共享边索引 (0,1,2) + }; + + struct SeamBlendParams { + int blendWidth = 4; // 融合宽度(像素) + float sigma = 1.0f; // 高斯权重参数 + bool enableBlending = true; + }; + + struct SeamInfo { + FIndex faceA, faceB; + int edgeIdxA, edgeIdxB; // 在各自面中的边索引 + std::vector seamPixels; // 接缝上的像素坐标 }; // used to interpolate adjustments color over the whole texture patch @@ -657,8 +672,36 @@ public: return x + 1; } + void FeatherSeams( + const std::vector& seams, + int textureSize, + Image8U3& atlas); + void GenerateSeamMask( + const std::vector& seams, + int textureSize, + cv::Mat& seamMask); // 输出:CV_32FC1,接缝处值 > 0 bool ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMap); - + bool DetectSeamsForBlending( + const VirtualFaceMap& virtualFaceMap, + const std::vector>& virtualFaceViews, + std::vector& seams); + void ExtractSeamPixels( + const SeamInfo& seam, + const VirtualFaceGeometry& geomA, + const VirtualFaceGeometry& geomB, + int textureSize, + std::vector& pixelsA, + std::vector& pixelsB); + void BlendSeamPixels( + const SeamInfo& seam, + const std::vector& pixelsA, + const std::vector& pixelsB, + const VirtualFaceGeometry& geomA, + const VirtualFaceGeometry& geomB, + int textureSize, + Image8U3& atlas); + float PointToLineDistance(const Point2f& p, const Point2f& a, const Point2f& b); + bool ComputeHomographyForVirtualFace( const VirtualFace& vf, IIndex viewID, @@ -14334,6 +14377,9 @@ bool MeshTexture::RasterizeVirtualFaces( { for (int y = 0; y < patchH; ++y) { for (int x = 0; x < patchW; ++x) { + // 在 RasterizeVirtualFaces 的像素写入循环中 + // ✅ 只保留最核心的逻辑,不做任何衰减 + cv::Vec3b color = patch.at(y, x); if (color[0] == 0 && color[1] == 0 && color[2] == 0) continue; @@ -14347,12 +14393,11 @@ bool MeshTexture::RasterizeVirtualFaces( size_t idx = atlasY * textureSize + atlasX; TexelScore& ts = m_texelScores[idx]; - // ✅ 获取当前虚拟面的评分 float currentScore = virtualFaceViewWeights[i].empty() ? -1.0f : virtualFaceViewWeights[i][0]; - // ✅ 只接受更高评分的写入 + // ✅ 正常写入,不衰减,不修改 if (currentScore > ts.score) { atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; @@ -14365,6 +14410,41 @@ bool MeshTexture::RasterizeVirtualFaces( } m_texelScores.clear(); + + DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); + + // ✅ 新增:接缝融合后处理 + if (true) { // 可配置开关 + TD_TIMER_START(); + + // 1. 检测需要融合的接缝 + std::vector seams; + if (!DetectSeamsForBlending(virtualFaceMap, virtualFaceViews, seams)) { + DEBUG_EXTRA("Seam detection failed"); + return true; // 不影响主流程 + } + // // 2. 方向性高斯羽化 + // FeatherSeams(seams, textureSize, atlas); + + // 2. 对每个接缝进行融合 + for (const SeamInfo& seam : seams) { + const VirtualFaceGeometry& geomA = m_virtualFaceGeometries[seam.faceA]; + const VirtualFaceGeometry& geomB = m_virtualFaceGeometries[seam.faceB]; + + // 提取接缝两侧像素 + std::vector pixelsA, pixelsB; + ExtractSeamPixels(seam, geomA, geomB, textureSize, pixelsA, pixelsB); + + if (!pixelsA.empty() || !pixelsB.empty()) { + // 执行融合 + BlendSeamPixels(seam, pixelsA, pixelsB, geomA, geomB, textureSize, atlas); + } + } + + DEBUG_EXTRA("Seam blending completed: %zu seams processed (%s)", + seams.size(), TD_TIMER_GET_FMT().c_str()); + } + DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); return true; } @@ -15094,6 +15174,11 @@ bool MeshTexture::ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMa // ✅ std::vector 用 resize m_virtualFaceGeometries.resize(virtualFaceMap.size()); + // ✅ 确保面邻接关系已计算 + if (scene.mesh.faceFaces.empty()) { + scene.mesh.ListIncidenteFaceFaces(); + } + #ifdef _USE_OPENMP #pragma omp parallel for schedule(dynamic) #endif @@ -15106,6 +15191,21 @@ bool MeshTexture::ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMa continue; } + FIndex faceID = vf.faces[0]; + + // ✅ 存储邻接面信息 + geom.neighborFaces.clear(); + geom.sharedEdgeIdx.clear(); + const Mesh::Face& neighbors = scene.mesh.faceFaces[faceID]; + + for (int edgeIdx = 0; edgeIdx < 3; ++edgeIdx) { + FIndex neighborID = neighbors[edgeIdx]; + if (neighborID != NO_ID) { + geom.neighborFaces.push_back(neighborID); + geom.sharedEdgeIdx.push_back(edgeIdx); + } + } + IIndex viewID = faceViews[i][0]; geom.isValid = ComputeHomographyForVirtualFace(vf, viewID, geom); } @@ -15113,6 +15213,401 @@ bool MeshTexture::ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMa return true; } +void MeshTexture::FeatherSeams( + const std::vector& seams, + int textureSize, + Image8U3& atlas) +{ + // 1. 生成接缝掩码 + cv::Mat seamMask; + GenerateSeamMask(seams, textureSize, seamMask); + + // 2. 创建输出缓冲区 + cv::Mat result(atlas.rows, atlas.cols, CV_8UC3); + atlas.copyTo(result); // 先复制原始数据 + + const int featherRadius = 8; + + // 3. 对每条接缝做方向性高斯模糊 + for (const SeamInfo& seam : seams) { + int eA = seam.edgeIdxA; + const TexCoord& uvA0 = scene.mesh.faceTexcoords[seam.faceA * 3 + eA]; + const TexCoord& uvA1 = scene.mesh.faceTexcoords[seam.faceA * 3 + ((eA + 1) % 3)]; + + // 接缝方向向量(像素空间) + float dx = (uvA1.x - uvA0.x) * textureSize; + float dy = (uvA1.y - uvA0.y) * textureSize; + float len = std::sqrt(dx * dx + dy * dy); + if (len < 1e-6f) continue; + + // 单位方向向量 + float dirX = dx / len; + float dirY = dy / len; + + // 沿接缝采样中心点 + const int samples = 30; + for (int s = 0; s <= samples; ++s) { + float t = (float)s / samples; + int cx = (int)((uvA0.x + t * (uvA1.x - uvA0.x)) * textureSize + 0.5f); + int cy = (int)((uvA0.y + t * (uvA1.y - uvA0.y)) * textureSize + 0.5f); + + // 沿接缝方向做一维高斯模糊 + for (int side = -1; side <= 1; side += 2) { + for (int offset = 1; offset <= featherRadius; ++offset) { + int px = cx + (int)(side * (-dirY) * offset); // 法线方向 + int py = cy + (int)(side * (dirX) * offset); + + if (px < 0 || px >= textureSize || py < 0 || py >= textureSize) + continue; + + // 高斯权重 + float sigma = featherRadius / 2.0f; + float weight = std::exp(-(offset * offset) / (2 * sigma * sigma)); + + // 沿接缝方向采样(一维卷积) + cv::Vec3f sum(0, 0, 0); + float totalW = 0.0f; + + for (int k = -featherRadius; k <= featherRadius; ++k) { + int sx = px + (int)(dirX * k); + int sy = py + (int)(dirY * k); + + if (sx < 0 || sx >= textureSize || sy < 0 || sy >= textureSize) + continue; + + float kw = std::exp(-(k * k) / (2 * sigma * sigma)); + Pixel8U p = atlas(sy, sx); + sum[0] += p.b * kw; + sum[1] += p.g * kw; + sum[2] += p.r * kw; + totalW += kw; + } + + if (totalW > 0.0f) { + // 混合原始颜色和模糊颜色 + Pixel8U orig = atlas(py, px); + float maskVal = seamMask.at(py, px); + + cv::Vec3f blended( + orig.b * (1.0f - maskVal) + (sum[0] / totalW) * maskVal, + orig.g * (1.0f - maskVal) + (sum[1] / totalW) * maskVal, + orig.r * (1.0f - maskVal) + (sum[2] / totalW) * maskVal + ); + + result.at(py, px) = cv::Vec3b( + (uchar)std::min(255.0f, blended[0]), + (uchar)std::min(255.0f, blended[1]), + (uchar)std::min(255.0f, blended[2]) + ); + } + } + } + } + } + + // 4. 写回 + for (int y = 0; y < textureSize; ++y) { + for (int x = 0; x < textureSize; ++x) { + cv::Vec3b c = result.at(y, x); + atlas(y, x) = Pixel8U{c[2], c[1], c[0]}; + } + } +} + +void MeshTexture::GenerateSeamMask( + const std::vector& seams, + int textureSize, + cv::Mat& seamMask) +{ + seamMask = cv::Mat(textureSize, textureSize, CV_32FC1, cv::Scalar(0.0f)); + + const int featherRadius = 8; // 羽化半径(像素),可调 4~16 + + for (const SeamInfo& seam : seams) { + // 获取接缝的两个端点(UV 空间) + int eA = seam.edgeIdxA; + const TexCoord& uvA0 = scene.mesh.faceTexcoords[seam.faceA * 3 + eA]; + const TexCoord& uvA1 = scene.mesh.faceTexcoords[seam.faceA * 3 + ((eA + 1) % 3)]; + + // 沿接缝采样 + const int samples = 30; + for (int s = 0; s <= samples; ++s) { + float t = (float)s / samples; + + // 接缝中心点(UV) + float u = uvA0.x + t * (uvA1.x - uvA0.x); + float v = uvA0.y + t * (uvA1.y - uvA1.y); + + // 转像素坐标 + int cx = (int)(u * textureSize + 0.5f); + int cy = (int)(v * textureSize + 0.5f); + + // 计算接缝方向(用于方向性模糊) + float dx = (uvA1.x - uvA0.x) * textureSize; + float dy = (uvA1.y - uvA0.y) * textureSize; + float len = std::sqrt(dx * dx + dy * dy); + if (len < 1e-6f) continue; + + // 法线方向(垂直接缝) + float nx = -dy / len; + float ny = dx / len; + + // 在法线方向上标记羽化带 + for (int offset = -featherRadius; offset <= featherRadius; ++offset) { + if (offset == 0) continue; + + int px = cx + (int)(nx * offset); + int py = cy + (int)(ny * offset); + + if (px < 0 || px >= textureSize || py < 0 || py >= textureSize) + continue; + + // 高斯权重:距离越远,权重越低 + float gauss = std::exp(-(offset * offset) / (2.0f * (featherRadius / 2.0f) * (featherRadius / 2.0f))); + + float& current = seamMask.at(py, px); + current = std::max(current, gauss); // 取最大值(多条接缝可能重叠) + } + } + } +} + +bool MeshTexture::DetectSeamsForBlending( + const VirtualFaceMap& virtualFaceMap, + const std::vector>& virtualFaceViews, + std::vector& seams) +{ + seams.clear(); + + for (size_t i = 0; i < virtualFaceMap.size(); ++i) { + const VirtualFaceGeometry& geomA = m_virtualFaceGeometries[i]; + if (!geomA.isValid) continue; + + IIndex viewA = virtualFaceViews[i].empty() ? NO_ID : virtualFaceViews[i][0]; + + for (size_t n = 0; n < geomA.neighborFaces.size(); ++n) { + FIndex neighborID = geomA.neighborFaces[n]; + int edgeIdxA = geomA.sharedEdgeIdx[n]; + + // 避免重复处理(只处理一次) + if (neighborID < (FIndex)i) continue; + + const VirtualFaceGeometry& geomB = m_virtualFaceGeometries[neighborID]; + if (!geomB.isValid) continue; + + IIndex viewB = virtualFaceViews[neighborID].empty() ? + NO_ID : virtualFaceViews[neighborID][0]; + + // ✅ 核心判断:不同视图才需要融合 + if (viewA != viewB && viewA != NO_ID && viewB != NO_ID) { + SeamInfo seam; + seam.faceA = i; + seam.faceB = neighborID; + seam.edgeIdxA = edgeIdxA; + + // 找到边在 faceB 中的对应索引 + const Mesh::Face& neighbors = scene.mesh.faceFaces[neighborID]; + for (int e = 0; e < 3; ++e) { + if (neighbors[e] == (FIndex)i) { + seam.edgeIdxB = e; + break; + } + } + + seams.push_back(seam); + } + } + } + + DEBUG_EXTRA("Detected %zu seams requiring blending", seams.size()); + return true; +} +void MeshTexture::ExtractSeamPixels( + const SeamInfo& seam, + const VirtualFaceGeometry& /*geomA*/, + const VirtualFaceGeometry& /*geomB*/, + int textureSize, + std::vector& pixelsA, + std::vector& pixelsB) +{ + pixelsA.clear(); + pixelsB.clear(); + + // ✅ 从 mesh 直接取 UV(唯一正确方式) + FIndex faceIDA = seam.faceA; + int edgeIdxA = seam.edgeIdxA; + const TexCoord& uvA0 = scene.mesh.faceTexcoords[faceIDA * 3 + edgeIdxA]; + const TexCoord& uvA1 = scene.mesh.faceTexcoords[faceIDA * 3 + ((edgeIdxA + 1) % 3)]; + + FIndex faceIDB = seam.faceB; + int edgeIdxB = seam.edgeIdxB; + const TexCoord& uvB0 = scene.mesh.faceTexcoords[faceIDB * 3 + edgeIdxB]; + const TexCoord& uvB1 = scene.mesh.faceTexcoords[faceIDB * 3 + ((edgeIdxB + 1) % 3)]; + + // ✅ 在纹理空间中采样接缝线段 + const int samples = 20; + for (int s = 0; s <= samples; ++s) { + float t = (float)s / samples; + + // 接缝上的点(UV 坐标) + Point2f uvOnSeamA( + uvA0.x + t * (uvA1.x - uvA0.x), + uvA0.y + t * (uvA1.y - uvA0.y) + ); + Point2f uvOnSeamB( + uvB0.x + t * (uvB1.x - uvB0.x), + uvB0.y + t * (uvB1.y - uvB0.y) + ); + + // 转换为纹理像素坐标 + Point2i pixelA( + (int)(uvOnSeamA.x * textureSize + 0.5f), + (int)(uvOnSeamA.y * textureSize + 0.5f) + ); + Point2i pixelB( + (int)(uvOnSeamB.x * textureSize + 0.5f), + (int)(uvOnSeamB.y * textureSize + 0.5f) + ); + + // ✅ 计算接缝方向向量 + Point2f dirA(uvA1.x - uvA0.x, uvA1.y - uvA0.y); + Point2f dirB(uvB1.x - uvB0.x, uvB1.y - uvB0.y); + + // 计算法线(逆时针旋转90度) + Point2f normalA(-dirA.y, dirA.x); + Point2f normalB(-dirB.y, dirB.x); + + // 归一化 + float lenA = std::sqrt(normalA.x*normalA.x + normalA.y*normalA.y); + float lenB = std::sqrt(normalB.x*normalB.x + normalB.y*normalB.y); + + if (lenA > 1e-6f) { + normalA.x /= lenA; normalA.y /= lenA; + } + if (lenB > 1e-6f) { + normalB.x /= lenB; normalB.y /= lenB; + } + + // ✅ 提取两侧像素带 + const int blendWidth = 4; + for (int offset = 1; offset <= blendWidth; ++offset) { + // 面A侧 + Point2f pixelOffsetA( + uvOnSeamA.x + normalA.x * (offset / (float)textureSize), + uvOnSeamA.y + normalA.y * (offset / (float)textureSize) + ); + Point2i pA( + (int)(pixelOffsetA.x * textureSize + 0.5f), + (int)(pixelOffsetA.y * textureSize + 0.5f) + ); + if (pA.x >= 0 && pA.x < textureSize && pA.y >= 0 && pA.y < textureSize) { + pixelsA.push_back(pA); + } + + // 面B侧 + Point2f pixelOffsetB( + uvOnSeamB.x - normalB.x * (offset / (float)textureSize), + uvOnSeamB.y - normalB.y * (offset / (float)textureSize) + ); + Point2i pB( + (int)(pixelOffsetB.x * textureSize + 0.5f), + (int)(pixelOffsetB.y * textureSize + 0.5f) + ); + if (pB.x >= 0 && pB.x < textureSize && pB.y >= 0 && pB.y < textureSize) { + pixelsB.push_back(pB); + } + } + } + + // ✅ 去重 + std::sort(pixelsA.begin(), pixelsA.end(), + [](const Point2i& a, const Point2i& b) { + return a.y < b.y || (a.y == b.y && a.x < b.x); + }); + pixelsA.erase(std::unique(pixelsA.begin(), pixelsA.end()), pixelsA.end()); + + std::sort(pixelsB.begin(), pixelsB.end(), + [](const Point2i& a, const Point2i& b) { + return a.y < b.y || (a.y == b.y && a.x < b.x); + }); + pixelsB.erase(std::unique(pixelsB.begin(), pixelsB.end()), pixelsB.end()); +} +void MeshTexture::BlendSeamPixels( + const SeamInfo& seam, + const std::vector& pixelsA, + const std::vector& pixelsB, + const VirtualFaceGeometry& geomA, + const VirtualFaceGeometry& geomB, + int textureSize, + Image8U3& atlas) +{ + return; + const int GUTTER_PX = 6; // 4px gutter,可根据效果调3~6 + + // ========================================== + // 处理面A侧:只降低边缘像素的评分,不修改颜色 + // ========================================== + for (const Point2i& pixel : pixelsA) { + Point2f uvPixel(pixel.x / (float)textureSize, pixel.y / (float)textureSize); + + // 计算到接缝边的距离(UV空间转像素距离) + int eA = seam.edgeIdxA; + const TexCoord& uv0 = scene.mesh.faceTexcoords[seam.faceA * 3 + eA]; + const TexCoord& uv1 = scene.mesh.faceTexcoords[seam.faceA * 3 + ((eA + 1) % 3)]; + float dist = PointToLineDistance(uvPixel, Point2f(uv0.x, uv0.y), Point2f(uv1.x, uv1.y)) * textureSize; + + // ✅ 核心:只衰减评分,不碰颜色 + // 距离接缝越近,评分越低,越容易被对面覆盖 + if (dist < GUTTER_PX) { + size_t idx = pixel.y * textureSize + pixel.x; + if (idx < m_texelScores.size()) { + // 二次曲线衰减,比线性衰减更平滑 + float attenuation = (dist * dist * dist) / (float)(GUTTER_PX * GUTTER_PX * GUTTER_PX); + m_texelScores[idx].score *= attenuation; + } + } + } + + // ========================================== + // 处理面B侧:对称操作,只降低边缘像素评分 + // ========================================== + for (const Point2i& pixel : pixelsB) { + Point2f uvPixel(pixel.x / (float)textureSize, pixel.y / (float)textureSize); + + int eB = seam.edgeIdxB; + const TexCoord& uv0 = scene.mesh.faceTexcoords[seam.faceB * 3 + eB]; + const TexCoord& uv1 = scene.mesh.faceTexcoords[seam.faceB * 3 + ((eB + 1) % 3)]; + float dist = PointToLineDistance(uvPixel, Point2f(uv0.x, uv0.y), Point2f(uv1.x, uv1.y)) * textureSize; + + if (dist < GUTTER_PX) { + size_t idx = pixel.y * textureSize + pixel.x; + if (idx < m_texelScores.size()) { + float attenuation = (dist * dist * dist) / (float)(GUTTER_PX * GUTTER_PX * GUTTER_PX); + m_texelScores[idx].score *= attenuation; + } + } + } +} + +// ✅ 辅助函数:点到直线距离 +float MeshTexture::PointToLineDistance(const Point2f& p, const Point2f& a, const Point2f& b) { + float dx = b.x - a.x; + float dy = b.y - a.y; + float len2 = dx*dx + dy*dy; + if (len2 < 1e-12f) { + float dx2 = p.x - a.x; + float dy2 = p.y - a.y; + return std::sqrt(dx2*dx2 + dy2*dy2); + } + float t = ((p.x - a.x)*dx + (p.y - a.y)*dy) / len2; + t = std::max(0.0f, std::min(1.0f, t)); + float projx = a.x + t*dx; + float projy = a.y + t*dy; + float distx = p.x - projx; + float disty = p.y - projy; + return std::sqrt(distx*distx + disty*disty); +} + // ============================================================ // 2. 核心:计算单应矩阵 / 仿射矩阵 // - 3 个点 → Affine(getAffineTransform)