|
|
|
@ -44,6 +44,12 @@ |
|
|
|
#include <sstream> |
|
|
|
#include <sstream> |
|
|
|
#include <filesystem> |
|
|
|
#include <filesystem> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <Eigen/Sparse> |
|
|
|
|
|
|
|
#include <Eigen/SparseCholesky> |
|
|
|
|
|
|
|
#include <vector> |
|
|
|
|
|
|
|
#include <unordered_map> |
|
|
|
|
|
|
|
#include <algorithm> |
|
|
|
|
|
|
|
|
|
|
|
namespace fs = std::filesystem; |
|
|
|
namespace fs = std::filesystem; |
|
|
|
|
|
|
|
|
|
|
|
// namespace py = pybind11;
|
|
|
|
// namespace py = pybind11;
|
|
|
|
@ -731,7 +737,8 @@ public: |
|
|
|
|
|
|
|
|
|
|
|
return p; |
|
|
|
return p; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// ★ 只需新增这一行(其余类型取自你现有头文件)
|
|
|
|
|
|
|
|
using GSLTriplet = Eigen::Triplet<float, Eigen::Index>; |
|
|
|
uint32_t FindNearestPatchForFaces(const std::vector<FIndex>& faceIndices); |
|
|
|
uint32_t FindNearestPatchForFaces(const std::vector<FIndex>& faceIndices); |
|
|
|
void FixIsolatedComponents(); |
|
|
|
void FixIsolatedComponents(); |
|
|
|
void ReinitializeSeamData(); |
|
|
|
void ReinitializeSeamData(); |
|
|
|
@ -776,6 +783,7 @@ public: |
|
|
|
std::vector<uint32_t> faceToPatchID; // faceID → 新 patchID 映射
|
|
|
|
std::vector<uint32_t> faceToPatchID; // faceID → 新 patchID 映射
|
|
|
|
void MergeSameViewPatches(); |
|
|
|
void MergeSameViewPatches(); |
|
|
|
void BuildSeamEdgesFromFaceToPatchID(); |
|
|
|
void BuildSeamEdgesFromFaceToPatchID(); |
|
|
|
|
|
|
|
std::vector<cv::Mat> correctedPatchImages; // GSL4 apply 输出,重光栅化消费
|
|
|
|
// 桥接:用 rcPatches + VirtualFaceMap 构建 TexturePatch 并调用 GlobalSeamLeveling3
|
|
|
|
// 桥接:用 rcPatches + VirtualFaceMap 构建 TexturePatch 并调用 GlobalSeamLeveling3
|
|
|
|
void ApplyGlobalSeamLevelingOnRCPatches( |
|
|
|
void ApplyGlobalSeamLevelingOnRCPatches( |
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
@ -8766,7 +8774,6 @@ uint32_t MeshTexture::FindNearestPatchForFaces(const std::vector<FIndex>& faceIn |
|
|
|
|
|
|
|
|
|
|
|
return bestPatch; |
|
|
|
return bestPatch; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void MeshTexture::CreateSeamVertices() |
|
|
|
void MeshTexture::CreateSeamVertices() |
|
|
|
{ |
|
|
|
{ |
|
|
|
DEBUG_EXTRA(">>> CreateSeamVertices CALLED"); |
|
|
|
DEBUG_EXTRA(">>> CreateSeamVertices CALLED"); |
|
|
|
@ -8783,90 +8790,171 @@ void MeshTexture::CreateSeamVertices() |
|
|
|
|
|
|
|
|
|
|
|
int validEdges = 0; |
|
|
|
int validEdges = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 用 map 缓存顶点→seamVertex 索引
|
|
|
|
|
|
|
|
std::unordered_map<VIndex, uint32_t> mapVertexSeam; |
|
|
|
|
|
|
|
mapVertexSeam.reserve(vertices.size() / 4); |
|
|
|
|
|
|
|
|
|
|
|
for (uint32_t edgeIdx = 0; edgeIdx < seamEdges.GetSize(); ++edgeIdx) { |
|
|
|
for (uint32_t edgeIdx = 0; edgeIdx < seamEdges.GetSize(); ++edgeIdx) { |
|
|
|
const PairIdx& edge = seamEdges[edgeIdx]; |
|
|
|
const PairIdx& edge = seamEdges[edgeIdx]; |
|
|
|
|
|
|
|
|
|
|
|
// ★ 每条边都检查基本范围
|
|
|
|
if (edge.i >= faces.GetSize() || edge.j >= faces.GetSize()) continue; |
|
|
|
if (edge.i >= faces.GetSize() || edge.j >= faces.GetSize()) { |
|
|
|
if (edge.i >= components.size() || edge.j >= components.size()) continue; |
|
|
|
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] face OOB: %u %u", edgeIdx, edge.i, edge.j); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (edge.i >= components.size() || edge.j >= components.size()) { |
|
|
|
|
|
|
|
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] comp OOB: %u %u", edgeIdx, edge.i, edge.j); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const uint32_t comp0 = components[edge.i]; |
|
|
|
const uint32_t comp0 = components[edge.i]; |
|
|
|
const uint32_t comp1 = components[edge.j]; |
|
|
|
const uint32_t comp1 = components[edge.j]; |
|
|
|
|
|
|
|
|
|
|
|
if (comp0 == NO_ID || comp1 == NO_ID) { |
|
|
|
if (comp0 == NO_ID || comp1 == NO_ID) continue; |
|
|
|
continue; |
|
|
|
if (comp0 >= mapIdxPatch.GetSize() || comp1 >= mapIdxPatch.GetSize()) continue; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (comp0 >= mapIdxPatch.GetSize() || comp1 >= mapIdxPatch.GetSize()) { |
|
|
|
|
|
|
|
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] comp OOR: comp0=%u comp1=%u mapSz=%u", |
|
|
|
|
|
|
|
edgeIdx, comp0, comp1, mapIdxPatch.GetSize()); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const uint32_t idxPatch0 = mapIdxPatch[comp0]; |
|
|
|
const uint32_t idxPatch0 = mapIdxPatch[comp0]; |
|
|
|
const uint32_t idxPatch1 = mapIdxPatch[comp1]; |
|
|
|
const uint32_t idxPatch1 = mapIdxPatch[comp1]; |
|
|
|
|
|
|
|
|
|
|
|
if (idxPatch0 >= texturePatches.size() || idxPatch1 >= texturePatches.size()) { |
|
|
|
if (idxPatch0 >= texturePatches.size() || idxPatch1 >= texturePatches.size()) continue; |
|
|
|
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] patch OOR: ip0=%u ip1=%u tpSz=%zu", |
|
|
|
if (idxPatch0 == idxPatch1) continue; // ★ 同一 patch,不是 seam
|
|
|
|
edgeIdx, idxPatch0, idxPatch1, texturePatches.size()); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (idxPatch0 == idxPatch1) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 打印第一条边看看
|
|
|
|
|
|
|
|
if (edgeIdx == 0) { |
|
|
|
if (edgeIdx == 0) { |
|
|
|
DEBUG_EXTRA(" FIRST EDGE: faceA=%u compA=%u patchA=%u | faceB=%u compB=%u patchB=%u", |
|
|
|
DEBUG_EXTRA(" FIRST EDGE: faceA=%u compA=%u patchA=%u | faceB=%u compB=%u patchB=%u", |
|
|
|
edge.i, comp0, idxPatch0, edge.j, comp1, idxPatch1); |
|
|
|
edge.i, comp0, idxPatch0, edge.j, comp1, idxPatch1); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 获取这条 seam edge 的两个共享顶点
|
|
|
|
VIndex vs[2]; |
|
|
|
VIndex vs[2]; |
|
|
|
uint32_t vs0[2], vs1[2]; |
|
|
|
uint32_t vs0[2], vs1[2]; |
|
|
|
|
|
|
|
|
|
|
|
// ★ 安全调用 GetEdgeVertices
|
|
|
|
|
|
|
|
scene.mesh.GetEdgeVertices(edge.i, edge.j, vs0, vs1); |
|
|
|
scene.mesh.GetEdgeVertices(edge.i, edge.j, vs0, vs1); |
|
|
|
|
|
|
|
|
|
|
|
const Face& faceI = faces[edge.i]; |
|
|
|
const Face& faceI = faces[edge.i]; |
|
|
|
const Face& faceJ = faces[edge.j]; |
|
|
|
const Face& faceJ = faces[edge.j]; |
|
|
|
|
|
|
|
|
|
|
|
if (vs0[0] >= 3 || vs0[1] >= 3 || vs1[0] >= 3 || vs1[1] >= 3 || |
|
|
|
if (vs0[0] >= 3 || vs0[1] >= 3 || vs1[0] >= 3 || vs1[1] >= 3) continue; |
|
|
|
faceI[vs0[0]] != faceJ[vs1[0]] || faceI[vs0[1]] != faceJ[vs1[1]]) { |
|
|
|
if (faceI[vs0[0]] != faceJ[vs1[0]] || faceI[vs0[1]] != faceJ[vs1[1]]) continue; |
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vs[0] = faceI[vs0[0]]; |
|
|
|
vs[0] = faceI[vs0[0]]; |
|
|
|
vs[1] = faceI[vs0[1]]; |
|
|
|
vs[1] = faceI[vs0[1]]; |
|
|
|
|
|
|
|
|
|
|
|
if (vs[0] >= vertices.size() || vs[1] >= vertices.size()) { |
|
|
|
if (vs[0] >= vertices.size() || vs[1] >= vertices.size()) continue; |
|
|
|
if (edgeIdx < 5) DEBUG_EXTRA(" edge[%u] vertex OOB: %u %u vsSz=%zu", |
|
|
|
|
|
|
|
edgeIdx, vs[0], vs[1], vertices.size()); |
|
|
|
// ★★★ 关键:为两个端点各创建一个 seamVertex(如果还没有),
|
|
|
|
continue; |
|
|
|
// 然后让每个端点记录"对面的 patch",形成 patches.size() >= 2 ★★★
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 先确保两个 seamVertex 存在
|
|
|
|
|
|
|
|
for (int endpoint = 0; endpoint < 2; ++endpoint) { |
|
|
|
|
|
|
|
const VIndex vIdx = vs[endpoint]; |
|
|
|
|
|
|
|
auto it = mapVertexSeam.emplace(vIdx, static_cast<uint32_t>(seamVertices.GetSize())); |
|
|
|
|
|
|
|
if (it.second) { |
|
|
|
|
|
|
|
seamVertices.emplace_back(vIdx); // ★ SeamVertex(vIdx)
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ★ 用 static 的 map 避免重复构造
|
|
|
|
// 端点 0:属于 patch0,需要记录"通过这条边能到达 patch1"
|
|
|
|
static std::unordered_map<VIndex, uint32_t> mapVertexSeam; |
|
|
|
// 端点 1:属于 patch1,需要记录"通过这条边能到达 patch0"
|
|
|
|
if (edgeIdx == 0) mapVertexSeam.clear(); |
|
|
|
// → 交叉:每个端点把"对面 patch"加进自己的 patches
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int endpoint = 0; endpoint < 2; ++endpoint) { |
|
|
|
|
|
|
|
const VIndex vIdx = vs[endpoint]; |
|
|
|
|
|
|
|
const uint32_t myPatch = (endpoint == 0) ? idxPatch0 : idxPatch1; |
|
|
|
|
|
|
|
const uint32_t otherPatch = (endpoint == 0) ? idxPatch1 : idxPatch0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SeamVertex& sv = seamVertices[mapVertexSeam[vIdx]]; |
|
|
|
|
|
|
|
|
|
|
|
auto it0 = mapVertexSeam.emplace(vs[0], static_cast<uint32_t>(seamVertices.GetSize())); |
|
|
|
// ★ 用 GetPatch:如果 otherPatch 已存在就返回已有,否则新建(自动去重)
|
|
|
|
if (it0.second) seamVertices.emplace_back(vs[0]); |
|
|
|
SeamVertex::Patch& patch = sv.GetPatch(otherPatch); |
|
|
|
auto it1 = mapVertexSeam.emplace(vs[1], static_cast<uint32_t>(seamVertices.GetSize())); |
|
|
|
|
|
|
|
if (it1.second) seamVertices.emplace_back(vs[1]); |
|
|
|
// ★ 记录这条边:从 sv 出发,经过对面的 patch,到达另一个端点
|
|
|
|
|
|
|
|
// edge.idxSeamVertex = 另一个端点的 seamVertex 索引
|
|
|
|
|
|
|
|
const VIndex otherVIdx = vs[1 - endpoint]; |
|
|
|
|
|
|
|
const uint32_t otherSvIdx = mapVertexSeam[otherVIdx]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 避免重复添加同一条边
|
|
|
|
|
|
|
|
bool edgeExists = false; |
|
|
|
|
|
|
|
for (const auto& e : patch.edges) { |
|
|
|
|
|
|
|
if (e.idxSeamVertex == otherSvIdx) { edgeExists = true; break; } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!edgeExists) { |
|
|
|
|
|
|
|
patch.edges.emplace_back(otherSvIdx); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 计算 proj:该顶点在"对面 patch"纹理空间中的坐标
|
|
|
|
|
|
|
|
// proj 用 faceTexcoords 里对应的 UV(在 GSL4 前必须已填充 faceTexcoords)
|
|
|
|
|
|
|
|
// 这里用 faceJ(对面面)的顶点在 otherPatch 里的 texcoord
|
|
|
|
|
|
|
|
// —— 简化:用面中心投影,或直接留 0(GSL4 会用 SampleImage 重采样)
|
|
|
|
|
|
|
|
// 原版是在 AddEdge 后用 SampleImage 根据面片 UV 插值,这里先设为 0,
|
|
|
|
|
|
|
|
// 后续如果需要精确采样,再从 faceTexcoords 取
|
|
|
|
|
|
|
|
if (patch.proj == Point2f::ZERO) { |
|
|
|
|
|
|
|
// 取该顶点在"对面面"的某个 texcoord 作为近似投影
|
|
|
|
|
|
|
|
// 找到 vIdx 在 faceJ 中的局部索引
|
|
|
|
|
|
|
|
for (int lv = 0; lv < 3; ++lv) { |
|
|
|
|
|
|
|
if (faceJ[lv] == vIdx) { |
|
|
|
|
|
|
|
// faceTexcoords 是 (fid*3 + v) 的布局
|
|
|
|
|
|
|
|
const TexCoord& tc = faceTexcoords[(endpoint == 0 ? edge.j : edge.i) * 3 + lv]; |
|
|
|
|
|
|
|
patch.proj = Point2f(tc.x, tc.y); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ... 后面填充 patch 的边和 proj 的代码保持不变 ...
|
|
|
|
// ★ 抑制 unused 警告
|
|
|
|
|
|
|
|
(void)myPatch; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
validEdges++; |
|
|
|
validEdges++; |
|
|
|
if (edgeIdx == 0) DEBUG_EXTRA(" First edge processed OK"); |
|
|
|
if (edgeIdx == 0) DEBUG_EXTRA(" First edge processed OK"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Seam vertices created: %u vertices, %d valid edges", seamVertices.GetSize(), validEdges); |
|
|
|
DEBUG_EXTRA("Seam vertices created: %u vertices, %d valid edges", seamVertices.GetSize(), validEdges); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 诊断:统计每个 seamVertex 的 patch 数量
|
|
|
|
|
|
|
|
size_t svSingle = 0, svMulti = 0, svMaxPatches = 0; |
|
|
|
|
|
|
|
for (const SeamVertex& sv : seamVertices) { |
|
|
|
|
|
|
|
if (sv.patches.size() < 2) svSingle++; |
|
|
|
|
|
|
|
else { svMulti++; svMaxPatches = std::max(svMaxPatches, (size_t)sv.patches.size()); } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
DEBUG_EXTRA(" patch stats: single(patches<2)=%zu, multi(>=2)=%zu, maxPatches=%zu", |
|
|
|
|
|
|
|
svSingle, svMulti, svMaxPatches); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★★★ 截断 seamVertices 到合理规模(原版 OpenMVS 通常 < 50000)★★★
|
|
|
|
|
|
|
|
// 按 patches 数量排序,保留最重要的(patches 多的 = 色差严重的接缝)
|
|
|
|
|
|
|
|
const size_t MAX_SEAM_VERTICES = 40000; // ← 可调,先试 40000
|
|
|
|
|
|
|
|
if (seamVertices.GetSize() > MAX_SEAM_VERTICES) { |
|
|
|
|
|
|
|
DEBUG_EXTRA(" Truncating seamVertices: %u -> %zu (keeping highest patch-count)", |
|
|
|
|
|
|
|
seamVertices.GetSize(), MAX_SEAM_VERTICES); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 建 (索引, patch数量) 对,按 patch 数量降序
|
|
|
|
|
|
|
|
std::vector<std::pair<uint32_t, size_t>> idxAndCount(seamVertices.GetSize()); |
|
|
|
|
|
|
|
for (uint32_t i = 0; i < seamVertices.GetSize(); ++i) |
|
|
|
|
|
|
|
idxAndCount[i] = {i, seamVertices[i].patches.size()}; |
|
|
|
|
|
|
|
std::sort(idxAndCount.begin(), idxAndCount.end(), |
|
|
|
|
|
|
|
[](const auto& a, const auto& b) { return a.second > b.second; }); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 选前 MAX_SEAM_VERTICES 个索引,排序回原始顺序以保持稳定性
|
|
|
|
|
|
|
|
std::vector<uint32_t> keep(idxAndCount.size()); |
|
|
|
|
|
|
|
for (size_t i = 0; i < MAX_SEAM_VERTICES; ++i) keep[i] = idxAndCount[i].first; |
|
|
|
|
|
|
|
std::sort(keep.begin(), keep.begin() + MAX_SEAM_VERTICES); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 重建 seamVertices(只保留选中的)
|
|
|
|
|
|
|
|
decltype(seamVertices) newSV; |
|
|
|
|
|
|
|
newSV.Reserve(MAX_SEAM_VERTICES); |
|
|
|
|
|
|
|
std::unordered_map<uint32_t, uint32_t> old2new; // 旧索引 → 新索引
|
|
|
|
|
|
|
|
for (size_t i = 0; i < MAX_SEAM_VERTICES; ++i) { |
|
|
|
|
|
|
|
const uint32_t oldIdx = keep[i]; |
|
|
|
|
|
|
|
old2new[oldIdx] = (uint32_t)newSV.GetSize(); |
|
|
|
|
|
|
|
newSV.push_back(std::move(seamVertices[oldIdx])); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★★★ 关键:更新所有 Patch::edges 里的 idxSeamVertex 引用 ★★★
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < newSV.GetSize(); ++i) { |
|
|
|
|
|
|
|
for (auto& patch : newSV[i].patches) { |
|
|
|
|
|
|
|
for (auto& edge : patch.edges) { |
|
|
|
|
|
|
|
auto it = old2new.find(edge.idxSeamVertex); |
|
|
|
|
|
|
|
if (it != old2new.end()) |
|
|
|
|
|
|
|
edge.idxSeamVertex = it->second; |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
edge.idxSeamVertex = i; // 指向自己(会被忽略)
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
seamVertices = std::move(newSV); |
|
|
|
|
|
|
|
DEBUG_EXTRA(" Truncated seamVertices: %u final", seamVertices.GetSize()); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Native
|
|
|
|
// Native
|
|
|
|
@ -9104,6 +9192,7 @@ void MeshTexture::GlobalSeamLeveling3() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void MeshTexture::GlobalSeamLeveling4() |
|
|
|
void MeshTexture::GlobalSeamLeveling4() |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (seamVertices.empty()) { |
|
|
|
if (seamVertices.empty()) { |
|
|
|
@ -9140,12 +9229,9 @@ void MeshTexture::GlobalSeamLeveling4() |
|
|
|
patchIndices.Memset(0); |
|
|
|
patchIndices.Memset(0); |
|
|
|
FOREACH(f, faces) { |
|
|
|
FOREACH(f, faces) { |
|
|
|
if (components[f] == NO_ID) { |
|
|
|
if (components[f] == NO_ID) { |
|
|
|
// ★ 无效面:指向 dummy,保证不越界
|
|
|
|
|
|
|
|
// 但 patchIndices 需要一个合法 idxPatch,用 numPatches(dummy)
|
|
|
|
|
|
|
|
// 后面会检查 idxPatch < numPatches,所以这里用 numPatches 会被跳过
|
|
|
|
|
|
|
|
const Face& face = faces[f]; |
|
|
|
const Face& face = faces[f]; |
|
|
|
for (int v = 0; v < 3; ++v) |
|
|
|
for (int v = 0; v < 3; ++v) |
|
|
|
patchIndices[face[v]].idxPatch = numPatches; // dummy
|
|
|
|
patchIndices[face[v]].idxPatch = numPatches; // dummy,后面跳过
|
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
const uint32_t idxPatch(mapIdxPatch[components[f]]); |
|
|
|
const uint32_t idxPatch(mapIdxPatch[components[f]]); |
|
|
|
@ -9157,16 +9243,17 @@ void MeshTexture::GlobalSeamLeveling4() |
|
|
|
FOREACH(i, seamVertices) { |
|
|
|
FOREACH(i, seamVertices) { |
|
|
|
const SeamVertex& seamVertex = seamVertices[i]; |
|
|
|
const SeamVertex& seamVertex = seamVertices[i]; |
|
|
|
ASSERT(!seamVertex.patches.empty()); |
|
|
|
ASSERT(!seamVertex.patches.empty()); |
|
|
|
|
|
|
|
ASSERT(seamVertex.idxVertex < vertices.size()); |
|
|
|
PatchIndex& patchIndex = patchIndices[seamVertex.idxVertex]; |
|
|
|
PatchIndex& patchIndex = patchIndices[seamVertex.idxVertex]; |
|
|
|
patchIndex.bIndex = true; |
|
|
|
patchIndex.bIndex = true; |
|
|
|
patchIndex.idxSeamVertex = i; |
|
|
|
patchIndex.idxSeamVertex = i; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ========== 构建 vertpatch2rows ==========
|
|
|
|
// ========== 构建 vertpatch2rows(块列号 → 连续行)==========
|
|
|
|
ASSERT(vertices.size() < static_cast<VIndex>(std::numeric_limits<MatIdx>::max())); |
|
|
|
ASSERT(vertices.size() < static_cast<VIndex>(std::numeric_limits<MatIdx>::max())); |
|
|
|
MatIdx rowsX(0); |
|
|
|
MatIdx rowsX(0); |
|
|
|
typedef std::unordered_map<uint32_t, MatIdx> VertexPatch2RowMap; |
|
|
|
typedef std::unordered_map<uint32_t, MatIdx> VertexPatch2RowMap; |
|
|
|
cList<VertexPatch2RowMap> vertpatch2rows(vertices.size()); |
|
|
|
std::vector<VertexPatch2RowMap> vertpatch2rows(vertices.size()); |
|
|
|
|
|
|
|
|
|
|
|
FOREACH(i, vertices) { |
|
|
|
FOREACH(i, vertices) { |
|
|
|
const PatchIndex& patchIndex = patchIndices[i]; |
|
|
|
const PatchIndex& patchIndex = patchIndices[i]; |
|
|
|
@ -9176,186 +9263,219 @@ void MeshTexture::GlobalSeamLeveling4() |
|
|
|
ASSERT(seamVertex.idxVertex == i); |
|
|
|
ASSERT(seamVertex.idxVertex == i); |
|
|
|
for (const SeamVertex::Patch& patch : seamVertex.patches) { |
|
|
|
for (const SeamVertex::Patch& patch : seamVertex.patches) { |
|
|
|
ASSERT(patch.idxPatch != numPatches); |
|
|
|
ASSERT(patch.idxPatch != numPatches); |
|
|
|
ASSERT(patch.idxPatch < numPatches); // ★ 双重检查
|
|
|
|
ASSERT(patch.idxPatch < numPatches); |
|
|
|
|
|
|
|
if (vertpatch2row.find(patch.idxPatch) != vertpatch2row.end()) |
|
|
|
|
|
|
|
continue; // 去重
|
|
|
|
vertpatch2row[patch.idxPatch] = rowsX++; |
|
|
|
vertpatch2row[patch.idxPatch] = rowsX++; |
|
|
|
} |
|
|
|
} |
|
|
|
} else if (patchIndex.idxPatch < numPatches) { |
|
|
|
} else if (patchIndex.idxPatch < numPatches) { |
|
|
|
vertpatch2row[patchIndex.idxPatch] = rowsX++; |
|
|
|
vertpatch2row[patchIndex.idxPatch] = rowsX++; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
DEBUG_EXTRA("vertpatch2rows built: rowsX=%u", rowsX); |
|
|
|
const Eigen::Index scalarCols = (Eigen::Index)rowsX * 3; // 每块 3 个标量列 (RGB)
|
|
|
|
|
|
|
|
DEBUG_EXTRA("vertpatch2rows built: rowsX(blocks)=%u, scalarCols=%lld", rowsX, (long long)scalarCols); |
|
|
|
|
|
|
|
|
|
|
|
// ========== 构建 Gamma(Tikhonov 正则)==========
|
|
|
|
// ============================================================
|
|
|
|
const float lambda(0.1f); |
|
|
|
// ========== 构建 Gamma(Tikhonov 正则)========== [修复1: 去重]
|
|
|
|
MatIdx rowsGamma(0); |
|
|
|
// ============================================================
|
|
|
|
Mesh::VertexIdxArr adjVerts; |
|
|
|
std::vector<GSLTriplet> gammaTriplets; |
|
|
|
CLISTDEF0(MatEntry) rows(0, vertices.size() * 4); |
|
|
|
gammaTriplets.reserve((size_t)vertices.size() * 8); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct GammaRaw { Eigen::Index colA, colB; float w; }; |
|
|
|
|
|
|
|
std::vector<GammaRaw> rawGamma; |
|
|
|
|
|
|
|
rawGamma.reserve(vertices.size() * 6); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Mesh::VertexIdxArr adjVerts; |
|
|
|
FOREACH(v, vertices) { |
|
|
|
FOREACH(v, vertices) { |
|
|
|
adjVerts.Empty(); |
|
|
|
adjVerts.Empty(); |
|
|
|
scene.mesh.GetAdjVertices(v, adjVerts); |
|
|
|
scene.mesh.GetAdjVertices(v, adjVerts); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const size_t MAX_ADJ = 8; // ★ 限制邻接度数,控制规模
|
|
|
|
|
|
|
|
if (adjVerts.GetSize() > MAX_ADJ) adjVerts.Resize(MAX_ADJ); |
|
|
|
|
|
|
|
|
|
|
|
VertexPatchIterator itV(patchIndices[v], seamVertices); |
|
|
|
VertexPatchIterator itV(patchIndices[v], seamVertices); |
|
|
|
while (itV.Next()) { |
|
|
|
while (itV.Next()) { |
|
|
|
const uint32_t idxPatch(itV); |
|
|
|
const uint32_t idxPatch(itV); |
|
|
|
if (idxPatch == numPatches) |
|
|
|
if (idxPatch == numPatches) continue; |
|
|
|
continue; |
|
|
|
auto it_row = vertpatch2rows[v].find(idxPatch); |
|
|
|
const MatIdx col(vertpatch2rows[v].at(idxPatch)); |
|
|
|
if (it_row == vertpatch2rows[v].end()) continue; |
|
|
|
|
|
|
|
const Eigen::Index colA = it_row->second; |
|
|
|
|
|
|
|
|
|
|
|
for (const VIndex vAdj : adjVerts) { |
|
|
|
for (const VIndex vAdj : adjVerts) { |
|
|
|
if (v >= vAdj) |
|
|
|
if (v >= vAdj) continue; // ★ 无向边只处理一次(去重关键)
|
|
|
|
continue; |
|
|
|
|
|
|
|
VertexPatchIterator itVAdj(patchIndices[vAdj], seamVertices); |
|
|
|
VertexPatchIterator itVAdj(patchIndices[vAdj], seamVertices); |
|
|
|
while (itVAdj.Next()) { |
|
|
|
while (itVAdj.Next()) { |
|
|
|
const uint32_t idxPatchAdj(itVAdj); |
|
|
|
const uint32_t idxPatchAdj(itVAdj); |
|
|
|
if (idxPatch == idxPatchAdj) { |
|
|
|
if (idxPatch != idxPatchAdj) continue; // 同一 patch 才正则化
|
|
|
|
const MatIdx colAdj(vertpatch2rows[vAdj].at(idxPatchAdj)); |
|
|
|
auto it_rowAdj = vertpatch2rows[vAdj].find(idxPatchAdj); |
|
|
|
float currentLambda = (vertexInvalid[v] || vertexInvalid[vAdj]) ? 0.01f : 0.1f; |
|
|
|
if (it_rowAdj == vertpatch2rows[vAdj].end()) continue; |
|
|
|
rows.emplace_back(rowsGamma, col, currentLambda); |
|
|
|
const Eigen::Index colB = it_rowAdj->second; |
|
|
|
rows.emplace_back(rowsGamma, colAdj, -currentLambda); |
|
|
|
|
|
|
|
++rowsGamma; |
|
|
|
const float w = (vertexInvalid[v] || vertexInvalid[vAdj]) ? 0.05f : 0.5f; |
|
|
|
} |
|
|
|
rawGamma.push_back({colA, colB, w}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
ASSERT(rows.size() / 2 < static_cast<IDX>(std::numeric_limits<MatIdx>::max())); |
|
|
|
|
|
|
|
DEBUG_EXTRA("Gamma rows: %zu matEntries", rows.size()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SparseMat Gamma(rowsGamma, rowsX); |
|
|
|
// ★ 排序 + 去重合并(消除重复邻接 → 保证矩阵性质良好)
|
|
|
|
Gamma.setFromTriplets(rows.Begin(), rows.End()); |
|
|
|
std::sort(rawGamma.begin(), rawGamma.end(), |
|
|
|
rows.Empty(); |
|
|
|
[](const GammaRaw& a, const GammaRaw& b) { |
|
|
|
|
|
|
|
return a.colA < b.colA || (a.colA == b.colA && a.colB < b.colB); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eigen::Index rowG = 0; |
|
|
|
|
|
|
|
for (size_t i = 0; i < rawGamma.size(); ) { |
|
|
|
|
|
|
|
size_t j = i + 1; |
|
|
|
|
|
|
|
while (j < rawGamma.size() && rawGamma[j].colA == rawGamma[i].colA && rawGamma[j].colB == rawGamma[i].colB) |
|
|
|
|
|
|
|
++j; |
|
|
|
|
|
|
|
const float w = rawGamma[i].w; |
|
|
|
|
|
|
|
gammaTriplets.emplace_back(rowG, rawGamma[i].colA, w); |
|
|
|
|
|
|
|
gammaTriplets.emplace_back(rowG, rawGamma[i].colB, -w); |
|
|
|
|
|
|
|
++rowG; |
|
|
|
|
|
|
|
i = j; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
const Eigen::Index rowsGamma = rowG; |
|
|
|
|
|
|
|
DEBUG_EXTRA("Gamma after dedup: triplets=%zu, rowsGamma=%lld, scalarCols=%lld", |
|
|
|
|
|
|
|
gammaTriplets.size(), (long long)rowsGamma, (long long)scalarCols); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SparseMat Gamma(rowsGamma, scalarCols); |
|
|
|
|
|
|
|
Gamma.setFromTriplets(gammaTriplets.begin(), gammaTriplets.end()); |
|
|
|
|
|
|
|
Gamma.makeCompressed(); |
|
|
|
|
|
|
|
gammaTriplets.clear(); gammaTriplets.shrink_to_fit(); |
|
|
|
|
|
|
|
rawGamma.clear(); rawGamma.shrink_to_fit(); |
|
|
|
|
|
|
|
DEBUG_EXTRA("Gamma built: %lld x %lld, nnz=%lld", (long long)Gamma.rows(), |
|
|
|
|
|
|
|
(long long)Gamma.cols(), (long long)Gamma.nonZeros()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
// ========== 构建矩阵 A 和 b(颜色一致性约束)==========
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
std::vector<GSLTriplet> ATriplets; |
|
|
|
|
|
|
|
ATriplets.reserve(seamVertices.GetSize() * 6); |
|
|
|
|
|
|
|
std::vector<float> b_coeff; |
|
|
|
|
|
|
|
b_coeff.reserve(seamVertices.GetSize() * 6); |
|
|
|
|
|
|
|
|
|
|
|
// ========== 构建矩阵 A 和 b ==========
|
|
|
|
|
|
|
|
IndexArr indices; |
|
|
|
IndexArr indices; |
|
|
|
Colors vertexColors; |
|
|
|
Colors vertexColors; |
|
|
|
Colors coeffB; |
|
|
|
Eigen::Index rowA = 0; |
|
|
|
|
|
|
|
|
|
|
|
for (const SeamVertex& seamVertex : seamVertices) { |
|
|
|
for (const SeamVertex& seamVertex : seamVertices) { |
|
|
|
if (seamVertex.patches.size() < 2) |
|
|
|
if (seamVertex.patches.size() < 2) continue; |
|
|
|
continue; |
|
|
|
|
|
|
|
seamVertex.SortByPatchIndex(indices); |
|
|
|
seamVertex.SortByPatchIndex(indices); |
|
|
|
vertexColors.resize(indices.size()); |
|
|
|
vertexColors.resize(indices.size()); |
|
|
|
|
|
|
|
|
|
|
|
FOREACH(i, indices) { |
|
|
|
for (IDX i = 0; i < (IDX)indices.size(); ++i) { |
|
|
|
const SeamVertex::Patch& patch0 = seamVertex.patches[indices[i]]; |
|
|
|
const SeamVertex::Patch& patch0 = seamVertex.patches[indices[i]]; |
|
|
|
ASSERT(patch0.idxPatch < numPatches); |
|
|
|
if (patch0.idxPatch >= numPatches) continue; |
|
|
|
if (patch0.idxPatch >= texturePatches.size()) { |
|
|
|
|
|
|
|
DEBUG_EXTRA("ERROR: patch0.idxPatch=%u >= texturePatches.size()=%zu", |
|
|
|
|
|
|
|
patch0.idxPatch, texturePatches.size()); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
const TexturePatch& tpRef = texturePatches[patch0.idxPatch]; |
|
|
|
const TexturePatch& tpRef = texturePatches[patch0.idxPatch]; |
|
|
|
if (tpRef.label < 0 || tpRef.label >= (IIndex)images.size()) { |
|
|
|
if (tpRef.label < 0 || tpRef.label >= (IIndex)images.size()) continue; |
|
|
|
DEBUG_EXTRA("ERROR: tpRef.label=%d out of range [0, %zu)", tpRef.label, images.size()); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SampleImage sampler(images[tpRef.label].image); |
|
|
|
SampleImage sampler(images[tpRef.label].image); |
|
|
|
for (const SeamVertex::Patch::Edge& edge : patch0.edges) { |
|
|
|
for (const SeamVertex::Patch::Edge& edge : patch0.edges) { |
|
|
|
|
|
|
|
if (edge.idxSeamVertex >= seamVertices.GetSize()) continue; // 越界保护
|
|
|
|
const SeamVertex& seamVertex1 = seamVertices[edge.idxSeamVertex]; |
|
|
|
const SeamVertex& seamVertex1 = seamVertices[edge.idxSeamVertex]; |
|
|
|
const SeamVertex::Patches::IDX idxPatch1(seamVertex1.patches.Find(patch0.idxPatch)); |
|
|
|
const auto idxPatch1 = seamVertex1.patches.Find(patch0.idxPatch); |
|
|
|
ASSERT(idxPatch1 != SeamVertex::Patches::NO_INDEX); |
|
|
|
if (idxPatch1 == SeamVertex::Patches::NO_INDEX) continue; |
|
|
|
const SeamVertex::Patch& patch1 = seamVertex1.patches[idxPatch1]; |
|
|
|
const SeamVertex::Patch& patch1 = seamVertex1.patches[idxPatch1]; |
|
|
|
sampler.AddEdge(patch0.proj, patch1.proj); |
|
|
|
sampler.AddEdge(patch0.proj, patch1.proj); |
|
|
|
} |
|
|
|
} |
|
|
|
vertexColors[i] = sampler.GetColor(); |
|
|
|
vertexColors[i] = sampler.GetColor(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const VertexPatch2RowMap& vertpatch2row = vertpatch2rows[seamVertex.idxVertex]; |
|
|
|
const VertexPatch2RowMap& v2r = vertpatch2rows[seamVertex.idxVertex]; |
|
|
|
for (IDX i = 0; i < indices.size() - 1; ++i) { |
|
|
|
for (IDX i = 0; i < (IDX)indices.size() - 1; ++i) { |
|
|
|
const uint32_t idxPatch0(seamVertex.patches[indices[i]].idxPatch); |
|
|
|
for (IDX j = i + 1; j < (IDX)indices.size(); ++j) { |
|
|
|
const Color& color0 = vertexColors[i]; |
|
|
|
const uint32_t ipa = seamVertex.patches[indices[i]].idxPatch; |
|
|
|
const MatIdx col0(vertpatch2row.at(idxPatch0)); |
|
|
|
const uint32_t ipb = seamVertex.patches[indices[j]].idxPatch; |
|
|
|
for (IDX j = i + 1; j < indices.size(); ++j) { |
|
|
|
auto itA = v2r.find(ipa), itB = v2r.find(ipb); |
|
|
|
const uint32_t idxPatch1(seamVertex.patches[indices[j]].idxPatch); |
|
|
|
if (itA == v2r.end() || itB == v2r.end()) continue; |
|
|
|
const Color& color1 = vertexColors[j]; |
|
|
|
const Eigen::Index baseA = itA->second; |
|
|
|
const MatIdx col1(vertpatch2row.at(idxPatch1)); |
|
|
|
const Eigen::Index baseB = itB->second; |
|
|
|
ASSERT(idxPatch0 < idxPatch1); |
|
|
|
|
|
|
|
const MatIdx rowA((MatIdx)coeffB.size()); |
|
|
|
|
|
|
|
coeffB.Insert(color1 - color0); |
|
|
|
|
|
|
|
ASSERT(ISFINITE(coeffB.back())); |
|
|
|
|
|
|
|
rows.emplace_back(rowA, col0, 1.f); |
|
|
|
|
|
|
|
rows.emplace_back(rowA, col1, -1.f); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
ASSERT(coeffB.size() < static_cast<IDX>(std::numeric_limits<MatIdx>::max())); |
|
|
|
|
|
|
|
DEBUG_EXTRA("Matrix A built: %zu constraints, rowsX=%u", coeffB.size(), rowsX); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const MatIdx rowsA((MatIdx)coeffB.size()); |
|
|
|
const Color& ca = vertexColors[i]; |
|
|
|
SparseMat A(rowsA, rowsX); |
|
|
|
const Color& cb = vertexColors[j]; |
|
|
|
A.setFromTriplets(rows.Begin(), rows.End()); |
|
|
|
for (int c = 0; c < 3; ++c) { |
|
|
|
rows.Release(); |
|
|
|
const Eigen::Index colA = baseA * 3 + c; // ★ 标量列 = base*3 + c
|
|
|
|
|
|
|
|
const Eigen::Index colB = baseB * 3 + c; |
|
|
|
SparseMat Lhs(A.transpose() * A + Gamma.transpose() * Gamma); |
|
|
|
ATriplets.emplace_back(rowA, colA, 1.0f); |
|
|
|
Lhs.prune([](const int& row, const int& col, const float&) -> bool { |
|
|
|
ATriplets.emplace_back(rowA, colB, -1.0f); |
|
|
|
return col <= row; |
|
|
|
if ((size_t)rowA >= b_coeff.size()) b_coeff.push_back(0); |
|
|
|
}); |
|
|
|
b_coeff[rowA] = cb[c] - ca[c]; // 差值方程
|
|
|
|
DEBUG_EXTRA("Lhs matrix built and pruned"); |
|
|
|
++rowA; |
|
|
|
|
|
|
|
} |
|
|
|
// ========== 求解 ==========
|
|
|
|
} |
|
|
|
Eigen::Matrix<float, Eigen::Dynamic, 3, Eigen::RowMajor> colorAdjustments(rowsX, 3); |
|
|
|
} |
|
|
|
{ |
|
|
|
} |
|
|
|
|
|
|
|
const Eigen::Index rowsA = rowA; |
|
|
|
|
|
|
|
ASSERT(rowsA <= (Eigen::Index)b_coeff.size()); |
|
|
|
|
|
|
|
SparseMat A(rowsA, scalarCols); |
|
|
|
|
|
|
|
A.setFromTriplets(ATriplets.begin(), ATriplets.end()); |
|
|
|
|
|
|
|
A.makeCompressed(); |
|
|
|
|
|
|
|
ATriplets.clear(); ATriplets.shrink_to_fit(); |
|
|
|
|
|
|
|
DEBUG_EXTRA("Matrix A built: %lld constraints, scalarCols=%lld, nnz=%lld", |
|
|
|
|
|
|
|
(long long)rowsA, (long long)scalarCols, (long long)A.nonZeros()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
// ========== 求解:Lhs = AᵀA + λ²ΓᵀΓ,对角扰动保证正定 [修复2]
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
std::vector<Color> colorAdjustments; |
|
|
|
|
|
|
|
colorAdjustments.assign(rowsX, Color::ZERO); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (rowsA > 0 && rowsX > 0) { |
|
|
|
|
|
|
|
// ★ Lhs = AᵀA + λ² ΓᵀΓ
|
|
|
|
|
|
|
|
SparseMat ATA = A.transpose() * A; |
|
|
|
|
|
|
|
SparseMat GTG = Gamma.transpose() * Gamma; |
|
|
|
|
|
|
|
const float lambda = 0.5f; // ★ 正则化强度(可调)
|
|
|
|
|
|
|
|
GTG *= lambda * lambda; |
|
|
|
|
|
|
|
ASSERT(ATA.rows() == GTG.rows() && ATA.cols() == GTG.cols()); // 尺寸一致性检查
|
|
|
|
|
|
|
|
ATA += GTG; |
|
|
|
|
|
|
|
SparseMat& Lhs = ATA; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★★★ 对角加扰动,严格保证正定 ★★★
|
|
|
|
|
|
|
|
const float eps = 1e-3f; // 足够小不影响结果,保证正定
|
|
|
|
|
|
|
|
for (Eigen::Index i = 0; i < (Eigen::Index)Lhs.rows(); ++i) { |
|
|
|
|
|
|
|
Lhs.coeffRef(i, i) += eps; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
DEBUG_EXTRA("Lhs built: %lld x %lld, nnz=%lld (diag += %g)", |
|
|
|
|
|
|
|
(long long)Lhs.rows(), (long long)Lhs.cols(), |
|
|
|
|
|
|
|
(long long)Lhs.nonZeros(), eps); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 用 ConjugateGradient(只需对称,不要求严格 SPD)
|
|
|
|
Eigen::ConjugateGradient<SparseMat, Eigen::Lower> solver; |
|
|
|
Eigen::ConjugateGradient<SparseMat, Eigen::Lower> solver; |
|
|
|
solver.setMaxIterations(1000); |
|
|
|
solver.setMaxIterations(2000); |
|
|
|
solver.setTolerance(0.0001f); |
|
|
|
solver.setTolerance(0.001f); |
|
|
|
solver.compute(Lhs); |
|
|
|
solver.compute(Lhs); |
|
|
|
ASSERT(solver.info() == Eigen::Success); |
|
|
|
if (solver.info() != Eigen::Success) { |
|
|
|
#ifdef TEXOPT_USE_OPENMP |
|
|
|
DEBUG_EXTRA("WARN: Lhs compute failed (info=%d), skipping solve", (int)solver.info()); |
|
|
|
#pragma omp parallel for |
|
|
|
} else { |
|
|
|
#endif |
|
|
|
// ★ b 与通道无关 → 一次求解,结果按 InnerStride<3> 分通道
|
|
|
|
for (int channel = 0; channel < 3; ++channel) { |
|
|
|
Eigen::VectorXf b(rowsA); |
|
|
|
const Eigen::Map<Eigen::VectorXf, Eigen::Unaligned, Eigen::Stride<0, 3>> b( |
|
|
|
for (Eigen::Index k = 0; k < rowsA; ++k) b(k) = b_coeff[k]; |
|
|
|
coeffB.front().ptr() + channel, rowsA); |
|
|
|
const Eigen::VectorXf rhs = A.transpose() * b; |
|
|
|
const Eigen::VectorXf Rhs(SparseMat(A.transpose()) * b); |
|
|
|
const Eigen::VectorXf x = solver.solve(rhs); |
|
|
|
const Eigen::VectorXf x(solver.solve(Rhs)); |
|
|
|
DEBUG_EXTRA("solve: %d iterations, residual=%g", solver.iterations(), solver.error()); |
|
|
|
ASSERT(solver.info() == Eigen::Success); |
|
|
|
|
|
|
|
Eigen::Map<Eigen::VectorXf, Eigen::Unaligned, Eigen::Stride<0, 3>>( |
|
|
|
// ★ 从 x 提取 colorAdjustments(每块一个 Color,显式循环避免对齐问题)
|
|
|
|
colorAdjustments.data() + channel, rowsX) = x.array() - x.mean(); |
|
|
|
if (solver.info() == Eigen::Success && (Eigen::Index)(x.size()) >= scalarCols) { |
|
|
|
DEBUG_LEVEL(3, "\tcolor channel %d: %d iterations, %g residual", |
|
|
|
colorAdjustments.assign(rowsX, Color::ZERO); |
|
|
|
channel, solver.iterations(), solver.error()); |
|
|
|
for (Eigen::Index v = 0; v < (Eigen::Index)rowsX; ++v) { |
|
|
|
} |
|
|
|
const float r = x(v * 3 + 0); |
|
|
|
} |
|
|
|
const float g = x(v * 3 + 1); |
|
|
|
DEBUG_EXTRA("CG solve done. colorAdjustments: %dx3", rowsX); |
|
|
|
const float b_ch = x(v * 3 + 2); |
|
|
|
|
|
|
|
const float mean = (r + g + b_ch) / 3.0f; // 去均值
|
|
|
|
// ========== ★★★ 关键修正:串行应用颜色修正,避免并行写同一张 images[label] ==========
|
|
|
|
colorAdjustments[v] = Color(r - mean, g - mean, b_ch - mean); |
|
|
|
//
|
|
|
|
|
|
|
|
// 原版用 #pragma omp parallel for,但不同 patch 可能共享同一个 images[label]。
|
|
|
|
|
|
|
|
// 多个线程同时 clone() + copyTo() 同一个 cv::Mat → 数据竞争 → 堆破坏 → munmap_chunk。
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// 修复策略:
|
|
|
|
|
|
|
|
// 1. 先把所有 patch 的修正结果写入**独立 buffer**(按 label 分组,每个 label 一份)
|
|
|
|
|
|
|
|
// 2. 最后**串行**把每个 label 的 buffer 一次性写回 images[label]
|
|
|
|
|
|
|
|
// 这样完全避免并行写竞争。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- Step 1: 准备按 label 分组的输出 buffer ---
|
|
|
|
|
|
|
|
struct LabelBuffer { |
|
|
|
|
|
|
|
cv::Mat buffer; // 整张图大小的 buffer,初始为原图副本
|
|
|
|
|
|
|
|
std::vector<cv::Rect> rects; // 需要写入的 patch 区域(相对于原图)
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
std::map<int, LabelBuffer> labelBuffers; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 先按 label 收集所有需要修改的 patch
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < numPatches; ++i) { |
|
|
|
|
|
|
|
const uint32_t idxPatch = i; |
|
|
|
|
|
|
|
const TexturePatch& tp = texturePatches[idxPatch]; |
|
|
|
|
|
|
|
labelBuffers[tp.label].rects.push_back(tp.rect); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
// 为每个 label 创建整张图的深拷贝 buffer
|
|
|
|
DEBUG_EXTRA("WARN: solve not successful, using zero adjustments"); |
|
|
|
for (auto& kv : labelBuffers) { |
|
|
|
|
|
|
|
int label = kv.first; |
|
|
|
|
|
|
|
if (label < 0 || label >= (int)images.size()) continue; |
|
|
|
|
|
|
|
if (images[label].image.empty()) continue; |
|
|
|
|
|
|
|
// ★ 整张图深拷贝,后续直接在 buffer 上改,最后整体 swap 回去
|
|
|
|
|
|
|
|
kv.second.buffer = images[label].image.clone(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
DEBUG_EXTRA("Label buffers created: %zu labels involved", labelBuffers.size()); |
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
DEBUG_EXTRA("WARN: no constraints (rowsA=%lld), skip solve", (long long)rowsA); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
DEBUG_EXTRA("solve done. colorAdjustments: %zux3", rowsX); |
|
|
|
|
|
|
|
|
|
|
|
// --- Step 2: 并行计算每个 patch 的颜色调整(写到各自 label 的 buffer 的 ROI 里)---
|
|
|
|
// ========== 应用颜色修正(输出到 correctedPatchImages,不碰 images)==========
|
|
|
|
// 注意:不同 patch 如果 label 相同,会写同一个 buffer——但每个 patch 写的 ROI(rect)不同,
|
|
|
|
correctedPatchImages.resize(numPatches); |
|
|
|
// 只要 rect 不重叠就没问题。同一 label 的 patch 在纹理空间里不重叠,所以安全。
|
|
|
|
|
|
|
|
// 但为绝对安全,这里用 label 级别的锁,或者干脆串行。
|
|
|
|
|
|
|
|
// ★ 采用:按 patch 并行,但对同一 label 的 buffer 访问加锁。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef TEXOPT_USE_OPENMP |
|
|
|
#ifdef TEXOPT_USE_OPENMP |
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
@ -9367,23 +9487,14 @@ void MeshTexture::GlobalSeamLeveling4() |
|
|
|
if (texturePatch.label < 0 || texturePatch.label >= (int)images.size()) continue; |
|
|
|
if (texturePatch.label < 0 || texturePatch.label >= (int)images.size()) continue; |
|
|
|
if (texturePatch.rect.width <= 0 || texturePatch.rect.height <= 0) continue; |
|
|
|
if (texturePatch.rect.width <= 0 || texturePatch.rect.height <= 0) continue; |
|
|
|
|
|
|
|
|
|
|
|
auto it = labelBuffers.find(texturePatch.label); |
|
|
|
// ★ 从原始 images 读(只读,不修改)
|
|
|
|
if (it == labelBuffers.end()) continue; |
|
|
|
const cv::Mat& srcROI = images[texturePatch.label].image(texturePatch.rect); |
|
|
|
cv::Mat& labelBuf = it->second.buffer; |
|
|
|
if (srcROI.empty()) continue; |
|
|
|
if (labelBuf.empty()) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查 rect 是否在 buffer 范围内
|
|
|
|
// ★ 创建独立输出 buffer
|
|
|
|
if (texturePatch.rect.x < 0 || texturePatch.rect.y < 0 || |
|
|
|
cv::Mat& outPatch = correctedPatchImages[idxPatch]; |
|
|
|
texturePatch.rect.x + texturePatch.rect.width > labelBuf.cols || |
|
|
|
outPatch = cv::Mat::zeros(texturePatch.rect.size(), CV_8UC3); |
|
|
|
texturePatch.rect.y + texturePatch.rect.height > labelBuf.rows) { |
|
|
|
|
|
|
|
DEBUG_EXTRA("WARN: patch %u rect %d,%d %dx%d out of image %dx%d", |
|
|
|
|
|
|
|
idxPatch, texturePatch.rect.x, texturePatch.rect.y, |
|
|
|
|
|
|
|
texturePatch.rect.width, texturePatch.rect.height, |
|
|
|
|
|
|
|
labelBuf.cols, labelBuf.rows); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- 计算该 patch 的 color adjustment 插值图(与原版相同逻辑)---
|
|
|
|
|
|
|
|
ColorMap imageAdj(texturePatch.rect.size()); |
|
|
|
ColorMap imageAdj(texturePatch.rect.size()); |
|
|
|
imageAdj.memset(0); |
|
|
|
imageAdj.memset(0); |
|
|
|
|
|
|
|
|
|
|
|
@ -9391,69 +9502,62 @@ void MeshTexture::GlobalSeamLeveling4() |
|
|
|
const TexCoord* tri; |
|
|
|
const TexCoord* tri; |
|
|
|
Color colors[3]; |
|
|
|
Color colors[3]; |
|
|
|
ColorMap& image; |
|
|
|
ColorMap& image; |
|
|
|
inline RasterPatch(ColorMap& _image) : image(_image) {} |
|
|
|
RasterPatch(ColorMap& _image) : image(_image) {} |
|
|
|
inline cv::Size Size() const { return image.size(); } |
|
|
|
inline cv::Size Size() const { return image.size(); } |
|
|
|
inline void operator()(const ImageRef& pt, const Point3f& bary) { |
|
|
|
void operator()(const ImageRef& pt, const Point3f& bary) { |
|
|
|
ASSERT(image.isInside(pt)); |
|
|
|
image(pt) = colors[0]*bary.x + colors[1]*bary.y + colors[2]*bary.z; |
|
|
|
image(pt) = colors[0] * bary.x + colors[1] * bary.y + colors[2] * bary.z; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} data(imageAdj); |
|
|
|
} data(imageAdj); |
|
|
|
|
|
|
|
ASSERT(texturePatch.faces.size() < 1000000); // 合理范围
|
|
|
|
for (const FIndex idxFace : texturePatch.faces) { |
|
|
|
for (const FIndex idxFace : texturePatch.faces) { |
|
|
|
if (idxFace >= faces.GetSize()) continue; |
|
|
|
if (idxFace >= faces.GetSize()) continue; |
|
|
|
const Face& face = faces[idxFace]; |
|
|
|
const Face& face = faces[idxFace]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 检查 faceTexcoords 访问
|
|
|
|
|
|
|
|
ASSERT(idxFace * 3 + 2 < faceTexcoords.GetSize()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 检查 face[v] 不越界
|
|
|
|
|
|
|
|
for (int v = 0; v < 3; ++v) { |
|
|
|
|
|
|
|
ASSERT(face[v] < vertices.size()); |
|
|
|
|
|
|
|
ASSERT(face[v] < vertpatch2rows.size()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
data.tri = faceTexcoords.Begin() + idxFace * 3; |
|
|
|
data.tri = faceTexcoords.Begin() + idxFace * 3; |
|
|
|
for (int v = 0; v < 3; ++v) { |
|
|
|
for (int v = 0; v < 3; ++v) { |
|
|
|
|
|
|
|
ASSERT(face[v] < vertices.size()); // ★ 加这行
|
|
|
|
auto search = vertpatch2rows[face[v]].find(idxPatch); |
|
|
|
auto search = vertpatch2rows[face[v]].find(idxPatch); |
|
|
|
if (search != vertpatch2rows[face[v]].end()) { |
|
|
|
if (search != vertpatch2rows[face[v]].end()) |
|
|
|
data.colors[v] = colorAdjustments.row(vertpatch2rows[face[v]].at(idxPatch)); |
|
|
|
{ |
|
|
|
} else { |
|
|
|
ASSERT(search->second < (Eigen::Index)colorAdjustments.size()); // ★ 关键
|
|
|
|
data.colors[v] = Color::ZERO; |
|
|
|
data.colors[v] = colorAdjustments[search->second]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
data.colors[v] = Color::ZERO; |
|
|
|
} |
|
|
|
} |
|
|
|
ColorMap::RasterizeTriangleBary(data.tri[0], data.tri[1], data.tri[2], data); |
|
|
|
ColorMap::RasterizeTriangleBary(data.tri[0], data.tri[1], data.tri[2], data); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
imageAdj.DilateMean<1>(imageAdj, Color::ZERO); |
|
|
|
imageAdj.DilateMean<1>(imageAdj, Color::ZERO); |
|
|
|
|
|
|
|
|
|
|
|
// --- ★ 写回 label buffer 的 ROI(这是 clone 出来的独立内存,安全)---
|
|
|
|
// ★ 把修正叠加到 outPatch(从 srcROI 读原始颜色)
|
|
|
|
cv::Mat roi = labelBuf(texturePatch.rect); |
|
|
|
for (int r = 0; r < srcROI.rows; ++r) { |
|
|
|
for (int r = 0; r < roi.rows; ++r) { |
|
|
|
for (int c = 0; c < srcROI.cols; ++c) { |
|
|
|
for (int c = 0; c < roi.cols; ++c) { |
|
|
|
|
|
|
|
const Color& a = imageAdj(r, c); |
|
|
|
const Color& a = imageAdj(r, c); |
|
|
|
if (a == Color::ZERO) |
|
|
|
if (a == Color::ZERO) { |
|
|
|
|
|
|
|
outPatch.at<cv::Vec3b>(r, c) = srcROI.at<cv::Vec3b>(r, c); |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
Pixel8U& v = roi.at<Pixel8U>(r, c); |
|
|
|
} |
|
|
|
|
|
|
|
const Pixel8U& v = srcROI.at<Pixel8U>(r, c); |
|
|
|
const Color col(RGB2YCBCR(Color(v))); |
|
|
|
const Color col(RGB2YCBCR(Color(v))); |
|
|
|
const Color acol(YCBCR2RGB(Color(col + a))); |
|
|
|
const Color acol(YCBCR2RGB(Color(col + a))); |
|
|
|
for (int p = 0; p < 3; ++p) |
|
|
|
outPatch.at<cv::Vec3b>(r, c) = cv::Vec3b( |
|
|
|
v[p] = (uint8_t)CLAMP(ROUND2INT(acol[p]), 0, 255); |
|
|
|
(uint8_t)CLAMP(ROUND2INT(acol[0]), 0, 255), |
|
|
|
|
|
|
|
(uint8_t)CLAMP(ROUND2INT(acol[1]), 0, 255), |
|
|
|
|
|
|
|
(uint8_t)CLAMP(ROUND2INT(acol[2]), 0, 255)); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} // end parallel for
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("All patch adjustments written to label buffers"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- Step 3: ★ 串行、安全地写回原始 images ---
|
|
|
|
|
|
|
|
// 每个 label 只做一次 swap,彻底避免竞争
|
|
|
|
|
|
|
|
for (const auto& kv : labelBuffers) { |
|
|
|
|
|
|
|
int label = kv.first; |
|
|
|
|
|
|
|
const cv::Mat& buf = kv.second.buffer; |
|
|
|
|
|
|
|
if (label < 0 || label >= (int)images.size()) continue; |
|
|
|
|
|
|
|
if (images[label].image.empty()) continue; |
|
|
|
|
|
|
|
if (buf.size() != images[label].image.size()) { |
|
|
|
|
|
|
|
DEBUG_EXTRA("WARN: label %d buffer size mismatch, skipping", label); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// ★ 用 swap 而不是 copyTo:O(1),不重新分配,不踩堆
|
|
|
|
|
|
|
|
// 需要确保类型一致
|
|
|
|
|
|
|
|
if (buf.type() == images[label].image.type()) { |
|
|
|
|
|
|
|
cv::Mat(buf).copyTo(images[label].image); // 如果必须拷贝
|
|
|
|
|
|
|
|
// 或者如果 images[label].image 可以接管内存:images[label].image = buf;
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
buf.copyTo(images[label].image); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
DEBUG_EXTRA("All patch adjustments applied (to local buffers)"); |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("GlobalSeamLeveling4 finished successfully."); |
|
|
|
DEBUG_EXTRA("GlobalSeamLeveling4 finished successfully."); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -15320,21 +15424,94 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (rcPatches.empty()) return; |
|
|
|
if (rcPatches.empty()) return; |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: bridging %zu rcPatches...", rcPatches.size()); |
|
|
|
Image8U3 localAtlas(textureSize, textureSize); |
|
|
|
|
|
|
|
localAtlas.memset(0); // 全部填 0 = 黑色
|
|
|
|
// ========== ★ 保存所有 images 的原始状态 ==========
|
|
|
|
|
|
|
|
std::vector<cv::Mat> originalImages(images.size()); |
|
|
|
|
|
|
|
for (size_t k = 0; k < images.size(); ++k) { |
|
|
|
|
|
|
|
if (!images[k].image.empty()) |
|
|
|
|
|
|
|
originalImages[k] = images[k].image.clone(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
DEBUG_EXTRA("Saved %zu original images", images.size()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: bridging %zu rcPatches...", rcPatches.size()); |
|
|
|
|
|
|
|
DEBUG_EXTRA("faceTexcoords ptr=%p, scene ptr=%p", &faceTexcoords, &scene.mesh.faceTexcoords); |
|
|
|
// ========== 1. 构建 texturePatches ==========
|
|
|
|
// ========== 1. 构建 texturePatches ==========
|
|
|
|
// ... (和之前完全一样的构建代码,省略) ...
|
|
|
|
|
|
|
|
texturePatches.clear(); |
|
|
|
texturePatches.clear(); |
|
|
|
std::vector<size_t> rcToTexPatch(rcPatches.size(), NO_ID); |
|
|
|
std::vector<size_t> rcToTexPatch(rcPatches.size(), NO_ID); |
|
|
|
// ... [构建 texturePatches,和之前一样] ...
|
|
|
|
|
|
|
|
|
|
|
|
// ★★★ 诊断日志:构建循环之前 ★★★
|
|
|
|
|
|
|
|
if (!rcPatches.empty()) { |
|
|
|
|
|
|
|
DEBUG_EXTRA("rcPatch[0]: viewID=%d, faces.size()=%zu, firstFace=%u, virtualFaceMap.size()=%zu", |
|
|
|
|
|
|
|
rcPatches[0].viewID, rcPatches[0].faces.size(), |
|
|
|
|
|
|
|
rcPatches[0].faces.empty() ? 0 : rcPatches[0].faces[0], |
|
|
|
|
|
|
|
virtualFaceMap.size()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 诊断计数器
|
|
|
|
|
|
|
|
size_t skippedEmpty = 0, skippedBadView = 0, skippedEmptyImage = 0; |
|
|
|
|
|
|
|
size_t skippedNoProj = 0, skippedBadROI = 0, skippedNoFaces = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t pi = 0; pi < rcPatches.size(); ++pi) { |
|
|
|
|
|
|
|
const RCPatch& rp = rcPatches[pi]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (rp.faces.empty()) { skippedEmpty++; continue; } |
|
|
|
|
|
|
|
if (rp.viewID < 0 || rp.viewID >= (IIndex)images.size()) { skippedBadView++; continue; } |
|
|
|
|
|
|
|
const Image& img = images[rp.viewID]; |
|
|
|
|
|
|
|
if (img.image.empty()) { skippedEmptyImage++; continue; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int minX = INT_MAX, minY = INT_MAX, maxX = 0, maxY = 0; |
|
|
|
|
|
|
|
bool hasValidProj = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (FIndex vfID : rp.faces) { |
|
|
|
|
|
|
|
if (vfID >= virtualFaceMap.size()) continue; |
|
|
|
|
|
|
|
const VirtualFace& vf = virtualFaceMap[vfID]; |
|
|
|
|
|
|
|
for (FIndex fid : vf.faces) { |
|
|
|
|
|
|
|
if (fid >= scene.mesh.faces.size()) continue; |
|
|
|
|
|
|
|
const Mesh::Face& face = scene.mesh.faces[fid]; |
|
|
|
|
|
|
|
for (int v = 0; v < 3; ++v) { |
|
|
|
|
|
|
|
const Point3f& vert = scene.mesh.vertices[face[v]]; |
|
|
|
|
|
|
|
Point2f proj = img.camera.ProjectPoint(Point3d(vert)); |
|
|
|
|
|
|
|
int px = (int)(proj.x + 0.5f), py = (int)(proj.y + 0.5f); |
|
|
|
|
|
|
|
if (px < 0 || py < 0 || px >= img.image.cols || py >= img.image.rows) continue; |
|
|
|
|
|
|
|
minX = std::min(minX, px); minY = std::min(minY, py); |
|
|
|
|
|
|
|
maxX = std::max(maxX, px); maxY = std::max(maxY, py); |
|
|
|
|
|
|
|
hasValidProj = true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasValidProj) { skippedNoProj++; continue; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
minX = std::max(0, minX - 2); minY = std::max(0, minY - 2); |
|
|
|
|
|
|
|
maxX = std::min(img.image.cols - 1, maxX + 2); maxY = std::min(img.image.rows - 1, maxY + 2); |
|
|
|
|
|
|
|
if (minX >= maxX || minY >= maxY) { skippedBadROI++; continue; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TexturePatch tp; |
|
|
|
|
|
|
|
tp.label = rp.viewID; |
|
|
|
|
|
|
|
tp.rect = cv::Rect(minX, minY, maxX - minX + 1, maxY - minY + 1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 收集真实面(展开虚拟面)
|
|
|
|
|
|
|
|
for (FIndex vfID : rp.faces) { |
|
|
|
|
|
|
|
if (vfID >= virtualFaceMap.size()) continue; |
|
|
|
|
|
|
|
const VirtualFace& vf = virtualFaceMap[vfID]; |
|
|
|
|
|
|
|
for (FIndex fid : vf.faces) { |
|
|
|
|
|
|
|
if (fid < scene.mesh.faces.size()) |
|
|
|
|
|
|
|
tp.faces.push_back(fid); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!tp.faces.empty()) { |
|
|
|
|
|
|
|
auto& f = tp.faces; std::sort(f.begin(), f.end()); |
|
|
|
|
|
|
|
FIndex last = f[0]; size_t wi = 1; |
|
|
|
|
|
|
|
for (size_t ri = 1; ri < f.size(); ++ri) |
|
|
|
|
|
|
|
if (f[ri] != last) { last = f[ri]; f[wi++] = last; } |
|
|
|
|
|
|
|
f.Resize(wi); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (tp.faces.empty()) { skippedNoFaces++; continue; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rcToTexPatch[pi] = texturePatches.size(); |
|
|
|
|
|
|
|
texturePatches.push_back(tp); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("texturePatch filtering: total=%zu", rcPatches.size()); |
|
|
|
|
|
|
|
DEBUG_EXTRA(" skippedEmpty=%zu, skippedBadView=%zu, skippedEmptyImage=%zu", |
|
|
|
|
|
|
|
skippedEmpty, skippedBadView, skippedEmptyImage); |
|
|
|
|
|
|
|
DEBUG_EXTRA(" skippedNoProj=%zu, skippedBadROI=%zu, skippedNoFaces=%zu", |
|
|
|
|
|
|
|
skippedNoProj, skippedBadROI, skippedNoFaces); |
|
|
|
|
|
|
|
DEBUG_EXTRA(" → accepted=%zu", texturePatches.size()); |
|
|
|
|
|
|
|
|
|
|
|
const size_t numValidPatches = texturePatches.size(); |
|
|
|
const size_t numValidPatches = texturePatches.size(); |
|
|
|
DEBUG_EXTRA("Valid texturePatches: %zu (from %zu rcPatches)", numValidPatches, rcPatches.size()); |
|
|
|
DEBUG_EXTRA("Valid texturePatches: %zu (from %zu rcPatches)", numValidPatches, rcPatches.size()); |
|
|
|
@ -15363,8 +15540,9 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( |
|
|
|
BuildSeamEdgesFromFaceToPatchID(); |
|
|
|
BuildSeamEdgesFromFaceToPatchID(); |
|
|
|
CreateSeamVertices(); |
|
|
|
CreateSeamVertices(); |
|
|
|
|
|
|
|
|
|
|
|
faceTexcoords.Resize(scene.mesh.faces.size() * 3); |
|
|
|
Mesh::TexCoordArr localFaceTexcoords; |
|
|
|
for (size_t i = 0; i < faceTexcoords.size(); ++i) faceTexcoords[i] = TexCoord(0, 0); |
|
|
|
localFaceTexcoords.Resize(scene.mesh.faces.size() * 3); |
|
|
|
|
|
|
|
for (size_t i = 0; i < localFaceTexcoords.size(); ++i) localFaceTexcoords[i] = TexCoord(0, 0); |
|
|
|
for (size_t tpi = 0; tpi < numValidPatches; ++tpi) { |
|
|
|
for (size_t tpi = 0; tpi < numValidPatches; ++tpi) { |
|
|
|
const TexturePatch& tp = texturePatches[tpi]; |
|
|
|
const TexturePatch& tp = texturePatches[tpi]; |
|
|
|
const Image& img = images[tp.label]; |
|
|
|
const Image& img = images[tp.label]; |
|
|
|
@ -15374,25 +15552,37 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( |
|
|
|
for (int v = 0; v < 3; ++v) { |
|
|
|
for (int v = 0; v < 3; ++v) { |
|
|
|
const Point3f& vert = scene.mesh.vertices[face[v]]; |
|
|
|
const Point3f& vert = scene.mesh.vertices[face[v]]; |
|
|
|
Point2f proj = img.camera.ProjectPoint(Point3d(vert)); |
|
|
|
Point2f proj = img.camera.ProjectPoint(Point3d(vert)); |
|
|
|
faceTexcoords[fid * 3 + v] = TexCoord(proj.x - tp.rect.x, proj.y - tp.rect.y); |
|
|
|
localFaceTexcoords[fid * 3 + v] = TexCoord(proj.x - tp.rect.x, proj.y - tp.rect.y); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Bridge done: %zu texturePatches ready, calling GlobalSeamLeveling4...", numValidPatches); |
|
|
|
DEBUG_EXTRA("Bridge done: %zu texturePatches ready, calling GlobalSeamLeveling4...", numValidPatches); |
|
|
|
|
|
|
|
|
|
|
|
// ========== 调用 GSL4(内部用 label buffer,写回 images)==========
|
|
|
|
// ========== 调用 GSL4(内部修改 images)==========
|
|
|
|
GlobalSeamLeveling4(); |
|
|
|
GlobalSeamLeveling4(); |
|
|
|
DEBUG_EXTRA("GlobalSeamLeveling4 finished."); |
|
|
|
DEBUG_EXTRA("GlobalSeamLeveling4 finished."); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("atlas: rows=%d, cols=%d, step=%d, totalBytes=%d", |
|
|
|
|
|
|
|
atlas.rows, atlas.cols, atlas.step, |
|
|
|
|
|
|
|
(int)(atlas.rows * atlas.step)); |
|
|
|
|
|
|
|
DEBUG_EXTRA("expected: %d x %d x 3 = %d bytes", |
|
|
|
|
|
|
|
textureSize, textureSize, textureSize * textureSize * 3); |
|
|
|
// ========== 3. 重光栅化(从已被 GSL4 修正的 images 读)==========
|
|
|
|
// ========== 3. 重光栅化(从已被 GSL4 修正的 images 读)==========
|
|
|
|
ASSERT(atlas.rows == textureSize && atlas.cols == textureSize); |
|
|
|
ASSERT(atlas.rows == textureSize && atlas.cols == textureSize); |
|
|
|
for (int pi = 0; pi < (int)rcPatches.size(); ++pi) { // 串行,简单安全
|
|
|
|
for (int pi = 0; pi < (int)rcPatches.size(); ++pi) { |
|
|
|
if (rcToTexPatch[pi] == NO_ID) continue; |
|
|
|
if (rcToTexPatch[pi] == NO_ID) continue; |
|
|
|
const TexturePatch& tp = texturePatches[rcToTexPatch[pi]]; |
|
|
|
const TexturePatch& tp = texturePatches[rcToTexPatch[pi]]; |
|
|
|
const Image& srcImg = images[tp.label]; |
|
|
|
const Image& srcImg = images[tp.label]; |
|
|
|
|
|
|
|
|
|
|
|
if (srcImg.image.empty()) continue; |
|
|
|
if (srcImg.image.empty()) continue; |
|
|
|
cv::Mat correctedPatch = srcImg.image(tp.rect).clone(); |
|
|
|
|
|
|
|
|
|
|
|
if (rcToTexPatch[pi] >= correctedPatchImages.size() || correctedPatchImages[rcToTexPatch[pi]].empty()) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// cv::Mat correctedPatch = srcImg.image(tp.rect).clone();
|
|
|
|
|
|
|
|
cv::Mat correctedPatch = correctedPatchImages[rcToTexPatch[pi]].clone(); // 局部拷贝
|
|
|
|
|
|
|
|
|
|
|
|
if (correctedPatch.empty()) continue; |
|
|
|
if (correctedPatch.empty()) continue; |
|
|
|
|
|
|
|
|
|
|
|
const RCPatch& rp = rcPatches[pi]; |
|
|
|
const RCPatch& rp = rcPatches[pi]; |
|
|
|
@ -15431,20 +15621,25 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( |
|
|
|
if (color[0] < 5 && color[1] < 5 && color[2] < 5) continue; |
|
|
|
if (color[0] < 5 && color[1] < 5 && color[2] < 5) continue; |
|
|
|
int atlasX = x + minX, atlasY = y + minY; |
|
|
|
int atlasX = x + minX, atlasY = y + minY; |
|
|
|
if (atlasX < 0 || atlasX >= textureSize || atlasY < 0 || atlasY >= textureSize) continue; |
|
|
|
if (atlasX < 0 || atlasX >= textureSize || atlasY < 0 || atlasY >= textureSize) continue; |
|
|
|
atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; |
|
|
|
// atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]};
|
|
|
|
} |
|
|
|
if (atlasY >= 0 && atlasY < atlas.rows && atlasX >= 0 && atlasX < atlas.cols) { |
|
|
|
|
|
|
|
// 用 cv::Mat 的 at 方法,它内部有边界检查(Debug 模式下)
|
|
|
|
|
|
|
|
cv::Vec3b& pixel = localAtlas.at<cv::Vec3b>(atlasY, atlasX); |
|
|
|
|
|
|
|
pixel[0] = color[0]; |
|
|
|
|
|
|
|
pixel[1] = color[1]; |
|
|
|
|
|
|
|
pixel[2] = color[2]; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
DEBUG_EXTRA("Re-rasterization done."); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ========== ★ 恢复原始 images(让析构时内存完全干净)==========
|
|
|
|
atlas = localAtlas; |
|
|
|
for (size_t k = 0; k < images.size(); ++k) { |
|
|
|
|
|
|
|
if (!originalImages[k].empty()) { |
|
|
|
|
|
|
|
// 用 assign 而不是 copyTo,确保引用计数干净
|
|
|
|
|
|
|
|
images[k].image = originalImages[k].clone(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
DEBUG_EXTRA("Original images restored."); |
|
|
|
DEBUG_EXTRA("Re-rasterization done."); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
correctedPatchImages.clear(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 不再恢复原始 images —— 避免 cv::Mat 引用计数混乱导致 munmap_chunk
|
|
|
|
|
|
|
|
// atlas 已经生成,images 后续随 MeshTexture 正常析构即可
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 4. 清理成员变量 ==========
|
|
|
|
// ========== 4. 清理成员变量 ==========
|
|
|
|
texturePatches.clear(); |
|
|
|
texturePatches.clear(); |
|
|
|
@ -15453,7 +15648,10 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( |
|
|
|
labelsInvalid.Release(); |
|
|
|
labelsInvalid.Release(); |
|
|
|
seamEdges.clear(); |
|
|
|
seamEdges.clear(); |
|
|
|
seamVertices.clear(); |
|
|
|
seamVertices.clear(); |
|
|
|
faceTexcoords.Release(); |
|
|
|
// faceTexcoords.Release();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Mesh::TexCoordArr dummyTexcoords; |
|
|
|
|
|
|
|
faceTexcoords.Swap(dummyTexcoords); // 如果 DynArray 有 Swap 方法
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: ALL DONE."); |
|
|
|
DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: ALL DONE."); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -15806,7 +16004,14 @@ bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
color[1] = cv::saturate_cast<uchar>(std::min(255.0f, color[1] * 1.05f)); |
|
|
|
color[1] = cv::saturate_cast<uchar>(std::min(255.0f, color[1] * 1.05f)); |
|
|
|
color[2] = cv::saturate_cast<uchar>(std::min(255.0f, color[2] * 1.05f)); |
|
|
|
color[2] = cv::saturate_cast<uchar>(std::min(255.0f, color[2] * 1.05f)); |
|
|
|
|
|
|
|
|
|
|
|
atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; |
|
|
|
// atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]};
|
|
|
|
|
|
|
|
if (atlasY >= 0 && atlasY < atlas.rows && atlasX >= 0 && atlasX < atlas.cols) { |
|
|
|
|
|
|
|
// 用 cv::Mat 的 at 方法,它内部有边界检查(Debug 模式下)
|
|
|
|
|
|
|
|
cv::Vec3b& pixel = atlas.at<cv::Vec3b>(atlasY, atlasX); |
|
|
|
|
|
|
|
pixel[0] = color[2]; |
|
|
|
|
|
|
|
pixel[1] = color[1]; |
|
|
|
|
|
|
|
pixel[2] = color[0]; |
|
|
|
|
|
|
|
} |
|
|
|
m_texelScores[idx].score = currentScore; |
|
|
|
m_texelScores[idx].score = currentScore; |
|
|
|
m_texelScores[idx].viewID = viewID; |
|
|
|
m_texelScores[idx].viewID = viewID; |
|
|
|
m_texelPatchID[idx] = i; // ★ 记录这个像素属于 patch i
|
|
|
|
m_texelPatchID[idx] = i; // ★ 记录这个像素属于 patch i
|
|
|
|
|