|
|
|
|
@ -791,6 +791,14 @@ public:
@@ -791,6 +791,14 @@ public:
|
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
Image8U3& atlas, |
|
|
|
|
int textureSize); |
|
|
|
|
|
|
|
|
|
// 在 class MeshTexture 的 public 或 private 区域添加
|
|
|
|
|
struct AffineCorrection { |
|
|
|
|
float kr, kg, kb; // gain per channel
|
|
|
|
|
float br, bg, bb; // bias per channel
|
|
|
|
|
}; |
|
|
|
|
std::vector<AffineCorrection> patchAffineByTexPatch; // index = texturePatch ID
|
|
|
|
|
|
|
|
|
|
void RunSeamLevelingOnAtlas(Image8U3& atlas, int textureSize); |
|
|
|
|
bool RasterizeVirtualFaces( |
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
@ -9205,15 +9213,7 @@ void MeshTexture::GlobalSeamLeveling4()
@@ -9205,15 +9213,7 @@ void MeshTexture::GlobalSeamLeveling4()
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const unsigned numPatches = (unsigned)(texturePatches.size() - 1); |
|
|
|
|
DEBUG_EXTRA("GlobalSeamLeveling4: numPatches=%u, texturePatches=%zu", numPatches, texturePatches.size()); |
|
|
|
|
|
|
|
|
|
// ========== 验证输入 ==========
|
|
|
|
|
for (size_t i = 0; i < components.size(); ++i) { |
|
|
|
|
if (components[i] != NO_ID && components[i] >= texturePatches.size()) { |
|
|
|
|
DEBUG_EXTRA("ERROR: components[%zu]=%u >= texturePatches.size()=%zu", |
|
|
|
|
i, components[i], texturePatches.size()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("GlobalSeamLeveling4 (Affine): numPatches=%u", numPatches); |
|
|
|
|
|
|
|
|
|
// ========== 标记无效顶点 ==========
|
|
|
|
|
BoolArr vertexInvalid(vertices.size()); |
|
|
|
|
@ -9244,15 +9244,13 @@ void MeshTexture::GlobalSeamLeveling4()
@@ -9244,15 +9244,13 @@ void MeshTexture::GlobalSeamLeveling4()
|
|
|
|
|
|
|
|
|
|
FOREACH(i, seamVertices) { |
|
|
|
|
const SeamVertex& seamVertex = seamVertices[i]; |
|
|
|
|
ASSERT(!seamVertex.patches.empty()); |
|
|
|
|
ASSERT(seamVertex.idxVertex < vertices.size()); |
|
|
|
|
PatchIndex& patchIndex = patchIndices[seamVertex.idxVertex]; |
|
|
|
|
patchIndex.bIndex = true; |
|
|
|
|
patchIndex.idxSeamVertex = i; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ========== 构建 vertpatch2rows ==========
|
|
|
|
|
ASSERT(vertices.size() < static_cast<VIndex>(std::numeric_limits<MatIdx>::max())); |
|
|
|
|
// ★ 每个 vertpatch 有 6 个变量: scale_R, scale_G, scale_B, offset_R, offset_G, offset_B
|
|
|
|
|
MatIdx rowsX(0); |
|
|
|
|
typedef std::unordered_map<uint32_t, MatIdx> VertexPatch2RowMap; |
|
|
|
|
std::vector<VertexPatch2RowMap> vertpatch2rows(vertices.size()); |
|
|
|
|
@ -9262,38 +9260,31 @@ void MeshTexture::GlobalSeamLeveling4()
@@ -9262,38 +9260,31 @@ void MeshTexture::GlobalSeamLeveling4()
|
|
|
|
|
VertexPatch2RowMap& vertpatch2row = vertpatch2rows[i]; |
|
|
|
|
if (patchIndex.bIndex) { |
|
|
|
|
const SeamVertex& seamVertex = seamVertices[patchIndex.idxSeamVertex]; |
|
|
|
|
ASSERT(seamVertex.idxVertex == i); |
|
|
|
|
for (const SeamVertex::Patch& patch : seamVertex.patches) { |
|
|
|
|
ASSERT(patch.idxPatch != numPatches); |
|
|
|
|
ASSERT(patch.idxPatch < numPatches); |
|
|
|
|
if (vertpatch2row.find(patch.idxPatch) != vertpatch2row.end()) |
|
|
|
|
continue; |
|
|
|
|
if (patch.idxPatch >= numPatches) continue; |
|
|
|
|
if (vertpatch2row.find(patch.idxPatch) != vertpatch2row.end()) continue; |
|
|
|
|
vertpatch2row[patch.idxPatch] = rowsX++; |
|
|
|
|
} |
|
|
|
|
} else if (patchIndex.idxPatch < numPatches) { |
|
|
|
|
vertpatch2row[patchIndex.idxPatch] = rowsX++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
const Eigen::Index scalarCols = (Eigen::Index)rowsX * 3; |
|
|
|
|
const Eigen::Index scalarCols = (Eigen::Index)rowsX * 6; // ★ 6 variables per vertpatch
|
|
|
|
|
DEBUG_EXTRA("vertpatch2rows built: rowsX=%u, scalarCols=%lld", rowsX, (long long)scalarCols); |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ========== 构建 Gamma(Tikhonov 正则)==========
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ========== 构建 Gamma(平滑正则)==========
|
|
|
|
|
std::vector<GSLTriplet> gammaTriplets; |
|
|
|
|
gammaTriplets.reserve((size_t)vertices.size() * 8); |
|
|
|
|
gammaTriplets.reserve((size_t)vertices.size() * 12); |
|
|
|
|
|
|
|
|
|
struct GammaRaw { Eigen::Index colA, colB; float w; }; |
|
|
|
|
std::vector<GammaRaw> rawGamma; |
|
|
|
|
rawGamma.reserve(vertices.size() * 6); |
|
|
|
|
rawGamma.reserve(vertices.size() * 8); |
|
|
|
|
|
|
|
|
|
Mesh::VertexIdxArr adjVerts; |
|
|
|
|
FOREACH(v, vertices) { |
|
|
|
|
adjVerts.Empty(); |
|
|
|
|
scene.mesh.GetAdjVertices(v, adjVerts); |
|
|
|
|
|
|
|
|
|
const size_t MAX_ADJ = 8; |
|
|
|
|
if (adjVerts.GetSize() > MAX_ADJ) adjVerts.Resize(MAX_ADJ); |
|
|
|
|
if (adjVerts.GetSize() > 8) adjVerts.Resize(8); |
|
|
|
|
|
|
|
|
|
VertexPatchIterator itV(patchIndices[v], seamVertices); |
|
|
|
|
while (itV.Next()) { |
|
|
|
|
@ -9301,7 +9292,7 @@ void MeshTexture::GlobalSeamLeveling4()
@@ -9301,7 +9292,7 @@ void MeshTexture::GlobalSeamLeveling4()
|
|
|
|
|
if (idxPatch == numPatches) continue; |
|
|
|
|
auto it_row = vertpatch2rows[v].find(idxPatch); |
|
|
|
|
if (it_row == vertpatch2rows[v].end()) continue; |
|
|
|
|
const Eigen::Index colA = it_row->second; |
|
|
|
|
const Eigen::Index baseA = it_row->second * 6; // ★ base * 6
|
|
|
|
|
|
|
|
|
|
for (const VIndex vAdj : adjVerts) { |
|
|
|
|
if (v >= vAdj) continue; |
|
|
|
|
@ -9311,10 +9302,13 @@ void MeshTexture::GlobalSeamLeveling4()
@@ -9311,10 +9302,13 @@ void MeshTexture::GlobalSeamLeveling4()
|
|
|
|
|
if (idxPatch != idxPatchAdj) continue; |
|
|
|
|
auto it_rowAdj = vertpatch2rows[vAdj].find(idxPatchAdj); |
|
|
|
|
if (it_rowAdj == vertpatch2rows[vAdj].end()) continue; |
|
|
|
|
const Eigen::Index colB = it_rowAdj->second; |
|
|
|
|
const Eigen::Index baseB = it_rowAdj->second * 6; |
|
|
|
|
|
|
|
|
|
const float w = (vertexInvalid[v] || vertexInvalid[vAdj]) ? 0.05f : 0.5f; |
|
|
|
|
rawGamma.push_back({colA, colB, w}); |
|
|
|
|
const float w = (vertexInvalid[v] || vertexInvalid[vAdj]) ? 0.02f : 0.3f; |
|
|
|
|
// scale 和 offset 都加平滑
|
|
|
|
|
for (int k = 0; k < 6; ++k) { |
|
|
|
|
rawGamma.push_back({baseA + k, baseB + k, w}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -9330,27 +9324,23 @@ void MeshTexture::GlobalSeamLeveling4()
@@ -9330,27 +9324,23 @@ void MeshTexture::GlobalSeamLeveling4()
|
|
|
|
|
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); |
|
|
|
|
gammaTriplets.emplace_back(rowG, rawGamma[i].colA, rawGamma[i].w); |
|
|
|
|
gammaTriplets.emplace_back(rowG, rawGamma[i].colB, -rawGamma[i].w); |
|
|
|
|
++rowG; |
|
|
|
|
i = j; |
|
|
|
|
} |
|
|
|
|
const Eigen::Index rowsGamma = rowG; |
|
|
|
|
|
|
|
|
|
SparseMat Gamma(rowsGamma, scalarCols); |
|
|
|
|
SparseMat Gamma(rowG, scalarCols); |
|
|
|
|
Gamma.setFromTriplets(gammaTriplets.begin(), gammaTriplets.end()); |
|
|
|
|
Gamma.makeCompressed(); |
|
|
|
|
gammaTriplets.clear(); gammaTriplets.shrink_to_fit(); |
|
|
|
|
rawGamma.clear(); rawGamma.shrink_to_fit(); |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ========== 构建矩阵 A 和 b(颜色一致性约束)==========
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ========== 构建约束 A * x = b ==========
|
|
|
|
|
std::vector<GSLTriplet> ATriplets; |
|
|
|
|
ATriplets.reserve(seamVertices.GetSize() * 6); |
|
|
|
|
ATriplets.reserve(seamVertices.GetSize() * 12); |
|
|
|
|
std::vector<float> b_coeff; |
|
|
|
|
b_coeff.reserve(seamVertices.GetSize() * 6); |
|
|
|
|
b_coeff.reserve(seamVertices.GetSize() * 12); |
|
|
|
|
|
|
|
|
|
IndexArr indices; |
|
|
|
|
Colors vertexColors; |
|
|
|
|
@ -9367,9 +9357,7 @@ void MeshTexture::GlobalSeamLeveling4()
@@ -9367,9 +9357,7 @@ void MeshTexture::GlobalSeamLeveling4()
|
|
|
|
|
const TexturePatch& tpRef = texturePatches[patch0.idxPatch]; |
|
|
|
|
if (tpRef.label < 0 || tpRef.label >= (IIndex)images.size()) continue; |
|
|
|
|
|
|
|
|
|
// ★FIX: 检查 patch0.proj 是否为 NaN/Inf(崩溃根源)
|
|
|
|
|
if (std::isnan(patch0.proj.x) || std::isnan(patch0.proj.y) || |
|
|
|
|
std::isinf(patch0.proj.x) || std::isinf(patch0.proj.y)) { |
|
|
|
|
if (std::isnan(patch0.proj.x) || std::isnan(patch0.proj.y)) { |
|
|
|
|
vertexColors[i] = Color::ZERO; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
@ -9381,12 +9369,7 @@ void MeshTexture::GlobalSeamLeveling4()
@@ -9381,12 +9369,7 @@ void MeshTexture::GlobalSeamLeveling4()
|
|
|
|
|
const auto idxPatch1 = seamVertex1.patches.Find(patch0.idxPatch); |
|
|
|
|
if (idxPatch1 == SeamVertex::Patches::NO_INDEX) continue; |
|
|
|
|
const SeamVertex::Patch& patch1 = seamVertex1.patches[idxPatch1]; |
|
|
|
|
|
|
|
|
|
// ★FIX: 检查 patch1.proj 是否为 NaN/Inf,防止 AddEdge 崩溃
|
|
|
|
|
if (std::isnan(patch1.proj.x) || std::isnan(patch1.proj.y) || |
|
|
|
|
std::isinf(patch1.proj.x) || std::isinf(patch1.proj.y)) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
if (std::isnan(patch1.proj.x) || std::isnan(patch1.proj.y)) continue; |
|
|
|
|
sampler.AddEdge(patch0.proj, patch1.proj); |
|
|
|
|
} |
|
|
|
|
vertexColors[i] = sampler.GetColor(); |
|
|
|
|
@ -9399,74 +9382,128 @@ void MeshTexture::GlobalSeamLeveling4()
@@ -9399,74 +9382,128 @@ void MeshTexture::GlobalSeamLeveling4()
|
|
|
|
|
const uint32_t ipb = seamVertex.patches[indices[j]].idxPatch; |
|
|
|
|
auto itA = v2r.find(ipa), itB = v2r.find(ipb); |
|
|
|
|
if (itA == v2r.end() || itB == v2r.end()) continue; |
|
|
|
|
const Eigen::Index baseA = itA->second; |
|
|
|
|
const Eigen::Index baseB = itB->second; |
|
|
|
|
|
|
|
|
|
const Eigen::Index baseA = itA->second * 6; // ★ ×6
|
|
|
|
|
const Eigen::Index baseB = itB->second * 6; |
|
|
|
|
|
|
|
|
|
const Color& ca = vertexColors[i]; |
|
|
|
|
const Color& cb = vertexColors[j]; |
|
|
|
|
|
|
|
|
|
// ★ 仿射约束: scale_A * ca + offset_A = scale_B * cb + offset_B
|
|
|
|
|
// 即: scale_A * ca - scale_B * cb + offset_A - offset_B = 0
|
|
|
|
|
for (int c = 0; c < 3; ++c) { |
|
|
|
|
const Eigen::Index colA = baseA * 3 + c; |
|
|
|
|
const Eigen::Index colB = baseB * 3 + c; |
|
|
|
|
ATriplets.emplace_back(rowA, colA, 1.0f); |
|
|
|
|
ATriplets.emplace_back(rowA, colB, -1.0f); |
|
|
|
|
const float norm_ca = ca[c] / 255.0f; // ★ 归一化
|
|
|
|
|
const float norm_cb = cb[c] / 255.0f; // ★ 归一化
|
|
|
|
|
|
|
|
|
|
const Eigen::Index colA_scale = baseA + c; |
|
|
|
|
const Eigen::Index colA_offset = baseA + 3 + c; |
|
|
|
|
const Eigen::Index colB_scale = baseB + c; |
|
|
|
|
const Eigen::Index colB_offset = baseB + 3 + c; |
|
|
|
|
|
|
|
|
|
ATriplets.emplace_back(rowA, colA_scale, norm_ca); |
|
|
|
|
ATriplets.emplace_back(rowA, colB_scale, -norm_cb); |
|
|
|
|
ATriplets.emplace_back(rowA, colA_offset, 1.0f); |
|
|
|
|
ATriplets.emplace_back(rowA, colB_offset, -1.0f); |
|
|
|
|
|
|
|
|
|
if ((size_t)rowA >= b_coeff.size()) b_coeff.push_back(0); |
|
|
|
|
b_coeff[rowA] = cb[c] - ca[c]; |
|
|
|
|
b_coeff[rowA] = 1.0f; |
|
|
|
|
++rowA; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const Eigen::Index rowsA = rowA; |
|
|
|
|
SparseMat A(rowsA, scalarCols); |
|
|
|
|
A.setFromTriplets(ATriplets.begin(), ATriplets.end()); |
|
|
|
|
A.makeCompressed(); |
|
|
|
|
ATriplets.clear(); ATriplets.shrink_to_fit(); |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ========== 求解 ==========
|
|
|
|
|
// ============================================================
|
|
|
|
|
std::vector<Color> colorAdjustments; |
|
|
|
|
colorAdjustments.assign(rowsX, Color::ZERO); |
|
|
|
|
struct AffineParam { float scale[3]; float offset[3]; }; |
|
|
|
|
std::vector<AffineParam> patchAffine; |
|
|
|
|
patchAffine.assign(rowsX, {{1,1,1}, {0,0,0}}); |
|
|
|
|
|
|
|
|
|
if (rowsA > 0 && rowsX > 0) { |
|
|
|
|
SparseMat ATA = A.transpose() * A; |
|
|
|
|
SparseMat GTG = Gamma.transpose() * Gamma; |
|
|
|
|
const float lambda = 0.1f; |
|
|
|
|
const float lambda = 20.0f; |
|
|
|
|
GTG *= lambda * lambda; |
|
|
|
|
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; |
|
|
|
|
// ★ 正确构建 rhs = A^T * b
|
|
|
|
|
Eigen::VectorXf rhs = A.transpose() * Eigen::Map<Eigen::VectorXf>(b_coeff.data(), rowsA); |
|
|
|
|
|
|
|
|
|
// ★ 正则化:同时加 Lhs 和 rhs
|
|
|
|
|
const float lambda_identity = 20.0f; // ★ 增大
|
|
|
|
|
for (Eigen::Index v = 0; v < (Eigen::Index)rowsX; ++v) { |
|
|
|
|
for (int c = 0; c < 3; ++c) { |
|
|
|
|
const Eigen::Index col_scale = v * 6 + c; |
|
|
|
|
const Eigen::Index col_offset = v * 6 + 3 + c; |
|
|
|
|
|
|
|
|
|
Lhs.coeffRef(col_scale, col_scale) += lambda_identity; |
|
|
|
|
Lhs.coeffRef(col_offset, col_offset) += lambda_identity * 0.5f; |
|
|
|
|
|
|
|
|
|
rhs(col_scale) += lambda_identity * 1.0f; |
|
|
|
|
// rhs(col_offset) += 0 (target = 0)
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const float eps = 1e-4f; |
|
|
|
|
for (Eigen::Index i = 0; i < (Eigen::Index)Lhs.rows(); ++i) |
|
|
|
|
Lhs.coeffRef(i, i) += eps; |
|
|
|
|
|
|
|
|
|
Eigen::ConjugateGradient<SparseMat, Eigen::Lower> solver; |
|
|
|
|
solver.setMaxIterations(2000); |
|
|
|
|
solver.setMaxIterations(3000); |
|
|
|
|
solver.setTolerance(0.001f); |
|
|
|
|
solver.compute(Lhs); |
|
|
|
|
if (solver.info() == Eigen::Success) { |
|
|
|
|
Eigen::VectorXf b(rowsA); |
|
|
|
|
for (Eigen::Index k = 0; k < rowsA; ++k) b(k) = b_coeff[k]; |
|
|
|
|
const Eigen::VectorXf rhs = A.transpose() * b; |
|
|
|
|
const Eigen::VectorXf x = solver.solve(rhs); |
|
|
|
|
|
|
|
|
|
// ★★★ 放在这里 ★★★
|
|
|
|
|
DEBUG_EXTRA("Solver: info=%d iterations=%d error=%.6f", |
|
|
|
|
(int)solver.info(), solver.iterations(), solver.error()); |
|
|
|
|
|
|
|
|
|
printf(">>> MY_NEW_CODE_IS_RUNNING <<<\n"); |
|
|
|
|
fflush(stdout); |
|
|
|
|
|
|
|
|
|
// ★ 同时把 RAW x stats 也放这里
|
|
|
|
|
if ((Eigen::Index)(x.size()) >= scalarCols) { |
|
|
|
|
float x_min = x.minCoeff(), x_max = x.maxCoeff(); |
|
|
|
|
float x_mean = x.mean(); |
|
|
|
|
DEBUG_EXTRA("RAW x stats: min=%.4f max=%.4f mean=%.4f", x_min, x_max, x_mean); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (solver.info() == Eigen::Success && (Eigen::Index)(x.size()) >= scalarCols) { |
|
|
|
|
colorAdjustments.assign(rowsX, Color::ZERO); |
|
|
|
|
for (Eigen::Index v = 0; v < (Eigen::Index)rowsX; ++v) { |
|
|
|
|
const float r = x(v * 3 + 0); |
|
|
|
|
const float g = x(v * 3 + 1); |
|
|
|
|
const float b_ch = x(v * 3 + 2); |
|
|
|
|
|
|
|
|
|
// ★ 恢复原版逻辑:计算均值并减去(消除亮度偏移,只保留色度调整)
|
|
|
|
|
const float mean = (r + g + b_ch) / 3.0f; |
|
|
|
|
colorAdjustments[v] = Color(r - mean, g - mean, b_ch - mean); |
|
|
|
|
AffineParam& param = patchAffine[v]; |
|
|
|
|
for (int c = 0; c < 3; ++c) { |
|
|
|
|
param.scale[c] = std::max(-2.0f, std::min(4.0f, x(v * 6 + c))); |
|
|
|
|
param.offset[c] = std::max(-1.0f, std::min(1.0f, x(v * 6 + 3 + c))); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
b_coeff.clear(); b_coeff.shrink_to_fit(); |
|
|
|
|
|
|
|
|
|
// ========== 统计 ==========
|
|
|
|
|
{ |
|
|
|
|
float s_sum=0, o_sum=0, s_mn=1e9, s_mx=-1e9, o_mn=1e9, o_mx=-1e9; |
|
|
|
|
int cnt=0; |
|
|
|
|
for (const auto& p : patchAffine) { |
|
|
|
|
for (int c=0; c<3; ++c) { |
|
|
|
|
s_sum += p.scale[c]; o_sum += p.offset[c]; |
|
|
|
|
s_mn = std::min(s_mn, p.scale[c]); s_mx = std::max(s_mx, p.scale[c]); |
|
|
|
|
o_mn = std::min(o_mn, p.offset[c]); o_mx = std::max(o_mx, p.offset[c]); |
|
|
|
|
++cnt; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("Affine stats: scale min=%.3f max=%.3f mean=%.3f | offset min=%.1f max=%.1f mean=%.1f", |
|
|
|
|
s_mn, s_mx, s_sum/cnt, o_mn, o_mx, o_sum/(cnt*3)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ========== 应用颜色修正 ==========
|
|
|
|
|
correctedPatchImages.resize(numPatches); |
|
|
|
|
|
|
|
|
|
@ -9486,25 +9523,34 @@ void MeshTexture::GlobalSeamLeveling4()
@@ -9486,25 +9523,34 @@ void MeshTexture::GlobalSeamLeveling4()
|
|
|
|
|
cv::Mat& outPatch = correctedPatchImages[idxPatch]; |
|
|
|
|
outPatch = cv::Mat::zeros(texturePatch.rect.size(), CV_8UC3); |
|
|
|
|
|
|
|
|
|
ColorMap imageAdj(texturePatch.rect.size()); |
|
|
|
|
imageAdj.memset(0); |
|
|
|
|
ColorMap imageAffine(texturePatch.rect.size()); |
|
|
|
|
// 初始化为 identity (scale=1, offset=0)
|
|
|
|
|
for (int _r = 0; _r < imageAffine.rows; ++_r) |
|
|
|
|
for (int _c = 0; _c < imageAffine.cols; ++_c) |
|
|
|
|
imageAffine(_r, _c) = Color(1.0f, 1.0f, 1.0f); // 只用 scale,offset 用另一个
|
|
|
|
|
|
|
|
|
|
struct RasterPatch { |
|
|
|
|
// ★ 用两个 ColorMap 分别存 scale 和 offset
|
|
|
|
|
ColorMap imageOffset(texturePatch.rect.size()); |
|
|
|
|
for (int _r = 0; _r < imageOffset.rows; ++_r) |
|
|
|
|
for (int _c = 0; _c < imageOffset.cols; ++_c) |
|
|
|
|
imageOffset(_r, _c) = Color(0.0f, 0.0f, 0.0f); |
|
|
|
|
|
|
|
|
|
struct RasterPatchAffine { |
|
|
|
|
const TexCoord* tri; |
|
|
|
|
Color colors[3]; |
|
|
|
|
ColorMap& image; |
|
|
|
|
RasterPatch(ColorMap& _image) : image(_image) {} |
|
|
|
|
inline cv::Size Size() const { return image.size(); } |
|
|
|
|
Color colors[3]; // scale
|
|
|
|
|
Color colorsOffset[3]; // offset
|
|
|
|
|
ColorMap& imageScale; |
|
|
|
|
ColorMap& imageOffset; |
|
|
|
|
RasterPatchAffine(ColorMap& s, ColorMap& o) : imageScale(s), imageOffset(o) {} |
|
|
|
|
inline cv::Size Size() const { return imageScale.size(); } |
|
|
|
|
void operator()(const ImageRef& pt, const Point3f& bary) { |
|
|
|
|
Color c = colors[0]*bary.x + colors[1]*bary.y + colors[2]*bary.z; |
|
|
|
|
// ★ 过滤 NaN/Inf 调整值
|
|
|
|
|
if (std::isnan(c[0]) || std::isnan(c[1]) || std::isnan(c[2]) || |
|
|
|
|
std::isinf(c[0]) || std::isinf(c[1]) || std::isinf(c[2])) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
image(pt) = c; |
|
|
|
|
Color s = colors[0]*bary.x + colors[1]*bary.y + colors[2]*bary.z; |
|
|
|
|
Color o = colorsOffset[0]*bary.x + colorsOffset[1]*bary.y + colorsOffset[2]*bary.z; |
|
|
|
|
if (std::isnan(s[0]) || std::isinf(s[0])) return; |
|
|
|
|
imageScale(pt) = s; |
|
|
|
|
imageOffset(pt) = o; |
|
|
|
|
} |
|
|
|
|
} data(imageAdj); |
|
|
|
|
} data(imageAffine, imageOffset); |
|
|
|
|
|
|
|
|
|
for (const FIndex idxFace : texturePatch.faces) { |
|
|
|
|
if (idxFace >= faces.GetSize()) continue; |
|
|
|
|
@ -9515,39 +9561,102 @@ void MeshTexture::GlobalSeamLeveling4()
@@ -9515,39 +9561,102 @@ void MeshTexture::GlobalSeamLeveling4()
|
|
|
|
|
ASSERT(face[v] < vertices.size()); |
|
|
|
|
auto search = vertpatch2rows[face[v]].find(idxPatch); |
|
|
|
|
if (search != vertpatch2rows[face[v]].end()) { |
|
|
|
|
ASSERT(search->second < (Eigen::Index)colorAdjustments.size()); |
|
|
|
|
data.colors[v] = colorAdjustments[search->second]; |
|
|
|
|
const AffineParam& param = patchAffine[search->second]; |
|
|
|
|
data.colors[v] = Color(param.scale[0], param.scale[1], param.scale[2]); |
|
|
|
|
data.colorsOffset[v] = Color(param.offset[0], param.offset[1], param.offset[2]); |
|
|
|
|
} else { |
|
|
|
|
data.colors[v] = Color::ZERO; |
|
|
|
|
data.colors[v] = Color(1.0f, 1.0f, 1.0f); |
|
|
|
|
data.colorsOffset[v] = Color(0.0f, 0.0f, 0.0f); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
ColorMap::RasterizeTriangleBary(data.tri[0], data.tri[1], data.tri[2], data); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ★FIX: 改回 DilateMean<1>(和原版一致,之前 <5> 过度模糊)
|
|
|
|
|
imageAdj.DilateMean<1>(imageAdj, Color::ZERO); |
|
|
|
|
imageAffine.DilateMean<1>(imageAffine, Color(1.0f, 1.0f, 1.0f)); |
|
|
|
|
imageOffset.DilateMean<1>(imageOffset, Color(0.0f, 0.0f, 0.0f)); |
|
|
|
|
|
|
|
|
|
// ★FIX: 去掉 GAIN=10,Y/Cb/Cr 全部 1:1 微调(和原版 GSL3 一致)
|
|
|
|
|
// 应用: out = src * scale + offset * 255
|
|
|
|
|
for (int r = 0; r < srcROI.rows; ++r) { |
|
|
|
|
for (int c = 0; c < srcROI.cols; ++c) { |
|
|
|
|
const Color& a = imageAdj(r, c); |
|
|
|
|
if (a == Color::ZERO) { |
|
|
|
|
outPatch.at<cv::Vec3b>(r, c) = srcROI.at<cv::Vec3b>(r, c); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
const Pixel8U& v = srcROI.at<Pixel8U>(r, c); |
|
|
|
|
const Color col(RGB2YCBCR(Color(v))); |
|
|
|
|
// ★ 直接加,不放大任何通道
|
|
|
|
|
const Color acol(YCBCR2RGB(Color(col + a))); |
|
|
|
|
const Color& scale = imageAffine(r, c); |
|
|
|
|
const Color& offset = imageOffset(r, c); |
|
|
|
|
const cv::Vec3b& src = srcROI.at<cv::Vec3b>(r, c); |
|
|
|
|
|
|
|
|
|
outPatch.at<cv::Vec3b>(r, c) = cv::Vec3b( |
|
|
|
|
(uint8_t)CLAMP(ROUND2INT(acol[0]), 0, 255), |
|
|
|
|
(uint8_t)CLAMP(ROUND2INT(acol[1]), 0, 255), |
|
|
|
|
(uint8_t)CLAMP(ROUND2INT(acol[2]), 0, 255)); |
|
|
|
|
(uint8_t)CLAMP(ROUND2INT(src[0] * scale[0] + offset[0] * 255.0f), 0, 255), |
|
|
|
|
(uint8_t)CLAMP(ROUND2INT(src[1] * scale[1] + offset[1] * 255.0f), 0, 255), |
|
|
|
|
(uint8_t)CLAMP(ROUND2INT(src[2] * scale[2] + offset[2] * 255.0f), 0, 255) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("GlobalSeamLeveling4 finished successfully."); |
|
|
|
|
// ========== 映射到按 texturePatch 索引的数组 ==========
|
|
|
|
|
patchAffineByTexPatch.clear(); |
|
|
|
|
patchAffineByTexPatch.resize(images.size(), {1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f}); |
|
|
|
|
|
|
|
|
|
// 对于每个 texturePatch,取其第一个顶点的仿射参数作为代表
|
|
|
|
|
for (size_t tpi = 0; tpi < numPatches; ++tpi) { |
|
|
|
|
const TexturePatch& tp = texturePatches[tpi]; |
|
|
|
|
if (tp.label < 0 || tp.label >= (int)images.size()) continue; |
|
|
|
|
|
|
|
|
|
// 取该 patch 的第一个有效 face 的第一个顶点
|
|
|
|
|
bool found = false; |
|
|
|
|
for (FIndex fid : tp.faces) { |
|
|
|
|
if (fid >= faces.GetSize()) continue; |
|
|
|
|
const Face& face = faces[fid]; |
|
|
|
|
for (int v = 0; v < 3; ++v) { |
|
|
|
|
auto search = vertpatch2rows[face[v]].find((uint32_t)tpi); |
|
|
|
|
if (search != vertpatch2rows[face[v]].end()) { |
|
|
|
|
const AffineParam& param = patchAffine[search->second]; |
|
|
|
|
AffineCorrection& ac = patchAffineByTexPatch[tp.label]; |
|
|
|
|
ac.kr = param.scale[0]; |
|
|
|
|
ac.kg = param.scale[1]; |
|
|
|
|
ac.kb = param.scale[2]; |
|
|
|
|
ac.br = param.offset[0]; |
|
|
|
|
ac.bg = param.offset[1]; |
|
|
|
|
ac.bb = param.offset[2]; |
|
|
|
|
found = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (found) break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
patchAffineByTexPatch.clear(); |
|
|
|
|
patchAffineByTexPatch.resize(numPatches, {1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f}); |
|
|
|
|
|
|
|
|
|
printf("[DEBUG] Filling patchAffineByTexPatch, numPatches=%zu\n", numPatches); |
|
|
|
|
|
|
|
|
|
for (size_t tpi = 0; tpi < numPatches; ++tpi) { |
|
|
|
|
const TexturePatch& tp = texturePatches[tpi]; |
|
|
|
|
bool found = false; |
|
|
|
|
for (FIndex fid : tp.faces) { |
|
|
|
|
if (fid >= faces.GetSize()) continue; |
|
|
|
|
const Face& face = faces[fid]; |
|
|
|
|
for (int v = 0; v < 3; ++v) { |
|
|
|
|
auto search = vertpatch2rows[face[v]].find((uint32_t)tpi); |
|
|
|
|
if (search != vertpatch2rows[face[v]].end()) { |
|
|
|
|
const AffineParam& param = patchAffine[search->second]; |
|
|
|
|
AffineCorrection& ac = patchAffineByTexPatch[tpi]; |
|
|
|
|
ac.kr = param.scale[0]; |
|
|
|
|
ac.kg = param.scale[1]; |
|
|
|
|
ac.kb = param.scale[2]; |
|
|
|
|
ac.br = param.offset[0]; |
|
|
|
|
ac.bg = param.offset[1]; |
|
|
|
|
ac.bb = param.offset[2]; |
|
|
|
|
found = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (found) break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
printf("[DEBUG] patchAffineByTexPatch filled, size=%zu\n", patchAffineByTexPatch.size()); |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("GlobalSeamLeveling4 (Affine) finished successfully."); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// set to one in order to dilate also on the diagonal of the border
|
|
|
|
|
@ -15611,7 +15720,7 @@ void MeshTexture::AlignPatchColors(Image8U3& atlas,
@@ -15611,7 +15720,7 @@ void MeshTexture::AlignPatchColors(Image8U3& atlas,
|
|
|
|
|
for (int c = 0; c < 3; ++c) { |
|
|
|
|
if (selfMean[c] > 1.0f) { |
|
|
|
|
float g = (float)(nbMean[c] / selfMean[c]); |
|
|
|
|
gain[i][c] = std::max(0.7f, std::min(1.4f, g)); // 保守范围
|
|
|
|
|
gain[i][c] = std::max(0.5f, std::min(2.0f, g)); // 保守范围
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -15687,11 +15796,11 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
@@ -15687,11 +15796,11 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: bridging %zu rcPatches...", rcPatches.size()); |
|
|
|
|
DEBUG_EXTRA("faceTexcoords ptr=%p, scene ptr=%p", &faceTexcoords, &scene.mesh.faceTexcoords); |
|
|
|
|
|
|
|
|
|
// ========== 1. 构建 texturePatches ==========
|
|
|
|
|
texturePatches.clear(); |
|
|
|
|
std::vector<size_t> rcToTexPatch(rcPatches.size(), NO_ID); |
|
|
|
|
|
|
|
|
|
// ★★★ 诊断日志:构建循环之前 ★★★
|
|
|
|
|
if (!rcPatches.empty()) { |
|
|
|
|
DEBUG_EXTRA("rcPatch[0]: viewID=%d, faces.size()=%zu, firstFace=%u, virtualFaceMap.size()=%zu", |
|
|
|
|
rcPatches[0].viewID, rcPatches[0].faces.size(), |
|
|
|
|
@ -15699,7 +15808,6 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
@@ -15699,7 +15808,6 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
|
|
|
|
virtualFaceMap.size()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ★ 诊断计数器
|
|
|
|
|
size_t skippedEmpty = 0, skippedBadView = 0, skippedEmptyImage = 0; |
|
|
|
|
size_t skippedNoProj = 0, skippedBadROI = 0, skippedNoFaces = 0; |
|
|
|
|
|
|
|
|
|
@ -15742,7 +15850,6 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
@@ -15742,7 +15850,6 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
|
|
|
|
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]; |
|
|
|
|
@ -15796,20 +15903,6 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
@@ -15796,20 +15903,6 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
|
|
|
|
|
|
|
|
|
seamEdges.clear(); seamVertices.clear(); |
|
|
|
|
|
|
|
|
|
// // 填充 faceToPatchID
|
|
|
|
|
// faceToPatchID.assign(scene.mesh.faces.size(), NO_ID);
|
|
|
|
|
// for (size_t pi = 0; pi < rcPatches.size(); ++pi) {
|
|
|
|
|
// if (rcToTexPatch[pi] == NO_ID) continue;
|
|
|
|
|
// uint32_t texPatchID = rcToTexPatch[pi];
|
|
|
|
|
// for (FIndex vfID : rcPatches[pi].faces) {
|
|
|
|
|
// if (vfID >= virtualFaceMap.size()) continue;
|
|
|
|
|
// for (FIndex fid : virtualFaceMap[vfID].faces) {
|
|
|
|
|
// if (fid < faceToPatchID.size())
|
|
|
|
|
// faceToPatchID[fid] = texPatchID;
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
BuildSeamEdgesFromFaceToPatchID(); |
|
|
|
|
CreateSeamVertices(); |
|
|
|
|
|
|
|
|
|
@ -15829,11 +15922,10 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
@@ -15829,11 +15922,10 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// faceTexcoords = localFaceTexcoords;
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Bridge done: %zu texturePatches ready, calling GlobalSeamLeveling4...", numValidPatches); |
|
|
|
|
|
|
|
|
|
// ========== 调用 GSL4(内部修改 images)==========
|
|
|
|
|
// ========== 调用 GSL4 ==========
|
|
|
|
|
GlobalSeamLeveling4(); |
|
|
|
|
DEBUG_EXTRA("GlobalSeamLeveling4 finished."); |
|
|
|
|
|
|
|
|
|
@ -15843,24 +15935,16 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
@@ -15843,24 +15935,16 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
|
|
|
|
DEBUG_EXTRA("expected: %d x %d x 3 = %d bytes", |
|
|
|
|
textureSize, textureSize, textureSize * textureSize * 3); |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ★ 桥接补丁:确保 texturePatches[i].label = viewID
|
|
|
|
|
// ============================================================
|
|
|
|
|
// 你的 rcPatches 里每个 patch 有 viewID,但桥接时可能没设 label。
|
|
|
|
|
// 如果桥接代码已经设了,这个循环是幂等的,不会破坏任何东西。
|
|
|
|
|
// ========== 桥接补丁 ==========
|
|
|
|
|
VERBOSE("Syncing texturePatches.label ← rcPatches.viewID..."); |
|
|
|
|
for (uint32_t i = 0; i < texturePatches.size(); ++i) { |
|
|
|
|
// texturePatches 索引 i 对应 rcPatches 索引 i(你的桥接是 1:1 的)
|
|
|
|
|
if (i < rcPatches.size()) { |
|
|
|
|
texturePatches[i].label = rcPatches[i].viewID; |
|
|
|
|
} else { |
|
|
|
|
// 兜底:如果数量不匹配,用 faceToPatchID 反查
|
|
|
|
|
// 这种情况不应该发生,打日志报警
|
|
|
|
|
VERBOSE(" WARNING: texturePatch[%u] has no matching rcPatch!", i); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 验证一下 label 是否正确
|
|
|
|
|
int labelOK = 0, labelBad = 0; |
|
|
|
|
for (uint32_t i = 0; i < texturePatches.size(); ++i) { |
|
|
|
|
if (texturePatches[i].label >= 0 && texturePatches[i].label < (int)images.size()) { |
|
|
|
|
@ -15872,23 +15956,18 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
@@ -15872,23 +15956,18 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
|
|
|
|
VERBOSE(" label check: %d OK, %d BAD (out of %u)", labelOK, labelBad, (uint32_t)texturePatches.size()); |
|
|
|
|
if (labelBad > 0) { |
|
|
|
|
VERBOSE(" ERROR: %d texturePatches have invalid label (viewID out of range)!", labelBad); |
|
|
|
|
// 不 return,让 LSL3 尽量跑,坏 label 的 patch 会采样到错误图像但不会导致崩溃
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ★ 确认 seamVertices 已构建(你的日志显示已经 90152 个)
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ========== seamVertices 检查 ==========
|
|
|
|
|
if (seamVertices.empty()) { |
|
|
|
|
VERBOSE("seamVertices is empty! Calling CreateSeamVertices()..."); |
|
|
|
|
CreateSeamVertices(); // 如果之前没调过,这里补调
|
|
|
|
|
CreateSeamVertices(); |
|
|
|
|
VERBOSE(" seamVertices created: %zu", seamVertices.size()); |
|
|
|
|
} else { |
|
|
|
|
VERBOSE("seamVertices already built: %zu vertices, ready for LSL3", seamVertices.size()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ★ 调用 LocalSeamLeveling4
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ========== 调用 LocalSeamLeveling4 ==========
|
|
|
|
|
VERBOSE("Calling LocalSeamLeveling4 (poisson blending on %zu patches)...", texturePatches.size() - 1); |
|
|
|
|
VERBOSE(" This may take 5~15 minutes for %zu patches...", texturePatches.size() - 1); |
|
|
|
|
|
|
|
|
|
@ -15902,41 +15981,57 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
@@ -15902,41 +15981,57 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
|
|
|
|
for (int i = 0; i < std::min(3, (int)images.size()); ++i) { |
|
|
|
|
char dbgName[256]; |
|
|
|
|
sprintf(dbgName, "debug_image_%d_after_gsl4.png", i); |
|
|
|
|
cv::imwrite(dbgName, images[i].image); // ← 用 .image
|
|
|
|
|
cv::imwrite(dbgName, images[i].image); |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("Exported debug images after GSL4"); |
|
|
|
|
|
|
|
|
|
// ========== 3. 重光栅化(从已被 GSL4 修正的 images 读)==========
|
|
|
|
|
// ========== 3. 重光栅化(自动检测H方向 + ROI + 仿射校正)==========
|
|
|
|
|
|
|
|
|
|
if (!images.empty() && !images[0].image.empty()) { |
|
|
|
|
const auto& p = images[0].image.at<cv::Vec3b>(images[0].image.rows/2, images[0].image.cols/2); |
|
|
|
|
LOG("center pixel B=%d G=%d R=%d\n", p[0], p[1], p[2]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ASSERT(atlas.rows == textureSize && atlas.cols == textureSize); |
|
|
|
|
localAtlas.setTo(cv::Scalar(0,0,0)); |
|
|
|
|
|
|
|
|
|
bool firstParamPrinted = false; |
|
|
|
|
bool savedDebugRemapped = false; |
|
|
|
|
|
|
|
|
|
for (int pi = 0; pi < (int)rcPatches.size(); ++pi) { |
|
|
|
|
if (pi % 500 == 0 || pi == (int)rcPatches.size() - 1) { |
|
|
|
|
printf("[Reraster] patch %d / %zu\n", pi, rcPatches.size()); |
|
|
|
|
fflush(stdout); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (rcToTexPatch[pi] == NO_ID) continue; |
|
|
|
|
const TexturePatch& tp = texturePatches[rcToTexPatch[pi]]; |
|
|
|
|
const Image& srcImg = images[tp.label]; |
|
|
|
|
|
|
|
|
|
if (srcImg.image.empty()) continue; |
|
|
|
|
|
|
|
|
|
if (rcToTexPatch[pi] >= correctedPatchImages.size() || correctedPatchImages[rcToTexPatch[pi]].empty()) |
|
|
|
|
continue; |
|
|
|
|
int texPatchIdx = rcToTexPatch[pi]; |
|
|
|
|
const AffineCorrection* acPtr = nullptr; |
|
|
|
|
if (texPatchIdx >= 0 && texPatchIdx < (int)patchAffineByTexPatch.size()) { |
|
|
|
|
acPtr = &patchAffineByTexPatch[texPatchIdx]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// cv::Mat correctedPatch = srcImg.image(tp.rect).clone();
|
|
|
|
|
cv::Mat correctedPatch = correctedPatchImages[rcToTexPatch[pi]].clone(); // 局部拷贝
|
|
|
|
|
if (!firstParamPrinted && acPtr) { |
|
|
|
|
printf("[DEBUG] pi=%d texPatchIdx=%d kr=%.4f kg=%.4f kb=%.4f br=%.4f bg=%.4f bb=%.4f\n", |
|
|
|
|
pi, texPatchIdx, acPtr->kr, acPtr->kg, acPtr->kb, acPtr->br, acPtr->bg, acPtr->bb); |
|
|
|
|
firstParamPrinted = true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 加上严格检查
|
|
|
|
|
static int cnt = 0; |
|
|
|
|
if (cnt < 5) { |
|
|
|
|
DEBUG_EXTRA("Patch %d: rcToTexPatch=%d, correctedPatch empty=%d, size=(%dx%d), type=%d", |
|
|
|
|
cnt, rcToTexPatch[pi], correctedPatch.empty(), |
|
|
|
|
correctedPatch.cols, correctedPatch.rows, correctedPatch.type()); |
|
|
|
|
// 从原图裁出 ROI
|
|
|
|
|
cv::Mat patchImage = srcImg.image(tp.rect).clone(); |
|
|
|
|
|
|
|
|
|
if (!correctedPatch.empty()) { |
|
|
|
|
char nm[256]; sprintf(nm, "debug_corrected_patch_%d.png", cnt); |
|
|
|
|
cv::imwrite(nm, correctedPatch); |
|
|
|
|
} |
|
|
|
|
cnt++; |
|
|
|
|
// 保存 ROI 诊断图
|
|
|
|
|
if (pi == 0) { |
|
|
|
|
cv::imwrite("/home/algo/Documents/openMVS/data/546893/out.546893/debug_patch0_roi.png", patchImage); |
|
|
|
|
printf("[DIAG] patchImage size=%dx%d, nonZero=%d\n", |
|
|
|
|
patchImage.cols, patchImage.rows, |
|
|
|
|
cv::countNonZero(patchImage.reshape(1))); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (correctedPatch.empty()) continue; |
|
|
|
|
|
|
|
|
|
const RCPatch& rp = rcPatches[pi]; |
|
|
|
|
for (FIndex vfID : rp.faces) { |
|
|
|
|
if (vfID >= m_virtualFaceGeometries.size()) continue; |
|
|
|
|
@ -15950,57 +16045,163 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
@@ -15950,57 +16045,163 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
|
|
|
|
if (minX > maxX || minY > maxY) continue; |
|
|
|
|
|
|
|
|
|
int patchW = maxX - minX + 1, patchH = maxY - minY + 1; |
|
|
|
|
|
|
|
|
|
// ★ 防止 patch 过大导致 remap 卡死
|
|
|
|
|
if (patchW > textureSize/2 || patchH > textureSize/2) { |
|
|
|
|
printf("[WARN] Skipping oversized patch at pi=%d: %dx%d\n", pi, patchW, patchH); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cv::Mat mapX(patchH, patchW, CV_32FC1), mapY(patchH, patchW, CV_32FC1); |
|
|
|
|
const float* H = geom.homography.ptr<float>(); |
|
|
|
|
for (int y = minY; y <= maxY; ++y) |
|
|
|
|
|
|
|
|
|
// ★ 策略1:先尝试求逆(假设 H 是 image→atlas)
|
|
|
|
|
cv::Mat H_atlas2img; |
|
|
|
|
cv::invert(geom.homography, H_atlas2img); |
|
|
|
|
const float* H = H_atlas2img.ptr<float>(); |
|
|
|
|
|
|
|
|
|
bool allOutOfRange = true; |
|
|
|
|
|
|
|
|
|
for (int y = minY; y <= maxY; ++y) { |
|
|
|
|
for (int x = minX; x <= maxX; ++x) { |
|
|
|
|
float u = (float)x / textureSize, v = (float)y / textureSize; |
|
|
|
|
float w = H[6]*u + H[7]*v + H[8]; |
|
|
|
|
if (std::abs(w) < 1e-12f) { |
|
|
|
|
mapX.at<float>(y-minY, x-minX) = -1; |
|
|
|
|
mapY.at<float>(y-minY, x-minX) = -1; continue; |
|
|
|
|
mapY.at<float>(y-minY, x-minX) = -1; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 映射到原图坐标
|
|
|
|
|
float srcX = (H[0]*u + H[1]*v + H[2])/w; |
|
|
|
|
float srcY = (H[3]*u + H[4]*v + H[5])/w; |
|
|
|
|
|
|
|
|
|
// 减去 rect 偏移得到 ROI 内坐标
|
|
|
|
|
srcX -= tp.rect.x; |
|
|
|
|
srcY -= tp.rect.y; |
|
|
|
|
|
|
|
|
|
mapX.at<float>(y-minY, x-minX) = srcX; |
|
|
|
|
mapY.at<float>(y-minY, x-minX) = srcY; |
|
|
|
|
|
|
|
|
|
if (srcX >= 0 && srcX < patchImage.cols && |
|
|
|
|
srcY >= 0 && srcY < patchImage.rows) { |
|
|
|
|
allOutOfRange = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 诊断:第一个 patch 的第一个像素
|
|
|
|
|
if (pi == 0 && vfID == rp.faces[0] && y == minY && x == minX) { |
|
|
|
|
printf("[DIAG] INVERT: uv=(%.4f, %.4f) -> src=(%.2f, %.2f) rect=(%d,%d,%d,%d) w=%.6f\n", |
|
|
|
|
u, v, srcX, srcY, tp.rect.x, tp.rect.y, tp.rect.width, tp.rect.height); |
|
|
|
|
printf("[DIAG] H_inv=[%.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f]\n", |
|
|
|
|
H[0], H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ★ 如果求逆策略全部越界,尝试直接用 H(不逆)
|
|
|
|
|
if (allOutOfRange) { |
|
|
|
|
printf("[DIAG] Invert strategy failed for pi=%d, trying direct H...\n", pi); |
|
|
|
|
|
|
|
|
|
const float* H_direct = geom.homography.ptr<float>(); |
|
|
|
|
allOutOfRange = true; |
|
|
|
|
|
|
|
|
|
for (int y = minY; y <= maxY; ++y) { |
|
|
|
|
for (int x = minX; x <= maxX; ++x) { |
|
|
|
|
float u = (float)x / textureSize, v = (float)y / textureSize; |
|
|
|
|
float w = H_direct[6]*u + H_direct[7]*v + H_direct[8]; |
|
|
|
|
if (std::abs(w) < 1e-12f) { |
|
|
|
|
mapX.at<float>(y-minY, x-minX) = -1; |
|
|
|
|
mapY.at<float>(y-minY, x-minX) = -1; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
float srcX = (H_direct[0]*u + H_direct[1]*v + H_direct[2])/w; |
|
|
|
|
float srcY = (H_direct[3]*u + H_direct[4]*v + H_direct[5])/w; |
|
|
|
|
|
|
|
|
|
// 不减去 rect(假设 H 已经在 ROI 坐标系)
|
|
|
|
|
// srcX -= tp.rect.x;
|
|
|
|
|
// srcY -= tp.rect.y;
|
|
|
|
|
|
|
|
|
|
mapX.at<float>(y-minY, x-minX) = srcX; |
|
|
|
|
mapY.at<float>(y-minY, x-minX) = srcY; |
|
|
|
|
|
|
|
|
|
if (srcX >= 0 && srcX < patchImage.cols && |
|
|
|
|
srcY >= 0 && srcY < patchImage.rows) { |
|
|
|
|
allOutOfRange = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (pi == 0 && vfID == rp.faces[0] && y == minY && x == minX) { |
|
|
|
|
printf("[DIAG] DIRECT: uv=(%.4f, %.4f) -> src=(%.2f, %.2f) w=%.6f\n", |
|
|
|
|
u, v, srcX, srcY, w); |
|
|
|
|
printf("[DIAG] H_direct=[%.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f]\n", |
|
|
|
|
H_direct[0], H_direct[1], H_direct[2], |
|
|
|
|
H_direct[3], H_direct[4], H_direct[5], |
|
|
|
|
H_direct[6], H_direct[7], H_direct[8]); |
|
|
|
|
} |
|
|
|
|
mapX.at<float>(y-minY, x-minX) = (H[0]*u + H[1]*v + H[2])/w - tp.rect.x; |
|
|
|
|
mapY.at<float>(y-minY, x-minX) = (H[3]*u + H[4]*v + H[5])/w - tp.rect.y; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 如果两种策略都失败,跳过这个虚拟面
|
|
|
|
|
if (allOutOfRange) { |
|
|
|
|
printf("[WARN] Both mapping strategies failed for pi=%d, vfID=%u\n", pi, vfID); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cv::Mat remapped; |
|
|
|
|
cv::remap(correctedPatch, remapped, mapX, mapY, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); |
|
|
|
|
cv::remap(patchImage, remapped, mapX, mapY, |
|
|
|
|
cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); |
|
|
|
|
|
|
|
|
|
for (int y = 0; y < patchH; ++y) |
|
|
|
|
{ |
|
|
|
|
// 保存 remapped 诊断图
|
|
|
|
|
if (pi == 0 && !savedDebugRemapped) { |
|
|
|
|
cv::imwrite("/home/algo/Documents/openMVS/data/546893/out.546893/debug_patch0_remapped.png", remapped); |
|
|
|
|
savedDebugRemapped = true; |
|
|
|
|
printf("[DIAG] Saved debug_patch0_remapped.png, nonZero=%d\n", |
|
|
|
|
cv::countNonZero(remapped.reshape(1))); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 写入 Atlas:【临时关闭仿射校正,解决偏蓝/反色】
|
|
|
|
|
for (int y = 0; y < patchH; ++y) { |
|
|
|
|
for (int x = 0; x < patchW; ++x) { |
|
|
|
|
cv::Vec3b color = remapped.at<cv::Vec3b>(y, x); |
|
|
|
|
// 严格跳过黑色(背景/无效区),防止橙色污染
|
|
|
|
|
if (color[0] == 0 && color[1] == 0 && color[2] == 0) continue; |
|
|
|
|
|
|
|
|
|
int atlasX = x + minX, atlasY = y + minY; |
|
|
|
|
if (atlasX < 0 || atlasX >= textureSize || atlasY < 0 || atlasY >= textureSize) continue; |
|
|
|
|
// 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]; |
|
|
|
|
|
|
|
|
|
// ★ 强制原样写入(正确图的效果)
|
|
|
|
|
localAtlas.at<cv::Vec3b>(atlasY, atlasX) = color; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
// ★ 若后续需恢复仿射,请先打印参数确认:
|
|
|
|
|
// printf("ac: kb=%.2f kg=%.2f kr=%.2f bb=%.2f bg=%.2f br=%.2f\n", ac.kb, ac.kg, ac.kr, ac.bb, ac.bg, ac.br);
|
|
|
|
|
// 注意:OpenCV 是 BGR,确保 k/bb 对应 BGR 而非 RGB
|
|
|
|
|
if (acPtr) { |
|
|
|
|
const AffineCorrection& ac = *acPtr; |
|
|
|
|
cv::Vec3f c(color[0]/255.f, color[1]/255.f, color[2]/255.f); |
|
|
|
|
c[0] = std::max(0.f, std::min(1.f, c[0] * ac.kb + ac.bb)); |
|
|
|
|
c[1] = std::max(0.f, std::min(1.f, c[1] * ac.kg + ac.bg)); |
|
|
|
|
c[2] = std::max(0.f, std::min(1.f, c[2] * ac.kr + ac.br)); |
|
|
|
|
localAtlas.at<cv::Vec3b>(atlasY, atlasX) = cv::Vec3b( |
|
|
|
|
(uchar)(c[0]*255.f+0.5f), (uchar)(c[1]*255.f+0.5f), (uchar)(c[2]*255.f+0.5f) |
|
|
|
|
); |
|
|
|
|
} else { |
|
|
|
|
localAtlas.at<cv::Vec3b>(atlasY, atlasX) = color; |
|
|
|
|
} |
|
|
|
|
*/ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// // 调试用:导出调整后的 atlas
|
|
|
|
|
// static int dbgCount = 0;
|
|
|
|
|
// char dbgName[256];
|
|
|
|
|
// sprintf(dbgName, "debug_atlas_after_gsl4_%d.png", dbgCount++);
|
|
|
|
|
// cv::imwrite(dbgName, atlas);
|
|
|
|
|
// DEBUG_EXTRA("Exported %s", dbgName);
|
|
|
|
|
} |
|
|
|
|
atlas = localAtlas; |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("Re-rasterization done."); |
|
|
|
|
|
|
|
|
|
correctedPatchImages.clear(); |
|
|
|
|
// 将 localAtlas 合并到 atlas
|
|
|
|
|
for (int y = 0; y < textureSize; ++y) |
|
|
|
|
for (int x = 0; x < textureSize; ++x) |
|
|
|
|
if (localAtlas.at<cv::Vec3b>(y, x) != cv::Vec3b(0,0,0)) |
|
|
|
|
atlas.at<cv::Vec3b>(y, x) = localAtlas.at<cv::Vec3b>(y, x); |
|
|
|
|
|
|
|
|
|
// ★ 不再恢复原始 images —— 避免 cv::Mat 引用计数混乱导致 munmap_chunk
|
|
|
|
|
// atlas 已经生成,images 后续随 MeshTexture 正常析构即可
|
|
|
|
|
DEBUG_EXTRA("Re-rasterization done (auto-detect H direction + ROI + affine correction)."); |
|
|
|
|
correctedPatchImages.clear(); |
|
|
|
|
|
|
|
|
|
// ========== 4. 清理成员变量 ==========
|
|
|
|
|
texturePatches.clear(); |
|
|
|
|
@ -16009,10 +16210,9 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
@@ -16009,10 +16210,9 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
|
|
|
|
labelsInvalid.Release(); |
|
|
|
|
seamEdges.clear(); |
|
|
|
|
seamVertices.clear(); |
|
|
|
|
// faceTexcoords.Release();
|
|
|
|
|
|
|
|
|
|
Mesh::TexCoordArr dummyTexcoords; |
|
|
|
|
faceTexcoords.Swap(dummyTexcoords); // 如果 DynArray 有 Swap 方法
|
|
|
|
|
faceTexcoords.Swap(dummyTexcoords); |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("ApplyGlobalSeamLevelingOnRCPatches: ALL DONE."); |
|
|
|
|
} |
|
|
|
|
@ -16376,9 +16576,11 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -16376,9 +16576,11 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
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]; |
|
|
|
|
// pixel[0] = color[2];
|
|
|
|
|
// pixel[1] = color[1];
|
|
|
|
|
// pixel[2] = color[0];
|
|
|
|
|
|
|
|
|
|
pixel = color; // 原样写入,不再交换
|
|
|
|
|
} |
|
|
|
|
m_texelScores[idx].score = currentScore; |
|
|
|
|
m_texelScores[idx].viewID = viewID; |
|
|
|
|
@ -16428,9 +16630,9 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -16428,9 +16630,9 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
if (x < 0 || x >= textureSize || y < 0 || y >= textureSize) continue; |
|
|
|
|
const Pixel8U& px = atlas(y, x); |
|
|
|
|
if (px[0]==0 && px[1]==0 && px[2]==0) continue; |
|
|
|
|
patchAvgColor[i][0] += px[2]; |
|
|
|
|
patchAvgColor[i][0] += px[0]; |
|
|
|
|
patchAvgColor[i][1] += px[1]; |
|
|
|
|
patchAvgColor[i][2] += px[0]; |
|
|
|
|
patchAvgColor[i][2] += px[2]; |
|
|
|
|
pixelCount[i]++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -16446,8 +16648,8 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -16446,8 +16648,8 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
NP, NP - std::count(pixelCount.begin(), pixelCount.end(), 0)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// for (int iter = 0; iter < 3; ++iter) {
|
|
|
|
|
for (int iter = 0; iter < 1; ++iter) { |
|
|
|
|
for (int iter = 0; iter < 3; ++iter) { |
|
|
|
|
// for (int iter = 0; iter < 1; ++iter) {
|
|
|
|
|
AlignPatchColors(atlas, m_texelPatchID, textureSize); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -17840,7 +18042,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
@@ -17840,7 +18042,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
|
|
|
|
|
|
|
|
|
// ========== 颜色一致性代价(局部采样版)==========
|
|
|
|
|
{ |
|
|
|
|
const float lambda = 0.5f; // 从 100 降到 0.5
|
|
|
|
|
const float lambda = 1.0f; // 从 100 降到 0.5
|
|
|
|
|
|
|
|
|
|
// 采样当前候选视图下面中心的颜色
|
|
|
|
|
cv::Vec3f candidateColor = SampleFaceCenterColor(vid, faceCenter); |
|
|
|
|
|