|
|
|
@ -16211,7 +16211,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( |
|
|
|
// ★ 强制原样写入(正确图的效果)
|
|
|
|
// ★ 强制原样写入(正确图的效果)
|
|
|
|
localAtlas.at<cv::Vec3b>(atlasY, atlasX) = color; |
|
|
|
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);
|
|
|
|
// 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
|
|
|
|
// 注意:OpenCV 是 BGR,确保 k/bb 对应 BGR 而非 RGB
|
|
|
|
@ -16227,7 +16227,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
localAtlas.at<cv::Vec3b>(atlasY, atlasX) = color; |
|
|
|
localAtlas.at<cv::Vec3b>(atlasY, atlasX) = color; |
|
|
|
} |
|
|
|
} |
|
|
|
*/ |
|
|
|
//*/
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -16313,7 +16313,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( |
|
|
|
|
|
|
|
|
|
|
|
// 对标记为接缝的像素做高斯模糊
|
|
|
|
// 对标记为接缝的像素做高斯模糊
|
|
|
|
cv::Mat blurred; |
|
|
|
cv::Mat blurred; |
|
|
|
cv::GaussianBlur(atlas, blurred, cv::Size(3, 3), 0.5); |
|
|
|
cv::GaussianBlur(atlas, blurred, cv::Size(5, 5), 1.5); // 更大核
|
|
|
|
|
|
|
|
|
|
|
|
// 只替换接缝像素
|
|
|
|
// 只替换接缝像素
|
|
|
|
for (int y = 0; y < textureSize; ++y) { |
|
|
|
for (int y = 0; y < textureSize; ++y) { |
|
|
|
@ -16674,7 +16674,7 @@ bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
// GSL4 会统一做全局颜色优化,这里乘了会导致双重校正
|
|
|
|
// GSL4 会统一做全局颜色优化,这里乘了会导致双重校正
|
|
|
|
// 如果后续不走 GSL4 流程,可以把下面 false 改为 true 恢复
|
|
|
|
// 如果后续不走 GSL4 流程,可以把下面 false 改为 true 恢复
|
|
|
|
// ============================================================
|
|
|
|
// ============================================================
|
|
|
|
#if 0 // ← 改为 1 可临时恢复光栅化时乘 gain
|
|
|
|
#if 1 // ← 改为 1 可临时恢复光栅化时乘 gain
|
|
|
|
// 应用光度校正
|
|
|
|
// 应用光度校正
|
|
|
|
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); |
|
|
|
const bool applyGain = (gain != cv::Vec3f(1,1,1)); |
|
|
|
const bool applyGain = (gain != cv::Vec3f(1,1,1)); |
|
|
|
@ -16691,7 +16691,7 @@ bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
} |
|
|
|
} |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
// 写入 atlas(最高分胜出)
|
|
|
|
// 写入 atlas(最高分胜出,不做过程混合)
|
|
|
|
#pragma omp critical (atlas_write) |
|
|
|
#pragma omp critical (atlas_write) |
|
|
|
{ |
|
|
|
{ |
|
|
|
for (int y = 0; y < patchH; ++y) { |
|
|
|
for (int y = 0; y < patchH; ++y) { |
|
|
|
@ -16704,26 +16704,12 @@ bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
// ★ 已删除错误放在这里的 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.0f)); |
|
|
|
// 原样写入,不做任何混合
|
|
|
|
color[1] = cv::saturate_cast<uchar>(std::min(255.0f, color[1] * 1.0f)); |
|
|
|
atlas.at<cv::Vec3b>(atlasY, atlasX) = color; |
|
|
|
color[2] = cv::saturate_cast<uchar>(std::min(255.0f, color[2] * 1.0f)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pixel = color; // 原样写入,不再交换
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
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; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -16817,6 +16803,77 @@ bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
|
|
|
|
|
|
|
|
// AlignPatchColors(atlas, m_texelPatchID, textureSize);
|
|
|
|
// AlignPatchColors(atlas, m_texelPatchID, textureSize);
|
|
|
|
// FeatherTextureSeams(atlas, m_texelPatchID, textureSize, 3);
|
|
|
|
// FeatherTextureSeams(atlas, m_texelPatchID, textureSize, 3);
|
|
|
|
|
|
|
|
// ★★★ 后处理:接缝平滑 ★★★
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
Image8U3 smoothSrc = atlas.clone(); |
|
|
|
|
|
|
|
int seamPixelCount = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int y = 1; y < textureSize - 1; ++y) { |
|
|
|
|
|
|
|
for (int x = 1; x < textureSize - 1; ++x) { |
|
|
|
|
|
|
|
size_t centerIdx = y * textureSize + x; |
|
|
|
|
|
|
|
int centerPatch = m_texelPatchID[centerIdx]; |
|
|
|
|
|
|
|
if (centerPatch == NO_ID) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查 4 邻域是否有不同 patch
|
|
|
|
|
|
|
|
bool isSeam = false; |
|
|
|
|
|
|
|
int neighbors[4] = { |
|
|
|
|
|
|
|
m_texelPatchID[(y-1) * textureSize + x], |
|
|
|
|
|
|
|
m_texelPatchID[(y+1) * textureSize + x], |
|
|
|
|
|
|
|
m_texelPatchID[y * textureSize + (x-1)], |
|
|
|
|
|
|
|
m_texelPatchID[y * textureSize + (x+1)] |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int k = 0; k < 4; ++k) { |
|
|
|
|
|
|
|
if (neighbors[k] != NO_ID && neighbors[k] != centerPatch) { |
|
|
|
|
|
|
|
isSeam = true; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isSeam) { |
|
|
|
|
|
|
|
cv::Vec3i sum(0, 0, 0); |
|
|
|
|
|
|
|
int count = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int dy = -1; dy <= 1; ++dy) { |
|
|
|
|
|
|
|
for (int dx = -1; dx <= 1; ++dx) { |
|
|
|
|
|
|
|
int nx = x + dx, ny = y + dy; |
|
|
|
|
|
|
|
if (nx < 0 || nx >= textureSize || ny < 0 || ny >= textureSize) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
size_t nidx = ny * textureSize + nx; |
|
|
|
|
|
|
|
int nPatch = m_texelPatchID[nidx]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (nPatch != NO_ID && nPatch != centerPatch) { |
|
|
|
|
|
|
|
cv::Vec3b nc = smoothSrc.at<cv::Vec3b>(ny, nx); |
|
|
|
|
|
|
|
sum[0] += nc[0]; |
|
|
|
|
|
|
|
sum[1] += nc[1]; |
|
|
|
|
|
|
|
sum[2] += nc[2]; |
|
|
|
|
|
|
|
count++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (count > 0) { |
|
|
|
|
|
|
|
cv::Vec3b avg( |
|
|
|
|
|
|
|
cv::saturate_cast<uchar>(sum[0] / count), |
|
|
|
|
|
|
|
cv::saturate_cast<uchar>(sum[1] / count), |
|
|
|
|
|
|
|
cv::saturate_cast<uchar>(sum[2] / count) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cv::Vec3b original = smoothSrc.at<cv::Vec3b>(y, x); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 混合:50% 原始 + 50% 邻居平均
|
|
|
|
|
|
|
|
atlas.at<cv::Vec3b>(y, x) = cv::Vec3b( |
|
|
|
|
|
|
|
cv::saturate_cast<uchar>(original[0] * 0.5f + avg[0] * 0.5f), |
|
|
|
|
|
|
|
cv::saturate_cast<uchar>(original[1] * 0.5f + avg[1] * 0.5f), |
|
|
|
|
|
|
|
cv::saturate_cast<uchar>(original[2] * 0.5f + avg[2] * 0.5f) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
seamPixelCount++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
DEBUG_EXTRA("Post-process seam smoothing done: %d seam pixels processed.", seamPixelCount); |
|
|
|
|
|
|
|
} |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -18013,20 +18070,57 @@ int MeshTexture::ComputeOptimalTextureSizeAdaptive( |
|
|
|
cv::Vec3f MeshTexture::SampleFaceCenterColor(IIndex vid, const Point3f& faceCenter) { |
|
|
|
cv::Vec3f MeshTexture::SampleFaceCenterColor(IIndex vid, const Point3f& faceCenter) { |
|
|
|
const Image& img = images[vid]; |
|
|
|
const Image& img = images[vid]; |
|
|
|
if (img.image.empty()) return cv::Vec3f(-1, -1, -1); |
|
|
|
if (img.image.empty()) return cv::Vec3f(-1, -1, -1); |
|
|
|
|
|
|
|
|
|
|
|
Point2f proj = img.camera.ProjectPoint(Point3d(faceCenter)); |
|
|
|
Point2f proj = img.camera.ProjectPoint(Point3d(faceCenter)); |
|
|
|
int px = (int)(proj.x + 0.5f), py = (int)(proj.y + 0.5f); |
|
|
|
int cx = (int)(proj.x + 0.5f), cy = (int)(proj.y + 0.5f); |
|
|
|
if (px < 0 || py < 0 || px >= img.image.cols || py >= img.image.rows) |
|
|
|
|
|
|
|
|
|
|
|
// ★★★ 3x3 区域平均采样 ★★★
|
|
|
|
|
|
|
|
cv::Vec3f sum(0, 0, 0); |
|
|
|
|
|
|
|
int validCount = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int dy = -1; dy <= 1; ++dy) { |
|
|
|
|
|
|
|
for (int dx = -1; dx <= 1; ++dx) { |
|
|
|
|
|
|
|
int sx = cx + dx; |
|
|
|
|
|
|
|
int sy = cy + dy; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 边界检查
|
|
|
|
|
|
|
|
if (sx < 0 || sx >= img.image.cols || sy < 0 || sy >= img.image.rows) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const cv::Vec3b& c = img.image.at<cv::Vec3b>(sy, sx); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 跳过黑色/无效像素(假设纯黑(0,0,0)为无效)
|
|
|
|
|
|
|
|
if (c[0] == 0 && c[1] == 0 && c[2] == 0) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sum[0] += c[2]; // B→R
|
|
|
|
|
|
|
|
sum[1] += c[1]; // G→G
|
|
|
|
|
|
|
|
sum[2] += c[0]; // R→B
|
|
|
|
|
|
|
|
validCount++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果 3x3 区域内全部无效,退回单点采样
|
|
|
|
|
|
|
|
if (validCount == 0) { |
|
|
|
|
|
|
|
if (cx < 0 || cy < 0 || cx >= img.image.cols || cy >= img.image.rows) |
|
|
|
return cv::Vec3f(-1, -1, -1); |
|
|
|
return cv::Vec3f(-1, -1, -1); |
|
|
|
const cv::Vec3b& c = img.image.at<cv::Vec3b>(py, px); |
|
|
|
const cv::Vec3b& c = img.image.at<cv::Vec3b>(cy, cx); |
|
|
|
return cv::Vec3f(c[2]/255.0f, c[1]/255.0f, c[0]/255.0f); // BGR→RGB, normalize
|
|
|
|
if (c[0] == 0 && c[1] == 0 && c[2] == 0) |
|
|
|
|
|
|
|
return cv::Vec3f(-1, -1, -1); |
|
|
|
|
|
|
|
return cv::Vec3f(c[2]/255.0f, c[1]/255.0f, c[0]/255.0f); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 返回归一化的平均值
|
|
|
|
|
|
|
|
return cv::Vec3f( |
|
|
|
|
|
|
|
sum[0] / (validCount * 255.0f), |
|
|
|
|
|
|
|
sum[1] / (validCount * 255.0f), |
|
|
|
|
|
|
|
sum[2] / (validCount * 255.0f) |
|
|
|
|
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
// ============================================================
|
|
|
|
// 5. SelectBestViewsForVirtualFaces(修复版)
|
|
|
|
// 5. SelectBestViewsForVirtualFaces(修复版)
|
|
|
|
// ============================================================
|
|
|
|
// ============================================================
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
// 5. SelectBestViewsForVirtualFaces(局部采样颜色版)
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
// 前置条件:MeshTexture 类中需声明并定义 SampleFaceCenterColor:
|
|
|
|
// 前置条件:MeshTexture 类中需声明并定义 SampleFaceCenterColor:
|
|
|
|
// cv::Vec3f SampleFaceCenterColor(IIndex vid, const Point3f& faceCenter) const;
|
|
|
|
// cv::Vec3f SampleFaceCenterColor(IIndex vid, const Point3f& faceCenter) const;
|
|
|
|
// 实现见下方附录
|
|
|
|
// 实现见下方附录
|
|
|
|
@ -18253,17 +18347,22 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( |
|
|
|
- 0.1f * s_occlusion; |
|
|
|
- 0.1f * s_occlusion; |
|
|
|
float score = rawScore; |
|
|
|
float score = rawScore; |
|
|
|
|
|
|
|
|
|
|
|
// ========== 颜色一致性代价(局部采样版)==========
|
|
|
|
// ========== 颜色一致性代价(动态阈值版)==========
|
|
|
|
{ |
|
|
|
{ |
|
|
|
const float lambda = 1.0f; // 从 100 降到 0.5
|
|
|
|
// ★ 判断是否为头发区域(法线朝下 + 位置偏高)
|
|
|
|
|
|
|
|
bool isHair = (faceNormal.y < -0.3f); // 法线向下,典型头发区域
|
|
|
|
|
|
|
|
// 可根据模型调整:也可加 faceCenter.y > 某个高度阈值
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 动态参数:头发区域放宽限制
|
|
|
|
|
|
|
|
float dynamicLambda = isHair ? 60.0f : 120.0f; |
|
|
|
|
|
|
|
float dynamicMaxDiff = isHair ? 40.0f : 18.0f; // 头发放宽到 40
|
|
|
|
|
|
|
|
|
|
|
|
// 采样当前候选视图下面中心的颜色
|
|
|
|
// 采样当前候选视图下面中心的颜色(3x3 平均已在 SampleFaceCenterColor 中实现)
|
|
|
|
cv::Vec3f candidateColor = SampleFaceCenterColor(vid, faceCenter); |
|
|
|
cv::Vec3f candidateColor = SampleFaceCenterColor(vid, faceCenter); |
|
|
|
if (candidateColor[0] < 0) { |
|
|
|
if (candidateColor[0] < 0) { |
|
|
|
// 投影失败,跳过颜色代价但不 return
|
|
|
|
// 投影失败,跳过颜色代价
|
|
|
|
// 保持 score 不变
|
|
|
|
|
|
|
|
} else { |
|
|
|
} else { |
|
|
|
// 收集邻居颜色:已分配的面,采样邻居面中心在邻居视图下的颜色
|
|
|
|
// 收集邻居颜色
|
|
|
|
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()) { |
|
|
|
const Mesh::FaceFaces& topoNeighbors = scene.mesh.faceFaces[faceID]; |
|
|
|
const Mesh::FaceFaces& topoNeighbors = scene.mesh.faceFaces[faceID]; |
|
|
|
@ -18279,7 +18378,6 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( |
|
|
|
} |
|
|
|
} |
|
|
|
if (nbView == NO_ID || nbView >= (IIndex)images.size()) continue; |
|
|
|
if (nbView == NO_ID || nbView >= (IIndex)images.size()) continue; |
|
|
|
|
|
|
|
|
|
|
|
// 计算邻居面中心
|
|
|
|
|
|
|
|
const Mesh::Face& nbFace = scene.mesh.faces[nb]; |
|
|
|
const Mesh::Face& nbFace = scene.mesh.faces[nb]; |
|
|
|
Point3f nbCenter = (scene.mesh.vertices[nbFace[0]] + |
|
|
|
Point3f nbCenter = (scene.mesh.vertices[nbFace[0]] + |
|
|
|
scene.mesh.vertices[nbFace[1]] + |
|
|
|
scene.mesh.vertices[nbFace[1]] + |
|
|
|
@ -18294,23 +18392,32 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( |
|
|
|
|
|
|
|
|
|
|
|
float colorDiff = 0.0f; |
|
|
|
float colorDiff = 0.0f; |
|
|
|
if (!neighborColors.empty()) { |
|
|
|
if (!neighborColors.empty()) { |
|
|
|
|
|
|
|
// ★ 使用中位数代替平均,抗高光干扰
|
|
|
|
|
|
|
|
std::vector<float> diffs; |
|
|
|
for (const auto& nc : neighborColors) { |
|
|
|
for (const auto& nc : neighborColors) { |
|
|
|
cv::Vec3f d = candidateColor - nc; |
|
|
|
cv::Vec3f d = candidateColor - nc; |
|
|
|
colorDiff += std::sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2]); |
|
|
|
diffs.push_back(std::sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2])); |
|
|
|
} |
|
|
|
} |
|
|
|
colorDiff = colorDiff / neighborColors.size(); |
|
|
|
std::sort(diffs.begin(), diffs.end()); |
|
|
|
colorDiff = std::min(colorDiff, 1.0f); |
|
|
|
colorDiff = diffs[diffs.size() / 2]; // 中位数
|
|
|
|
|
|
|
|
|
|
|
|
score -= lambda * colorDiff; |
|
|
|
// ★ 平滑惩罚:平方衰减,小差异不罚,大差异渐重
|
|
|
|
|
|
|
|
float normDiff = std::min(colorDiff / dynamicMaxDiff, 1.0f); |
|
|
|
|
|
|
|
score -= dynamicLambda * normDiff * normDiff; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★ 极端的才重罚(避免硬截断导致碎片)
|
|
|
|
|
|
|
|
if (colorDiff > dynamicMaxDiff * 1.8f) { |
|
|
|
|
|
|
|
score -= 300.0f; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ---- 调试日志(前20个面 + 每5万面)----
|
|
|
|
// ---- 调试日志 ----
|
|
|
|
static int g_dbg = 0; |
|
|
|
static int g_dbg = 0; |
|
|
|
g_dbg++; |
|
|
|
g_dbg++; |
|
|
|
if (g_dbg <= 20 || faceID % 50000 == 0) { |
|
|
|
if (g_dbg <= 20 || faceID % 50000 == 0) { |
|
|
|
VERBOSE("[Score] face=%d view=%d rawScore=%.3f finalScore=%.3f", |
|
|
|
VERBOSE("[Score] face=%d view=%d rawScore=%.3f finalScore=%.3f colorDiff=%.1f isHair=%d", |
|
|
|
faceID, vid, rawScore, score); |
|
|
|
faceID, vid, rawScore, score, colorDiff, (int)isHair); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// ========== 颜色代价结束 ==========
|
|
|
|
// ========== 颜色代价结束 ==========
|
|
|
|
@ -18334,8 +18441,8 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ---------- 2. 改进的 Patch 一致性传播 ----------
|
|
|
|
// ---------- 2. 改进的 Patch 一致性传播(削弱版)----------
|
|
|
|
const int PROPAGATION_ITER = 6; |
|
|
|
const int PROPAGATION_ITER = 3; // 从 6 降为 3,减少过度平滑
|
|
|
|
for (int iter = 0; iter < PROPAGATION_ITER; ++iter) { |
|
|
|
for (int iter = 0; iter < PROPAGATION_ITER; ++iter) { |
|
|
|
std::vector<IIndex> newFaceToView = faceToView; |
|
|
|
std::vector<IIndex> newFaceToView = faceToView; |
|
|
|
std::vector<float> newFaceScores = faceScores; |
|
|
|
std::vector<float> newFaceScores = faceScores; |
|
|
|
@ -18347,7 +18454,8 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( |
|
|
|
const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[fid]; |
|
|
|
const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[fid]; |
|
|
|
float currentScore = faceScores[fid]; |
|
|
|
float currentScore = faceScores[fid]; |
|
|
|
|
|
|
|
|
|
|
|
if (currentScore > 0.95f) continue; |
|
|
|
// ★ 提高跳过门槛:高分面片不参与传播
|
|
|
|
|
|
|
|
if (currentScore > 0.85f) continue; // 原 0.95,降低到 0.85
|
|
|
|
|
|
|
|
|
|
|
|
std::unordered_map<IIndex, int> vote; |
|
|
|
std::unordered_map<IIndex, int> vote; |
|
|
|
vote[faceToView[fid]] = 1; |
|
|
|
vote[faceToView[fid]] = 1; |
|
|
|
@ -18381,7 +18489,8 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( |
|
|
|
} |
|
|
|
} |
|
|
|
if (count > 0) majorityAvgScore /= count; |
|
|
|
if (count > 0) majorityAvgScore /= count; |
|
|
|
|
|
|
|
|
|
|
|
if (majorityAvgScore > currentScore - 0.5f) { |
|
|
|
// ★ 提高切换门槛:多数派质量必须明显更好才切换
|
|
|
|
|
|
|
|
if (majorityAvgScore > currentScore - 0.2f && majorityAvgScore > 0.75f) { |
|
|
|
newFaceToView[fid] = majorityView; |
|
|
|
newFaceToView[fid] = majorityView; |
|
|
|
newFaceScores[fid] = majorityAvgScore; |
|
|
|
newFaceScores[fid] = majorityAvgScore; |
|
|
|
} |
|
|
|
} |
|
|
|
|