Browse Source

手动UV分支

ManualUV
hesuicong 1 month ago
parent
commit
7fd81ffbea
  1. 9
      apps/TextureMesh/TextureMesh.cpp
  2. 129
      libs/IO/OBJ.cpp
  3. 3
      libs/IO/OBJ.h
  4. 72
      libs/MVS/Mesh.cpp
  5. 10
      libs/MVS/Mesh.h
  6. 2
      libs/MVS/Scene.h
  7. 787
      libs/MVS/SceneTexture.cpp

9
apps/TextureMesh/TextureMesh.cpp

@ -94,6 +94,9 @@ unsigned nMaxThreads;
int nMaxTextureSize; int nMaxTextureSize;
String strExportType; String strExportType;
String strConfigFileName; String strConfigFileName;
bool bUseExistingUV;
String strUVMeshFileName;
boost::program_options::variables_map vm; boost::program_options::variables_map vm;
} // namespace OPT } // namespace OPT
@ -163,6 +166,8 @@ bool Application::Initialize(size_t argc, LPCTSTR* argv)
("image-folder", boost::program_options::value<std::string>(&OPT::strImageFolder)->default_value(COLMAP_IMAGES_FOLDER), "folder to the undistorted images") ("image-folder", boost::program_options::value<std::string>(&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") ("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") ("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<std::string>(&OPT::strUVMeshFileName), "mesh file with pre-computed UV coordinates")
; ;
// hidden options, allowed both on command line and // 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, 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::bGlobalSeamLeveling, OPT::bLocalSeamLeveling, OPT::nTextureSizeMultiple, OPT::nRectPackingHeuristic, Pixel8U(OPT::nColEmpty),
OPT::fSharpnessWeight, OPT::nIgnoreMaskLabel, OPT::nMaxTextureSize, views, baseFileName, OPT::bOriginFaceview, 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; 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()); 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 // save the final mesh
scene.mesh.Save(baseFileName+OPT::strExportType); scene.mesh.Save(baseFileName+OPT::strExportType,cList<String>(),true,OPT::bUseExistingUV);
#if TD_VERBOSE != TD_VERBOSE_OFF #if TD_VERBOSE != TD_VERBOSE_OFF
if (VERBOSITY_LEVEL > 2) if (VERBOSITY_LEVEL > 2)
scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+OPT::strExportType); scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+OPT::strExportType);

129
libs/IO/OBJ.cpp

