|
|
|
|
@ -740,6 +740,18 @@ public:
@@ -740,6 +740,18 @@ public:
|
|
|
|
|
void CreateSeamVertices(); |
|
|
|
|
uint32_t FindOrCreateComponentForFace(FIndex faceIdx); |
|
|
|
|
uint32_t FindNearestPatchForComponent(uint32_t compID); |
|
|
|
|
void RunGlobalColorOptimization(cv::Mat& atlas, int textureSize, float lambda); |
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
// 3.3 把求解出的调整量应用到 atlas
|
|
|
|
|
// —— 在三角形内用重心坐标插值(对齐 OpenMVS RasterPatch bary 插值)
|
|
|
|
|
// 调整量从 3 个顶点 (顶点,patch) 的调整向量插值到每个像素,再加到 RGB 上
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
void ApplyColorAdjustments(cv::Mat& atlasMat, const Eigen::VectorXf& x, const Eigen::Index rowsX); |
|
|
|
|
void GlobalSeamLeveling2(Eigen::Index* pRowsX, |
|
|
|
|
Eigen::SparseMatrix<float>* pA, |
|
|
|
|
Eigen::VectorXf* pb, |
|
|
|
|
float lambda, |
|
|
|
|
cv::Mat& atlasMat); |
|
|
|
|
void GlobalSeamLeveling(); |
|
|
|
|
void GlobalSeamLeveling3(); |
|
|
|
|
void LocalSeamLeveling(); |
|
|
|
|
@ -762,6 +774,7 @@ public:
@@ -762,6 +774,7 @@ public:
|
|
|
|
|
); |
|
|
|
|
std::vector<uint32_t> faceToPatchID; // faceID → 新 patchID 映射
|
|
|
|
|
void MergeSameViewPatches(); |
|
|
|
|
void BuildSeamEdgesFromFaceToPatchID(); |
|
|
|
|
void RunSeamLevelingOnAtlas(Image8U3& atlas, int textureSize); |
|
|
|
|
bool RasterizeVirtualFaces( |
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
@ -774,6 +787,15 @@ public:
@@ -774,6 +787,15 @@ public:
|
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
const std::vector<std::vector<IIndex>>& virtualFaceViews, |
|
|
|
|
int textureSize); |
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
// 3.1 从 rcSeamEdges 构建 SeamVert(对齐 OpenMVS seamVertices)
|
|
|
|
|
// 每个 seam edge 的两个端点(mesh 顶点)被收集;若一个顶点属于多个 patch
|
|
|
|
|
// (即位于 patch 边界),则成为 SeamVert。
|
|
|
|
|
// 同时为每个 (顶点, patch) 分配唯一的行号 rowsX —— 对齐 OpenMVS 的
|
|
|
|
|
// vertpatch2rows(变量索引的核心)。
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
bool BuildSeamVertsFromEdges(); |
|
|
|
|
bool BuildSeamVertsFromFaceAdjacency(); |
|
|
|
|
void GlobalAlignPatches(Image8U3& atlas, int textureSize); |
|
|
|
|
void LocalBlendSeams(Image8U3& atlas, int textureSize); |
|
|
|
|
void FeatherTextureSeams(Image8U3& texture, |
|
|
|
|
@ -1216,13 +1238,52 @@ public:
@@ -1216,13 +1238,52 @@ public:
|
|
|
|
|
Point2f uvMin, uvMax; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
struct SeamVert { |
|
|
|
|
VIndex idxVertex; // 对应的 mesh 顶点 ID
|
|
|
|
|
std::vector<uint32_t> patchIDs; // 该顶点属于哪些 patch(即 "patches")
|
|
|
|
|
|
|
|
|
|
struct Patch { |
|
|
|
|
uint32_t idxPatch; // patch 索引
|
|
|
|
|
// 如需边信息可加: std::vector<Edge> edges;
|
|
|
|
|
Patch(uint32_t id = UINT32_MAX) : idxPatch(id) {} |
|
|
|
|
}; |
|
|
|
|
// 若你原代码用的是 patchIDs 扁平数组,上面 Patch 结构 + patches 可省略
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
struct GSLInput { |
|
|
|
|
const std::vector<RCPatch>* rcPatches = nullptr; // [in]
|
|
|
|
|
const std::vector<SeamEdge>* rcSeamEdges = nullptr; // [in]
|
|
|
|
|
const std::vector<uint32_t>* faceToPatchID = nullptr; // [in] face -> merged patch
|
|
|
|
|
const Mesh::FaceFaces* faceFaces = nullptr; // [in] 面-面邻接
|
|
|
|
|
const std::vector<TexCoord>* faceTexcoords = nullptr; // [in] per-face UV (3 per face)
|
|
|
|
|
const std::vector<Face>* faces = nullptr; // [in]
|
|
|
|
|
cv::Mat* atlas = nullptr; // [in/out] atlas 图像
|
|
|
|
|
int textureSize = 0; // [in]
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
inline cv::Vec3b AddColorClamped(const cv::Vec3b& base, const Eigen::Vector3f& adj) { |
|
|
|
|
return cv::Vec3b( |
|
|
|
|
(uint8_t)cv::saturate_cast<int>(base[0] + (int)adj[0]), |
|
|
|
|
(uint8_t)cv::saturate_cast<int>(base[1] + (int)adj[1]), |
|
|
|
|
(uint8_t)cv::saturate_cast<int>(base[2] + (int)adj[2])); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
std::vector<SeamEdge> rcSeamEdges; |
|
|
|
|
std::vector<RCPatch> rcPatches; |
|
|
|
|
int currentTextureSize; |
|
|
|
|
|
|
|
|
|
// ===== 全局颜色优化(对齐 OpenMVS GlobalSeamLeveling3)=====
|
|
|
|
|
std::vector<SeamVert> seamVerts; // 接缝顶点列表
|
|
|
|
|
std::vector<uint32_t> vertToSeamVert; // mesh 顶点 -> seamVerts 索引 (UINT32_MAX = 非接缝)
|
|
|
|
|
bool seamDataBuilt; // 是否已构建
|
|
|
|
|
// CG 求解结果缓存(供 ApplyColorAdjustments 使用)
|
|
|
|
|
Eigen::VectorXf m_gslSolution; // 解向量 x(per-顶点×patch 的调整量)
|
|
|
|
|
Eigen::Index m_gslRowsX; // 变量总数(= rowsX)
|
|
|
|
|
// (可选)vertpatch -> row 映射,按需启用:
|
|
|
|
|
std::vector<Eigen::Index> m_gslSVPatchRow; |
|
|
|
|
|
|
|
|
|
// ===== 函数声明 =====
|
|
|
|
|
Color SampleImageBilinear(const cv::Mat& img, float x, float y); |
|
|
|
|
void BuildSeamEdgesFromRCPatches(); |
|
|
|
|
void SeamBlendingFromOriginalImages(Image8U3& atlas); |
|
|
|
|
|
|
|
|
|
// ---- 全局光度校正(per-image gain,3 通道独立)----
|
|
|
|
|
@ -10092,6 +10153,187 @@ void MeshTexture::LocalSeamLevelingExternalUV()
@@ -10092,6 +10153,187 @@ void MeshTexture::LocalSeamLevelingExternalUV()
|
|
|
|
|
// 针对外部UV数据的局部接缝处理
|
|
|
|
|
// 实现保留原始UV特征的接缝处理
|
|
|
|
|
} |
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
// 3.3 把求解出的调整量应用到 atlas
|
|
|
|
|
// —— 在三角形内用重心坐标插值(对齐 OpenMVS RasterPatch bary 插值)
|
|
|
|
|
// 调整量从 3 个顶点 (顶点,patch) 的调整向量插值到每个像素,再加到 RGB 上
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
void MeshTexture::ApplyColorAdjustments(cv::Mat& atlasMat, |
|
|
|
|
const Eigen::VectorXf& x, |
|
|
|
|
const Eigen::Index rowsX) |
|
|
|
|
{ |
|
|
|
|
const size_t NP = rcPatches.size(); |
|
|
|
|
if (x.size() == 0 || seamVerts.empty()) return; |
|
|
|
|
|
|
|
|
|
// 计算每个 patch 的平均调整量(从接缝顶点处取)
|
|
|
|
|
std::vector<cv::Vec3f> patchAdj(NP, cv::Vec3f(0,0,0)); |
|
|
|
|
std::vector<int> patchCnt(NP, 0); |
|
|
|
|
|
|
|
|
|
for (size_t svi = 0; svi < seamVerts.size(); ++svi) { |
|
|
|
|
const SeamVert& sv = seamVerts[svi]; |
|
|
|
|
// ★ 修正:用 patchIDs(vector<uint32_t>),不是 patches
|
|
|
|
|
for (uint32_t pid : sv.patchIDs) { |
|
|
|
|
if (pid >= NP) continue; |
|
|
|
|
Eigen::Index row = m_gslSVPatchRow[svi * NP + pid]; |
|
|
|
|
if (row < 0 || row >= rowsX) continue; |
|
|
|
|
|
|
|
|
|
patchAdj[pid][0] += x(row * 3 + 0); |
|
|
|
|
patchAdj[pid][1] += x(row * 3 + 1); |
|
|
|
|
patchAdj[pid][2] += x(row * 3 + 2); |
|
|
|
|
patchCnt[pid]++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 均值
|
|
|
|
|
for (size_t i = 0; i < NP; ++i) { |
|
|
|
|
if (patchCnt[i] > 0) { |
|
|
|
|
patchAdj[i] *= (1.0f / patchCnt[i]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 应用(加法调整,clamp ±15)
|
|
|
|
|
for (size_t i = 0; i < NP; ++i) { |
|
|
|
|
const cv::Rect& r = rcPatches[i].rect; |
|
|
|
|
if (r.x < 0 || r.y < 0 || r.x + r.width > atlasMat.cols || r.y + r.height > atlasMat.rows) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
cv::Vec3f adj( |
|
|
|
|
std::clamp(patchAdj[i][0], -15.0f, 15.0f), |
|
|
|
|
std::clamp(patchAdj[i][1], -15.0f, 15.0f), |
|
|
|
|
std::clamp(patchAdj[i][2], -15.0f, 15.0f) |
|
|
|
|
); |
|
|
|
|
if (adj[0] == 0 && adj[1] == 0 && adj[2] == 0) continue; |
|
|
|
|
|
|
|
|
|
cv::Mat patch = atlasMat(r); |
|
|
|
|
for (int y = 0; y < patch.rows; ++y) |
|
|
|
|
for (int x = 0; x < patch.cols; ++x) { |
|
|
|
|
cv::Vec3b& p = patch.at<cv::Vec3b>(y, x); |
|
|
|
|
if (p[0] < 5 && p[1] < 5 && p[2] < 5) continue; |
|
|
|
|
p[0] = cv::saturate_cast<uchar>(p[0] + adj[0]); |
|
|
|
|
p[1] = cv::saturate_cast<uchar>(p[1] + adj[1]); |
|
|
|
|
p[2] = cv::saturate_cast<uchar>(p[2] + adj[2]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
// 3.4 一站式入口(推荐调用)
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
void MeshTexture::RunGlobalColorOptimization(cv::Mat& atlas, int textureSize, float lambda) |
|
|
|
|
{ |
|
|
|
|
if (!seamDataBuilt) { |
|
|
|
|
BuildSeamVertsFromEdges(); // 或 BuildSeamVertsFromFaceAdjacency()
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Eigen::Index rowsX; |
|
|
|
|
Eigen::SparseMatrix<float> A; |
|
|
|
|
Eigen::VectorXf b; |
|
|
|
|
|
|
|
|
|
GlobalSeamLeveling2(&rowsX, &A, &b, lambda, atlas); |
|
|
|
|
ApplyColorAdjustments(atlas, m_gslSolution, m_gslRowsX); |
|
|
|
|
} |
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
// 3.2 核心:构建并求解全局颜色调整(对齐 OpenMVS GlobalSeamLeveling3)
|
|
|
|
|
//
|
|
|
|
|
// 变量:每个 (接缝顶点, patch) 一个 3 通道调整量 → 行号 rowsX
|
|
|
|
|
// 数据项 A:同一接缝顶点,不同 patch 的调整后颜色一致
|
|
|
|
|
// color_i + x_i = color_j + x_j
|
|
|
|
|
// → x_i - x_j = color_j - color_i (右端 b = 颜色差)
|
|
|
|
|
// 正则项 Γ:同一 patch 内相邻顶点调整量平滑
|
|
|
|
|
// x_v,P - x_vAdj,P = 0, 权重 λ
|
|
|
|
|
// 求解:(AᵀA + ΓᵀΓ) x = Aᵀ b (对称正定,用 ConjugateGradient)
|
|
|
|
|
//
|
|
|
|
|
// 输出:x(按 rowsX 索引的颜色调整向量),供 ApplyColorAdjustments 使用
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
void MeshTexture::GlobalSeamLeveling2(Eigen::Index* pRowsX, |
|
|
|
|
Eigen::SparseMatrix<float>* pA, |
|
|
|
|
Eigen::VectorXf* pb, |
|
|
|
|
float lambda, |
|
|
|
|
cv::Mat& atlasMat) |
|
|
|
|
{ |
|
|
|
|
const size_t NP = rcPatches.size(); |
|
|
|
|
if (seamVerts.empty() || NP == 0) return; |
|
|
|
|
|
|
|
|
|
// 构建 vertpatch -> row 映射(扁平数组)
|
|
|
|
|
m_gslSVPatchRow.assign(seamVerts.size() * NP, -1); |
|
|
|
|
Eigen::Index row = 0; |
|
|
|
|
for (size_t svi = 0; svi < seamVerts.size(); ++svi) { |
|
|
|
|
const SeamVert& sv = seamVerts[svi]; |
|
|
|
|
for (uint32_t pid : sv.patchIDs) { |
|
|
|
|
if (pid < NP) { |
|
|
|
|
m_gslSVPatchRow[svi * NP + pid] = row++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
const Eigen::Index rowsX = row; |
|
|
|
|
if (pRowsX) *pRowsX = rowsX; |
|
|
|
|
|
|
|
|
|
// 构建稀疏矩阵 A 和向量 b
|
|
|
|
|
std::vector<Eigen::Triplet<float>> trips; |
|
|
|
|
trips.reserve(rowsX * 4); |
|
|
|
|
Eigen::VectorXf bb(rowsX * 3); // ★ 改名 b -> bb,避免和参数/成员冲突
|
|
|
|
|
bb.setZero(); |
|
|
|
|
|
|
|
|
|
// 数据项:接缝处相邻 patch 调整量一致
|
|
|
|
|
for (const SeamEdge& edge : rcSeamEdges) { |
|
|
|
|
const uint32_t a = edge.rcPatchID0; |
|
|
|
|
const uint32_t bp = edge.rcPatchID1; // ★ b_patch -> bp
|
|
|
|
|
if (a >= NP || bp >= NP || a == bp) continue; |
|
|
|
|
|
|
|
|
|
for (size_t svi = 0; svi < seamVerts.size(); ++svi) { |
|
|
|
|
const SeamVert& sv = seamVerts[svi]; |
|
|
|
|
bool hasA = false, hasBp = false; |
|
|
|
|
for (uint32_t pid : sv.patchIDs) { |
|
|
|
|
if (pid == a) hasA = true; |
|
|
|
|
if (pid == bp) hasBp = true; // ★ pid == b -> pid == bp
|
|
|
|
|
} |
|
|
|
|
if (hasA && hasBp) { |
|
|
|
|
Eigen::Index rowA = m_gslSVPatchRow[svi * NP + a]; |
|
|
|
|
Eigen::Index rowB = m_gslSVPatchRow[svi * NP + bp]; // ★ bp
|
|
|
|
|
if (rowA < 0 || rowB < 0) continue; |
|
|
|
|
|
|
|
|
|
for (int c = 0; c < 3; ++c) { |
|
|
|
|
Eigen::Index rA = rowA * 3 + c; |
|
|
|
|
Eigen::Index rB = rowB * 3 + c; |
|
|
|
|
trips.emplace_back(rA, rA, 1.0f); |
|
|
|
|
trips.emplace_back(rA, rB, -1.0f); |
|
|
|
|
trips.emplace_back(rB, rB, 1.0f); |
|
|
|
|
trips.emplace_back(rB, rA, -1.0f); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 正则项:Tikhonov λ
|
|
|
|
|
for (Eigen::Index i = 0; i < rowsX * 3; ++i) { |
|
|
|
|
trips.emplace_back(i, i, lambda); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Eigen::SparseMatrix<float> A(rowsX * 3, rowsX * 3); |
|
|
|
|
A.setFromTriplets(trips.begin(), trips.end()); |
|
|
|
|
|
|
|
|
|
if (pA) *pA = A; |
|
|
|
|
if (pb) *pb = bb; // ★ 赋给输出参数(不再是局部 b)
|
|
|
|
|
|
|
|
|
|
// CG 求解
|
|
|
|
|
Eigen::ConjugateGradient<Eigen::SparseMatrix<float>, Eigen::Lower|Eigen::Upper> solver; |
|
|
|
|
solver.setTolerance(1e-6f); |
|
|
|
|
solver.setMaxIterations(500); |
|
|
|
|
solver.compute(A); |
|
|
|
|
|
|
|
|
|
Eigen::VectorXf x = solver.solve(bb); // ★ bb
|
|
|
|
|
|
|
|
|
|
// 减均值
|
|
|
|
|
for (int c = 0; c < 3; ++c) { |
|
|
|
|
x.segment(c, rowsX).array() -= x.segment(c, rowsX).mean(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
m_gslSolution = x; |
|
|
|
|
m_gslRowsX = rowsX; |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("GlobalSeamLeveling: %lld vars, %d iters, err=%.2e", |
|
|
|
|
rowsX, solver.iterations(), solver.error()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// New
|
|
|
|
|
void MeshTexture::GlobalSeamLeveling() |
|
|
|
|
@ -14792,6 +15034,42 @@ void MeshTexture::FeatherTextureSeams(Image8U3& texture,
@@ -14792,6 +15034,42 @@ void MeshTexture::FeatherTextureSeams(Image8U3& texture,
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void MeshTexture::BuildSeamEdgesFromFaceToPatchID() { |
|
|
|
|
rcSeamEdges.clear(); |
|
|
|
|
const size_t numFaces = scene.mesh.faces.size(); |
|
|
|
|
for (FIndex fid = 0; fid < (FIndex)numFaces; ++fid) { |
|
|
|
|
if (fid >= faceToPatchID.size()) continue; |
|
|
|
|
const uint32_t pidA = faceToPatchID[fid]; |
|
|
|
|
if (pidA == UINT32_MAX) continue; |
|
|
|
|
if (fid >= faceFaces.size()) continue; |
|
|
|
|
const Mesh::FaceFaces& adj = faceFaces[fid]; |
|
|
|
|
for (int e = 0; e < 3; ++e) { |
|
|
|
|
const FIndex adjFid = adj[e]; |
|
|
|
|
if (adjFid == NO_ID || adjFid <= fid) continue; |
|
|
|
|
if (adjFid >= faceToPatchID.size()) continue; |
|
|
|
|
const uint32_t pidB = faceToPatchID[adjFid]; |
|
|
|
|
if (pidB == UINT32_MAX || pidB == pidA) continue; |
|
|
|
|
// 避免重复添加
|
|
|
|
|
bool exists = false; |
|
|
|
|
for (const SeamEdge& se : rcSeamEdges) { |
|
|
|
|
if ((se.rcPatchID0 == pidA && se.rcPatchID1 == pidB) || |
|
|
|
|
(se.rcPatchID0 == pidB && se.rcPatchID1 == pidA)) { |
|
|
|
|
exists = true; break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (!exists) { |
|
|
|
|
SeamEdge edge; |
|
|
|
|
edge.rcPatchID0 = pidA; |
|
|
|
|
edge.rcPatchID1 = pidB; |
|
|
|
|
// uv0/uv1 留空,让 CG 用 patchAvgColor fallback
|
|
|
|
|
rcSeamEdges.push_back(edge); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("BuildSeamEdgesFromFaceToPatchID: %zu edges", rcSeamEdges.size()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void MeshTexture::MergeSameViewPatches() |
|
|
|
|
{ |
|
|
|
|
const size_t numPatches = rcPatches.size(); |
|
|
|
|
@ -15199,99 +15477,397 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -15199,99 +15477,397 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
// FeatherTextureSeams(atlas, m_texelPatchID, textureSize, 3);
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool MeshTexture::BuildSeamVertsFromEdges() |
|
|
|
|
{ |
|
|
|
|
const size_t NP = rcPatches.size(); |
|
|
|
|
if (NP == 0 || rcSeamEdges.empty()) return false; |
|
|
|
|
|
|
|
|
|
// 确保 faceFaces 已计算
|
|
|
|
|
if (scene.mesh.faceFaces.empty()) { |
|
|
|
|
scene.mesh.ListIncidenteFaceFaces(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
seamVerts.clear(); |
|
|
|
|
vertToSeamVert.assign(scene.mesh.vertices.size(), UINT32_MAX); |
|
|
|
|
|
|
|
|
|
for (const SeamEdge& edge : rcSeamEdges) { |
|
|
|
|
const uint32_t a = edge.rcPatchID0; |
|
|
|
|
const uint32_t b = edge.rcPatchID1; |
|
|
|
|
if (a >= NP || b >= NP || a == b) continue; |
|
|
|
|
|
|
|
|
|
for (size_t fi = 0; fi < scene.mesh.faces.size(); ++fi) { |
|
|
|
|
const uint32_t pid = faceToPatchID[fi]; |
|
|
|
|
if (pid != a && pid != b) continue; |
|
|
|
|
|
|
|
|
|
const Mesh::Face& face = scene.mesh.faces[fi]; |
|
|
|
|
for (int v = 0; v < 3; ++v) { |
|
|
|
|
VIndex vtx = face[v]; |
|
|
|
|
|
|
|
|
|
bool inA = false, inB = false; |
|
|
|
|
|
|
|
|
|
// ★ 修正:用索引循环访问邻接面(兼容数组/vector)
|
|
|
|
|
const Mesh::FaceFaces& adj = scene.mesh.faceFaces[fi]; |
|
|
|
|
for (int e = 0; e < 3; ++e) { // 3 条边
|
|
|
|
|
const FIndex adjFi = adj[e]; // ★ 索引访问
|
|
|
|
|
if (adjFi == NO_ID || adjFi >= (FIndex)scene.mesh.faces.size()) continue; |
|
|
|
|
const uint32_t adjPid = faceToPatchID[adjFi]; |
|
|
|
|
if (adjPid == a) inA = true; |
|
|
|
|
if (adjPid == b) inB = true; |
|
|
|
|
} |
|
|
|
|
// 当前面本身
|
|
|
|
|
if (pid == a) inA = true; |
|
|
|
|
if (pid == b) inB = true; |
|
|
|
|
|
|
|
|
|
if (!inA || !inB) continue; |
|
|
|
|
|
|
|
|
|
uint32_t svIdx = vertToSeamVert[vtx]; |
|
|
|
|
if (svIdx == UINT32_MAX) { |
|
|
|
|
svIdx = (uint32_t)seamVerts.size(); |
|
|
|
|
seamVerts.emplace_back(); |
|
|
|
|
seamVerts.back().idxVertex = vtx; |
|
|
|
|
seamVerts.back().patchIDs.push_back(a); |
|
|
|
|
seamVerts.back().patchIDs.push_back(b); |
|
|
|
|
vertToSeamVert[vtx] = svIdx; |
|
|
|
|
} else { |
|
|
|
|
auto& sv = seamVerts[svIdx]; |
|
|
|
|
bool hasA = false, hasB = false; |
|
|
|
|
for (uint32_t p : sv.patchIDs) { |
|
|
|
|
if (p == a) hasA = true; |
|
|
|
|
if (p == b) hasB = true; |
|
|
|
|
} |
|
|
|
|
if (!hasA) sv.patchIDs.push_back(a); |
|
|
|
|
if (!hasB) sv.patchIDs.push_back(b); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
seamDataBuilt = true; |
|
|
|
|
DEBUG_EXTRA("BuildSeamVertsFromEdges: %zu seam vertices from %zu edges", |
|
|
|
|
seamVerts.size(), rcSeamEdges.size()); |
|
|
|
|
return !seamVerts.empty(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void MeshTexture::GlobalAlignPatches(Image8U3& atlas, int textureSize) { |
|
|
|
|
const size_t NP = rcPatches.size(); |
|
|
|
|
if (NP == 0 || rcSeamEdges.empty()) return; |
|
|
|
|
if (NP == 0) return; |
|
|
|
|
|
|
|
|
|
// 每个 patch 有一个颜色增益 g_i,初始为 1.0
|
|
|
|
|
std::vector<float> gain(NP, 1.0f); |
|
|
|
|
|
|
|
|
|
// 用 patch 的平均颜色作为参考
|
|
|
|
|
std::vector<cv::Vec3f> avgColor(NP, cv::Vec3f(0,0,0)); |
|
|
|
|
cv::Mat atlasMat(atlas.rows, atlas.cols, CV_8UC3, atlas.data); |
|
|
|
|
|
|
|
|
|
// ========== 第一步:对每个 patch,收集接缝边处的实际像素颜色 ==========
|
|
|
|
|
// seamColorSum[i] = patch i 在所有接缝边处的像素颜色累加
|
|
|
|
|
// seamColorCount[i] = 累加次数
|
|
|
|
|
std::vector<cv::Vec3d> seamColorSum(NP, cv::Vec3d(0,0,0)); |
|
|
|
|
std::vector<int> seamColorCount(NP, 0); |
|
|
|
|
|
|
|
|
|
const size_t numFaces = scene.mesh.faces.size(); |
|
|
|
|
|
|
|
|
|
// 遍历面邻接,找跨 patch 的共享边
|
|
|
|
|
for (FIndex fid = 0; fid < (FIndex)numFaces; ++fid) { |
|
|
|
|
if (fid >= faceToPatchID.size()) continue; |
|
|
|
|
const uint32_t pidA = faceToPatchID[fid]; |
|
|
|
|
if (pidA == UINT32_MAX || pidA >= NP) continue; |
|
|
|
|
|
|
|
|
|
if (fid >= faceFaces.size()) continue; |
|
|
|
|
const Mesh::FaceFaces& adj = faceFaces[fid]; |
|
|
|
|
|
|
|
|
|
for (int e = 0; e < 3; ++e) { |
|
|
|
|
const FIndex adjFid = adj[e]; |
|
|
|
|
if (adjFid == NO_ID || adjFid <= fid) continue; // 每条边只处理一次
|
|
|
|
|
|
|
|
|
|
if (adjFid >= faceToPatchID.size()) continue; |
|
|
|
|
const uint32_t pidB = faceToPatchID[adjFid]; |
|
|
|
|
if (pidB == UINT32_MAX || pidB >= NP) continue; |
|
|
|
|
if (pidA == pidB) continue; // 同一 patch,跳过
|
|
|
|
|
|
|
|
|
|
// 找到共享边的两个端点
|
|
|
|
|
const Mesh::Face& faceA = scene.mesh.faces[fid]; |
|
|
|
|
const VIndex v0 = faceA[e]; |
|
|
|
|
const VIndex v1 = faceA[(e + 1) % 3]; |
|
|
|
|
|
|
|
|
|
// 在 patchA 的 face 上找这条边的 UV
|
|
|
|
|
Point2f uvA0, uvA1; |
|
|
|
|
bool foundA = false; |
|
|
|
|
for (FIndex f : rcPatches[pidA].faces) { |
|
|
|
|
if (f >= numFaces) continue; |
|
|
|
|
const Mesh::Face& ff = scene.mesh.faces[f]; |
|
|
|
|
for (int ee = 0; ee < 3; ++ee) { |
|
|
|
|
if ((ff[ee] == v0 && ff[(ee+1)%3] == v1) || |
|
|
|
|
(ff[ee] == v1 && ff[(ee+1)%3] == v0)) { |
|
|
|
|
uvA0 = Point2f(scene.mesh.faceTexcoords[f * 3 + ee].x * textureSize, |
|
|
|
|
scene.mesh.faceTexcoords[f * 3 + ee].y * textureSize); |
|
|
|
|
uvA1 = Point2f(scene.mesh.faceTexcoords[f * 3 + (ee+1)%3].x * textureSize, |
|
|
|
|
scene.mesh.faceTexcoords[f * 3 + (ee+1)%3].y * textureSize); |
|
|
|
|
foundA = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (foundA) break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 在 patchB 的 face 上找这条边的 UV
|
|
|
|
|
Point2f uvB0, uvB1; |
|
|
|
|
bool foundB = false; |
|
|
|
|
for (FIndex f : rcPatches[pidB].faces) { |
|
|
|
|
if (f >= numFaces) continue; |
|
|
|
|
const Mesh::Face& ff = scene.mesh.faces[f]; |
|
|
|
|
for (int ee = 0; ee < 3; ++ee) { |
|
|
|
|
if ((ff[ee] == v0 && ff[(ee+1)%3] == v1) || |
|
|
|
|
(ff[ee] == v1 && ff[(ee+1)%3] == v0)) { |
|
|
|
|
uvB0 = Point2f(scene.mesh.faceTexcoords[f * 3 + ee].x * textureSize, |
|
|
|
|
scene.mesh.faceTexcoords[f * 3 + ee].y * textureSize); |
|
|
|
|
uvB1 = Point2f(scene.mesh.faceTexcoords[f * 3 + (ee+1)%3].x * textureSize, |
|
|
|
|
scene.mesh.faceTexcoords[f * 3 + (ee+1)%3].y * textureSize); |
|
|
|
|
foundB = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (foundB) break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!foundA || !foundB) continue; |
|
|
|
|
|
|
|
|
|
// 沿边采样(5 个采样点)
|
|
|
|
|
const int nSamples = 5; |
|
|
|
|
for (int s = 1; s < nSamples; ++s) { |
|
|
|
|
const float t = (float)s / nSamples; |
|
|
|
|
|
|
|
|
|
const int xA = (int)(uvA0.x + (uvA1.x - uvA0.x) * t + 0.5f); |
|
|
|
|
const int yA = (int)(uvA0.y + (uvA1.y - uvA0.y) * t + 0.5f); |
|
|
|
|
const int xB = (int)(uvB0.x + (uvB1.x - uvB0.x) * t + 0.5f); |
|
|
|
|
const int yB = (int)(uvB0.y + (uvB1.y - uvB0.y) * t + 0.5f); |
|
|
|
|
|
|
|
|
|
if (xA < 0 || yA < 0 || xA >= atlasMat.cols || yA >= atlasMat.rows) continue; |
|
|
|
|
if (xB < 0 || yB < 0 || xB >= atlasMat.cols || yB >= atlasMat.rows) continue; |
|
|
|
|
|
|
|
|
|
const cv::Vec3b& cA = atlasMat.at<cv::Vec3b>(yA, xA); |
|
|
|
|
const cv::Vec3b& cB = atlasMat.at<cv::Vec3b>(yB, xB); |
|
|
|
|
|
|
|
|
|
seamColorSum[pidA] += cv::Vec3d(cA[0], cA[1], cA[2]); |
|
|
|
|
seamColorSum[pidB] += cv::Vec3d(cB[0], cB[1], cB[2]); |
|
|
|
|
seamColorCount[pidA]++; |
|
|
|
|
seamColorCount[pidB]++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 算每个 patch 的接缝处平均颜色
|
|
|
|
|
std::vector<cv::Vec3f> seamAvgColor(NP, cv::Vec3f(0,0,0)); |
|
|
|
|
for (size_t i = 0; i < NP; ++i) { |
|
|
|
|
if (patchAvgColor.size() > i) { |
|
|
|
|
avgColor[i] = patchAvgColor[i]; // 你之前算过的 patchAvgColor
|
|
|
|
|
if (seamColorCount[i] > 0) { |
|
|
|
|
seamAvgColor[i] = cv::Vec3f( |
|
|
|
|
seamColorSum[i][0] / seamColorCount[i], |
|
|
|
|
seamColorSum[i][1] / seamColorCount[i], |
|
|
|
|
seamColorSum[i][2] / seamColorCount[i] |
|
|
|
|
); |
|
|
|
|
} else { |
|
|
|
|
// 没有接缝边的 patch,回退到 patchAvgColor
|
|
|
|
|
if (i < patchAvgColor.size()) { |
|
|
|
|
const Color& c = patchAvgColor[i]; |
|
|
|
|
seamAvgColor[i] = cv::Vec3f(c.x, c.y, c.z); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 简单迭代求解:让相邻 patch 在接缝处颜色一致
|
|
|
|
|
for (int iter = 0; iter < 10; ++iter) { |
|
|
|
|
// ========== 第二步:迭代求解增益 ==========
|
|
|
|
|
std::vector<float> gain(NP, 1.0f); |
|
|
|
|
|
|
|
|
|
for (int iter = 0; iter < 20; ++iter) { |
|
|
|
|
std::vector<float> newGain(NP, 0.0f); |
|
|
|
|
std::vector<int> count(NP, 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 用 rcSeamEdges 做邻接传播
|
|
|
|
|
for (const SeamEdge& edge : rcSeamEdges) { |
|
|
|
|
const uint32_t a = edge.rcPatchID0; |
|
|
|
|
const uint32_t b = edge.rcPatchID1; |
|
|
|
|
if (a >= NP || b >= NP) continue; |
|
|
|
|
if (a == b) continue; |
|
|
|
|
|
|
|
|
|
// 目标:gain[a] * avgColor[a] ≈ gain[b] * avgColor[b]
|
|
|
|
|
// 用 avgColor 的比值更新
|
|
|
|
|
const cv::Vec3f& ca = avgColor[a]; |
|
|
|
|
const cv::Vec3f& cb = avgColor[b]; |
|
|
|
|
if (a >= NP || b >= NP || a == b) continue; |
|
|
|
|
|
|
|
|
|
const cv::Vec3f& ca = seamAvgColor[a]; |
|
|
|
|
const cv::Vec3f& cb = seamAvgColor[b]; |
|
|
|
|
|
|
|
|
|
for (int c = 0; c < 3; ++c) { |
|
|
|
|
if (ca[c] > 1e-6f && cb[c] > 1e-6f) { |
|
|
|
|
if (ca[c] > 1.0f) { // 避免极暗区域
|
|
|
|
|
const float ratio = cb[c] / ca[c]; |
|
|
|
|
newGain[a] += ratio * gain[b]; |
|
|
|
|
const float clamped = std::clamp(ratio, 0.5f, 2.0f); |
|
|
|
|
newGain[a] += clamped * gain[b]; |
|
|
|
|
count[a]++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < NP; ++i) { |
|
|
|
|
if (count[i] > 0) { |
|
|
|
|
gain[i] = newGain[i] / count[i]; |
|
|
|
|
gain[i] = std::clamp(gain[i], 0.5f, 2.0f); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 应用增益到 atlas:遍历每个 patch 的像素,乘上 gain
|
|
|
|
|
// ========== 第三步:应用增益到 atlas ==========
|
|
|
|
|
for (size_t i = 0; i < NP; ++i) { |
|
|
|
|
const cv::Rect& r = rcPatches[i].rect; |
|
|
|
|
if (r.x < 0 || r.y < 0 || r.x + r.width > atlas.cols || r.y + r.height > atlas.rows) |
|
|
|
|
if (r.x < 0 || r.y < 0 || r.x + r.width > atlasMat.cols || r.y + r.height > atlasMat.rows) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
cv::Mat patch = atlas(r); |
|
|
|
|
const float g = std::clamp(gain[i], 0.5f, 2.0f); |
|
|
|
|
|
|
|
|
|
cv::Mat patch = atlasMat(r); |
|
|
|
|
const float g = gain[i]; |
|
|
|
|
patch.convertTo(patch, CV_32FC3); |
|
|
|
|
patch *= g; |
|
|
|
|
patch.convertTo(patch, CV_8UC3); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("GlobalAlignPatches: %zu patches aligned", NP); |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("GlobalAlignPatches: %zu patches aligned (seam-based, %d iters)", NP, 20); |
|
|
|
|
} |
|
|
|
|
void MeshTexture::LocalBlendSeams(Image8U3& atlas, int textureSize) { |
|
|
|
|
// ★ 包装为 cv::Mat
|
|
|
|
|
cv::Mat atlasMat(atlas.cols, atlas.rows, CV_8UC3, atlas.data); |
|
|
|
|
cv::Mat atlasMat(atlas.rows, atlas.cols, CV_8UC3, atlas.data); |
|
|
|
|
|
|
|
|
|
const size_t NP = rcPatches.size(); |
|
|
|
|
const size_t numFaces = scene.mesh.faces.size(); |
|
|
|
|
|
|
|
|
|
if (NP == 0 || faceToPatchID.size() != numFaces) { |
|
|
|
|
DEBUG_EXTRA("LocalBlendSeams: invalid state (faceToPatchID.size=%zu, numFaces=%zu)", |
|
|
|
|
faceToPatchID.size(), numFaces); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const int blendWidth = 4; |
|
|
|
|
// ---- 1. 构建邻接面 → patch 的映射(已有 faceToPatchID,这里直接用)----
|
|
|
|
|
// faceToPatchID[faceID] = patchID
|
|
|
|
|
|
|
|
|
|
for (const SeamEdge& edge : rcSeamEdges) { |
|
|
|
|
const uint32_t a = edge.rcPatchID0; |
|
|
|
|
const uint32_t b = edge.rcPatchID1; |
|
|
|
|
if (a >= rcPatches.size() || b >= rcPatches.size()) continue; |
|
|
|
|
|
|
|
|
|
const cv::Rect& ra = rcPatches[a].rect; |
|
|
|
|
const cv::Rect& rb = rcPatches[b].rect; |
|
|
|
|
cv::Rect overlap = ra & rb; |
|
|
|
|
if (overlap.area() == 0) continue; |
|
|
|
|
|
|
|
|
|
// 在重叠区域做线性混合
|
|
|
|
|
for (int y = 0; y < overlap.height; ++y) { |
|
|
|
|
for (int x = 0; x < overlap.width; ++x) { |
|
|
|
|
const float t = (float)x / overlap.width; |
|
|
|
|
const cv::Vec3b& ca = atlasMat.at<cv::Vec3b>(ra.y + y, ra.x + x); |
|
|
|
|
const cv::Vec3b& cb = atlasMat.at<cv::Vec3b>(rb.y + y, rb.x + x); |
|
|
|
|
atlasMat.at<cv::Vec3b>(overlap.y + y, overlap.x + x) = cv::Vec3b( |
|
|
|
|
(uint8_t)(ca[0] * (1 - t) + cb[0] * t), |
|
|
|
|
(uint8_t)(ca[1] * (1 - t) + cb[1] * t), |
|
|
|
|
(uint8_t)(ca[2] * (1 - t) + cb[2] * t) |
|
|
|
|
); |
|
|
|
|
// ---- 2. 遍历所有 face,找跨 patch 的共享边 ----
|
|
|
|
|
// 对于每条共享边,记录:端点顶点、两侧 patch ID
|
|
|
|
|
struct SeamEdgeInfo { |
|
|
|
|
VIndex v0, v1; // 共享边的两个端点
|
|
|
|
|
uint32_t patchA, patchB; |
|
|
|
|
}; |
|
|
|
|
std::vector<SeamEdgeInfo> seamEdges; |
|
|
|
|
|
|
|
|
|
// 用 faceFaces 遍历邻接关系(每条边只处理一次)
|
|
|
|
|
for (FIndex fid = 0; fid < (FIndex)numFaces; ++fid) { |
|
|
|
|
const uint32_t pidA = faceToPatchID[fid]; |
|
|
|
|
if (pidA == UINT32_MAX || pidA >= NP) continue; |
|
|
|
|
|
|
|
|
|
if (fid >= faceFaces.size()) continue; |
|
|
|
|
const Mesh::FaceFaces& adj = faceFaces[fid]; // [3] 邻接面
|
|
|
|
|
|
|
|
|
|
for (int e = 0; e < 3; ++e) { |
|
|
|
|
const FIndex adjFid = adj[e]; |
|
|
|
|
if (adjFid == NO_ID || adjFid <= fid) continue; // 只处理一次(无向边)
|
|
|
|
|
|
|
|
|
|
const uint32_t pidB = faceToPatchID[adjFid]; |
|
|
|
|
if (pidB == UINT32_MAX || pidB >= NP) continue; |
|
|
|
|
if (pidA == pidB) continue; // 同一 patch,不是接缝
|
|
|
|
|
|
|
|
|
|
// 找到共享边:face fid 的第 e 条边,与 face adjFid 共享两个顶点
|
|
|
|
|
const Mesh::Face& faceA = scene.mesh.faces[fid]; |
|
|
|
|
const Mesh::Face& faceB = scene.mesh.faces[adjFid]; |
|
|
|
|
|
|
|
|
|
// faceA 的第 e 条边的两个端点
|
|
|
|
|
const VIndex v0 = faceA[e]; |
|
|
|
|
const VIndex v1 = faceA[(e + 1) % 3]; |
|
|
|
|
|
|
|
|
|
seamEdges.push_back({v0, v1, pidA, pidB}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("LocalBlendSeams: found %zu 3D seam edges", seamEdges.size()); |
|
|
|
|
|
|
|
|
|
// ---- 3. 对每条接缝边,在 atlas 上做逐像素混合 ----
|
|
|
|
|
int blendedPixels = 0; |
|
|
|
|
const int blendWidth = 3; // 混合带宽(像素),可调整
|
|
|
|
|
|
|
|
|
|
for (const SeamEdgeInfo& edge : seamEdges) { |
|
|
|
|
// 取边两端点在 atlas 上的 UV(用 patchA 的 face 里的 UV)
|
|
|
|
|
// 需要找到该边在 patchA 的某个 face 上对应的两个 UV 坐标
|
|
|
|
|
// 简化:直接用两个端点的 UV(端点在所有包含它的 face 上的 UV 应该一致)
|
|
|
|
|
|
|
|
|
|
// 找一个包含 v0、v1 且属于 patchA 的 face
|
|
|
|
|
Point2f uvA0, uvA1, uvB0, uvB1; |
|
|
|
|
bool foundA = false, foundB = false; |
|
|
|
|
|
|
|
|
|
// 在 patchA 的 faces 里找包含 v0、v1 的边
|
|
|
|
|
for (FIndex fid : rcPatches[edge.patchA].faces) { |
|
|
|
|
if (fid >= numFaces) continue; |
|
|
|
|
const Mesh::Face& face = scene.mesh.faces[fid]; |
|
|
|
|
for (int e = 0; e < 3; ++e) { |
|
|
|
|
if ((face[e] == edge.v0 && face[(e+1)%3] == edge.v1) || |
|
|
|
|
(face[e] == edge.v1 && face[(e+1)%3] == edge.v0)) { |
|
|
|
|
uvA0 = Point2f(scene.mesh.faceTexcoords[fid * 3 + e].x * textureSize, |
|
|
|
|
scene.mesh.faceTexcoords[fid * 3 + e].y * textureSize); |
|
|
|
|
uvA1 = Point2f(scene.mesh.faceTexcoords[fid * 3 + (e+1)%3].x * textureSize, |
|
|
|
|
scene.mesh.faceTexcoords[fid * 3 + (e+1)%3].y * textureSize); |
|
|
|
|
foundA = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (foundA) break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (FIndex fid : rcPatches[edge.patchB].faces) { |
|
|
|
|
if (fid >= numFaces) continue; |
|
|
|
|
const Mesh::Face& face = scene.mesh.faces[fid]; |
|
|
|
|
for (int e = 0; e < 3; ++e) { |
|
|
|
|
if ((face[e] == edge.v0 && face[(e+1)%3] == edge.v1) || |
|
|
|
|
(face[e] == edge.v1 && face[(e+1)%3] == edge.v0)) { |
|
|
|
|
uvB0 = Point2f(scene.mesh.faceTexcoords[fid * 3 + e].x * textureSize, |
|
|
|
|
scene.mesh.faceTexcoords[fid * 3 + e].y * textureSize); |
|
|
|
|
uvB1 = Point2f(scene.mesh.faceTexcoords[fid * 3 + (e+1)%3].x * textureSize, |
|
|
|
|
scene.mesh.faceTexcoords[fid * 3 + (e+1)%3].y * textureSize); |
|
|
|
|
foundB = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (foundB) break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!foundA || !foundB) continue; |
|
|
|
|
|
|
|
|
|
// 在 atlas 上画一条从 (uvA0,uvA1) 到 (uvB0,uvB1) 的"四边形带"并混合
|
|
|
|
|
// 简化:沿边方向做线性插值混合
|
|
|
|
|
const int numSteps = (int)std::sqrt((uvA1.x - uvA0.x)*(uvA1.x - uvA0.x) + |
|
|
|
|
(uvA1.y - uvA0.y)*(uvA1.y - uvA0.y)) + 1; |
|
|
|
|
const int steps = std::max(numSteps, 2); |
|
|
|
|
|
|
|
|
|
for (int s = 0; s <= steps; ++s) { |
|
|
|
|
const float t = (float)s / steps; |
|
|
|
|
|
|
|
|
|
// 边上的点在 patchA 侧的位置
|
|
|
|
|
const Point2f ptA(uvA0.x + (uvA1.x - uvA0.x) * t, |
|
|
|
|
uvA0.y + (uvA1.y - uvA0.y) * t); |
|
|
|
|
// 对应 patchB 侧的位置(同一 3D 边,不同 UV 参数化)
|
|
|
|
|
const Point2f ptB(uvB0.x + (uvB1.x - uvB0.x) * t, |
|
|
|
|
uvB0.y + (uvB1.y - uvB0.y) * t); |
|
|
|
|
|
|
|
|
|
// 在 ptA 和 ptB 周围 blendWidth 像素内做混合
|
|
|
|
|
for (int dy = -blendWidth; dy <= blendWidth; ++dy) { |
|
|
|
|
for (int dx = -blendWidth; dx <= blendWidth; ++dx) { |
|
|
|
|
const int xA = (int)ptA.x + dx; |
|
|
|
|
const int yA = (int)ptA.y + dy; |
|
|
|
|
const int xB = (int)ptB.x + dx; |
|
|
|
|
const int yB = (int)ptB.y + dy; |
|
|
|
|
|
|
|
|
|
if (xA < 0 || yA < 0 || xA >= atlasMat.cols || yA >= atlasMat.rows) continue; |
|
|
|
|
if (xB < 0 || yB < 0 || xB >= atlasMat.cols || yB >= atlasMat.rows) continue; |
|
|
|
|
|
|
|
|
|
// 距离边的权重(越近混合越强)
|
|
|
|
|
const float w = 1.0f - (float)(dx*dx + dy*dy) / ((blendWidth+1)*(blendWidth+1)); |
|
|
|
|
if (w <= 0) continue; |
|
|
|
|
|
|
|
|
|
const cv::Vec3b& cA = atlasMat.at<cv::Vec3b>(yA, xA); |
|
|
|
|
const cv::Vec3b& cB = atlasMat.at<cv::Vec3b>(yB, xB); |
|
|
|
|
|
|
|
|
|
atlasMat.at<cv::Vec3b>(yA, xA) = cv::Vec3b( |
|
|
|
|
(uint8_t)(cA[0] * (1 - w) + cB[0] * w + 0.5f), |
|
|
|
|
(uint8_t)(cA[1] * (1 - w) + cB[1] * w + 0.5f), |
|
|
|
|
(uint8_t)(cA[2] * (1 - w) + cB[2] * w + 0.5f) |
|
|
|
|
); |
|
|
|
|
blendedPixels++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("LocalBlendSeams: %zu edges blended", rcSeamEdges.size()); |
|
|
|
|
DEBUG_EXTRA("LocalBlendSeams: blended %d pixels along 3D seams", blendedPixels); |
|
|
|
|
} |
|
|
|
|
// ============================================================
|
|
|
|
|
// 把 rcPatches 体系转换为标准 texturePatches + seamVertices 体系
|
|
|
|
|
@ -15401,7 +15977,9 @@ void MeshTexture::BuildStandardSeamDataFromRCPatches(
@@ -15401,7 +15977,9 @@ void MeshTexture::BuildStandardSeamDataFromRCPatches(
|
|
|
|
|
seamVertices.push_back(sv); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("mapIdxPatch: %zu faces, unique patch IDs: %zu", |
|
|
|
|
mapIdxPatch.size(), |
|
|
|
|
std::unordered_set<uint32_t>(mapIdxPatch.begin(), mapIdxPatch.end()).size()); |
|
|
|
|
DEBUG_EXTRA("BuildStandardSeamData: %zu patches, %zu seamVertices, %zu edges", |
|
|
|
|
NP, seamVertices.size(), rcSeamEdges.size()); |
|
|
|
|
} |
|
|
|
|
@ -15546,69 +16124,6 @@ bool MeshTexture::EstimateGlobalPhotometricCorrection()
@@ -15546,69 +16124,6 @@ bool MeshTexture::EstimateGlobalPhotometricCorrection()
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// 构建接缝边(基于 rcPatches + faceFaces)
|
|
|
|
|
// ============================================================
|
|
|
|
|
void MeshTexture::BuildSeamEdgesFromRCPatches() |
|
|
|
|
{ |
|
|
|
|
rcSeamEdges.clear(); |
|
|
|
|
seamEdges.clear(); // ✅ 同时清空老的
|
|
|
|
|
|
|
|
|
|
// face → rcPatch 映射
|
|
|
|
|
std::vector<uint32_t> faceToRCPatch(scene.mesh.faces.size(), NO_ID); |
|
|
|
|
for (uint32_t pi = 0; pi < rcPatches.size(); ++pi) { |
|
|
|
|
for (FIndex f : rcPatches[pi].faces) { |
|
|
|
|
if (f < (FIndex)faceToRCPatch.size()) |
|
|
|
|
faceToRCPatch[f] = pi; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (FIndex f0 = 0; f0 < (FIndex)scene.mesh.faces.size(); ++f0) { |
|
|
|
|
uint32_t p0 = faceToRCPatch[f0]; |
|
|
|
|
if (p0 == NO_ID) continue; |
|
|
|
|
|
|
|
|
|
const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[f0]; |
|
|
|
|
for (int e = 0; e < 3; ++e) { |
|
|
|
|
FIndex f1 = neighbors[e]; |
|
|
|
|
if (f1 == NO_ID) continue; |
|
|
|
|
|
|
|
|
|
uint32_t p1 = faceToRCPatch[f1]; |
|
|
|
|
if (p1 == NO_ID || p1 == p0) continue; |
|
|
|
|
|
|
|
|
|
const Mesh::Face& face0 = scene.mesh.faces[f0]; |
|
|
|
|
int v0_idx = e; |
|
|
|
|
int v1_idx = (e + 1) % 3; |
|
|
|
|
|
|
|
|
|
int idx0_in_f1 = -1, idx1_in_f1 = -1; |
|
|
|
|
for (int k = 0; k < 3; ++k) { |
|
|
|
|
if (scene.mesh.faces[f1][k] == face0[v0_idx]) idx0_in_f1 = k; |
|
|
|
|
if (scene.mesh.faces[f1][k] == face0[v1_idx]) idx1_in_f1 = k; |
|
|
|
|
} |
|
|
|
|
if (idx0_in_f1 == -1 || idx1_in_f1 == -1) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
const TexCoord& uv0_p0 = scene.mesh.faceTexcoords[f0 * 3 + v0_idx]; |
|
|
|
|
const TexCoord& uv1_p0 = scene.mesh.faceTexcoords[f0 * 3 + v1_idx]; |
|
|
|
|
|
|
|
|
|
// ✅ 存进你自己的 rcSeamEdges
|
|
|
|
|
SeamEdge edge; |
|
|
|
|
edge.rcPatchID0 = p0; |
|
|
|
|
edge.rcPatchID1 = p1; |
|
|
|
|
edge.faceID0 = f0; |
|
|
|
|
edge.faceID1 = f1; |
|
|
|
|
edge.uv0 = uv0_p0; |
|
|
|
|
edge.uv1 = uv1_p0; |
|
|
|
|
rcSeamEdges.push_back(edge); // ✅ 改回 rcSeamEdges
|
|
|
|
|
|
|
|
|
|
// ✅ 同步往老的 seamEdges 里塞 PairIdx(供 SeamBlendingFromOriginalImages 用)
|
|
|
|
|
seamEdges.Insert(PairIdx(f0, f1)); // 或 seamEdges.emplace_back(f0, f1);
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Built %zu RC seam edges, %zu legacy seam edges", |
|
|
|
|
rcSeamEdges.size(), seamEdges.size()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// 接缝融合:从原始图像采样 + YCrCb 羽化
|
|
|
|
|
// ============================================================
|
|
|
|
|
@ -20861,6 +21376,7 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
@@ -20861,6 +21376,7 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
texture.MergeSameViewPatches(); |
|
|
|
|
texture.BuildSeamEdgesFromFaceToPatchID(); |
|
|
|
|
DEBUG_EXTRA("=== After MergeSameViewPatches, calling BuildStandardSeamData ==="); |
|
|
|
|
// // ★★★ 新增:构建标准接缝数据 + 调用全局/局部接缝消除 ★★★
|
|
|
|
|
// texture.BuildStandardSeamDataFromRCPatches(
|
|
|
|
|
@ -20869,8 +21385,9 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
@@ -20869,8 +21385,9 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
|
|
|
|
|
// // 直接操作 atlas(不污染 images)
|
|
|
|
|
// texture.RunSeamLevelingOnAtlas(textures.back(), nTextureSizeMultiple);
|
|
|
|
|
|
|
|
|
|
texture.GlobalAlignPatches(textures.back(), nTextureSizeMultiple); |
|
|
|
|
texture.LocalBlendSeams(textures.back(), nTextureSizeMultiple); |
|
|
|
|
// texture.RunGlobalColorOptimization(textures.back(), nTextureSizeMultiple, 0.1f); // λ=0.1 对齐 OpenMVS
|
|
|
|
|
// texture.GlobalAlignPatches(textures.back(), nTextureSizeMultiple);
|
|
|
|
|
// texture.LocalBlendSeams(textures.back(), nTextureSizeMultiple);
|
|
|
|
|
|
|
|
|
|
mesh.texturesDiffuse = std::move(textures); |
|
|
|
|
DEBUG_EXTRA("Existing UV texturing completed: %u faces (%s)", |
|
|
|
|
|