|
|
|
|
@ -768,6 +768,10 @@ public:
@@ -768,6 +768,10 @@ public:
|
|
|
|
|
unsigned nTextureSizeMultiple, |
|
|
|
|
Pixel8U colEmpty, |
|
|
|
|
Mesh::Image8U3Arr& outTextures); |
|
|
|
|
void FeatherTextureSeams(Image8U3& texture, |
|
|
|
|
const std::vector<IIndex>& texelPatchID, |
|
|
|
|
int textureSize, |
|
|
|
|
int featherRadius=3); |
|
|
|
|
void GlobalPatchColorAlignment(Image8U3& atlas, int textureSize); |
|
|
|
|
void LocalSeamBlending(Image8U3& atlas, int textureSize); |
|
|
|
|
std::vector<Color> patchAvgColor; // ← 直接声明 vector,不要加括号!
|
|
|
|
|
@ -827,8 +831,8 @@ public:
@@ -827,8 +831,8 @@ public:
|
|
|
|
|
std::vector<uint32_t> m_texelPatchID; // 每个 texel 属于哪个 rcPatch(-1 表示无)
|
|
|
|
|
inline void SmoothVirtualFaceViews( |
|
|
|
|
std::vector<std::vector<IIndex>>& virtualFaceViews, |
|
|
|
|
const Mesh::FaceFacesArr& faceFaces, // ← 改成这个类型
|
|
|
|
|
int iterations = 2) |
|
|
|
|
const Mesh::FaceFacesArr& faceFaces, |
|
|
|
|
int iterations = 6) |
|
|
|
|
{ |
|
|
|
|
for (int iter = 0; iter < iterations; ++iter) { |
|
|
|
|
auto newViews = virtualFaceViews; |
|
|
|
|
@ -837,25 +841,23 @@ public:
@@ -837,25 +841,23 @@ public:
|
|
|
|
|
IIndex myView = virtualFaceViews[i][0]; |
|
|
|
|
|
|
|
|
|
std::map<IIndex, int> vote; |
|
|
|
|
int validNeighbors = 0; |
|
|
|
|
|
|
|
|
|
// faceFaces[i] 是 TPoint3<FIndex>,用 .x .y .z 或 [0][1][2] 访问3个邻居
|
|
|
|
|
// 1-ring
|
|
|
|
|
const auto& ff = faceFaces[i]; |
|
|
|
|
for (int k = 0; k < 3; ++k) { |
|
|
|
|
FIndex nb = ff[k]; // ff.x / ff.y / ff.z 也行
|
|
|
|
|
if (nb < (FIndex)virtualFaceViews.size() && !virtualFaceViews[nb].empty()) { |
|
|
|
|
FIndex nb = ff[k]; |
|
|
|
|
if (nb < (FIndex)virtualFaceViews.size() && !virtualFaceViews[nb].empty()) |
|
|
|
|
vote[virtualFaceViews[nb][0]]++; |
|
|
|
|
validNeighbors++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (validNeighbors < 2) continue; |
|
|
|
|
if (vote.empty()) continue; |
|
|
|
|
|
|
|
|
|
auto best = std::max_element(vote.begin(), vote.end(), |
|
|
|
|
[](const std::pair<IIndex, int>& a, const std::pair<IIndex, int>& b) { |
|
|
|
|
return a.second < b.second; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
if (best->first != myView && best->second >= 2) { |
|
|
|
|
// ★ 条件:多数票 > 我的票 + 至少 2 票
|
|
|
|
|
int myVote = vote[myView]; |
|
|
|
|
if (best->first != myView && best->second > myVote && best->second >= 2) { |
|
|
|
|
newViews[i][0] = best->first; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -14605,9 +14607,48 @@ void MeshTexture::GlobalPatchColorAlignment(Image8U3& atlas, int textureSize)
@@ -14605,9 +14607,48 @@ void MeshTexture::GlobalPatchColorAlignment(Image8U3& atlas, int textureSize)
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Global alignment done: %d constraints (%s)", rows, TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
} |
|
|
|
|
// ============================================================
|
|
|
|
|
// 3. RC 风格光栅化主函数(含接缝优化)
|
|
|
|
|
// ============================================================
|
|
|
|
|
// 对每个 texel,如果它的 patchID 和邻居不同,说明是接缝
|
|
|
|
|
// 在接缝处做 3x3 或 5x5 的高斯模糊/均值模糊
|
|
|
|
|
void MeshTexture::FeatherTextureSeams(Image8U3& texture, |
|
|
|
|
const std::vector<IIndex>& texelPatchID, |
|
|
|
|
int textureSize, |
|
|
|
|
int featherRadius) |
|
|
|
|
{ |
|
|
|
|
if (texture.empty() || texelPatchID.empty()) return; |
|
|
|
|
|
|
|
|
|
// 对整张图做高斯模糊
|
|
|
|
|
Image8U3 blurred; |
|
|
|
|
cv::GaussianBlur(texture, blurred, |
|
|
|
|
cv::Size(2 * featherRadius + 1, 2 * featherRadius + 1), 0); |
|
|
|
|
|
|
|
|
|
// 逐像素检查接缝
|
|
|
|
|
for (int y = 0; y < textureSize; ++y) { |
|
|
|
|
for (int x = 0; x < textureSize; ++x) { |
|
|
|
|
int idx = y * textureSize + x; |
|
|
|
|
if (texelPatchID[idx] == NO_ID) continue; |
|
|
|
|
|
|
|
|
|
IIndex myPatch = texelPatchID[idx]; |
|
|
|
|
bool isSeam = false; |
|
|
|
|
|
|
|
|
|
// 检查 4 邻域
|
|
|
|
|
const int dx4[] = { -1, 1, 0, 0 }; |
|
|
|
|
const int dy4[] = { 0, 0, -1, 1 }; |
|
|
|
|
for (int d = 0; d < 4; ++d) { |
|
|
|
|
int nx = x + dx4[d], ny = y + dy4[d]; |
|
|
|
|
if (nx < 0 || ny < 0 || nx >= textureSize || ny >= textureSize) continue; |
|
|
|
|
IIndex nb = texelPatchID[ny * textureSize + nx]; |
|
|
|
|
if (nb != NO_ID && nb != myPatch) { |
|
|
|
|
isSeam = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (isSeam) { |
|
|
|
|
texture(y, x) = blurred(y, x); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// ============================================================
|
|
|
|
|
// 3. RC 风格光栅化主函数(含接缝优化)
|
|
|
|
|
// ============================================================
|
|
|
|
|
@ -14813,6 +14854,8 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14813,6 +14854,8 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
SeamBlendingFromOriginalImages(atlas); |
|
|
|
|
DEBUG_EXTRA("Seam blending completed: %zu edges (%s)", seamEdges.size(), TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// FeatherTextureSeams(atlas, m_texelPatchID, textureSize, 3);
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -15753,30 +15796,30 @@ if (!g_avgColorsComputed) {
@@ -15753,30 +15796,30 @@ if (!g_avgColorsComputed) {
|
|
|
|
|
// ========== 颜色一致性代价 ==========
|
|
|
|
|
{ |
|
|
|
|
const float lambda = 20.35f; |
|
|
|
|
|
|
|
|
|
// ========== 收集邻居颜色(修复版:1-ring + faceToView)==========
|
|
|
|
|
std::vector<cv::Vec3f> neighborColors; |
|
|
|
|
if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) { |
|
|
|
|
const Mesh::Face& topoNeighbors = scene.mesh.faceFaces[faceID]; |
|
|
|
|
for (int k = 0; k < 3; ++k) { |
|
|
|
|
FIndex nb = topoNeighbors[k]; |
|
|
|
|
if (nb == NO_ID || nb >= (FIndex)faceToView.size()) continue; |
|
|
|
|
if (!scene.mesh.faceFaces.empty() && nb < scene.mesh.faceFaces.size()) { |
|
|
|
|
const auto& nbrs = scene.mesh.faceFaces[nb]; |
|
|
|
|
for (int ni = 0; ni < 3; ++ni) { |
|
|
|
|
unsigned int nbrFace = nbrs[ni]; |
|
|
|
|
if (nbrFace == 0xFFFFFFFF) continue; |
|
|
|
|
if (nbrFace >= (unsigned int)faceNeighbors.size()) continue; |
|
|
|
|
if (faceNeighbors[nbrFace].empty()) continue; |
|
|
|
|
|
|
|
|
|
unsigned int nbrView = (unsigned int)faceNeighbors[nbrFace][0]; |
|
|
|
|
if (nbrView == 0xFFFFFFFF) continue; |
|
|
|
|
if (nbrView < (unsigned int)g_avgColors.size()) { |
|
|
|
|
neighborColors.push_back(g_avgColors[nbrView]); |
|
|
|
|
IIndex nbView = faceToView[nb]; |
|
|
|
|
if (nbView == NO_ID) { |
|
|
|
|
// fallback: 邻居还没分配 view,用第一个可见相机
|
|
|
|
|
if (nb < (FIndex)faceNeighbors.size() && !faceNeighbors[nb].empty()) { |
|
|
|
|
nbView = faceNeighbors[nb][0]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (nbView != NO_ID && nbView < (IIndex)g_avgColors.size()) { |
|
|
|
|
const cv::Vec3f& nc = g_avgColors[nbView]; |
|
|
|
|
if (nc[0] >= 0) { // 有效颜色才收集
|
|
|
|
|
neighborColors.push_back(nc); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// ========== 修复结束 ==========
|
|
|
|
|
|
|
|
|
|
float colorDiff = 0.0f; |
|
|
|
|
if (!neighborColors.empty()) { |
|
|
|
|
@ -15842,7 +15885,7 @@ if (!g_avgColorsComputed) {
@@ -15842,7 +15885,7 @@ if (!g_avgColorsComputed) {
|
|
|
|
|
|
|
|
|
|
// ---------- 2. 改进的 Patch 一致性传播 ----------
|
|
|
|
|
|
|
|
|
|
const int PROPAGATION_ITER = 2; |
|
|
|
|
const int PROPAGATION_ITER = 6; // 从2改到6
|
|
|
|
|
for (int iter = 0; iter < PROPAGATION_ITER; ++iter) { |
|
|
|
|
std::vector<IIndex> newFaceToView = faceToView; |
|
|
|
|
std::vector<float> newFaceScores = faceScores; |
|
|
|
|
@ -15854,7 +15897,8 @@ if (!g_avgColorsComputed) {
@@ -15854,7 +15897,8 @@ if (!g_avgColorsComputed) {
|
|
|
|
|
const Mesh::Face& neighbors = scene.mesh.faceFaces[fid]; |
|
|
|
|
float currentScore = faceScores[fid]; |
|
|
|
|
|
|
|
|
|
if (currentScore > 0.8f) continue; // 锁死高质量面
|
|
|
|
|
// ★ 放宽:只锁死 > 0.95 的极高分明面,其余都允许被传播覆盖
|
|
|
|
|
if (currentScore > 0.95f) continue; |
|
|
|
|
|
|
|
|
|
std::unordered_map<IIndex, int> vote; |
|
|
|
|
vote[faceToView[fid]] = 1; |
|
|
|
|
@ -15875,8 +15919,9 @@ if (!g_avgColorsComputed) {
@@ -15875,8 +15919,9 @@ if (!g_avgColorsComputed) {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (maxVote >= 3 && majorityView != faceToView[fid]) { |
|
|
|
|
// 多数视图在邻居中的平均评分
|
|
|
|
|
// ★ 放宽:只要多数票 >= 2 且和我不一样,就考虑切换(原来是 >= 3)
|
|
|
|
|
if (maxVote >= 2 && majorityView != faceToView[fid]) { |
|
|
|
|
// 计算多数view在邻居中的平均评分
|
|
|
|
|
float majorityAvgScore = 0.0f; |
|
|
|
|
int count = 0; |
|
|
|
|
for (int k = 0; k < 3; ++k) { |
|
|
|
|
@ -15889,76 +15934,9 @@ if (!g_avgColorsComputed) {
@@ -15889,76 +15934,9 @@ if (!g_avgColorsComputed) {
|
|
|
|
|
} |
|
|
|
|
if (count > 0) majorityAvgScore /= count; |
|
|
|
|
|
|
|
|
|
// ========== 颜色一致性检查(安全闸,优先于评分)==========
|
|
|
|
|
bool colorBlocked = false; |
|
|
|
|
{ |
|
|
|
|
cv::Vec3f majorityColor(0, 0, 0); |
|
|
|
|
int colorCount = 0; |
|
|
|
|
for (int k = 0; k < 3; ++k) { |
|
|
|
|
FIndex nb = neighbors[k]; |
|
|
|
|
if (nb == NO_ID || nb >= (FIndex)faceToView.size()) continue; |
|
|
|
|
if (faceToView[nb] == majorityView) { |
|
|
|
|
Point3f nbCenter = (scene.mesh.vertices[scene.mesh.faces[nb][0]] + |
|
|
|
|
scene.mesh.vertices[scene.mesh.faces[nb][1]] + |
|
|
|
|
scene.mesh.vertices[scene.mesh.faces[nb][2]]) / 3.0f; |
|
|
|
|
// cv::Vec3f col = SampleColorAtPoint(
|
|
|
|
|
// images[majorityView].image,
|
|
|
|
|
// images[majorityView].camera,
|
|
|
|
|
// nbCenter
|
|
|
|
|
// );
|
|
|
|
|
cv::Vec3f col = g_avgColors[majorityView]; |
|
|
|
|
|
|
|
|
|
if (col[0] >= 0) { |
|
|
|
|
majorityColor += col; |
|
|
|
|
colorCount++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (colorCount > 0) { |
|
|
|
|
majorityColor /= (float)colorCount; |
|
|
|
|
Point3f curCenter = (scene.mesh.vertices[scene.mesh.faces[fid][0]] + |
|
|
|
|
scene.mesh.vertices[scene.mesh.faces[fid][1]] + |
|
|
|
|
scene.mesh.vertices[scene.mesh.faces[fid][2]]) / 3.0f; |
|
|
|
|
// cv::Vec3f curColor = SampleColorAtPoint(
|
|
|
|
|
// images[faceToView[fid]].image,
|
|
|
|
|
// images[faceToView[fid]].camera,
|
|
|
|
|
// curCenter
|
|
|
|
|
// );
|
|
|
|
|
cv::Vec3f curColor = g_avgColors[faceToView[fid]]; |
|
|
|
|
if (curColor[0] >= 0) { |
|
|
|
|
cv::Vec3f d = curColor - majorityColor; |
|
|
|
|
float colorDiff = std::sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2]) / 1.732f; |
|
|
|
|
|
|
|
|
|
if (fid % 50000 == 0) { |
|
|
|
|
VERBOSE("[Propagate] face=%d majorityView=%d curView=%d colorDiff=%.3f", |
|
|
|
|
fid, majorityView, faceToView[fid], colorDiff); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (colorDiff > 0.4f) { |
|
|
|
|
colorBlocked = true; // ✅ 阻断传播
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// ========== 颜色检查结束 ==========
|
|
|
|
|
|
|
|
|
|
// ✅ 只有颜色没被阻断时才考虑评分条件
|
|
|
|
|
bool shouldPropagate = false; |
|
|
|
|
if (!colorBlocked) { |
|
|
|
|
if (currentScore < 0.3f) { |
|
|
|
|
shouldPropagate = (majorityAvgScore > currentScore - 0.2f); |
|
|
|
|
} else if (currentScore < 0.6f) { |
|
|
|
|
shouldPropagate = (majorityAvgScore > currentScore - 0.1f); |
|
|
|
|
} else { |
|
|
|
|
shouldPropagate = (majorityAvgScore > currentScore + 0.05f); |
|
|
|
|
} |
|
|
|
|
if (currentScore > 0.6f && std::abs(majorityAvgScore - currentScore) < 0.1f) { |
|
|
|
|
shouldPropagate = false; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (shouldPropagate) { |
|
|
|
|
// ★ 简化:去掉颜色阻断,直接让评分说话
|
|
|
|
|
// 只要多数view的平均分不低于我太多(差 < 0.3),就切换
|
|
|
|
|
if (majorityAvgScore > currentScore - 0.3f) { |
|
|
|
|
newFaceToView[fid] = majorityView; |
|
|
|
|
newFaceScores[fid] = majorityAvgScore; |
|
|
|
|
} |
|
|
|
|
@ -15982,6 +15960,28 @@ if (!g_avgColorsComputed) {
@@ -15982,6 +15960,28 @@ if (!g_avgColorsComputed) {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 在写回循环之后,加这个:
|
|
|
|
|
for (int iter = 0; iter < 3; ++iter) { |
|
|
|
|
auto newFaceToView = faceToView; |
|
|
|
|
for (size_t i = 0; i < faceToView.size(); ++i) { |
|
|
|
|
if (faceToView[i] == NO_ID) continue; |
|
|
|
|
std::map<IIndex, int> vote; |
|
|
|
|
const auto& ff = scene.mesh.faceFaces[i]; |
|
|
|
|
for (int k = 0; k < 3; ++k) { |
|
|
|
|
FIndex nb = ff[k]; |
|
|
|
|
if (nb < (FIndex)faceToView.size() && faceToView[nb] != NO_ID) |
|
|
|
|
vote[faceToView[nb]]++; |
|
|
|
|
} |
|
|
|
|
if (vote.size() <= 1) continue; |
|
|
|
|
auto best = std::max_element(vote.begin(), vote.end(), |
|
|
|
|
[](const std::pair<IIndex, int>& a, const std::pair<IIndex, int>& b) { |
|
|
|
|
return a.second < b.second; }); |
|
|
|
|
if (best->first != faceToView[i] && best->second >= 2) |
|
|
|
|
newFaceToView[i] = best->first; |
|
|
|
|
} |
|
|
|
|
faceToView.swap(newFaceToView); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("====== Virtual Face View Selection Summary ======"); |
|
|
|
|
DEBUG_EXTRA("Total virtual faces : %zu", virtualFaceMap.size()); |
|
|
|
|
DEBUG_EXTRA("Successfully assigned : %zu", successVF); |
|
|
|
|
@ -20305,7 +20305,7 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
@@ -20305,7 +20305,7 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
texture.SmoothVirtualFaceViews(texture.faceViews, mesh.faceFaces, 2); |
|
|
|
|
texture.SmoothVirtualFaceViews(texture.faceViews, mesh.faceFaces, 6); |
|
|
|
|
// ✅ 4. RC 风格光栅化(Affine + Homography 混合)
|
|
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
|
if (!texture.RasterizeVirtualFaces( |
|
|
|
|
|