Browse Source

接缝处理

ManualUV
hesuicong 11 hours ago
parent
commit
5cf68ce634
  1. 501
      libs/MVS/SceneTexture.cpp

501
libs/MVS/SceneTexture.cpp

@ -545,6 +545,21 @@ struct MeshTexture {
AABB2f uvBounds; // UV 包围盒 AABB2f uvBounds; // UV 包围盒
cv::Mat1f homography; // 3x3 单应矩阵(从 UV 到视图) cv::Mat1f homography; // 3x3 单应矩阵(从 UV 到视图)
bool isValid = false; bool isValid = false;
std::vector<FIndex> neighborFaces; // 邻接面ID
std::vector<int> 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<Point2f> seamPixels; // 接缝上的像素坐标
}; };
// used to interpolate adjustments color over the whole texture patch // used to interpolate adjustments color over the whole texture patch
@ -657,8 +672,36 @@ public:
return x + 1; return x + 1;
} }
void FeatherSeams(
const std::vector<SeamInfo>& seams,
int textureSize,
Image8U3& atlas);
void GenerateSeamMask(
const std::vector<SeamInfo>& seams,
int textureSize,
cv::Mat& seamMask); // 输出:CV_32FC1,接缝处值 > 0
bool ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMap); bool ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMap);
bool DetectSeamsForBlending(
const VirtualFaceMap& virtualFaceMap,
const std::vector<std::vector<IIndex>>& virtualFaceViews,
std::vector<SeamInfo>& seams);
void ExtractSeamPixels(
const SeamInfo& seam,
const VirtualFaceGeometry& geomA,
const VirtualFaceGeometry& geomB,
int textureSize,
std::vector<Point2i>& pixelsA,
std::vector<Point2i>& pixelsB);
void BlendSeamPixels(
const SeamInfo& seam,
const std::vector<Point2i>& pixelsA,
const std::vector<Point2i>& pixelsB,
const VirtualFaceGeometry& geomA,
const VirtualFaceGeometry& geomB,
int textureSize,
Image8U3& atlas);
float PointToLineDistance(const Point2f& p, const Point2f& a, const Point2f& b);
bool ComputeHomographyForVirtualFace( bool ComputeHomographyForVirtualFace(
const VirtualFace& vf, const VirtualFace& vf,
IIndex viewID, IIndex viewID,
@ -14334,6 +14377,9 @@ bool MeshTexture::RasterizeVirtualFaces(
{ {
for (int y = 0; y < patchH; ++y) { for (int y = 0; y < patchH; ++y) {
for (int x = 0; x < patchW; ++x) { for (int x = 0; x < patchW; ++x) {
// 在 RasterizeVirtualFaces 的像素写入循环中
// ✅ 只保留最核心的逻辑,不做任何衰减
cv::Vec3b color = patch.at<cv::Vec3b>(y, x); cv::Vec3b color = patch.at<cv::Vec3b>(y, x);
if (color[0] == 0 && color[1] == 0 && color[2] == 0) if (color[0] == 0 && color[1] == 0 && color[2] == 0)
continue; continue;
@ -14347,12 +14393,11 @@ bool MeshTexture::RasterizeVirtualFaces(
size_t idx = atlasY * textureSize + atlasX; size_t idx = atlasY * textureSize + atlasX;
TexelScore& ts = m_texelScores[idx]; TexelScore& ts = m_texelScores[idx];
// ✅ 获取当前虚拟面的评分
float currentScore = virtualFaceViewWeights[i].empty() float currentScore = virtualFaceViewWeights[i].empty()
? -1.0f ? -1.0f
: virtualFaceViewWeights[i][0]; : virtualFaceViewWeights[i][0];
// ✅ 只接受更高评分的写入 // ✅ 正常写入,不衰减,不修改
if (currentScore > ts.score) { if (currentScore > ts.score) {
atlas(atlasY, atlasX) = atlas(atlasY, atlasX) =
Pixel8U{color[2], color[1], color[0]}; Pixel8U{color[2], color[1], color[0]};
@ -14365,6 +14410,41 @@ bool MeshTexture::RasterizeVirtualFaces(
} }
m_texelScores.clear(); m_texelScores.clear();
DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str());
// ✅ 新增:接缝融合后处理
if (true) { // 可配置开关
TD_TIMER_START();
// 1. 检测需要融合的接缝
std::vector<SeamInfo> 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<Point2i> 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()); DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str());
return true; return true;
} }
@ -15094,6 +15174,11 @@ bool MeshTexture::ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMa
// ✅ std::vector 用 resize // ✅ std::vector 用 resize
m_virtualFaceGeometries.resize(virtualFaceMap.size()); m_virtualFaceGeometries.resize(virtualFaceMap.size());
// ✅ 确保面邻接关系已计算
if (scene.mesh.faceFaces.empty()) {
scene.mesh.ListIncidenteFaceFaces();
}
#ifdef _USE_OPENMP #ifdef _USE_OPENMP
#pragma omp parallel for schedule(dynamic) #pragma omp parallel for schedule(dynamic)
#endif #endif
@ -15106,6 +15191,21 @@ bool MeshTexture::ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMa
continue; 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]; IIndex viewID = faceViews[i][0];
geom.isValid = ComputeHomographyForVirtualFace(vf, viewID, geom); geom.isValid = ComputeHomographyForVirtualFace(vf, viewID, geom);
} }
@ -15113,6 +15213,401 @@ bool MeshTexture::ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMa
return true; return true;
} }
void MeshTexture::FeatherSeams(
const std::vector<SeamInfo>& 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<float>(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<cv::Vec3b>(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<cv::Vec3b>(y, x);
atlas(y, x) = Pixel8U{c[2], c[1], c[0]};
}
}
}
void MeshTexture::GenerateSeamMask(
const std::vector<SeamInfo>& 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<float>(py, px);
current = std::max(current, gauss); // 取最大值(多条接缝可能重叠)
}
}
}
}
bool MeshTexture::DetectSeamsForBlending(
const VirtualFaceMap& virtualFaceMap,
const std::vector<std::vector<IIndex>>& virtualFaceViews,
std::vector<SeamInfo>& 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<Point2i>& pixelsA,
std::vector<Point2i>& 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<Point2i>& pixelsA,
const std::vector<Point2i>& 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. 核心:计算单应矩阵 / 仿射矩阵 // 2. 核心:计算单应矩阵 / 仿射矩阵
// - 3 个点 → Affine(getAffineTransform) // - 3 个点 → Affine(getAffineTransform)

Loading…
Cancel
Save