From 7fd81ffbea32f1cde6fd99ecd84c0a4cc87fff1c Mon Sep 17 00:00:00 2001 From: hesuicong Date: Wed, 31 Dec 2025 09:51:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=8B=E5=8A=A8UV=E5=88=86=E6=94=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/TextureMesh/TextureMesh.cpp | 9 +- libs/IO/OBJ.cpp | 129 +++- libs/IO/OBJ.h | 3 +- libs/MVS/Mesh.cpp | 74 +- libs/MVS/Mesh.h | 10 +- libs/MVS/Scene.h | 2 +- libs/MVS/SceneTexture.cpp | 1197 ++++++++++++++++++++++++------ 7 files changed, 1187 insertions(+), 237 deletions(-) diff --git a/apps/TextureMesh/TextureMesh.cpp b/apps/TextureMesh/TextureMesh.cpp index 775d29a..5ab5203 100644 --- a/apps/TextureMesh/TextureMesh.cpp +++ b/apps/TextureMesh/TextureMesh.cpp @@ -94,6 +94,9 @@ unsigned nMaxThreads; int nMaxTextureSize; String strExportType; String strConfigFileName; +bool bUseExistingUV; +String strUVMeshFileName; + boost::program_options::variables_map vm; } // namespace OPT @@ -163,6 +166,8 @@ bool Application::Initialize(size_t argc, LPCTSTR* argv) ("image-folder", boost::program_options::value(&OPT::strImageFolder)->default_value(COLMAP_IMAGES_FOLDER), "folder to the undistorted images") ("origin-faceview", boost::program_options::value(&OPT::bOriginFaceview)->default_value(false), "use origin faceview selection") ("id", boost::program_options::value(&OPT::nID)->default_value(0), "id") + ("use-existing-uv", boost::program_options::value(&OPT::bUseExistingUV)->default_value(false), "use existing UV coordinates from the input mesh") + ("uv-mesh-file", boost::program_options::value(&OPT::strUVMeshFileName), "mesh file with pre-computed UV coordinates") ; // hidden options, allowed both on command line and @@ -1134,12 +1139,12 @@ int main(int argc, LPCTSTR* argv) if (!scene.TextureMesh(OPT::nResolutionLevel, OPT::nMinResolution, OPT::minCommonCameras, OPT::fOutlierThreshold, OPT::fRatioDataSmoothness, OPT::bGlobalSeamLeveling, OPT::bLocalSeamLeveling, OPT::nTextureSizeMultiple, OPT::nRectPackingHeuristic, Pixel8U(OPT::nColEmpty), OPT::fSharpnessWeight, OPT::nIgnoreMaskLabel, OPT::nMaxTextureSize, views, baseFileName, OPT::bOriginFaceview, - OPT::strInputFileName, OPT::strMeshFileName)) + OPT::strInputFileName, OPT::strMeshFileName, OPT::bUseExistingUV, OPT::strUVMeshFileName)) return EXIT_FAILURE; VERBOSE("Mesh texturing completed: %u vertices, %u faces (%s)", scene.mesh.vertices.GetSize(), scene.mesh.faces.GetSize(), TD_TIMER_GET_FMT().c_str()); // save the final mesh - scene.mesh.Save(baseFileName+OPT::strExportType); + scene.mesh.Save(baseFileName+OPT::strExportType,cList(),true,OPT::bUseExistingUV); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+OPT::strExportType); diff --git a/libs/IO/OBJ.cpp b/libs/IO/OBJ.cpp index 0c1f530..8e05d14 100644 --- a/libs/IO/OBJ.cpp +++ b/libs/IO/OBJ.cpp @@ -48,6 +48,7 @@ ObjModel::MaterialLib::MaterialLib() bool ObjModel::MaterialLib::Save(const String& prefix, bool texLossless) const { + DEBUG_EXTRA("MaterialLib::Save %s", prefix.c_str()); std::ofstream out((prefix+".mtl").c_str()); if (!out.good()) return false; @@ -130,7 +131,7 @@ bool ObjModel::MaterialLib::Load(const String& fileName) // S T R U C T S /////////////////////////////////////////////////// -bool ObjModel::Save(const String& fileName, unsigned precision, bool texLossless) const +bool ObjModel::Save(const String& fileName, unsigned precision, bool texLossless, bool bUseExistingUV) const { if (vertices.empty()) return false; @@ -140,6 +141,9 @@ bool ObjModel::Save(const String& fileName, unsigned precision, bool texLossless if (!material_lib.Save(prefix, texLossless)) return false; + if (bUseExistingUV) + return true; + std::ofstream out((prefix + ".obj").c_str()); if (!out.good()) return false; @@ -269,6 +273,129 @@ bool ObjModel::Load(const String& fileName) return !vertices.empty(); } +bool ObjModel::LoadUV(const String& fileName, SEACAVE::cList &faceTexcoords, bool bLoadUV) +{ + DEBUG_EXTRA("LoadUV bLoadUV=%b", bLoadUV); + ASSERT(vertices.empty() && groups.empty() && material_lib.materials.empty()); + std::ifstream fin(fileName.c_str()); + String line, keyword; + std::istringstream in; + + while (fin.good()) { + std::getline(fin, line); + if (line.empty() || line[0u] == '#') + continue; + + in.str(line); + in >> keyword; + + if (keyword == "v") { + Vertex v; + in >> v[0] >> v[1] >> v[2]; + if (in.fail()) + { + DEBUG_EXTRA("1"); + return false; + } + vertices.push_back(v); + // DEBUG_EXTRA("10, %d", vertices.size()); + } else if (keyword == "vt") { + TexCoord vt; + in >> vt[0] >> vt[1]; + if (in.fail()) + { + DEBUG_EXTRA("2"); + return false; + } + texcoords.push_back(vt); + } else if (keyword == "vn") { + Normal vn; + in >> vn[0] >> vn[1] >> vn[2]; + if (in.fail()) + { + DEBUG_EXTRA("3"); + return false; + } + normals.push_back(vn); + } else if (keyword == "f") { + Face f; + memset(&f, 0xFF, sizeof(Face)); + + for (size_t k = 0; k < 3; ++k) { + in >> keyword; + + // 更健壮的解析方法,处理各种索引格式 + std::vector parts; + size_t start = 0, end = 0; + + // 按'/'分割字符串 + while ((end = keyword.find('/', start)) != String::npos) { + parts.push_back(keyword.substr(start, end - start)); + start = end + 1; + } + parts.push_back(keyword.substr(start)); + + // 根据分割后的部分数量处理不同情况 + if (parts.size() >= 1 && !parts[0].empty()) { + f.vertices[k] = std::stoi(parts[0]) - OBJ_INDEX_OFFSET; + } + + if (parts.size() >= 2 && !parts[1].empty()) { + f.texcoords[k] = std::stoi(parts[1]) - OBJ_INDEX_OFFSET; + } + + if (parts.size() >= 3 && !parts[2].empty()) { + f.normals[k] = std::stoi(parts[2]) - OBJ_INDEX_OFFSET; + } + } + + if (in.fail()) + { + DEBUG_EXTRA("4"); + return false; + } + + if (bLoadUV) + { + for (int i = 0; i < 3; ++i) { + if (f.texcoords[i] != NO_ID && f.texcoords[i] < texcoords.size()) { + faceTexcoords.push_back(texcoords[f.texcoords[i]]); + } else { + // 索引无效(例如面定义中未提供vt索引或索引越界),使用默认值 + faceTexcoords.push_back(Point2f(0.0f, 0.0f)); // 默认UV + DEBUG_EXTRA("Invalid texcoords %d", f.texcoords[i]) + } + } + + // DEBUG_EXTRA("faceTexcoords push_back [(%f,%f),(%f,%f),(%f,%f)]", texcoords[f.texcoords[0]].x, texcoords[f.texcoords[0]].y, + // texcoords[f.texcoords[1]].x, texcoords[f.texcoords[1]].y, texcoords[f.texcoords[2]].x, texcoords[f.texcoords[2]].y); + } + + if (groups.empty()) + AddGroup(""); + groups.back().faces.push_back(f); + // } else if (keyword == "mtllib") { + // in >> keyword; + // if (!material_lib.Load(keyword)) + // DEBUG_EXTRA("3"); + // return false; + } else if (keyword == "usemtl") { + Group group; + in >> group.material_name; + if (in.fail()) + { + DEBUG_EXTRA("5"); + return false; + } + groups.push_back(group); + } + + in.clear(); + } + + DEBUG_EXTRA("6, vertices.size=%d, faceTexcoords.size=%d", vertices.size(), faceTexcoords.size()); + return !vertices.empty(); +} ObjModel::Group& ObjModel::AddGroup(const String& material_name) { diff --git a/libs/IO/OBJ.h b/libs/IO/OBJ.h index d32c356..3df5155 100644 --- a/libs/IO/OBJ.h +++ b/libs/IO/OBJ.h @@ -93,9 +93,10 @@ public: ObjModel() {} // Saves the obj model to an .obj file, its material lib and the materials with the given file name - bool Save(const String& fileName, unsigned precision=6, bool texLossless=false) const; + bool Save(const String& fileName, unsigned precision=6, bool texLossless=false, bool bUseExistingUV=false) const; // Loads the obj model from an .obj file, its material lib and the materials with the given file name bool Load(const String& fileName); + bool LoadUV(const String& fileName, SEACAVE::cList &faceTexcoords, bool bLoadUV = false); // Creates a new group with the given material name Group& AddGroup(const String& material_name); diff --git a/libs/MVS/Mesh.cpp b/libs/MVS/Mesh.cpp index f198262..6db34dd 100644 --- a/libs/MVS/Mesh.cpp +++ b/libs/MVS/Mesh.cpp @@ -1191,15 +1191,14 @@ namespace BasicPLY { } // namespace MeshInternal // import the mesh from the given file -bool Mesh::Load(const String& fileName) +bool Mesh::Load(const String& fileName, bool bLoadUV) { TD_TIMER_STARTD(); const String ext(Util::getFileExt(fileName).ToLower()); bool ret; if (ext == _T(".obj")) - ret = LoadOBJ(fileName); - else - if (ext == _T(".gltf") || ext == _T(".glb")) + ret = LoadOBJ(fileName, bLoadUV); + else if (ext == _T(".gltf") || ext == _T(".glb")) ret = LoadGLTF(fileName, ext == _T(".glb")); else ret = LoadPLY(fileName); @@ -1208,6 +1207,43 @@ bool Mesh::Load(const String& fileName) DEBUG_EXTRA("Mesh loaded: %u vertices, %u faces (%s)", vertices.size(), faces.size(), TD_TIMER_GET_FMT().c_str()); return true; } + +void Mesh::CheckUVValid() +{ + for (int_t idxFace = 0; idxFace < (int_t)faces.size(); ++idxFace) + { + FOREACH(idxFace, faces) + { + const FIndex faceID = (FIndex)idxFace; + + const TexCoord* uv = &faceTexcoords[faceID * 3]; + + const Point2f& a = uv[0]; + const Point2f& b = uv[1]; + const Point2f& c = uv[2]; + + // DEBUG_EXTRA("a=(%f,%f),b=(%f,%f),c=(%f,%f)", a.x, a.y, b.x, b.y, c.x, c.y); + + // 计算边向量 + Point2f v0 = b - a; + 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-10f; + if (std::abs(denom) < epsilon) + { + // DEBUG_EXTRA("PointInTriangle - Degenerate triangle, denom=%.10f", denom); + } + else + { + DEBUG_EXTRA("PointInTriangle Yes idxFace=%d", idxFace); + } + } + } +} + // import the mesh as a PLY file bool Mesh::LoadPLY(const String& fileName) { @@ -1302,16 +1338,28 @@ bool Mesh::LoadPLY(const String& fileName) return true; } // import the mesh as a OBJ file -bool Mesh::LoadOBJ(const String& fileName) +bool Mesh::LoadOBJ(const String& fileName, bool bLoadUV) { ASSERT(!fileName.empty()); Release(); // open and parse OBJ file ObjModel model; - if (!model.Load(fileName)) { - DEBUG_EXTRA("error: invalid OBJ file"); - return false; + if (bLoadUV) + { + if (!model.LoadUV(fileName, faceTexcoords, true)) { + DEBUG_EXTRA("error: LoadUV invalid OBJ file %s", fileName.c_str()); + return false; + } + } + else + { + if (!model.LoadUV(fileName, faceTexcoords)) { + // if (!model.Load(fileName)) { + DEBUG_EXTRA("error: Load invalid OBJ file %s", fileName.c_str()); + return false; + } + } if (model.get_vertices().empty() || model.get_groups().empty()) { @@ -1340,11 +1388,13 @@ bool Mesh::LoadOBJ(const String& fileName) for (const ObjModel::Face& f: group.faces) { ASSERT(f.vertices[0] != NO_ID); faces.emplace_back(f.vertices[0], f.vertices[1], f.vertices[2]); + /* if (f.texcoords[0] != NO_ID) { for (int i=0; i<3; ++i) faceTexcoords.emplace_back(model.get_texcoords()[f.texcoords[i]]); faceTexindices.emplace_back((TexIndex)groupIdx); } + */ if (f.normals[0] != NO_ID) { Normal& n = faceNormals.emplace_back(Normal::ZERO); for (int i=0; i<3; ++i) @@ -1445,13 +1495,13 @@ bool Mesh::LoadGLTF(const String& fileName, bool bBinary) /*----------------------------------------------------------------*/ // export the mesh to the given file -bool Mesh::Save(const String& fileName, const cList& comments, bool bBinary) const +bool Mesh::Save(const String& fileName, const cList& comments, bool bBinary, bool bUseExistingUV) const { TD_TIMER_STARTD(); const String ext(Util::getFileExt(fileName).ToLower()); bool ret; if (ext == _T(".obj")) - ret = SaveOBJ(fileName); + ret = SaveOBJ(fileName, bUseExistingUV); else if (ext == _T(".gltf") || ext == _T(".glb")) ret = SaveGLTF(fileName, ext == _T(".glb")); @@ -1538,7 +1588,7 @@ bool Mesh::SavePLY(const String& fileName, const cList& comments, bool b return true; } // export the mesh as a OBJ file -bool Mesh::SaveOBJ(const String& fileName) const +bool Mesh::SaveOBJ(const String& fileName, bool bUseExistingUV) const { ASSERT(!fileName.empty()); Util::ensureFolder(fileName); @@ -1597,7 +1647,7 @@ bool Mesh::SaveOBJ(const String& fileName) const pMaterial->diffuse_map = texturesDiffuse[idxTexture]; } - return model.Save(fileName, 6U, true); + return model.Save(fileName, 6U, true, bUseExistingUV); } // export the mesh as a GLTF file template diff --git a/libs/MVS/Mesh.h b/libs/MVS/Mesh.h index ecb188f..e40e218 100644 --- a/libs/MVS/Mesh.h +++ b/libs/MVS/Mesh.h @@ -262,8 +262,8 @@ public: bool TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices={}, unsigned borderSize=3, unsigned textureSize=4096); // file IO - bool Load(const String& fileName); - bool Save(const String& fileName, const cList& comments=cList(), bool bBinary=true) const; + bool Load(const String& fileName, bool bLoadUV=false); + bool Save(const String& fileName, const cList& comments=cList(), bool bBinary=true, bool bUseExistingUV=false) const; bool Save(const FacesChunkArr&, const String& fileName, const cList& comments=cList(), bool bBinary=true) const; static bool Save(const VertexArr& vertices, const String& fileName, bool bBinary=true); @@ -271,13 +271,15 @@ public: static inline VIndex GetVertex(const Face& f, VIndex v) { const uint32_t idx(FindVertex(f, v)); ASSERT(idx != NO_ID); return f[idx]; } static inline VIndex& GetVertex(Face& f, VIndex v) { const uint32_t idx(FindVertex(f, v)); ASSERT(idx != NO_ID); return f[idx]; } + void CheckUVValid(); + protected: bool LoadPLY(const String& fileName); - bool LoadOBJ(const String& fileName); + bool LoadOBJ(const String& fileName, bool bLoadUV); bool LoadGLTF(const String& fileName, bool bBinary=true); bool SavePLY(const String& fileName, const cList& comments=cList(), bool bBinary=true, bool bTexLossless=true) const; - bool SaveOBJ(const String& fileName) const; + bool SaveOBJ(const String& fileName, bool bUseExistingUV) const; bool SaveGLTF(const String& fileName, bool bBinary=true) const; #ifdef _USE_CUDA diff --git a/libs/MVS/Scene.h b/libs/MVS/Scene.h index 2f67a26..f4eab4c 100644 --- a/libs/MVS/Scene.h +++ b/libs/MVS/Scene.h @@ -169,7 +169,7 @@ public: bool TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsigned minCommonCameras=0, float fOutlierThreshold=0.f, float fRatioDataSmoothness=0.3f, bool bGlobalSeamLeveling=true, bool bLocalSeamLeveling=true, unsigned nTextureSizeMultiple=0, unsigned nRectPackingHeuristic=3, Pixel8U colEmpty=Pixel8U(255,127,39), float fSharpnessWeight=0.5f, int ignoreMaskLabel=-1, int maxTextureSize=0, const IIndexArr& views=IIndexArr(), const SEACAVE::String& basename = "", bool bOriginFaceview = false, - const std::string& inputFileName = "", const std::string& meshFileName = ""); + const std::string& inputFileName = "", const std::string& meshFileName = "", bool bUseExistingUV = false, const std::string& strUVMeshFileName = ""); std::string runPython(const std::string& command); bool is_face_visible(const std::string& image_name, int face_index); diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 9921dcd..3d11ba6 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -441,7 +441,7 @@ public: MeshTexture(Scene& _scene, unsigned _nResolutionLevel=0, unsigned _nMinResolution=640); ~MeshTexture(); - void ListVertexFaces(); + void ListVertexFaces(bool bUseExistingUV = false); bool ListCameraFaces(FaceDataViewArr&, float fOutlierThreshold, int nIgnoreMaskLabel, const IIndexArr& views, bool bUseVirtualFaces); bool CheckInvalidFaces(FaceDataViewArr& facesDatas, float fOutlierThreshold, int nIgnoreMaskLabel, const IIndexArr& _views, bool bUseVirtualFaces); @@ -465,7 +465,7 @@ public: bool FaceViewSelection(unsigned minCommonCameras, float fOutlierThreshold, float fRatioDataSmoothness, int nIgnoreMaskLabel, const IIndexArr& views); bool FaceViewSelection2(unsigned minCommonCameras, float fOutlierThreshold, float fRatioDataSmoothness, int nIgnoreMaskLabel, const IIndexArr& views); - bool FaceViewSelection3(unsigned minCommonCameras, float fOutlierThreshold, float fRatioDataSmoothness, int nIgnoreMaskLabel, const IIndexArr& views); + bool FaceViewSelection3(unsigned minCommonCameras, float fOutlierThreshold, float fRatioDataSmoothness, int nIgnoreMaskLabel, const IIndexArr& views, bool bUseExistingUV); bool FaceViewSelection4( unsigned minCommonCameras, float fOutlierThreshold, float fRatioDataSmoothness, int nIgnoreMaskLabel, const IIndexArr& views, const Mesh::FaceIdxArr* faceIndices = nullptr); void CreateAdaptiveVirtualFaces(FaceDataViewArr& facesDatas, FaceDataViewArr& virtualFacesDatas, VirtualFaceIdxsArr& virtualFaces, unsigned minCommonCameras); bool ShouldMergeVirtualFace(const MeshTexture::FaceDataViewArr& facesDatas, const Mesh::FaceIdxArr& currentVirtualFace, FIndex candidateFace, unsigned minCommonCameras); @@ -474,9 +474,21 @@ public: void GlobalSeamLeveling3(); void LocalSeamLeveling(); void LocalSeamLeveling3(); - void GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& baseFileName, bool bOriginFaceview); - void GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& baseFileName); + void GlobalSeamLevelingExternalUV(); + 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 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); + 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); + 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); // Bruce //* template @@ -764,9 +776,10 @@ void MeshTexture::ComputeFaceCurvatures() const { // extract array of triangles incident to each vertex // and check each vertex if it is at the boundary or not -void MeshTexture::ListVertexFaces() +void MeshTexture::ListVertexFaces(bool bUseExistingUV) { - scene.mesh.EmptyExtra(); + // if (!bUseExistingUV) + scene.mesh.EmptyExtra(); scene.mesh.ListIncidenteFaces(); scene.mesh.ListBoundaryVertices(); scene.mesh.ListIncidenteFaceFaces(); @@ -6067,10 +6080,10 @@ bool MeshTexture::ShouldMergeVirtualFace( // return false; } -bool MeshTexture::FaceViewSelection3( unsigned minCommonCameras, float fOutlierThreshold, float fRatioDataSmoothness, int nIgnoreMaskLabel, const IIndexArr& views) +bool MeshTexture::FaceViewSelection3( unsigned minCommonCameras, float fOutlierThreshold, float fRatioDataSmoothness, int nIgnoreMaskLabel, const IIndexArr& views, bool bUseExistingUV) { // extract array of triangles incident to each vertex - ListVertexFaces(); + ListVertexFaces(bUseExistingUV); // create texture patches { @@ -7156,6 +7169,7 @@ bool MeshTexture::FaceViewSelection3( unsigned minCommonCameras, float fOutlierT mapIdxPatch.Insert(numPatches); } } + return true; } @@ -8330,8 +8344,16 @@ void MeshTexture::LocalSeamLeveling3() } } -void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& basename, bool bOriginFaceview) +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; + + if (!pScene->mesh.faceTexcoords.empty() && pScene->mesh.faceTexcoords.size() == pScene->mesh.faces.size() * 3) { + bUseExternalUV = true; + DEBUG_EXTRA("使用外部UV数据,跳过自动UV生成流程"); + } + DEBUG_EXTRA("GenerateTexture bUseExternalUV=%b", bUseExternalUV); + // Bruce // bGlobalSeamLeveling = false; // bLocalSeamLeveling = false; @@ -8340,84 +8362,149 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel int border = 2; if (!bOriginFaceview) border = 4; - faceTexcoords.resize(faces.size()*3); - faceTexindices.resize(faces.size()); + + // ===== 修改:只在非外部UV模式下初始化faceTexcoords ===== + if (!bUseExternalUV) { + faceTexcoords.resize(faces.size()*3); + faceTexindices.resize(faces.size()); + } + #ifdef TEXOPT_USE_OPENMP // LOG_OUT() << "def TEXOPT_USE_OPENMP" << std::endl; const unsigned numPatches(texturePatches.size()-1); - #pragma omp parallel for schedule(dynamic) - for (int_t idx=0; idx<(int_t)numPatches; ++idx) { - TexturePatch& texturePatch = texturePatches[(uint32_t)idx]; - #else - for (TexturePatch *pTexturePatch=texturePatches.Begin(), *pTexturePatchEnd=texturePatches.End()-1; pTexturePatch 0 && textureSize > maxTextureSize) - textureSize = maxTextureSize; - } - RectsBinPack::RectWIdxArr newPlacedRects; - switch (typeRectsBinPack) { - case 0: { - MaxRectsBinPack pack(textureSize, textureSize); - newPlacedRects = pack.Insert(unplacedRects, (MaxRectsBinPack::FreeRectChoiceHeuristic)typeHeuristic); - break; } - case 1: { - SkylineBinPack pack(textureSize, textureSize, typeSplit!=0); - newPlacedRects = pack.Insert(unplacedRects, (SkylineBinPack::LevelChoiceHeuristic)typeHeuristic); - break; } - case 2: { - GuillotineBinPack pack(textureSize, textureSize); - newPlacedRects = pack.Insert(unplacedRects, false, (GuillotineBinPack::FreeRectChoiceHeuristic)typeHeuristic, (GuillotineBinPack::GuillotineSplitHeuristic)typeSplit); - break; } - default: - ABORT("error: unknown RectsBinPack type"); - } - DEBUG_ULTIMATE("\tpacking texture completed: %u initial patches, %u placed patches, %u texture-size, %u textures (%s)", texturePatches.size(), newPlacedRects.size(), textureSize, placedRects.size(), TD_TIMER_GET_FMT().c_str()); + if (bUseExternalUV) { + // 对于外部UV数据,直接使用现有的纹理坐标,跳过复杂的打包流程 + DEBUG_EXTRA("使用外部UV数据,简化纹理图集生成流程"); + + // 创建单个纹理图集 + textureSize = maxTextureSize > 0 ? maxTextureSize : 4096; // 默认大小 + texturesDiffuse.emplace_back(textureSize, textureSize).setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); + + // 直接使用现有的faceTexcoords和faceTexindices + // 不需要重新计算纹理坐标偏移 + DEBUG_EXTRA("外部UV纹理映射完成: 使用现有UV坐标"); + } else { + while (!unplacedRects.empty()) { + TD_TIMER_STARTD(); + if (textureSize == 0) { + textureSize = RectsBinPack::ComputeTextureSize(unplacedRects, nTextureSizeMultiple); + if (maxTextureSize > 0 && textureSize > maxTextureSize) + textureSize = maxTextureSize; + } - if (textureSize == maxTextureSize || unplacedRects.empty()) { - // create texture image - placedRects.emplace_back(std::move(newPlacedRects)); - // Pixel8U colEmpty2=Pixel8U(0,0,255); - texturesDiffuse.emplace_back(textureSize, textureSize).setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); - textureSize = 0; - } else { - // try again with a bigger texture - textureSize *= 2; - if (maxTextureSize > 0) - textureSize = std::max(textureSize, maxTextureSize); - unplacedRects.JoinRemove(newPlacedRects); + RectsBinPack::RectWIdxArr newPlacedRects; + switch (typeRectsBinPack) { + case 0: { + MaxRectsBinPack pack(textureSize, textureSize); + newPlacedRects = pack.Insert(unplacedRects, (MaxRectsBinPack::FreeRectChoiceHeuristic)typeHeuristic); + break; } + case 1: { + SkylineBinPack pack(textureSize, textureSize, typeSplit!=0); + newPlacedRects = pack.Insert(unplacedRects, (SkylineBinPack::LevelChoiceHeuristic)typeHeuristic); + break; } + case 2: { + GuillotineBinPack pack(textureSize, textureSize); + newPlacedRects = pack.Insert(unplacedRects, false, (GuillotineBinPack::FreeRectChoiceHeuristic)typeHeuristic, (GuillotineBinPack::GuillotineSplitHeuristic)typeSplit); + break; } + default: + ABORT("error: unknown RectsBinPack type"); + } + DEBUG_ULTIMATE("\tpacking texture completed: %u initial patches, %u placed patches, %u texture-size, %u textures (%s)", texturePatches.size(), newPlacedRects.size(), textureSize, placedRects.size(), TD_TIMER_GET_FMT().c_str()); + + if (textureSize == maxTextureSize || unplacedRects.empty()) { + // create texture image + placedRects.emplace_back(std::move(newPlacedRects)); + // Pixel8U colEmpty2=Pixel8U(0,0,255); + texturesDiffuse.emplace_back(textureSize, textureSize).setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); + textureSize = 0; + } else { + // try again with a bigger texture + textureSize *= 2; + if (maxTextureSize > 0) + textureSize = std::max(textureSize, maxTextureSize); + unplacedRects.JoinRemove(newPlacedRects); + } } } } LOG_OUT() << "Third loop completed" << std::endl; + Mesh::FaceIdxArr emptyFaceIndexes; + if (!bUseExternalUV) { - #ifdef TEXOPT_USE_OPENMP - #pragma omp parallel for schedule(dynamic) - for (int_t i=0; i<(int_t)placedRects.size(); ++i) { - for (int_t j=0; j<(int_t)placedRects[(TexIndex)i].size(); ++j) { - const TexIndex idxTexture((TexIndex)i); - const uint32_t idxPlacedPatch((uint32_t)j); - #else - FOREACH(idxTexture, placedRects) { - FOREACH(idxPlacedPatch, placedRects[idxTexture]) { - #endif - const TexturePatch& texturePatch = texturePatches[placedRects[idxTexture][idxPlacedPatch].patchIdx]; - const RectsBinPack::Rect& rect = placedRects[idxTexture][idxPlacedPatch].rect; - // copy patch image - ASSERT((rect.width == texturePatch.rect.width && rect.height == texturePatch.rect.height) || - (rect.height == texturePatch.rect.width && rect.width == texturePatch.rect.height)); - int x(0), y(1); - if (texturePatch.label != NO_ID) { - const Image& imageData = images[texturePatch.label]; - cv::Mat patch(imageData.image(texturePatch.rect)); - if (rect.width != texturePatch.rect.width) { - // flip patch and texture-coordinates - patch = patch.t(); - x = 1; y = 0; + #ifdef TEXOPT_USE_OPENMP + #pragma omp parallel for schedule(dynamic) + for (int_t i=0; i<(int_t)placedRects.size(); ++i) { + for (int_t j=0; j<(int_t)placedRects[(TexIndex)i].size(); ++j) { + const TexIndex idxTexture((TexIndex)i); + const uint32_t idxPlacedPatch((uint32_t)j); + #else + FOREACH(idxTexture, placedRects) { + FOREACH(idxPlacedPatch, placedRects[idxTexture]) { + #endif + const TexturePatch& texturePatch = texturePatches[placedRects[idxTexture][idxPlacedPatch].patchIdx]; + const RectsBinPack::Rect& rect = placedRects[idxTexture][idxPlacedPatch].rect; + // copy patch image + ASSERT((rect.width == texturePatch.rect.width && rect.height == texturePatch.rect.height) || + (rect.height == texturePatch.rect.width && rect.width == texturePatch.rect.height)); + int x(0), y(1); + if (texturePatch.label != NO_ID) { + const Image& imageData = images[texturePatch.label]; + cv::Mat patch(imageData.image(texturePatch.rect)); + if (rect.width != texturePatch.rect.width) { + // flip patch and texture-coordinates + patch = patch.t(); + x = 1; y = 0; + } + + patch.copyTo(texturesDiffuse[idxTexture](rect)); } - - patch.copyTo(texturesDiffuse[idxTexture](rect)); - } - else - { - //* - auto it = texturePatch.faces.begin(); - while (it != texturePatch.faces.end()) + else { - emptyFaceIndexes.push_back(*it); - ++it; - } - //*/ - /* - // 处理无效贴片:使用备用纹理 - if (alternativeTexture != nullptr) { - // 使用备用纹理进行采样 - cv::Mat patch(rect.size(), CV_8UC3); - for (int r = 0; r < patch.rows; ++r) { - for (int c = 0; c < patch.cols; ++c) { - // 计算UV坐标:将像素位置映射到备用纹理的UV空间 - float u = (float)c / patch.cols; - float v = (float)r / patch.rows; - // 从备用纹理中采样 - int xSrc = static_cast(u * alternativeTexture->width()); - int ySrc = static_cast(v * alternativeTexture->height()); - xSrc = std::min(std::max(xSrc, 0), alternativeTexture->width() - 1); - ySrc = std::min(std::max(ySrc, 0), alternativeTexture->height() - 1); - Pixel8U color = (*alternativeTexture)(ySrc, xSrc); - patch.at(r, c) = color; + //* + auto it = texturePatch.faces.begin(); + while (it != texturePatch.faces.end()) + { + emptyFaceIndexes.push_back(*it); + ++it; + } + //*/ + /* + // 处理无效贴片:使用备用纹理 + if (alternativeTexture != nullptr) { + // 使用备用纹理进行采样 + cv::Mat patch(rect.size(), CV_8UC3); + for (int r = 0; r < patch.rows; ++r) { + for (int c = 0; c < patch.cols; ++c) { + // 计算UV坐标:将像素位置映射到备用纹理的UV空间 + float u = (float)c / patch.cols; + float v = (float)r / patch.rows; + // 从备用纹理中采样 + int xSrc = static_cast(u * alternativeTexture->width()); + int ySrc = static_cast(v * alternativeTexture->height()); + xSrc = std::min(std::max(xSrc, 0), alternativeTexture->width() - 1); + ySrc = std::min(std::max(ySrc, 0), alternativeTexture->height() - 1); + Pixel8U color = (*alternativeTexture)(ySrc, xSrc); + patch.at(r, c) = color; + } } + // Pixel8U colEmpty2=Pixel8U(0,0,255); + // cv::Mat patch2(rect.size(), CV_8UC3, cv::Scalar(colEmpty2.b, colEmpty2.g, colEmpty2.r)); + // patch2.copyTo(texturesDiffuse[idxTexture](rect)); + patch.copyTo(texturesDiffuse[idxTexture](rect)); + } else { + // 没有备用纹理,使用默认颜色 + // Pixel8U colEmpty2=Pixel8U(0,0,255); + cv::Mat patch(rect.size(), CV_8UC3, cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); + patch.copyTo(texturesDiffuse[idxTexture](rect)); } - // Pixel8U colEmpty2=Pixel8U(0,0,255); - // cv::Mat patch2(rect.size(), CV_8UC3, cv::Scalar(colEmpty2.b, colEmpty2.g, colEmpty2.r)); - // patch2.copyTo(texturesDiffuse[idxTexture](rect)); - patch.copyTo(texturesDiffuse[idxTexture](rect)); - } else { - // 没有备用纹理,使用默认颜色 - // Pixel8U colEmpty2=Pixel8U(0,0,255); - cv::Mat patch(rect.size(), CV_8UC3, cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); - patch.copyTo(texturesDiffuse[idxTexture](rect)); + */ } - */ - } - // compute final texture coordinates - const TexCoord offset(rect.tl()); - for (const FIndex idxFace: texturePatch.faces) { - TexCoord* texcoords = faceTexcoords.data()+idxFace*3; - faceTexindices[idxFace] = idxTexture; - for (int v=0; v<3; ++v) { - TexCoord& texcoord = texcoords[v]; - texcoord = TexCoord( - texcoord[x]+offset.x, - texcoord[y]+offset.y - ); + // compute final texture coordinates + const TexCoord offset(rect.tl()); + for (const FIndex idxFace: texturePatch.faces) { + TexCoord* texcoords = faceTexcoords.data()+idxFace*3; + faceTexindices[idxFace] = idxTexture; + for (int v=0; v<3; ++v) { + TexCoord& texcoord = texcoords[v]; + texcoord = TexCoord( + texcoord[x]+offset.x, + texcoord[y]+offset.y + ); + } } } } + } else { + // 外部UV数据:直接使用现有的纹理坐标,不需要重新计算 + DEBUG_EXTRA("跳过自动颜色采样,使用外部UV坐标"); } if (texturesDiffuse.size() == 1) faceTexindices.Release(); @@ -8710,6 +8831,18 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel } } +void MeshTexture::GlobalSeamLevelingExternalUV() +{ + // 针对外部UV数据的全局接缝处理 + // 实现更温和的接缝处理算法 +} + +void MeshTexture::LocalSeamLevelingExternalUV() +{ + // 针对外部UV数据的局部接缝处理 + // 实现保留原始UV特征的接缝处理 +} + // New void MeshTexture::GlobalSeamLeveling() { @@ -9400,6 +9533,601 @@ void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeve } } +#include + +// 保存生成的纹理图集 +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 < generatedTextures.size(); ++i) { + if (generatedTextures[i].empty()) { + DEBUG_EXTRA("警告: 纹理 %zu 为空,跳过保存", i); + continue; + } + + // 生成文件名 + std::string filename = outputDir + "/texture_" + std::to_string(i) + ".png"; + + // 使用OpenCV保存图像 + if (cv::imwrite(filename, generatedTextures[i])) { + DEBUG_EXTRA("成功保存纹理: %s (尺寸: %dx%d)", + filename.c_str(), + generatedTextures[i].cols, + generatedTextures[i].rows); + } else { + DEBUG_EXTRA("错误: 无法保存纹理到 %s", filename.c_str()); + return false; + } + } + + 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); + + 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; +} + +Point2f MeshTexture::ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage) { + Point2f imgPoint = camera.ProjectPointP(worldPoint); + + // 检查投影点是否在有效范围内 + if (!sourceImage.image.isInside(imgPoint)) { + // 尝试不同的偏移量来找到最佳投影点 + std::vector testOffsets = { + Point2f(0, sourceImage.image.rows * 0.015f), // 当前使用的偏移 + Point2f(0, -sourceImage.image.rows * 0.015f), // 反向偏移 + Point2f(sourceImage.image.cols * 0.015f, 0), // 水平偏移 + Point2f(0, 0) // 无偏移 + }; + + for (const auto& offset : testOffsets) { + Point2f testPoint = imgPoint + offset; + if (sourceImage.image.isInside(testPoint) && camera.IsInFront(worldPoint)) { + return testPoint; + } + } + } + + return imgPoint; +} + +Point2f MeshTexture::ProjectPointRobust(const Camera& camera, const Vertex& worldPoint, + const Image& sourceImage, float searchRadius) { + Point2f imgPoint = camera.ProjectPointP(worldPoint); + + // 如果投影点有效,直接返回 + if (sourceImage.image.isInside(imgPoint) && camera.IsInFront(worldPoint)) { + return imgPoint; + } + + // 在投影点周围搜索最佳匹配点 + int searchSteps = 5; + float stepSize = searchRadius * sourceImage.image.rows / searchSteps; + + // 在y方向进行搜索(主要偏差方向) + for (int dy = -searchSteps; dy <= searchSteps; ++dy) { + Point2f testPoint = imgPoint + Point2f(0, dy * stepSize); + if (sourceImage.image.isInside(testPoint) && camera.IsInFront(worldPoint)) { + // 可选:进一步验证这个点是否在面片边界内 + return testPoint; + } + } + + // 如果在y方向没找到,尝试x方向 + for (int dx = -searchSteps; dx <= searchSteps; ++dx) { + Point2f testPoint = imgPoint + Point2f(dx * stepSize, 0); + if (sourceImage.image.isInside(testPoint) && camera.IsInFront(worldPoint)) { + return testPoint; + } + } + + // 如果都没找到,返回原始投影点(后续会跳过) + return imgPoint; +} + +Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLabels, const IIndexArr& views, + unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) +{ + // 1. 分析整个模型的UV布局 + AABB2f uvBounds(true); + FOREACH(i, scene.mesh.faceTexcoords) { + const TexCoord& uv = scene.mesh.faceTexcoords[i]; + uvBounds.InsertFull(uv); + } + + // 2. 根据UV范围确定纹理图集尺寸 + const int textureSize = ComputeOptimalTextureSize(uvBounds.ptMin, uvBounds.ptMax, nTextureSizeMultiple); + + // 3. 创建单个纹理图集 + Mesh::Image8U3Arr textures; + Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); + textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); + + DEBUG_EXTRA("生成纹理图集: 尺寸=%dx%d, UV范围=[%.3f,%.3f]-[%.3f,%.3f]", + textureSize, textureSize, + uvBounds.ptMin.x(), uvBounds.ptMin.y(), uvBounds.ptMax.x(), uvBounds.ptMax.y()); + // 4. 为每个面片采样颜色并填充纹理图集 + #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) { + #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坐标和几何信息 + const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; + const Face& face = scene.mesh.faces[faceID]; + const Image& sourceImage = images[idxView]; + + // 计算面片在纹理图集中的边界框 + AABB2f faceUVBounds(true); + for (int i = 0; i < 3; ++i) { + faceUVBounds.InsertFull(uvCoords[i]); + } + + // 将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)); + + // 对面片覆盖的每个纹理像素进行采样 + for (int y = startY; y <= endY; ++y) { + for (int x = startX; x <= endX; ++x) { + const Point2f texCoord((float)x / textureSize, (float)y / textureSize); + + // 检查点是否在三角形内 + Point3f barycentric; + if (!PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { + continue; + } + + // 计算3D空间中的对应点 + const Vertex worldPoint = + vertices[face[0]] * barycentric.x + + vertices[face[1]] * barycentric.y + + vertices[face[2]] * barycentric.z; + + // 将3D点投影到源图像 + // Point2f imgPoint = sourceImage.camera.ProjectPointP(worldPoint); + + // Point2f imgPoint = ProjectPointRobust(sourceImage.camera, worldPoint, sourceImage, 0.02f); + Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage); + + if (!ValidateProjection(worldPoint, sourceImage, imgPoint)) + { + continue; // 跳过几何不一致的采样点 + } + + if (imgPoint.x < -100 || imgPoint.x > sourceImage.image.cols + 100 || + imgPoint.y < -100 || imgPoint.y > sourceImage.image.rows + 100) { + // 投影异常,记录日志用于调试 + DEBUG_EXTRA("异常投影: 图像点(%.1f,%.1f) 超出图像范围(%dx%d)", + imgPoint.x, imgPoint.y, sourceImage.image.cols, sourceImage.image.rows); + } + + // imgPoint.y += sourceImage.image.rows * 0.015f; + + // 检查投影有效性 + if (!sourceImage.image.isInside(imgPoint) || + !sourceImage.camera.IsInFront(worldPoint)) { + continue; + } + + // 从源图像采样颜色(使用双线性插值) + Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint); + + // 将采样颜色写入纹理图集 + #ifdef _USE_OPENMP + #pragma omp critical + #endif + { + textureAtlas(y, x) = sampledColor; + } + } + } + } + + // 5. 应用后处理 + if (fSharpnessWeight > 0) { + // ApplySharpening(textureAtlas, fSharpnessWeight); + } + + // 6. 填充空白区域(使用邻近像素扩散) + // FillEmptyRegions(textureAtlas, colEmpty); + + DEBUG_EXTRA("纹理图集生成完成: %u个面片, 纹理尺寸%dx%d", + scene.mesh.faces.size(), textureSize, textureSize); + + return textures; +} + +bool MeshTexture::ValidateProjection(const Vertex& worldPoint, + const Image& sourceImage, Point2f imgPoint, + float maxReprojectionError) { + // 1. 前向投影:3D点 → 2D图像坐标 + Point2f projectedPoint = sourceImage.camera.ProjectPointP(worldPoint); + + // 2. 计算重投影误差 + float reprojectionError = norm(projectedPoint - imgPoint); + + // 3. 设置误差阈值 + if (reprojectionError > maxReprojectionError) { + DEBUG_EXTRA("重投影误差过大: %.3f像素,跳过该采样点", reprojectionError); + return false; + } + + // 4. 视线方向一致性检查 + if (!sourceImage.camera.IsInFront(worldPoint)) { + DEBUG_EXTRA("点位于相机后方,跳过"); + return false; + } + + return true; +} + +Pixel8U MeshTexture::SampleImageBilinear(const Image8U3& image, const Point2f& point) { + const int x1 = (int)point.x; + const int y1 = (int)point.y; + const int x2 = std::min(x1 + 1, image.cols - 1); + const int y2 = std::min(y1 + 1, image.rows - 1); + + const float dx = point.x - x1; + const float dy = point.y - y1; + + const Pixel8U& p11 = image(y1, x1); + const Pixel8U& p12 = image(y1, x2); + const Pixel8U& p21 = image(y2, x1); + const Pixel8U& p22 = image(y2, x2); + + Pixel8U result; + for (int i = 0; i < 3; ++i) { + result[i] = (uint8_t)( + p11[i] * (1-dx)*(1-dy) + + p12[i] * dx*(1-dy) + + p21[i] * (1-dx)*dy + + p22[i] * dx*dy + ); + } + return result; +} + +void MeshTexture::ProjectFaceToTexture(FIndex faceID, IIndex viewID, + const TexCoord* uv, Image8U3& texture) +{ + // DEBUG_EXTRA("ProjectFaceToTexture 1"); + const Image& image = images[viewID]; + const Face& face = scene.mesh.faces[faceID]; + + // 计算面的包围盒(在纹理空间中) + Point2f minUV(FLT_MAX, FLT_MAX), maxUV(FLT_MIN, FLT_MIN); + for (int i = 0; i < 3; ++i) { + minUV.x = MIN(minUV.x, uv[i].x); + minUV.y = MIN(minUV.y, uv[i].y); + maxUV.x = MAX(maxUV.x, uv[i].x); + maxUV.y = MAX(maxUV.y, uv[i].y); + } + + // 将UV坐标转换到纹理像素坐标 + const int texWidth = texture.cols; + const int texHeight = texture.rows; + + const int startX = MAX(0, (int)(minUV.x * texWidth)); + const int startY = MAX(0, (int)(minUV.y * texHeight)); + const int endX = MIN(texWidth - 1, (int)(maxUV.x * texWidth)); + const int endY = MIN(texHeight - 1, (int)(maxUV.y * texHeight)); + + // 对纹理中的每个像素进行采样 + for (int y = startY; y <= endY; ++y) { + for (int x = startX; x <= endX; ++x) { + // 将像素坐标转换回UV坐标 + const Point2f texCoord((float)x / texWidth, (float)y / texHeight); + + // DEBUG_EXTRA("ProjectFaceToTexture1 %d", x); + // 检查点是否在三角形内[6,8](@ref) + Point3f bary; + if (!PointInTriangle(texCoord, uv[0], uv[1], uv[2], bary)) { + continue; + } + + // DEBUG_EXTRA("ProjectFaceToTexture2 %d", x); + // 计算3D空间中的对应点 + const Vertex worldPoint = + vertices[face[0]] * bary.x + + vertices[face[1]] * bary.y + + vertices[face[2]] * bary.z; + + // 将3D点投影到图像 + Point2f imgPoint; + + // 确保 worldPoint 的类型与相机参数匹配 + const TPoint3 worldPointDouble( + static_cast(worldPoint.x), + static_cast(worldPoint.y), + static_cast(worldPoint.z) + ); + + imgPoint = image.camera.ProjectPoint(worldPointDouble); + + // 添加有效性检查 + if (!image.camera.IsInFront(worldPoint)) { + continue; // 点不在相机前方,跳过 + } + + // DEBUG_EXTRA("ProjectFaceToTexture3 %d", x); + // 检查投影点是否在图像范围内 + if (!image.camera.IsInside(imgPoint, Point2f(image.width, image.height))) { + continue; // 点不在图像内,跳过 + } + + // DEBUG_EXTRA("ProjectFaceToTexture4 %d", x); + // 检查投影点是否在图像范围内 + // 从图像中采样颜色(使用双线性插值) + if (image.image.isInside(imgPoint)) { + // 获取图像中对应点的颜色[1,4](@ref) + const int imgX = (int)imgPoint.x; + const int imgY = (int)imgPoint.y; + + // 边界检查 + if (imgX >= 0 && imgX < image.image.cols - 1 && + imgY >= 0 && imgY < image.image.rows - 1) { + + // 双线性插值参数 + const float dx = imgPoint.x - imgX; + const float dy = imgPoint.y - imgY; + const float w1 = (1 - dx) * (1 - dy); + const float w2 = dx * (1 - dy); + const float w3 = (1 - dx) * dy; + const float w4 = dx * dy; + + // 获取四个相邻像素的颜色 + const Pixel8U& p1 = image.image(imgY, imgX); + const Pixel8U& p2 = image.image(imgY, imgX + 1); + const Pixel8U& p3 = image.image(imgY + 1, imgX); + const Pixel8U& p4 = image.image(imgY + 1, imgX + 1); + + // 计算加权平均颜色 + Pixel8U finalColor; + finalColor.r = (uint8_t)(p1.r * w1 + p2.r * w2 + p3.r * w3 + p4.r * w4); + finalColor.g = (uint8_t)(p1.g * w1 + p2.g * w2 + p3.g * w3 + p4.g * w4); + finalColor.b = (uint8_t)(p1.b * w1 + p2.b * w2 + p3.b * w3 + p4.b * w4); + + // DEBUG_EXTRA("ProjectFaceToTexture5 finalColor=(%d,%d,%d)", finalColor.r, finalColor.g, finalColor.b); + // 将颜色赋给纹理像素[1](@ref) + texture(y, x) = finalColor; + } else { + // 边界情况:使用最近邻插值 + const int safeX = std::min(std::max(0, (int)imgPoint.x), image.image.cols - 1); + const int safeY = std::min(std::max(0, (int)imgPoint.y), image.image.rows - 1); + texture(y, x) = image.image(safeY, safeX); + // DEBUG_EXTRA("ProjectFaceToTexture6 %d", x); + } + } + } + } +} + +#include + +/** + * @brief 使用重心坐标法判断点是否在三角形内,并计算重心坐标 + * @param p 需要判断的点(纹理坐标空间中的点) + * @param a 三角形的第一个顶点 + * @param b 三角形的第二个顶点 + * @param c 三角形的第三个顶点 + * @param bary 输出的重心坐标 (1-u-v, u, v) + * @return bool 如果点在三角形内返回true,否则返回false + */ +bool MeshTexture::PointInTriangle(const Point2f& p, const Point2f& a, const Point2f& b, const Point2f& c, Point3f& bary) +{ + // 添加调试输出 + // DEBUG_EXTRA("PointInTriangle - Input: p(%.6f,%.6f), a(%.6f,%.6f), b(%.6f,%.6f), c(%.6f,%.6f)", + // p.x, p.y, a.x, a.y, b.x, b.y, c.x, c.y); + + // 检查输入有效性 + if (!std::isfinite(p.x) || !std::isfinite(p.y) || + !std::isfinite(a.x) || !std::isfinite(a.y) || + !std::isfinite(b.x) || !std::isfinite(b.y) || + !std::isfinite(c.x) || !std::isfinite(c.y)) { + // DEBUG_EXTRA("PointInTriangle - Invalid input coordinates"); + return false; + } + + // 计算边向量 + Point2f v0 = b - a; + Point2f v1 = c - a; + Point2f v2 = p - a; + + // 计算必要的点积 + float dot00 = v0.x * v0.x + v0.y * v0.y; + float dot01 = v0.x * v1.x + v0.y * v1.y; + float dot02 = v0.x * v2.x + v0.y * v2.y; + float dot11 = v1.x * v1.x + v1.y * v1.y; + float dot12 = v1.x * v2.x + v1.y * v2.y; + + // 计算分母(三角形平行四边形面积的两倍) + float denom = dot00 * dot11 - dot01 * dot01; + + // 处理退化三角形情况(面积接近0) + const float epsilon = 1e-10f; + if (std::abs(denom) < epsilon) { + // DEBUG_EXTRA("PointInTriangle - Degenerate triangle, denom=%.10f", denom); + // return false; + } + + // 计算重心坐标参数 + float invDenom = 1.0f / denom; + float u = (dot11 * dot02 - dot01 * dot12) * invDenom; + float v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + // DEBUG_EXTRA("PointInTriangle - u=%.6f, v=%.6f, u+v=%.6f", u, v, u+v); + + // 检查点是否在三角形内(使用更宽松的容差) + if (u >= -epsilon && v >= -epsilon && (u + v) <= 1.0f + epsilon) { + // 点在三角形内,计算完整的重心坐标 + bary.x = 1.0f - u - v; + bary.y = u; + bary.z = v; + // DEBUG_EXTRA("PointInTriangle - Point INSIDE triangle, bary(%.3f,%.3f,%.3f)", bary.x, bary.y, bary.z); + return true; + } + + // DEBUG_EXTRA("PointInTriangle - Point OUTSIDE triangle"); + return false; +} + +int MeshTexture::ComputeOptimalTextureSize(const Point2f& uvMin, const Point2f& uvMax, unsigned nTextureSizeMultiple) { + + // 1. 计算UV坐标的实际覆盖范围 + const float uvRangeX = uvMax.x - uvMin.x; + const float uvRangeY = uvMax.y - uvMin.y; + + + // 如果UV范围无效,返回默认尺寸 + if (uvRangeX <= 0.0f || uvRangeY <= 0.0f) { + return 1024; // 默认回退尺寸 + } + + // 2. 基于UV覆盖范围计算基础纹理尺寸 + // 假设我们希望纹理密度为:每单位UV空间对应N个像素 + const float pixelsPerUV = 256.0f; // 可配置的密度系数 + int baseSizeX = static_cast(uvRangeX * pixelsPerUV); + int baseSizeY = static_cast(uvRangeY * pixelsPerUV); + + // 取较大者作为基础尺寸,以确保覆盖整个UV范围 + int baseSize = std::max(baseSizeX, baseSizeY); + + DEBUG_EXTRA("%d, %d", nTextureSizeMultiple, baseSize); + // 3. 应用纹理尺寸倍数约束 + baseSize = (baseSize + nTextureSizeMultiple - 1) / nTextureSizeMultiple * nTextureSizeMultiple; + + // 4. 设备能力限制 + int maxTextureSize = 4096; // 默认最大值 + int minTextureSize = 64; // 默认最小值 + + // 此处可查询设备实际支持的最大纹理尺寸 + // glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + + // 5. 质量等级调整(可根据OPT中的质量设置进行调整) + int qualityLevel = 1; // 默认中等质量 + switch (qualityLevel) { + case 0: // 低质量:缩小纹理尺寸 + baseSize = baseSize / 2; + break; + case 1: // 中等质量:保持原计算尺寸 + // baseSize 保持不变 + break; + case 2: // 高质量:适度增大尺寸(如有足够内存) + baseSize = std::min(baseSize * 2, maxTextureSize); + break; + } + + DEBUG_EXTRA("(UV范围: [%.3f, %.3f]), %d, %d, %d", uvRangeX, uvRangeY, nTextureSizeMultiple, baseSize, minTextureSize); + // 6. 最终钳制到有效范围 + baseSize = (std::max)(minTextureSize, (std::min)(baseSize, maxTextureSize)); + + // 7. 确保尺寸为2的幂次(兼容性考虑,可选) + int finalSize = 1; + while (finalSize < baseSize) { + finalSize <<= 1; + if (finalSize >= maxTextureSize) { + finalSize = maxTextureSize; + break; + } + } + + DEBUG_EXTRA("计算出的最优纹理尺寸: %d (UV范围: [%.3f, %.3f])", + finalSize, uvRangeX, uvRangeY); + + return finalSize; +} + // 保存遮挡数据到文件 void Scene::SaveVisibleFacesData(std::map>& visible_faces_map, std::unordered_set& face_visible_relative, @@ -9536,8 +10264,11 @@ bool Scene::LoadVisibleFacesData(std::map>& bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsigned minCommonCameras, float fOutlierThreshold, float fRatioDataSmoothness, bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int nIgnoreMaskLabel, int maxTextureSize, const IIndexArr& views, const SEACAVE::String& baseFileName, bool bOriginFaceview, - const std::string& inputFileName, const std::string& meshFileName) + const std::string& inputFileName, const std::string& meshFileName, bool bUseExistingUV, const std::string& strUVMeshFileName) { + nTextureSizeMultiple = 8192; // 8192 4096 + + // if (!bOriginFaceview && !bUseExistingUV) if (!bOriginFaceview) { // 确保网格拓扑结构已计算 @@ -9647,7 +10378,7 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi // if (false) { TD_TIMER_STARTD(); - if (!texture.FaceViewSelection3(minCommonCameras, fOutlierThreshold, fRatioDataSmoothness, nIgnoreMaskLabel, views)) + if (!texture.FaceViewSelection3(minCommonCameras, fOutlierThreshold, fRatioDataSmoothness, nIgnoreMaskLabel, views, bUseExistingUV)) return false; DEBUG_EXTRA("First pass (virtual faces) completed: %u faces (%s)", mesh.faces.size(), TD_TIMER_GET_FMT().c_str()); } @@ -9689,13 +10420,47 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi DEBUG_EXTRA("Assigning the best view to each face completed: %u faces (%s)", mesh.faces.size(), TD_TIMER_GET_FMT().c_str()); } + // mesh.CheckUVValid(); + + DEBUG_EXTRA("TextureMesh %b, %s", bUseExistingUV, strUVMeshFileName.c_str()); + if (bUseExistingUV && !strUVMeshFileName.empty()) { + VERBOSE("1faceTexcoords.size=%d, faces.size=%d", mesh.faceTexcoords.size(), mesh.faces.size() * 3); + // 使用预计算UV模式 + if (!mesh.Load(MAKE_PATH_SAFE(strUVMeshFileName), true)) { + VERBOSE("error: cannot load mesh file with UV coordinates"); + return false; // 注意:在成员函数中,返回 false 表示失败 + } + + VERBOSE("2faceTexcoords.size=%d, faces.size=%d", mesh.faceTexcoords.size(), mesh.faces.size() * 3); + // mesh.CheckUVValid(); + //* + // 确保网格包含UV坐标 + if (mesh.faceTexcoords.empty()) { + VERBOSE("error: the specified mesh does not contain UV coordinates"); + return false; + } + + // 使用新的纹理生成方法 + MeshTexture texture(*this, nResolutionLevel, nMinResolution); + if (!texture.TextureWithExistingUV(views, nIgnoreMaskLabel, fOutlierThreshold, nTextureSizeMultiple, colEmpty, fSharpnessWeight)){ + return false; + } + //*/ + + // return true; + } + + // mesh.CheckUVValid(); + // generate the texture image and atlas { TD_TIMER_STARTD(); - texture.GenerateTexture(bGlobalSeamLeveling, bLocalSeamLeveling, nTextureSizeMultiple, nRectPackingHeuristic, colEmpty, fSharpnessWeight, maxTextureSize, baseFileName, bOriginFaceview); + texture.GenerateTexture(bGlobalSeamLeveling, bLocalSeamLeveling, nTextureSizeMultiple, nRectPackingHeuristic, colEmpty, fSharpnessWeight, maxTextureSize, baseFileName, bOriginFaceview, this); DEBUG_EXTRA("Generating texture atlas and image completed: %u patches, %u image size, %u textures (%s)", texture.texturePatches.size(), mesh.texturesDiffuse[0].width(), mesh.texturesDiffuse.size(), TD_TIMER_GET_FMT().c_str()); } + // mesh.CheckUVValid(); + return true; } // TextureMesh