Compare commits

...

2 Commits

  1. 37
      .gitignore
  2. 9
      apps/TextureMesh/TextureMesh.cpp
  3. 129
      libs/IO/OBJ.cpp
  4. 3
      libs/IO/OBJ.h
  5. 72
      libs/MVS/Mesh.cpp
  6. 10
      libs/MVS/Mesh.h
  7. 2
      libs/MVS/Scene.h
  8. 787
      libs/MVS/SceneTexture.cpp

37
.gitignore vendored

@ -37,3 +37,40 @@ CMakeSettings.json @@ -37,3 +37,40 @@ CMakeSettings.json
out/
bin*/
make*/
libs/MVS/texture_output/texture_0.png
libs/MVS/texture_output/texture_1.png
libs/MVS/texture_output/texture_2.png
libs/MVS/texture_output/texture_3.png
libs/MVS/texture_output/texture_4.png
libs/MVS/texture_output/texture_5.png
libs/MVS/texture_output/texture_6.png
libs/MVS/texture_output/texture_7.png
libs/MVS/texture_output/texture_8.png
libs/MVS/texture_output/texture_9.png
libs/MVS/texture_output/texture_10.png
libs/MVS/texture_output/texture_11.png
libs/MVS/texture_output/texture_12.png
libs/MVS/texture_output/texture_13.png
libs/MVS/texture_output/texture_14.png
libs/MVS/texture_output/texture_15.png
libs/MVS/texture_output/texture_16.png
libs/MVS/texture_output/texture_17.png
libs/MVS/texture_output/texture_18.png
libs/MVS/texture_output/texture_19.png
libs/MVS/texture_output/texture_20.png
libs/MVS/texture_output/texture_21.png
libs/MVS/texture_output/texture_22.png
libs/MVS/texture_output/texture_23.png
libs/MVS/texture_output/texture_24.png
libs/MVS/texture_output/texture_25.png
libs/MVS/texture_output/texture_26.png
libs/MVS/texture_output/texture_27.png
libs/MVS/texture_output/texture_28.png
libs/MVS/texture_output/texture_29.png
libs/MVS/texture_output/texture_30.png
libs/MVS/texture_output/texture_31.png
libs/MVS/texture_output/texture_32.png
libs/MVS/texture_output/texture_33.png
libs/MVS/texture_output/texture_34.png
libs/MVS/texture_output/texture_35.png
texture_output/texture_0.png

9
apps/TextureMesh/TextureMesh.cpp

@ -94,6 +94,9 @@ unsigned nMaxThreads; @@ -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) @@ -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")
("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<std::string>(&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) @@ -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<String>(),true,OPT::bUseExistingUV);
#if TD_VERBOSE != TD_VERBOSE_OFF
if (VERBOSITY_LEVEL > 2)
scene.ExportCamerasMLP(baseFileName+_T(".mlp"), baseFileName+OPT::strExportType);

129
libs/IO/OBJ.cpp

@ -48,6 +48,7 @@ ObjModel::MaterialLib::MaterialLib() @@ -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) @@ -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 @@ -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) @@ -269,6 +273,129 @@ bool ObjModel::Load(const String& fileName)
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)
{

3
libs/IO/OBJ.h

@ -93,9 +93,10 @@ public: @@ -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<TexCoord,const TexCoord&,0,8192,uint32_t> &faceTexcoords, bool bLoadUV = false);
// Creates a new group with the given material name
Group& AddGroup(const String& material_name);

72
libs/MVS/Mesh.cpp

@ -1191,15 +1191,14 @@ namespace BasicPLY { @@ -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) @@ -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,17 +1338,29 @@ bool Mesh::LoadPLY(const String& fileName) @@ -1302,17 +1338,29 @@ 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");
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()) {
DEBUG_EXTRA("error: invalid mesh file");
@ -1340,11 +1388,13 @@ bool Mesh::LoadOBJ(const String& fileName) @@ -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) @@ -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<String>& comments, bool bBinary) const
bool Mesh::Save(const String& fileName, const cList<String>& 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<String>& comments, bool b @@ -1538,7 +1588,7 @@ bool Mesh::SavePLY(const String& fileName, const cList<String>& 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 @@ -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 <typename T>

10
libs/MVS/Mesh.h

@ -262,8 +262,8 @@ public: @@ -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<String>& comments=cList<String>(), bool bBinary=true) const;
bool Load(const String& fileName, bool bLoadUV=false);
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;
static bool Save(const VertexArr& vertices, const String& fileName, bool bBinary=true);
@ -271,13 +271,15 @@ public: @@ -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<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;
#ifdef _USE_CUDA

2
libs/MVS/Scene.h

@ -169,7 +169,7 @@ public: @@ -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);