@ -48,6 +48,7 @@ ObjModel::MaterialLib::MaterialLib()
bool ObjModel::MaterialLib::Save(const String& prefix, bool texLossless) const 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()); std::ofstream out((prefix+".mtl").c_str());
if (!out.good()) if (!out.good())
return false; return false;
@ -130,7 +131,7 @@ bool ObjModel::MaterialLib::Load(const String& fileName)
// S T R U C T S /////////////////////////////////////////////////// // 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()) if (vertices.empty())
return false; return false;
@ -140,6 +141,9 @@ bool ObjModel::Save(const String& fileName, unsigned precision, bool texLossless
if (!material_lib.Save(prefix, texLossless)) if (!material_lib.Save(prefix, texLossless))
return false; return false;
if (bUseExistingUV)
return true;
std::ofstream out((prefix + ".obj").c_str()); std::ofstream out((prefix + ".obj").c_str());
if (!out.good()) if (!out.good())
return false; return false;
@ -269,6 +273,129 @@ bool ObjModel::Load(const String& fileName)
return !vertices.empty(); return !vertices.empty();
} }
bool ObjModel::LoadUV(const String& fileName, SEACAVE::cList<TexCoord,const TexCoord&,0,8192,uint32_t> &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<String> 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) ObjModel::Group& ObjModel::AddGroup(const String& material_name)
{ {

3
libs/IO/OBJ.h

@ -93,9 +93,10 @@ public:
ObjModel() {} ObjModel() {}
// Saves the obj model to an .obj file, its material lib and the materials with the given file name // 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 // 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 Load(const String& fileName);
bool LoadUV(const String& fileName, SEACAVE::cList<TexCoord,const TexCoord&,0,8192,uint32_t> &faceTexcoords, bool bLoadUV = false);
// Creates a new group with the given material name // Creates a new group with the given material name
Group& AddGroup(const String& material_name); Group& AddGroup(const String& material_name);

72
libs/MVS/Mesh.cpp

@ -1191,15 +1191,14 @@ namespace BasicPLY {
} // namespace MeshInternal } // namespace MeshInternal
// import the mesh from the given file // import the mesh from the given file
bool Mesh::Load(const String& fileName) bool Mesh::Load(const String& fileName, bool bLoadUV)
{ {
TD_TIMER_STARTD(); TD_TIMER_STARTD();
const String ext(Util::getFileExt(fileName).ToLower()); const String ext(Util::getFileExt(fileName).ToLower());
bool ret; bool ret;
if (ext == _T(".obj")) if (ext == _T(".obj"))
ret = LoadOBJ(fileName); ret = LoadOBJ(fileName, bLoadUV);
else else if (ext == _T(".gltf") || ext == _T(".glb"))
if (ext == _T(".gltf") || ext == _T(".glb"))
ret = LoadGLTF(fileName, ext == _T(".glb")); ret = LoadGLTF(fileName, ext == _T(".glb"));
else else
ret = LoadPLY(fileName); 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()); DEBUG_EXTRA("Mesh loaded: %u vertices, %u faces (%s)", vertices.size(), faces.size(), TD_TIMER_GET_FMT().c_str());
return true; 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 // import the mesh as a PLY file
bool Mesh::LoadPLY(const String& fileName) bool Mesh::LoadPLY(const String& fileName)
{ {
@ -1302,17 +1338,29 @@ bool Mesh::LoadPLY(const String& fileName)
return true; return true;
} }
// import the mesh as a OBJ file // import the mesh as a OBJ file
bool Mesh::LoadOBJ(const String& fileName) bool Mesh::LoadOBJ(const String& fileName, bool bLoadUV)
{ {
ASSERT(!fileName.empty()); ASSERT(!fileName.empty());
Release(); Release();
// open and parse OBJ file // open and parse OBJ file
ObjModel model; ObjModel model;
if (!model.Load(fileName)) { if (bLoadUV)
DEBUG_EXTRA("error: invalid OBJ file"); {
if (!model.LoadUV(fileName, faceTexcoords, true)) {
DEBUG_EXTRA("error: LoadUV invalid OBJ file %s", fileName.c_str());
return false; 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()) { if (model.get_vertices().empty() || model.get_groups().empty()) {
DEBUG_EXTRA("error: invalid mesh file"); DEBUG_EXTRA("error: invalid mesh file");
@ -1340,11 +1388,13 @@ bool Mesh::LoadOBJ(const String& fileName)
for (const ObjModel::Face& f: group.faces) { for (const ObjModel::Face& f: group.faces) {
ASSERT(f.vertices[0] != NO_ID); ASSERT(f.vertices[0] != NO_ID);
faces.emplace_back(f.vertices[0], f.vertices[1], f.vertices[2]); faces.emplace_back(f.vertices[0], f.vertices[1], f.vertices[2]);
/*
if (f.texcoords[0] != NO_ID) { if (f.texcoords[0] != NO_ID) {
for (int i=0; i<3; ++i) for (int i=0; i<3; ++i)
faceTexcoords.emplace_back(model.get_texcoords()[f.texcoords[i]]); faceTexcoords.emplace_back(model.get_texcoords()[f.texcoords[i]]);
faceTexindices.emplace_back((TexIndex)groupIdx); faceTexindices.emplace_back((TexIndex)groupIdx);
} }
*/
if (f.normals[0] != NO_ID) { if (f.normals[0] != NO_ID) {
Normal& n = faceNormals.emplace_back(Normal::ZERO); Normal& n = faceNormals.emplace_back(Normal::ZERO);
for (int i=0; i<3; ++i) 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 // export the mesh to the given file
bool Mesh::Save(const String& fileName, const cList<String>& comments, bool bBinary) const bool Mesh::Save(const String& fileName, const cList<String>& comments, bool bBinary, bool bUseExistingUV) const
{ {
TD_TIMER_STARTD(); TD_TIMER_STARTD();
const String ext(Util::getFileExt(fileName).ToLower()); const String ext(Util::getFileExt(fileName).ToLower());
bool ret; bool ret;
if (ext == _T(".obj")) if (ext == _T(".obj"))
ret = SaveOBJ(fileName); ret = SaveOBJ(fileName, bUseExistingUV);
else else
if (ext == _T(".gltf") || ext == _T(".glb")) if (ext == _T(".gltf") || ext == _T(".glb"))
ret = SaveGLTF(fileName, ext == _T(".glb")); ret = SaveGLTF(fileName, ext == _T(".glb"));
@ -1538,7 +1588,7 @@ bool Mesh::SavePLY(const String& fileName, const cList<String>& comments, bool b
return true; return true;
} }
// export the mesh as a OBJ file // 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()); ASSERT(!fileName.empty());
Util::ensureFolder(fileName); Util::ensureFolder(fileName);
@ -1597,7 +1647,7 @@ bool Mesh::SaveOBJ(const String& fileName) const
pMaterial->diffuse_map = texturesDiffuse[idxTexture]; 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 // export the mesh as a GLTF file
template <typename T> template <typename T>

10
libs/MVS/Mesh.h

@ -262,8 +262,8 @@ public:
bool TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices={}, unsigned borderSize=3, unsigned textureSize=4096); bool TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices={}, unsigned borderSize=3, unsigned textureSize=4096);
// file IO // file IO
bool Load(const String& fileName); bool Load(const String& fileName, bool bLoadUV=false);
bool Save(const String& fileName, const cList<String>& comments=cList<String>(), bool bBinary=true) const; bool Save(const String& fileName, const cList<String>& comments=cList<String>(), bool bBinary=true, bool bUseExistingUV=false) const;
bool Save(const FacesChunkArr&, const String& fileName, const cList<String>& comments=cList<String>(), bool bBinary=true) const; bool Save(const FacesChunkArr&, const String& fileName, const cList<String>& comments=cList<String>(), bool bBinary=true) const;
static bool Save(const VertexArr& vertices, const String& fileName, bool bBinary=true); 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(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]; } 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: protected:
bool LoadPLY(const String& fileName); 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 LoadGLTF(const String& fileName, bool bBinary=true);
bool SavePLY(const String& fileName, const cList<String>& comments=cList<String>(), bool bBinary=true, bool bTexLossless=true) const; bool SavePLY(const String& fileName, const cList<String>& comments=cList<String>(), 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; bool SaveGLTF(const String& fileName, bool bBinary=true) const;
#ifdef _USE_CUDA #ifdef _USE_CUDA

2
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 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), 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, 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); std::string runPython(const std::string& command);
bool is_face_visible(const std::string& image_name, int face_index); bool is_face_visible(const std::string& image_name, int face_index);

787
libs/MVS/SceneTexture.cpp

@ -441,7 +441,7 @@ public:
MeshTexture(Scene& _scene, unsigned _nResolutionLevel=0, unsigned _nMinResolution=640); MeshTexture(Scene& _scene, unsigned _nResolutionLevel=0, unsigned _nMinResolution=640);
~MeshTexture(); ~MeshTexture();
void ListVertexFaces(); void ListVertexFaces(bool bUseExistingUV = false);
bool ListCameraFaces(FaceDataViewArr&, float fOutlierThreshold, int nIgnoreMaskLabel, const IIndexArr& views, bool bUseVirtualFaces); 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); 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 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 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); 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); void CreateAdaptiveVirtualFaces(FaceDataViewArr& facesDatas, FaceDataViewArr& virtualFacesDatas, VirtualFaceIdxsArr& virtualFaces, unsigned minCommonCameras);
bool ShouldMergeVirtualFace(const MeshTexture::FaceDataViewArr& facesDatas, const Mesh::FaceIdxArr& currentVirtualFace, FIndex candidateFace, unsigned minCommonCameras); bool ShouldMergeVirtualFace(const MeshTexture::FaceDataViewArr& facesDatas, const Mesh::FaceIdxArr& currentVirtualFace, FIndex candidateFace, unsigned minCommonCameras);
@ -474,9 +474,21 @@ public:
void GlobalSeamLeveling3(); void GlobalSeamLeveling3();
void LocalSeamLeveling(); void LocalSeamLeveling();
void LocalSeamLeveling3(); 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 // Bruce
//* //*
template <typename PIXEL> template <typename PIXEL>
@ -764,8 +776,9 @@ void MeshTexture::ComputeFaceCurvatures() const {
// extract array of triangles incident to each vertex // extract array of triangles incident to each vertex
// and check each vertex if it is at the boundary or not // and check each vertex if it is at the boundary or not
void MeshTexture::ListVertexFaces() void MeshTexture::ListVertexFaces(bool bUseExistingUV)
{ {
// if (!bUseExistingUV)
scene.mesh.EmptyExtra(); scene.mesh.EmptyExtra();
scene.mesh.ListIncidenteFaces(); scene.mesh.ListIncidenteFaces();
scene.mesh.ListBoundaryVertices(); scene.mesh.ListBoundaryVertices();
@ -6067,10 +6080,10 @@ bool MeshTexture::ShouldMergeVirtualFace(
// return false; // 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 // extract array of triangles incident to each vertex
ListVertexFaces(); ListVertexFaces(bUseExistingUV);
// create texture patches // create texture patches
{ {
@ -7156,6 +7169,7 @@ bool MeshTexture::FaceViewSelection3( unsigned minCommonCameras, float fOutlierT
mapIdxPatch.Insert(numPatches); mapIdxPatch.Insert(numPatches);
} }
} }
return true; 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 // Bruce
// bGlobalSeamLeveling = false; // bGlobalSeamLeveling = false;
// bLocalSeamLeveling = false; // bLocalSeamLeveling = false;
@ -8340,11 +8362,19 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
int border = 2; int border = 2;
if (!bOriginFaceview) if (!bOriginFaceview)
border = 4; border = 4;
// ===== 修改:只在非外部UV模式下初始化faceTexcoords =====
if (!bUseExternalUV) {
faceTexcoords.resize(faces.size()*3); faceTexcoords.resize(faces.size()*3);
faceTexindices.resize(faces.size()); faceTexindices.resize(faces.size());
}
#ifdef TEXOPT_USE_OPENMP #ifdef TEXOPT_USE_OPENMP
// LOG_OUT() << "def TEXOPT_USE_OPENMP" << std::endl; // LOG_OUT() << "def TEXOPT_USE_OPENMP" << std::endl;
const unsigned numPatches(texturePatches.size()-1); const unsigned numPatches(texturePatches.size()-1);
// ===== 修改:只在非外部UV模式下执行投影计算 =====
if (!bUseExternalUV) {
#pragma omp parallel for schedule(dynamic) #pragma omp parallel for schedule(dynamic)
for (int_t idx=0; idx<(int_t)numPatches; ++idx) { for (int_t idx=0; idx<(int_t)numPatches; ++idx) {
TexturePatch& texturePatch = texturePatches[(uint32_t)idx]; TexturePatch& texturePatch = texturePatches[(uint32_t)idx];
@ -8361,6 +8391,31 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
texcoords[i] = imageData.camera.ProjectPointP(vertices[face[i]]); texcoords[i] = imageData.camera.ProjectPointP(vertices[face[i]]);
ASSERT(imageData.image.isInsideWithBorder(texcoords[i], border)); ASSERT(imageData.image.isInsideWithBorder(texcoords[i], border));
aabb.InsertFull(texcoords[i]); aabb.InsertFull(texcoords[i]);
if (false)
{
const Point2f& a = texcoords[0];
const Point2f& b = texcoords[1];
const Point2f& c = texcoords[2];
// 计算边向量
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-6f;
if (std::abs(denom) < epsilon)
{
// DEBUG_EXTRA("PointInTriangle - Degenerate triangle, denom=%.10f", denom);
}
else
{
DEBUG_EXTRA("PointInTriangle Yes idxFace=%d", idxFace);
}
}
} }
} }
// compute relative texture coordinates // compute relative texture coordinates
@ -8415,9 +8470,41 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
for (const FIndex idxFace: texturePatch.faces) { for (const FIndex idxFace: texturePatch.faces) {
TexCoord* texcoords = faceTexcoords.data()+idxFace*3; TexCoord* texcoords = faceTexcoords.data()+idxFace*3;
for (int v=0; v<3; ++v) for (int v=0; v<3; ++v)
{
texcoords[v] -= offset; texcoords[v] -= offset;
if (false)
{
const Point2f& a = texcoords[0];
const Point2f& b = texcoords[1];
const Point2f& c = texcoords[2];
// 计算边向量
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-6f;
if (std::abs(denom) < epsilon)
{
// DEBUG_EXTRA("PointInTriangle - Degenerate triangle, denom=%.10f", denom);
} }
else
{
// DEBUG_EXTRA("PointInTriangle Yes idxFace=%d", idxFace);
} }
}
}
}
}
}
// ===== 修改:只在非外部UV模式下初始化最后一个patch =====
if (!bUseExternalUV)
{ {
// init last patch to point to a small uniform color patch // init last patch to point to a small uniform color patch
TexturePatch& texturePatch = texturePatches.back(); TexturePatch& texturePatch = texturePatches.back();
@ -8442,7 +8529,12 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
// perform global seam leveling // perform global seam leveling
if (bGlobalSeamLeveling) { if (bGlobalSeamLeveling) {
TD_TIMER_STARTD(); TD_TIMER_STARTD();
if (bUseExternalUV) {
// 外部UV数据可能需要更温和的接缝处理
GlobalSeamLevelingExternalUV();
} else {
GlobalSeamLeveling3(); 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());
} }
@ -8450,12 +8542,20 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
if (bLocalSeamLeveling) { if (bLocalSeamLeveling) {
TD_TIMER_STARTD(); TD_TIMER_STARTD();
// LocalSeamLeveling(); // LocalSeamLeveling();
if (bUseExternalUV) {
// 外部UV数据可能需要不同的局部接缝处理
LocalSeamLevelingExternalUV();
} else {
LocalSeamLeveling3(); 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());
// ===== 修改:纹理块合并逻辑调整 =====
// 外部UV数据可能需要跳过纹理块合并,因为UV已经固定
if (!bUseExternalUV) {
// merge texture patches with overlapping rectangles // merge texture patches with overlapping rectangles
for (unsigned i=0; i<texturePatches.size()-1; ++i) { for (unsigned i=0; i<texturePatches.size()-1; ++i) {
TexturePatch& texturePatchBig = texturePatches[i]; TexturePatch& texturePatchBig = texturePatches[i];
@ -8480,6 +8580,7 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
texturePatches.RemoveAtMove(j--); texturePatches.RemoveAtMove(j--);
} }
} }
}
LOG_OUT() << "Second loop completed" << std::endl; LOG_OUT() << "Second loop completed" << std::endl;
// create texture // create texture
@ -8518,6 +8619,19 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
typeHeuristic = 1; typeHeuristic = 1;
} }
int textureSize = 0; int textureSize = 0;
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()) { while (!unplacedRects.empty()) {
TD_TIMER_STARTD(); TD_TIMER_STARTD();
if (textureSize == 0) { if (textureSize == 0) {
@ -8560,8 +8674,11 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
} }
} }
} }
}
LOG_OUT() << "Third loop completed" << std::endl; LOG_OUT() << "Third loop completed" << std::endl;
Mesh::FaceIdxArr emptyFaceIndexes; Mesh::FaceIdxArr emptyFaceIndexes;
if (!bUseExternalUV) {
#ifdef TEXOPT_USE_OPENMP #ifdef TEXOPT_USE_OPENMP
#pragma omp parallel for schedule(dynamic) #pragma omp parallel for schedule(dynamic)
@ -8646,6 +8763,10 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
} }
} }
} }
} else {
// 外部UV数据:直接使用现有的纹理坐标,不需要重新计算
DEBUG_EXTRA("跳过自动颜色采样,使用外部UV坐标");
}
if (texturesDiffuse.size() == 1) if (texturesDiffuse.size() == 1)
faceTexindices.Release(); faceTexindices.Release();
@ -8710,6 +8831,18 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
} }
} }
void MeshTexture::GlobalSeamLevelingExternalUV()
{
// 针对外部UV数据的全局接缝处理
// 实现更温和的接缝处理算法
}
void MeshTexture::LocalSeamLevelingExternalUV()
{
// 针对外部UV数据的局部接缝处理
// 实现保留原始UV特征的接缝处理
}
// New // New
void MeshTexture::GlobalSeamLeveling() void MeshTexture::GlobalSeamLeveling()
{ {
@ -9400,6 +9533,601 @@ void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeve
} }
} }
#include <opencv2/imgcodecs.hpp>
// 保存生成的纹理图集
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<Point2f> 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<double> worldPointDouble(
static_cast<double>(worldPoint.x),
static_cast<double>(worldPoint.y),
static_cast<double>(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 <cmath>
/**
* @brief 使
* @param p
* @param a
* @param b
* @param c
* @param bary (1-u-v, u, v)
* @return bool truefalse
*/
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<int>(uvRangeX * pixelsPerUV);
int baseSizeY = static_cast<int>(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<std::string, std::unordered_set<int>>& visible_faces_map, void Scene::SaveVisibleFacesData(std::map<std::string, std::unordered_set<int>>& visible_faces_map,
std::unordered_set<int>& face_visible_relative, std::unordered_set<int>& face_visible_relative,
@ -9536,8 +10264,11 @@ bool Scene::LoadVisibleFacesData(std::map<std::string, std::unordered_set<int>>&
bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsigned minCommonCameras, float fOutlierThreshold, float fRatioDataSmoothness, 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, 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, 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) if (!bOriginFaceview)
{ {
// 确保网格拓扑结构已计算 // 确保网格拓扑结构已计算
@ -9647,7 +10378,7 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
// if (false) // if (false)
{ {
TD_TIMER_STARTD(); TD_TIMER_STARTD();
if (!texture.FaceViewSelection3(minCommonCameras, fOutlierThreshold, fRatioDataSmoothness, nIgnoreMaskLabel, views)) if (!texture.FaceViewSelection3(minCommonCameras, fOutlierThreshold, fRatioDataSmoothness, nIgnoreMaskLabel, views, bUseExistingUV))
return false; return false;
DEBUG_EXTRA("First pass (virtual faces) completed: %u faces (%s)", mesh.faces.size(), TD_TIMER_GET_FMT().c_str()); 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()); 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 // generate the texture image and atlas
{ {
TD_TIMER_STARTD(); 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()); 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; return true;
} // TextureMesh } // TextureMesh

Loading…
Cancel
Save