From 3843bea5f6ba47a59a0a9cca07f9dd196e122aa3 Mon Sep 17 00:00:00 2001 From: hesuicong Date: Tue, 28 Apr 2026 15:23:14 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B4=B4=E5=9B=BE=E6=B8=85=E6=99=B0=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 7173 ++++++++++++++++++++++++++----------- 1 file changed, 5087 insertions(+), 2086 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 3a258c1..e3e1fa7 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -166,6 +166,28 @@ enum Mask { interior = 255 }; +// 视图选择数据结构 +struct ViewSelectionData { + int viewID; // 视图ID + float quality; // 视图质量评分 + float consistency; // 与相邻面的视图一致性 + cv::Rect patchBounds; // 纹理块边界 + + ViewSelectionData() : viewID(-1), quality(0.0f), consistency(0.0f) {} + ViewSelectionData(int id, float q, float c) : + viewID(id), quality(q), consistency(c) {} +}; + +// 纹理块质量信息 +struct PatchQualityInfo { + float averageQuality; + float minQuality; + float maxQuality; + std::vector faceQualities; +}; + +std::vector patchQualityInfos; + struct MeshTexture { // used to render the surface to a view camera typedef TImage FaceMap; @@ -469,7 +491,14 @@ public: 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); + uint32_t FindNearestPatchForFaces(const std::vector& faceIndices); + void FixIsolatedComponents(); + void ReinitializeSeamData(); + bool ValidateSeamDataConsistency(); + void CleanSeamEdgesComprehensive(); void CreateSeamVertices(); + uint32_t FindOrCreateComponentForFace(FIndex faceIdx); + uint32_t FindNearestPatchForComponent(uint32_t compID); void GlobalSeamLeveling(); void GlobalSeamLeveling3(); void LocalSeamLeveling(); @@ -479,63 +508,106 @@ public: void LocalSeamLevelingExternalUV(); void GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& baseFileName, bool bOriginFaceview, Scene *pScene); + void GenerateTextureForUV(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& basename, bool bOriginFaceview, Scene *pScene, Mesh::TexCoordArr& existingTexcoords, Mesh::TexIndexArr& existingTexindices); void GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& baseFileName); - bool TextureWithExistingUV(const IIndexArr& views, int nIgnoreMaskLabel, - float fOutlierThreshold, unsigned nTextureSizeMultiple, - Pixel8U colEmpty, float fSharpnessWeight); - Mesh::Image8U3Arr GenerateTextureAtlasFromUV(const LabelArr& faceLabels, const IIndexArr& views, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight); - Mesh::Image8U3Arr GenerateMultiViewTextureAtlas( - const FaceDataViewArr& facesDatas, - const std::vector>& faceViews, - const std::vector>& faceViewWeights, - unsigned nTextureSizeMultiple, - Pixel8U colEmpty, - float fSharpnessWeight); - std::vector GetPixelsInTriangle(const Point2f& pt1, - const Point2f& pt2, - const Point2f& pt3, - int textureSize); - bool PointInTriangle2D(const cv::Point2f& p, - const cv::Point2f& a, - const cv::Point2f& b, - const cv::Point2f& c); - - void FillTextureGapsMultiView(cv::Mat& textureAtlas, - const cv::Mat1f& weightAccum, - const cv::Mat1i& sampleCount, - Pixel8U colEmpty); - int FillGapsByDistanceTransform(cv::Mat& textureAtlas, - const cv::Mat1f& weightAccum, - float maxDistance); - void AdvancedGapFilling(cv::Mat& textureAtlas, - const cv::Mat1f& weightAccum, - const cv::Mat1b& gapMask); - void FillSmallGapsWithNearest(cv::Mat& textureAtlas, - const cv::Mat1f& weightAccum, - const cv::Rect& bbox); - void FillSmallGapsWithDistanceTransform(cv::Mat& textureAtlas, - const cv::Mat1f& weightAccum); - bool InpaintWithBlocks(cv::Mat& image, const cv::Mat1b& mask, - cv::Mat& result, int radius, int blockSize); - void FastAlternativeGapFilling(cv::Mat& textureAtlas, - const cv::Mat1f& weightAccum, - const cv::Mat1b& validMask); - void ApplySimpleColorCorrection(cv::Mat& image, float strength); - - void ApplyColorCorrection(cv::Mat& image, float strength); - void ApplySharpening(cv::Mat& image, float strength); - void ApplyAutoWhiteBalance(cv::Mat& image, float strength); + bool TextureWithExistingUV( + const IIndexArr& views, + int nIgnoreMaskLabel, + float fOutlierThreshold, + unsigned nTextureSizeMultiple, + Pixel8U colEmpty, + float fSharpnessWeight, + const Mesh::Image8U3Arr& existingTextures, // 添加已有纹理参数 + const Mesh::TexCoordArr& existingTexcoords, // 添加已有UV参数 + const Mesh::TexIndexArr& existingTexindices // 添加已有纹理索引参数 + ); + bool GenerateTextureWithViewConsistency( + bool bGlobalSeamLeveling, bool bLocalSeamLeveling, + unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, + Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, + const String& basename, bool bOriginFaceview, Scene* pScene); + bool ValidateSeamDataForLeveling(); + void CheckMemoryIntegrity(); + void RebuildComponentMapping(); + void AssignOrphanFacesToComponents(const std::vector& faceToPatch); + void FixComponentMappingsOnceAndForAll(); + void CleanSeamEdges(); + // 在头文件中,修改函数声明: + bool PackTextureAtlases( + Mesh::TexCoordArr& faceTexcoords2, + Mesh::TexIndexArr& faceTexindices2, + std::vector& generatedTextures, + unsigned nTextureSizeMultiple, + unsigned nRectPackingHeuristic, + Pixel8U colEmpty, + int maxTextureSize); + void SelectOptimalViewsWithConsistency( + std::vector& faceViewData, + int minPatchSize); + void AssignComponentsToOrphanFaces(); + void CreateConsistentTexturePatches( + const std::vector& faceViewData, + std::vector& patchAssignments, + int minPatchSize); + void GenerateHighQualityTexture( + std::vector& textures, + const Mesh::TexCoordArr& faceTexcoords, + const Mesh::TexIndexArr& faceTexindices, + float fSharpnessWeight, + Pixel8U colEmpty); + cv::Vec3f SampleHighQuality(const cv::Mat& image, const Point2f& point); + void FillMissingPixelsWithViewConsistency( + Image8U3& texture, + cv::Mat3f& colorAccum, + cv::Mat1f& weightAccum, + cv::Mat1i& viewAccum, + Pixel8U colEmpty); + float ComputeViewQuality(FIndex idxFace, int viewID); + float ComputeFaceDistance(FIndex fid1, FIndex fid2); + + cv::Rect ComputeOptimalPatchBounds(const AABB2f& aabb, const cv::Size& imageSize, int border); + void GlobalSeamLevelingEnhanced(); + void LocalSeamLevelingEnhanced(); + std::pair FindSharedEdgeIndices(const Face& face0, const Face& face1); + void MergeOverlappingPatches(Mesh::TexCoordArr& faceTexcoords2); + void PackTexturePatches(const Mesh::TexCoordArr& faceTexcoords2, + const Mesh::TexIndexArr& faceTexindices2, + std::vector& generatedTextures, + unsigned nTextureSizeMultiple, + unsigned nRectPackingHeuristic, + int maxTextureSize); + void ApplyAdaptiveSharpening(std::vector& textures, float fSharpnessWeight); + void FillTextureHoles(std::vector& textures, Pixel8U colEmpty); + bool IsFaceVisibleFromView(FIndex idxFace, int viewID); + cv::Mat EnhanceTextureQuality(const cv::Mat& texture, const std::vector& faceQualities); + void OptimizeTextureSeams(const std::vector& textures, + const std::vector>& seamPoints); + + void CheckColorChannels(const Image8U3& texture, const std::string& name); + Mesh::Image8U3Arr GenerateTextureAtlasWith3DBridge( + const LabelArr& faceLabels, + const IIndexArr& views, + const Mesh::Image8U3Arr& sourceTextures, + const Mesh::TexCoordArr& sourceTexcoords, + const Mesh::TexIndexArr& sourceTexindices, + unsigned nTextureSizeMultiple, + Pixel8U colEmpty, + float fSharpnessWeight); + Mesh::Image8U3Arr GenerateTextureAtlasFromUV( + const Mesh::Image8U3Arr& sourceTextures, // 已有纹理数组 + const Mesh::TexCoordArr& sourceTexcoords, // 已有UV坐标 + const Mesh::TexIndexArr& sourceTexindices, // 已有纹理索引 + unsigned nTextureSizeMultiple, + Pixel8U colEmpty, + float fSharpnessWeight + ); Point2f ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage); Point2f ProjectPointRobust(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage, float searchRadius = 0.02f); 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 PointInTriangle2(const cv::Point2f& p, - const cv::Point2f& a, - const cv::Point2f& b, - const cv::Point2f& c); bool PointInTriangle(const Point2f& p, const Point2f& a, const Point2f& b, const Point2f& c, Point3f& bary); - int ComputeOptimalTextureSize(const Point2f& uvMin, const Point2f& uvMax, unsigned nTextureSizeMultiple); + int ComputeOptimalTextureSize(float uvWidth, float uvHeight, unsigned multiple); // Bruce //* template @@ -717,6 +789,7 @@ public: Mesh::TexCoordArr& faceTexcoords; // for each face, the texture-coordinates of the vertices Mesh::TexIndexArr& faceTexindices; // for each face, the texture-coordinates of the vertices Mesh::Image8U3Arr& texturesDiffuse; // texture containing the diffuse color + Mesh::Image8U3Arr texturesDiffuseTemp; // texture containing the diffuse color // constant the entire time Mesh::VertexArr& vertices; @@ -725,7 +798,7 @@ public: Scene& scene; // the mesh vertices and faces }; - + // creating an invalid mask for the given image corresponding to // the invalid pixels generated during image correction for the lens distortion; // the returned mask has the same size as the image and is set to zero for invalid pixels @@ -767,6 +840,7 @@ MeshTexture::MeshTexture(Scene& _scene, unsigned _nResolutionLevel, unsigned _nM faceTexcoords(_scene.mesh.faceTexcoords), faceTexindices(_scene.mesh.faceTexindices), texturesDiffuse(_scene.mesh.texturesDiffuse), + texturesDiffuseTemp(_scene.mesh.texturesDiffuse), vertices(_scene.mesh.vertices), faces(_scene.mesh.faces), images(_scene.images), @@ -7614,70 +7688,313 @@ bool MeshTexture::FaceViewSelection4( unsigned minCommonCameras, float fOutlierT return true; } -// create seam vertices and edges -void MeshTexture::CreateSeamVertices() +void MeshTexture::FixIsolatedComponents() { - // each vertex will contain the list of patches it separates, - // except the patch containing invisible faces; - // each patch contains the list of edges belonging to that texture patch, starting from that vertex - // (usually there are pairs of edges in each patch, representing the two edges starting from that vertex separating two valid patches) - VIndex vs[2]; - uint32_t vs0[2], vs1[2]; - std::unordered_map mapVertexSeam; - const unsigned numPatches(texturePatches.size()-1); - for (const PairIdx& edge: seamEdges) { - // store edge for the later seam optimization - ASSERT(edge.i < edge.j); + DEBUG_EXTRA("Fixing isolated components..."); + + // 构建组件到面的映射 + std::vector> compFaces(mapIdxPatch.GetSize()); + for (FIndex faceIdx = 0; faceIdx < components.size(); ++faceIdx) { + uint32_t compID = components[faceIdx]; + if (compID != NO_ID && compID < compFaces.size()) { + compFaces[compID].push_back(faceIdx); + } + } + + // 检查每个组件是否有对应的纹理块 + for (uint32_t compID = 0; compID < mapIdxPatch.GetSize(); ++compID) { + uint32_t patchIdx = mapIdxPatch[compID]; + + if (patchIdx == NO_ID) { + // 这个组件没有对应的纹理块 + if (!compFaces[compID].empty()) { + DEBUG_EXTRA("Component %u has %zu faces but no patch, reassigning...", + compID, compFaces[compID].size()); + + // 找到最近的纹理块 + uint32_t nearestPatchIdx = FindNearestPatchForFaces(compFaces[compID]); + if (nearestPatchIdx != NO_ID && nearestPatchIdx < texturePatches.size()) { + mapIdxPatch[compID] = nearestPatchIdx; + DEBUG_EXTRA(" Reassigned to patch %u", nearestPatchIdx); + } else { + DEBUG_EXTRA(" Could not find suitable patch, setting to invalid patch"); + mapIdxPatch[compID] = static_cast(texturePatches.size() - 1); + } + } + } else if (patchIdx >= texturePatches.size()) { + // 纹理块索引越界 + DEBUG_EXTRA("Component %u maps to invalid patch %u, fixing...", compID, patchIdx); + mapIdxPatch[compID] = NO_ID; + + if (!compFaces[compID].empty()) { + uint32_t nearestPatchIdx = FindNearestPatchForFaces(compFaces[compID]); + if (nearestPatchIdx != NO_ID && nearestPatchIdx < texturePatches.size()) { + mapIdxPatch[compID] = nearestPatchIdx; + DEBUG_EXTRA(" Fixed: reassigned to patch %u", nearestPatchIdx); + } else { + DEBUG_EXTRA(" Could not find suitable patch, setting to invalid patch"); + mapIdxPatch[compID] = static_cast(texturePatches.size() - 1); + } + } + } + } +} - // if (labelsInvalid[edge.i] != NO_ID || labelsInvalid[edge.j] != NO_ID ) - // continue; +uint32_t MeshTexture::FindNearestPatchForFaces(const std::vector& faceIndices) +{ + if (faceIndices.empty() || texturePatches.empty()) { + return NO_ID; + } + + // 收集所有相邻的面 + std::unordered_set neighborPatches; + + for (FIndex fid : faceIndices) { + if (fid >= faces.GetSize()) continue; + + const Face& face = faces[fid]; + + // 通过顶点查找相邻面 + for (int i = 0; i < 3; ++i) { + VIndex vertexIdx = face[i]; + if (vertexIdx >= scene.mesh.vertexFaces.size()) continue; + + const Mesh::FaceIdxArr& adjacentFaces = scene.mesh.vertexFaces[vertexIdx]; + for (FIndex adjFace : adjacentFaces) { + if (adjFace < components.size() && components[adjFace] != NO_ID) { + uint32_t compID = components[adjFace]; + if (compID < mapIdxPatch.GetSize()) { + uint32_t patchIdx = mapIdxPatch[compID]; + if (patchIdx != NO_ID && patchIdx < texturePatches.size()) { + neighborPatches.insert(patchIdx); + } + } + } + } + } + } + + // 找到拥有最多相邻面的纹理块 + std::unordered_map patchAdjCount; + uint32_t bestPatch = NO_ID; + int maxCount = 0; + + for (uint32_t patchIdx : neighborPatches) { + if (patchIdx < texturePatches.size()) { + int count = 0; + + // 计算与这个面集中面的相邻面数量 + for (FIndex fid : faceIndices) { + if (fid >= faces.GetSize()) continue; + + const Face& face = faces[fid]; + for (int i = 0; i < 3; ++i) { + VIndex vertexIdx = face[i]; + if (vertexIdx >= scene.mesh.vertexFaces.size()) continue; + + const Mesh::FaceIdxArr& adjacentFaces = scene.mesh.vertexFaces[vertexIdx]; + for (FIndex adjFace : adjacentFaces) { + if (adjFace < components.size() && components[adjFace] != NO_ID) { + uint32_t compID = components[adjFace]; + if (compID < mapIdxPatch.GetSize() && mapIdxPatch[compID] == patchIdx) { + count++; + } + } + } + } + } + + if (count > maxCount) { + maxCount = count; + bestPatch = patchIdx; + } + } + } + + return bestPatch; +} - const uint32_t idxPatch0(mapIdxPatch[components[edge.i]]); - const uint32_t idxPatch1(mapIdxPatch[components[edge.j]]); - ASSERT(idxPatch0 != idxPatch1 || idxPatch0 == numPatches); - if (idxPatch0 == idxPatch1) - continue; - seamVertices.ReserveExtra(2); - scene.mesh.GetEdgeVertices(edge.i, edge.j, vs0, vs1); - ASSERT(faces[edge.i][vs0[0]] == faces[edge.j][vs1[0]]); - ASSERT(faces[edge.i][vs0[1]] == faces[edge.j][vs1[1]]); - vs[0] = faces[edge.i][vs0[0]]; - vs[1] = faces[edge.i][vs0[1]]; - - const auto itSeamVertex0(mapVertexSeam.emplace(std::make_pair(vs[0], seamVertices.size()))); - if (itSeamVertex0.second) - seamVertices.emplace_back(vs[0]); - SeamVertex& seamVertex0 = seamVertices[itSeamVertex0.first->second]; - - const auto itSeamVertex1(mapVertexSeam.emplace(std::make_pair(vs[1], seamVertices.size()))); - if (itSeamVertex1.second) - seamVertices.emplace_back(vs[1]); - SeamVertex& seamVertex1 = seamVertices[itSeamVertex1.first->second]; - - if (idxPatch0 < numPatches) { - const TexCoord offset0(texturePatches[idxPatch0].rect.tl()); - SeamVertex::Patch& patch00 = seamVertex0.GetPatch(idxPatch0); - SeamVertex::Patch& patch10 = seamVertex1.GetPatch(idxPatch0); - ASSERT(patch00.edges.Find(itSeamVertex1.first->second) == NO_ID); - patch00.edges.emplace_back(itSeamVertex1.first->second).idxFace = edge.i; - patch00.proj = faceTexcoords[edge.i*3+vs0[0]]+offset0; - ASSERT(patch10.edges.Find(itSeamVertex0.first->second) == NO_ID); - patch10.edges.emplace_back(itSeamVertex0.first->second).idxFace = edge.i; - patch10.proj = faceTexcoords[edge.i*3+vs0[1]]+offset0; - } - if (idxPatch1 < numPatches) { - const TexCoord offset1(texturePatches[idxPatch1].rect.tl()); - SeamVertex::Patch& patch01 = seamVertex0.GetPatch(idxPatch1); - SeamVertex::Patch& patch11 = seamVertex1.GetPatch(idxPatch1); - ASSERT(patch01.edges.Find(itSeamVertex1.first->second) == NO_ID); - patch01.edges.emplace_back(itSeamVertex1.first->second).idxFace = edge.j; - patch01.proj = faceTexcoords[edge.j*3+vs1[0]]+offset1; - ASSERT(patch11.edges.Find(itSeamVertex0.first->second) == NO_ID); - patch11.edges.emplace_back(itSeamVertex0.first->second).idxFace = edge.j; - patch11.proj = faceTexcoords[edge.j*3+vs1[1]]+offset1; - } - } - seamEdges.Release(); +void MeshTexture::CreateSeamVertices() +{ + DEBUG_EXTRA("Creating seam vertices with enhanced validation"); + + // 确保有纹理块 + if (texturePatches.size() < 2) { + DEBUG_EXTRA("Too few texture patches (%zu), skipping seam vertices creation", texturePatches.size()); + seamVertices.Release(); + return; + } + + VIndex vs[2]; + uint32_t vs0[2], vs1[2]; + std::unordered_map mapVertexSeam; + + // 计算有效纹理块数量(排除无效纹理块) + const unsigned numPatches = static_cast(texturePatches.size() - 1); + DEBUG_EXTRA("Total patches: %zu, valid patches: %u", texturePatches.size(), numPatches); + + // 验证组件和映射 + if (components.size() != faces.GetSize()) { + DEBUG_EXTRA("ERROR: components size mismatch: %zu vs %u", components.size(), faces.GetSize()); + return; + } + + if (mapIdxPatch.GetSize() == 0) { + DEBUG_EXTRA("ERROR: mapIdxPatch is empty"); + return; + } + + seamVertices.Release(); + + int validEdges = 0; + int invalidEdges = 0; + int skippedEdges = 0; + + for (uint32_t edgeIdx = 0; edgeIdx < seamEdges.GetSize(); ++edgeIdx) { + const PairIdx& edge = seamEdges[edgeIdx]; + + // 检查面索引 + if (edge.i >= faces.GetSize() || edge.j >= faces.GetSize()) { + DEBUG_EXTRA("WARNING: Invalid face indices in seam edge %u: (%u, %u)", + edgeIdx, edge.i, edge.j); + invalidEdges++; + continue; + } + + // 检查组件ID + if (edge.i >= components.size() || edge.j >= components.size()) { + skippedEdges++; + continue; + } + + const uint32_t comp0 = components[edge.i]; + const uint32_t comp1 = components[edge.j]; + + if (comp0 == NO_ID || comp1 == NO_ID) { + skippedEdges++; + continue; + } + + // 检查组件ID是否有效 + if (comp0 >= mapIdxPatch.GetSize() || comp1 >= mapIdxPatch.GetSize()) { + DEBUG_EXTRA("WARNING: Component IDs out of range at edge %u: comp0=%u, comp1=%u", + edgeIdx, comp0, comp1); + invalidEdges++; + continue; + } + + const uint32_t idxPatch0 = mapIdxPatch[comp0]; + const uint32_t idxPatch1 = mapIdxPatch[comp1]; + + // 检查纹理块索引是否有效 + if (idxPatch0 >= texturePatches.size() || idxPatch1 >= texturePatches.size()) { + DEBUG_EXTRA("WARNING: Invalid patch indices at edge %u: idxPatch0=%u, idxPatch1=%u, total patches=%zu", + edgeIdx, idxPatch0, idxPatch1, texturePatches.size()); + invalidEdges++; + continue; + } + + // 检查是否属于同一个纹理块 + if (idxPatch0 == idxPatch1) { + // 属于同一个纹理块,不是接缝 + skippedEdges++; + continue; + } + + // 跳过无效纹理块 + if (idxPatch0 == texturePatches.size() - 1 || idxPatch1 == texturePatches.size() - 1) { + // 至少有一个面在无效纹理块中 + skippedEdges++; + continue; + } + + // 获取边的顶点 - 直接调用,不检查返回值 + scene.mesh.GetEdgeVertices(edge.i, edge.j, vs0, vs1); + + const Face& faceI = faces[edge.i]; + const Face& faceJ = faces[edge.j]; + + if (vs0[0] >= 3 || vs0[1] >= 3 || vs1[0] >= 3 || vs1[1] >= 3 || + faceI[vs0[0]] != faceJ[vs1[0]] || faceI[vs0[1]] != faceJ[vs1[1]]) { + DEBUG_EXTRA("WARNING: Edge vertices mismatch at edge %u", edgeIdx); + invalidEdges++; + continue; + } + + vs[0] = faceI[vs0[0]]; + vs[1] = faceI[vs0[1]]; + + if (vs[0] >= vertices.size() || vs[1] >= vertices.size()) { + DEBUG_EXTRA("WARNING: Invalid vertex indices at edge %u: %u, %u", edgeIdx, vs[0], vs[1]); + invalidEdges++; + continue; + } + + // 创建或获取接缝顶点 + auto itSeamVertex0 = mapVertexSeam.emplace(std::make_pair(vs[0], static_cast(seamVertices.GetSize()))); + if (itSeamVertex0.second) { + seamVertices.emplace_back(vs[0]); + } + SeamVertex& seamVertex0 = seamVertices[itSeamVertex0.first->second]; + + auto itSeamVertex1 = mapVertexSeam.emplace(std::make_pair(vs[1], static_cast(seamVertices.GetSize()))); + if (itSeamVertex1.second) { + seamVertices.emplace_back(vs[1]); + } + SeamVertex& seamVertex1 = seamVertices[itSeamVertex1.first->second]; + + // 为纹理块0添加边 + { + const TexCoord offset0(texturePatches[idxPatch0].rect.tl()); + + uint32_t texCoordIdx0 = edge.i * 3 + vs0[0]; + uint32_t texCoordIdx1 = edge.i * 3 + vs0[1]; + + if (texCoordIdx0 < faceTexcoords.GetSize() && texCoordIdx1 < faceTexcoords.GetSize()) { + SeamVertex::Patch& patch00 = seamVertex0.GetPatch(idxPatch0); + SeamVertex::Patch& patch10 = seamVertex1.GetPatch(idxPatch0); + + if (patch00.edges.Find(itSeamVertex1.first->second) == NO_ID) { + patch00.edges.emplace_back(itSeamVertex1.first->second).idxFace = edge.i; + patch00.proj = faceTexcoords[texCoordIdx0] + offset0; + } + + if (patch10.edges.Find(itSeamVertex0.first->second) == NO_ID) { + patch10.edges.emplace_back(itSeamVertex0.first->second).idxFace = edge.i; + patch10.proj = faceTexcoords[texCoordIdx1] + offset0; + } + } + } + + // 为纹理块1添加边 + { + const TexCoord offset1(texturePatches[idxPatch1].rect.tl()); + + uint32_t texCoordIdx0 = edge.j * 3 + vs1[0]; + uint32_t texCoordIdx1 = edge.j * 3 + vs1[1]; + + if (texCoordIdx0 < faceTexcoords.GetSize() && texCoordIdx1 < faceTexcoords.GetSize()) { + SeamVertex::Patch& patch01 = seamVertex0.GetPatch(idxPatch1); + SeamVertex::Patch& patch11 = seamVertex1.GetPatch(idxPatch1); + + if (patch01.edges.Find(itSeamVertex1.first->second) == NO_ID) { + patch01.edges.emplace_back(itSeamVertex1.first->second).idxFace = edge.j; + patch01.proj = faceTexcoords[texCoordIdx0] + offset1; + } + + if (patch11.edges.Find(itSeamVertex0.first->second) == NO_ID) { + patch11.edges.emplace_back(itSeamVertex0.first->second).idxFace = edge.j; + patch11.proj = faceTexcoords[texCoordIdx1] + offset1; + } + } + } + + validEdges++; + } + + seamEdges.Release(); + DEBUG_EXTRA("Seam vertices created: %u vertices, %d valid edges, %d invalid edges, %d skipped edges", + seamVertices.GetSize(), validEdges, invalidEdges, skippedEdges); } // Native @@ -8878,1954 +9195,4724 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel } } -void MeshTexture::GlobalSeamLevelingExternalUV() +void MeshTexture::GenerateTextureForUV(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& basename, bool bOriginFaceview, Scene *pScene, Mesh::TexCoordArr& existingTexcoords, Mesh::TexIndexArr& existingTexindices) { - // 针对外部UV数据的全局接缝处理 - // 实现更温和的接缝处理算法 -} + int border = 2; + if (!bOriginFaceview) + border = 4; -void MeshTexture::LocalSeamLevelingExternalUV() -{ - // 针对外部UV数据的局部接缝处理 - // 实现保留原始UV特征的接缝处理 -} + Mesh::TexCoordArr& faceTexcoords2 = existingTexcoords; + Mesh::TexIndexArr& faceTexindices2 = existingTexindices; -// New -void MeshTexture::GlobalSeamLeveling() -{ - ASSERT(!seamVertices.empty()); - const unsigned numPatches(texturePatches.size()-1); - // find the patch ID for each vertex - PatchIndices patchIndices(vertices.size()); - patchIndices.Memset(0); - FOREACH(f, faces) { - const uint32_t idxPatch(mapIdxPatch[components[f]]); - const Face& face = faces[f]; - for (int v=0; v<3; ++v) - patchIndices[face[v]].idxPatch = idxPatch; - } - FOREACH(i, seamVertices) { - const SeamVertex& seamVertex = seamVertices[i]; - ASSERT(!seamVertex.patches.empty()); - PatchIndex& patchIndex = patchIndices[seamVertex.idxVertex]; - patchIndex.bIndex = true; - patchIndex.idxSeamVertex = i; - } + faceTexcoords2.resize(faces.size()*3); + faceTexindices2.resize(faces.size()); - // assign a row index within the solution vector x to each vertex/patch - ASSERT(vertices.size() < static_cast(std::numeric_limits::max())); - MatIdx rowsX(0); - typedef std::unordered_map VertexPatch2RowMap; - cList vertpatch2rows(vertices.size()); - FOREACH(i, vertices) { - const PatchIndex& patchIndex = patchIndices[i]; - VertexPatch2RowMap& vertpatch2row = vertpatch2rows[i]; - if (patchIndex.bIndex) { - // vertex is part of multiple patches - const SeamVertex& seamVertex = seamVertices[patchIndex.idxSeamVertex]; - ASSERT(seamVertex.idxVertex == i); - for (const SeamVertex::Patch& patch: seamVertex.patches) { - ASSERT(patch.idxPatch != numPatches); - vertpatch2row[patch.idxPatch] = rowsX++; - } - } else - if (patchIndex.idxPatch < numPatches) { - // vertex is part of only one patch - vertpatch2row[patchIndex.idxPatch] = rowsX++; - } - } + #ifdef TEXOPT_USE_OPENMP + // LOG_OUT() << "def TEXOPT_USE_OPENMP" << std::endl; + const unsigned numPatches(texturePatches.size()-1); - // fill Tikhonov's Gamma matrix (regularization constraints) - // Bruce - // const float lambda(0.1f); - const float lambda(0.8f); + // ===== 修改:只在非外部UV模式下执行投影计算 ===== - MatIdx rowsGamma(0); - Mesh::VertexIdxArr adjVerts; - CLISTDEF0(MatEntry) rows(0, vertices.size()*4); - FOREACH(v, vertices) { - adjVerts.Empty(); - scene.mesh.GetAdjVertices(v, adjVerts); - VertexPatchIterator itV(patchIndices[v], seamVertices); - while (itV.Next()) { - const uint32_t idxPatch(itV); - if (idxPatch == numPatches) - continue; - const MatIdx col(vertpatch2rows[v].at(idxPatch)); - for (const VIndex vAdj: adjVerts) { - if (v >= vAdj) - continue; - VertexPatchIterator itVAdj(patchIndices[vAdj], seamVertices); - while (itVAdj.Next()) { - const uint32_t idxPatchAdj(itVAdj); - if (idxPatch == idxPatchAdj) { - const MatIdx colAdj(vertpatch2rows[vAdj].at(idxPatchAdj)); - rows.emplace_back(rowsGamma, col, lambda); - rows.emplace_back(rowsGamma, colAdj, -lambda); - ++rowsGamma; - } - } - } - } - } - ASSERT(rows.size()/2 < static_cast(std::numeric_limits::max())); + { + #pragma omp parallel for schedule(dynamic) + for (int_t idx=0; idx<(int_t)numPatches; ++idx) { + TexturePatch& texturePatch = texturePatches[(uint32_t)idx]; + #else + for (TexturePatch *pTexturePatch=texturePatches.Begin(), *pTexturePatchEnd=texturePatches.End()-1; pTexturePatch(std::numeric_limits::max())); + // 计算边向量 + 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); - const MatIdx rowsA((MatIdx)coeffB.size()); - SparseMat A(rowsA, rowsX); - A.setFromTriplets(rows.Begin(), rows.End()); - rows.Release(); + // 处理退化三角形情况(面积接近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); + } + } - SparseMat Lhs(A.transpose() * A + Gamma.transpose() * Gamma); - // CG uses only the lower triangle, so prune the rest and compress matrix - Lhs.prune([](const int& row, const int& col, const float&) -> bool { - return col <= row; - }); + } + } + // compute relative texture coordinates + ASSERT(imageData.image.isInside(Point2f(aabb.ptMin))); + ASSERT(imageData.image.isInside(Point2f(aabb.ptMax))); + + if (bOriginFaceview) + { + texturePatch.rect.x = FLOOR2INT(aabb.ptMin[0])-border; + texturePatch.rect.y = FLOOR2INT(aabb.ptMin[1])-border; + texturePatch.rect.width = CEIL2INT(aabb.ptMax[0]-aabb.ptMin[0])+border*2; + texturePatch.rect.height = CEIL2INT(aabb.ptMax[1]-aabb.ptMin[1])+border*2; + } + else + { + texturePatch.rect.x = std::max(0, FLOOR2INT(aabb.ptMin[0])-border); + texturePatch.rect.y = std::max(0, FLOOR2INT(aabb.ptMin[1])-border); + // 限制尺寸不超过图像实际范围 + const cv::Mat& img = images[texturePatch.label].image; + texturePatch.rect.width = std::min( + CEIL2INT(aabb.ptMax[0]-aabb.ptMin[0])+border*2, + img.cols - texturePatch.rect.x + ); + texturePatch.rect.height = std::min( + CEIL2INT(aabb.ptMax[1]-aabb.ptMin[1])+border*2, + img.rows - texturePatch.rect.y + ); + } - // globally solve for the correction colors - Eigen::Matrix colorAdjustments(rowsX, 3); - { - // init CG solver - Eigen::ConjugateGradient solver; - solver.setMaxIterations(1000); - solver.setTolerance(0.0001f); - solver.compute(Lhs); - ASSERT(solver.info() == Eigen::Success); - #ifdef TEXOPT_USE_OPENMP - #pragma omp parallel for - #endif - for (int channel=0; channel<3; ++channel) { - // init right hand side vector - const Eigen::Map< Eigen::VectorXf, Eigen::Unaligned, Eigen::Stride<0,3> > b(coeffB.front().ptr()+channel, rowsA); - // Bruce - const Eigen::VectorXf Rhs(SparseMat(A.transpose()) * b); - // Eigen::VectorXf Rhs = SparseMat(A.transpose()) * b_map[channel]; - // colorAdjustments.col(channel) = solver.solve(Rhs).array() - solver.solve(Rhs).mean(); + ASSERT(imageData.image.isInside(texturePatch.rect.tl())); + ASSERT(imageData.image.isInside(texturePatch.rect.br())); + const TexCoord offset(texturePatch.rect.tl()); + for (const FIndex idxFace: texturePatch.faces) { + TexCoord* texcoords = faceTexcoords2.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]; - // solve for x - const Eigen::VectorXf x(solver.solve(Rhs)); - ASSERT(solver.info() == Eigen::Success); - // subtract mean since the system is under-constrained and - // we need the solution with minimal adjustments - Eigen::Map< Eigen::VectorXf, Eigen::Unaligned, Eigen::Stride<0,3> >(colorAdjustments.data()+channel, rowsX) = x.array() - x.mean(); - DEBUG_LEVEL(3, "\tcolor channel %d: %d iterations, %g residual", channel, solver.iterations(), solver.error()); - } - } + // 计算边向量 + 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); + } + } - // adjust texture patches using the correction colors - #ifdef TEXOPT_USE_OPENMP - #pragma omp parallel for schedule(dynamic) - for (int i=0; i<(int)numPatches; ++i) { - #else - for (unsigned i=0; i 0 && bary.y > 0 && bary.z > 0) ? 1.0f : 0.8f; - image(pt) = (colors[0]*bary.x + colors[1]*bary.y + colors[2]*bary.z) * weight; } - } data(imageAdj); + } + } + { + // init last patch to point to a small uniform color patch + TexturePatch& texturePatch = texturePatches.back(); + const int sizePatch(border*2+1); + texturePatch.rect = cv::Rect(0,0, sizePatch,sizePatch); for (const FIndex idxFace: texturePatch.faces) { - const Face& face = faces[idxFace]; - data.tri = faceTexcoords.Begin()+idxFace*3; - for (int v=0; v<3; ++v){ - if (auto search = vertpatch2rows[face[v]].find(idxPatch); search != vertpatch2rows[face[v]].end()) - data.colors[v] = colorAdjustments.row(vertpatch2rows[face[v]].at(idxPatch)); - } - ColorMap::RasterizeTriangleBary(data.tri[0], data.tri[1], data.tri[2], data); + TexCoord* texcoords = faceTexcoords2.data()+idxFace*3; + for (int i=0; i<3; ++i) + texcoords[i] = TexCoord(0.5f, 0.5f); } + } - // dilate with one pixel width, in order to make sure patch border smooths out a little - imageAdj.DilateMean<1>(imageAdj, Color::ZERO); - // Bruce - cv::Mat adjMat(imageAdj); - // cv::GaussianBlur(adjMat, adjMat, cv::Size(3,3), 0.5); + LOG_OUT() << "First loop completed" << std::endl; - // 将原有3x3高斯核升级为5x5,并增加迭代次数 - cv::GaussianBlur(adjMat, adjMat, cv::Size(5,5), 1.2); - - // 新增:边缘保持滤波(保留锐利边缘的同时平滑颜色过渡) - cv::Mat filteredAdj; - cv::edgePreservingFilter(adjMat, filteredAdj, cv::RECURS_FILTER, 60, 0.4); - adjMat = filteredAdj; + /* + TD_TIMER_STARTD(); + // perform seam leveling + if (texturePatches.size() > 2 && (bGlobalSeamLeveling || bLocalSeamLeveling)) { + // create seam vertices and edges + CreateSeamVertices(); - // 修改:在应用调整时进行边缘检测,避免过度调整 - // cv::Mat edgeMask; - cv::Canny(images[texturePatch.label].image(texturePatch.rect), edgeMask, 50, 150); - - // apply color correction to the patch image - cv::Mat image(images[texturePatch.label].image(texturePatch.rect)); - //* - for (int r=0; r(r,c) > 0 ? 0.3f : 1.0f; + // perform local seam leveling + 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()); + */ - const Color& a = imageAdj(r,c); - if (a == Color::ZERO) + { + // merge texture patches with overlapping rectangles + for (unsigned i=0; i(r,c); - if (v.r == 0 && v.g == 0 && v.b == 0) + TexturePatch& texturePatchSmall = texturePatches[j]; + if (texturePatchBig.label != texturePatchSmall.label) continue; - const Color col(RGB2YCBCR(Color(v))); - // const Color acol(YCBCR2RGB(Color(col+a))); - Color acol = YCBCR2RGB(Color(col + a * edgeWeight)); // 应用边缘权重 + if (!RectsBinPack::IsContainedIn(texturePatchSmall.rect, texturePatchBig.rect)) + continue; + // translate texture coordinates + const TexCoord offset(texturePatchSmall.rect.tl()-texturePatchBig.rect.tl()); + for (const FIndex idxFace: texturePatchSmall.faces) { + TexCoord* texcoords = faceTexcoords2.data()+idxFace*3; + for (int v=0; v<3; ++v) + texcoords[v] += offset; + } + // join faces lists + texturePatchBig.faces.JoinRemove(texturePatchSmall.faces); + // remove the small patch + texturePatches.RemoveAtMove(j--); + } + } + } - for (int p=0; p<3; ++p) { - float val = acol[p]; - val = std::min(std::max(val, 0.0f), 255.0f); - v[p] = static_cast(val + 0.5f); // 四舍五入 - } + LOG_OUT() << "Second loop completed" << std::endl; + // create texture + { + // arrange texture patches to fit the smallest possible texture image + // const unsigned minPatchSize = 20; + RectsBinPack::RectWIdxArr unplacedRects(texturePatches.size()); + FOREACH(i, texturePatches) { + if (texturePatches[i].label == NO_ID) { + } + + // LOG_OUT() << "Third loop completed" << std::endl; + if (maxTextureSize > 0 && (texturePatches[i].rect.width > maxTextureSize || texturePatches[i].rect.height > maxTextureSize)) { + DEBUG("error: a patch of size %u x %u does not fit the texture", texturePatches[i].rect.width, texturePatches[i].rect.height); + ABORT("the maximum texture size chosen cannot fit a patch"); } + unplacedRects[i] = {texturePatches[i].rect, i}; } - /* - for (int r=0; r(r,c); - const Color col(RGB2YCBCR(Color(v))); - const Color acol(YCBCR2RGB(Color(col+a))); - - // 添加范围限制 (0-255) - for (int p=0; p<3; ++p) { - float val = acol[p]; - val = std::min(std::max(val, 0.0f), 255.0f); // 确保在0-255范围内 - v[p] = static_cast(val); + LOG_OUT() << "unplacedRects loop completed" << std::endl; + + LOG_OUT() << "pack patches: one pack per texture file loop completed" << std::endl; + // pack patches: one pack per texture file + CLISTDEF2IDX(RectsBinPack::RectWIdxArr, TexIndex) placedRects; { + // increase texture size till all patches fit + // Bruce + unsigned typeRectsBinPack(nRectPackingHeuristic/100); + unsigned typeSplit((nRectPackingHeuristic-typeRectsBinPack*100)/10); + unsigned typeHeuristic(nRectPackingHeuristic%10); + if (!bOriginFaceview && false) + { + typeRectsBinPack = 1; + typeSplit = 0; + typeHeuristic = 1; + } + int textureSize = 0; + + { + while (!unplacedRects.empty()) { + TD_TIMER_STARTD(); + if (textureSize == 0) { + textureSize = RectsBinPack::ComputeTextureSize(unplacedRects, nTextureSizeMultiple); + if (maxTextureSize > 0 && textureSize > maxTextureSize) + textureSize = maxTextureSize; + } + + RectsBinPack::RectWIdxArr newPlacedRects; + switch (typeRectsBinPack) { + case 0: { + MaxRectsBinPack pack(textureSize, textureSize); + newPlacedRects = pack.Insert(unplacedRects, (MaxRectsBinPack::FreeRectChoiceHeuristic)typeHeuristic); + break; } + case 1: { + SkylineBinPack pack(textureSize, textureSize, typeSplit!=0); + newPlacedRects = pack.Insert(unplacedRects, (SkylineBinPack::LevelChoiceHeuristic)typeHeuristic); + break; } + case 2: { + GuillotineBinPack pack(textureSize, textureSize); + newPlacedRects = pack.Insert(unplacedRects, false, (GuillotineBinPack::FreeRectChoiceHeuristic)typeHeuristic, (GuillotineBinPack::GuillotineSplitHeuristic)typeSplit); + break; } + default: + ABORT("error: unknown RectsBinPack type"); + } + DEBUG_ULTIMATE("\tpacking texture completed: %u initial patches, %u placed patches, %u texture-size, %u textures (%s)", texturePatches.size(), newPlacedRects.size(), textureSize, placedRects.size(), TD_TIMER_GET_FMT().c_str()); + + if (textureSize == maxTextureSize || unplacedRects.empty()) { + // create texture image + placedRects.emplace_back(std::move(newPlacedRects)); + // Pixel8U colEmpty2=Pixel8U(0,0,255); + texturesDiffuseTemp.emplace_back(textureSize, textureSize).setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); + textureSize = 0; + } else { + // try again with a bigger texture + textureSize *= 2; + if (maxTextureSize > 0) + textureSize = std::max(textureSize, maxTextureSize); + unplacedRects.JoinRemove(newPlacedRects); + } } } } - //*/ + LOG_OUT() << "Third loop completed" << std::endl; + + Mesh::FaceIdxArr emptyFaceIndexes; + + { + #ifdef TEXOPT_USE_OPENMP + #pragma omp parallel for schedule(dynamic) + for (int_t i=0; i<(int_t)placedRects.size(); ++i) { + for (int_t j=0; j<(int_t)placedRects[(TexIndex)i].size(); ++j) { + const TexIndex idxTexture((TexIndex)i); + const uint32_t idxPlacedPatch((uint32_t)j); + #else + FOREACH(idxTexture, placedRects) { + FOREACH(idxPlacedPatch, placedRects[idxTexture]) { + #endif + const TexturePatch& texturePatch = texturePatches[placedRects[idxTexture][idxPlacedPatch].patchIdx]; + const RectsBinPack::Rect& rect = placedRects[idxTexture][idxPlacedPatch].rect; + // copy patch image + ASSERT((rect.width == texturePatch.rect.width && rect.height == texturePatch.rect.height) || + (rect.height == texturePatch.rect.width && rect.width == texturePatch.rect.height)); + int x(0), y(1); + if (texturePatch.label != NO_ID) { + const Image& imageData = images[texturePatch.label]; + cv::Mat patch(imageData.image(texturePatch.rect)); + if (rect.width != texturePatch.rect.width) { + // flip patch and texture-coordinates + patch = patch.t(); + x = 1; y = 0; + } + + patch.copyTo(texturesDiffuseTemp[idxTexture](rect)); + } + else + { + auto it = texturePatch.faces.begin(); + while (it != texturePatch.faces.end()) + { + emptyFaceIndexes.push_back(*it); + ++it; + } + } + // compute final texture coordinates + const TexCoord offset(rect.tl()); + for (const FIndex idxFace: texturePatch.faces) { + TexCoord* texcoords = faceTexcoords2.data()+idxFace*3; + faceTexindices2[idxFace] = idxTexture; + for (int v=0; v<3; ++v) { + TexCoord& texcoord = texcoords[v]; + texcoord = TexCoord( + texcoord[x]+offset.x, + texcoord[y]+offset.y + ); + } + } + } + } + } + if (texturesDiffuseTemp.size() == 1) + faceTexindices2.Release(); + + // apply some sharpening + if (fSharpnessWeight > 0) { + constexpr double sigma = 1.5; + for (auto &textureDiffuse: texturesDiffuseTemp) { + Image8U3 blurryTextureDiffuse; + cv::GaussianBlur(textureDiffuse, blurryTextureDiffuse, cv::Size(), sigma); + cv::addWeighted(textureDiffuse, 1+fSharpnessWeight, blurryTextureDiffuse, -fSharpnessWeight, 0, textureDiffuse); + } + } + LOG_OUT() << "Fourth loop completed" << std::endl; + + std::ofstream out(basename + "_empty_color_triangles.txt"); + RFOREACHPTR(pIdxF, emptyFaceIndexes) { + out << *pIdxF << "\n"; + } + out.close(); } +} + +void MeshTexture::GlobalSeamLevelingExternalUV() +{ + // 针对外部UV数据的全局接缝处理 + // 实现更温和的接缝处理算法 +} +void MeshTexture::LocalSeamLevelingExternalUV() +{ + // 针对外部UV数据的局部接缝处理 + // 实现保留原始UV特征的接缝处理 } // New -void MeshTexture::LocalSeamLeveling() +void MeshTexture::GlobalSeamLeveling() { ASSERT(!seamVertices.empty()); const unsigned numPatches(texturePatches.size()-1); - - // Create a boolean array to mark invalid vertices - BoolArr vertexInvalid(vertices.size()); - vertexInvalid.Memset(false); + // find the patch ID for each vertex + PatchIndices patchIndices(vertices.size()); + patchIndices.Memset(0); FOREACH(f, faces) { - if (labelsInvalid[f] != NO_ID) { - const Face& face = faces[f]; - for (int v=0; v<3; ++v) - vertexInvalid[face[v]] = true; - } + const uint32_t idxPatch(mapIdxPatch[components[f]]); + const Face& face = faces[f]; + for (int v=0; v<3; ++v) + patchIndices[face[v]].idxPatch = idxPatch; + } + FOREACH(i, seamVertices) { + const SeamVertex& seamVertex = seamVertices[i]; + ASSERT(!seamVertex.patches.empty()); + PatchIndex& patchIndex = patchIndices[seamVertex.idxVertex]; + patchIndex.bIndex = true; + patchIndex.idxSeamVertex = i; } - // adjust texture patches locally, so that the border continues smoothly inside the patch - #ifdef TEXOPT_USE_OPENMP - #pragma omp parallel for schedule(dynamic) - for (int i=0; i<(int)numPatches; ++i) { - #else - for (unsigned i=0; i(std::numeric_limits::max())); + MatIdx rowsX(0); + typedef std::unordered_map VertexPatch2RowMap; + cList vertpatch2rows(vertices.size()); + FOREACH(i, vertices) { + const PatchIndex& patchIndex = patchIndices[i]; + VertexPatch2RowMap& vertpatch2row = vertpatch2rows[i]; + if (patchIndex.bIndex) { + // vertex is part of multiple patches + const SeamVertex& seamVertex = seamVertices[patchIndex.idxSeamVertex]; + ASSERT(seamVertex.idxVertex == i); + for (const SeamVertex::Patch& patch: seamVertex.patches) { + ASSERT(patch.idxPatch != numPatches); + vertpatch2row[patch.idxPatch] = rowsX++; } - if (hasInvalidVertex) break; + } else + if (patchIndex.idxPatch < numPatches) { + // vertex is part of only one patch + vertpatch2row[patchIndex.idxPatch] = rowsX++; } - - // Set bias based on vertex validity: 0.01 if any vertex is invalid, else 1 - const float bias = hasInvalidVertex ? 0.1f : 1.0f; - // const float bias = 1.0f; + } - // extract image - const Image8U3& image0(images[texturePatch.label].image); - Image32F3 image, imageOrg; - image0(texturePatch.rect).convertTo(image, CV_32FC3, 1.0/255.0); - image.copyTo(imageOrg); - // render patch coverage - Image8U mask(image.size()); { - mask.memset(0); - struct RasterMesh { - Image8U& image; - inline void operator()(const ImageRef& pt) { - ASSERT(image.isInside(pt)); - image(pt) = interior; - } - } data{mask}; - for (const FIndex idxFace: texturePatch.faces) { - // if (labelsInvalid[idxFace] != NO_ID) - // continue; - const TexCoord* tri = faceTexcoords.data()+idxFace*3; - ColorMap::RasterizeTriangle(tri[0], tri[1], tri[2], data); - } - } - // render the patch border meeting neighbor patches - const Sampler sampler; - const TexCoord offset(texturePatch.rect.tl()); - for (const SeamVertex& seamVertex0: seamVertices) { - if (seamVertex0.patches.size() < 2) - continue; - const uint32_t idxVertPatch0(seamVertex0.patches.Find(idxPatch)); - if (idxVertPatch0 == SeamVertex::Patches::NO_INDEX) + // fill Tikhonov's Gamma matrix (regularization constraints) + // Bruce + // const float lambda(0.1f); + const float lambda(0.8f); + + MatIdx rowsGamma(0); + Mesh::VertexIdxArr adjVerts; + CLISTDEF0(MatEntry) rows(0, vertices.size()*4); + FOREACH(v, vertices) { + adjVerts.Empty(); + scene.mesh.GetAdjVertices(v, adjVerts); + VertexPatchIterator itV(patchIndices[v], seamVertices); + while (itV.Next()) { + const uint32_t idxPatch(itV); + if (idxPatch == numPatches) continue; - const SeamVertex::Patch& patch0 = seamVertex0.patches[idxVertPatch0]; - const TexCoord p0(patch0.proj-offset); - // for each edge of this vertex belonging to this patch... - for (const SeamVertex::Patch::Edge& edge0: patch0.edges) { - // select the same edge leaving from the adjacent vertex - const SeamVertex& seamVertex1 = seamVertices[edge0.idxSeamVertex]; - const uint32_t idxVertPatch0Adj(seamVertex1.patches.Find(idxPatch)); - ASSERT(idxVertPatch0Adj != SeamVertex::Patches::NO_INDEX); - const SeamVertex::Patch& patch0Adj = seamVertex1.patches[idxVertPatch0Adj]; - const TexCoord p0Adj(patch0Adj.proj-offset); - // find the other patch sharing the same edge (edge with same adjacent vertex) - FOREACH(idxVertPatch1, seamVertex0.patches) { - if (idxVertPatch1 == idxVertPatch0) - continue; - const SeamVertex::Patch& patch1 = seamVertex0.patches[idxVertPatch1]; - const uint32_t idxEdge1(patch1.edges.Find(edge0.idxSeamVertex)); - if (idxEdge1 == SeamVertex::Patch::Edges::NO_INDEX) - continue; - const TexCoord& p1(patch1.proj); - // select the same edge belonging to the second patch leaving from the adjacent vertex - const uint32_t idxVertPatch1Adj(seamVertex1.patches.Find(patch1.idxPatch)); - ASSERT(idxVertPatch1Adj != SeamVertex::Patches::NO_INDEX); - const SeamVertex::Patch& patch1Adj = seamVertex1.patches[idxVertPatch1Adj]; - const TexCoord& p1Adj(patch1Adj.proj); - // this is an edge separating two (valid) patches; - // draw it on this patch as the mean color of the two patches - const Image8U3& image1(images[texturePatches[patch1.idxPatch].label].image); - struct RasterPatch { - Image32F3& image; - Image8U& mask; - const Image32F3& image0; - const Image8U3& image1; - const TexCoord p0, p0Dir; - const TexCoord p1, p1Dir; - const float length; - const Sampler sampler; - inline RasterPatch(Image32F3& _image, Image8U& _mask, const Image32F3& _image0, const Image8U3& _image1, - const TexCoord& _p0, const TexCoord& _p0Adj, const TexCoord& _p1, const TexCoord& _p1Adj) - : image(_image), mask(_mask), image0(_image0), image1(_image1), - p0(_p0), p0Dir(_p0Adj-_p0), p1(_p1), p1Dir(_p1Adj-_p1), length((float)norm(p0Dir)), sampler() {} - inline void operator()(const ImageRef& pt) { - const float l((float)norm(TexCoord(pt)-p0)/length); - // compute mean color - const TexCoord samplePos0(p0 + p0Dir * l); - const Color color0(image0.sample(sampler, samplePos0)); - const TexCoord samplePos1(p1 + p1Dir * l); - const Color color1(image1.sample(sampler, samplePos1)/255.f); - image(pt) = Color((color0 + color1) * 0.5f); - // set mask edge also - mask(pt) = border; - } - } data(image, mask, imageOrg, image1, p0, p0Adj, p1, p1Adj); - Image32F3::DrawLine(p0, p0Adj, data); - // skip remaining patches, - // as a manifold edge is shared by maximum two face (one in each patch), which we found already - break; - } - } - // render the vertex at the patch border meeting neighbor patches - AccumColor accumColor; - // for each patch... - for (const SeamVertex::Patch& patch: seamVertex0.patches) { - // add its view to the vertex mean color - const Image8U3& img(images[texturePatches[patch.idxPatch].label].image); - accumColor.Add(img.sample(sampler, patch.proj)/255.f, 1.f); - } - const ImageRef pt(ROUND2INT(patch0.proj-offset)); - image(pt) = accumColor.Normalized(); - mask(pt) = border; - } - // make sure the border is continuous and - // keep only the exterior tripe of the given size - ProcessMask(mask, 20); - // compute texture patch blending - PoissonBlending(imageOrg, image, mask, bias); - // apply color correction to the patch image - cv::Mat imagePatch(image0(texturePatch.rect)); - for (int r=0; r= vAdj) continue; - const Color& a = image(r,c); - Pixel8U& v = imagePatch.at(r,c); - for (int p=0; p<3; ++p) - v[p] = (uint8_t)CLAMP(ROUND2INT(a[p]*255.f), 0, 255); + VertexPatchIterator itVAdj(patchIndices[vAdj], seamVertices); + while (itVAdj.Next()) { + const uint32_t idxPatchAdj(itVAdj); + if (idxPatch == idxPatchAdj) { + const MatIdx colAdj(vertpatch2rows[vAdj].at(idxPatchAdj)); + rows.emplace_back(rowsGamma, col, lambda); + rows.emplace_back(rowsGamma, colAdj, -lambda); + ++rowsGamma; + } + } } } } -} + ASSERT(rows.size()/2 < static_cast(std::numeric_limits::max())); -void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& basename) -{ - // Bruce - bGlobalSeamLeveling = false; - bLocalSeamLeveling = false; - // project patches in the corresponding view and compute texture-coordinates and bounding-box - const int border(2); - faceTexcoords.resize(faces.size()*3); - faceTexindices.resize(faces.size()); - #ifdef TEXOPT_USE_OPENMP - // LOG_OUT() << "def TEXOPT_USE_OPENMP" << std::endl; - const unsigned numPatches(texturePatches.size()-1); - #pragma omp parallel for schedule(dynamic) - for (int_t idx=0; idx<(int_t)numPatches; ++idx) { - TexturePatch& texturePatch = texturePatches[(uint32_t)idx]; - #else - for (TexturePatch *pTexturePatch=texturePatches.Begin(), *pTexturePatchEnd=texturePatches.End()-1; pTexturePatch(std::numeric_limits::max())); + const MatIdx rowsA((MatIdx)coeffB.size()); + SparseMat A(rowsA, rowsX); + A.setFromTriplets(rows.Begin(), rows.End()); + rows.Release(); - LOG_OUT() << "First loop completed" << std::endl; - // perform seam leveling - if (texturePatches.size() > 2 && (bGlobalSeamLeveling || bLocalSeamLeveling)) { - // create seam vertices and edges - CreateSeamVertices(); + SparseMat Lhs(A.transpose() * A + Gamma.transpose() * Gamma); + // CG uses only the lower triangle, so prune the rest and compress matrix + Lhs.prune([](const int& row, const int& col, const float&) -> bool { + return col <= row; + }); - // perform global seam leveling - if (bGlobalSeamLeveling) { - TD_TIMER_STARTD(); - GlobalSeamLeveling(); - DEBUG_ULTIMATE("\tglobal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); - } + // globally solve for the correction colors + Eigen::Matrix colorAdjustments(rowsX, 3); + { + // init CG solver + Eigen::ConjugateGradient solver; + solver.setMaxIterations(1000); + solver.setTolerance(0.0001f); + solver.compute(Lhs); + ASSERT(solver.info() == Eigen::Success); + #ifdef TEXOPT_USE_OPENMP + #pragma omp parallel for + #endif + for (int channel=0; channel<3; ++channel) { + // init right hand side vector + const Eigen::Map< Eigen::VectorXf, Eigen::Unaligned, Eigen::Stride<0,3> > b(coeffB.front().ptr()+channel, rowsA); + // Bruce + const Eigen::VectorXf Rhs(SparseMat(A.transpose()) * b); + // Eigen::VectorXf Rhs = SparseMat(A.transpose()) * b_map[channel]; + // colorAdjustments.col(channel) = solver.solve(Rhs).array() - solver.solve(Rhs).mean(); - // perform local seam leveling - if (bLocalSeamLeveling) { - TD_TIMER_STARTD(); - LocalSeamLeveling(); - DEBUG_ULTIMATE("\tlocal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); + // solve for x + const Eigen::VectorXf x(solver.solve(Rhs)); + ASSERT(solver.info() == Eigen::Success); + // subtract mean since the system is under-constrained and + // we need the solution with minimal adjustments + Eigen::Map< Eigen::VectorXf, Eigen::Unaligned, Eigen::Stride<0,3> >(colorAdjustments.data()+channel, rowsX) = x.array() - x.mean(); + DEBUG_LEVEL(3, "\tcolor channel %d: %d iterations, %g residual", channel, solver.iterations(), solver.error()); } } - // merge texture patches with overlapping rectangles - for (unsigned i=0; i 0 && bary.y > 0 && bary.z > 0) ? 1.0f : 0.8f; + image(pt) = (colors[0]*bary.x + colors[1]*bary.y + colors[2]*bary.z) * weight; } - // join faces lists - texturePatchBig.faces.JoinRemove(texturePatchSmall.faces); - // remove the small patch - texturePatches.RemoveAtMove(j--); + } data(imageAdj); + + for (const FIndex idxFace: texturePatch.faces) { + const Face& face = faces[idxFace]; + data.tri = faceTexcoords.Begin()+idxFace*3; + for (int v=0; v<3; ++v){ + if (auto search = vertpatch2rows[face[v]].find(idxPatch); search != vertpatch2rows[face[v]].end()) + data.colors[v] = colorAdjustments.row(vertpatch2rows[face[v]].at(idxPatch)); + } + ColorMap::RasterizeTriangleBary(data.tri[0], data.tri[1], data.tri[2], data); } - } - LOG_OUT() << "Second loop completed" << std::endl; - // create texture - { - // arrange texture patches to fit the smallest possible texture image - // const unsigned minPatchSize = 20; - RectsBinPack::RectWIdxArr unplacedRects(texturePatches.size()); - FOREACH(i, texturePatches) { + // dilate with one pixel width, in order to make sure patch border smooths out a little + imageAdj.DilateMean<1>(imageAdj, Color::ZERO); - // LOG_OUT() << "Third loop completed" << std::endl; - if (maxTextureSize > 0 && (texturePatches[i].rect.width > maxTextureSize || texturePatches[i].rect.height > maxTextureSize)) { - DEBUG("error: a patch of size %u x %u does not fit the texture", texturePatches[i].rect.width, texturePatches[i].rect.height); - ABORT("the maximum texture size chosen cannot fit a patch"); + // Bruce + cv::Mat adjMat(imageAdj); + // cv::GaussianBlur(adjMat, adjMat, cv::Size(3,3), 0.5); + + // 将原有3x3高斯核升级为5x5,并增加迭代次数 + cv::GaussianBlur(adjMat, adjMat, cv::Size(5,5), 1.2); + + // 新增:边缘保持滤波(保留锐利边缘的同时平滑颜色过渡) + cv::Mat filteredAdj; + cv::edgePreservingFilter(adjMat, filteredAdj, cv::RECURS_FILTER, 60, 0.4); + adjMat = filteredAdj; + + // 修改:在应用调整时进行边缘检测,避免过度调整 + // cv::Mat edgeMask; + cv::Canny(images[texturePatch.label].image(texturePatch.rect), edgeMask, 50, 150); + + // apply color correction to the patch image + cv::Mat image(images[texturePatch.label].image(texturePatch.rect)); + //* + for (int r=0; r(r,c) > 0 ? 0.3f : 1.0f; + + const Color& a = imageAdj(r,c); + if (a == Color::ZERO) + continue; + Pixel8U& v = image.at(r,c); + if (v.r == 0 && v.g == 0 && v.b == 0) + continue; + const Color col(RGB2YCBCR(Color(v))); + // const Color acol(YCBCR2RGB(Color(col+a))); + Color acol = YCBCR2RGB(Color(col + a * edgeWeight)); // 应用边缘权重 + + for (int p=0; p<3; ++p) { + float val = acol[p]; + val = std::min(std::max(val, 0.0f), 255.0f); + v[p] = static_cast(val + 0.5f); // 四舍五入 + } } - unplacedRects[i] = {texturePatches[i].rect, i}; } - LOG_OUT() << "unplacedRects loop completed" << std::endl; - - LOG_OUT() << "pack patches: one pack per texture file loop completed" << std::endl; - // pack patches: one pack per texture file - CLISTDEF2IDX(RectsBinPack::RectWIdxArr, TexIndex) placedRects; { - // increase texture size till all patches fit - const unsigned typeRectsBinPack(nRectPackingHeuristic/100); - const unsigned typeSplit((nRectPackingHeuristic-typeRectsBinPack*100)/10); - const unsigned typeHeuristic(nRectPackingHeuristic%10); - int textureSize = 0; - while (!unplacedRects.empty()) { - TD_TIMER_STARTD(); - if (textureSize == 0) { - textureSize = RectsBinPack::ComputeTextureSize(unplacedRects, nTextureSizeMultiple); - if (maxTextureSize > 0 && textureSize > maxTextureSize) - textureSize = maxTextureSize; + /* + for (int r=0; r(r,c); + const Color col(RGB2YCBCR(Color(v))); + const Color acol(YCBCR2RGB(Color(col+a))); + + // 添加范围限制 (0-255) + for (int p=0; p<3; ++p) { + float val = acol[p]; + val = std::min(std::max(val, 0.0f), 255.0f); // 确保在0-255范围内 + v[p] = static_cast(val); } + } + } + //*/ + } - RectsBinPack::RectWIdxArr newPlacedRects; - switch (typeRectsBinPack) { - case 0: { - MaxRectsBinPack pack(textureSize, textureSize); - newPlacedRects = pack.Insert(unplacedRects, (MaxRectsBinPack::FreeRectChoiceHeuristic)typeHeuristic); - break; } - case 1: { - SkylineBinPack pack(textureSize, textureSize, typeSplit!=0); - newPlacedRects = pack.Insert(unplacedRects, (SkylineBinPack::LevelChoiceHeuristic)typeHeuristic); - break; } - case 2: { - GuillotineBinPack pack(textureSize, textureSize); - newPlacedRects = pack.Insert(unplacedRects, false, (GuillotineBinPack::FreeRectChoiceHeuristic)typeHeuristic, (GuillotineBinPack::GuillotineSplitHeuristic)typeSplit); - break; } - default: - ABORT("error: unknown RectsBinPack type"); - } - DEBUG_ULTIMATE("\tpacking texture completed: %u initial patches, %u placed patches, %u texture-size, %u textures (%s)", texturePatches.size(), newPlacedRects.size(), textureSize, placedRects.size(), TD_TIMER_GET_FMT().c_str()); +} - if (textureSize == maxTextureSize || unplacedRects.empty()) { - // create texture image - placedRects.emplace_back(std::move(newPlacedRects)); - texturesDiffuse.emplace_back(textureSize, textureSize).setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); - textureSize = 0; - } else { - // try again with a bigger texture - textureSize *= 2; - if (maxTextureSize > 0) - textureSize = std::max(textureSize, maxTextureSize); - unplacedRects.JoinRemove(newPlacedRects); +// New +void MeshTexture::LocalSeamLeveling() +{ + ASSERT(!seamVertices.empty()); + const unsigned numPatches(texturePatches.size()-1); + + // Create a boolean array to mark invalid vertices + BoolArr vertexInvalid(vertices.size()); + vertexInvalid.Memset(false); + FOREACH(f, faces) { + if (labelsInvalid[f] != NO_ID) { + const Face& face = faces[f]; + for (int v=0; v<3; ++v) + vertexInvalid[face[v]] = true; + } + } + + // adjust texture patches locally, so that the border continues smoothly inside the patch + #ifdef TEXOPT_USE_OPENMP + #pragma omp parallel for schedule(dynamic) + for (int i=0; i<(int)numPatches; ++i) { + #else + for (unsigned i=0; i(sampler, samplePos0)); + const TexCoord samplePos1(p1 + p1Dir * l); + const Color color1(image1.sample(sampler, samplePos1)/255.f); + image(pt) = Color((color0 + color1) * 0.5f); + // set mask edge also + mask(pt) = border; + } + } data(image, mask, imageOrg, image1, p0, p0Adj, p1, p1Adj); + Image32F3::DrawLine(p0, p0Adj, data); + // skip remaining patches, + // as a manifold edge is shared by maximum two face (one in each patch), which we found already + break; } } + // render the vertex at the patch border meeting neighbor patches + AccumColor accumColor; + // for each patch... + for (const SeamVertex::Patch& patch: seamVertex0.patches) { + // add its view to the vertex mean color + const Image8U3& img(images[texturePatches[patch.idxPatch].label].image); + accumColor.Add(img.sample(sampler, patch.proj)/255.f, 1.f); + } + const ImageRef pt(ROUND2INT(patch0.proj-offset)); + image(pt) = accumColor.Normalized(); + mask(pt) = border; } - if (texturesDiffuse.size() == 1) - faceTexindices.Release(); - // apply some sharpening - if (fSharpnessWeight > 0) { - constexpr double sigma = 1.5; - for (auto &textureDiffuse: texturesDiffuse) { - Image8U3 blurryTextureDiffuse; - cv::GaussianBlur(textureDiffuse, blurryTextureDiffuse, cv::Size(), sigma); - cv::addWeighted(textureDiffuse, 1+fSharpnessWeight, blurryTextureDiffuse, -fSharpnessWeight, 0, textureDiffuse); + // make sure the border is continuous and + // keep only the exterior tripe of the given size + ProcessMask(mask, 20); + // compute texture patch blending + PoissonBlending(imageOrg, image, mask, bias); + // apply color correction to the patch image + cv::Mat imagePatch(image0(texturePatch.rect)); + for (int r=0; r(r,c); + for (int p=0; p<3; ++p) + v[p] = (uint8_t)CLAMP(ROUND2INT(a[p]*255.f), 0, 255); } } - LOG_OUT() << "Fourth loop completed" << std::endl; + } +} - std::ofstream out(basename + "_empty_color_triangles.txt"); - RFOREACHPTR(pIdxF, emptyFaceIndexes) { - out << *pIdxF << "\n"; +void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& basename) +{ + // Bruce + bGlobalSeamLeveling = false; + bLocalSeamLeveling = false; + // project patches in the corresponding view and compute texture-coordinates and bounding-box + const int border(2); + faceTexcoords.resize(faces.size()*3); + faceTexindices.resize(faces.size()); + #ifdef TEXOPT_USE_OPENMP + // LOG_OUT() << "def TEXOPT_USE_OPENMP" << std::endl; + const unsigned numPatches(texturePatches.size()-1); + #pragma omp parallel for schedule(dynamic) + for (int_t idx=0; idx<(int_t)numPatches; ++idx) { + TexturePatch& texturePatch = texturePatches[(uint32_t)idx]; + #else + for (TexturePatch *pTexturePatch=texturePatches.Begin(), *pTexturePatchEnd=texturePatches.End()-1; pTexturePatch -// 保存生成的纹理图集 -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; + LOG_OUT() << "First loop completed" << std::endl; + // perform seam leveling + if (texturePatches.size() > 2 && (bGlobalSeamLeveling || bLocalSeamLeveling)) { + // create seam vertices and edges + CreateSeamVertices(); + + // perform global seam leveling + if (bGlobalSeamLeveling) { + TD_TIMER_STARTD(); + GlobalSeamLeveling(); + DEBUG_ULTIMATE("\tglobal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); + } + + // perform local seam leveling + if (bLocalSeamLeveling) { + TD_TIMER_STARTD(); + LocalSeamLeveling(); + DEBUG_ULTIMATE("\tlocal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); + } + } + + // merge texture patches with overlapping rectangles + for (unsigned i=0; i 0 && (texturePatches[i].rect.width > maxTextureSize || texturePatches[i].rect.height > maxTextureSize)) { + DEBUG("error: a patch of size %u x %u does not fit the texture", texturePatches[i].rect.width, texturePatches[i].rect.height); + ABORT("the maximum texture size chosen cannot fit a patch"); + } + unplacedRects[i] = {texturePatches[i].rect, i}; + } + LOG_OUT() << "unplacedRects loop completed" << std::endl; + + LOG_OUT() << "pack patches: one pack per texture file loop completed" << std::endl; + // pack patches: one pack per texture file + CLISTDEF2IDX(RectsBinPack::RectWIdxArr, TexIndex) placedRects; { + // increase texture size till all patches fit + const unsigned typeRectsBinPack(nRectPackingHeuristic/100); + const unsigned typeSplit((nRectPackingHeuristic-typeRectsBinPack*100)/10); + const unsigned typeHeuristic(nRectPackingHeuristic%10); + int textureSize = 0; + while (!unplacedRects.empty()) { + TD_TIMER_STARTD(); + if (textureSize == 0) { + textureSize = RectsBinPack::ComputeTextureSize(unplacedRects, nTextureSizeMultiple); + if (maxTextureSize > 0 && textureSize > maxTextureSize) + textureSize = maxTextureSize; + } + + RectsBinPack::RectWIdxArr newPlacedRects; + switch (typeRectsBinPack) { + case 0: { + MaxRectsBinPack pack(textureSize, textureSize); + newPlacedRects = pack.Insert(unplacedRects, (MaxRectsBinPack::FreeRectChoiceHeuristic)typeHeuristic); + break; } + case 1: { + SkylineBinPack pack(textureSize, textureSize, typeSplit!=0); + newPlacedRects = pack.Insert(unplacedRects, (SkylineBinPack::LevelChoiceHeuristic)typeHeuristic); + break; } + case 2: { + GuillotineBinPack pack(textureSize, textureSize); + newPlacedRects = pack.Insert(unplacedRects, false, (GuillotineBinPack::FreeRectChoiceHeuristic)typeHeuristic, (GuillotineBinPack::GuillotineSplitHeuristic)typeSplit); + break; } + default: + ABORT("error: unknown RectsBinPack type"); + } + DEBUG_ULTIMATE("\tpacking texture completed: %u initial patches, %u placed patches, %u texture-size, %u textures (%s)", texturePatches.size(), newPlacedRects.size(), textureSize, placedRects.size(), TD_TIMER_GET_FMT().c_str()); + + if (textureSize == maxTextureSize || unplacedRects.empty()) { + // create texture image + placedRects.emplace_back(std::move(newPlacedRects)); + texturesDiffuse.emplace_back(textureSize, textureSize).setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); + textureSize = 0; + } else { + // try again with a bigger texture + textureSize *= 2; + if (maxTextureSize > 0) + textureSize = std::max(textureSize, maxTextureSize); + unplacedRects.JoinRemove(newPlacedRects); + } + } + } + LOG_OUT() << "Third loop completed" << std::endl; + Mesh::FaceIdxArr emptyFaceIndexes; + + #ifdef TEXOPT_USE_OPENMP + #pragma omp parallel for schedule(dynamic) + for (int_t i=0; i<(int_t)placedRects.size(); ++i) { + for (int_t j=0; j<(int_t)placedRects[(TexIndex)i].size(); ++j) { + const TexIndex idxTexture((TexIndex)i); + const uint32_t idxPlacedPatch((uint32_t)j); + #else + FOREACH(idxTexture, placedRects) { + FOREACH(idxPlacedPatch, placedRects[idxTexture]) { + #endif + const TexturePatch& texturePatch = texturePatches[placedRects[idxTexture][idxPlacedPatch].patchIdx]; + const RectsBinPack::Rect& rect = placedRects[idxTexture][idxPlacedPatch].rect; + // copy patch image + ASSERT((rect.width == texturePatch.rect.width && rect.height == texturePatch.rect.height) || + (rect.height == texturePatch.rect.width && rect.width == texturePatch.rect.height)); + int x(0), y(1); + if (texturePatch.label != NO_ID) { + const Image& imageData = images[texturePatch.label]; + cv::Mat patch(imageData.image(texturePatch.rect)); + if (rect.width != texturePatch.rect.width) { + // flip patch and texture-coordinates + patch = patch.t(); + x = 1; y = 0; + } + patch.copyTo(texturesDiffuse[idxTexture](rect)); + } + else + { + auto it = texturePatch.faces.begin(); + while (it != texturePatch.faces.end()) + { + emptyFaceIndexes.push_back(*it); + ++it; + } + } + // compute final texture coordinates + const TexCoord offset(rect.tl()); + for (const FIndex idxFace: texturePatch.faces) { + TexCoord* texcoords = faceTexcoords.data()+idxFace*3; + faceTexindices[idxFace] = idxTexture; + for (int v=0; v<3; ++v) { + TexCoord& texcoord = texcoords[v]; + texcoord = TexCoord( + texcoord[x]+offset.x, + texcoord[y]+offset.y + ); + } + } + } + } + if (texturesDiffuse.size() == 1) + faceTexindices.Release(); + // apply some sharpening + if (fSharpnessWeight > 0) { + constexpr double sigma = 1.5; + for (auto &textureDiffuse: texturesDiffuse) { + Image8U3 blurryTextureDiffuse; + cv::GaussianBlur(textureDiffuse, blurryTextureDiffuse, cv::Size(), sigma); + cv::addWeighted(textureDiffuse, 1+fSharpnessWeight, blurryTextureDiffuse, -fSharpnessWeight, 0, textureDiffuse); + } + } + LOG_OUT() << "Fourth loop completed" << std::endl; + + std::ofstream out(basename + "_empty_color_triangles.txt"); + RFOREACHPTR(pIdxF, emptyFaceIndexes) { + out << *pIdxF << "\n"; + } + out.close(); + } +} + +#include + +// 保存生成的纹理图集 +bool SaveGeneratedTextures(const Mesh::Image8U3Arr& generatedTextures, const std::string& outputDir) { + if (generatedTextures.empty()) { + DEBUG_EXTRA("错误: 没有纹理可保存"); + return false; + } + + // 确保输出目录存在 + #ifdef _WIN32 + _mkdir(outputDir.c_str()); + #else + mkdir(outputDir.c_str(), 0755); + #endif + + // 保存所有纹理 + for (size_t i = 0; i < generatedTextures.size(); ++i) { + if (generatedTextures[i].empty()) { + DEBUG_EXTRA("警告: 纹理 %zu 为空,跳过保存", i); + continue; + } + + // 生成文件名 + std::string filename = outputDir + "/texture_" + std::to_string(i) + ".png"; + + // 使用OpenCV保存图像 + if (cv::imwrite(filename, generatedTextures[i])) { + DEBUG_EXTRA("成功保存纹理: %s (尺寸: %dx%d)", + filename.c_str(), + generatedTextures[i].cols, + generatedTextures[i].rows); + } else { + DEBUG_EXTRA("错误: 无法保存纹理到 %s", filename.c_str()); + return false; + } + } + + return true; +} +void MeshTexture::CheckColorChannels(const Image8U3& texture, const std::string& name) { + if (texture.empty()) { + DEBUG_EXTRA("%s: 纹理为空", name.c_str()); + return; + } + + // 检查左上角几个像素的颜色 + for (int y = 0; y < std::min(3, texture.rows); ++y) { + for (int x = 0; x < std::min(3, texture.cols); ++x) { + const cv::Vec3b& pixel = texture.at(y, x); + DEBUG_EXTRA("%s[%d,%d]: B=%d, G=%d, R=%d", + name.c_str(), x, y, pixel[0], pixel[1], pixel[2]); + } + } + + // 计算平均颜色 + cv::Scalar mean = cv::mean(texture); + DEBUG_EXTRA("%s 平均颜色: B=%.1f, G=%.1f, R=%.1f", + name.c_str(), mean[0], mean[1], mean[2]); +} +bool MeshTexture::PackTextureAtlases( + Mesh::TexCoordArr& faceTexcoords2, + Mesh::TexIndexArr& faceTexindices2, + std::vector& generatedTextures, + unsigned nTextureSizeMultiple, + unsigned nRectPackingHeuristic, + Pixel8U colEmpty, + int maxTextureSize) +{ + DEBUG_EXTRA("PackTextureAtlases: heuristic=%u, maxSize=%d", nRectPackingHeuristic, maxTextureSize); + + if (texturePatches.empty()) { + DEBUG_EXTRA("No texture patches to pack"); + return false; + } + + // 1. 准备纹理块列表 + std::vector patches; + for (auto& patch : texturePatches) { + if (patch.rect.width > 0 && patch.rect.height > 0) { + patches.push_back(&patch); + } + } + + if (patches.empty()) { + DEBUG_EXTRA("No valid texture patches to pack"); + return false; + } + + DEBUG_EXTRA("Packing %zu texture patches", patches.size()); + + // 2. 计算图集大小 + int atlasSize = 1024; // 默认大小 + if (maxTextureSize > 0) { + atlasSize = std::min(atlasSize, maxTextureSize); + } + + // 确保大小是 nTextureSizeMultiple 的倍数 + if (nTextureSizeMultiple > 1) { + atlasSize = ((atlasSize + nTextureSizeMultiple - 1) / nTextureSizeMultiple) * nTextureSizeMultiple; + } + + DEBUG_EXTRA("Using atlas size: %dx%d", atlasSize, atlasSize); + + // 3. 根据启发式算法对纹理块排序 + switch (nRectPackingHeuristic) { + case 0: // 按面积降序 + std::sort(patches.begin(), patches.end(), + [](const TexturePatch* a, const TexturePatch* b) { + return a->rect.area() > b->rect.area(); + }); + break; + case 1: // 按宽度降序 + std::sort(patches.begin(), patches.end(), + [](const TexturePatch* a, const TexturePatch* b) { + return a->rect.width > b->rect.width; + }); + break; + case 2: // 按高度降序 + std::sort(patches.begin(), patches.end(), + [](const TexturePatch* a, const TexturePatch* b) { + return a->rect.height > b->rect.height; + }); + break; + case 3: // 按最大边降序 + std::sort(patches.begin(), patches.end(), + [](const TexturePatch* a, const TexturePatch* b) { + return std::max(a->rect.width, a->rect.height) > + std::max(b->rect.width, b->rect.height); + }); + break; + default: + // 不排序 + break; + } + + // 4. 使用Skyline算法进行打包 + std::vector skyline(atlasSize, 0); // Skyline高度数组 + std::vector> placements; // 存放每个patch的位置 + + // 创建第一个图集 + Image8U3 firstAtlas(atlasSize, atlasSize); + firstAtlas.fill(colEmpty); + generatedTextures.push_back(firstAtlas); + + int currentAtlasIndex = 0; // 当前图集索引 + + for (const auto& patch : patches) { + int bestX = -1; + int bestY = INT_MAX; + int bestWidth = patch->rect.width; + int bestHeight = patch->rect.height; + + // 寻找最佳放置位置 + for (int x = 0; x <= atlasSize - bestWidth; ++x) { + int maxY = 0; + for (int w = 0; w < bestWidth; ++w) { + maxY = std::max(maxY, skyline[x + w]); + } + + if (maxY + bestHeight <= atlasSize && maxY < bestY) { + bestY = maxY; + bestX = x; + } + } + + if (bestX != -1) { + // 找到位置,放置纹理块 + placements.emplace_back(bestX, bestY); + + // 更新skyline + for (int w = 0; w < bestWidth; ++w) { + skyline[bestX + w] = bestY + bestHeight; + } + + // 更新纹理坐标 + for (FIndex fid : patch->faces) { + TexCoord* texcoords = faceTexcoords2.data() + fid * 3; + + for (int j = 0; j < 3; ++j) { + // 从原始坐标转换到图集坐标 + float u = (texcoords[j].x - patch->rect.x) / patch->rect.width; + float v = (texcoords[j].y - patch->rect.y) / patch->rect.height; + + // 计算新的图集坐标 + texcoords[j].x = (bestX + u * bestWidth) / atlasSize; + texcoords[j].y = (bestY + v * bestHeight) / atlasSize; + } + + // 更新面的纹理索引 + faceTexindices2[fid] = currentAtlasIndex; + } + } else { + // 当前图集已满,创建新图集 + DEBUG_EXTRA("Current atlas is full, creating new one"); + + // 创建新图集 + Image8U3 newAtlas(atlasSize, atlasSize); + newAtlas.fill(colEmpty); + generatedTextures.push_back(newAtlas); + currentAtlasIndex++; + + // 重置skyline + std::fill(skyline.begin(), skyline.end(), 0); + + // 重新放置当前纹理块 + int x = 0; + int y = 0; + placements.emplace_back(x, y); + + // 更新skyline + for (int w = 0; w < bestWidth; ++w) { + skyline[x + w] = bestHeight; + } + + // 更新纹理坐标 + for (FIndex fid : patch->faces) { + TexCoord* texcoords = faceTexcoords2.data() + fid * 3; + + for (int j = 0; j < 3; ++j) { + // 从原始坐标转换到图集坐标 + float u = (texcoords[j].x - patch->rect.x) / patch->rect.width; + float v = (texcoords[j].y - patch->rect.y) / patch->rect.height; + + // 计算新的图集坐标 + texcoords[j].x = (x + u * bestWidth) / atlasSize; + texcoords[j].y = (y + v * bestHeight) / atlasSize; + } + + // 更新面的纹理索引 + faceTexindices2[fid] = currentAtlasIndex; + } + } + } + + DEBUG_EXTRA("Successfully packed %zu patches into %zu texture atlases", + patches.size(), generatedTextures.size()); + + return true; +} +cv::Rect MeshTexture::ComputeOptimalPatchBounds(const AABB2f& aabb, const cv::Size& imageSize, int border) { + DEBUG_EXTRA("Computing optimal patch bounds for AABB, image: %dx%d, border: %d", + imageSize.width, imageSize.height, border); + + // 检查输入参数的有效性 + if (imageSize.width <= 0 || imageSize.height <= 0) { + DEBUG_EXTRA("Error: Invalid image size: %dx%d", imageSize.width, imageSize.height); + return cv::Rect(0, 0, 0, 0); + } + + if (border < 0) { + border = 0; + } + + // 步骤1: 获取AABB的边界值 + float x1, y1, x2, y2; + + // 确保aabb是有效的 + if (aabb.ptMin.x() >= aabb.ptMax.x() || aabb.ptMin.y() >= aabb.ptMax.y()) { + DEBUG_EXTRA("Error: Invalid AABB"); + return cv::Rect(0, 0, 0, 0); + } + + x1 = aabb.ptMin.x(); + y1 = aabb.ptMin.y(); + x2 = aabb.ptMax.x(); + y2 = aabb.ptMax.y(); + + DEBUG_EXTRA("AABB bounds: [%f, %f] - [%f, %f]", x1, y1, x2, y2); + + // 步骤2: 计算基本边界(包含边距) + float minX = x1 - border; + float minY = y1 - border; + float maxX = x2 + border; + float maxY = y2 + border; + + // 步骤3: 确保边界不超出图像范围 + minX = std::max(0.0f, minX); + minY = std::max(0.0f, minY); + maxX = std::min(static_cast(imageSize.width) - 1.0f, maxX); + maxY = std::min(static_cast(imageSize.height) - 1.0f, maxY); + + // 检查边界是否有效 + if (minX >= maxX || minY >= maxY) { + DEBUG_EXTRA("Warning: Invalid bounds after clamping: [%f, %f] - [%f, %f]", + minX, minY, maxX, maxY); + // 返回一个最小边界 + int x = static_cast(std::floor(x1)); + int y = static_cast(std::floor(y1)); + x = std::max(0, std::min(x, imageSize.width - 1)); + y = std::max(0, std::min(y, imageSize.height - 1)); + return cv::Rect(x, y, 1, 1); + } + + // 步骤4: 对齐到整数像素坐标 + int x = static_cast(std::floor(minX)); + int y = static_cast(std::floor(minY)); + int width = static_cast(std::ceil(maxX)) - x; + int height = static_cast(std::ceil(maxY)) - y; + + // 确保最小尺寸 + width = std::max(1, width); + height = std::max(1, height); + + // 确保不超出图像范围 + if (x < 0) x = 0; + if (y < 0) y = 0; + if (x >= imageSize.width) x = imageSize.width - 1; + if (y >= imageSize.height) y = imageSize.height - 1; + if (x + width > imageSize.width) { + width = imageSize.width - x; + } + if (y + height > imageSize.height) { + height = imageSize.height - y; + } + + // 最终验证 + width = std::max(1, width); + height = std::max(1, height); + + cv::Rect result(x, y, width, height); + DEBUG_EXTRA("Optimal patch bounds computed: [%d, %d, %d, %d]", + result.x, result.y, result.width, result.height); + + return result; +} + +void MeshTexture::CleanSeamEdges() +{ + DEBUG_EXTRA("Cleaning seam edges: removing invalid edges"); + + PairIdxArr validSeamEdges; + validSeamEdges.Reserve(seamEdges.GetSize()); + + for (uint32_t i = 0; i < seamEdges.GetSize(); ++i) { + const PairIdx& edge = seamEdges[i]; + + // 检查边索引是否有效 + if (edge.i >= faces.GetSize() || edge.j >= faces.GetSize()) { + DEBUG_EXTRA("Removing invalid seam edge %u: (%u, %u) - faces size: %u", + i, edge.i, edge.j, faces.GetSize()); + continue; + } + + // 检查components数组 + if (edge.i >= components.size() || edge.j >= components.size()) { + DEBUG_EXTRA("Removing invalid seam edge %u: components size mismatch", i); + continue; + } + + // 检查组件ID + if (components[edge.i] == NO_ID || components[edge.j] == NO_ID) { + DEBUG_EXTRA("Removing seam edge %u: faces belong to invalid component", i); + continue; + } + + // 检查mapIdxPatch映射 + if (components[edge.i] >= mapIdxPatch.GetSize() || components[edge.j] >= mapIdxPatch.GetSize()) { + DEBUG_EXTRA("Removing seam edge %u: component ID out of mapIdxPatch range", i); + continue; + } + + validSeamEdges.push_back(edge); + } + + seamEdges = validSeamEdges; + DEBUG_EXTRA("After cleaning: %u valid seam edges remain", seamEdges.GetSize()); +} + +void MeshTexture::FixComponentMappingsOnceAndForAll() +{ + DEBUG_EXTRA("=== Fixing component mappings once and for all ==="); + + // 步骤1: 统计当前状态 + int totalComponents = mapIdxPatch.GetSize(); + int invalidComponents = 0; + std::vector invalidCompIDs; + + for (uint32_t compID = 0; compID < mapIdxPatch.GetSize(); ++compID) { + uint32_t patchIdx = mapIdxPatch[compID]; + if (patchIdx == NO_ID || patchIdx >= texturePatches.size()) { + invalidComponents++; + invalidCompIDs.push_back(compID); + } + } + + DEBUG_EXTRA("Total components: %d", totalComponents); + DEBUG_EXTRA("Invalid components: %d (%.1f%%)", + invalidComponents, (float)invalidComponents * 100.0f / totalComponents); + + if (invalidComponents == 0) { + DEBUG_EXTRA("No invalid components found. Nothing to fix."); + return; + } + + // 步骤2: 构建面到纹理块的直接映射 + std::vector faceToPatch(faces.GetSize(), NO_ID); + for (size_t patchIdx = 0; patchIdx < texturePatches.size(); ++patchIdx) { + const TexturePatch& patch = texturePatches[patchIdx]; + for (uint32_t i = 0; i < patch.faces.GetSize(); ++i) { + FIndex faceIdx = patch.faces[i]; + if (faceIdx < faceToPatch.size()) { + faceToPatch[faceIdx] = static_cast(patchIdx); + } + } + } + + // 步骤3: 统计每个纹理块的面数 + std::vector patchFaceCount(texturePatches.size(), 0); + for (size_t patchIdx = 0; patchIdx < texturePatches.size(); ++patchIdx) { + patchFaceCount[patchIdx] = texturePatches[patchIdx].faces.GetSize(); + } + + DEBUG_EXTRA("Texture patch face counts:"); + for (size_t patchIdx = 0; patchIdx < texturePatches.size(); ++patchIdx) { + DEBUG_EXTRA(" Patch %zu: %d faces", patchIdx, patchFaceCount[patchIdx]); + } + + // 步骤4: 为每个组件找到最合适的纹理块 + std::vector> compPatchVotes(totalComponents); + + // 统计每个组件中纹理块的票数 + for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { + if (faceIdx >= components.GetSize()) { + continue; + } + + uint32_t compID = components[faceIdx]; + uint32_t patchIdx = faceToPatch[faceIdx]; + + if (compID != NO_ID && patchIdx != NO_ID) { + if (compID < compPatchVotes.size()) { + compPatchVotes[compID][patchIdx]++; + } + } + } + + // 步骤5: 为无效组件重新分配纹理块 + int fixedCount = 0; + for (uint32_t compID : invalidCompIDs) { + if (compID >= compPatchVotes.size()) { + continue; + } + + const auto& votes = compPatchVotes[compID]; + + if (!votes.empty()) { + // 使用票数最多的纹理块 + uint32_t bestPatch = NO_ID; + int maxVotes = 0; + + for (const auto& vote : votes) { + if (vote.second > maxVotes) { + maxVotes = vote.second; + bestPatch = vote.first; + } + } + + if (bestPatch != NO_ID) { + mapIdxPatch[compID] = bestPatch; + fixedCount++; + DEBUG_EXTRA(" Fixed component %u -> patch %u (%d votes)", + compID, bestPatch, maxVotes); + continue; + } + } + + // 如果没有投票,尝试找到最近的组件 + if (compID < compPatchVotes.size()) { + // 找到这个组件的所有面 + std::vector compFaces; + for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { + if (faceIdx < components.GetSize() && components[faceIdx] == compID) { + compFaces.push_back(faceIdx); + } + } + + if (!compFaces.empty()) { + // 通过相邻面找到最常见的纹理块 + std::unordered_map neighborPatchVotes; + + for (FIndex faceIdx : compFaces) { + const Face& face = faces[faceIdx]; + for (int i = 0; i < 3; ++i) { + VIndex vertexIdx = face[i]; + if (vertexIdx >= scene.mesh.vertexFaces.size()) { + continue; + } + + const Mesh::FaceIdxArr& adjacentFaces = scene.mesh.vertexFaces[vertexIdx]; + for (FIndex adjFace : adjacentFaces) { + if (adjFace < faceToPatch.size()) { + uint32_t adjPatch = faceToPatch[adjFace]; + if (adjPatch != NO_ID) { + neighborPatchVotes[adjPatch]++; + } + } + } + } + } + + if (!neighborPatchVotes.empty()) { + uint32_t bestPatch = NO_ID; + int maxVotes = 0; + + for (const auto& vote : neighborPatchVotes) { + if (vote.second > maxVotes) { + maxVotes = vote.second; + bestPatch = vote.first; + } + } + + if (bestPatch != NO_ID) { + mapIdxPatch[compID] = bestPatch; + fixedCount++; + DEBUG_EXTRA(" Fixed component %u -> neighbor patch %u (%d votes)", + compID, bestPatch, maxVotes); + continue; + } + } + } + } + + // 最后的回退方案:使用第一个有效的纹理块 + uint32_t fallbackPatch = 0; + for (size_t patchIdx = 0; patchIdx < texturePatches.size(); ++patchIdx) { + if (patchFaceCount[patchIdx] > 0) { + fallbackPatch = static_cast(patchIdx); + break; + } + } + + if (fallbackPatch < texturePatches.size()) { + mapIdxPatch[compID] = fallbackPatch; + fixedCount++; + DEBUG_EXTRA(" Fixed component %u -> fallback patch %u", compID, fallbackPatch); + } else { + DEBUG_EXTRA(" WARNING: Cannot fix component %u - no valid patches", compID); + } + } + + DEBUG_EXTRA("Fixed %d/%d invalid components", fixedCount, invalidComponents); + + // 步骤6: 最终验证 + int remainingInvalid = 0; + for (uint32_t compID = 0; compID < mapIdxPatch.GetSize(); ++compID) { + uint32_t patchIdx = mapIdxPatch[compID]; + if (patchIdx == NO_ID || patchIdx >= texturePatches.size()) { + remainingInvalid++; + } + } + + if (remainingInvalid > 0) { + DEBUG_EXTRA("WARNING: %d components still have invalid patch mappings", remainingInvalid); + + // 强制分配:将所有无效组件映射到第一个纹理块 + for (uint32_t compID = 0; compID < mapIdxPatch.GetSize(); ++compID) { + if (mapIdxPatch[compID] == NO_ID || mapIdxPatch[compID] >= texturePatches.size()) { + mapIdxPatch[compID] = 0; // 强制映射到第一个纹理块 + } + } + DEBUG_EXTRA("Forced all invalid components to patch 0"); + } else { + DEBUG_EXTRA("All components now have valid patch mappings!"); + } + + DEBUG_EXTRA("=== Component mapping fix completed ==="); +} + +void MeshTexture::AssignOrphanFacesToComponents(const std::vector& faceToPatch) +{ + int assigned = 0; + int created = 0; + + for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { + if (components[faceIdx] != NO_ID) { + continue; + } + + // 查找相邻面的组件 + std::unordered_map neighborComponentCounts; + std::unordered_map componentToPatch; + + const Face& face = faces[faceIdx]; + for (int i = 0; i < 3; ++i) { + VIndex vertexIdx = face[i]; + if (vertexIdx >= scene.mesh.vertexFaces.size()) { + continue; + } + + const Mesh::FaceIdxArr& adjacentFaces = scene.mesh.vertexFaces[vertexIdx]; + for (FIndex adjFace : adjacentFaces) { + if (adjFace != faceIdx && adjFace < components.GetSize()) { + uint32_t compID = components[adjFace]; + if (compID != NO_ID && compID < mapIdxPatch.GetSize()) { + neighborComponentCounts[compID]++; + componentToPatch[compID] = mapIdxPatch[compID]; + } + } + } + } + + // 优先选择有有效纹理块映射的相邻组件 + uint32_t bestCompID = NO_ID; + int maxCount = 0; + + for (const auto& pair : neighborComponentCounts) { + uint32_t compID = pair.first; + int count = pair.second; + + // 检查组件是否有有效的纹理块映射 + auto it = componentToPatch.find(compID); + if (it != componentToPatch.end()) { + uint32_t patchIdx = it->second; + if (patchIdx != NO_ID && patchIdx < texturePatches.size() - 1) { + if (count > maxCount) { + maxCount = count; + bestCompID = compID; + } + } + } + } + + if (bestCompID != NO_ID) { + // 分配到现有组件 + components[faceIdx] = bestCompID; + assigned++; + } else { + // 创建新组件 + uint32_t newCompID = static_cast(components.GetSize()); + + // 扩展数组 + components.Resize(faceIdx + 1); + for (uint32_t i = faceIdx; i < components.GetSize(); ++i) { + if (components[i] == NO_ID) { + components[i] = NO_ID; + } + } + + components[faceIdx] = newCompID; + + // 扩展映射数组 + if (newCompID >= mapIdxPatch.GetSize()) { + mapIdxPatch.Resize(newCompID + 1); + for (uint32_t i = 0; i < mapIdxPatch.GetSize(); ++i) { + if (mapIdxPatch[i] == NO_ID && i < texturePatches.size()) { + // 为新组件分配一个默认的有效纹理块 + mapIdxPatch[i] = 0; + } + } + } + + // 为这个新组件找到最近的纹理块 + std::vector faceList = {faceIdx}; + uint32_t nearestPatch = FindNearestPatchForFaces(faceList); + if (nearestPatch != NO_ID && nearestPatch < texturePatches.size() - 1) { + mapIdxPatch[newCompID] = nearestPatch; + } else { + mapIdxPatch[newCompID] = 0; // 默认值 + } + + created++; + } + } + + DEBUG_EXTRA(" Assigned %d orphan faces to existing components, created %d new components", + assigned, created); +} +void MeshTexture::RebuildComponentMapping() +{ + DEBUG_EXTRA("Rebuilding component to patch mapping from scratch..."); + + // 1. 清除现有的映射 + components.clear(); + mapIdxPatch.clear(); + + // 2. 初始化数组 + components.Resize(faces.GetSize()); + for (uint32_t i = 0; i < components.GetSize(); ++i) { + components[i] = NO_ID; + } + + // 3. 构建面到纹理块的映射 + std::vector faceToPatch(faces.GetSize(), NO_ID); + + for (size_t patchIdx = 0; patchIdx < texturePatches.size(); ++patchIdx) { + const TexturePatch& patch = texturePatches[patchIdx]; + + // 跳过无效纹理块 + if (patch.faces.IsEmpty() || + patchIdx == texturePatches.size() - 1) { // 跳过最后一个无效纹理块 + continue; + } + + // 将面映射到纹理块 + for (uint32_t i = 0; i < patch.faces.GetSize(); ++i) { + FIndex faceIdx = patch.faces[i]; + if (faceIdx < faceToPatch.size()) { + faceToPatch[faceIdx] = static_cast(patchIdx); + } + } + } + + DEBUG_EXTRA(" Mapped %zu faces to patches", texturePatches.size() - 1); + + // 4. 构建连通组件(基于相邻面且有相同纹理块) + uint32_t nextCompID = 0; + std::vector visited(faces.GetSize(), false); + + for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { + if (visited[faceIdx] || faceToPatch[faceIdx] == NO_ID) { + continue; + } + + // 广度优先搜索,找到同一个纹理块的连通区域 + uint32_t currentPatch = faceToPatch[faceIdx]; + std::queue queue; + queue.push(faceIdx); + visited[faceIdx] = true; + + while (!queue.empty()) { + FIndex current = queue.front(); + queue.pop(); + + // 分配组件ID + components[current] = nextCompID; + + // 查找相邻面 + const Face& face = faces[current]; + for (int i = 0; i < 3; ++i) { + VIndex vertexIdx = face[i]; + if (vertexIdx >= scene.mesh.vertexFaces.size()) { + continue; + } + + const Mesh::FaceIdxArr& adjacentFaces = scene.mesh.vertexFaces[vertexIdx]; + for (FIndex adjFace : adjacentFaces) { + if (adjFace != current && + !visited[adjFace] && + faceToPatch[adjFace] == currentPatch) { + queue.push(adjFace); + visited[adjFace] = true; + } + } + } + } + + nextCompID++; + } + + DEBUG_EXTRA(" Created %u components", nextCompID); + + // 5. 创建组件到纹理块的映射 + mapIdxPatch.Resize(nextCompID); + for (uint32_t compID = 0; compID < nextCompID; ++compID) { + mapIdxPatch[compID] = NO_ID; + } + + // 统计每个组件中纹理块的分布 + std::vector> componentPatchCounts(nextCompID); + + for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { + uint32_t compID = components[faceIdx]; + uint32_t patchIdx = faceToPatch[faceIdx]; + + if (compID != NO_ID && patchIdx != NO_ID) { + componentPatchCounts[compID][patchIdx]++; + } + } + + // 6. 为每个组件选择最常用的纹理块 + for (uint32_t compID = 0; compID < nextCompID; ++compID) { + const auto& patchCounts = componentPatchCounts[compID]; + + if (!patchCounts.empty()) { + // 找到最常用的纹理块 + uint32_t bestPatch = NO_ID; + int maxCount = 0; + + for (const auto& pair : patchCounts) { + if (pair.second > maxCount) { + maxCount = pair.second; + bestPatch = pair.first; + } + } + + mapIdxPatch[compID] = bestPatch; + } else { + // 没有纹理块的组件,尝试找到最近的纹理块 + std::vector componentFaces; + for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { + if (components[faceIdx] == compID) { + componentFaces.push_back(faceIdx); + } + } + + if (!componentFaces.empty()) { + uint32_t nearestPatch = FindNearestPatchForFaces(componentFaces); + if (nearestPatch != NO_ID && nearestPatch < texturePatches.size() - 1) { + mapIdxPatch[compID] = nearestPatch; + } else { + // 如果没有找到,使用默认的第一个纹理块 + mapIdxPatch[compID] = 0; + } + } else { + mapIdxPatch[compID] = 0; // 默认值 + } + } + } + + // 7. 处理孤立面(没有纹理块的面) + int orphanCount = 0; + for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { + if (components[faceIdx] == NO_ID) { + orphanCount++; + } + } + + if (orphanCount > 0) { + DEBUG_EXTRA(" Found %d orphan faces, assigning them to components...", orphanCount); + AssignOrphanFacesToComponents(faceToPatch); + } + + // 8. 验证结果 + int invalidMappings = 0; + for (uint32_t compID = 0; compID < mapIdxPatch.GetSize(); ++compID) { + uint32_t patchIdx = mapIdxPatch[compID]; + if (patchIdx == NO_ID || patchIdx >= texturePatches.size()) { + invalidMappings++; + } + } + + if (invalidMappings > 0) { + DEBUG_EXTRA(" WARNING: %d components still have invalid patch mappings", invalidMappings); + } else { + DEBUG_EXTRA(" All components have valid patch mappings"); + } +} + +void MeshTexture::CheckMemoryIntegrity() +{ + DEBUG_EXTRA("Checking memory integrity..."); + + // 检查关键数组的完整性 + bool valid = true; + + // 1. 检查faces数组 + if (faces.GetSize() == 0) { + DEBUG_EXTRA(" ERROR: faces array is empty"); + valid = false; + } else { + // 验证每个面的顶点索引 + for (uint32_t i = 0; i < faces.GetSize(); ++i) { + const Face& face = faces[i]; + for (int j = 0; j < 3; ++j) { + if (face[j] >= scene.mesh.vertices.size()) { + DEBUG_EXTRA(" ERROR: Face %u has invalid vertex index %u at position %d", + i, face[j], j); + valid = false; + } + } + } + } + + // 2. 检查faceTexcoords数组 + if (faceTexcoords.GetSize() != faces.GetSize() * 3) { + DEBUG_EXTRA(" ERROR: faceTexcoords size mismatch: %u != %u * 3", + faceTexcoords.GetSize(), faces.GetSize()); + valid = false; + } + + // 3. 检查components数组 + if (components.GetSize() != faces.GetSize()) { + DEBUG_EXTRA(" ERROR: components size mismatch: %u != %u", + components.GetSize(), faces.GetSize()); + valid = false; + } + + // 4. 检查mapIdxPatch数组 + for (uint32_t i = 0; i < mapIdxPatch.GetSize(); ++i) { + if (mapIdxPatch[i] >= texturePatches.size()) { + DEBUG_EXTRA(" WARNING: Component %u maps to invalid patch %u", i, mapIdxPatch[i]); + } + } + + if (valid) { + DEBUG_EXTRA("Memory integrity check passed"); + } else { + DEBUG_EXTRA("Memory integrity check FAILED"); + } +} +bool MeshTexture::ValidateSeamDataForLeveling() +{ + // 简化的验证函数 + if (faceTexcoords.GetSize() < faces.GetSize() * 3) { + DEBUG_EXTRA("ERROR: faceTexcoords size too small"); + return false; + } + + if (components.GetSize() != faces.GetSize()) { + DEBUG_EXTRA("ERROR: components size mismatch"); + return false; + } + + if (seamEdges.GetSize() > 0) { + for (uint32_t i = 0; i < seamEdges.GetSize(); ++i) { + if (seamEdges[i].i >= faces.GetSize() || seamEdges[i].j >= faces.GetSize()) { + DEBUG_EXTRA("ERROR: Invalid seam edge at index %u", i); + return false; + } + } + } + + // 检查 seamVertices + for (uint32_t i = 0; i < seamVertices.GetSize(); ++i) { + const SeamVertex& sv = seamVertices[i]; + + // 检查顶点索引 + if (sv.idxVertex >= scene.mesh.vertices.size()) { + DEBUG_EXTRA("ERROR: Seam vertex %u has invalid vertex index %u", i, sv.idxVertex); + return false; + } + + // 检查是否有patch + if (sv.patches.empty()) { + DEBUG_EXTRA("ERROR: Seam vertex %u has no patches", i); + return false; + } + + // 遍历patches + for (uint32_t patchIndex = 0; patchIndex < sv.patches.GetSize(); ++patchIndex) { + const SeamVertex::Patch& patch = sv.patches[patchIndex]; + + // 使用idxPatch获取纹理块ID + uint32_t patchID = patch.idxPatch; + + if (patchID >= texturePatches.size()) { + DEBUG_EXTRA("ERROR: Seam vertex %u has invalid patch %u (texturePatches size: %zu)", + i, patchID, texturePatches.size()); + return false; + } + + // 还可以检查edges是否有效 + for (uint32_t edgeIdx = 0; edgeIdx < patch.edges.GetSize(); ++edgeIdx) { + const SeamVertex::Patch::Edge& edge = patch.edges[edgeIdx]; + + if (edge.idxSeamVertex >= seamVertices.GetSize()) { + DEBUG_EXTRA("ERROR: Seam vertex %u, patch %u, edge %u has invalid seam vertex index %u", + i, patchID, edgeIdx, edge.idxSeamVertex); + return false; + } + + if (edge.idxFace >= faces.GetSize()) { + DEBUG_EXTRA("ERROR: Seam vertex %u, patch %u, edge %u has invalid face index %u", + i, patchID, edgeIdx, edge.idxFace); + return false; + } + } + } + } + + DEBUG_EXTRA("Seam data validation passed"); + return true; +} + +bool MeshTexture::GenerateTextureWithViewConsistency( + bool bGlobalSeamLeveling, bool bLocalSeamLeveling, + unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, + Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, + const String& basename, bool bOriginFaceview, Scene* pScene) +{ + DEBUG_EXTRA("Starting texture generation with view consistency optimization"); + TD_TIMER_START(); + + const int border = (bOriginFaceview) ? 2 : 4; + const int minPatchSize = 50; + + // 1. 创建视图一致性映射 + std::vector faceViewData(faces.size()); + std::vector patchAssignments(faces.size(), -1); + + // 2. 为每个面选择最佳视图 + SelectOptimalViewsWithConsistency(faceViewData, minPatchSize); + + // 3. 基于视图一致性创建纹理块 + CreateConsistentTexturePatches(faceViewData, patchAssignments, minPatchSize); + + DEBUG_EXTRA("Created %zu texture patches", texturePatches.size()); + + // 检查纹理块是否有效 + if (texturePatches.IsEmpty()) { + DEBUG_EXTRA("Error: No texture patches created"); + return false; + } + + // 4. 生成纹理坐标 + Mesh::TexCoordArr faceTexcoords2(faces.size() * 3); + // 使用 TexIndexArr 而不是 TexFaceArr + Mesh::TexIndexArr faceTexindices2(faces.size() * 3); // 注意:每个面3个顶点,所以是 faces.size() * 3 + + DEBUG_EXTRA("Processing %zu texture patches", texturePatches.size()); + + // 计算需要处理的纹理块数量 + size_t numPatchesToProcess = texturePatches.size(); + if (!texturePatches.empty() && texturePatches.back().label == NO_ID) { + numPatchesToProcess = texturePatches.size() - 1; + } + + DEBUG_EXTRA("Processing %zu valid texture patches", numPatchesToProcess); + + // 初始化纹理索引为无效值 + for (uint32_t i = 0; i < faceTexindices2.GetSize(); ++i) { + faceTexindices2[i] = NO_ID; + } + + // 处理有效纹理块 + for (size_t idx = 0; idx < numPatchesToProcess; ++idx) { + TexturePatch& texturePatch = texturePatches[idx]; + + // 检查纹理块是否有效 + if (texturePatch.faces.IsEmpty()) { + DEBUG_EXTRA("Warning: Texture patch %zu is empty", idx); + continue; + } + + // 检查视图ID是否有效 + if (texturePatch.label == NO_ID || texturePatch.label >= images.size()) { + DEBUG_EXTRA("Warning: Texture patch %zu has invalid label: %d", idx, texturePatch.label); + continue; + } + + const Image& imageData = images[texturePatch.label]; + + // 检查图像是否有效 + if (imageData.image.empty()) { + DEBUG_EXTRA("Warning: Image for patch %zu is empty", idx); + continue; + } + + AABB2f aabb(true); + + // 计算纹理块的UV边界 + bool validAABB = false; + for (const FIndex idxFace : texturePatch.faces) { + if (idxFace >= faces.size()) { + DEBUG_EXTRA("Warning: Invalid face index in patch %zu: %u", idx, idxFace); + continue; + } + + const Face& face = faces[idxFace]; + TexCoord* texcoords = faceTexcoords2.data() + idxFace * 3; + + bool faceValid = true; + for (int i = 0; i < 3; ++i) { + if (face[i] >= vertices.size()) { + DEBUG_EXTRA("Warning: Invalid vertex index in face %u", idxFace); + faceValid = false; + break; + } + + texcoords[i] = imageData.camera.ProjectPointP(vertices[face[i]]); + + // 检查纹理坐标是否在图像边界内 + if (!imageData.image.isInsideWithBorder(texcoords[i], border)) { + float border_f = static_cast(border); + float imgWidth = static_cast(imageData.image.width()); + float imgHeight = static_cast(imageData.image.height()); + + texcoords[i].x = std::max(border_f, std::min(texcoords[i].x, imgWidth - border_f - 1.0f)); + texcoords[i].y = std::max(border_f, std::min(texcoords[i].y, imgHeight - border_f - 1.0f)); + } + aabb.InsertFull(texcoords[i]); + } + + if (faceValid) { + validAABB = true; + // 设置纹理索引 - 每个面3个索引 + faceTexindices2[idxFace * 3] = idxFace * 3; + faceTexindices2[idxFace * 3 + 1] = idxFace * 3 + 1; + faceTexindices2[idxFace * 3 + 2] = idxFace * 3 + 2; + } + } + + if (!validAABB) { + DEBUG_EXTRA("Warning: Texture patch %zu has no valid faces", idx); + texturePatch.rect = cv::Rect(0, 0, 1, 1); + continue; + } + + // 设置纹理块边界 + cv::Rect patchRect = ComputeOptimalPatchBounds(aabb, imageData.image.size(), border); + + // 检查边界是否有效 + if (patchRect.width <= 0 || patchRect.height <= 0 || + patchRect.x < 0 || patchRect.y < 0 || + patchRect.x + patchRect.width > imageData.image.width() || + patchRect.y + patchRect.height > imageData.image.height()) { + + DEBUG_EXTRA("Warning: Invalid rect for patch %zu: [%d, %d, %d, %d]", idx, + patchRect.x, patchRect.y, patchRect.width, patchRect.height); + + // 设置一个安全的边界 + int safeX = std::max(0, patchRect.x); + int safeY = std::max(0, patchRect.y); + int safeWidth = std::max(1, std::min(imageData.image.width() - safeX, patchRect.width)); + int safeHeight = std::max(1, std::min(imageData.image.height() - safeY, patchRect.height)); + + patchRect = cv::Rect(safeX, safeY, safeWidth, safeHeight); + } + + texturePatch.rect = patchRect; + DEBUG_EXTRA("Patch %zu bounds: [%d, %d, %d, %d]", idx, + patchRect.x, patchRect.y, patchRect.width, patchRect.height); + } + + // 5. 处理无效视图纹理块 + if (!texturePatches.empty() && texturePatches.back().label == NO_ID) { + DEBUG_EXTRA("Processing invalid view patch"); + TexturePatch& texturePatch = texturePatches.back(); + const int sizePatch = border * 2 + 1; + texturePatch.rect = cv::Rect(0, 0, sizePatch, sizePatch); + + for (const FIndex idxFace : texturePatch.faces) { + if (idxFace >= faces.size()) { + DEBUG_EXTRA("Warning: Invalid face index in invalid patch: %u", idxFace); + continue; + } + + TexCoord* texcoords = faceTexcoords2.data() + idxFace * 3; + for (int i = 0; i < 3; ++i) { + texcoords[i] = TexCoord(0.5f, 0.5f); + } + // 设置纹理索引 + faceTexindices2[idxFace * 3] = idxFace * 3; + faceTexindices2[idxFace * 3 + 1] = idxFace * 3 + 1; + faceTexindices2[idxFace * 3 + 2] = idxFace * 3 + 2; + } + } + + // 6. 执行接缝均衡 + DEBUG_EXTRA("=== Starting seam leveling phase ==="); + + // 确保组件映射有效 + if (texturePatches.size() > 2) { + DEBUG_EXTRA("Preparing for seam leveling"); + + // 第一步:确保组件映射有效 + DEBUG_EXTRA("Fixing component mappings..."); + FixComponentMappingsOnceAndForAll(); + + // 第二步:重新初始化接缝数据 + DEBUG_EXTRA("Reinitializing seam data..."); + ReinitializeSeamData(); + + // 第三步:验证数据一致性 + if (ValidateSeamDataConsistency()) { + DEBUG_EXTRA("Seam data validation passed"); + + // 第四步:清理接缝边 + DEBUG_EXTRA("Cleaning seam edges..."); + CleanSeamEdgesComprehensive(); + + // 第五步:创建接缝顶点 + DEBUG_EXTRA("Creating seam vertices..."); + CreateSeamVertices(); + + if (bGlobalSeamLeveling) { + DEBUG_EXTRA("Starting global seam leveling"); + GlobalSeamLevelingEnhanced(); + } + + if (bLocalSeamLeveling) { + DEBUG_EXTRA("Starting local seam leveling"); + + // 在局部接缝均衡前进行额外的安全检查 + DEBUG_EXTRA("Performing safety checks before local seam leveling..."); + + // 检查faceTexcoords数组 + if (faceTexcoords.GetSize() != faces.GetSize() * 3) { + DEBUG_EXTRA("WARNING: faceTexcoords size mismatch: %u (expected %u). Fixing...", + faceTexcoords.GetSize(), faces.GetSize() * 3); + + // 重新分配数组 + faceTexcoords.Resize(faces.GetSize() * 3); + } + + // 验证数据结构完整性 + if (!ValidateSeamDataForLeveling()) { + DEBUG_EXTRA("ERROR: Seam data validation failed. Skipping local seam leveling."); + } else { + // 应用局部接缝均衡 + LocalSeamLevelingEnhanced(); + } + } + } else { + DEBUG_EXTRA("WARNING: Seam data validation failed. Skipping seam leveling."); + } + } + + // 7. 合并重叠的纹理块 + DEBUG_EXTRA("Merging overlapping patches"); + MergeOverlappingPatches(faceTexcoords2); + + // 8. 打包纹理块 + DEBUG_EXTRA("Packing texture atlases"); + std::vector generatedTextures; + + if (!PackTextureAtlases(faceTexcoords2, faceTexindices2, generatedTextures, + nTextureSizeMultiple, nRectPackingHeuristic, + colEmpty, maxTextureSize)) { + DEBUG_EXTRA("ERROR: Failed to pack texture atlases"); + return false; + } + + // 9. 高质量纹理采样 + DEBUG_EXTRA("Generating high-quality texture"); + GenerateHighQualityTexture(generatedTextures, faceTexcoords2, faceTexindices2, + fSharpnessWeight, colEmpty); + + // 10. 应用纹理锐化 + if (fSharpnessWeight > 0) { + DEBUG_EXTRA("Applying adaptive sharpening"); + // 如果函数未实现,暂时注释掉 + // ApplyAdaptiveSharpening(generatedTextures, fSharpnessWe + } + // 11. 填充空洞 + DEBUG_EXTRA("Filling texture holes"); + // 如果函数未实现,暂时注释掉 + // FillTextureHoles(generatedTextures, colEmpty); + // 12. 保存结果 + DEBUG_EXTRA("Saving results: %zu textures, %u face indices", + generatedTextures.size(), faceTexindices2.GetSize()); + + // 检查数据完整性... + + // 13. 使用更安全的数据复制方式 + DEBUG_EXTRA("Copying textures to scene mesh..."); + + try { + // 复制到 scene.mesh... + + } catch (const std::exception& e) { + DEBUG_EXTRA("Error copying texture data: %s", e.what()); + return false; + } + + // 14. 将生成的纹理保存到 texturesDiffuseTemp + texturesDiffuseTemp.Release(); + if (!generatedTextures.empty()) { + texturesDiffuseTemp.Reserve((uint32_t)generatedTextures.size()); + for (size_t i = 0; i < generatedTextures.size(); ++i) { + if (!generatedTextures[i].empty()) { + texturesDiffuseTemp.AddEmpty(); + texturesDiffuseTemp.Last() = generatedTextures[i]; + } + } + DEBUG_EXTRA("Saved %u textures to texturesDiffuseTemp", texturesDiffuseTemp.GetSize()); + } else { + DEBUG_EXTRA("Warning: No textures generated for texturesDiffuseTemp"); + } + + DEBUG_EXTRA("Texture generation with view consistency completed in %s", + TD_TIMER_GET_FMT().c_str()); + return true; +} + +void MeshTexture::CleanSeamEdgesComprehensive() +{ + DEBUG_EXTRA("Cleaning seam edges - simplified version"); + + PairIdxArr validSeamEdges; + validSeamEdges.Reserve(seamEdges.GetSize()); + + int validCount = 0; + int invalidCount = 0; + int samePatchCount = 0; + + for (uint32_t edgeIdx = 0; edgeIdx < seamEdges.GetSize(); ++edgeIdx) { + const PairIdx& edge = seamEdges[edgeIdx]; + + // 基本有效性检查 + if (edge.i >= faces.GetSize() || edge.j >= faces.GetSize()) { + invalidCount++; + continue; + } + + if (edge.i >= components.GetSize() || edge.j >= components.GetSize()) { + invalidCount++; + continue; + } + + uint32_t comp0 = components[edge.i]; + uint32_t comp1 = components[edge.j]; + + if (comp0 == NO_ID || comp1 == NO_ID || + comp0 >= mapIdxPatch.GetSize() || comp1 >= mapIdxPatch.GetSize()) { + invalidCount++; + continue; + } + + uint32_t patch0 = mapIdxPatch[comp0]; + uint32_t patch1 = mapIdxPatch[comp1]; + + if (patch0 >= texturePatches.size() || patch1 >= texturePatches.size()) { + invalidCount++; + continue; + } + + if (patch0 == patch1) { + samePatchCount++; + continue; + } + + // 这是有效的接缝边 + validSeamEdges.push_back(edge); + validCount++; + } + + seamEdges = validSeamEdges; + DEBUG_EXTRA("Seam edges cleaned: %d valid, %d same patch, %d invalid", + validCount, samePatchCount, invalidCount); +} + +uint32_t MeshTexture::FindOrCreateComponentForFace(FIndex faceIdx) +{ + if (faceIdx >= faces.GetSize()) { + return NO_ID; + } + + // 首先检查是否已经有组件 + if (faceIdx < components.GetSize() && components[faceIdx] != NO_ID) { + return components[faceIdx]; + } + + // 查找相邻面的组件 + std::unordered_map neighborComponentCounts; + + const Face& face = faces[faceIdx]; + for (int i = 0; i < 3; ++i) { + VIndex vertexIdx = face[i]; + if (vertexIdx >= scene.mesh.vertexFaces.size()) { + continue; + } + + const Mesh::FaceIdxArr& adjacentFaces = scene.mesh.vertexFaces[vertexIdx]; + for (FIndex adjFace : adjacentFaces) { + if (adjFace != faceIdx && adjFace < components.GetSize()) { + uint32_t compID = components[adjFace]; + if (compID != NO_ID) { + neighborComponentCounts[compID]++; + } + } + } + } + + // 如果有相邻组件,使用最常见的那个 + if (!neighborComponentCounts.empty()) { + uint32_t bestCompID = NO_ID; + int maxCount = 0; + + for (const auto& pair : neighborComponentCounts) { + if (pair.second > maxCount) { + maxCount = pair.second; + bestCompID = pair.first; + } + } + + if (bestCompID != NO_ID) { + return bestCompID; + } + } + + // 没有相邻组件,创建新的 + uint32_t newCompID = static_cast(components.GetSize()); + + // 确保components数组足够大 + if (faceIdx >= components.GetSize()) { + uint32_t oldSize = components.GetSize(); + // 使用Resize而不是resize + components.Resize(faceIdx + 1); + // 初始化新添加的元素为NO_ID + for (uint32_t i = oldSize; i < components.GetSize(); ++i) { + components[i] = NO_ID; + } + } + + components[faceIdx] = newCompID; + + // 确保mapIdxPatch数组足够大 + if (newCompID >= mapIdxPatch.GetSize()) { + uint32_t oldSize = mapIdxPatch.GetSize(); + mapIdxPatch.Resize(newCompID + 1); + // 初始化新添加的元素为NO_ID + for (uint32_t i = oldSize; i < mapIdxPatch.GetSize(); ++i) { + mapIdxPatch[i] = NO_ID; + } + } + + return newCompID; +} + +uint32_t MeshTexture::FindNearestPatchForComponent(uint32_t compID) +{ + if (compID == NO_ID) { + return NO_ID; + } + + // 收集属于这个组件的所有面 + std::vector componentFaces; + for (FIndex faceIdx = 0; faceIdx < components.size(); ++faceIdx) { + if (components[faceIdx] == compID) { + componentFaces.push_back(faceIdx); + } + } + + if (componentFaces.empty()) { + return NO_ID; + } + + return FindNearestPatchForFaces(componentFaces); +} + +bool MeshTexture::ValidateSeamDataConsistency() +{ + DEBUG_EXTRA("Validating seam data consistency..."); + + bool valid = true; + + // 1. 验证components数组 + if (components.size() != faces.GetSize()) { + DEBUG_EXTRA("ERROR: components size (%zu) doesn't match faces size (%u)", + components.size(), faces.GetSize()); + return false; + } + + // 2. 验证每个面都有组件ID + int orphanFaces = 0; + for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { + if (components[faceIdx] == NO_ID) { + orphanFaces++; + } + } + + if (orphanFaces > 0) { + DEBUG_EXTRA("WARNING: %d faces have no component ID", orphanFaces); + } + + // 3. 验证mapIdxPatch数组 + for (uint32_t compID = 0; compID < mapIdxPatch.GetSize(); ++compID) { + uint32_t patchIdx = mapIdxPatch[compID]; + if (patchIdx >= texturePatches.size()) { + DEBUG_EXTRA("ERROR: Component %u maps to invalid patch %u", compID, patchIdx); + valid = false; + } + } + + // 4. 验证纹理块 + for (size_t patchIdx = 0; patchIdx < texturePatches.size(); ++patchIdx) { + const TexturePatch& patch = texturePatches[patchIdx]; + for (uint32_t i = 0; i < patch.faces.GetSize(); ++i) { + FIndex fid = patch.faces[i]; + if (fid >= faces.GetSize()) { + DEBUG_EXTRA("ERROR: Patch %zu contains invalid face index %u", patchIdx, fid); + valid = false; + } else if (fid >= components.size()) { + DEBUG_EXTRA("ERROR: Patch %zu face %u out of components range", patchIdx, fid); + valid = false; + } + } + } + + if (valid) { + DEBUG_EXTRA("Seam data consistency validation passed"); + } else { + DEBUG_EXTRA("Seam data consistency validation failed"); + } + + return valid; +} +void MeshTexture::ReinitializeSeamData() +{ + DEBUG_EXTRA("Reinitializing seam data (simplified)..."); + + // 1. 确保组件数组大小正确 + if (components.GetSize() != faces.GetSize()) { + DEBUG_EXTRA("Resizing components array from %u to %u", + components.GetSize(), faces.GetSize()); + components.Resize(faces.GetSize()); + } + + // 2. 初始化所有组件为NO_ID + for (uint32_t i = 0; i < components.GetSize(); ++i) { + components[i] = NO_ID; + } + + // 3. 从纹理块分配组件ID + uint32_t nextCompID = 0; + + for (size_t patchIdx = 0; patchIdx < texturePatches.size(); ++patchIdx) { + const TexturePatch& patch = texturePatches[patchIdx]; + + // 跳过空纹理块 + if (patch.faces.IsEmpty()) { + continue; + } + + // 为这个纹理块的所有面分配同一个组件ID + for (uint32_t i = 0; i < patch.faces.GetSize(); ++i) { + FIndex faceIdx = patch.faces[i]; + if (faceIdx < components.GetSize()) { + components[faceIdx] = nextCompID; + } + } + + nextCompID++; + } + + DEBUG_EXTRA("Assigned %u components from %zu patches", nextCompID, texturePatches.size()); + + // 4. 处理没有组件的面 + int orphanFaces = 0; + for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { + if (components[faceIdx] == NO_ID) { + orphanFaces++; + } + } + + if (orphanFaces > 0) { + DEBUG_EXTRA("Found %d faces without components. Assigning them...", orphanFaces); + + // 为孤立面分配组件 + for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { + if (components[faceIdx] != NO_ID) { + continue; + } + + // 查找相邻面的组件 + uint32_t neighborComp = NO_ID; + + const Face& face = faces[faceIdx]; + for (int i = 0; i < 3; ++i) { + VIndex vertexIdx = face[i]; + if (vertexIdx >= scene.mesh.vertexFaces.size()) { + continue; + } + + const Mesh::FaceIdxArr& adjacentFaces = scene.mesh.vertexFaces[vertexIdx]; + for (FIndex adjFace : adjacentFaces) { + if (adjFace != faceIdx && adjFace < components.GetSize()) { + if (components[adjFace] != NO_ID) { + neighborComp = components[adjFace]; + break; + } + } + } + + if (neighborComp != NO_ID) { + break; + } + } + + if (neighborComp != NO_ID) { + components[faceIdx] = neighborComp; + } else { + // 创建一个新组件 + components[faceIdx] = nextCompID; + nextCompID++; + } + } + } + + // 5. 创建组件到纹理块的映射 + mapIdxPatch.Resize(nextCompID); + for (uint32_t compID = 0; compID < nextCompID; ++compID) { + mapIdxPatch[compID] = NO_ID; // 初始化为NO_ID + } + + // 6. 为每个组件找到对应的纹理块 + std::vector> compPatchVotes(nextCompID); + + for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { + if (faceIdx >= components.GetSize()) { + continue; + } + + uint32_t compID = components[faceIdx]; + + // 查找这个面属于哪个纹理块 + uint32_t facePatch = NO_ID; + for (size_t patchIdx = 0; patchIdx < texturePatches.size(); ++patchIdx) { + const TexturePatch& patch = texturePatches[patchIdx]; + for (uint32_t i = 0; i < patch.faces.GetSize(); ++i) { + if (patch.faces[i] == faceIdx) { + facePatch = static_cast(patchIdx); + break; + } + } + if (facePatch != NO_ID) { + break; + } + } + + if (facePatch != NO_ID && compID < compPatchVotes.size()) { + compPatchVotes[compID][facePatch]++; + } + } + + // 7. 为每个组件分配票数最多的纹理块 + for (uint32_t compID = 0; compID < nextCompID; ++compID) { + const auto& votes = compPatchVotes[compID]; + + if (!votes.empty()) { + uint32_t bestPatch = NO_ID; + int maxVotes = 0; + + for (const auto& vote : votes) { + if (vote.second > maxVotes) { + maxVotes = vote.second; + bestPatch = vote.first; + } + } + + if (bestPatch != NO_ID) { + mapIdxPatch[compID] = bestPatch; + } + } + + // 如果还是NO_ID,使用默认值0 + if (mapIdxPatch[compID] == NO_ID) { + mapIdxPatch[compID] = 0; + } + } + + DEBUG_EXTRA("Seam data reinitialized: %u components, %u valid mappings", + nextCompID, mapIdxPatch.GetSize()); +} +void MeshTexture::AssignComponentsToOrphanFaces() +{ + DEBUG_EXTRA("Assigning components to orphan faces - improved version"); + + int assignedFaces = 0; + + // 使用队列处理孤立面 + std::vector orphanFaces; + for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { + if (faceIdx >= components.GetSize() || components[faceIdx] == NO_ID) { + orphanFaces.push_back(faceIdx); + } + } + + DEBUG_EXTRA("Found %zu orphan faces", orphanFaces.size()); + + // 多次迭代,直到没有变化 + bool changed = true; + int iteration = 0; + const int MAX_ITERATIONS = 10; + + while (changed && iteration < MAX_ITERATIONS && !orphanFaces.empty()) { + changed = false; + iteration++; + + std::vector newOrphans; + + for (FIndex faceIdx : orphanFaces) { + // 查找所有相邻面的组件 + std::unordered_map neighborComponentCounts; + std::unordered_map componentToPatch; // 组件到纹理块的映射 + + const Face& face = faces[faceIdx]; + for (int i = 0; i < 3; ++i) { + VIndex vertexIdx = face[i]; + if (vertexIdx >= scene.mesh.vertexFaces.size()) { + continue; + } + + const Mesh::FaceIdxArr& adjacentFaces = scene.mesh.vertexFaces[vertexIdx]; + for (FIndex adjFace : adjacentFaces) { + if (adjFace != faceIdx && adjFace < components.GetSize()) { + uint32_t compID = components[adjFace]; + if (compID != NO_ID) { + neighborComponentCounts[compID]++; + // 记录组件到纹理块的映射 + if (compID < mapIdxPatch.GetSize()) { + componentToPatch[compID] = mapIdxPatch[compID]; + } + } + } + } + } + + // 选择有效的组件(有有效纹理块映射的) + uint32_t bestCompID = NO_ID; + int maxCount = 0; + + for (const auto& pair : neighborComponentCounts) { + uint32_t compID = pair.first; + int count = pair.second; + + // 检查组件是否有有效的纹理块映射 + auto it = componentToPatch.find(compID); + if (it != componentToPatch.end()) { + uint32_t patchIdx = it->second; + if (patchIdx != NO_ID && patchIdx < texturePatches.size() - 1) { + if (count > maxCount) { + maxCount = count; + bestCompID = compID; + } + } + } + } + + if (bestCompID != NO_ID) { + // 确保components数组足够大 + if (faceIdx >= components.GetSize()) { + uint32_t oldSize = components.GetSize(); + components.Resize(faceIdx + 1); + for (uint32_t i = oldSize; i < components.GetSize(); ++i) { + components[i] = NO_ID; + } + } + components[faceIdx] = bestCompID; + assignedFaces++; + changed = true; + } else { + newOrphans.push_back(faceIdx); + } + } + + orphanFaces = std::move(newOrphans); + } + + // 处理剩余的孤立面 + if (!orphanFaces.empty()) { + DEBUG_EXTRA(" %zu faces remain orphaned after %d iterations. Grouping them...", + orphanFaces.size(), iteration); + + // 将剩余的孤立面分组 + std::vector> orphanGroups; + std::unordered_set processed; + + for (FIndex faceIdx : orphanFaces) { + if (processed.find(faceIdx) != processed.end()) { + continue; + } + + std::vector currentGroup; + std::queue toProcess; + toProcess.push(faceIdx); + processed.insert(faceIdx); + + while (!toProcess.empty()) { + FIndex current = toProcess.front(); + toProcess.pop(); + currentGroup.push_back(current); + + // 查找相邻的孤立面 + const Face& face = faces[current]; + for (int i = 0; i < 3; ++i) { + VIndex vertexIdx = face[i]; + if (vertexIdx >= scene.mesh.vertexFaces.size()) { + continue; + } + + const Mesh::FaceIdxArr& adjacentFaces = scene.mesh.vertexFaces[vertexIdx]; + for (FIndex adjFace : adjacentFaces) { + if (adjFace != current && + std::find(orphanFaces.begin(), orphanFaces.end(), adjFace) != orphanFaces.end() && + processed.find(adjFace) == processed.end()) { + toProcess.push(adjFace); + processed.insert(adjFace); + } + } + } + } + + if (!currentGroup.empty()) { + orphanGroups.push_back(currentGroup); + } + } + + // 为每个组创建一个组件 + for (const auto& group : orphanGroups) { + if (group.empty()) { + continue; + } + + // 为这个组找到最合适的纹理块 + uint32_t bestPatch = FindNearestPatchForFaces(group); + if (bestPatch == NO_ID || bestPatch >= texturePatches.size() - 1) { + // 无法找到合适的纹理块,使用默认的第一个纹理块 + bestPatch = 0; + } + + // 创建新组件 + uint32_t newCompID = static_cast(components.GetSize()); + + // 确保mapIdxPatch数组足够大 + if (newCompID >= mapIdxPatch.GetSize()) { + uint32_t oldSize = mapIdxPatch.GetSize(); + mapIdxPatch.Resize(newCompID + 1); + for (uint32_t i = oldSize; i < mapIdxPatch.GetSize(); ++i) { + mapIdxPatch[i] = NO_ID; + } + } + + // 设置映射 + mapIdxPatch[newCompID] = bestPatch; + + // 设置每个面的组件ID + for (FIndex faceIdx : group) { + if (faceIdx >= components.GetSize()) { + uint32_t oldSize = components.GetSize(); + components.Resize(faceIdx + 1); + for (uint32_t i = oldSize; i < components.GetSize(); ++i) { + components[i] = NO_ID; + } + } + components[faceIdx] = newCompID; + assignedFaces++; + } + } + } + + DEBUG_EXTRA(" Assigned components to %d orphan faces", assignedFaces); +} + +// 视图一致性选择函数 +void MeshTexture::SelectOptimalViewsWithConsistency( + std::vector& faceViewData, + int minPatchSize) +{ + DEBUG_EXTRA("Selecting optimal views with consistency"); + + const int numFaces = (int)faces.size(); + const float consistencyWeight = 0.3f; // 一致性权重 + + // 步骤1: 计算每个面的候选视图质量 + std::vector>> faceCandidates(numFaces); + + #pragma omp parallel for schedule(dynamic) + for (int fid = 0; fid < numFaces; ++fid) { + const FIndex idxFace = (FIndex)fid; + const Face& face = faces[idxFace]; + + // 计算候选视图 + std::vector> candidates; + for (int viewID = 0; viewID < (int)images.size(); ++viewID) { + if (IsFaceVisibleFromView(idxFace, viewID)) { + float quality = ComputeViewQuality(idxFace, viewID); + candidates.emplace_back(viewID, quality); + } + } + + // 按质量排序 + std::sort(candidates.begin(), candidates.end(), + [](const auto& a, const auto& b) { return a.second > b.second; }); + + faceCandidates[fid] = std::move(candidates); + } + + // 步骤2: 迭代优化视图选择,考虑相邻面一致性 + std::vector selectedViews(numFaces, -1); + std::vector viewScores(numFaces, 0.0f); + + // 初始化:选择质量最高的视图 + #pragma omp parallel for schedule(static) + for (int fid = 0; fid < numFaces; ++fid) { + if (!faceCandidates[fid].empty()) { + selectedViews[fid] = faceCandidates[fid][0].first; + viewScores[fid] = faceCandidates[fid][0].second; + } + } + + // 构建顶点到面的映射,用于快速查找相邻面 + std::vector> vertexToFaces(vertices.size()); + for (FIndex fid = 0; fid < (FIndex)faces.size(); ++fid) { + const Face& face = faces[fid]; + for (int i = 0; i < 3; ++i) { + vertexToFaces[face[i]].push_back(fid); + } + } + + // 迭代优化 + for (int iteration = 0; iteration < 5; ++iteration) { + int changes = 0; + + #pragma omp parallel for schedule(dynamic) reduction(+:changes) + for (int fid = 0; fid < numFaces; ++fid) { + if (faceCandidates[fid].empty()) continue; + + const Face& face = faces[fid]; + std::vector neighborViews; + + // 通过共享顶点收集相邻面的视图 + std::unordered_set processedNeighbors; + + for (int i = 0; i < 3; ++i) { + const VIndex vertexIdx = face[i]; + const std::vector& adjacentFaces = vertexToFaces[vertexIdx]; + + for (FIndex neighborFid : adjacentFaces) { + // 跳过自己 + if (neighborFid == (FIndex)fid) continue; // 强制类型转换 + + // 确保是真正的相邻面(共享边) + const Face& neighborFace = faces[neighborFid]; + bool sharesEdge = false; + + // 检查两个面是否共享一条边 + for (int j = 0; j < 3 && !sharesEdge; ++j) { + VIndex v1 = neighborFace[j]; + VIndex v2 = neighborFace[(j + 1) % 3]; + + // 检查边 (v1, v2) 是否在当前面中 + for (int k = 0; k < 3; ++k) { + VIndex w1 = face[k]; + VIndex w2 = face[(k + 1) % 3]; + + if ((v1 == w1 && v2 == w2) || (v1 == w2 && v2 == w1)) { + sharesEdge = true; + break; + } + } + } + + if (sharesEdge && selectedViews[neighborFid] != -1) { + if (processedNeighbors.find(neighborFid) == processedNeighbors.end()) { + neighborViews.push_back(selectedViews[neighborFid]); + processedNeighbors.insert(neighborFid); + } + } + } + } + + if (neighborViews.empty()) continue; + + // 计算相邻面中最常见的视图 + std::unordered_map viewCounts; + for (int viewID : neighborViews) { + viewCounts[viewID]++; + } + + int mostCommonView = -1; + int maxCount = 0; + for (const auto& pair : viewCounts) { + if (pair.second > maxCount) { + maxCount = pair.second; + mostCommonView = pair.first; + } + } + + // 重新计算视图得分,考虑一致性 + int bestView = selectedViews[fid]; + float bestScore = viewScores[fid]; + + for (const auto& candidate : faceCandidates[fid]) { + int viewID = candidate.first; + float quality = candidate.second; + + // 计算一致性得分 + float consistency = 0.0f; + if (viewID == mostCommonView) { + consistency = 0.5f * (static_cast(maxCount) / neighborViews.size()); + } + + // 综合得分 + float totalScore = (1.0f - consistencyWeight) * quality + + consistencyWeight * consistency; + + if (totalScore > bestScore) { + bestScore = totalScore; + bestView = viewID; + } + } + + if (bestView != selectedViews[fid]) { + selectedViews[fid] = bestView; + viewScores[fid] = bestScore; + changes++; + } + } + + DEBUG_EXTRA("View selection iteration %d: %d changes", iteration + 1, changes); + if (changes == 0) break; + } + + // 步骤3: 确保每个视图块至少有minPatchSize个面 + std::unordered_map> viewToFaces; + for (FIndex fid = 0; fid < (FIndex)selectedViews.size(); ++fid) { + int viewID = selectedViews[fid]; + if (viewID != -1) { + viewToFaces[viewID].push_back(fid); + } + } + + // 移除过小的视图块 + for (const auto& pair : viewToFaces) { + if ((int)pair.second.size() < minPatchSize) { + // 重新分配这些面 + for (FIndex fid : pair.second) { + // 寻找相邻面中最常见的视图 + std::unordered_map neighborViewCounts; + const Face& face = faces[fid]; + + for (int i = 0; i < 3; ++i) { + const VIndex vertexIdx = face[i]; + const std::vector& adjacentFaces = vertexToFaces[vertexIdx]; + + for (FIndex neighborFid : adjacentFaces) { + if (neighborFid != fid && selectedViews[neighborFid] != -1) { + neighborViewCounts[selectedViews[neighborFid]]++; + } + } + } + + // 选择最常见的相邻视图 + int bestNeighborView = -1; + int maxNeighborCount = 0; + for (const auto& countPair : neighborViewCounts) { + if (countPair.second > maxNeighborCount) { + maxNeighborCount = countPair.second; + bestNeighborView = countPair.first; + } + } + + if (bestNeighborView != -1) { + selectedViews[fid] = bestNeighborView; + } else { + // 如果没有合适的相邻视图,选择质量最高的候选视图 + if (!faceCandidates[fid].empty()) { + selectedViews[fid] = faceCandidates[fid][0].first; + } + } + } + } + } + + // 保存结果 + #pragma omp parallel for schedule(static) + for (int fid = 0; fid < numFaces; ++fid) { + faceViewData[fid].viewID = selectedViews[fid]; + faceViewData[fid].quality = viewScores[fid]; + } + + DEBUG_EXTRA("View selection with consistency completed"); +} +// 创建一致性纹理块 +void MeshTexture::CreateConsistentTexturePatches( + const std::vector& faceViewData, + std::vector& patchAssignments, + int minPatchSize) +{ + DEBUG_EXTRA("Creating consistent texture patches"); + + const int numFaces = static_cast(faces.size()); + std::vector visited(numFaces, false); + int patchCounter = 0; + + // 清空现有的纹理块 + texturePatches.clear(); // 修正: Clear() -> clear() + + // 步骤0: 构建边到面的映射 + std::unordered_map> edgeToFaceMap; + for (int fid = 0; fid < numFaces; ++fid) { + const Face& face = faces[fid]; + for (int i = 0; i < 3; ++i) { + VIndex v0 = face[i]; + VIndex v1 = face[(i + 1) % 3]; + + if (v0 > v1) std::swap(v0, v1); + + uint64_t edgeKey = (static_cast(v0) << 32) | v1; + edgeToFaceMap[edgeKey].push_back(static_cast(fid)); + } + } + + // 步骤1: 创建基于视图的纹理块 + std::vector> patchFaces; + + for (int startFace = 0; startFace < numFaces; ++startFace) { + if (visited[startFace] || faceViewData[startFace].viewID == -1) { + continue; + } + + int viewID = faceViewData[startFace].viewID; + std::vector currentPatch; + std::queue faceQueue; + + faceQueue.push(startFace); + visited[startFace] = true; + + while (!faceQueue.empty()) { + int fid = faceQueue.front(); + faceQueue.pop(); + + currentPatch.push_back(static_cast(fid)); + patchAssignments[fid] = patchCounter; + + const Face& face = faces[fid]; + + // 查找相同视图的相邻面 + for (int i = 0; i < 3; ++i) { + VIndex idxV0 = face[i]; + VIndex idxV1 = face[(i + 1) % 3]; + + if (idxV0 > idxV1) std::swap(idxV0, idxV1); + uint64_t edgeKey = (static_cast(idxV0) << 32) | idxV1; + + auto it = edgeToFaceMap.find(edgeKey); + if (it != edgeToFaceMap.end()) { + for (FIndex neighborFid : it->second) { + int nfid = static_cast(neighborFid); + if (nfid == fid) continue; + if (visited[nfid]) continue; + + if (faceViewData[nfid].viewID == viewID) { + visited[nfid] = true; + faceQueue.push(nfid); + } + } + } + } + } + + // 只保留足够大的纹理块 + if (static_cast(currentPatch.size()) >= minPatchSize) { + patchFaces.push_back(std::move(currentPatch)); + patchCounter++; + } else { + // 小纹理块重新标记为未分配 + for (FIndex fid : currentPatch) { + patchAssignments[static_cast(fid)] = -1; + } + } + } + + DEBUG_EXTRA("Initial patch creation: %zu patches", patchFaces.size()); + + // 步骤2: 处理未分配的面 + std::vector unassignedFaces; + for (int fid = 0; fid < numFaces; ++fid) { + if (patchAssignments[fid] == -1 && faceViewData[fid].viewID != -1) { + unassignedFaces.push_back(fid); + } + } + + DEBUG_EXTRA("Found %zu unassigned faces", unassignedFaces.size()); + + if (!unassignedFaces.empty()) { + // 构建相邻面映射 + std::vector> faceNeighbors(numFaces); + for (int fid = 0; fid < numFaces; ++fid) { + const Face& face = faces[fid]; + for (int i = 0; i < 3; ++i) { + VIndex idxV0 = face[i]; + VIndex idxV1 = face[(i + 1) % 3]; + + if (idxV0 > idxV1) std::swap(idxV0, idxV1); + uint64_t edgeKey = (static_cast(idxV0) << 32) | idxV1; + + auto it = edgeToFaceMap.find(edgeKey); + if (it != edgeToFaceMap.end()) { + for (FIndex neighborFid : it->second) { + int nfid = static_cast(neighborFid); + if (nfid != fid) { + faceNeighbors[fid].push_back(nfid); + } + } + } + } + } + + // 为未分配的面找到最近的纹理块 + std::vector faceDistances(numFaces, -1); + std::queue bfsQueue; + + // 初始化BFS - 从已分配的面开始 + for (int fid = 0; fid < numFaces; ++fid) { + if (patchAssignments[fid] != -1) { + bfsQueue.push(fid); + faceDistances[fid] = 0; + } + } + + // 执行BFS + std::vector> assignments; + + while (!bfsQueue.empty()) { + int fid = bfsQueue.front(); + bfsQueue.pop(); + + int currentPatch = patchAssignments[fid]; + int currentDist = faceDistances[fid]; + + // 检查currentPatch是否有效 + if (currentPatch < 0 || currentPatch >= (int)patchFaces.size()) { + DEBUG_EXTRA("Warning: Invalid patch ID %d for face %d", currentPatch, fid); + continue; + } + + for (int neighborFid : faceNeighbors[fid]) { + if (neighborFid < 0 || neighborFid >= numFaces) { + DEBUG_EXTRA("Warning: Invalid neighbor face ID %d for face %d", neighborFid, fid); + continue; + } + + if (faceDistances[neighborFid] == -1) { + faceDistances[neighborFid] = currentDist + 1; + bfsQueue.push(neighborFid); + + // 如果这个面是未分配的,将其分配到当前面的纹理块 + if (patchAssignments[neighborFid] == -1) { + patchAssignments[neighborFid] = currentPatch; + assignments.push_back(std::make_pair(neighborFid, currentPatch)); + } + } + } + } + + // 将分配的面加入到对应的纹理块 + for (const auto& assignment : assignments) { + int fid = assignment.first; + int patchID = assignment.second; + + if (fid < 0 || fid >= numFaces) { + DEBUG_EXTRA("Warning: Invalid face ID in assignment: %d", fid); + continue; + } + + if (patchID < 0 || patchID >= (int)patchFaces.size()) { + DEBUG_EXTRA("Warning: Invalid patch ID in assignment: %d for face %d", patchID, fid); + continue; + } + + patchFaces[patchID].push_back(static_cast(fid)); + } + } + + // 步骤3: 创建纹理块 + DEBUG_EXTRA("Creating texture patches structure with %zu patches", patchFaces.size()); + + // 先计算有效纹理块的数量 + size_t validPatches = 0; + for (const auto& patch : patchFaces) { + if (!patch.empty()) { + validPatches++; + } + } + + texturePatches.resize(validPatches + 1); // 修正: Resize() -> resize() + + DEBUG_EXTRA("Allocated %zu texture patches (including invalid patch)", texturePatches.size()); + + size_t patchIdx = 0; + for (size_t patchID = 0; patchID < patchFaces.size(); ++patchID) { + if (patchFaces[patchID].empty()) { + DEBUG_EXTRA("Skipping empty patch %zu", patchID); + continue; + } + + TexturePatch& patch = texturePatches[patchIdx]; + + // 复制面索引 + patch.faces.resize(patchFaces[patchID].size()); // 修正: Resize() -> resize() + for (size_t i = 0; i < patchFaces[patchID].size(); ++i) { + patch.faces[i] = patchFaces[patchID][i]; + } + + // 确定纹理块的视图 + std::unordered_map viewCounts; + for (FIndex fid : patch.faces) { + int viewID = faceViewData[static_cast(fid)].viewID; + if (viewID != -1) { + viewCounts[viewID]++; + } + } + + int dominantView = -1; + int maxCount = 0; + for (const auto& pair : viewCounts) { + if (pair.second > maxCount) { + maxCount = pair.second; + dominantView = pair.first; + } + } + + patch.label = dominantView; + DEBUG_EXTRA("Patch %zu: %zu faces, label: %d", + patchIdx, patch.faces.size(), patch.label); + patchIdx++; + } + + // 步骤4: 创建无效面纹理块 + if (!texturePatches.empty()) { + TexturePatch& invalidPatch = texturePatches.back(); + invalidPatch.label = NO_ID; + invalidPatch.faces.clear(); // 清空数组 + + for (int fid = 0; fid < numFaces; ++fid) { + if (faceViewData[fid].viewID == -1) { + invalidPatch.faces.push_back(static_cast(fid)); // 修正: Insert -> push_back + } + } + + DEBUG_EXTRA("Invalid patch: %zu faces", invalidPatch.faces.size()); + } + + DEBUG_EXTRA("Created %zu texture patches", texturePatches.size()); +} + +// 高质量纹理生成 +void MeshTexture::GenerateHighQualityTexture( + std::vector& textures, + const Mesh::TexCoordArr& faceTexcoords, + const Mesh::TexIndexArr& faceTexindices, + float fSharpnessWeight, + Pixel8U colEmpty) +{ + DEBUG_EXTRA("Generating high-quality texture with view consistency"); + + for (size_t texID = 0; texID < textures.size(); ++texID) { + Image8U3& texture = textures[texID]; + int texWidth = texture.cols; + int texHeight = texture.rows; + + // 为每个像素跟踪采样信息 + cv::Mat1f weightAccum(texHeight, texWidth, 0.0f); + cv::Mat1i viewAccum(texHeight, texWidth, -1); + cv::Mat3f colorAccum(texHeight, texWidth, cv::Vec3f(0, 0, 0)); + + // 第一次遍历:从最优视图采样 + #ifdef TEXOPT_USE_OPENMP + #pragma omp parallel for schedule(dynamic) + #endif + for (int_t idx = 0; idx < (int_t)texturePatches.size() - 1; ++idx) { + const TexturePatch& texturePatch = texturePatches[idx]; + if (texturePatch.label == NO_ID) continue; + + const Image& imageData = images[texturePatch.label]; + const cv::Mat& sourceImage = imageData.image; + + for (FIndex idxFace : texturePatch.faces) { + if (faceTexindices[idxFace] != texID) continue; + + const TexCoord* texcoords = &faceTexcoords[idxFace * 3]; + const Face& face = faces[idxFace]; + + // 计算纹理三角形边界 + AABB2f uvBounds(true); + for (int i = 0; i < 3; ++i) { + uvBounds.InsertFull(texcoords[i]); + } + + int startX = std::max(0, (int)floor(uvBounds.ptMin.x())); + int startY = std::max(0, (int)floor(uvBounds.ptMin.y())); + int endX = std::min(texWidth - 1, (int)ceil(uvBounds.ptMax.x())); + int endY = std::min(texHeight - 1, (int)ceil(uvBounds.ptMax.y())); + + // 遍历纹理三角形内的所有像素 + for (int y = startY; y <= endY; ++y) { + for (int x = startX; x <= endX; ++x) { + Point2f texCoord(x + 0.5f, y + 0.5f); + + // 检查像素是否在三角形内 + Point3f barycentric; + if (!PointInTriangle(texCoord, texcoords[0], + texcoords[1], texcoords[2], barycentric)) { + continue; + } + + // 计算3D坐标 + Vertex worldPoint = + vertices[face[0]] * barycentric.x + + vertices[face[1]] * barycentric.y + + vertices[face[2]] * barycentric.z; + + // 投影到源图像 + Point2f imgPoint = imageData.camera.ProjectPointP(worldPoint); + + if (imgPoint.x < 0 || imgPoint.x >= sourceImage.cols || + imgPoint.y < 0 || imgPoint.y >= sourceImage.rows) { + continue; + } + + // 高质量采样(双线性插值 + 各向异性过滤) + cv::Vec3f color = SampleHighQuality(sourceImage, imgPoint); + + #ifdef TEXOPT_USE_OPENMP + #pragma omp critical + #endif + { + // 累积颜色和权重 + colorAccum(y, x) += cv::Vec3f(color[2], color[1], color[0]); // BGR -> RGB + weightAccum(y, x) += 1.0f; + viewAccum(y, x) = texturePatch.label; // 记录主视图 + } + } + } + } + } + + // 第二次遍历:填充缺失像素,尽量从相同视图采样 + FillMissingPixelsWithViewConsistency(texture, colorAccum, weightAccum, + viewAccum, colEmpty); + } +} + +// 高质量采样函数 +cv::Vec3f MeshTexture::SampleHighQuality(const cv::Mat& image, const Point2f& point) +{ + int x0 = (int)floor(point.x); + int y0 = (int)floor(point.y); + int x1 = std::min(x0 + 1, image.cols - 1); + int y1 = std::min(y0 + 1, image.rows - 1); + + float fx = point.x - x0; + float fy = point.y - y0; + float fx1 = 1.0f - fx; + float fy1 = 1.0f - fy; + + // 双线性插值 + const cv::Vec3b& c00 = image.at(y0, x0); + const cv::Vec3b& c01 = image.at(y0, x1); + const cv::Vec3b& c10 = image.at(y1, x0); + const cv::Vec3b& c11 = image.at(y1, x1); + + cv::Vec3f result = + cv::Vec3f(c00[0], c00[1], c00[2]) * (fx1 * fy1) + + cv::Vec3f(c01[0], c01[1], c01[2]) * (fx * fy1) + + cv::Vec3f(c10[0], c10[1], c10[2]) * (fx1 * fy) + + cv::Vec3f(c11[0], c11[1], c11[2]) * (fx * fy); + + return result; +} + +// 基于视图一致性填充缺失像素 +void MeshTexture::FillMissingPixelsWithViewConsistency( + Image8U3& texture, + cv::Mat3f& colorAccum, + cv::Mat1f& weightAccum, + cv::Mat1i& viewAccum, + Pixel8U colEmpty) +{ + int rows = texture.rows; + int cols = texture.cols; + + // 首先,从有数据的像素直接赋值 + #pragma omp parallel for schedule(static) + for (int y = 0; y < rows; ++y) { + for (int x = 0; x < cols; ++x) { + float weight = weightAccum(y, x); + if (weight > 0) { + cv::Vec3f color = colorAccum(y, x) / weight; + texture.at(y, x) = cv::Vec3b( + (unsigned char)std::min(255.0f, std::max(0.0f, color[2])), // B + (unsigned char)std::min(255.0f, std::max(0.0f, color[1])), // G + (unsigned char)std::min(255.0f, std::max(0.0f, color[0])) // R + ); + } + } + } + + // 填充缺失的像素 + cv::Mat1b mask(rows, cols, (unsigned char)0); + for (int y = 0; y < rows; ++y) { + for (int x = 0; x < cols; ++x) { + if (weightAccum(y, x) == 0) { + mask(y, x) = 255; + } + } + } + + // 使用视图一致性进行填充 + int iterations = 0; + int remainingPixels = cv::countNonZero(mask); + + while (remainingPixels > 0 && iterations < 10) { + int filled = 0; + + #pragma omp parallel for schedule(dynamic) reduction(+:filled) + for (int y = 0; y < rows; ++y) { + for (int x = 0; x < cols; ++x) { + if (mask(y, x) == 0) continue; + + int dominantView = -1; + cv::Vec3f avgColor(0, 0, 0); + int count = 0; + + // 检查3x3邻域 + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + int nx = x + dx; + int ny = y + dy; + + if (nx >= 0 && nx < cols && ny >= 0 && ny < rows) { + if (weightAccum(ny, nx) > 0) { + int neighborView = viewAccum(ny, nx); + if (dominantView == -1 || neighborView == dominantView) { + dominantView = neighborView; + avgColor += cv::Vec3f( + texture.at(ny, nx)[2], + texture.at(ny, nx)[1], + texture.at(ny, nx)[0] + ); + count++; + } + } + } + } + } + + if (count > 0) { + cv::Vec3f finalColor = avgColor / count; + texture.at(y, x) = cv::Vec3b( + (unsigned char)finalColor[2], + (unsigned char)finalColor[1], + (unsigned char)finalColor[0] + ); + weightAccum(y, x) = 1.0f; + viewAccum(y, x) = dominantView; + mask(y, x) = 0; + filled++; + } + } } - // 生成文件名 - std::string filename = outputDir + "/texture_" + std::to_string(i) + ".png"; + remainingPixels -= filled; + iterations++; - // 使用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; + if (filled == 0) { + // 如果无法填充,使用更宽松的条件 + break; } } - return true; + // 填充最后剩余的像素 + if (remainingPixels > 0) { + cv::inpaint(texture, mask, texture, 3, cv::INPAINT_TELEA); + } + + DEBUG_EXTRA("Filled %d missing pixels in %d iterations", + cv::countNonZero(weightAccum == 0) - remainingPixels, iterations); } -bool MeshTexture::TextureWithExistingUV(const IIndexArr& views, int nIgnoreMaskLabel, - float fOutlierThreshold, unsigned nTextureSizeMultiple, - Pixel8U colEmpty, float fSharpnessWeight) +float MeshTexture::ComputeViewQuality(FIndex idxFace, int viewID) { - DEBUG_EXTRA("TextureWithExistingUV with multi-view blending"); - TD_TIMER_START(); + const Face& face = faces[idxFace]; + const Image& imageData = images[viewID]; - // 1. 验证输入 - if (scene.mesh.faceTexcoords.empty()) { - VERBOSE("error: mesh does not contain UV coordinates"); - return false; + // 计算投影面积 + TexCoord texCoords[3]; + for (int i = 0; i < 3; ++i) { + texCoords[i] = imageData.camera.ProjectPointP(vertices[face[i]]); } - 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; + // 计算三角形面积 + float area = std::abs( + (texCoords[1].x - texCoords[0].x) * (texCoords[2].y - texCoords[0].y) - + (texCoords[1].y - texCoords[0].y) * (texCoords[2].x - texCoords[0].x) + ) * 0.5f; + + // 计算视角质量(法线夹角) + const Point3f& faceNormal = scene.mesh.faceNormals[idxFace]; + + // 计算面中心 + const cv::Point3f faceCenter = (vertices[face[0]] + vertices[face[1]] + vertices[face[2]]) / 3.0f; + + // 计算相机中心到面中心的方向 + // 注意:imageData.camera.C 是 CMatrix 类型,需要转换为 cv::Point3f + const cv::Point3f cameraCenter( + static_cast(imageData.camera.C.x), + static_cast(imageData.camera.C.y), + static_cast(imageData.camera.C.z) + ); + + const cv::Point3f viewDir = cameraCenter - faceCenter; + + // 归一化 + float viewLen = std::sqrt(viewDir.x * viewDir.x + + viewDir.y * viewDir.y + + viewDir.z * viewDir.z); + + if (viewLen > 0) { + cv::Point3f normalizedViewDir = viewDir * (1.0f / viewLen); + + // 计算点积 + float cosAngle = std::abs( + faceNormal.x * normalizedViewDir.x + + faceNormal.y * normalizedViewDir.y + + faceNormal.z * normalizedViewDir.z + ); + + // 计算图像分辨率 + float resolution = 1.0f; + + // 综合质量评分 + float quality = area * cosAngle * resolution; + + return quality; } - // 2. 获取所有面的视图数据 - FaceDataViewArr facesDatas; - if (!ListCameraFaces(facesDatas, fOutlierThreshold, nIgnoreMaskLabel, views, false)) { + return 0.0f; +} + +float MeshTexture::ComputeFaceDistance(FIndex fid1, FIndex fid2) +{ + const Point3f center1 = (vertices[faces[fid1][0]] + vertices[faces[fid1][1]] + vertices[faces[fid1][2]]) / 3.0f; + const Point3f center2 = (vertices[faces[fid2][0]] + vertices[faces[fid2][1]] + vertices[faces[fid2][2]]) / 3.0f; + + Point3f diff = center2 - center1; + return std::sqrt(diff.x * diff.x + diff.y * diff.y + diff.z * diff.z); +} +// 判断面是否在视图中可见 +// 判断面是否在视图中可见 +bool MeshTexture::IsFaceVisibleFromView(FIndex idxFace, int viewID) +{ + if (viewID < 0 || viewID >= (int)images.size()) { return false; } - DEBUG_EXTRA("Processing %zu faces with existing UV coordinates", scene.mesh.faces.size()); - - // 3. 为每个面选择最佳视图,但保留多个高质量视图用于融合 - std::vector> faceViews(faces.size()); - std::vector> faceViewWeights(faces.size()); + const Face& face = faces[idxFace]; + const Image& imageData = images[viewID]; - #ifdef _USE_OPENMP - #pragma omp parallel for schedule(dynamic) - for (int_t idxFace = 0; idxFace < (int_t)faces.size(); ++idxFace) { - #else - FOREACH(idxFace, faces) { - #endif - const FaceDataArr& faceDatas = facesDatas[idxFace]; - std::vector> viewScores; - - // 收集所有有效视图 - for (const FaceData& data : faceDatas) { - if (!data.bInvalidFacesRelative) { - // 计算视图质量权重 - float weight = data.quality; - - // 添加视角与法线夹角的权重 - const Image& image = images[data.idxView]; - const Normal& faceNormal = scene.mesh.faceNormals[idxFace]; - - // 计算相机方向 - Point3f cameraDir = -image.camera.C; // 相机位置到原点的方向 - Point3f cameraForward(0, 0, -1); // 相机前方 - Point3f cameraForwardWorld; - cameraForwardWorld.x = (float)(image.camera.R(0,0) * cameraForward.x + - image.camera.R(0,1) * cameraForward.y + - image.camera.R(0,2) * cameraForward.z); - cameraForwardWorld.y = (float)(image.camera.R(1,0) * cameraForward.x + - image.camera.R(1,1) * cameraForward.y + - image.camera.R(1,2) * cameraForward.z); - cameraForwardWorld.z = (float)(image.camera.R(2,0) * cameraForward.x + - image.camera.R(2,1) * cameraForward.y + - image.camera.R(2,2) * cameraForward.z); - cameraForward = cameraForwardWorld; - - float len = sqrt(cameraForward.x * cameraForward.x + - cameraForward.y * cameraForward.y + - cameraForward.z * cameraForward.z); - if (len > 0) { - cameraForward.x /= len; - cameraForward.y /= len; - cameraForward.z /= len; - } - - // 法线与相机方向的点积(越大越好,表示面正对相机) - float dotProduct = faceNormal.dot(cameraForward); - float angleWeight = (dotProduct + 1.0f) * 0.5f; // 归一化到[0,1] - - // 综合权重 - float finalWeight = weight * 0.7f + angleWeight * 0.3f; - - viewScores.emplace_back(finalWeight, data.idxView); - } - } + // 检查面的三个顶点是否都在相机视锥体内 + for (int i = 0; i < 3; ++i) { + const cv::Point3f& vertex = vertices[face[i]]; - // 按权重排序,选择前N个视图 - std::sort(viewScores.begin(), viewScores.end(), - [](const auto& a, const auto& b) { return a.first > b.first; }); + // 将cv::Point3f转换为OpenMVS的Point3类型 + // Camera类使用double精度,但ProjectPointP是模板函数,支持多种类型 + cv::Point3d vertex3d(static_cast(vertex.x), + static_cast(vertex.y), + static_cast(vertex.z)); - const int maxViews = 5; // 最多使用5个视图进行融合 - float totalWeight = 0.0f; + // 使用正确的ProjectPointP函数 + // 从Camera头文件可以看到ProjectPointP返回TPoint2 + cv::Point2d proj = imageData.camera.ProjectPointP(vertex3d); - for (int i = 0; i < std::min((int)viewScores.size(), maxViews); ++i) { - if (viewScores[i].first > 0.1f) { // 质量阈值 - faceViews[idxFace].push_back(viewScores[i].second); - faceViewWeights[idxFace].push_back(viewScores[i].first); - totalWeight += viewScores[i].first; - } + // 检查是否在图像范围内(添加边界容差) + const float border = 5.0f; + if (proj.x < -border || proj.x >= imageData.image.cols + border || + proj.y < -border || proj.y >= imageData.image.rows + border) { + return false; } - // 归一化权重 - if (totalWeight > 0.0f) { - for (float& w : faceViewWeights[idxFace]) { - w /= totalWeight; - } + // 可选:检查深度(点在相机前方) + // 可以使用camera.PointDepth函数 + double depth = imageData.camera.PointDepth(vertex3d); + if (depth <= 0) { + return false; // 点在相机后面 } } - // 4. 生成纹理图集(使用多视图融合) - Mesh::Image8U3Arr textures = GenerateMultiViewTextureAtlas(facesDatas, faceViews, faceViewWeights, - nTextureSizeMultiple, colEmpty, fSharpnessWeight); + // 检查面法线和视图方向的夹角 + const cv::Point3f& faceNormal = scene.mesh.faceNormals[idxFace]; - if (!textures.empty()) { - scene.mesh.texturesDiffuse = std::move(textures); - - // 修复:cList 的 resize 只接受一个参数 - scene.mesh.faceTexindices.resize(scene.mesh.faces.size()); - for (size_t i = 0; i < scene.mesh.faces.size(); ++i) { - scene.mesh.faceTexindices[i] = 0; - } + // 计算面中心 + cv::Point3f faceCenter = (vertices[face[0]] + vertices[face[1]] + vertices[face[2]]) * (1.0f / 3.0f); + cv::Point3d faceCenter3d(faceCenter.x, faceCenter.y, faceCenter.z); + + // 计算相机中心 + const cv::Point3d& cameraCenter = imageData.camera.C; + + // 计算视图方向 + cv::Point3d viewDir = cameraCenter - faceCenter3d; + double viewLen = cv::norm(viewDir); + + if (viewLen > 0) { + // 归一化 + viewDir /= viewLen; - DEBUG_EXTRA("Successfully generated %zu texture atlases", scene.mesh.texturesDiffuse.size()); + // 计算法线点积 + double cosAngle = faceNormal.x * viewDir.x + + faceNormal.y * viewDir.y + + faceNormal.z * viewDir.z; - // 保存纹理 - std::string outputDir = "texture_output"; - if (SaveGeneratedTextures(scene.mesh.texturesDiffuse, outputDir)) { - DEBUG_EXTRA("Textures saved to: %s", outputDir.c_str()); + // 如果夹角太大(接近90度),面可能不可见 + // 使用阈值cos(85°) ≈ 0.087 + if (cosAngle < 0.1) { + return false; } - - return true; } - DEBUG_EXTRA("Texture generation failed"); - return false; + return true; } -Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlas( - const FaceDataViewArr& facesDatas, - const std::vector>& faceViews, - const std::vector>& faceViewWeights, - unsigned nTextureSizeMultiple, - Pixel8U colEmpty, - float fSharpnessWeight) +// 优化纹理接缝 +void MeshTexture::OptimizeTextureSeams(const std::vector& textures, + const std::vector>& seamPoints) { - DEBUG_EXTRA("Generating multi-view texture atlas"); + DEBUG_EXTRA("Optimizing texture seams"); - // 1. 分析UV布局 - AABB2f uvBounds(true); - FOREACH(i, scene.mesh.faceTexcoords) { - const TexCoord& uv = scene.mesh.faceTexcoords[i]; - uvBounds.InsertFull(uv); + if (textures.empty() || seamPoints.empty()) { + return; } - // 2. 计算纹理尺寸 - 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)); - - // 4. 创建权重图、累积颜色图和采样计数图,用于混合 - cv::Mat1f weightAccum(textureSize, textureSize, 0.0f); - cv::Mat3f colorAccum(textureSize, textureSize, cv::Vec3f(0, 0, 0)); // 用浮点数累积颜色 - cv::Mat1i sampleCount(textureSize, textureSize, 0); // 采样计数,用于统计每个像素被采样了多少次 - - DEBUG_EXTRA("Texture atlas size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]", - textureSize, textureSize, - uvBounds.ptMin.x(), uvBounds.ptMin.y(), - uvBounds.ptMax.x(), uvBounds.ptMax.y()); - - // 5. 第一次遍历:从所有视图采样并累积颜色 - #ifdef _USE_OPENMP - #pragma omp parallel for schedule(dynamic) - for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) { - #else - FOREACH(idxFace, scene.mesh.faces) { - #endif - const FIndex faceID = (FIndex)idxFace; + // 对每个纹理,在其边界处进行羽化 + for (size_t i = 0; i < textures.size(); ++i) { + cv::Mat texture = textures[i].clone(); + const std::vector& seams = seamPoints[i]; - // 检查是否有可用视图 - if (faceViews[faceID].empty()) { + if (seams.empty()) { continue; } - const Face& face = scene.mesh.faces[faceID]; - const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; - - // 计算面片在纹理空间中的边界框 - AABB2f faceUVBounds(true); - for (int i = 0; i < 3; ++i) { - faceUVBounds.InsertFull(uvCoords[i]); - } - - 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)); - - // 为当前面片预计算3D点的重心坐标映射 - std::vector texPoints; - std::vector pointColors; // 存储每个采样点的颜色 + // 创建接缝掩模 + cv::Mat seamMask(texture.size(), CV_8UC1, cv::Scalar(0)); - // 均匀采样面片内部 - 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)) { - // 计算3D点 - const Vertex worldPoint = - vertices[face[0]] * barycentric.x + - vertices[face[1]] * barycentric.y + - vertices[face[2]] * barycentric.z; - - // 为每个视图采样颜色 - cv::Vec3f accumColor(0, 0, 0); - float totalWeight = 0.0f; - - for (size_t viewIdx = 0; viewIdx < faceViews[faceID].size(); ++viewIdx) { - const IIndex idxView = faceViews[faceID][viewIdx]; - const float viewWeight = faceViewWeights[faceID][viewIdx]; - - if (idxView >= images.size()) continue; - - const Image& sourceImage = images[idxView]; - - // 投影到图像 - Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage); + for (const Point2f& point : seams) { + int x = static_cast(point.x); + int y = static_cast(point.y); + + if (x >= 0 && x < texture.cols && y >= 0 && y < texture.rows) { + // 在接缝点周围创建羽化区域 + int radius = 3; // 羽化半径 + for (int dy = -radius; dy <= radius; ++dy) { + for (int dx = -radius; dx <= radius; ++dx) { + int nx = x + dx; + int ny = y + dy; - // 验证投影 - if (!ValidateProjection(worldPoint, sourceImage, imgPoint) || - !sourceImage.image.isInside(imgPoint) || - !sourceImage.camera.IsInFront(worldPoint)) { - continue; + if (nx >= 0 && nx < texture.cols && ny >= 0 && ny < texture.rows) { + float distance = std::sqrt(dx*dx + dy*dy); + float weight = std::max(0.0f, 1.0f - distance / radius); + uchar current = seamMask.at(ny, nx); + uchar newValue = cv::saturate_cast(current + weight * 255); + seamMask.at(ny, nx) = newValue; } - - // 采样图像 - Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint); - - // 累积加权颜色 - accumColor[0] += sampledColor.r * viewWeight; - accumColor[1] += sampledColor.g * viewWeight; - accumColor[2] += sampledColor.b * viewWeight; - totalWeight += viewWeight; - } - - if (totalWeight > 0.0f) { - // 平均颜色 - accumColor /= totalWeight; - - // 保存采样点和颜色 - texPoints.emplace_back(texCoord); - pointColors.push_back(accumColor); } } } } - // 使用Delaunay三角剖分在面片内部生成均匀采样 - if (texPoints.size() >= 3) { - // 创建Delaunay三角剖分 - cv::Subdiv2D subdiv(cv::Rect(0, 0, textureSize, textureSize)); - for (const auto& pt : texPoints) { - subdiv.insert(cv::Point2f(pt.x * textureSize, pt.y * textureSize)); - } - - std::vector triangleList; - subdiv.getTriangleList(triangleList); - - // 遍历三角形并填充 - for (const auto& t : triangleList) { - cv::Point2f pt1(t[0], t[1]); - cv::Point2f pt2(t[2], t[3]); - cv::Point2f pt3(t[4], t[5]); - - // 获取三角形内的像素 - std::vector pixels = GetPixelsInTriangle(pt1, pt2, pt3, textureSize); - - for (const auto& pixel : pixels) { - if (pixel.x < 0 || pixel.x >= textureSize || - pixel.y < 0 || pixel.y >= textureSize) { - continue; - } + // 对掩模进行高斯模糊,创建平滑的过渡 + cv::GaussianBlur(seamMask, seamMask, cv::Size(5, 5), 0); + + // 应用羽化 + for (int y = 0; y < texture.rows; ++y) { + for (int x = 0; x < texture.cols; ++x) { + uchar maskValue = seamMask.at(y, x); + if (maskValue > 0) { + float alpha = maskValue / 255.0f; + + // 与相邻纹理混合 + // 这里可以添加与相邻纹理的混合逻辑 + // 暂时简单地进行高斯模糊 + cv::Vec3b& pixel = texture.at(y, x); - const Point2f texCoord((float)pixel.x / textureSize, (float)pixel.y / textureSize); + // 计算3x3邻域的平均值 + cv::Vec3f sum(0, 0, 0); + int count = 0; - // 找到最近采样点(使用最近邻插值) - int nearestIdx = 0; - float minDist = std::numeric_limits::max(); - for (size_t i = 0; i < texPoints.size(); ++i) { - float dx = texPoints[i].x - texCoord.x; - float dy = texPoints[i].y - texCoord.y; - float dist = dx*dx + dy*dy; // 使用平方距离避免sqrt - if (dist < minDist) { - minDist = dist; - nearestIdx = i; + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + int nx = x + dx; + int ny = y + dy; + + if (nx >= 0 && nx < texture.cols && ny >= 0 && ny < texture.rows) { + sum += cv::Vec3f(texture.at(ny, nx)); + ++count; + } } } - if (minDist < 25.0f / (textureSize * textureSize)) { // 距离阈值 - // 获取最近采样点的颜色 - const cv::Vec3f& newColor = pointColors[nearestIdx]; - - // 累加颜色和权重 - #ifdef _USE_OPENMP - #pragma omp atomic - #endif - weightAccum(pixel.y, pixel.x) += 1.0f; - - #ifdef _USE_OPENMP - #pragma omp atomic - #endif - sampleCount(pixel.y, pixel.x) += 1; - - #ifdef _USE_OPENMP - #pragma omp critical - #endif - { - colorAccum(pixel.y, pixel.x) += newColor; - } + if (count > 0) { + cv::Vec3f average = sum / count; + cv::Vec3b blended = cv::Vec3b( + cv::saturate_cast(pixel[0] * (1 - alpha) + average[0] * alpha), + cv::saturate_cast(pixel[1] * (1 - alpha) + average[1] * alpha), + cv::saturate_cast(pixel[2] * (1 - alpha) + average[2] * alpha) + ); + texture.at(y, x) = blended; } } } } + + // 更新纹理 + const_cast(textures[i]) = texture; } - // 6. 应用权重归一化 - DEBUG_EXTRA("Applying weight normalization"); - for (int y = 0; y < textureSize; ++y) { - for (int x = 0; x < textureSize; ++x) { - float weight = weightAccum(y, x); - if (weight > 0.0f) { - // 计算加权平均颜色 - cv::Vec3f avgColor = colorAccum(y, x) / weight; - - // 转换为Pixel8U - Pixel8U finalColor; - finalColor.r = (unsigned char)cv::saturate_cast(avgColor[0]); - finalColor.g = (unsigned char)cv::saturate_cast(avgColor[1]); - finalColor.b = (unsigned char)cv::saturate_cast(avgColor[2]); - - textureAtlas(y, x) = finalColor; - } else { - // 保持背景色 - textureAtlas(y, x) = colEmpty; + DEBUG_EXTRA("Seam optimization completed"); +} +std::pair MeshTexture::FindSharedEdgeIndices(const Face& face0, const Face& face1) +{ + // 找到两个面共享的边 + for (int i = 0; i < 3; ++i) { + int j = (i + 1) % 3; + VIndex v0 = face0[i]; + VIndex v1 = face0[j]; + + for (int k = 0; k < 3; ++k) { + int l = (k + 1) % 3; + VIndex w0 = face1[k]; + VIndex w1 = face1[l]; + + // 检查是否是同一条边(顺序可能相同或相反) + if ((v0 == w0 && v1 == w1) || (v0 == w1 && v1 == w0)) { + return {static_cast(i), static_cast(k)}; } } } - // 7. 第二次遍历:填充缝隙和未采样区域 - DEBUG_EXTRA("Filling gaps in texture atlas"); - // 将 Image8U3 转换为 cv::Mat - cv::Mat textureMat = (cv::Mat&)textureAtlas; - cv::Mat1f weightMat = weightAccum; - cv::Mat1i sampleMat = sampleCount; - FillTextureGapsMultiView(textureMat, weightMat, sampleMat, colEmpty); - - // 8. 应用颜色校正和均匀化 - // ApplyColorCorrection(textureMat, 0.5f); // 中等强度的颜色校正 - - // 9. 应用锐化 - if (fSharpnessWeight > 0) { - // ApplySharpening(textureMat, fSharpnessWeight); - } - - DEBUG_EXTRA("Multi-view texture atlas generation complete"); - return textures; + return {NO_ID, NO_ID}; } -// 获取三角形内的所有像素 -std::vector MeshTexture::GetPixelsInTriangle(const Point2f& pt1_, - const Point2f& pt2_, - const Point2f& pt3_, - int textureSize) +void MeshTexture::LocalSeamLevelingEnhanced() { - std::vector pixels; - - // 转换到cv::Point2f - cv::Point2f pt1(pt1_.x, pt1_.y); - cv::Point2f pt2(pt2_.x, pt2_.y); - cv::Point2f pt3(pt3_.x, pt3_.y); + DEBUG_EXTRA("Starting enhanced local seam leveling (simplified)..."); - // 计算三角形边界框 - int minX = std::max(0, (int)std::min({pt1.x, pt2.x, pt3.x})); - int maxX = std::min(textureSize-1, (int)std::max({pt1.x, pt2.x, pt3.x})); - int minY = std::max(0, (int)std::min({pt1.y, pt2.y, pt3.y})); - int maxY = std::min(textureSize-1, (int)std::max({pt1.y, pt2.y, pt3.y})); + // 简化版本:只做最基本的处理,避免内存问题 - // 遍历边界框内的像素 - for (int y = minY; y <= maxY; ++y) { - for (int x = minX; x <= maxX; ++x) { - if (PointInTriangle2D(cv::Point2f(x, y), pt1, pt2, pt3)) { - pixels.emplace_back(x, y); - } - } + if (seamEdges.empty()) { + DEBUG_EXTRA("No seam edges. Skipping local seam leveling."); + return; } - return pixels; -} - -// 检查点是否在三角形内 -bool MeshTexture::PointInTriangle2(const cv::Point2f& p, - const cv::Point2f& a, - const cv::Point2f& b, - const cv::Point2f& c) -{ - // 使用重心坐标法 - float denominator = ((c.y - a.y) * (b.x - a.x) + (c.x - a.x) * (a.y - b.y)); - if (fabs(denominator) < 1e-6) return false; - - float alpha = ((c.y - a.y) * (p.x - a.x) + (c.x - a.x) * (a.y - p.y)) / denominator; - float beta = ((a.y - b.y) * (p.x - a.x) + (b.x - a.x) * (p.y - a.y)) / -denominator; - float gamma = 1.0f - alpha - beta; + if (faceTexcoords.GetSize() < faces.GetSize() * 3) { + DEBUG_EXTRA("ERROR: faceTexcoords too small. Cannot perform local seam leveling."); + return; + } - return (alpha >= 0 && beta >= 0 && gamma >= 0); -} - - -// 检查点是否在三角形内(2D版本) -bool MeshTexture::PointInTriangle2D(const cv::Point2f& p, - const cv::Point2f& a, - const cv::Point2f& b, - const cv::Point2f& c) -{ - // 使用重心坐标法 - float denominator = ((c.y - a.y) * (b.x - a.x) + (c.x - a.x) * (a.y - b.y)); - if (fabs(denominator) < 1e-6) return false; + // 简单的平滑处理:对每个接缝边的纹理坐标进行平均 + int processedEdges = 0; - float alpha = ((c.y - a.y) * (p.x - a.x) + (c.x - a.x) * (a.y - p.y)) / denominator; - float beta = ((a.y - b.y) * (p.x - a.x) + (b.x - a.x) * (p.y - a.y)) / -denominator; - float gamma = 1.0f - alpha - beta; + for (uint32_t edgeIdx = 0; edgeIdx < seamEdges.GetSize(); ++edgeIdx) { + const PairIdx& edge = seamEdges[edgeIdx]; + + // 边界检查 + if (edge.i >= faces.GetSize() || edge.j >= faces.GetSize()) { + continue; + } + + uint32_t texIdx0 = edge.i * 3; + uint32_t texIdx1 = edge.j * 3; + + if (texIdx0 + 2 >= faceTexcoords.GetSize() || + texIdx1 + 2 >= faceTexcoords.GetSize()) { + continue; + } + + // 获取两个面的组件 + if (edge.i >= components.GetSize() || edge.j >= components.GetSize()) { + continue; + } + + uint32_t comp0 = components[edge.i]; + uint32_t comp1 = components[edge.j]; + + if (comp0 >= mapIdxPatch.GetSize() || comp1 >= mapIdxPatch.GetSize()) { + continue; + } + + uint32_t patch0 = mapIdxPatch[comp0]; + uint32_t patch1 = mapIdxPatch[comp1]; + + if (patch0 == patch1) { + continue; // 不是接缝 + } + + // 获取面的顶点 + const Face& face0 = faces[edge.i]; + const Face& face1 = faces[edge.j]; + + // 找到共享边 + std::pair sharedIndices = FindSharedEdgeIndices(face0, face1); + if (sharedIndices.first == NO_ID || sharedIndices.second == NO_ID) { + continue; + } + + uint32_t idx00 = sharedIndices.first; + uint32_t idx01 = (sharedIndices.first + 1) % 3; + uint32_t idx10 = sharedIndices.second; + uint32_t idx11 = (sharedIndices.second + 1) % 3; + + // 获取纹理坐标 + TexCoord& tc00 = faceTexcoords[texIdx0 + idx00]; + TexCoord& tc01 = faceTexcoords[texIdx0 + idx01]; + TexCoord& tc10 = faceTexcoords[texIdx1 + idx10]; + TexCoord& tc11 = faceTexcoords[texIdx1 + idx11]; + + // 计算平均值 + TexCoord avg0 = (tc00 + tc01) * 0.5f; + TexCoord avg1 = (tc10 + tc11) * 0.5f; + TexCoord avg = (avg0 + avg1) * 0.5f; + + // 应用平滑 + tc00 = tc00 + (avg - avg0) * 0.1f; + tc01 = tc01 + (avg - avg0) * 0.1f; + tc10 = tc10 + (avg - avg1) * 0.1f; + tc11 = tc11 + (avg - avg1) * 0.1f; + + processedEdges++; + + // 每处理1000条边检查一次内存 + if (processedEdges % 1000 == 0) { + DEBUG_EXTRA(" Processed %d seam edges", processedEdges); + } + } - return (alpha >= 0 && beta >= 0 && gamma >= 0); + DEBUG_EXTRA("Enhanced local seam leveling completed. Processed %d seam edges.", processedEdges); } - -#include // 添加这行 -#include // 如果使用原子变量 -#include // 如果使用线程 - -// 在函数开始前添加进度结构 -struct InpaintProgress { - std::atomic finished{false}; - std::atomic progress{0}; - std::exception_ptr exception{nullptr}; -}; - -void MeshTexture::FillTextureGapsMultiView(cv::Mat& textureAtlas, - const cv::Mat1f& weightAccum, - const cv::Mat1i& sampleCount, - Pixel8U colEmpty) +void MeshTexture::GlobalSeamLevelingEnhanced() { - DEBUG_EXTRA("Filling gaps in multi-view texture atlas"); - TD_TIMER_START(); + DEBUG_EXTRA("Starting enhanced global seam leveling with memory safety..."); - // 确保是3通道8位图像 - if (textureAtlas.type() != CV_8UC3) { - DEBUG_EXTRA("Error: textureAtlas is not CV_8UC3 type"); + if (seamVertices.empty()) { + DEBUG_EXTRA("No seam vertices found. Skipping global seam leveling."); return; } - int rows = textureAtlas.rows; - int cols = textureAtlas.cols; - int totalPixels = rows * cols; + if (seamEdges.empty()) { + DEBUG_EXTRA("No seam edges found. Skipping global seam leveling."); + return; + } - DEBUG_EXTRA("Starting gap filling for %dx%d texture", cols, rows); + DEBUG_EXTRA("Processing %u seam vertices and %u seam edges", + seamVertices.GetSize(), seamEdges.GetSize()); - // 1. 创建有效掩码 - cv::Mat1b validMask(rows, cols, (unsigned char)0); - int validPixels = 0; + // 1. 构建接缝图 + std::vector> seamGraph(seamVertices.GetSize()); + std::vector seamVertexPositions(seamVertices.GetSize()); - #pragma omp parallel for schedule(static) reduction(+:validPixels) - for (int y = 0; y < rows; ++y) { - const float* weightRow = weightAccum.ptr(y); - unsigned char* maskRow = validMask.ptr(y); + // 收集接缝顶点位置 + for (uint32_t i = 0; i < seamVertices.GetSize(); ++i) { + const SeamVertex& sv = seamVertices[i]; - for (int x = 0; x < cols; ++x) { - bool valid = weightRow[x] > 0; - maskRow[x] = valid ? 255 : 0; - if (valid) ++validPixels; + // 检查顶点索引 + if (sv.idxVertex >= scene.mesh.vertices.size()) { + DEBUG_EXTRA("WARNING: Seam vertex %u has invalid vertex index %u", i, sv.idxVertex); + continue; } - } - - float validRatio = static_cast(validPixels) / totalPixels; - DEBUG_EXTRA("Valid pixels: %d (%.2f%%), Gap pixels: %d (%.2f%%)", - validPixels, validRatio * 100.0f, - totalPixels - validPixels, (1.0f - validRatio) * 100.0f); - - // 2. 使用距离变换填充小缝隙 - int smallGapsFilled = FillGapsByDistanceTransform(textureAtlas, weightAccum, 10.0f); - DEBUG_EXTRA("Distance transform filled %d small gaps (radius < 10)", smallGapsFilled); - - // 3. 形态学填充扩展有效掩码 - if (validRatio > 0.5f) { // 降低阈值,更多情况下使用形态学填充 - // 更新有效掩码 - #pragma omp parallel for schedule(static) - for (int y = 0; y < rows; ++y) { - const float* weightRow = weightAccum.ptr(y); - unsigned char* maskRow = validMask.ptr(y); - for (int x = 0; x < cols; ++x) { - maskRow[x] = (weightRow[x] > 0) ? 255 : 0; - } + + // 检查patches是否为空 + if (sv.patches.empty()) { + DEBUG_EXTRA("WARNING: Seam vertex %u has no patches", i); + continue; } - cv::Mat1b dilatedMask; - cv::Mat element3 = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3)); - cv::dilate(validMask, dilatedMask, element3); + // 计算接缝顶点的平均位置 + Point3f pos(0, 0, 0); + int count = 0; - int morphologyGapsFilled = 0; - #pragma omp parallel for schedule(static) reduction(+:morphologyGapsFilled) - for (int y = 0; y < rows; ++y) { - const unsigned char* dilatedRow = dilatedMask.ptr(y); - const unsigned char* validRow = validMask.ptr(y); - cv::Vec3b* textureRow = textureAtlas.ptr(y); + // 遍历patches + for (uint32_t patchIndex = 0; patchIndex < sv.patches.GetSize(); ++patchIndex) { + const SeamVertex::Patch& patch = sv.patches[patchIndex]; + uint32_t patchID = patch.idxPatch; - for (int x = 0; x < cols; ++x) { - if (dilatedRow[x] && !validRow[x]) { - cv::Vec3b avgColor(0, 0, 0); - int count = 0; - - // 搜索更大的邻域 - for (int dy = -2; dy <= 2; ++dy) { - int ny = y + dy; - if (ny < 0 || ny >= rows) continue; - - const unsigned char* neighborMaskRow = validMask.ptr(ny); - const cv::Vec3b* neighborTexRow = textureAtlas.ptr(ny); - - for (int dx = -2; dx <= 2; ++dx) { - int nx = x + dx; - if (nx < 0 || nx >= cols) continue; - - if (neighborMaskRow[nx]) { - avgColor += neighborTexRow[nx]; - count++; - } - } - } - - if (count > 0) { - textureRow[x] = cv::Vec3b( - avgColor[0] / count, - avgColor[1] / count, - avgColor[2] / count - ); - morphologyGapsFilled++; + if (patchID >= texturePatches.size()) { + DEBUG_EXTRA("WARNING: Invalid patch ID %u in seam vertex %u", patchID, i); + continue; + } + + const TexturePatch& texturePatch = texturePatches[patchID]; + + // 遍历 patch 中的所有面 + for (uint32_t faceIdx = 0; faceIdx < texturePatch.faces.GetSize(); ++faceIdx) { + FIndex faceID = texturePatch.faces[faceIdx]; + if (faceID >= faces.GetSize()) { + DEBUG_EXTRA("WARNING: Invalid face index %u in patch %u", faceID, patchID); + continue; + } + + const Face& face = faces[faceID]; + for (int j = 0; j < 3; ++j) { + VIndex vIdx = face[j]; + if (vIdx < scene.mesh.vertices.size()) { + pos += scene.mesh.vertices[vIdx]; + count++; } } } } - DEBUG_EXTRA("Morphology filled %d small gaps", morphologyGapsFilled); + if (count > 0) { + seamVertexPositions[i] = pos / (float)count; + } else { + // 如果无法从关联的面计算位置,使用顶点自身位置 + seamVertexPositions[i] = scene.mesh.vertices[sv.idxVertex]; + } } - // 4. 识别剩余的大缝隙区域 - cv::Mat1b remainingGaps(rows, cols, (unsigned char)0); - int remainingGapArea = 0; - - #pragma omp parallel for schedule(static) reduction(+:remainingGapArea) - for (int y = 0; y < rows; ++y) { - const float* weightRow = weightAccum.ptr(y); - unsigned char* gapRow = remainingGaps.ptr(y); + // 2. 构建接缝边连接 + for (uint32_t i = 0; i < seamEdges.GetSize(); ++i) { + const PairIdx& edge = seamEdges[i]; - for (int x = 0; x < cols; ++x) { - if (weightRow[x] <= 0) { - gapRow[x] = 255; - remainingGapArea++; - } + // 验证边有效性 + if (edge.i >= faces.GetSize() || edge.j >= faces.GetSize()) { + DEBUG_EXTRA("WARNING: Invalid seam edge %u: (%u, %u)", i, edge.i, edge.j); + continue; } + + // 找到边对应的接缝顶点 + // 这里简化处理,实际需要根据顶点位置匹配 + // 可以添加边连接 } - if (remainingGapArea == 0) { - DEBUG_EXTRA("No remaining gaps to fill"); - return; - } + // 3. 构建并求解线性系统 + DEBUG_EXTRA("Building linear system for global seam leveling..."); - DEBUG_EXTRA("Remaining gaps: %d pixels (%.2f%% of total)", - remainingGapArea, static_cast(remainingGapArea) / totalPixels * 100.0f); + // 这里简化处理,实际实现需要构建线性系统 + // 通常涉及求解以下形式的线性系统:A * x = b + // 其中x是需要求解的顶点位移 - // 5. 分块inpaint处理大缝隙 - DEBUG_EXTRA("Starting block-based inpainting for large gaps..."); + // 4. 应用全局变换 + int changedCount = 0; - // 创建用于inpaint的纹理副本 - cv::Mat textureForInpaint = textureAtlas.clone(); + // 这里简化处理,实际需要应用求解得到的变换 - // 分块inpaint - bool success = InpaintWithBlocks(textureForInpaint, remainingGaps, textureAtlas, 3, 256); + DEBUG_EXTRA("Enhanced global seam leveling completed. Changed %d face labels.", changedCount); +} + +// 合并重叠的纹理块 +void MeshTexture::MergeOverlappingPatches(Mesh::TexCoordArr& faceTexcoords2) +{ + DEBUG_EXTRA("Merging overlapping texture patches"); - if (!success) { - DEBUG_EXTRA("Block-based inpaint failed, using advanced alternative..."); - AdvancedGapFilling(textureAtlas, weightAccum, remainingGaps); - } else { - DEBUG_EXTRA("Block-based inpainting completed"); + if (texturePatches.IsEmpty()) { + return; } - // 6. 最后检查并填充剩余的任何小缝隙 - int finalGapsFilled = 0; - #pragma omp parallel for schedule(static) reduction(+:finalGapsFilled) - for (int y = 0; y < rows; ++y) { - const float* weightRow = weightAccum.ptr(y); - cv::Vec3b* textureRow = textureAtlas.ptr(y); + // 按视图分组纹理块 + std::unordered_map> patchesByView; + for (int i = 0; i < (int)texturePatches.size(); ++i) { + if (texturePatches[i].label != NO_ID) { + patchesByView[texturePatches[i].label].push_back(i); + } + } + + // 对每个视图的纹理块进行合并 + for (auto& viewPatches : patchesByView) { + std::vector& patchIndices = viewPatches.second; + if (patchIndices.size() <= 1) { + continue; // 不需要合并 + } - for (int x = 0; x < cols; ++x) { - if (weightRow[x] <= 0) { - // 检查这个像素是否仍然是默认颜色 - cv::Vec3b current = textureRow[x]; - if (current[0] == 0 && current[1] == 0 && current[2] == 0) { - // 使用更大的邻域搜索 - cv::Vec3b avgColor(0, 0, 0); - int count = 0; + // 计算纹理块之间的重叠程度 + std::vector>> overlaps; + + for (size_t i = 0; i < patchIndices.size(); ++i) { + TexturePatch& patch1 = texturePatches[patchIndices[i]]; + if (patch1.rect.empty()) continue; + + for (size_t j = i + 1; j < patchIndices.size(); ++j) { + TexturePatch& patch2 = texturePatches[patchIndices[j]]; + if (patch2.rect.empty()) continue; + + // 计算两个纹理块的重叠区域 + cv::Rect intersection = patch1.rect & patch2.rect; + if (!intersection.empty()) { + float overlapArea = (float)intersection.area(); + float minArea = std::min((float)patch1.rect.area(), (float)patch2.rect.area()); + float overlapRatio = overlapArea / minArea; - for (int radius = 1; radius <= 5; ++radius) { - for (int dy = -radius; dy <= radius; ++dy) { - int ny = y + dy; - if (ny < 0 || ny >= rows) continue; - - const float* neighborWeightRow = weightAccum.ptr(ny); - const cv::Vec3b* neighborTexRow = textureAtlas.ptr(ny); - - for (int dx = -radius; dx <= radius; ++dx) { - int nx = x + dx; - if (nx < 0 || nx >= cols) continue; - if (dx == 0 && dy == 0) continue; - - if (neighborWeightRow[nx] > 0) { - avgColor += neighborTexRow[nx]; - count++; - } - } - } - - if (count > 0) { - textureRow[x] = cv::Vec3b( - avgColor[0] / count, - avgColor[1] / count, - avgColor[2] / count - ); - finalGapsFilled++; - break; - } + if (overlapRatio > 0.1f) { // 重叠超过10% + overlaps.emplace_back(overlapRatio, + std::make_pair(patchIndices[i], patchIndices[j])); } } } } + + // 按重叠程度排序 + std::sort(overlaps.begin(), overlaps.end(), + [](const auto& a, const auto& b) { return a.first > b.first; }); + + // 合并高度重叠的纹理块 + std::vector merged(texturePatches.size(), false); + + for (const auto& overlap : overlaps) { + int idx1 = overlap.second.first; + int idx2 = overlap.second.second; + + if (merged[idx1] || merged[idx2]) { + continue; // 已经合并过了 + } + + TexturePatch& patch1 = texturePatches[idx1]; + TexturePatch& patch2 = texturePatches[idx2]; + + // 合并两个纹理块 + // 计算合并后的边界框 + cv::Rect mergedRect = patch1.rect | patch2.rect; + + // 合并面列表 - 使用 std::vector 代替 FIndexArr + std::vector mergedFaces; + // 从 patch1.faces 复制 + for (size_t k = 0; k < patch1.faces.GetSize(); ++k) { + mergedFaces.push_back(patch1.faces[k]); + } + // 从 patch2.faces 添加 + for (size_t k = 0; k < patch2.faces.GetSize(); ++k) { + mergedFaces.push_back(patch2.faces[k]); + } + + // 创建新的纹理块 + patch1.rect = mergedRect; + // 将 std::vector 转回 patch1.faces + patch1.faces.Release(); + for (FIndex fid : mergedFaces) { + patch1.faces.Insert(fid); + } + + // 标记第二个纹理块为已合并 + patch2.label = NO_ID; + patch2.faces.Release(); + merged[idx2] = true; + + DEBUG_EXTRA("Merged patches %d and %d (overlap: %.2f%%)", + idx1, idx2, overlap.first * 100.0f); + } } - if (finalGapsFilled > 0) { - DEBUG_EXTRA("Final pass filled %d remaining gaps", finalGapsFilled); + // 移除空的纹理块 + std::vector newIndices(texturePatches.size(), NO_ID); + int newIdx = 0; + for (int i = 0; i < (int)texturePatches.size(); ++i) { + if (texturePatches[i].label != NO_ID && texturePatches[i].faces.GetSize() > 0) { + texturePatches[newIdx] = texturePatches[i]; + newIndices[i] = newIdx; + ++newIdx; + } else { + newIndices[i] = NO_ID; + } } + texturePatches.Resize(newIdx); - DEBUG_EXTRA("Total gap filling time: %s", TD_TIMER_GET_FMT().c_str()); + DEBUG_EXTRA("Merged overlapping patches, new count: %d", texturePatches.size()); } -// 辅助函数:使用距离变换加速最近邻填充 -void MeshTexture::FillSmallGapsWithDistanceTransform(cv::Mat& textureAtlas, - const cv::Mat1f& weightAccum) +// 打包纹理块 +void MeshTexture::PackTexturePatches(const Mesh::TexCoordArr& faceTexcoords2, + const Mesh::TexIndexArr& faceTexindices2, + std::vector& generatedTextures, + unsigned nTextureSizeMultiple, + unsigned nRectPackingHeuristic, + int maxTextureSize) { - int rows = textureAtlas.rows; - int cols = textureAtlas.cols; + DEBUG_EXTRA("Packing texture patches with enhanced algorithm"); - // 创建有效像素掩码 - cv::Mat1b validMask(rows, cols, (unsigned char)0); - #pragma omp parallel for schedule(static) - for (int y = 0; y < rows; ++y) { - const float* weightRow = weightAccum.ptr(y); - unsigned char* maskRow = validMask.ptr(y); - for (int x = 0; x < cols; ++x) { - maskRow[x] = (weightRow[x] > 0) ? 255 : 0; - } + if (texturePatches.IsEmpty()) { + DEBUG_EXTRA("No texture patches to pack"); + return; } - // 计算距离变换 - cv::Mat distance; - cv::distanceTransform(~validMask, distance, cv::DIST_L2, cv::DIST_MASK_PRECISE, CV_32F); - - // 找到最近的有效像素 - cv::Mat nearestLabels; - cv::distanceTransform(~validMask, nearestLabels, cv::DIST_L2, cv::DIST_MASK_PRECISE, CV_32S); - - // 填充缝隙 - #pragma omp parallel for schedule(static) - for (int y = 0; y < rows; ++y) { - const float* distRow = distance.ptr(y); - const int* labelRow = nearestLabels.ptr(y); - cv::Vec3b* textureRow = textureAtlas.ptr(y); - const float* weightRow = weightAccum.ptr(y); - - for (int x = 0; x < cols; ++x) { - if (weightRow[x] <= 0 && distRow[x] > 0 && distRow[x] < 10) { // 只填充距离小于10的缝隙 - int nearestIdx = labelRow[x]; - int nearestY = nearestIdx / cols; - int nearestX = nearestIdx % cols; - - if (nearestY >= 0 && nearestY < rows && nearestX >= 0 && nearestX < cols) { - textureRow[x] = textureAtlas.at(nearestY, nearestX); - } - } + // 收集所有需要打包的纹理块 + std::vector validPatches; + for (int i = 0; i < (int)texturePatches.size(); ++i) { + if (texturePatches[i].label != NO_ID && !texturePatches[i].rect.empty()) { + validPatches.push_back(&texturePatches[i]); } } -} - -// 辅助函数:使用最近有效像素填充小缝隙 -void MeshTexture::FillSmallGapsWithNearest(cv::Mat& textureAtlas, - const cv::Mat1f& weightAccum, - const cv::Rect& bbox) -{ - int rows = textureAtlas.rows; - int cols = textureAtlas.cols; - // 限制边界 - int x1 = std::max(0, bbox.x); - int y1 = std::max(0, bbox.y); - int x2 = std::min(cols, bbox.x + bbox.width); - int y2 = std::min(rows, bbox.y + bbox.height); + if (validPatches.empty()) { + DEBUG_EXTRA("No valid texture patches to pack"); + return; + } - // 查找区域内所有有效像素的位置 - std::vector validPoints; - for (int y = y1; y < y2; ++y) { - const float* weightRow = weightAccum.ptr(y); - for (int x = x1; x < x2; ++x) { - if (weightRow[x] > 0) { - validPoints.emplace_back(x, y); - } - } + // 按视图分组纹理块 + std::unordered_map> patchesByView; + for (TexturePatch* patch : validPatches) { + patchesByView[patch->label].push_back(patch); } - if (validPoints.empty()) return; + // 为每个视图创建一个纹理图集 + generatedTextures.clear(); + std::vector> textureAtlases; - // 对每个缝隙像素,使用最近的有效像素颜色 - for (int y = y1; y < y2; ++y) { - const float* weightRow = weightAccum.ptr(y); - cv::Vec3b* textureRow = textureAtlas.ptr(y); + for (auto& viewPatches : patchesByView) { + std::vector& patches = viewPatches.second; - for (int x = x1; x < x2; ++x) { - if (weightRow[x] <= 0) { // 是缝隙像素 - // 找到最近的有效像素 - int nearestIdx = 0; - float minDist = std::numeric_limits::max(); - - for (size_t i = 0; i < validPoints.size(); ++i) { - int dx = validPoints[i].x - x; - int dy = validPoints[i].y - y; - float dist = dx*dx + dy*dy; // 平方距离 - - if (dist < minDist) { - minDist = dist; - nearestIdx = i; - } - } - - // 使用最近有效像素的颜色 - const cv::Point& nearest = validPoints[nearestIdx]; - textureRow[x] = textureAtlas.at(nearest.y, nearest.x); - } + // 对纹理块按大小排序(从大到小) + std::sort(patches.begin(), patches.end(), + [](const TexturePatch* a, const TexturePatch* b) { + return a->rect.area() > b->rect.area(); + }); + + // 使用矩形打包算法 + std::vector rectangles; + std::vector rectPatches; + + for (TexturePatch* patch : patches) { + rectangles.push_back(patch->rect); + rectPatches.push_back(patch); } - } -} - -// 增强的距离变换填充函数 -int MeshTexture::FillGapsByDistanceTransform(cv::Mat& textureAtlas, - const cv::Mat1f& weightAccum, - float maxDistance) -{ - int rows = textureAtlas.rows; - int cols = textureAtlas.cols; - int filledPixels = 0; - - // 创建有效像素掩码 - cv::Mat1b validMask(rows, cols, (unsigned char)0); - #pragma omp parallel for schedule(static) - for (int y = 0; y < rows; ++y) { - const float* weightRow = weightAccum.ptr(y); - unsigned char* maskRow = validMask.ptr(y); - for (int x = 0; x < cols; ++x) { - maskRow[x] = (weightRow[x] > 0) ? 255 : 0; + + // 计算所需纹理大小 + int totalArea = 0; + for (const cv::Rect& rect : rectangles) { + totalArea += rect.area(); } - } - - // 计算距离变换 - cv::Mat distance; - cv::Mat labels; - cv::distanceTransform(~validMask, distance, cv::DIST_L2, cv::DIST_MASK_PRECISE, CV_32F); - - // 使用更精确的方法找到最近邻 - #pragma omp parallel for schedule(static) reduction(+:filledPixels) - for (int y = 0; y < rows; ++y) { - const float* distRow = distance.ptr(y); - const float* weightRow = weightAccum.ptr(y); - cv::Vec3b* textureRow = textureAtlas.ptr(y); - for (int x = 0; x < cols; ++x) { - if (weightRow[x] <= 0 && (maxDistance <= 0 || distRow[x] <= maxDistance)) { - // 查找最近的有效像素 - float minDist = FLT_MAX; - int bestY = -1, bestX = -1; - - // 搜索范围基于距离 - int searchRadius = std::min(std::max(1, static_cast(distRow[x])), 20); - int startY = std::max(0, y - searchRadius); - int endY = std::min(rows - 1, y + searchRadius); - int startX = std::max(0, x - searchRadius); - int endX = std::min(cols - 1, x + searchRadius); - - for (int ny = startY; ny <= endY; ++ny) { - const float* neighborWeightRow = weightAccum.ptr(ny); - const cv::Vec3b* neighborTexRow = textureAtlas.ptr(ny); - - for (int nx = startX; nx <= endX; ++nx) { - if (neighborWeightRow[nx] > 0) { - float dx = static_cast(nx - x); - float dy = static_cast(ny - y); - float dist = dx * dx + dy * dy; - - if (dist < minDist) { - minDist = dist; - bestY = ny; - bestX = nx; - } - } - } - } - - if (bestY != -1 && bestX != -1) { - textureRow[x] = textureAtlas.at(bestY, bestX); - filledPixels++; - } - } + // 估计纹理大小(考虑填充) + int estimatedSize = (int)std::ceil(std::sqrt(totalArea * 1.5f)); + + // 对齐到倍数 + if (nTextureSizeMultiple > 1) { + estimatedSize = ((estimatedSize + nTextureSizeMultiple - 1) / nTextureSizeMultiple) * nTextureSizeMultiple; } - } - - return filledPixels; -} - -// 高级缝隙填充方法 -void MeshTexture::AdvancedGapFilling(cv::Mat& textureAtlas, - const cv::Mat1f& weightAccum, - const cv::Mat1b& gapMask) -{ - int rows = textureAtlas.rows; - int cols = textureAtlas.cols; - int filledPixels = 0; - - // 多次迭代,逐渐扩大搜索范围 - for (int iteration = 0; iteration < 3; ++iteration) { - int iterationFilled = 0; - int searchRadius = 1 + iteration * 2; // 1, 3, 5 - #pragma omp parallel for schedule(static) reduction(+:iterationFilled) - for (int y = 0; y < rows; ++y) { - const float* weightRow = weightAccum.ptr(y); - const unsigned char* gapRow = gapMask.ptr(y); - cv::Vec3b* textureRow = textureAtlas.ptr(y); + // 限制最大大小 + if (maxTextureSize > 0 && estimatedSize > maxTextureSize) { + estimatedSize = maxTextureSize; + } + + // 创建纹理图集 + cv::Mat texture(estimatedSize, estimatedSize, CV_8UC3, cv::Scalar(0, 0, 0)); + + // 简单的打包策略:从左到右,从上到下 + int x = 0, y = 0; + int maxRowHeight = 0; + + for (size_t i = 0; i < rectangles.size(); ++i) { + cv::Rect& rect = rectangles[i]; + TexturePatch* patch = rectPatches[i]; - for (int x = 0; x < cols; ++x) { - if (gapRow[x] == 255 && weightRow[x] <= 0) { - cv::Vec3b avgColor(0, 0, 0); - int count = 0; - - for (int dy = -searchRadius; dy <= searchRadius; ++dy) { - int ny = y + dy; - if (ny < 0 || ny >= rows) continue; - - const float* neighborWeightRow = weightAccum.ptr(ny); - const cv::Vec3b* neighborTexRow = textureAtlas.ptr(ny); - - for (int dx = -searchRadius; dx <= searchRadius; ++dx) { - int nx = x + dx; - if (nx < 0 || nx >= cols) continue; - if (dx == 0 && dy == 0) continue; - - if (neighborWeightRow[nx] > 0) { - avgColor += neighborTexRow[nx]; - count++; - } - } - } - - if (count > 0) { - textureRow[x] = cv::Vec3b( - avgColor[0] / count, - avgColor[1] / count, - avgColor[2] / count - ); - iterationFilled++; - } - } + // 检查是否适合当前行 + if (x + rect.width > estimatedSize) { + // 换行 + x = 0; + y += maxRowHeight; + maxRowHeight = 0; + } + + // 检查是否适合纹理 + if (y + rect.height > estimatedSize) { + // 纹理太小,需要调整 + DEBUG_EXTRA("Texture atlas too small, increasing size"); + // 这里可以增加纹理大小或使用更复杂的打包算法 + break; + } + + // 放置纹理块 + patch->rect.x = x; + patch->rect.y = y; + x += rect.width; + + if (rect.height > maxRowHeight) { + maxRowHeight = rect.height; } } - filledPixels += iterationFilled; - DEBUG_EXTRA("Advanced filling iteration %d: filled %d pixels (radius=%d)", - iteration + 1, iterationFilled, searchRadius); - - if (iterationFilled == 0) break; + generatedTextures.push_back(texture); + textureAtlases.push_back(patches); } - DEBUG_EXTRA("Advanced gap filling completed, total filled: %d pixels", filledPixels); + DEBUG_EXTRA("Packed %zu texture atlases", generatedTextures.size()); } -void MeshTexture::FastAlternativeGapFilling(cv::Mat& textureAtlas, - const cv::Mat1f& weightAccum, - const cv::Mat1b& validMask) +// 自适应锐化 +void MeshTexture::ApplyAdaptiveSharpening(std::vector& textures, float fSharpnessWeight) { - DEBUG_EXTRA("Using fast alternative gap filling method"); + if (fSharpnessWeight <= 0.0f || textures.empty()) { + return; + } - int rows = textureAtlas.rows; - int cols = textureAtlas.cols; + DEBUG_EXTRA("Applying adaptive sharpening to textures (weight: %.2f)", fSharpnessWeight); - #pragma omp parallel for schedule(static) - for (int y = 0; y < rows; ++y) { - const float* weightRow = weightAccum.ptr(y); - cv::Vec3b* textureRow = textureAtlas.ptr(y); + for (size_t i = 0; i < textures.size(); ++i) { + cv::Mat& texture = textures[i]; + if (texture.empty()) continue; - for (int x = 0; x < cols; ++x) { - if (weightRow[x] <= 0) { // 缝隙像素 - cv::Vec3b avgColor(0, 0, 0); - int count = 0; + // 计算图像的梯度幅度,用于自适应锐化 + cv::Mat gray, gradient; + cv::cvtColor(texture, gray, cv::COLOR_BGR2GRAY); + + // 计算Sobel梯度 + cv::Mat gradX, gradY; + cv::Sobel(gray, gradX, CV_32F, 1, 0, 3); + cv::Sobel(gray, gradY, CV_32F, 0, 1, 3); + cv::magnitude(gradX, gradY, gradient); + + // 归一化梯度 + double minVal, maxVal; + cv::minMaxLoc(gradient, &minVal, &maxVal); + if (maxVal > minVal) { + gradient = (gradient - minVal) / (maxVal - minVal); + } + + // 对图像进行锐化 + cv::Mat sharpened; + + // 使用非锐化掩模 + cv::Mat blurred; + cv::GaussianBlur(texture, blurred, cv::Size(0, 0), 3.0); + + // 自适应锐化:在边缘区域使用更强的锐化 + for (int y = 0; y < texture.rows; ++y) { + for (int x = 0; x < texture.cols; ++x) { + float edgeStrength = gradient.at(y, x); + float weight = fSharpnessWeight * (0.5f + edgeStrength * 0.5f); - for (int dy = -1; dy <= 1 && count < 2; ++dy) { - int ny = y + dy; - if (ny < 0 || ny >= rows) continue; - - const float* neighborWeightRow = weightAccum.ptr(ny); - const cv::Vec3b* neighborTextureRow = textureAtlas.ptr(ny); - - for (int dx = -1; dx <= 1 && count < 2; ++dx) { - int nx = x + dx; - if (nx < 0 || nx >= cols) continue; - if (dx == 0 && dy == 0) continue; - - if (neighborWeightRow[nx] > 0) { - avgColor += neighborTextureRow[nx]; - count++; - } - } - } + cv::Vec3b& srcPixel = texture.at(y, x); + cv::Vec3b& blurPixel = blurred.at(y, x); + cv::Vec3b& dstPixel = sharpened.at(y, x); - if (count > 0) { - textureRow[x] = cv::Vec3b( - avgColor[0] / count, - avgColor[1] / count, - avgColor[2] / count - ); - } else { - for (int radius = 2; radius <= 5 && count == 0; ++radius) { - for (int dy = -radius; dy <= radius && count == 0; ++dy) { - int ny = y + dy; - if (ny < 0 || ny >= rows) continue; - - const float* neighborWeightRow = weightAccum.ptr(ny); - const cv::Vec3b* neighborTextureRow = textureAtlas.ptr(ny); - - for (int dx = -radius; dx <= radius && count == 0; ++dx) { - int nx = x + dx; - if (nx < 0 || nx >= cols) continue; - - if (neighborWeightRow[nx] > 0) { - avgColor += neighborTextureRow[nx]; - count++; - } - } - } - } - - if (count > 0) { - textureRow[x] = cv::Vec3b( - avgColor[0] / count, - avgColor[1] / count, - avgColor[2] / count - ); - } + for (int c = 0; c < 3; ++c) { + int value = static_cast(srcPixel[c] + weight * (srcPixel[c] - blurPixel[c])); + dstPixel[c] = cv::saturate_cast(value); } } } + + texture = sharpened; } - DEBUG_EXTRA("Fast alternative gap filling completed"); + DEBUG_EXTRA("Adaptive sharpening completed"); } -bool MeshTexture::InpaintWithBlocks(cv::Mat& image, const cv::Mat1b& mask, - cv::Mat& result, int radius, int blockSize) + +// 填充纹理空洞 +void MeshTexture::FillTextureHoles(std::vector& textures, Pixel8U colEmpty) { - int rows = image.rows; - int cols = image.cols; - - result.create(rows, cols, image.type()); + if (textures.empty()) { + return; + } - int numBlocksY = (rows + blockSize - 1) / blockSize; - int numBlocksX = (cols + blockSize - 1) / blockSize; - int totalBlocks = numBlocksY * numBlocksX; - int processedBlocks = 0; + DEBUG_EXTRA("Filling holes in textures"); - DEBUG_EXTRA("Processing inpaint in %d x %d blocks", numBlocksY, numBlocksX); + cv::Scalar emptyColor(colEmpty.r, colEmpty.g, colEmpty.b); - #pragma omp parallel for schedule(dynamic) collapse(2) - for (int by = 0; by < numBlocksY; ++by) { - for (int bx = 0; bx < numBlocksX; ++bx) { - int y1 = by * blockSize; - int x1 = bx * blockSize; - int y2 = std::min((by + 1) * blockSize, rows); - int x2 = std::min((bx + 1) * blockSize, cols); - - cv::Rect blockRect(x1, y1, x2 - x1, y2 - y1); - - cv::Mat blockImage = image(blockRect); - cv::Mat blockMask = mask(blockRect); - cv::Mat blockResult; - - if (cv::countNonZero(blockMask) > 0) { - cv::inpaint(blockImage, blockMask, blockResult, radius, cv::INPAINT_TELEA); - blockResult.copyTo(result(blockRect)); - } else { - blockImage.copyTo(result(blockRect)); + for (size_t i = 0; i < textures.size(); ++i) { + cv::Mat& texture = textures[i]; + if (texture.empty()) continue; + + // 创建掩模,标识空洞区域 + cv::Mat mask(texture.size(), CV_8UC1, cv::Scalar(0)); + + for (int y = 0; y < texture.rows; ++y) { + for (int x = 0; x < texture.cols; ++x) { + cv::Vec3b pixel = texture.at(y, x); + if (pixel[0] == emptyColor[0] && + pixel[1] == emptyColor[1] && + pixel[2] == emptyColor[2]) { + mask.at(y, x) = 255; + } } - - #pragma omp atomic - processedBlocks++; - - if (processedBlocks % 10 == 0) { - #pragma omp critical - { - int progress = static_cast(100.0f * processedBlocks / totalBlocks); - DEBUG_EXTRA("Inpaint progress: %d%% (%d/%d blocks)", - progress, processedBlocks, totalBlocks); + } + + int holeCount = cv::countNonZero(mask); + if (holeCount == 0) { + continue; // 没有空洞 + } + + // DEBUG_VERBOSE("Texture %zu: filling %d holes", i, holeCount); + + // 使用修复算法填充空洞 + cv::Mat inpainted; + cv::inpaint(texture, mask, inpainted, 3, cv::INPAINT_TELEA); + + // 将修复的区域复制回原图像 + for (int y = 0; y < texture.rows; ++y) { + for (int x = 0; x < texture.cols; ++x) { + if (mask.at(y, x) == 255) { + texture.at(y, x) = inpainted.at(y, x); } } } } - return true; + DEBUG_EXTRA("Hole filling completed"); } -void MeshTexture::ApplyColorCorrection(cv::Mat& image, float strength) +bool MeshTexture::TextureWithExistingUV( + const IIndexArr& views, + int nIgnoreMaskLabel, + float fOutlierThreshold, + unsigned nTextureSizeMultiple, + Pixel8U colEmpty, + float fSharpnessWeight, + const Mesh::Image8U3Arr& existingTextures, + const Mesh::TexCoordArr& existingTexcoords, + const Mesh::TexIndexArr& existingTexindices) { - if (strength <= 0) return; - - DEBUG_EXTRA("Applying color correction (strength: %.2f)", strength); + DEBUG_EXTRA("TextureWithExistingUV - 使用3D几何坐标作为桥梁"); TD_TIMER_START(); - if (strength < 0.3f) { - // 轻微的颜色调整 - ApplySimpleColorCorrection(image, strength); - return; + // 1. 验证输入 + if (scene.mesh.faceTexcoords.empty()) { + VERBOSE("error: mesh does not contain UV coordinates"); + return false; } - // 1. 转换为LAB颜色空间进行更自然的颜色调整 - cv::Mat labImage; - cv::cvtColor(image, labImage, cv::COLOR_BGR2Lab); - - // 分离通道 - std::vector labChannels; - cv::split(labImage, labChannels); - - // 2. 对亮度通道进行对比度拉伸 - cv::Mat& luminance = labChannels[0]; - - // 直方图均衡化 - cv::equalizeHist(luminance, luminance); + if (existingTextures.empty()) { + VERBOSE("error: no existing texture data provided"); + return false; + } - // 3. 对颜色通道进行平滑处理 - cv::Mat aChannel, bChannel; - cv::GaussianBlur(labChannels[1], aChannel, cv::Size(5, 5), 1.0); - cv::GaussianBlur(labChannels[2], bChannel, cv::Size(5, 5), 1.0); + DEBUG_EXTRA("Processing %zu faces with existing texture data", scene.mesh.faces.size()); - // 混合原始和平滑后的颜色通道 - cv::addWeighted(labChannels[1], 1.0f - strength, aChannel, strength, 0, labChannels[1]); - cv::addWeighted(labChannels[2], 1.0f - strength, bChannel, strength, 0, labChannels[2]); + // 2. 为每个面选择最佳视图(从原始图像,而不是已有纹理) + FaceDataViewArr facesDatas; + if (!ListCameraFaces(facesDatas, fOutlierThreshold, nIgnoreMaskLabel, views, false)) { + return false; + } - // 4. 合并通道 - cv::merge(labChannels, labImage); + // 3. 为每个面分配最佳视图 + LabelArr faceLabels(scene.mesh.faces.size()); + FOREACH(idxFace, scene.mesh.faces) { + const FaceDataArr& faceDatas = facesDatas[idxFace]; + if (faceDatas.empty()) { + faceLabels[idxFace] = 0; // 无视图可用 + continue; + } + + // 选择质量最高的视图 + float bestQuality = -1; + IIndex bestView = NO_ID; + for (const FaceData& data : faceDatas) { + if (data.quality > bestQuality && !data.bInvalidFacesRelative) { + bestQuality = data.quality; + bestView = data.idxView; + } + } + + faceLabels[idxFace] = (bestView != NO_ID) ? (bestView + 1) : 0; + } - // 5. 转换回BGR - cv::cvtColor(labImage, image, cv::COLOR_Lab2BGR); + // 4. 生成纹理图集 + Mesh::Image8U3Arr generatedTextures = GenerateTextureAtlasWith3DBridge( + faceLabels, views, existingTextures, existingTexcoords, existingTexindices, + nTextureSizeMultiple, colEmpty, fSharpnessWeight + ); - // 6. 应用自动颜色平衡 - ApplyAutoWhiteBalance(image, strength * 0.5f); + if (!generatedTextures.empty()) { + // 检查颜色通道 + CheckColorChannels(generatedTextures[0], "生成的纹理"); + + // 检查源纹理颜色通道 + if (!existingTextures.empty()) { + CheckColorChannels(existingTextures[0], "源纹理"); + } + + // 保存纹理 + scene.mesh.texturesDiffuse = std::move(generatedTextures); + + // 设置面的纹理索引 + if (scene.mesh.texturesDiffuse.size() > 1) { + scene.mesh.faceTexindices.resize(scene.mesh.faces.size()); + for (size_t i = 0; i < scene.mesh.faces.size(); ++i) { + scene.mesh.faceTexindices[i] = 0; + } + } else { + scene.mesh.faceTexindices.Release(); + } + + DEBUG_EXTRA("Generated %zu textures from existing data", + scene.mesh.texturesDiffuse.size()); + return true; + } - DEBUG_EXTRA("Color correction completed in %s", TD_TIMER_GET_FMT().c_str()); + DEBUG_EXTRA("Texture generation failed"); + return false; } -void MeshTexture::ApplySimpleColorCorrection(cv::Mat& image, float strength) +Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge( + const LabelArr& faceLabels, + const IIndexArr& views, + const Mesh::Image8U3Arr& sourceTextures, + const Mesh::TexCoordArr& sourceTexcoords, + const Mesh::TexIndexArr& sourceTexindices, + unsigned nTextureSizeMultiple, + Pixel8U colEmpty, + float fSharpnessWeight) { - if (strength <= 0) return; - - // 应用伽马校正 - cv::Mat temp; - image.convertTo(temp, CV_32FC3, 1.0/255.0); + DEBUG_EXTRA("GenerateTextureAtlasWith3DBridge - 使用3D几何坐标作为桥梁"); - // 调整对比度 - cv::Mat adjusted = temp.clone(); - float alpha = 1.0f + strength; // 对比度 - float beta = -0.1f * strength; // 亮度调整 + // 1. 分析外部UV布局 + AABB2f uvBounds(true); + FOREACH(i, scene.mesh.faceTexcoords) { + const TexCoord& uv = scene.mesh.faceTexcoords[i]; + uvBounds.InsertFull(uv); + } - for (int y = 0; y < adjusted.rows; ++y) { - cv::Vec3f* row = adjusted.ptr(y); - for (int x = 0; x < adjusted.cols; ++x) { - row[x][0] = cv::saturate_cast(alpha * row[x][0] + beta); - row[x][1] = cv::saturate_cast(alpha * row[x][1] + beta); - row[x][2] = cv::saturate_cast(alpha * row[x][2] + beta); + // 确保UV在[0,1]范围内 + if (uvBounds.ptMin.x() < 0 || uvBounds.ptMin.y() < 0 || + uvBounds.ptMax.x() > 1 || uvBounds.ptMax.y() > 1) { + DEBUG_EXTRA("UV超出[0,1]范围,进行归一化"); + uvBounds = AABB2f(true); + for (size_t i = 0; i < scene.mesh.faceTexcoords.size(); i += 3) { + for (int v = 0; v < 3; ++v) { + const TexCoord& uv = scene.mesh.faceTexcoords[i + v]; + uvBounds.InsertFull(uv); + } } } - // 混合回原图 - cv::addWeighted(temp, 1.0f - strength, adjusted, strength, 0.0, temp); + // 计算纹理尺寸 + const float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x(); + const float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y(); + const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); - // 转换回8位 - temp.convertTo(image, CV_8UC3, 255.0); -} - -void MeshTexture::ApplySharpening(cv::Mat& image, float strength) -{ - if (strength <= 0) return; +// 创建目标纹理 + Mesh::Image8U3Arr textures; + Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); + // 注意:cv::Scalar 使用 BGR 顺序 + textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); - DEBUG_EXTRA("Applying sharpening (strength: %.2f)", strength); - TD_TIMER_START(); + DEBUG_EXTRA("生成纹理图集: 尺寸=%dx%d, UV范围=[%.3f,%.3f]-[%.3f,%.3f]", + textureSize, textureSize, + uvBounds.ptMin.x(), uvBounds.ptMin.y(), + uvBounds.ptMax.x(), uvBounds.ptMax.y()); + + // 添加调试信息 + DEBUG_EXTRA("colEmpty: R=%d, G=%d, B=%d", colEmpty.r, colEmpty.g, colEmpty.b); + DEBUG_EXTRA("OpenCV图像通道顺序: BGR"); + + // 检查颜色通道顺序 + if (!sourceTextures.empty()) { + const Image8U3& firstTex = sourceTextures[0]; + cv::Vec3b firstPixel = firstTex.at(0, 0); + DEBUG_EXTRA("源纹理第一个像素: B=%d, G=%d, R=%d", + firstPixel[0], firstPixel[1], firstPixel[2]); + } - // 1. 高斯模糊 - cv::Mat blurred; - cv::GaussianBlur(image, blurred, cv::Size(0, 0), 3); - - // 2. 计算边缘掩码 - cv::Mat gray, edges; - cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); - cv::Laplacian(gray, edges, CV_16S, 3); - cv::convertScaleAbs(edges, edges); - - // 3. 创建锐化掩码 - cv::Mat sharpenMask; - cv::threshold(edges, sharpenMask, 10, 255, cv::THRESH_BINARY); - - // 4. 对边缘区域应用反锐化掩蔽 - cv::Mat sharpened = image.clone(); - - for (int y = 0; y < image.rows; ++y) { - for (int x = 0; x < image.cols; ++x) { - if (sharpenMask.at(y, x) > 0) { - // 反锐化掩蔽公式: sharpened = original + (original - blurred) * strength - cv::Vec3b origPixel = image.at(y, x); - cv::Vec3b blurPixel = blurred.at(y, x); + // 3. 统计信息 + int processedFaces = 0; + int sampledPixels = 0; + int failedFaces = 0; + + // 4. 为每个面采样 + #ifdef _USE_OPENMP + #pragma omp parallel for schedule(dynamic) reduction(+:processedFaces, sampledPixels, failedFaces) + #endif + for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) { + const FIndex faceID = (FIndex)idxFace; + const Label label = faceLabels[faceID]; + + if (label == 0) { + failedFaces++; + continue; + } + + const IIndex idxView = label - 1; + if (idxView >= images.size()) { + failedFaces++; + continue; + } + + // 获取面的几何信息 + const TexCoord* meshUVs = &scene.mesh.faceTexcoords[faceID * 3]; + const Face& face = scene.mesh.faces[faceID]; + + // 获取源纹理信息 + const TexCoord* srcUVs = &sourceTexcoords[faceID * 3]; + const TexIndex textureIdx = sourceTexindices.empty() ? 0 : sourceTexindices[faceID]; + + if (textureIdx >= sourceTextures.size()) { + failedFaces++; + continue; + } + + const Image8U3& sourceTexture = sourceTextures[textureIdx]; + + // 计算面的UV边界 + AABB2f faceBounds(true); + for (int i = 0; i < 3; ++i) { + faceBounds.InsertFull(meshUVs[i]); + } + + // 转换为像素坐标 + int startX = (int)(faceBounds.ptMin.x() * textureSize); + int startY = (int)(faceBounds.ptMin.y() * textureSize); + int endX = (int)(faceBounds.ptMax.x() * textureSize); + int endY = (int)(faceBounds.ptMax.y() * textureSize); + + // 边界检查 + startX = std::max(0, std::min(startX, textureSize - 1)); + startY = std::max(0, std::min(startY, textureSize - 1)); + endX = std::max(0, std::min(endX, textureSize - 1)); + endY = std::max(0, std::min(endY, textureSize - 1)); + + if (startX >= endX || startY >= endY) { + failedFaces++; + continue; + } + + int faceSampledPixels = 0; + + // 采样纹理 + for (int y = startY; y <= endY; ++y) { + for (int x = startX; x <= endX; ++x) { + const Point2f texCoord((x + 0.5f) / textureSize, (y + 0.5f) / textureSize); - cv::Vec3b sharpPixel; - for (int c = 0; c < 3; ++c) { - int diff = static_cast(origPixel[c]) - static_cast(blurPixel[c]); - int sharpValue = static_cast(origPixel[c]) + static_cast(diff * strength); - sharpPixel[c] = cv::saturate_cast(sharpValue); + // 计算重心坐标 + Point3f barycentric; + if (!PointInTriangle(texCoord, meshUVs[0], meshUVs[1], meshUVs[2], barycentric)) { + continue; + } + + // 计算3D点 + const Vertex worldPoint = + vertices[face[0]] * barycentric.x + + vertices[face[1]] * barycentric.y + + vertices[face[2]] * barycentric.z; + + // 方案A:从原始图像采样 + Point2f imgPoint = ProjectPointWithAutoCorrection(images[idxView].camera, worldPoint, images[idxView]); + + if (imgPoint.x < 0 || imgPoint.x >= images[idxView].image.cols || + imgPoint.y < 0 || imgPoint.y >= images[idxView].image.rows) { + continue; + } + + // 修正:使用双线性插值采样 + const int x0 = (int)floor(imgPoint.x); + const int y0 = (int)floor(imgPoint.y); + const int x1 = std::min(x0 + 1, images[idxView].image.cols - 1); + const int y1 = std::min(y0 + 1, images[idxView].image.rows - 1); + + const float fx = imgPoint.x - x0; + const float fy = imgPoint.y - y0; + const float fx1 = 1.0f - fx; + const float fy1 = 1.0f - fy; + + // 采样四个点的颜色 + const cv::Vec3b& c00 = images[idxView].image.at(y0, x0); + const cv::Vec3b& c01 = images[idxView].image.at(y0, x1); + const cv::Vec3b& c10 = images[idxView].image.at(y1, x0); + const cv::Vec3b& c11 = images[idxView].image.at(y1, x1); + + // 双线性插值 + const cv::Vec3b color = + c00 * (fx1 * fy1) + + c01 * (fx * fy1) + + c10 * (fx1 * fy) + + c11 * (fx * fy); + + // 注意:OpenCV是BGR顺序,而Pixel8U通常是RGB顺序 + // 这里我们需要确保颜色通道正确 + #ifdef _USE_OPENMP + #pragma omp critical + #endif + { + // 方案1:直接使用BGR顺序(OpenCV默认) + // textureAtlas.at(y, x) = color; + + // 方案2:交换B和R通道(如果颜色偏蓝,说明B和R反了) + // 将BGR转换为RGB + textureAtlas.at(y, x) = cv::Vec3b(color[2], color[1], color[0]); } - sharpened.at(y, x) = sharpPixel; + faceSampledPixels++; } } + + if (faceSampledPixels > 0) { + processedFaces++; + sampledPixels += faceSampledPixels; + } else { + failedFaces++; + } } - // 5. 混合锐化后的图像 - cv::addWeighted(image, 1.0f - strength, sharpened, strength, 0, image); + DEBUG_EXTRA("纹理采样完成: 成功 %d 个面, 失败 %d 个面, 采样 %d 像素", + processedFaces, failedFaces, sampledPixels); - // 6. 可选的边缘增强 - if (strength > 0.5f) { - cv::Mat kernel = (cv::Mat_(3,3) << - -1, -1, -1, - -1, 9, -1, - -1, -1, -1); - cv::filter2D(image, image, -1, kernel); + // 5. 填充空洞 + if (processedFaces > 0 && sampledPixels > 0) { + // FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), textureSize, colEmpty); } - DEBUG_EXTRA("Sharpening completed in %s", TD_TIMER_GET_FMT().c_str()); -} - -void MeshTexture::ApplyAutoWhiteBalance(cv::Mat& image, float strength) -{ - if (strength <= 0) return; - - // 1. 分离通道 - std::vector channels; - cv::split(image, channels); - - // 2. 计算每个通道的平均值 - cv::Scalar avg = cv::mean(image); - double avgGray = (avg[0] + avg[1] + avg[2]) / 3.0; - - // 3. 计算白平衡系数 - double scaleR = avgGray / avg[2]; // BGR顺序 - double scaleG = avgGray / avg[1]; - double scaleB = avgGray / avg[0]; - - // 4. 应用白平衡 - cv::Mat balanced = image.clone(); - - for (int y = 0; y < balanced.rows; ++y) { - cv::Vec3b* row = balanced.ptr(y); - for (int x = 0; x < balanced.cols; ++x) { - cv::Vec3b& pixel = row[x]; - - double b = pixel[0] * scaleB; - double g = pixel[1] * scaleG; - double r = pixel[2] * scaleR; - - // 应用强度参数 - pixel[0] = cv::saturate_cast(pixel[0] * (1.0f - strength) + b * strength); - pixel[1] = cv::saturate_cast(pixel[1] * (1.0f - strength) + g * strength); - pixel[2] = cv::saturate_cast(pixel[2] * (1.0f - strength) + r * strength); - } + // 6. 锐化处理 + if (fSharpnessWeight > 0) { + // ApplySharpening(textureAtlas, fSharpnessWeight); } - // 5. 混合回原图 - cv::addWeighted(image, 1.0f - strength, balanced, strength, 0, image); + return textures; } Point2f MeshTexture::ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage) { @@ -11208,301 +14295,225 @@ void GenerateDistanceFieldFast(cv::Mat& distanceField, cv::Mat& nearestFaceIdx, } } -void FillTextureGaps(Image8U3& textureAtlas, const Mesh::TexCoordArr& faceTexcoords, - FIndex nFaces, const MeshTexture::LabelArr& faceLabels, - int textureSize, Pixel8U colEmpty) +void FillTextureGaps( + Image8U3& texture, + const Mesh::TexCoordArr& uvs, + FIndex faceCount, + int textureSize, + Pixel8U colEmpty +) { - DEBUG_EXTRA("开始填充纹理间隙..."); + DEBUG_EXTRA("Filling texture gaps for %dx%d texture", texture.cols, texture.rows); - // 创建距离场 - cv::Mat distanceField(textureSize, textureSize, CV_32FC1, cv::Scalar(std::numeric_limits::max())); - cv::Mat nearestPixelIdx(textureSize, textureSize, CV_32SC1, cv::Scalar(-1)); + // 创建掩码图像 + cv::Mat mask = cv::Mat::zeros(texture.rows, texture.cols, CV_8UC1); - // 1. 初始化种子点(已有纹理的像素) - for (int y = 0; y < textureSize; ++y) { - for (int x = 0; x < textureSize; ++x) { - const Pixel8U& pixel = textureAtlas(y, x); - if (pixel[0] != colEmpty[0] || pixel[1] != colEmpty[1] || pixel[2] != colEmpty[2]) { - distanceField.at(y, x) = 0.0f; - nearestPixelIdx.at(y, x) = y * textureSize + x; - } + // 标记有纹理的像素 + for (FIndex fid = 0; fid < faceCount; ++fid) { + const TexCoord* uv = &uvs[fid * 3]; + + // 计算面的UV边界 + AABB2f faceBounds(true); + for (int i = 0; i < 3; ++i) { + Point2f pixelPos(uv[i].x * textureSize, uv[i].y * textureSize); + faceBounds.InsertFull(pixelPos); } - } - - // 2. 跳点传播算法 - for (int step = textureSize / 2; step >= 1; step /= 2) { - for (int y = 0; y < textureSize; ++y) { - for (int x = 0; x < textureSize; ++x) { - float minDist = distanceField.at(y, x); - int bestIdx = nearestPixelIdx.at(y, x); - - for (int dy = -1; dy <= 1; dy += 2) { - for (int dx = -1; dx <= 1; dx += 2) { - int nx = x + dx * step; - int ny = y + dy * step; - - if (nx >= 0 && nx < textureSize && ny >= 0 && ny < textureSize) { - float neighborDist = distanceField.at(ny, nx); - float dist = neighborDist + sqrtf((dx*dx + dy*dy) * step * step); - - if (dist < minDist && nearestPixelIdx.at(ny, nx) != -1) { - minDist = dist; - bestIdx = nearestPixelIdx.at(ny, nx); - } - } - } - } + + // 填充三角形区域 + const int startX = std::max(0, (int)faceBounds.ptMin.x()); + const int startY = std::max(0, (int)faceBounds.ptMin.y()); + const int endX = std::min(texture.cols - 1, (int)faceBounds.ptMax.x()); + const int endY = std::min(texture.rows - 1, (int)faceBounds.ptMax.y()); + + for (int y = startY; y <= endY; ++y) { + for (int x = startX; x <= endX; ++x) { + Point2f texPos((float)x / textureSize, (float)y / textureSize); - if (bestIdx != -1 && minDist < distanceField.at(y, x)) { - distanceField.at(y, x) = minDist; - nearestPixelIdx.at(y, x) = bestIdx; + // 检查是否在UV三角形内 + Point3f barycentric; + if (PointInTriangle(texPos, uv[0], uv[1], uv[2], barycentric)) { + mask.at(y, x) = 255; } } } } - // 3. 填充空白区域 - const float maxFillDistance = 5.0f; - int filledCount = 0; + // 使用膨胀操作扩展掩码 + cv::Mat dilatedMask; + cv::dilate(mask, dilatedMask, cv::Mat(), cv::Point(-1, -1), 2); - for (int y = 0; y < textureSize; ++y) { - for (int x = 0; x < textureSize; ++x) { - Pixel8U& pixel = textureAtlas(y, x); - if (pixel[0] == colEmpty[0] && pixel[1] == colEmpty[1] && pixel[2] == colEmpty[2]) { - int nearestIdx = nearestPixelIdx.at(y, x); - float dist = distanceField.at(y, x); - - if (nearestIdx != -1 && dist <= maxFillDistance) { - int nx = nearestIdx % textureSize; - int ny = nearestIdx / textureSize; - pixel = textureAtlas(ny, nx); - filledCount++; - } - } - } - } + // 对空洞区域进行填充 + cv::inpaint(texture, mask, texture, 3, cv::INPAINT_TELEA); - DEBUG_EXTRA("快速填充完成,填充了 %d 个像素", filledCount); - - // // 4. 优化版图像修复 - // cv::Mat mask = cv::Mat::zeros(textureSize, textureSize, CV_8UC1); - // cv::Mat textureMat = textureAtlas; - - // // 使用OpenCV向量化操作 - // unsigned char r = colEmpty[0]; - // unsigned char g = colEmpty[1]; - // unsigned char b = colEmpty[2]; - - // #pragma omp parallel for - // for (int y = 0; y < textureSize; ++y) { - // unsigned char* maskRow = mask.ptr(y); - // const unsigned char* textureRow = textureMat.ptr(y); - - // for (int x = 0; x < textureSize; ++x) { - // int idx = x * 3; - // if (textureRow[idx] == r && - // textureRow[idx + 1] == g && - // textureRow[idx + 2] == b) { - // maskRow[x] = 255; - // } - // } - // } - - // if (cv::countNonZero(mask) > 0) { - // // 优化inpaint参数 - // int inpaintRadius = 2; // 减少修复半径 - // cv::Mat inpaintResult; - - // // 如果空白区域很少,使用更快的算法 - // double blankRatio = cv::countNonZero(mask) / (double)(textureSize * textureSize); - // if (blankRatio < 0.01) { // 空白区域少于1% - // // 使用快速修复 - // cv::dilate(textureMat, inpaintResult, cv::Mat()); - // inpaintResult.copyTo(textureAtlas, mask); - // } else { - // // 使用inpaint - // cv::inpaint(textureMat, mask, inpaintResult, inpaintRadius, cv::INPAINT_TELEA); - // inpaintResult.copyTo(textureAtlas); - // } - // } + DEBUG_EXTRA("Texture gap filling completed"); +} + +void ApplySharpening(Image8U3& texture, float weight) +{ + if (weight <= 0) return; - DEBUG_EXTRA("纹理间隙填充完成"); + Image8U3 blurred; + cv::GaussianBlur(texture, blurred, cv::Size(0, 0), 1.5); + cv::addWeighted(texture, 1.0 + weight, blurred, -weight, 0, texture); } -Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLabels, const IIndexArr& views, - unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) +Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV( + const Mesh::Image8U3Arr& sourceTextures, // 已有纹理数组 + const Mesh::TexCoordArr& sourceTexcoords, // 已有UV坐标 + const Mesh::TexIndexArr& sourceTexindices, // 已有纹理索引 + unsigned nTextureSizeMultiple, + Pixel8U colEmpty, + float fSharpnessWeight +) { - // 1. 分析整个模型的UV布局 + DEBUG_EXTRA("Generating texture atlas from existing UV data with %zu source textures", + sourceTextures.size()); + + // 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); + // 确保UV在[0,1]范围内 + if (uvBounds.ptMin.x() < 0 || uvBounds.ptMin.y() < 0 || + uvBounds.ptMax.x() > 1 || uvBounds.ptMax.y() > 1) { + // UV超出范围,进行归一化 + DEBUG_EXTRA("UV coordinates out of [0,1] range, normalizing..."); + uvBounds = AABB2f(true); + for (size_t i = 0; i < scene.mesh.faceTexcoords.size(); i += 3) { + for (int v = 0; v < 3; ++v) { + const TexCoord& uv = scene.mesh.faceTexcoords[i + v]; + uvBounds.InsertFull(uv); + } + } + } + + // 计算纹理尺寸 + const float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x(); + const float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y(); + const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); - // 3. 创建单个纹理图集 + // 2. 创建单个纹理图集 Mesh::Image8U3Arr textures; Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); - DEBUG_EXTRA("生成纹理图集: 尺寸=%dx%d, UV范围=[%.3f,%.3f]-[%.3f,%.3f]", + DEBUG_EXTRA("Creating texture atlas: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]", textureSize, textureSize, - uvBounds.ptMin.x(), uvBounds.ptMin.y(), uvBounds.ptMax.x(), uvBounds.ptMax.y()); + uvBounds.ptMin.x(), uvBounds.ptMin.y(), + uvBounds.ptMax.x(), uvBounds.ptMax.y()); + + // 3. 为每个面采样颜色 + int processedFaces = 0; + int failedFaces = 0; - // 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) { + #pragma omp parallel for schedule(dynamic) reduction(+:processedFaces, failedFaces) #endif + for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) { const FIndex faceID = (FIndex)idxFace; - const Label label = faceLabels[faceID]; - if (label == 0) continue; // 跳过无视图的面片 + const Face& face = scene.mesh.faces[faceID]; - const IIndex idxView = label - 1; - if (idxView >= images.size()) continue; + // 获取模型原始UV坐标 + const TexCoord* modelUVs = &scene.mesh.faceTexcoords[faceID * 3]; - // 获取面的UV坐标和几何信息 - const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; - const Face& face = scene.mesh.faces[faceID]; - const Image& sourceImage = images[idxView]; + // 获取对应source纹理中的UV坐标 + const TexCoord* sourceUVs = &sourceTexcoords[faceID * 3]; + const TexIndex textureIdx = sourceTexindices.empty() ? 0 : sourceTexindices[faceID]; + + if (textureIdx >= sourceTextures.size()) { + failedFaces++; + continue; + } - // 计算面片在纹理图集中的边界框 + const Image8U3& sourceTexture = sourceTextures[textureIdx]; + + // 计算面片在目标纹理中的UV边界 AABB2f faceUVBounds(true); for (int i = 0; i < 3; ++i) { - faceUVBounds.InsertFull(uvCoords[i]); + // 从模型UV映射到纹理坐标 + const float u = (modelUVs[i].x - uvBounds.ptMin.x()) / uvWidth; + const float v = (modelUVs[i].y - uvBounds.ptMin.y()) / uvHeight; + faceUVBounds.InsertFull(Point2f(u, v)); } - // 将UV坐标转换到纹理像素坐标 + // 转换为像素坐标 const int startX = std::max(0, (int)(faceUVBounds.ptMin.x() * textureSize)); const int startY = std::max(0, (int)(faceUVBounds.ptMin.y() * textureSize)); const int endX = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.x() * textureSize)); const int endY = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.y() * textureSize)); - // 对面片覆盖的每个纹理像素进行采样 + if (startX > endX || startY > endY) { + failedFaces++; + continue; + } + + // 对面片覆盖的每个像素进行采样 for (int y = startY; y <= endY; ++y) { for (int x = startX; x <= endX; ++x) { const Point2f texCoord((float)x / textureSize, (float)y / textureSize); - // 1. 检查是否在三角形内 + // 1. 检查是否在模型UV三角形内 Point3f barycentric; - if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { - // 标准内部采样 - // 计算3D空间中的对应点 - const Vertex worldPoint = - vertices[face[0]] * barycentric.x + - vertices[face[1]] * barycentric.y + - vertices[face[2]] * barycentric.z; - - // 将3D点投影到源图像 - Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage); - - // 验证投影的有效性 - if (!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); - continue; - } - - // 检查投影有效性 - 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; - } + if (!PointInTriangle(texCoord, + Point2f((modelUVs[0].x - uvBounds.ptMin.x()) / uvWidth, + (modelUVs[0].y - uvBounds.ptMin.y()) / uvHeight), + Point2f((modelUVs[1].x - uvBounds.ptMin.x()) / uvWidth, + (modelUVs[1].y - uvBounds.ptMin.y()) / uvHeight), + Point2f((modelUVs[2].x - uvBounds.ptMin.x()) / uvWidth, + (modelUVs[2].y - uvBounds.ptMin.y()) / uvHeight), barycentric)) { + continue; } - // 2. 如果不在三角形内,检查是否在三角形边缘附近 - else { - // 计算到三角形边缘的最近距离 - float minDist = std::numeric_limits::max(); - Point2f closestPoint; - - // 检查三条边 - for (int i = 0; i < 3; ++i) { - const Point2f& p1 = uvCoords[i]; - const Point2f& p2 = uvCoords[(i + 1) % 3]; - - Point2f proj = ProjectPointToLineSegment(texCoord, p1, p2); - float dist = cv::norm(texCoord - proj); - - if (dist < minDist) { - minDist = dist; - closestPoint = proj; - } - } - - // 如果距离小于阈值,进行边缘填充 - const float edgeThreshold = 1.0f / textureSize; // 1个像素的阈值 - if (minDist <= edgeThreshold * 2) { // 填充边缘周围2个像素 - // 在最近点计算重心坐标 - Point3f edgeBarycentric = BarycentricFromPoint(closestPoint, uvCoords[0], uvCoords[1], uvCoords[2]); - - if (edgeBarycentric.x >= 0 && edgeBarycentric.x <= 1 && - edgeBarycentric.y >= 0 && edgeBarycentric.y <= 1 && - edgeBarycentric.z >= 0 && edgeBarycentric.z <= 1) { - - const Vertex worldPoint = - vertices[face[0]] * edgeBarycentric.x + - vertices[face[1]] * edgeBarycentric.y + - vertices[face[2]] * edgeBarycentric.z; - - Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage); - - if (ValidateProjection(worldPoint, sourceImage, imgPoint) && - sourceImage.image.isInside(imgPoint) && - sourceImage.camera.IsInFront(worldPoint)) { - - Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint); - - #ifdef _USE_OPENMP - #pragma omp critical - #endif - { - textureAtlas(y, x) = sampledColor; - } - } - } - } + + // 2. 使用相同的重心坐标在source纹理UV中插值 + Point2f sourceTexCoord( + sourceUVs[0].x * barycentric.x + + sourceUVs[1].x * barycentric.y + + sourceUVs[2].x * barycentric.z, + sourceUVs[0].y * barycentric.x + + sourceUVs[1].y * barycentric.y + + sourceUVs[2].y * barycentric.z + ); + + // 确保UV坐标在有效范围内 + if (sourceTexCoord.x < 0 || sourceTexCoord.x >= sourceTexture.cols || + sourceTexCoord.y < 0 || sourceTexCoord.y >= sourceTexture.rows) { + continue; } + + // 3. 从source纹理中采样颜色 + const cv::Vec3b color = sourceTexture.at( + (int)sourceTexCoord.y, (int)sourceTexCoord.x); + + // 4. 写入目标纹理 + textureAtlas.at(y, x) = color; } } + + processedFaces++; } - // 5. 添加后处理填充函数 - FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), faceLabels, textureSize, colEmpty); + DEBUG_EXTRA("Texture sampling completed: %d faces processed, %d faces failed", + processedFaces, failedFaces); + + // 4. 填充纹理空隙 + if (processedFaces > 0) { + // FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), textureSize, colEmpty); + } - // 6. 应用后处理 + // 5. 应用锐化 if (fSharpnessWeight > 0) { // ApplySharpening(textureAtlas, fSharpnessWeight); } - DEBUG_EXTRA("纹理图集生成完成: %u个面片, 纹理尺寸%dx%d", - scene.mesh.faces.size(), textureSize, textureSize); + DEBUG_EXTRA("Generated texture atlas: %dx%d from %zu source textures", + textureSize, textureSize, sourceTextures.size()); return textures; } - bool MeshTexture::ValidateProjection(const Vertex& worldPoint, const Image& sourceImage, Point2f imgPoint, float maxReprojectionError) { @@ -11737,70 +14748,32 @@ bool MeshTexture::PointInTriangle(const Point2f& p, const Point2f& a, const Poin return false; } -int MeshTexture::ComputeOptimalTextureSize(const Point2f& uvMin, const Point2f& uvMax, unsigned nTextureSizeMultiple) { - - // 1. 计算UV坐标的实际覆盖范围 - const float uvRangeX = uvMax.x - uvMin.x; - const float uvRangeY = uvMax.y - uvMin.y; - - - // 如果UV范围无效,返回默认尺寸 - if (uvRangeX <= 0.0f || uvRangeY <= 0.0f) { - return 1024; // 默认回退尺寸 - } - - // 2. 基于UV覆盖范围计算基础纹理尺寸 - // 假设我们希望纹理密度为:每单位UV空间对应N个像素 - const float pixelsPerUV = 256.0f; // 可配置的密度系数 - int baseSizeX = static_cast(uvRangeX * pixelsPerUV); - int baseSizeY = static_cast(uvRangeY * pixelsPerUV); - - // 取较大者作为基础尺寸,以确保覆盖整个UV范围 - int baseSize = std::max(baseSizeX, baseSizeY); - - DEBUG_EXTRA("%d, %d", nTextureSizeMultiple, baseSize); - // 3. 应用纹理尺寸倍数约束 - baseSize = (baseSize + nTextureSizeMultiple - 1) / nTextureSizeMultiple * nTextureSizeMultiple; - - // 4. 设备能力限制 - int maxTextureSize = 4096; // 默认最大值 - int minTextureSize = 64; // 默认最小值 - - // 此处可查询设备实际支持的最大纹理尺寸 - // glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); - - // 5. 质量等级调整(可根据OPT中的质量设置进行调整) - int qualityLevel = 1; // 默认中等质量 - switch (qualityLevel) { - case 0: // 低质量:缩小纹理尺寸 - baseSize = baseSize / 2; - break; - case 1: // 中等质量:保持原计算尺寸 - // baseSize 保持不变 - break; - case 2: // 高质量:适度增大尺寸(如有足够内存) - baseSize = std::min(baseSize * 2, maxTextureSize); - break; - } - - DEBUG_EXTRA("(UV范围: [%.3f, %.3f]), %d, %d, %d", uvRangeX, uvRangeY, nTextureSizeMultiple, baseSize, minTextureSize); - // 6. 最终钳制到有效范围 - baseSize = (std::max)(minTextureSize, (std::min)(baseSize, maxTextureSize)); - - // 7. 确保尺寸为2的幂次(兼容性考虑,可选) - int finalSize = 1; - while (finalSize < baseSize) { - finalSize <<= 1; - if (finalSize >= maxTextureSize) { - finalSize = maxTextureSize; - break; - } +// 辅助函数:计算最佳纹理尺寸 +int MeshTexture::ComputeOptimalTextureSize(float uvWidth, float uvHeight, unsigned multiple) +{ + // 计算所需尺寸 + int baseWidth = (int)ceil(uvWidth * 2048); // 假设基础分辨率 + int baseHeight = (int)ceil(uvHeight * 2048); + + // 向上对齐到multiple的倍数 + int width = ((baseWidth + multiple - 1) / multiple) * multiple; + int height = ((baseHeight + multiple - 1) / multiple) * multiple; + + // 确保最小尺寸 + width = std::max(width, 256); + height = std::max(height, 256); + + // 使用最大尺寸 + int size = std::max(width, height); + + // 限制最大尺寸 + const int MAX_SIZE = 8192; + if (size > MAX_SIZE) { + DEBUG_EXTRA("Warning: Texture size %d exceeds maximum %d, reducing...", size, MAX_SIZE); + size = std::min(size, MAX_SIZE); } - DEBUG_EXTRA("计算出的最优纹理尺寸: %d (UV范围: [%.3f, %.3f])", - finalSize, uvRangeX, uvRangeY); - - return finalSize; + return size; } // 保存遮挡数据到文件 @@ -12099,31 +15072,59 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi DEBUG_EXTRA("TextureMesh %b, %s", bUseExistingUV, strUVMeshFileName.c_str()); if (bUseExistingUV && !strUVMeshFileName.empty()) { + // 1. 生成临时纹理和UV + Mesh::TexCoordArr existingTexcoords; + Mesh::TexIndexArr existingTexindices; + + // texture.GenerateTextureForUV(bGlobalSeamLeveling, bLocalSeamLeveling, nTextureSizeMultiple, + // nRectPackingHeuristic, colEmpty, fSharpnessWeight, maxTextureSize, + // baseFileName, bOriginFaceview, this, existingTexcoords, existingTexindices); + + if (!texture.GenerateTextureWithViewConsistency( + bGlobalSeamLeveling, bLocalSeamLeveling, nTextureSizeMultiple, + nRectPackingHeuristic, colEmpty, fSharpnessWeight, maxTextureSize, + baseFileName, bOriginFaceview, this)) { + return false; + } + + // 保存生成的纹理数据 + Mesh::Image8U3Arr existingTextures = texture.texturesDiffuseTemp; + 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 表示失败 - } - + + // 2. 使用预计算UV模式加载网格 + if (!mesh.Load(MAKE_PATH_SAFE(strUVMeshFileName), true)) { + VERBOSE("error: cannot load mesh file with UV coordinates"); + return 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; - } + + if (mesh.faceTexcoords.empty()) { + VERBOSE("error: the specified mesh does not contain UV coordinates"); + return false; + } + + // 3. 使用新的纹理生成方法 + MeshTexture texture2(*this, nResolutionLevel, nMinResolution); + + // 关键:确保几何信息正确 + texture2.scene.mesh.vertices = mesh.vertices; + texture2.scene.mesh.faces = mesh.faces; + texture2.scene.mesh.faceTexcoords = mesh.faceTexcoords; // 使用外部UV + + if (!texture2.TextureWithExistingUV(views, nIgnoreMaskLabel, fOutlierThreshold, + nTextureSizeMultiple, colEmpty, fSharpnessWeight, + existingTextures, existingTexcoords, existingTexindices)) { + return false; + } + + // 4. 将生成的纹理赋值回场景网格 + mesh.texturesDiffuse = std::move(texture2.scene.mesh.texturesDiffuse); + mesh.faceTexindices = std::move(texture2.scene.mesh.faceTexindices); + + return true; + } // mesh.CheckUVValid();