|
|
|
|
@ -764,6 +764,7 @@ public:
@@ -764,6 +764,7 @@ public:
|
|
|
|
|
void GlobalSeamLeveling4(); |
|
|
|
|
void LocalSeamLeveling(); |
|
|
|
|
void LocalSeamLeveling3(); |
|
|
|
|
void LocalSeamLeveling4(); |
|
|
|
|
|
|
|
|
|
void GlobalSeamLevelingExternalUV(); |
|
|
|
|
void LocalSeamLevelingExternalUV(); |
|
|
|
|
@ -10054,7 +10055,277 @@ void MeshTexture::LocalSeamLeveling3()
@@ -10054,7 +10055,277 @@ void MeshTexture::LocalSeamLeveling3()
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
void MeshTexture::LocalSeamLeveling4() |
|
|
|
|
{ |
|
|
|
|
ASSERT(!seamVertices.empty()); |
|
|
|
|
const unsigned numPatches(texturePatches.size()-1); |
|
|
|
|
|
|
|
|
|
// ★ 统计跳过情况
|
|
|
|
|
int skipEmptyRect = 0, skipBadLabel = 0, skipBadProj = 0, skipNoSeam = 0; |
|
|
|
|
|
|
|
|
|
#ifdef TEXOPT_USE_OPENMP |
|
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
|
for (int i=0; i<(int)numPatches; ++i) { |
|
|
|
|
#else |
|
|
|
|
for (unsigned i=0; i<numPatches; ++i) { |
|
|
|
|
#endif |
|
|
|
|
const uint32_t idxPatch((uint32_t)i); |
|
|
|
|
const TexturePatch& texturePatch = texturePatches[idxPatch]; |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ★ 保护 1:检查 label (viewID) 合法性
|
|
|
|
|
// ============================================================
|
|
|
|
|
if (texturePatch.label < 0 || texturePatch.label >= (int)images.size()) { |
|
|
|
|
#ifdef TEXOPT_USE_OPENMP |
|
|
|
|
#pragma omp atomic |
|
|
|
|
#endif |
|
|
|
|
skipBadLabel++; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ★ 保护 2:检查 rect 合法性(最常见 crash 原因)
|
|
|
|
|
// ============================================================
|
|
|
|
|
if (texturePatch.rect.width <= 0 || texturePatch.rect.height <= 0 || |
|
|
|
|
texturePatch.rect.x < 0 || texturePatch.rect.y < 0) { |
|
|
|
|
#ifdef TEXOPT_USE_OPENMP |
|
|
|
|
#pragma omp atomic |
|
|
|
|
#endif |
|
|
|
|
skipEmptyRect++; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 检查 rect 是否超出源图像范围
|
|
|
|
|
const Image8U3& image0(images[texturePatch.label].image); |
|
|
|
|
if (image0.empty()) { |
|
|
|
|
#ifdef TEXOPT_USE_OPENMP |
|
|
|
|
#pragma omp atomic |
|
|
|
|
#endif |
|
|
|
|
skipBadLabel++; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
cv::Rect safeRect = texturePatch.rect & cv::Rect(0, 0, image0.cols, image0.rows); |
|
|
|
|
if (safeRect.width <= 0 || safeRect.height <= 0) { |
|
|
|
|
#ifdef TEXOPT_USE_OPENMP |
|
|
|
|
#pragma omp atomic |
|
|
|
|
#endif |
|
|
|
|
skipEmptyRect++; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// extract image
|
|
|
|
|
Image32F3 image, imageOrg; |
|
|
|
|
#ifdef USE_CUDA |
|
|
|
|
if (MeshTextureCUDA::ConvertToCUDA(image0(safeRect), image, 1.0/255.0)) {} |
|
|
|
|
else { |
|
|
|
|
image0(safeRect).convertTo(image, CV_32FC3, 1.0/255.0); |
|
|
|
|
} |
|
|
|
|
#else |
|
|
|
|
image0(safeRect).convertTo(image, CV_32FC3, 1.0/255.0); |
|
|
|
|
#endif |
|
|
|
|
image.copyTo(imageOrg); |
|
|
|
|
|
|
|
|
|
if (image.empty() || image.cols < 2 || image.rows < 2) { |
|
|
|
|
#ifdef TEXOPT_USE_OPENMP |
|
|
|
|
#pragma omp atomic |
|
|
|
|
#endif |
|
|
|
|
skipEmptyRect++; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// render patch coverage
|
|
|
|
|
Image8U mask(image.size()); { |
|
|
|
|
mask.memset(0); |
|
|
|
|
struct RasterMesh { |
|
|
|
|
Image8U& image; |
|
|
|
|
inline void operator()(const ImageRef& pt) { |
|
|
|
|
if (image.isInside(pt)) |
|
|
|
|
image(pt) = interior; |
|
|
|
|
} |
|
|
|
|
} data{mask}; |
|
|
|
|
for (const FIndex idxFace: texturePatch.faces) { |
|
|
|
|
if (idxFace >= faces.size()) continue; |
|
|
|
|
const TexCoord* tri = faceTexcoords.data()+idxFace*3; |
|
|
|
|
ColorMap::RasterizeTriangle(tri[0], tri[1], tri[2], data); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const Sampler sampler; |
|
|
|
|
const TexCoord offset(texturePatch.rect.tl()); |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ★ 保护 3:遍历接缝前,检查这个 patch 在 seamVertices 里有没有引用
|
|
|
|
|
// ============================================================
|
|
|
|
|
bool patchHasSeam = false; |
|
|
|
|
for (const SeamVertex& sv : seamVertices) { |
|
|
|
|
for (const SeamVertex::Patch& p : sv.patches) { |
|
|
|
|
if (p.idxPatch == idxPatch) { |
|
|
|
|
patchHasSeam = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (patchHasSeam) break; |
|
|
|
|
} |
|
|
|
|
if (!patchHasSeam) { |
|
|
|
|
// 这个 patch 不在任何接缝上,不需要局部融合,直接复制原图
|
|
|
|
|
#ifdef TEXOPT_USE_OPENMP |
|
|
|
|
#pragma omp atomic |
|
|
|
|
#endif |
|
|
|
|
skipNoSeam++; |
|
|
|
|
|
|
|
|
|
// 直接把 imageOrg 写回 imagePatch(不做泊松融合)
|
|
|
|
|
cv::Mat imagePatch(image0(safeRect)); |
|
|
|
|
for (int r=0; r<image.rows; ++r) { |
|
|
|
|
for (int c=0; c<image.cols; ++c) { |
|
|
|
|
const Color& a = imageOrg(r,c); |
|
|
|
|
Pixel8U& v = imagePatch.at<Pixel8U>(r,c); |
|
|
|
|
for (int p=0; p<3; ++p) |
|
|
|
|
v[p] = (uint8_t)CLAMP(ROUND2INT(a[p]*255.f), 0, 255); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// render the patch border meeting neighbor patches
|
|
|
|
|
for (const SeamVertex& seamVertex0: seamVertices) { |
|
|
|
|
if (seamVertex0.patches.size() < 2) |
|
|
|
|
continue; |
|
|
|
|
const uint32_t idxVertPatch0(seamVertex0.patches.Find(idxPatch)); |
|
|
|
|
if (idxVertPatch0 == SeamVertex::Patches::NO_INDEX) |
|
|
|
|
continue; |
|
|
|
|
const SeamVertex::Patch& patch0 = seamVertex0.patches[idxVertPatch0]; |
|
|
|
|
const TexCoord p0(patch0.proj-offset); |
|
|
|
|
|
|
|
|
|
// ★ 保护:p0 必须在 image 范围内
|
|
|
|
|
if (p0.x < 0 || p0.y < 0 || p0.x >= image.cols || p0.y >= image.rows) { |
|
|
|
|
skipBadProj++; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// for each edge of this vertex belonging to this patch...
|
|
|
|
|
for (const SeamVertex::Patch::Edge& edge0: patch0.edges) { |
|
|
|
|
if (edge0.idxSeamVertex >= seamVertices.size()) continue; |
|
|
|
|
|
|
|
|
|
const SeamVertex& seamVertex1 = seamVertices[edge0.idxSeamVertex]; |
|
|
|
|
const uint32_t idxVertPatch0Adj(seamVertex1.patches.Find(idxPatch)); |
|
|
|
|
if (idxVertPatch0Adj == SeamVertex::Patches::NO_INDEX) continue; |
|
|
|
|
|
|
|
|
|
const SeamVertex::Patch& patch0Adj = seamVertex1.patches[idxVertPatch0Adj]; |
|
|
|
|
const TexCoord p0Adj(patch0Adj.proj-offset); |
|
|
|
|
|
|
|
|
|
if (p0Adj.x < 0 || p0Adj.y < 0 || p0Adj.x >= image.cols || p0Adj.y >= image.rows) { |
|
|
|
|
skipBadProj++; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// find the other patch sharing the same edge
|
|
|
|
|
FOREACH(idxVertPatch1, seamVertex0.patches) { |
|
|
|
|
if (idxVertPatch1 == idxVertPatch0) continue; |
|
|
|
|
const SeamVertex::Patch& patch1 = seamVertex0.patches[idxVertPatch1]; |
|
|
|
|
|
|
|
|
|
if (patch1.idxPatch >= texturePatches.size()) continue; // ★ 越界保护
|
|
|
|
|
|
|
|
|
|
const uint32_t idxEdge1(patch1.edges.Find(edge0.idxSeamVertex)); |
|
|
|
|
if (idxEdge1 == SeamVertex::Patch::Edges::NO_INDEX) continue; |
|
|
|
|
|
|
|
|
|
const TexCoord& p1(patch1.proj); |
|
|
|
|
const uint32_t idxVertPatch1Adj(seamVertex1.patches.Find(patch1.idxPatch)); |
|
|
|
|
if (idxVertPatch1Adj == SeamVertex::Patches::NO_INDEX) continue; |
|
|
|
|
|
|
|
|
|
const SeamVertex::Patch& patch1Adj = seamVertex1.patches[idxVertPatch1Adj]; |
|
|
|
|
const TexCoord& p1Adj(patch1Adj.proj); |
|
|
|
|
|
|
|
|
|
// 检查 label
|
|
|
|
|
const int label1 = texturePatches[patch1.idxPatch].label; |
|
|
|
|
if (label1 < 0 || label1 >= (int)images.size()) continue; |
|
|
|
|
const Image8U3& image1(images[label1].image); |
|
|
|
|
if (image1.empty()) continue; |
|
|
|
|
|
|
|
|
|
struct RasterPatch { |
|
|
|
|
Image32F3& image; |
|
|
|
|
Image8U& mask; |
|
|
|
|
const Image32F3& image0; |
|
|
|
|
const Image8U3& image1; |
|
|
|
|
const TexCoord p0, p0Dir; |
|
|
|
|
const TexCoord p1, p1Dir; |
|
|
|
|
const float length; |
|
|
|
|
const Sampler sampler; |
|
|
|
|
inline RasterPatch(Image32F3& _image, Image8U& _mask, const Image32F3& _image0, const Image8U3& _image1, |
|
|
|
|
const TexCoord& _p0, const TexCoord& _p0Adj, const TexCoord& _p1, const TexCoord& _p1Adj) |
|
|
|
|
: image(_image), mask(_mask), image0(_image0), image1(_image1), |
|
|
|
|
p0(_p0), p0Dir(_p0Adj-_p0), p1(_p1), p1Dir(_p1Adj-_p1), length((float)norm(p0Dir)), sampler() {} |
|
|
|
|
inline void operator()(const ImageRef& pt) { |
|
|
|
|
// ★ 保护:pt 必须在 image 范围内
|
|
|
|
|
if (pt.x < 0 || pt.y < 0 || pt.x >= image.cols || pt.y >= image.rows) |
|
|
|
|
return; |
|
|
|
|
const float l((float)norm(TexCoord(pt)-p0)/length); |
|
|
|
|
const TexCoord samplePos0(p0 + p0Dir * l); |
|
|
|
|
const Color color0(image0.sample<Sampler,Color>(sampler, samplePos0)); |
|
|
|
|
const TexCoord samplePos1(p1 + p1Dir * l); |
|
|
|
|
const Color color1(image1.sample<Sampler,Color>(sampler, samplePos1)/255.f); |
|
|
|
|
image(pt) = Color((color0 + color1) * 0.5f); |
|
|
|
|
mask(pt) = border; |
|
|
|
|
} |
|
|
|
|
} data(image, mask, imageOrg, image1, p0, p0Adj, p1, p1Adj); |
|
|
|
|
|
|
|
|
|
// ★ 保护:DrawLine 前检查端点
|
|
|
|
|
if (p0.x >= 0 && p0.y >= 0 && p0Adj.x >= 0 && p0Adj.y >= 0 && |
|
|
|
|
p0.x < image.cols && p0.y < image.rows && |
|
|
|
|
p0Adj.x < image.cols && p0Adj.y < image.rows) { |
|
|
|
|
Image32F3::DrawLine(p0, p0Adj, data); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// render vertex
|
|
|
|
|
AccumColor accumColor; |
|
|
|
|
for (const SeamVertex::Patch& patch: seamVertex0.patches) { |
|
|
|
|
if (patch.idxPatch >= texturePatches.size()) continue; |
|
|
|
|
int plabel = texturePatches[patch.idxPatch].label; |
|
|
|
|
if (plabel < 0 || plabel >= (int)images.size()) continue; |
|
|
|
|
const Image8U3& img(images[plabel].image); |
|
|
|
|
accumColor.Add(img.sample<Sampler,Color>(sampler, patch.proj)/255.f, 1.f); |
|
|
|
|
} |
|
|
|
|
const ImageRef pt(ROUND2INT(patch0.proj-offset)); |
|
|
|
|
if (pt.x >= 0 && pt.y >= 0 && pt.x < image.cols && pt.y < image.rows) { |
|
|
|
|
image(pt) = accumColor.Normalized(); |
|
|
|
|
mask(pt) = border; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// make sure the border is continuous
|
|
|
|
|
#ifdef USE_CUDA |
|
|
|
|
if (MeshTextureCUDA::ProcessMaskCUDA(mask, 20)) {} |
|
|
|
|
else |
|
|
|
|
#endif |
|
|
|
|
ProcessMask(mask, 20); |
|
|
|
|
|
|
|
|
|
// compute texture patch blending
|
|
|
|
|
#ifdef USE_CUDA |
|
|
|
|
if (MeshTextureCUDA::PoissonBlendCUDA(image, imageOrg, mask, 1.0f)) {} |
|
|
|
|
else |
|
|
|
|
#endif |
|
|
|
|
PoissonBlending(imageOrg, image, mask); |
|
|
|
|
|
|
|
|
|
// apply color correction to the patch image
|
|
|
|
|
cv::Mat imagePatch(image0(safeRect)); |
|
|
|
|
#ifdef TEXOPT_USE_OPENMP |
|
|
|
|
#pragma omp parallel for collapse(2) |
|
|
|
|
#endif |
|
|
|
|
for (int r=0; r<image.rows; ++r) { |
|
|
|
|
for (int c=0; c<image.cols; ++c) { |
|
|
|
|
if (mask(r,c) == empty) continue; |
|
|
|
|
const Color& a = image(r,c); |
|
|
|
|
Pixel8U& v = imagePatch.at<Pixel8U>(r,c); |
|
|
|
|
for (int p=0; p<3; ++p) |
|
|
|
|
v[p] = (uint8_t)CLAMP(ROUND2INT(a[p]*255.f), 0, 255); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
VERBOSE("LocalSeamLeveling3 stats: skipEmptyRect=%d, skipBadLabel=%d, skipBadProj=%d, skipNoSeam=%d", |
|
|
|
|
skipEmptyRect, skipBadLabel, skipBadProj, skipNoSeam); |
|
|
|
|
} |
|
|
|
|
void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& basename, bool bOriginFaceview, Scene *pScene) |
|
|
|
|
{ |
|
|
|
|
bool bUseExternalUV = false; |
|
|
|
|
@ -15587,6 +15858,62 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
@@ -15587,6 +15858,62 @@ 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()) { |
|
|
|
|
++labelOK; |
|
|
|
|
} else { |
|
|
|
|
++labelBad; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
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 个)
|
|
|
|
|
// ============================================================
|
|
|
|
|
if (seamVertices.empty()) { |
|
|
|
|
VERBOSE("seamVertices is empty! Calling CreateSeamVertices()..."); |
|
|
|
|
CreateSeamVertices(); // 如果之前没调过,这里补调
|
|
|
|
|
VERBOSE(" seamVertices created: %zu", seamVertices.size()); |
|
|
|
|
} else { |
|
|
|
|
VERBOSE("seamVertices already built: %zu vertices, ready for LSL3", seamVertices.size()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ★ 调用 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); |
|
|
|
|
|
|
|
|
|
const auto lsl3Start = std::chrono::high_resolution_clock::now(); |
|
|
|
|
LocalSeamLeveling4(); |
|
|
|
|
const auto lsl3End = std::chrono::high_resolution_clock::now(); |
|
|
|
|
const double lsl3Sec = std::chrono::duration<double>(lsl3End - lsl3Start).count(); |
|
|
|
|
|
|
|
|
|
VERBOSE("LocalSeamLeveling4 done in %.1f seconds.", lsl3Sec); |
|
|
|
|
|
|
|
|
|
for (int i = 0; i < std::min(3, (int)images.size()); ++i) { |
|
|
|
|
char dbgName[256]; |
|
|
|
|
sprintf(dbgName, "debug_image_%d_after_gsl4.png", i); |
|
|
|
|
@ -17340,6 +17667,7 @@ int MeshTexture::ComputeOptimalTextureSizeAdaptive(
@@ -17340,6 +17667,7 @@ int MeshTexture::ComputeOptimalTextureSizeAdaptive(
|
|
|
|
|
// 防止过小
|
|
|
|
|
if (textureSize < 1024) textureSize = 1024; |
|
|
|
|
|
|
|
|
|
textureSize = 8192; // ★ 临时强制,调试完删掉
|
|
|
|
|
DEBUG_EXTRA("Adaptive texture size: %d (totalPixels=%.0f, validFaces=%d, max=%u)", |
|
|
|
|
textureSize, totalPixels, validFaces, nTextureSizeMultiple); |
|
|
|
|
|
|
|
|
|
@ -17569,7 +17897,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
@@ -17569,7 +17897,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
|
|
|
|
|
|
|
|
|
// ========== 颜色一致性代价 ==========
|
|
|
|
|
{ |
|
|
|
|
const float lambda = 2.0f; |
|
|
|
|
const float lambda = 100.0f; |
|
|
|
|
// ========== 收集邻居颜色(修复版:1-ring + faceToView)==========
|
|
|
|
|
std::vector<cv::Vec3f> neighborColors; |
|
|
|
|
if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) { |
|
|
|
|
|