|
|
|
|
@ -648,6 +648,8 @@ public:
@@ -648,6 +648,8 @@ public:
|
|
|
|
|
unsigned minCommonCameras, float fOutlierThreshold, |
|
|
|
|
float fRatioDataSmoothness, int nIgnoreMaskLabel, |
|
|
|
|
const IIndexArr& views); |
|
|
|
|
// 简单的 view smoothing:如果某个面的 view 和多数邻居不同,且分数差距不大,就改成邻居的 view
|
|
|
|
|
|
|
|
|
|
float ComputeViewNormalScore(const Mesh::Normal& faceNormal, const Camera& camera, const Point3f& faceCenter); |
|
|
|
|
float ComputeResolutionScore( |
|
|
|
|
const Camera& camera, |
|
|
|
|
@ -822,7 +824,44 @@ public:
@@ -822,7 +824,44 @@ public:
|
|
|
|
|
IIndex viewID = NO_ID; |
|
|
|
|
}; |
|
|
|
|
std::vector<TexelScore> m_texelScores; |
|
|
|
|
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) |
|
|
|
|
{ |
|
|
|
|
for (int iter = 0; iter < iterations; ++iter) { |
|
|
|
|
auto newViews = virtualFaceViews; |
|
|
|
|
for (size_t i = 0; i < virtualFaceViews.size(); ++i) { |
|
|
|
|
if (virtualFaceViews[i].empty()) continue; |
|
|
|
|
IIndex myView = virtualFaceViews[i][0]; |
|
|
|
|
|
|
|
|
|
std::map<IIndex, int> vote; |
|
|
|
|
int validNeighbors = 0; |
|
|
|
|
|
|
|
|
|
// faceFaces[i] 是 TPoint3<FIndex>,用 .x .y .z 或 [0][1][2] 访问3个邻居
|
|
|
|
|
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()) { |
|
|
|
|
vote[virtualFaceViews[nb][0]]++; |
|
|
|
|
validNeighbors++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (validNeighbors < 2) 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) { |
|
|
|
|
newViews[i][0] = best->first; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
virtualFaceViews.swap(newViews); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
float ComputeComprehensiveScore(const FaceData& data, const Normal& faceNormal, |
|
|
|
|
const Point3f& faceCenter, const Image& image); |
|
|
|
|
float EstimatePixelSize(const Point3f& faceCenter, const Normal& faceNormal, |
|
|
|
|
@ -14390,7 +14429,7 @@ void MeshTexture::LocalSeamBlending(Image8U3& atlas, int textureSize)
@@ -14390,7 +14429,7 @@ void MeshTexture::LocalSeamBlending(Image8U3& atlas, int textureSize)
|
|
|
|
|
cv::Rect overlap = rcPatches[e.rcPatchID0].rect & rcPatches[e.rcPatchID1].rect; |
|
|
|
|
if (overlap.width <= 0 || overlap.height <= 0) continue; |
|
|
|
|
// 只取 overlap 的边界环(带状),宽度 ~8px
|
|
|
|
|
int bw = 8; |
|
|
|
|
int bw = 16; |
|
|
|
|
cv::Rect inner(overlap.x + bw, overlap.y + bw, |
|
|
|
|
std::max(0, overlap.width - 2*bw), |
|
|
|
|
std::max(0, overlap.height - 2*bw)); |
|
|
|
|
@ -14543,30 +14582,32 @@ void MeshTexture::GlobalPatchColorAlignment(Image8U3& atlas, int textureSize)
@@ -14543,30 +14582,32 @@ void MeshTexture::GlobalPatchColorAlignment(Image8U3& atlas, int textureSize)
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 应用:对每个 patch 的像素加上偏移(加性,限制幅度)
|
|
|
|
|
const float maxAdj = 30.0f; // 8-bit 空间最大偏移
|
|
|
|
|
#pragma omp parallel for |
|
|
|
|
for (int i = 0; i < NP; ++i) { |
|
|
|
|
const RCPatch& patch = rcPatches[i]; |
|
|
|
|
float adjR = std::max(-maxAdj, std::min(maxAdj, xR(i))); |
|
|
|
|
float adjG = std::max(-maxAdj, std::min(maxAdj, xG(i))); |
|
|
|
|
float adjB = std::max(-maxAdj, std::min(maxAdj, xB(i))); |
|
|
|
|
|
|
|
|
|
for (int y = patch.rect.y; y < patch.rect.y + patch.rect.height; ++y) { |
|
|
|
|
for (int x = patch.rect.x; x < patch.rect.x + patch.rect.width; ++x) { |
|
|
|
|
if (x < 0 || x >= textureSize || y < 0 || y >= textureSize) continue; |
|
|
|
|
Pixel8U& px = atlas(y, x); |
|
|
|
|
if (px[0]==0 && px[1]==0 && px[2]==0) continue; |
|
|
|
|
// BGR 存储顺序
|
|
|
|
|
px[2] = (uint8_t)CLAMP(px[2] + adjR, 0.f, 255.f); // R
|
|
|
|
|
px[1] = (uint8_t)CLAMP(px[1] + adjG, 0.f, 255.f); // G
|
|
|
|
|
px[0] = (uint8_t)CLAMP(px[0] + adjB, 0.f, 255.f); // B
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
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()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// 3. RC 风格光栅化主函数(含接缝优化)
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ============================================================
|
|
|
|
|
// 3. RC 风格光栅化主函数(含接缝优化)
|
|
|
|
|
// ============================================================
|
|
|
|
|
@ -14600,19 +14641,30 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14600,19 +14641,30 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
atlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
m_texelScores.assign(textureSize * textureSize, TexelScore{}); |
|
|
|
|
|
|
|
|
|
// ★ 修复:m_texelPatchID 只初始化一次,移到循环外面
|
|
|
|
|
m_texelPatchID.assign(textureSize * textureSize, NO_ID); |
|
|
|
|
|
|
|
|
|
VERBOSE("[Raster] Step 1: initializing buffers... textureSize=%d, scores=%zu, patchID=%zu", |
|
|
|
|
textureSize, m_texelScores.size(), m_texelPatchID.size()); |
|
|
|
|
|
|
|
|
|
if (!ComputeVirtualFaceGeometry(virtualFaceMap)) { |
|
|
|
|
DEBUG_EXTRA("Failed to compute virtual face geometries"); return false; |
|
|
|
|
} |
|
|
|
|
currentTextureSize = textureSize; |
|
|
|
|
|
|
|
|
|
// 确保增益已估计(若外部未调用则在此兜底)
|
|
|
|
|
if (!m_gainsEstimated) EstimateGlobalPhotometricCorrection(); |
|
|
|
|
|
|
|
|
|
// 3. 逐三角形光栅化(直接写入,最高分胜出 + 光度校正)
|
|
|
|
|
VERBOSE("[Raster] Step 2: starting patch loop, total faces=%zu", virtualFaceMap.size()); |
|
|
|
|
|
|
|
|
|
// 3. 逐三角形光栅化
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
|
#endif |
|
|
|
|
for (int i = 0; i < (int)virtualFaceMap.size(); ++i) { |
|
|
|
|
|
|
|
|
|
if (i % 10000 == 0) |
|
|
|
|
VERBOSE("[Raster] processing face %d / %d", i, (int)virtualFaceMap.size()); |
|
|
|
|
|
|
|
|
|
const VirtualFace& vf = virtualFaceMap[i]; |
|
|
|
|
const VirtualFaceGeometry& geom = m_virtualFaceGeometries[i]; |
|
|
|
|
if (!geom.isValid || vf.faces.empty() || virtualFaceViews[i].empty()) continue; |
|
|
|
|
@ -14631,6 +14683,13 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14631,6 +14683,13 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
if (minX > maxX || minY > maxY) continue; |
|
|
|
|
int patchW = maxX - minX + 1, patchH = maxY - minY + 1; |
|
|
|
|
|
|
|
|
|
// ★ Guard:跳过异常巨大的 patch
|
|
|
|
|
if (patchW > textureSize || patchH > textureSize || |
|
|
|
|
(int64_t)patchW * patchH > (int64_t)textureSize * textureSize / 5) { |
|
|
|
|
DEBUG_EXTRA("[Raster] face %d has HUGE rect: %dx%d, skipping", i, 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) { |
|
|
|
|
@ -14645,7 +14704,7 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14645,7 +14704,7 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
cv::Mat patch; |
|
|
|
|
cv::remap(srcImg.image, patch, mapX, mapY, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); |
|
|
|
|
|
|
|
|
|
// 应用光度校正(在 patch 上原地乘 gain)
|
|
|
|
|
// 应用光度校正
|
|
|
|
|
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)); |
|
|
|
|
if (applyGain) { |
|
|
|
|
@ -14653,9 +14712,9 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14653,9 +14712,9 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
for (int x = 0; x < patchW; ++x) { |
|
|
|
|
cv::Vec3b& p = patch.at<cv::Vec3b>(y, x); |
|
|
|
|
if (p[0]==0 && p[1]==0 && p[2]==0) continue; |
|
|
|
|
p[0] = cv::saturate_cast<uchar>(p[0]*gain[0]); // B
|
|
|
|
|
p[1] = cv::saturate_cast<uchar>(p[1]*gain[1]); // G
|
|
|
|
|
p[2] = cv::saturate_cast<uchar>(p[2]*gain[2]); // R
|
|
|
|
|
p[0] = cv::saturate_cast<uchar>(p[0]*gain[0]); |
|
|
|
|
p[1] = cv::saturate_cast<uchar>(p[1]*gain[1]); |
|
|
|
|
p[2] = cv::saturate_cast<uchar>(p[2]*gain[2]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -14670,16 +14729,22 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14670,16 +14729,22 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
int atlasX = x + minX, atlasY = y + minY; |
|
|
|
|
if (atlasX<0||atlasX>=textureSize||atlasY<0||atlasY>=textureSize) continue; |
|
|
|
|
size_t idx = atlasY * textureSize + atlasX; |
|
|
|
|
|
|
|
|
|
// ★ 已删除错误放在这里的 m_texelPatchID.assign(...)
|
|
|
|
|
|
|
|
|
|
if (currentScore > m_texelScores[idx].score) { |
|
|
|
|
atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; |
|
|
|
|
m_texelScores[idx].score = currentScore; |
|
|
|
|
m_texelScores[idx].viewID = viewID; |
|
|
|
|
m_texelPatchID[idx] = i; // ★ 记录这个像素属于 patch i
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
VERBOSE("[Raster] Step 3: rasterization done, building RC patches..."); |
|
|
|
|
|
|
|
|
|
m_texelScores.clear(); |
|
|
|
|
DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
m_virtualFaceViewWeights = virtualFaceViewWeights; |
|
|
|
|
@ -14701,21 +14766,25 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14701,21 +14766,25 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("Created %zu RC patches", rcPatches.size()); |
|
|
|
|
|
|
|
|
|
// ★ 统计每个 patch 的平均色(光栅化完成后调用)
|
|
|
|
|
// ★ 统计每个 patch 的平均色
|
|
|
|
|
const int NP = (int)rcPatches.size(); |
|
|
|
|
patchAvgColor.assign(NP, Color(0,0,0)); |
|
|
|
|
std::vector<int> pixelCount(NP, 0); |
|
|
|
|
|
|
|
|
|
for (int i = 0; i < NP; ++i) { |
|
|
|
|
if ((int64_t)rcPatches[i].rect.width * rcPatches[i].rect.height > (int64_t)textureSize * textureSize / 5) { |
|
|
|
|
DEBUG_EXTRA("[Raster] patch %d HUGE rect in avgColor, skipping", i); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
const RCPatch& patch = rcPatches[i]; |
|
|
|
|
for (int y = patch.rect.y; y < patch.rect.y + patch.rect.height; ++y) { |
|
|
|
|
for (int x = patch.rect.x; x < patch.rect.x + patch.rect.width; ++x) { |
|
|
|
|
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]; // R
|
|
|
|
|
patchAvgColor[i][1] += px[1]; // G
|
|
|
|
|
patchAvgColor[i][2] += px[0]; // B
|
|
|
|
|
if (px[0]==0 && px[1]==0 && px[2]==0) continue; |
|
|
|
|
patchAvgColor[i][0] += px[2]; |
|
|
|
|
patchAvgColor[i][1] += px[1]; |
|
|
|
|
patchAvgColor[i][2] += px[0]; |
|
|
|
|
pixelCount[i]++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -14733,10 +14802,10 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14733,10 +14802,10 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
// 5/6/7. 接缝
|
|
|
|
|
BuildSeamEdgesFromRCPatches(); |
|
|
|
|
if (!rcSeamEdges.empty()) { |
|
|
|
|
DEBUG_EXTRA("Before GlobalAlign: rcSeamEdges=%zu, patchAvgColor=%zu", |
|
|
|
|
rcSeamEdges.size(), patchAvgColor.size()); |
|
|
|
|
GlobalPatchColorAlignment(atlas, textureSize); // 先全局对齐
|
|
|
|
|
LocalSeamBlending(atlas, textureSize); // 再局部软化
|
|
|
|
|
DEBUG_EXTRA("Before GlobalAlign: rcSeamEdges=%zu, patchAvgColor=%zu", |
|
|
|
|
rcSeamEdges.size(), patchAvgColor.size()); |
|
|
|
|
GlobalPatchColorAlignment(atlas, textureSize); |
|
|
|
|
LocalSeamBlending(atlas, textureSize); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!seamEdges.empty()) { |
|
|
|
|
@ -15683,7 +15752,7 @@ if (!g_avgColorsComputed) {
@@ -15683,7 +15752,7 @@ if (!g_avgColorsComputed) {
|
|
|
|
|
|
|
|
|
|
// ========== 颜色一致性代价 ==========
|
|
|
|
|
{ |
|
|
|
|
const float lambda = 200.35f; |
|
|
|
|
const float lambda = 20.35f; |
|
|
|
|
|
|
|
|
|
std::vector<cv::Vec3f> neighborColors; |
|
|
|
|
if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) { |
|
|
|
|
@ -15691,11 +15760,11 @@ if (!g_avgColorsComputed) {
@@ -15691,11 +15760,11 @@ if (!g_avgColorsComputed) {
|
|
|
|
|
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() && faceID < scene.mesh.faceFaces.size()) { |
|
|
|
|
const SEACAVE::TPoint3<unsigned int>& nbrs = scene.mesh.faceFaces[faceID]; |
|
|
|
|
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; // 边界边没有邻居(NO_ID)
|
|
|
|
|
if (nbrFace == 0xFFFFFFFF) continue; |
|
|
|
|
if (nbrFace >= (unsigned int)faceNeighbors.size()) continue; |
|
|
|
|
if (faceNeighbors[nbrFace].empty()) continue; |
|
|
|
|
|
|
|
|
|
@ -20235,6 +20304,8 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
@@ -20235,6 +20304,8 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
|
|
|
|
|
fRatioDataSmoothness, nIgnoreMaskLabel, views)) |
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
texture.SmoothVirtualFaceViews(texture.faceViews, mesh.faceFaces, 2); |
|
|
|
|
// ✅ 4. RC 风格光栅化(Affine + Homography 混合)
|
|
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
|
if (!texture.RasterizeVirtualFaces( |
|
|
|
|
|