From 811f85bbbd38bf2a7b90c3d5d5c805593703c348 Mon Sep 17 00:00:00 2001 From: hesuicong Date: Wed, 15 Apr 2026 14:19:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=AD=E9=97=B4=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 1310 ++++++++++--------------------------- 1 file changed, 336 insertions(+), 974 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index a4992b6..fc53249 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -479,24 +479,34 @@ public: void LocalSeamLevelingExternalUV(); void GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& baseFileName, bool bOriginFaceview, Scene *pScene); - void GenerateTextureForUV(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& baseFileName, bool bOriginFaceview, Scene *pScene); + void GenerateTextureForUV(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& basename, bool bOriginFaceview, Scene *pScene); void GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& baseFileName); - bool TextureWithExistingUV(const IIndexArr& views, int nIgnoreMaskLabel, float fOutlierThreshold, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight); - Mesh::Image8U3Arr GenerateTextureAtlasFromUV(const LabelArr& faceLabels, const IIndexArr& views, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight); - Mesh::Image8U3Arr GenerateTextureAtlasFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, int textureSize, Pixel8U colEmpty, float fSharpnessWeight); - Mesh::Image8U3Arr GenerateTextureAtlasFromImages(const LabelArr& faceLabels, const IIndexArr& views, int textureSize, Pixel8U colEmpty, float fSharpnessWeight); - void ValidateGeneratedTexture(const Image8U3& texture, const std::string& name); - Pixel8U SampleFromExistingTextureAtBarycentric(FIndex faceID, const Point3f& barycentric, const Image8U3& existingTexture); + bool TextureWithExistingUV( + const IIndexArr& views, + int nIgnoreMaskLabel, + float fOutlierThreshold, + unsigned nTextureSizeMultiple, + Pixel8U colEmpty, + float fSharpnessWeight, + const Mesh::Image8U3Arr& existingTextures, // 添加已有纹理参数 + const Mesh::TexCoordArr& existingTexcoords, // 添加已有UV参数 + const Mesh::TexIndexArr& existingTexindices // 添加已有纹理索引参数 + ); + Mesh::Image8U3Arr GenerateTextureAtlasFromUV( + const Mesh::Image8U3Arr& sourceTextures, // 已有纹理数组 + const Mesh::TexCoordArr& sourceTexcoords, // 已有UV坐标 + const Mesh::TexIndexArr& sourceTexindices, // 已有纹理索引 + unsigned nTextureSizeMultiple, + Pixel8U colEmpty, + float fSharpnessWeight + ); Point2f ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage); Point2f ProjectPointRobust(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage, float searchRadius = 0.02f); - Mesh::Image8U3Arr SampleFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight); - Pixel8U SampleTextureBilinear(const Image8U3& texture, const Point2f& texCoord); - void ApplySharpening(Image8U3& texture, float fSharpnessWeight); bool ValidateProjection(const Vertex& worldPoint, const Image& sourceImage, Point2f imgPoint, float maxReprojectionError = 1.5f); Pixel8U SampleImageBilinear(const Image8U3& image, const Point2f& point); void ProjectFaceToTexture(FIndex faceID, IIndex viewID, const TexCoord* uv, Image8U3& texture); bool PointInTriangle(const Point2f& p, const Point2f& a, const Point2f& b, const Point2f& c, Point3f& bary); - int ComputeOptimalTextureSize(const Point2f& uvMin, const Point2f& uvMax, unsigned nTextureSizeMultiple); + int ComputeOptimalTextureSize(float uvWidth, float uvHeight, unsigned multiple); // Bruce //* template @@ -678,6 +688,7 @@ public: Mesh::TexCoordArr& faceTexcoords; // for each face, the texture-coordinates of the vertices Mesh::TexIndexArr& faceTexindices; // for each face, the texture-coordinates of the vertices Mesh::Image8U3Arr& texturesDiffuse; // texture containing the diffuse color + Mesh::Image8U3Arr texturesDiffuseTemp; // texture containing the diffuse color // constant the entire time Mesh::VertexArr& vertices; @@ -728,6 +739,7 @@ MeshTexture::MeshTexture(Scene& _scene, unsigned _nResolutionLevel, unsigned _nM faceTexcoords(_scene.mesh.faceTexcoords), faceTexindices(_scene.mesh.faceTexindices), texturesDiffuse(_scene.mesh.texturesDiffuse), + texturesDiffuseTemp(_scene.mesh.texturesDiffuse), vertices(_scene.mesh.vertices), faces(_scene.mesh.faces), images(_scene.images), @@ -8838,21 +8850,25 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel out.close(); } } + void MeshTexture::GenerateTextureForUV(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& basename, bool bOriginFaceview, Scene *pScene) { int border = 2; if (!bOriginFaceview) border = 4; + Mesh::TexCoordArr& faceTexcoords2 = scene.mesh.faceTexcoords; + Mesh::TexIndexArr& faceTexindices2 = scene.mesh.faceTexindices; - faceTexcoords.resize(faces.size()*3); - faceTexindices.resize(faces.size()); + faceTexcoords2.resize(faces.size()*3); + faceTexindices2.resize(faces.size()); #ifdef TEXOPT_USE_OPENMP // LOG_OUT() << "def TEXOPT_USE_OPENMP" << std::endl; const unsigned numPatches(texturePatches.size()-1); // ===== 修改:只在非外部UV模式下执行投影计算 ===== + { #pragma omp parallel for schedule(dynamic) for (int_t idx=0; idx<(int_t)numPatches; ++idx) { @@ -8865,7 +8881,7 @@ void MeshTexture::GenerateTextureForUV(bool bGlobalSeamLeveling, bool bLocalSeam AABB2f aabb(true); for (const FIndex idxFace: texturePatch.faces) { const Face& face = faces[idxFace]; - TexCoord* texcoords = faceTexcoords.data()+idxFace*3; + TexCoord* texcoords = faceTexcoords2.data()+idxFace*3; for (int i=0; i<3; ++i) { texcoords[i] = imageData.camera.ProjectPointP(vertices[face[i]]); ASSERT(imageData.image.isInsideWithBorder(texcoords[i], border)); @@ -8928,7 +8944,7 @@ void MeshTexture::GenerateTextureForUV(bool bGlobalSeamLeveling, bool bLocalSeam ASSERT(imageData.image.isInside(texturePatch.rect.br())); const TexCoord offset(texturePatch.rect.tl()); for (const FIndex idxFace: texturePatch.faces) { - TexCoord* texcoords = faceTexcoords.data()+idxFace*3; + TexCoord* texcoords = faceTexcoords2.data()+idxFace*3; for (int v=0; v<3; ++v) { texcoords[v] -= offset; @@ -8943,20 +8959,11 @@ void MeshTexture::GenerateTextureForUV(bool bGlobalSeamLeveling, bool bLocalSeam Point2f v1 = c - a; float denom = (v0.x * v0.x + v0.y * v0.y) * (v1.x * v1.x + v1.y * v1.y) - std::pow(v0.x * v1.x + v0.y * v1.y, 2); - - // 处理退化三角形情况(面积接近0) - const float epsilon = 1e-6f; - if (std::abs(denom) < epsilon) - { - // DEBUG_EXTRA("PointInTriangle - Degenerate triangle, denom=%.10f", denom); - } - else - { - // DEBUG_EXTRA("PointInTriangle Yes idxFace=%d", idxFace); - } } } + + } } } @@ -8967,48 +8974,50 @@ void MeshTexture::GenerateTextureForUV(bool bGlobalSeamLeveling, bool bLocalSeam const int sizePatch(border*2+1); texturePatch.rect = cv::Rect(0,0, sizePatch,sizePatch); for (const FIndex idxFace: texturePatch.faces) { - TexCoord* texcoords = faceTexcoords.data()+idxFace*3; + TexCoord* texcoords = faceTexcoords2.data()+idxFace*3; for (int i=0; i<3; ++i) texcoords[i] = TexCoord(0.5f, 0.5f); } } - LOG_OUT() << "GenerateTextureForUV First loop completed" << std::endl; + LOG_OUT() << "First loop completed" << std::endl; + /* TD_TIMER_STARTD(); // perform seam leveling if (texturePatches.size() > 2 && (bGlobalSeamLeveling || bLocalSeamLeveling)) { - - LOG_OUT() << "GenerateTextureForUV 2" << std::endl; // create seam vertices and edges CreateSeamVertices(); - LOG_OUT() << "GenerateTextureForUV 3" << std::endl; // perform global seam leveling if (bGlobalSeamLeveling) { - // TD_TIMER_STARTD(); - { - // GlobalSeamLeveling3(); + TD_TIMER_STARTD(); + if (bUseExternalUV) { + // 外部UV数据可能需要更温和的接缝处理 + GlobalSeamLevelingExternalUV(); + } else { + GlobalSeamLeveling3(); } - // DEBUG_EXTRA("\tglobal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); + DEBUG_EXTRA("\tglobal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); } - LOG_OUT() << "GenerateTextureForUV 4" << std::endl; // perform local seam leveling - // if (bLocalSeamLeveling) - { - // TD_TIMER_STARTD(); + if (bLocalSeamLeveling) { + TD_TIMER_STARTD(); // LocalSeamLeveling(); - { - // LocalSeamLeveling3(); + if (bUseExternalUV) { + // 外部UV数据可能需要不同的局部接缝处理 + LocalSeamLevelingExternalUV(); + } else { + LocalSeamLeveling3(); } - // DEBUG_EXTRA("\tlocal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); + DEBUG_EXTRA("\tlocal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); } } - // DEBUG_EXTRA("seam (%s)", TD_TIMER_GET_FMT().c_str()); + DEBUG_EXTRA("seam (%s)", TD_TIMER_GET_FMT().c_str()); + */ - LOG_OUT() << "GenerateTextureForUV 5" << std::endl; { // merge texture patches with overlapping rectangles for (unsigned i=0; i 0) { constexpr double sigma = 1.5; - for (auto &textureDiffuse: texturesDiffuse) { + for (auto &textureDiffuse: texturesDiffuseTemp) { Image8U3 blurryTextureDiffuse; cv::GaussianBlur(textureDiffuse, blurryTextureDiffuse, cv::Size(), sigma); cv::addWeighted(textureDiffuse, 1+fSharpnessWeight, blurryTextureDiffuse, -fSharpnessWeight, 0, textureDiffuse); @@ -9905,205 +9907,58 @@ void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeve #include - -void DebugCheckTexture(const cv::Mat& texture, const std::string& name) -{ - if (texture.empty()) { - DEBUG_EXTRA("%s: 纹理为空!", name.c_str()); - return; - } - - DEBUG_EXTRA("%s 检查:", name.c_str()); - DEBUG_EXTRA(" - 尺寸: %dx%d", texture.rows, texture.cols); - DEBUG_EXTRA(" - 通道数: %d", texture.channels()); - DEBUG_EXTRA(" - 类型: %d (CV_8UC3=%d)", texture.type(), CV_8UC3); - DEBUG_EXTRA(" - 数据类型: %s", - texture.depth() == CV_8U ? "CV_8U" : - texture.depth() == CV_16U ? "CV_16U" : - texture.depth() == CV_32F ? "CV_32F" : "其他"); - - // 检查前5x5区域的像素值 - int checkSize = std::min(5, std::min(texture.rows, texture.cols)); - DEBUG_EXTRA(" - 前%d个像素值:", checkSize * checkSize); - - for (int y = 0; y < checkSize; ++y) { - std::string rowStr = " "; - for (int x = 0; x < checkSize; ++x) { - if (texture.channels() == 3) { - cv::Vec3b pixel = texture.at(y, x); - rowStr += cv::format("(%3d,%3d,%3d) ", pixel[0], pixel[1], pixel[2]); - } else if (texture.channels() == 1) { - uint8_t pixel = texture.at(y, x); - rowStr += cv::format("%3d ", pixel); - } - } - DEBUG_EXTRA("%s", rowStr.c_str()); - } - - // 计算统计信息 - double minVal, maxVal; - cv::minMaxLoc(texture, &minVal, &maxVal); - DEBUG_EXTRA(" - 像素值范围: %.0f ~ %.0f", minVal, maxVal); - - cv::Scalar mean, stddev; - cv::meanStdDev(texture, mean, stddev); - DEBUG_EXTRA(" - 平均值: (%.1f,%.1f,%.1f,%.1f)", mean[0], mean[1], mean[2], mean[3]); - DEBUG_EXTRA(" - 标准差: (%.1f,%.1f,%.1f,%.1f)", stddev[0], stddev[1], stddev[2], stddev[3]); - - // 保存用于验证 - std::string filename = "debug_" + name + ".png"; - if (cv::imwrite(filename, texture)) { - DEBUG_EXTRA(" - 已保存到: %s", filename.c_str()); - } else { - DEBUG_EXTRA(" - 保存失败!"); - } -} - // 保存生成的纹理图集 -bool SaveGeneratedTextures(const Mesh::Image8U3Arr& textures, const std::string& outputDir) -{ - DEBUG_EXTRA("===== SaveGeneratedTextures 开始 ====="); - DEBUG_EXTRA("输出目录: %s", outputDir.c_str()); - DEBUG_EXTRA("纹理数量: %zu", textures.size()); +bool SaveGeneratedTextures(const Mesh::Image8U3Arr& generatedTextures, const std::string& outputDir) { + if (generatedTextures.empty()) { + DEBUG_EXTRA("错误: 没有纹理可保存"); + return false; + } + // 确保输出目录存在 + #ifdef _WIN32 + _mkdir(outputDir.c_str()); + #else + mkdir(outputDir.c_str(), 0755); + #endif - for (size_t i = 0; i < textures.size(); ++i) { - const cv::Mat& texture = textures[i]; - - if (texture.empty()) { - DEBUG_EXTRA("纹理 %zu 为空,跳过保存", i); + // 保存所有纹理 + for (size_t i = 0; i < generatedTextures.size(); ++i) { + if (generatedTextures[i].empty()) { + DEBUG_EXTRA("警告: 纹理 %zu 为空,跳过保存", i); continue; } + // 生成文件名 std::string filename = outputDir + "/texture_" + std::to_string(i) + ".png"; - DEBUG_EXTRA("保存纹理 %zu: 尺寸=%dx%d, 通道数=%d, 保存到: %s", - i, texture.rows, texture.cols, texture.channels(), filename.c_str()); - - // 检查纹理内容 - DebugCheckTexture(texture, "before_save_" + std::to_string(i)); - // 保存纹理 - bool success = cv::imwrite(filename, texture); - - if (success) { - DEBUG_EXTRA("纹理 %zu 保存成功", i); - - // 验证保存的文件 - cv::Mat loaded = cv::imread(filename); - if (!loaded.empty()) { - DEBUG_EXTRA("重新加载验证: 尺寸=%dx%d, 通道数=%d", - loaded.rows, loaded.cols, loaded.channels()); - } else { - DEBUG_EXTRA("警告: 保存的文件无法重新加载!"); - } + // 使用OpenCV保存图像 + if (cv::imwrite(filename, generatedTextures[i])) { + DEBUG_EXTRA("成功保存纹理: %s (尺寸: %dx%d)", + filename.c_str(), + generatedTextures[i].cols, + generatedTextures[i].rows); } else { - DEBUG_EXTRA("错误: 纹理 %zu 保存失败!", i); - - // 尝试使用不同格式 - std::string jpgFilename = outputDir + "/texture_" + std::to_string(i) + ".jpg"; - if (cv::imwrite(jpgFilename, texture)) { - DEBUG_EXTRA("使用JPG格式保存成功: %s", jpgFilename.c_str()); - success = true; - } + DEBUG_EXTRA("错误: 无法保存纹理到 %s", filename.c_str()); + return false; } } - DEBUG_EXTRA("===== SaveGeneratedTextures 结束 ====="); return true; } -//* -bool MeshTexture::TextureWithExistingUV(const IIndexArr& views, int nIgnoreMaskLabel, float fOutlierThreshold, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) -{ - DEBUG_EXTRA("TextureWithExistingUV 1"); - TD_TIMER_START(); - - // 1. 验证输入 - if (scene.mesh.faceTexcoords.empty()) { - VERBOSE("error: mesh does not contain UV coordinates"); - return false; - } - - if (scene.mesh.faceTexcoords.size() != scene.mesh.faces.size() * 3) { - VERBOSE("error: UV coordinates count does not match face count, %d, %d", scene.mesh.faceTexcoords.size(), scene.mesh.faces.size() * 3); - return false; - } - - DEBUG_EXTRA("TextureWithExistingUV 2"); - // 2. 为每个面选择最佳视图 - FaceDataViewArr facesDatas; - if (!ListCameraFaces(facesDatas, fOutlierThreshold, nIgnoreMaskLabel, views, false)) { - return false; - } - - DEBUG_EXTRA("TextureWithExistingUV 3 faceSize=%d", scene.mesh.faces.size()); - // 3. 直接使用现有UV坐标,跳过视图选择优化 - // 为每个面分配最佳视图 - LabelArr faceLabels(scene.mesh.faces.size()); - FOREACH(idxFace, scene.mesh.faces) { - const FaceDataArr& faceDatas = facesDatas[idxFace]; - if (faceDatas.empty()) { - // DEBUG_EXTRA("TextureWithExistingUV %d", idxFace); - faceLabels[idxFace] = 0; // 无视图可用 - continue; - } - - // DEBUG_EXTRA("TextureWithExistingUV 4"); - // 选择质量最高的视图 - float bestQuality = -1; - IIndex bestView = NO_ID; - for (const FaceData& data : faceDatas) { - if (data.quality > bestQuality && !data.bInvalidFacesRelative) { - bestQuality = data.quality; - bestView = data.idxView; - } - } - - faceLabels[idxFace] = (bestView != NO_ID) ? (bestView + 1) : 0; - } - - // 4. 生成纹理图集 - Mesh::Image8U3Arr generatedTextures = GenerateTextureAtlasFromUV(faceLabels, views, nTextureSizeMultiple, colEmpty, fSharpnessWeight); - - DEBUG_EXTRA("纹理生成完成,返回了 %zu 个纹理", generatedTextures.size()); - - if (!generatedTextures.empty()) { - for (size_t i = 0; i < generatedTextures.size(); ++i) { - DebugCheckTexture(generatedTextures[i], "generated_texture_" + std::to_string(i)); - } - - scene.mesh.texturesDiffuse = std::move(generatedTextures); - DEBUG_EXTRA("已将纹理赋值给 scene.mesh.texturesDiffuse"); - } else { - DEBUG_EXTRA("警告: 生成的纹理数组为空!"); - } - - if (!generatedTextures.empty()) { - scene.mesh.texturesDiffuse = std::move(generatedTextures); - - // 同时设置面的纹理索引 - scene.mesh.faceTexindices.resize(scene.mesh.faces.size()); - - DEBUG_EXTRA("成功生成 %zu 个纹理,已赋值给 mesh", scene.mesh.texturesDiffuse.size()); - - // 🆕 保存纹理到文件 - std::string outputDir = "texture_output"; - if (SaveGeneratedTextures(scene.mesh.texturesDiffuse, outputDir)) { - DEBUG_EXTRA("纹理已成功保存到目录: %s", outputDir.c_str()); - } - - return true; - } - - DEBUG_EXTRA("纹理生成失败"); - return false; -} -//*/ - -/* -bool MeshTexture::TextureWithExistingUV(const IIndexArr& views, int nIgnoreMaskLabel, float fOutlierThreshold, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) +bool MeshTexture::TextureWithExistingUV( + const IIndexArr& views, + int nIgnoreMaskLabel, + float fOutlierThreshold, + unsigned nTextureSizeMultiple, + Pixel8U colEmpty, + float fSharpnessWeight, + const Mesh::Image8U3Arr& existingTextures, // 添加已有纹理参数 + const Mesh::TexCoordArr& existingTexcoords, // 添加已有UV参数 + const Mesh::TexIndexArr& existingTexindices // 添加已有纹理索引参数 +) { - DEBUG_EXTRA("TextureWithExistingUV 1 - 从已生成纹理采样"); + DEBUG_EXTRA("TextureWithExistingUV - Using existing texture data"); TD_TIMER_START(); // 1. 验证输入 @@ -10112,78 +9967,52 @@ bool MeshTexture::TextureWithExistingUV(const IIndexArr& views, int nIgnoreMaskL return false; } - if (scene.mesh.faceTexcoords.size() != scene.mesh.faces.size() * 3) { - VERBOSE("error: UV coordinates count does not match face count, %d, %d", scene.mesh.faceTexcoords.size(), scene.mesh.faces.size() * 3); + if (existingTextures.empty() || existingTexcoords.empty()) { + VERBOSE("error: no existing texture data provided"); return false; } - // 检查是否已经有生成的纹理 - if (!scene.mesh.texturesDiffuse.empty() && !scene.mesh.faceTexindices.empty()) { - DEBUG_EXTRA("TextureWithExistingUV: 使用已生成的纹理,数量: %zu", scene.mesh.texturesDiffuse.size()); - return true; // 已经通过 GenerateTextureForUV 生成了纹理,直接返回成功 - } - - // 如果没有已生成的纹理,重新从图像投影生成 - DEBUG_EXTRA("TextureWithExistingUV 2 - 从图像重新生成纹理"); - - // 2. 为每个面选择最佳视图 - FaceDataViewArr facesDatas; - if (!ListCameraFaces(facesDatas, fOutlierThreshold, nIgnoreMaskLabel, views, false)) { + // 验证UV坐标数量匹配 + if (existingTexcoords.size() != scene.mesh.faceTexcoords.size()) { + VERBOSE("error: UV coordinate count mismatch: existing=%zu, mesh=%zu", + existingTexcoords.size(), scene.mesh.faceTexcoords.size()); return false; } - DEBUG_EXTRA("TextureWithExistingUV 3 faceSize=%d", scene.mesh.faces.size()); - - // 3. 为每个面分配最佳视图 - LabelArr faceLabels(scene.mesh.faces.size()); - FOREACH(idxFace, scene.mesh.faces) { - const FaceDataArr& faceDatas = facesDatas[idxFace]; - if (faceDatas.empty()) { - faceLabels[idxFace] = 0; // 无视图可用 - continue; - } - - // 选择质量最高的视图 - float bestQuality = -1; - IIndex bestView = NO_ID; - for (const FaceData& data : faceDatas) { - if (data.quality > bestQuality && !data.bInvalidFacesRelative) { - bestQuality = data.quality; - bestView = data.idxView; - } - } - - faceLabels[idxFace] = (bestView != NO_ID) ? (bestView + 1) : 0; - } + DEBUG_EXTRA("Processing %zu faces with existing texture data", scene.mesh.faces.size()); - // 4. 从现有纹理采样生成纹理图集 - Mesh::Image8U3Arr generatedTextures = SampleFromExistingTextures(faceLabels, views, nTextureSizeMultiple, colEmpty, fSharpnessWeight); + // 2. 生成纹理图集(从已有纹理采样,但使用模型原始UV) + Mesh::Image8U3Arr generatedTextures = GenerateTextureAtlasFromUV( + existingTextures, + existingTexcoords, + existingTexindices, + nTextureSizeMultiple, + colEmpty, + fSharpnessWeight + ); if (!generatedTextures.empty()) { scene.mesh.texturesDiffuse = std::move(generatedTextures); - // 同时设置面的纹理索引 - scene.mesh.faceTexindices.resize(scene.mesh.faces.size()); - for (size_t i = 0; i < faceLabels.size(); ++i) { - // 假设所有面都在第一个纹理中 - scene.mesh.faceTexindices[i] = 0; + // 设置面的纹理索引 + if (scene.mesh.texturesDiffuse.size() > 1) { + scene.mesh.faceTexindices.resize(scene.mesh.faces.size()); + for (size_t i = 0; i < scene.mesh.faces.size(); ++i) { + scene.mesh.faceTexindices[i] = 0; // 所有面使用第一个纹理 + } + } else { + scene.mesh.faceTexindices.Release(); } - - DEBUG_EXTRA("成功生成 %zu 个纹理,已赋值给 mesh", scene.mesh.texturesDiffuse.size()); - // 🆕 保存纹理到文件 - std::string outputDir = "texture_output"; - if (SaveGeneratedTextures(scene.mesh.texturesDiffuse, outputDir)) { - DEBUG_EXTRA("纹理已成功保存到目录: %s", outputDir.c_str()); - } + DEBUG_EXTRA("Generated %zu textures from existing data", + scene.mesh.texturesDiffuse.size()); return true; } - DEBUG_EXTRA("纹理生成失败"); + DEBUG_EXTRA("Texture generation failed"); return false; } -*/ Point2f MeshTexture::ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage) { Point2f imgPoint = camera.ProjectPointP(worldPoint); @@ -10565,719 +10394,225 @@ void GenerateDistanceFieldFast(cv::Mat& distanceField, cv::Mat& nearestFaceIdx, } } -void FillTextureGaps(Image8U3& textureAtlas, const Mesh::TexCoordArr& faceTexcoords, - FIndex nFaces, const MeshTexture::LabelArr& faceLabels, - int textureSize, Pixel8U colEmpty) +void FillTextureGaps( + Image8U3& texture, + const Mesh::TexCoordArr& uvs, + FIndex faceCount, + int textureSize, + Pixel8U colEmpty +) { - DEBUG_EXTRA("开始填充纹理间隙..."); - - // 创建距离场 - cv::Mat distanceField(textureSize, textureSize, CV_32FC1, cv::Scalar(std::numeric_limits::max())); - cv::Mat nearestPixelIdx(textureSize, textureSize, CV_32SC1, cv::Scalar(-1)); + DEBUG_EXTRA("Filling texture gaps for %dx%d texture", texture.cols, texture.rows); - // 1. 初始化种子点(已有纹理的像素) - for (int y = 0; y < textureSize; ++y) { - for (int x = 0; x < textureSize; ++x) { - const Pixel8U& pixel = textureAtlas(y, x); - if (pixel[0] != colEmpty[0] || pixel[1] != colEmpty[1] || pixel[2] != colEmpty[2]) { - distanceField.at(y, x) = 0.0f; - nearestPixelIdx.at(y, x) = y * textureSize + x; - } - } - } - - // 2. 跳点传播算法 - for (int step = textureSize / 2; step >= 1; step /= 2) { - for (int y = 0; y < textureSize; ++y) { - for (int x = 0; x < textureSize; ++x) { - float minDist = distanceField.at(y, x); - int bestIdx = nearestPixelIdx.at(y, x); - - for (int dy = -1; dy <= 1; dy += 2) { - for (int dx = -1; dx <= 1; dx += 2) { - int nx = x + dx * step; - int ny = y + dy * step; - - if (nx >= 0 && nx < textureSize && ny >= 0 && ny < textureSize) { - float neighborDist = distanceField.at(ny, nx); - float dist = neighborDist + sqrtf((dx*dx + dy*dy) * step * step); - - if (dist < minDist && nearestPixelIdx.at(ny, nx) != -1) { - minDist = dist; - bestIdx = nearestPixelIdx.at(ny, nx); - } - } - } - } - - if (bestIdx != -1 && minDist < distanceField.at(y, x)) { - distanceField.at(y, x) = minDist; - nearestPixelIdx.at(y, x) = bestIdx; - } - } - } - } - - // 3. 填充空白区域 - const float maxFillDistance = 5.0f; - int filledCount = 0; - - for (int y = 0; y < textureSize; ++y) { - for (int x = 0; x < textureSize; ++x) { - Pixel8U& pixel = textureAtlas(y, x); - if (pixel[0] == colEmpty[0] && pixel[1] == colEmpty[1] && pixel[2] == colEmpty[2]) { - int nearestIdx = nearestPixelIdx.at(y, x); - float dist = distanceField.at(y, x); - - if (nearestIdx != -1 && dist <= maxFillDistance) { - int nx = nearestIdx % textureSize; - int ny = nearestIdx / textureSize; - pixel = textureAtlas(ny, nx); - filledCount++; - } - } - } - } - - DEBUG_EXTRA("快速填充完成,填充了 %d 个像素", filledCount); - - // // 4. 优化版图像修复 - // cv::Mat mask = cv::Mat::zeros(textureSize, textureSize, CV_8UC1); - // cv::Mat textureMat = textureAtlas; - - // // 使用OpenCV向量化操作 - // unsigned char r = colEmpty[0]; - // unsigned char g = colEmpty[1]; - // unsigned char b = colEmpty[2]; - - // #pragma omp parallel for - // for (int y = 0; y < textureSize; ++y) { - // unsigned char* maskRow = mask.ptr(y); - // const unsigned char* textureRow = textureMat.ptr(y); - - // for (int x = 0; x < textureSize; ++x) { - // int idx = x * 3; - // if (textureRow[idx] == r && - // textureRow[idx + 1] == g && - // textureRow[idx + 2] == b) { - // maskRow[x] = 255; - // } - // } - // } - - // if (cv::countNonZero(mask) > 0) { - // // 优化inpaint参数 - // int inpaintRadius = 2; // 减少修复半径 - // cv::Mat inpaintResult; - - // // 如果空白区域很少,使用更快的算法 - // double blankRatio = cv::countNonZero(mask) / (double)(textureSize * textureSize); - // if (blankRatio < 0.01) { // 空白区域少于1% - // // 使用快速修复 - // cv::dilate(textureMat, inpaintResult, cv::Mat()); - // inpaintResult.copyTo(textureAtlas, mask); - // } else { - // // 使用inpaint - // cv::inpaint(textureMat, mask, inpaintResult, inpaintRadius, cv::INPAINT_TELEA); - // inpaintResult.copyTo(textureAtlas); - // } - // } + // 创建掩码图像 + cv::Mat mask = cv::Mat::zeros(texture.rows, texture.cols, CV_8UC1); - DEBUG_EXTRA("纹理间隙填充完成"); -} - -// 在 GenerateTextureAtlasFromUV 函数中添加 -Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLabels, const IIndexArr& views, - unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) -{ - DEBUG_EXTRA("===== GenerateTextureAtlasFromUV 被调用 ====="); - DEBUG_EXTRA("输入参数: nTextureSizeMultiple=%u", nTextureSizeMultiple); - - // 1. 分析整个模型的UV布局 - AABB2f uvBounds(true); - FOREACH(i, scene.mesh.faceTexcoords) { - const TexCoord& uv = scene.mesh.faceTexcoords[i]; - uvBounds.InsertFull(uv); - } - - DEBUG_EXTRA("UV范围: 最小=(%.3f,%.3f), 最大=(%.3f,%.3f)", - uvBounds.ptMin.x(), uvBounds.ptMin.y(), - uvBounds.ptMax.x(), uvBounds.ptMax.y()); - - // 2. 根据UV范围确定纹理图集尺寸 - const int textureSize = ComputeOptimalTextureSize(uvBounds.ptMin, uvBounds.ptMax, nTextureSizeMultiple); - DEBUG_EXTRA("计算得到的纹理尺寸: %d", textureSize); - - // 3. 检查是否有已生成的纹理可以使用 - bool useExistingTextures = !scene.mesh.texturesDiffuse.empty() && - !scene.mesh.faceTexindices.empty(); - - DEBUG_EXTRA("纹理检查: 已有纹理数量=%zu, 使用现有纹理=%s", - scene.mesh.texturesDiffuse.size(), - useExistingTextures ? "是" : "否"); - - if (useExistingTextures) { - DEBUG_EXTRA("调用 GenerateTextureAtlasFromExistingTextures"); - auto result = GenerateTextureAtlasFromExistingTextures(faceLabels, views, textureSize, colEmpty, fSharpnessWeight); - DEBUG_EXTRA("GenerateTextureAtlasFromExistingTextures 返回 %zu 个纹理", result.size()); - return result; - } else { - DEBUG_EXTRA("调用 GenerateTextureAtlasFromImages"); - auto result = GenerateTextureAtlasFromImages(faceLabels, views, textureSize, colEmpty, fSharpnessWeight); - DEBUG_EXTRA("GenerateTextureAtlasFromImages 返回 %zu 个纹理", result.size()); - return result; - } -} - -Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, - int textureSize, Pixel8U colEmpty, float fSharpnessWeight) -{ - DEBUG_EXTRA("===== GenerateTextureAtlasFromExistingTextures 开始 ====="); - DEBUG_EXTRA("输入参数: textureSize=%d", textureSize); - - // 验证纹理尺寸 - if (textureSize <= 0) { - DEBUG_EXTRA("纹理尺寸无效,使用默认值1024"); - textureSize = 1024; - } - - // 创建纹理数组 - Mesh::Image8U3Arr textures; - - // 创建纹理图集 - try { - Mesh::Image8U3Arr textures; - cv::Mat textureMat(textureSize, textureSize, CV_8UC3); - Image8U3& textureAtlas = textures.emplace_back(std::move(textureMat)); - - DEBUG_EXTRA("纹理对象创建: 地址=%p, 尺寸=%dx%d, 通道数=%d, 类型=%d", - &textureAtlas, textureAtlas.rows, textureAtlas.cols, - textureAtlas.channels(), textureAtlas.type()); - - if (textureAtlas.empty()) { - DEBUG_EXTRA("错误: 纹理对象为空!"); - return textures; - } - - // 使用不同颜色填充纹理以便调试 - DEBUG_EXTRA("开始填充测试纹理..."); - - // 方法1: 使用纯色填充整个纹理 - textureAtlas.setTo(cv::Scalar(255, 0, 0)); // 蓝色 (OpenCV是BGR顺序) - DEBUG_EXTRA("纹理已填充为蓝色"); - - // 方法2: 绘制一个绿色的矩形 - int rectSize = textureSize / 2; - cv::Rect centerRect(textureSize/4, textureSize/4, rectSize, rectSize); - cv::rectangle(textureAtlas, centerRect, cv::Scalar(0, 255, 0), -1); // 绿色 - DEBUG_EXTRA("在中心绘制了绿色矩形: x=%d, y=%d, width=%d, height=%d", - centerRect.x, centerRect.y, centerRect.width, centerRect.height); - - // 方法3: 绘制一个红色的圆形 - cv::Point center(textureSize/2, textureSize/2); - int radius = textureSize / 8; - cv::circle(textureAtlas, center, radius, cv::Scalar(0, 0, 255), -1); // 红色 - DEBUG_EXTRA("在中心绘制了红色圆形: 中心=(%d,%d), 半径=%d", - center.x, center.y, radius); - - // 方法4: 绘制一些文字 - std::string text = "Test Texture"; - int fontFace = cv::FONT_HERSHEY_SIMPLEX; - double fontScale = 2.0; - int thickness = 3; - cv::Size textSize = cv::getTextSize(text, fontFace, fontScale, thickness, nullptr); - - cv::Point textOrg((textureSize - textSize.width) / 2, textureSize / 2 + textSize.height); - cv::putText(textureAtlas, text, textOrg, fontFace, fontScale, cv::Scalar(255, 255, 255), thickness); - DEBUG_EXTRA("添加了文字: '%s' 位置=(%d,%d)", text.c_str(), textOrg.x, textOrg.y); - - // 方法5: 绘制一个棋盘图案以便确认像素值 - int squareSize = textureSize / 8; - for (int y = 0; y < textureSize; y += squareSize) { - for (int x = 0; x < textureSize; x += squareSize) { - if ((x / squareSize + y / squareSize) % 2 == 0) { - cv::Rect square(x, y, squareSize, squareSize); - cv::rectangle(textureAtlas, square, cv::Scalar(200, 200, 200), -1); - } - } - } - - // 检查纹理的实际像素值 - int nonBluePixels = 0; - for (int y = 0; y < textureAtlas.rows; ++y) { - for (int x = 0; x < textureAtlas.cols; ++x) { - const cv::Vec3b& pixel = textureAtlas.at(y, x); - if (!(pixel[0] == 255 && pixel[1] == 0 && pixel[2] == 0)) { // 如果不是纯蓝色 - nonBluePixels++; - } - } - } - - DEBUG_EXTRA("纹理像素统计: 总像素=%d, 非蓝色像素=%d, 填充率=%.2f%%", - textureAtlas.rows * textureAtlas.cols, - nonBluePixels, - (nonBluePixels * 100.0) / (textureAtlas.rows * textureAtlas.cols)); - - // 保存纹理到文件 - std::string debugFilename = "debug_test_texture_" + std::to_string(textureSize) + ".png"; - bool saveResult = cv::imwrite(debugFilename, textureAtlas); - - if (saveResult) { - DEBUG_EXTRA("测试纹理已保存到: %s", debugFilename.c_str()); - - // 验证保存的纹理 - cv::Mat loaded = cv::imread(debugFilename); - if (!loaded.empty()) { - DEBUG_EXTRA("重新加载纹理成功: 尺寸=%dx%d, 通道数=%d", - loaded.rows, loaded.cols, loaded.channels()); - } else { - DEBUG_EXTRA("警告: 保存的纹理无法重新加载!"); - } - } else { - DEBUG_EXTRA("错误: 无法保存测试纹理到文件!"); - - // 尝试不同的保存路径 - debugFilename = "/tmp/debug_test_texture.png"; - saveResult = cv::imwrite(debugFilename, textureAtlas); - if (saveResult) { - DEBUG_EXTRA("测试纹理已保存到: %s", debugFilename.c_str()); - } - } - - // 返回纹理 - DEBUG_EXTRA("纹理生成完成,返回 %zu 个纹理", textures.size()); - return textures; - - } catch (const cv::Exception& e) { - DEBUG_EXTRA("OpenCV异常: %s", e.what()); - return textures; - } catch (const std::exception& e) { - DEBUG_EXTRA("标准异常: %s", e.what()); - return textures; - } -} - -Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromImages(const LabelArr& faceLabels, const IIndexArr& views, - int textureSize, Pixel8U colEmpty, float fSharpnessWeight) -{ - DEBUG_EXTRA("===== 开始从图像生成纹理图集 ====="); - DEBUG_EXTRA("纹理尺寸: %dx%d", textureSize, textureSize); - - Mesh::Image8U3Arr textures; - - // 创建纹理 - textures.push_back(Image8U3()); - Image8U3& textureAtlas = textures.back(); - textureAtlas.create(textureSize, textureSize); - - if (textureAtlas.empty()) { - DEBUG_EXTRA("错误: 无法创建纹理!"); - return textures; - } - - // 设置背景色 - textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); - - // 计算UV坐标范围 - AABB2f globalUVBounds(true); - FOREACH(i, scene.mesh.faceTexcoords) { - const TexCoord& uv = scene.mesh.faceTexcoords[i]; - globalUVBounds.InsertFull(uv); - } - - DEBUG_EXTRA("全局UV范围: 最小=(%.3f,%.3f), 最大=(%.3f,%.3f)", - globalUVBounds.ptMin.x(), globalUVBounds.ptMin.y(), - globalUVBounds.ptMax.x(), globalUVBounds.ptMax.y()); - - float uvWidth = globalUVBounds.ptMax.x() - globalUVBounds.ptMin.x(); - float uvHeight = globalUVBounds.ptMax.y() - globalUVBounds.ptMin.y(); - - DEBUG_EXTRA("UV尺寸: 宽度=%.3f, 高度=%.3f", uvWidth, uvHeight); - - // 检查是否需要归一化 - bool needNormalize = (globalUVBounds.ptMin.x() < 0.0f || globalUVBounds.ptMin.y() < 0.0f || - globalUVBounds.ptMax.x() > 1.0f || globalUVBounds.ptMax.y() > 1.0f); - - if (needNormalize) { - DEBUG_EXTRA("警告: UV坐标不在[0,1]范围内,需要进行归一化处理"); - } - - int totalFaces = (int)scene.mesh.faces.size(); - int processedFaces = 0; - int totalPixelsFilled = 0; - - DEBUG_EXTRA("开始处理 %d 个面片", totalFaces); - - // 为每个面片生成纹理 - #ifdef _USE_OPENMP - #pragma omp parallel for schedule(dynamic) reduction(+:processedFaces,totalPixelsFilled) - for (int idxFace = 0; idxFace < totalFaces; ++idxFace) { - #else - FOREACH(idxFace, scene.mesh.faces) { - #endif - const FIndex faceID = (FIndex)idxFace; - const Label label = faceLabels[faceID]; - - if (label == 0) { - // 跳过无视图的面片 - continue; - } - - const IIndex idxView = label - 1; - if (idxView >= images.size()) { - continue; - } - - // 获取面的UV坐标 - if (faceID * 3 + 2 >= scene.mesh.faceTexcoords.size()) { - DEBUG_EXTRA("警告: 面片 %d 缺少UV坐标", faceID); - continue; - } + // 标记有纹理的像素 + for (FIndex fid = 0; fid < faceCount; ++fid) { + const TexCoord* uv = &uvs[fid * 3]; - const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; - const Face& face = scene.mesh.faces[faceID]; - const Image& sourceImage = images[idxView]; - - // 将UV坐标转换到纹理空间 - TexCoord texCoords[3]; + // 计算面的UV边界 + AABB2f faceBounds(true); for (int i = 0; i < 3; ++i) { - // 计算归一化的UV坐标 - float u = uvCoords[i].x; - float v = uvCoords[i].y; - - // 如果UV坐标不在[0,1]范围内,进行归一化 - if (needNormalize) { - u = (u - globalUVBounds.ptMin.x()) / uvWidth; - v = (v - globalUVBounds.ptMin.y()) / uvHeight; - - // 确保在[0,1]范围内 - u = std::max(0.0f, std::min(1.0f, u)); - v = std::max(0.0f, std::min(1.0f, v)); - } - - // 转换到像素坐标 - texCoords[i].x = u * (textureSize - 1); - texCoords[i].y = v * (textureSize - 1); - - // 调试:输出前几个面片的UV坐标 - if (processedFaces < 5 && i == 0) { - DEBUG_EXTRA("面片 %d: 原始UV=(%.6f,%.6f), 归一化UV=(%.6f,%.6f), 像素坐标=(%.1f,%.1f)", - faceID, uvCoords[i].x, uvCoords[i].y, u, v, - texCoords[i].x, texCoords[i].y); - } - } - - // 计算三角形在纹理空间中的边界框 - float minX = std::min(std::min(texCoords[0].x, texCoords[1].x), texCoords[2].x); - float minY = std::min(std::min(texCoords[0].y, texCoords[1].y), texCoords[2].y); - float maxX = std::max(std::max(texCoords[0].x, texCoords[1].x), texCoords[2].x); - float maxY = std::max(std::max(texCoords[0].y, texCoords[1].y), texCoords[2].y); - - // 转换到像素坐标,并扩展1个像素确保覆盖边缘 - int startX = std::max(0, (int)std::floor(minX) - 1); - int startY = std::max(0, (int)std::floor(minY) - 1); - int endX = std::min(textureSize - 1, (int)std::ceil(maxX) + 1); - int endY = std::min(textureSize - 1, (int)std::ceil(maxY) + 1); - - // 调试:输出前几个面片的边界框 - if (processedFaces < 5) { - DEBUG_EXTRA("面片 %d 纹理边界框: [%d,%d]-[%d,%d]", faceID, startX, startY, endX, endY); + Point2f pixelPos(uv[i].x * textureSize, uv[i].y * textureSize); + faceBounds.InsertFull(pixelPos); } - int facePixelsFilled = 0; + // 填充三角形区域 + const int startX = std::max(0, (int)faceBounds.ptMin.x()); + const int startY = std::max(0, (int)faceBounds.ptMin.y()); + const int endX = std::min(texture.cols - 1, (int)faceBounds.ptMax.x()); + const int endY = std::min(texture.rows - 1, (int)faceBounds.ptMax.y()); - // 对边界框内的每个像素进行处理 for (int y = startY; y <= endY; ++y) { for (int x = startX; x <= endX; ++x) { - const Point2f texPoint(x, y); + Point2f texPos((float)x / textureSize, (float)y / textureSize); - // 计算重心坐标 + // 检查是否在UV三角形内 Point3f barycentric; - if (PointInTriangle(texPoint, texCoords[0], texCoords[1], texCoords[2], barycentric)) { - // 计算3D空间中的对应点 - const Vertex worldPoint = - vertices[face[0]] * barycentric.x + - vertices[face[1]] * barycentric.y + - vertices[face[2]] * barycentric.z; - - // 将3D点投影到源图像 - Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage); - - // 验证投影有效性 - if (sourceImage.image.isInside(imgPoint) && - sourceImage.camera.IsInFront(worldPoint)) { - - // 从源图像采样颜色 - Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint); - - // 写入纹理图集 - textureAtlas(y, x) = sampledColor; - facePixelsFilled++; - } + if (PointInTriangle(texPos, uv[0], uv[1], uv[2], barycentric)) { + mask.at(y, x) = 255; } } } - - // 更新统计 - if (facePixelsFilled > 0) { - totalPixelsFilled += facePixelsFilled; - processedFaces++; - } - - // 显示进度 - if ((processedFaces % 1000) == 0) { - DEBUG_EXTRA("处理进度: %d/%d 个面片, 填充了 %d 个像素", - processedFaces, totalFaces, totalPixelsFilled); - } } - DEBUG_EXTRA("纹理生成完成: 处理了 %d/%d 个面片, 总共填充了 %d 个像素", - processedFaces, totalFaces, totalPixelsFilled); + // 使用膨胀操作扩展掩码 + cv::Mat dilatedMask; + cv::dilate(mask, dilatedMask, cv::Mat(), cv::Point(-1, -1), 2); - // 计算填充率 - int totalPixels = textureSize * textureSize; - float fillRate = (totalPixelsFilled * 100.0f) / totalPixels; - DEBUG_EXTRA("纹理填充率: %.2f%% (%d/%d)", fillRate, totalPixelsFilled, totalPixels); + // 对空洞区域进行填充 + cv::inpaint(texture, mask, texture, 3, cv::INPAINT_TELEA); - return textures; + DEBUG_EXTRA("Texture gap filling completed"); } -void MeshTexture::ValidateGeneratedTexture(const Image8U3& texture, const std::string& name) +void ApplySharpening(Image8U3& texture, float weight) { - DEBUG_EXTRA("验证纹理: %s", name.c_str()); + if (weight <= 0) return; - if (texture.empty()) { - DEBUG_EXTRA("纹理为空!"); - return; - } - - int totalPixels = texture.rows * texture.cols; - int coloredPixels = 0; - int emptyPixels = 0; - Pixel8U firstColor = texture(0, 0); - bool allSameColor = true; - - // 检查前1000个像素 - int checkCount = std::min(1000, totalPixels); - for (int i = 0; i < checkCount; ++i) { - int y = i / texture.cols; - int x = i % texture.cols; - const Pixel8U& pixel = texture(y, x); - - if (pixel[0] == 39 && pixel[1] == 127 && pixel[2] == 255) { - emptyPixels++; - } else { - coloredPixels++; - } - - if (pixel[0] != firstColor[0] || pixel[1] != firstColor[1] || pixel[2] != firstColor[2]) { - allSameColor = false; - } - } - - DEBUG_EXTRA("纹理检查结果:"); - DEBUG_EXTRA(" - 总像素: %d", totalPixels); - DEBUG_EXTRA(" - 检查了前 %d 个像素", checkCount); - DEBUG_EXTRA(" - 有颜色像素: %d", coloredPixels); - DEBUG_EXTRA(" - 空白像素: %d", emptyPixels); - DEBUG_EXTRA(" - 所有像素颜色相同: %s", allSameColor ? "是" : "否"); - - if (allSameColor) { - DEBUG_EXTRA(" - 统一颜色: RGB(%d, %d, %d)", - firstColor[2], firstColor[1], firstColor[0]); // OpenCV是BGR顺序 - } - - // 检查纹理中心区域 - int centerX = texture.cols / 2; - int centerY = texture.rows / 2; - int radius = 10; - - DEBUG_EXTRA("中心区域(%d±%d, %d±%d)像素:", centerX, radius, centerY, radius); - for (int y = centerY - radius; y <= centerY + radius; ++y) { - if (y < 0 || y >= texture.rows) continue; - - std::string rowStr = " "; - for (int x = centerX - radius; x <= centerX + radius; ++x) { - if (x < 0 || x >= texture.cols) continue; - - const Pixel8U& pixel = texture(y, x); - rowStr += cv::format("(%3d,%3d,%3d) ", pixel[0], pixel[1], pixel[2]); - } - DEBUG_EXTRA("%s", rowStr.c_str()); - } -} - -Pixel8U MeshTexture::SampleFromExistingTextureAtBarycentric(FIndex faceID, const Point3f& barycentric, const Image8U3& existingTexture) -{ - // 获取面在已有纹理中的UV坐标 - // 注意:这里假设每个面在已有纹理中也有对应的UV坐标 - // 我们需要从 scene.mesh.faceTexcoords 中获取这些UV坐标 - - if (faceID * 3 + 2 >= scene.mesh.faceTexcoords.size()) { - return Pixel8U(0, 0, 0); - } - - const TexCoord* existingUVs = &scene.mesh.faceTexcoords[faceID * 3]; - - // 使用重心坐标计算采样点的UV坐标 - Point2f texCoord( - existingUVs[0].x * barycentric.x + - existingUVs[1].x * barycentric.y + - existingUVs[2].x * barycentric.z, - existingUVs[0].y * barycentric.x + - existingUVs[1].y * barycentric.y + - existingUVs[2].y * barycentric.z - ); - - // 确保UV坐标在[0,1]范围内 - texCoord.x = std::max(0.0f, std::min(1.0f, texCoord.x)); - texCoord.y = std::max(0.0f, std::min(1.0f, texCoord.y)); - - // 从已有纹理中采样 - return SampleTextureBilinear(existingTexture, texCoord); + Image8U3 blurred; + cv::GaussianBlur(texture, blurred, cv::Size(0, 0), 1.5); + cv::addWeighted(texture, 1.0 + weight, blurred, -weight, 0, texture); } -Mesh::Image8U3Arr MeshTexture::SampleFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, - unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) +Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV( + const Mesh::Image8U3Arr& sourceTextures, // 已有纹理数组 + const Mesh::TexCoordArr& sourceTexcoords, // 已有UV坐标 + const Mesh::TexIndexArr& sourceTexindices, // 已有纹理索引 + unsigned nTextureSizeMultiple, + Pixel8U colEmpty, + float fSharpnessWeight +) { - DEBUG_EXTRA("SampleFromExistingTextures: 从已生成纹理采样"); + DEBUG_EXTRA("Generating texture atlas from existing UV data with %zu source textures", + sourceTextures.size()); - // 1. 检查是否有已生成的纹理 - if (scene.mesh.texturesDiffuse.empty() || scene.mesh.faceTexindices.empty()) { - DEBUG_EXTRA("SampleFromExistingTextures: 没有已生成的纹理,回退到 GenerateTextureAtlasFromUV"); - return GenerateTextureAtlasFromUV(faceLabels, views, nTextureSizeMultiple, colEmpty, fSharpnessWeight); - } - - // 2. 分析整个模型的UV布局 + // 1. 分析模型原始UV布局 AABB2f uvBounds(true); FOREACH(i, scene.mesh.faceTexcoords) { const TexCoord& uv = scene.mesh.faceTexcoords[i]; uvBounds.InsertFull(uv); } - // 3. 根据UV范围确定纹理图集尺寸 - const int textureSize = ComputeOptimalTextureSize(uvBounds.ptMin, uvBounds.ptMax, nTextureSizeMultiple); + // 确保UV在[0,1]范围内 + if (uvBounds.ptMin.x() < 0 || uvBounds.ptMin.y() < 0 || + uvBounds.ptMax.x() > 1 || uvBounds.ptMax.y() > 1) { + // UV超出范围,进行归一化 + DEBUG_EXTRA("UV coordinates out of [0,1] range, normalizing..."); + uvBounds = AABB2f(true); + for (size_t i = 0; i < scene.mesh.faceTexcoords.size(); i += 3) { + for (int v = 0; v < 3; ++v) { + const TexCoord& uv = scene.mesh.faceTexcoords[i + v]; + uvBounds.InsertFull(uv); + } + } + } + + // 计算纹理尺寸 + const float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x(); + const float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y(); + const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); - // 4. 创建目标纹理图集 + // 2. 创建单个纹理图集 Mesh::Image8U3Arr textures; Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); - DEBUG_EXTRA("从已生成纹理采样: 创建纹理图集 %dx%d", textureSize, textureSize); + DEBUG_EXTRA("Creating texture atlas: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]", + textureSize, textureSize, + uvBounds.ptMin.x(), uvBounds.ptMin.y(), + uvBounds.ptMax.x(), uvBounds.ptMax.y()); + + // 3. 为每个面采样颜色 + int processedFaces = 0; + int failedFaces = 0; - // 5. 为每个面片从现有纹理采样 #ifdef _USE_OPENMP - #pragma omp parallel for schedule(dynamic) - for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) { - #else - FOREACH(idxFace, scene.mesh.faces) { + #pragma omp parallel for schedule(dynamic) reduction(+:processedFaces, failedFaces) #endif + for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) { const FIndex faceID = (FIndex)idxFace; + const Face& face = scene.mesh.faces[faceID]; - // 获取面的纹理索引 - if (faceID >= scene.mesh.faceTexindices.size()) { - continue; - } + // 获取模型原始UV坐标 + const TexCoord* modelUVs = &scene.mesh.faceTexcoords[faceID * 3]; - TexIndex texIdx = scene.mesh.faceTexindices[faceID]; - if (texIdx >= scene.mesh.texturesDiffuse.size()) { + // 获取对应source纹理中的UV坐标 + const TexCoord* sourceUVs = &sourceTexcoords[faceID * 3]; + const TexIndex textureIdx = sourceTexindices.empty() ? 0 : sourceTexindices[faceID]; + + if (textureIdx >= sourceTextures.size()) { + failedFaces++; continue; } - // 获取源纹理和目标纹理 - const Image8U3& sourceTexture = scene.mesh.texturesDiffuse[texIdx]; - - // 获取面的UV坐标 - const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; + const Image8U3& sourceTexture = sourceTextures[textureIdx]; - // 计算面片在纹理图集中的边界框 + // 计算面片在目标纹理中的UV边界 AABB2f faceUVBounds(true); for (int i = 0; i < 3; ++i) { - faceUVBounds.InsertFull(uvCoords[i]); + // 从模型UV映射到纹理坐标 + const float u = (modelUVs[i].x - uvBounds.ptMin.x()) / uvWidth; + const float v = (modelUVs[i].y - uvBounds.ptMin.y()) / uvHeight; + faceUVBounds.InsertFull(Point2f(u, v)); } - // 将UV坐标转换到纹理像素坐标 + // 转换为像素坐标 const int startX = std::max(0, (int)(faceUVBounds.ptMin.x() * textureSize)); const int startY = std::max(0, (int)(faceUVBounds.ptMin.y() * textureSize)); const int endX = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.x() * textureSize)); const int endY = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.y() * textureSize)); - // 对面片覆盖的每个纹理像素进行采样 + if (startX > endX || startY > endY) { + failedFaces++; + continue; + } + + // 对面片覆盖的每个像素进行采样 for (int y = startY; y <= endY; ++y) { for (int x = startX; x <= endX; ++x) { const Point2f texCoord((float)x / textureSize, (float)y / textureSize); - // 检查是否在三角形内 + // 1. 检查是否在模型UV三角形内 Point3f barycentric; - if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { - // 从源纹理采样颜色 - Pixel8U sampledColor = SampleTextureBilinear(sourceTexture, texCoord); - - // 将采样颜色写入纹理图集 - #ifdef _USE_OPENMP - #pragma omp critical - #endif - { - textureAtlas(y, x) = sampledColor; - } + if (!PointInTriangle(texCoord, + Point2f((modelUVs[0].x - uvBounds.ptMin.x()) / uvWidth, + (modelUVs[0].y - uvBounds.ptMin.y()) / uvHeight), + Point2f((modelUVs[1].x - uvBounds.ptMin.x()) / uvWidth, + (modelUVs[1].y - uvBounds.ptMin.y()) / uvHeight), + Point2f((modelUVs[2].x - uvBounds.ptMin.x()) / uvWidth, + (modelUVs[2].y - uvBounds.ptMin.y()) / uvHeight), barycentric)) { + continue; } + + // 2. 使用相同的重心坐标在source纹理UV中插值 + Point2f sourceTexCoord( + sourceUVs[0].x * barycentric.x + + sourceUVs[1].x * barycentric.y + + sourceUVs[2].x * barycentric.z, + sourceUVs[0].y * barycentric.x + + sourceUVs[1].y * barycentric.y + + sourceUVs[2].y * barycentric.z + ); + + // 确保UV坐标在有效范围内 + if (sourceTexCoord.x < 0 || sourceTexCoord.x >= sourceTexture.cols || + sourceTexCoord.y < 0 || sourceTexCoord.y >= sourceTexture.rows) { + continue; + } + + // 3. 从source纹理中采样颜色 + const cv::Vec3b color = sourceTexture.at( + (int)sourceTexCoord.y, (int)sourceTexCoord.x); + + // 4. 写入目标纹理 + textureAtlas.at(y, x) = color; } } + + processedFaces++; } - // 6. 应用后处理 - if (fSharpnessWeight > 0) { - ApplySharpening(textureAtlas, fSharpnessWeight); - } - - DEBUG_EXTRA("从已生成纹理采样完成: %u个面片, 纹理尺寸%dx%d", - scene.mesh.faces.size(), textureSize, textureSize); - - return textures; -} - -// 辅助函数:从纹理中双线性采样 -Pixel8U MeshTexture::SampleTextureBilinear(const Image8U3& texture, const Point2f& texCoord) { - const float u = texCoord.x * texture.cols; - const float v = texCoord.y * texture.rows; - - const int x = (int)u; - const int y = (int)v; - const float dx = u - x; - const float dy = v - y; + DEBUG_EXTRA("Texture sampling completed: %d faces processed, %d faces failed", + processedFaces, failedFaces); - const int x1 = std::min(x, texture.cols - 1); - const int y1 = std::min(y, texture.rows - 1); - const int x2 = std::min(x + 1, texture.cols - 1); - const int y2 = std::min(y + 1, texture.rows - 1); - - const Pixel8U& p11 = texture(y1, x1); - const Pixel8U& p12 = texture(y2, x1); - const Pixel8U& p21 = texture(y1, x2); - const Pixel8U& p22 = texture(y2, x2); + // 4. 填充纹理空隙 + if (processedFaces > 0) { + // FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), textureSize, colEmpty); + } - // 双线性插值 - Pixel8U result; - for (int c = 0; c < 3; ++c) { - const float a = p11[c] * (1 - dx) + p21[c] * dx; - const float b = p12[c] * (1 - dx) + p22[c] * dx; - result[c] = (uint8_t)(a * (1 - dy) + b * dy); + // 5. 应用锐化 + if (fSharpnessWeight > 0) { + // ApplySharpening(textureAtlas, fSharpnessWeight); } - return result; -} - -// 辅助函数:应用锐化 -void MeshTexture::ApplySharpening(Image8U3& texture, float fSharpnessWeight) { - if (fSharpnessWeight <= 0) return; + DEBUG_EXTRA("Generated texture atlas: %dx%d from %zu source textures", + textureSize, textureSize, sourceTextures.size()); - constexpr double sigma = 1.5; - Image8U3 blurryTexture; - cv::GaussianBlur(texture, blurryTexture, cv::Size(), sigma); - cv::addWeighted(texture, 1 + fSharpnessWeight, blurryTexture, -fSharpnessWeight, 0, texture); + return textures; } - bool MeshTexture::ValidateProjection(const Vertex& worldPoint, const Image& sourceImage, Point2f imgPoint, float maxReprojectionError) { @@ -11512,41 +10847,32 @@ bool MeshTexture::PointInTriangle(const Point2f& p, const Point2f& a, const Poin return false; } -int MeshTexture::ComputeOptimalTextureSize(const Point2f& uvMin, const Point2f& uvMax, unsigned nTextureSizeMultiple) +// 辅助函数:计算最佳纹理尺寸 +int MeshTexture::ComputeOptimalTextureSize(float uvWidth, float uvHeight, unsigned multiple) { - DEBUG_EXTRA("原始UV范围: 最小=(%.3f,%.3f), 最大=(%.3f,%.3f)", - uvMin.x, uvMin.y, uvMax.x, uvMax.y); - - // 检查UV范围是否过大,需要归一化 - float uvWidth = uvMax.x - uvMin.x; - float uvHeight = uvMax.y - uvMin.y; - - // 如果UV范围非常大,可能是非归一化坐标 - if (uvWidth > 100.0f || uvHeight > 100.0f) { - DEBUG_EXTRA("警告: UV坐标范围过大,可能需要进行归一化"); - DEBUG_EXTRA("UV宽度=%.3f, 高度=%.3f,使用固定尺寸4096", uvWidth, uvHeight); - return 4096; // 返回固定尺寸 - } - - // 如果是归一化的UV坐标(在[0,1]范围内) - if (uvMin.x >= 0.0f && uvMin.y >= 0.0f && - uvMax.x <= 1.0f && uvMax.y <= 1.0f) { - // 归一化坐标,使用最大范围 - float maxRange = std::max(uvWidth, uvHeight); - int baseSize = (int)(maxRange * 4096.0f); - int textureSize = ((baseSize + nTextureSizeMultiple - 1) / nTextureSizeMultiple) * nTextureSizeMultiple; - textureSize = std::max(512, std::min(8192, textureSize)); - DEBUG_EXTRA("归一化UV,计算尺寸: %.3f*4096=%d -> %d", maxRange, baseSize, textureSize); - return textureSize; + // 计算所需尺寸 + int baseWidth = (int)ceil(uvWidth * 2048); // 假设基础分辨率 + int baseHeight = (int)ceil(uvHeight * 2048); + + // 向上对齐到multiple的倍数 + int width = ((baseWidth + multiple - 1) / multiple) * multiple; + int height = ((baseHeight + multiple - 1) / multiple) * multiple; + + // 确保最小尺寸 + width = std::max(width, 256); + height = std::max(height, 256); + + // 使用最大尺寸 + int size = std::max(width, height); + + // 限制最大尺寸 + const int MAX_SIZE = 8192; + if (size > MAX_SIZE) { + DEBUG_EXTRA("Warning: Texture size %d exceeds maximum %d, reducing...", size, MAX_SIZE); + size = std::min(size, MAX_SIZE); } - // 如果UV范围很小但不是归一化的 - int baseSize = (int)std::max(uvWidth, uvHeight) * 1024; - int textureSize = ((baseSize + nTextureSizeMultiple - 1) / nTextureSizeMultiple) * nTextureSizeMultiple; - textureSize = std::max(512, std::min(8192, textureSize)); - - DEBUG_EXTRA("计算纹理尺寸: baseSize=%d, 最终尺寸=%d", baseSize, textureSize); - return textureSize; + return size; } // 保存遮挡数据到文件 @@ -11804,6 +11130,39 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi DEBUG_EXTRA("First pass (virtual faces) completed: %u faces (%s)", mesh.faces.size(), TD_TIMER_GET_FMT().c_str()); } + // 第二轮处理:专门处理无效面片(非虚拟面映射) + if (false) + { + TD_TIMER_STARTD(); + + // 收集所有无效面片的索引 + Mesh::FaceIdxArr invalidFaces; + for (FIndex fid = 0; fid < mesh.faces.size(); ++fid) { + if (texture.scene.mesh.invalidFaces.data.contains(fid)) { + invalidFaces.push_back(fid); + } + } + + if (!invalidFaces.empty()) { + // 创建新的纹理处理器,专门处理无效面片 + MeshTexture textureInvalid(*this, nResolutionLevel, nMinResolution); + + // 使用非虚拟面模式处理无效面片 + if (!textureInvalid.FaceViewSelection4(0, fOutlierThreshold, fRatioDataSmoothness, + nIgnoreMaskLabel, views, &invalidFaces)) + { + return false; + } + + // 合并两轮处理的结果 + texture.texturePatches.Join(textureInvalid.texturePatches); + texture.seamEdges.Join(textureInvalid.seamEdges); + } + + DEBUG_EXTRA("Second pass (invalid faces) completed: %u faces (%s)", + invalidFaces.size(), TD_TIMER_GET_FMT().c_str()); + } + } DEBUG_EXTRA("Assigning the best view to each face completed: %u faces (%s)", mesh.faces.size(), TD_TIMER_GET_FMT().c_str()); } @@ -11812,6 +11171,13 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi DEBUG_EXTRA("TextureMesh %b, %s", bUseExistingUV, strUVMeshFileName.c_str()); if (bUseExistingUV && !strUVMeshFileName.empty()) { + texture.GenerateTextureForUV(bGlobalSeamLeveling, bLocalSeamLeveling, nTextureSizeMultiple, nRectPackingHeuristic, colEmpty, fSharpnessWeight, maxTextureSize, baseFileName, bOriginFaceview, this); + + // 保存生成的纹理数据 + Mesh::Image8U3Arr existingTextures = texture.texturesDiffuseTemp; + Mesh::TexCoordArr existingTexcoords = mesh.faceTexcoords; // 保存临时计算的UV + Mesh::TexIndexArr existingTexindices = mesh.faceTexindices; // 保存纹理索引 + VERBOSE("1faceTexcoords.size=%d, faces.size=%d", mesh.faceTexcoords.size(), mesh.faces.size() * 3); // 使用预计算UV模式 if (!mesh.Load(MAKE_PATH_SAFE(strUVMeshFileName), true)) { @@ -11828,12 +11194,9 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi return false; } - texture.GenerateTextureForUV(bGlobalSeamLeveling, bLocalSeamLeveling, nTextureSizeMultiple, nRectPackingHeuristic, colEmpty, fSharpnessWeight, maxTextureSize, baseFileName, bOriginFaceview, this); - - // 使用新的纹理生成方法 - MeshTexture texture(*this, nResolutionLevel, nMinResolution); - if (!texture.TextureWithExistingUV(views, nIgnoreMaskLabel, fOutlierThreshold, nTextureSizeMultiple, colEmpty, fSharpnessWeight)){ + MeshTexture texture2(*this, nResolutionLevel, nMinResolution); + if (!texture2.TextureWithExistingUV(views, nIgnoreMaskLabel, fOutlierThreshold, nTextureSizeMultiple, colEmpty, fSharpnessWeight, existingTextures, existingTexcoords, existingTexindices)){ return false; } //*/ @@ -11844,7 +11207,6 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi // mesh.CheckUVValid(); // generate the texture image and atlas - if (false) { TD_TIMER_STARTD(); texture.GenerateTexture(bGlobalSeamLeveling, bLocalSeamLeveling, nTextureSizeMultiple, nRectPackingHeuristic, colEmpty, fSharpnessWeight, maxTextureSize, baseFileName, bOriginFaceview, this);