|
|
|
@ -14483,132 +14483,95 @@ void MeshTexture::LocalSeamBlending(Image8U3& atlas, int textureSize) |
|
|
|
// 在 seam edge 上采样两侧 patch 的颜色,求解每个 patch 的偏移量
|
|
|
|
// 在 seam edge 上采样两侧 patch 的颜色,求解每个 patch 的偏移量
|
|
|
|
void MeshTexture::GlobalPatchColorAlignment(Image8U3& atlas, int textureSize) |
|
|
|
void MeshTexture::GlobalPatchColorAlignment(Image8U3& atlas, int textureSize) |
|
|
|
{ |
|
|
|
{ |
|
|
|
DEBUG_EXTRA(">>> GlobalPatchColorAlignment called: rcSeamEdges=%zu, patchAvgColor=%zu", |
|
|
|
|
|
|
|
rcSeamEdges.size(), patchAvgColor.size()); |
|
|
|
|
|
|
|
if (rcSeamEdges.empty() || rcPatches.empty()) return; |
|
|
|
if (rcSeamEdges.empty() || rcPatches.empty()) return; |
|
|
|
if (patchAvgColor.empty()) return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Global color adjustment (patch-level, no blocks)..."); |
|
|
|
|
|
|
|
TD_TIMER_START(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const int NP = (int)rcPatches.size(); |
|
|
|
const int NP = (int)rcPatches.size(); |
|
|
|
|
|
|
|
|
|
|
|
struct Constraint { int pa, pb; float dR, dG, dB; }; |
|
|
|
// 1. 统计每个 patch 的真实平均色(用 m_texelPatchID,只统计该 patch 拥有的像素)
|
|
|
|
std::vector<Constraint> constraints; |
|
|
|
std::vector<cv::Vec3d> patchSum(NP, cv::Vec3d(0,0,0)); |
|
|
|
constraints.reserve(rcSeamEdges.size()); |
|
|
|
std::vector<int> patchCount(NP, 0); |
|
|
|
|
|
|
|
|
|
|
|
int skipNoFace = 0, skipSameView = 0; |
|
|
|
for (int y = 0; y < textureSize; ++y) { |
|
|
|
|
|
|
|
for (int x = 0; x < textureSize; ++x) { |
|
|
|
|
|
|
|
size_t idx = y * textureSize + x; |
|
|
|
|
|
|
|
if (m_texelPatchID[idx] == NO_ID) continue; |
|
|
|
|
|
|
|
int pid = m_texelPatchID[idx]; |
|
|
|
|
|
|
|
if (pid >= NP) continue; |
|
|
|
|
|
|
|
|
|
|
|
for (const auto& e : rcSeamEdges) { |
|
|
|
const Pixel8U& px = atlas(y, x); |
|
|
|
if (e.rcPatchID0 >= NP || e.rcPatchID1 >= NP) { skipNoFace++; continue; } |
|
|
|
// ★ 高光/暗部剔除:丢弃过暗和过亮的像素
|
|
|
|
|
|
|
|
float lum = px[0] + px[1] + px[2]; |
|
|
|
|
|
|
|
if (lum < 15 || lum > 740) continue; // 去掉死黑和高光
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 关键:不再检查 proj / black,只检查是否同一个 view
|
|
|
|
patchSum[pid][0] += px[2]; |
|
|
|
if (rcPatches[e.rcPatchID0].viewID == rcPatches[e.rcPatchID1].viewID) { |
|
|
|
patchSum[pid][1] += px[1]; |
|
|
|
skipSameView++; // 同一视图的 patch 之间不需要颜色校正
|
|
|
|
patchSum[pid][2] += px[0]; |
|
|
|
continue; |
|
|
|
patchCount[pid]++; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const Color& colorA = patchAvgColor[e.rcPatchID0]; |
|
|
|
|
|
|
|
const Color& colorB = patchAvgColor[e.rcPatchID1]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 只用平均色差作为约束(m_patchAvgColor 在光栅化后统计,已经是干净数据)
|
|
|
|
|
|
|
|
constraints.push_back({ |
|
|
|
|
|
|
|
e.rcPatchID0, e.rcPatchID1, |
|
|
|
|
|
|
|
colorB[0] - colorA[0], // R
|
|
|
|
|
|
|
|
colorB[1] - colorA[1], // G
|
|
|
|
|
|
|
|
colorB[2] - colorA[2] // B
|
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Constraints: %zu (skip: noFace=%d, sameView=%d)", |
|
|
|
// 2. 对每个接缝,做距离加权的颜色混合(只在接缝带内)
|
|
|
|
constraints.size(), skipNoFace, skipSameView); |
|
|
|
const int bandWidth = 12; // 接缝混合带宽(像素)
|
|
|
|
|
|
|
|
|
|
|
|
if (constraints.size() < 10) { // ★ 至少 10 条约束才求解
|
|
|
|
#pragma omp parallel for |
|
|
|
DEBUG_EXTRA("Too few constraints, skip global alignment"); |
|
|
|
for (int eidx = 0; eidx < (int)rcSeamEdges.size(); ++eidx) { |
|
|
|
return; |
|
|
|
const auto& e = rcSeamEdges[eidx]; |
|
|
|
} |
|
|
|
if (e.rcPatchID0 >= NP || e.rcPatchID1 >= NP) continue; |
|
|
|
|
|
|
|
|
|
|
|
const int rows = (int)constraints.size(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 构建稀疏矩阵 A 和 b
|
|
|
|
int pa = e.rcPatchID0, pb = e.rcPatchID1; |
|
|
|
std::vector<Eigen::Triplet<float>> triplets; |
|
|
|
if (rcPatches[pa].viewID == rcPatches[pb].viewID) continue; // 同视图不需要
|
|
|
|
triplets.reserve(rows * 2 + NP); // 约束 + 正则化
|
|
|
|
|
|
|
|
Eigen::VectorXf bR(rows), bG(rows), bB(rows); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < rows; ++i) { |
|
|
|
// 计算两个 patch 的平均色差
|
|
|
|
triplets.emplace_back(i, constraints[i].pa, -1.0f); |
|
|
|
if (patchCount[pa] < 10 || patchCount[pb] < 10) continue; |
|
|
|
triplets.emplace_back(i, constraints[i].pb, 1.0f); |
|
|
|
cv::Vec3d avgA = patchSum[pa] / patchCount[pa]; |
|
|
|
bR(i) = constraints[i].dR; |
|
|
|
cv::Vec3d avgB = patchSum[pb] / patchCount[pb]; |
|
|
|
bG(i) = constraints[i].dG; |
|
|
|
|
|
|
|
bB(i) = constraints[i].dB; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 正则化:所有 patch 的偏移量尽量小(Tikhonov)
|
|
|
|
// 色差太小就不处理了(视角差异是物理正确的)
|
|
|
|
const float lambda = 10.05f; |
|
|
|
double colorDiff = std::abs(avgA[0]-avgB[0]) + std::abs(avgA[1]-avgB[1]) + std::abs(avgA[2]-avgB[2]); |
|
|
|
for (int i = 0; i < NP; ++i) { |
|
|
|
if (colorDiff < 20.0) continue; // ← 阈值:差异小于 20 不管
|
|
|
|
triplets.emplace_back(rows + i, i, lambda); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Eigen::SparseMatrix<float> A(rows + NP, NP); |
|
|
|
// 在重叠区域内做渐变混合
|
|
|
|
A.setFromTriplets(triplets.begin(), triplets.end()); |
|
|
|
cv::Rect overlap = rcPatches[pa].rect & rcPatches[pb].rect; |
|
|
|
|
|
|
|
if (overlap.width <= 0 || overlap.height <= 0) continue; |
|
|
|
|
|
|
|
|
|
|
|
// ===== 纯稀疏求解,绝不转稠密 =====
|
|
|
|
for (int y = overlap.y; y < overlap.y + overlap.height; ++y) { |
|
|
|
Eigen::VectorXf xR(NP), xG(NP), xB(NP); |
|
|
|
for (int x = overlap.x; x < overlap.x + overlap.width; ++x) { |
|
|
|
{ |
|
|
|
size_t idx = y * textureSize + x; |
|
|
|
// 计算 AtA = A^T * A (稀疏矩阵乘法,结果还是稀疏的)
|
|
|
|
if (m_texelPatchID[idx] == NO_ID) continue; |
|
|
|
Eigen::SparseMatrix<float> AtA = A.transpose() * A; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 加对角阻尼,保证正定(coeffRef 会在对角线插入元素)
|
|
|
|
int pid = m_texelPatchID[idx]; |
|
|
|
for (int i = 0; i < NP; ++i) { |
|
|
|
if (pid != pa && pid != pb) continue; // 只处理接缝两侧的 patch
|
|
|
|
AtA.coeffRef(i, i) += 1e-6f; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算 A^T * b
|
|
|
|
// 到重叠区边界的距离(0=中心, 1=边缘)
|
|
|
|
Eigen::VectorXf AtbR = A.transpose() * bR; |
|
|
|
int dxL = x - overlap.x; |
|
|
|
Eigen::VectorXf AtbG = A.transpose() * bG; |
|
|
|
int dxR = (overlap.x + overlap.width - 1) - x; |
|
|
|
Eigen::VectorXf AtbB = A.transpose() * bB; |
|
|
|
int dyT = y - overlap.y; |
|
|
|
|
|
|
|
int dyB = (overlap.y + overlap.height - 1) - y; |
|
|
|
|
|
|
|
int dist = std::min({dxL, dxR, dyT, dyB}); |
|
|
|
|
|
|
|
if (dist >= bandWidth) continue; // 只在带内混合
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 用 SparseLU 求解(不挑矩阵,稳定,不会 lpNorm crash)
|
|
|
|
float t = (float)dist / bandWidth; // 0=中心, 1=边缘
|
|
|
|
Eigen::SparseLU<Eigen::SparseMatrix<float>> solver; |
|
|
|
// 余弦平滑
|
|
|
|
solver.compute(AtA); |
|
|
|
float w = 0.5f * (1.0f - cosf(M_PI * t)); |
|
|
|
if (solver.info() != Eigen::Success) { |
|
|
|
|
|
|
|
DEBUG_EXTRA("GlobalPatchColorAlignment: SparseLU decomposition failed"); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
xR = solver.solve(AtbR); |
|
|
|
Pixel8U& px = atlas(y, x); |
|
|
|
xG = solver.solve(AtbG); |
|
|
|
if (px[0] < 5 && px[1] < 5 && px[2] < 5) continue; |
|
|
|
xB = solver.solve(AtbB); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (solver.info() != Eigen::Success) { |
|
|
|
// 如果是 patch A 的像素,向 patch B 的颜色混合
|
|
|
|
DEBUG_EXTRA("GlobalPatchColorAlignment: SparseLU solve failed"); |
|
|
|
if (pid == pa) { |
|
|
|
return; |
|
|
|
px[2] = cv::saturate_cast<uchar>(px[2] * (1-w) + avgB[0] * w); |
|
|
|
|
|
|
|
px[1] = cv::saturate_cast<uchar>(px[1] * (1-w) + avgB[1] * w); |
|
|
|
|
|
|
|
px[0] = cv::saturate_cast<uchar>(px[0] * (1-w) + avgB[2] * w); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
px[2] = cv::saturate_cast<uchar>(px[2] * (1-w) + avgA[0] * w); |
|
|
|
|
|
|
|
px[1] = cv::saturate_cast<uchar>(px[1] * (1-w) + avgA[1] * w); |
|
|
|
|
|
|
|
px[0] = cv::saturate_cast<uchar>(px[0] * (1-w) + avgA[2] * w); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 应用:对每个 patch 的像素加上偏移(加性,限制幅度)
|
|
|
|
DEBUG_EXTRA("Seam-only gradient blending applied (no global gain)"); |
|
|
|
const float maxAdj = 50.0f; // 8-bit 空间最大偏移
|
|
|
|
|
|
|
|
// 应用:对每个像素,如果它属于 patch i,才加偏移
|
|
|
|
|
|
|
|
#pragma omp parallel for |
|
|
|
|
|
|
|
for (int y = 0; y < textureSize; ++y) { |
|
|
|
|
|
|
|
for (int x = 0; x < textureSize; ++x) { |
|
|
|
|
|
|
|
size_t idx = y * textureSize + x; |
|
|
|
|
|
|
|
if (m_texelPatchID[idx] == NO_ID) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int pid = m_texelPatchID[idx]; |
|
|
|
|
|
|
|
const float adjR = std::max(-maxAdj, std::min(maxAdj, xR(pid))); |
|
|
|
|
|
|
|
const float adjG = std::max(-maxAdj, std::min(maxAdj, xG(pid))); |
|
|
|
|
|
|
|
const float adjB = std::max(-maxAdj, std::min(maxAdj, xB(pid))); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Pixel8U& px = atlas(y, x); |
|
|
|
|
|
|
|
if (px[0]==0 && px[1]==0 && px[2]==0) continue; |
|
|
|
|
|
|
|
px[2] = (uint8_t)CLAMP(px[2] + adjR, 0.f, 255.f); |
|
|
|
|
|
|
|
px[1] = (uint8_t)CLAMP(px[1] + adjG, 0.f, 255.f); |
|
|
|
|
|
|
|
px[0] = (uint8_t)CLAMP(px[0] + adjB, 0.f, 255.f); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Global alignment done: %d constraints (%s)", rows, TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ========== Patch Color Alignment(改进版,不依赖 GlobalPatchColorAlignment)==========
|
|
|
|
// ========== Patch Color Alignment(改进版,不依赖 GlobalPatchColorAlignment)==========
|
|
|
|
@ -14820,7 +14783,7 @@ bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
cv::Mat patch; |
|
|
|
cv::Mat patch; |
|
|
|
cv::remap(srcImg.image, patch, mapX, mapY, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); |
|
|
|
cv::remap(srcImg.image, patch, mapX, mapY, cv::INTER_NEAREST, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); |
|
|
|
|
|
|
|
|
|
|
|
// 应用光度校正
|
|
|
|
// 应用光度校正
|
|
|
|
const cv::Vec3f& gain = (viewID < (IIndex)m_imageGains.size()) ? m_imageGains[viewID] : cv::Vec3f(1,1,1); |
|
|
|
const cv::Vec3f& gain = (viewID < (IIndex)m_imageGains.size()) ? m_imageGains[viewID] : cv::Vec3f(1,1,1); |
|
|
|
@ -14843,7 +14806,10 @@ bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
for (int y = 0; y < patchH; ++y) { |
|
|
|
for (int y = 0; y < patchH; ++y) { |
|
|
|
for (int x = 0; x < patchW; ++x) { |
|
|
|
for (int x = 0; x < patchW; ++x) { |
|
|
|
cv::Vec3b color = patch.at<cv::Vec3b>(y, x); |
|
|
|
cv::Vec3b color = patch.at<cv::Vec3b>(y, x); |
|
|
|
if (color[0]==0 && color[1]==0 && color[2]==0) continue; |
|
|
|
|
|
|
|
|
|
|
|
// if (color[0]==0 && color[1]==0 && color[2]==0) 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; |
|
|
|
size_t idx = atlasY * textureSize + atlasX; |
|
|
|
size_t idx = atlasY * textureSize + atlasX; |
|
|
|
@ -14851,6 +14817,10 @@ bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
// ★ 已删除错误放在这里的 m_texelPatchID.assign(...)
|
|
|
|
// ★ 已删除错误放在这里的 m_texelPatchID.assign(...)
|
|
|
|
|
|
|
|
|
|
|
|
if (currentScore > m_texelScores[idx].score) { |
|
|
|
if (currentScore > m_texelScores[idx].score) { |
|
|
|
|
|
|
|
color[0] = cv::saturate_cast<uchar>(std::min(255.0f, color[0] * 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)); |
|
|
|
|
|
|
|
|
|
|
|
atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; |
|
|
|
atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; |
|
|
|
m_texelScores[idx].score = currentScore; |
|
|
|
m_texelScores[idx].score = currentScore; |
|
|
|
m_texelScores[idx].viewID = viewID; |
|
|
|
m_texelScores[idx].viewID = viewID; |
|
|
|
@ -14917,7 +14887,9 @@ bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
DEBUG_EXTRA("Patch avg color computed: %d patches, %d with pixels", |
|
|
|
DEBUG_EXTRA("Patch avg color computed: %d patches, %d with pixels", |
|
|
|
NP, NP - std::count(pixelCount.begin(), pixelCount.end(), 0)); |
|
|
|
NP, NP - std::count(pixelCount.begin(), pixelCount.end(), 0)); |
|
|
|
|
|
|
|
|
|
|
|
for (int iter = 0; iter < 3; ++iter) { |
|
|
|
|
|
|
|
|
|
|
|
// for (int iter = 0; iter < 3; ++iter) {
|
|
|
|
|
|
|
|
for (int iter = 0; iter < 1; ++iter) { |
|
|
|
AlignPatchColors(atlas, m_texelPatchID, textureSize); |
|
|
|
AlignPatchColors(atlas, m_texelPatchID, textureSize); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -15870,14 +15842,14 @@ if (!g_avgColorsComputed) { |
|
|
|
float s_occlusion = ComputeOcclusionPenalty( |
|
|
|
float s_occlusion = ComputeOcclusionPenalty( |
|
|
|
p0, p1, p2, img.image.cols, img.image.rows, 8); |
|
|
|
p0, p1, p2, img.image.cols, img.image.rows, 8); |
|
|
|
|
|
|
|
|
|
|
|
float rawScore = 1.0f * s_normal |
|
|
|
float rawScore = 3.0f * s_normal // 正视角优先
|
|
|
|
+ 0.7f * s_resolution |
|
|
|
+ 0.5f * s_resolution |
|
|
|
- 0.3f * s_occlusion; |
|
|
|
- 0.1f * s_occlusion; |
|
|
|
float score = rawScore; |
|
|
|
float score = rawScore; |
|
|
|
|
|
|
|
|
|
|
|
// ========== 颜色一致性代价 ==========
|
|
|
|
// ========== 颜色一致性代价 ==========
|
|
|
|
{ |
|
|
|
{ |
|
|
|
const float lambda = 20.35f; |
|
|
|
const float lambda = 2.35f; |
|
|
|
// ========== 收集邻居颜色(修复版:1-ring + faceToView)==========
|
|
|
|
// ========== 收集邻居颜色(修复版:1-ring + faceToView)==========
|
|
|
|
std::vector<cv::Vec3f> neighborColors; |
|
|
|
std::vector<cv::Vec3f> neighborColors; |
|
|
|
if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) { |
|
|
|
if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) { |
|
|
|
|