787
libs/MVS/SceneTexture.cpp

@ -441,7 +441,7 @@ public: @@ -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: @@ -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: @@ -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 <typename PIXEL>
@ -764,8 +776,9 @@ void MeshTexture::ComputeFaceCurvatures() const { @@ -764,8 +776,9 @@ 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)
{
// if (!bUseExistingUV)
scene.mesh.EmptyExtra();
scene.mesh.ListIncidenteFaces();
scene.mesh.ListBoundaryVertices();
@ -6067,10 +6080,10 @@ bool MeshTexture::ShouldMergeVirtualFace( @@ -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 @@ -7156,6 +7169,7 @@ bool MeshTexture::FaceViewSelection3( unsigned minCommonCameras, float fOutlierT
mapIdxPatch.Insert(numPatches);
}
}
return true;
}
@ -8330,8 +8344,16 @@ void MeshTexture::LocalSeamLeveling3() @@ -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,11 +8362,19 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel @@ -8340,11 +8362,19 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
int border = 2;
if (!bOriginFaceview)
border = 4;
// ===== 修改:只在非外部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);
// ===== 修改:只在非外部UV模式下执行投影计算 =====
if (!bUseExternalUV) {
#pragma omp parallel for schedule(dynamic)
for (int_t idx=0; idx<(int_t)numPatches; ++idx) {
TexturePatch& texturePatch = texturePatches[(uint32_t)idx];
@ -8361,6 +8391,31 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel @@ -8361,6 +8391,31 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
texcoords[i] = imageData.camera.ProjectPointP(vertices[face[i]]);
ASSERT(imageData.image.isInsideWithBorder(texcoords[i], border));
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
@ -8415,9 +8470,41 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel @@ -8415,9 +8470,41 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
for (const FIndex idxFace: texturePatch.faces) {
TexCoord* texcoords = faceTexcoords.data()+idxFace*3;
for (int v=0; v<3; ++v)
{
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
TexturePatch& texturePatch = texturePatches.back();
@ -8442,7 +8529,12 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel @@ -8442,7 +8529,12 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
// perform global seam leveling
if (bGlobalSeamLeveling) {
TD_TIMER_STARTD();
if (bUseExternalUV) {
// 外部UV数据可能需要更温和的接缝处理
GlobalSeamLevelingExternalUV();
} else {
GlobalSeamLeveling3();
}
DEBUG_EXTRA("\tglobal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str());
}
@ -8450,12 +8542,20 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel @@ -8450,12 +8542,20 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
if (bLocalSeamLeveling) {
TD_TIMER_STARTD();
// LocalSeamLeveling();
if (bUseExternalUV) {
// 外部UV数据可能需要不同的局部接缝处理
LocalSeamLevelingExternalUV();
} else {
LocalSeamLeveling3();
}
DEBUG_EXTRA("\tlocal seam leveling completed (%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
for (unsigned i=0; i<texturePatches.size()-1; ++i) {
TexturePatch& texturePatchBig = texturePatches[i];
@ -8480,6 +8580,7 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel @@ -8480,6 +8580,7 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
texturePatches.RemoveAtMove(j--);
}
}
}
LOG_OUT() << "Second loop completed" << std::endl;
// create texture
@ -8518,6 +8619,19 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel @@ -8518,6 +8619,19 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
typeHeuristic = 1;
}
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()) {
TD_TIMER_STARTD();
if (textureSize == 0) {
@ -8560,8 +8674,11 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel @@ -8560,8 +8674,11 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
}
}
}
}
LOG_OUT() << "Third loop completed" << std::endl;
Mesh::FaceIdxArr emptyFaceIndexes;
if (!bUseExternalUV) {
#ifdef TEXOPT_USE_OPENMP
#pragma omp parallel for schedule(dynamic)
@ -8646,6 +8763,10 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel @@ -8646,6 +8763,10 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel
}
}
}
} else {
// 外部UV数据:直接使用现有的纹理坐标,不需要重新计算
DEBUG_EXTRA("跳过自动颜色采样,使用外部UV坐标");
}
if (texturesDiffuse.size() == 1)
faceTexindices.Release();
@ -8710,6 +8831,18 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel @@ -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 @@ -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,
std::unordered_set<int>& face_visible_relative,
@ -9536,8 +10264,11 @@ bool Scene::LoadVisibleFacesData(std::map<std::string, std::unordered_set<int>>& @@ -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 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 @@ -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 @@ -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

Loading…
Cancel
Save