diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 11f0f04..dde2b64 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -166,28 +166,6 @@ 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; @@ -491,16 +469,7 @@ 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(); - bool ReassignComponentForFace(FIndex faceIdx); - bool ReassignFaceToCorrectPatch(FIndex faceIdx); - void CleanSeamEdgesComprehensive(); void CreateSeamVertices(); - uint32_t FindOrCreateComponentForFace(FIndex faceIdx); - uint32_t FindNearestPatchForComponent(uint32_t compID); void GlobalSeamLeveling(); void GlobalSeamLeveling3(); void LocalSeamLeveling(); @@ -510,106 +479,54 @@ 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, - 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 - ); + 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); + 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 = 512); + void FastAlternativeGapFilling(cv::Mat& textureAtlas, + const cv::Mat1f& weightAccum, + const cv::Mat1b& validMask); + void ApplySimpleColorCorrection(cv::Mat& imgMat, float strength); + + void ApplyColorCorrection(cv::Mat& image, float strength); + void ApplySharpening(Image8U3& image, float strength); + void ApplyAutoWhiteBalance(cv::Mat& image, float strength); 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(float uvWidth, float uvHeight, unsigned multiple); + int ComputeOptimalTextureSize(const Point2f& uvMin, const Point2f& uvMax, unsigned nTextureSizeMultiple); // Bruce //* template @@ -791,7 +708,6 @@ 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; @@ -842,7 +758,6 @@ 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), @@ -7690,313 +7605,70 @@ bool MeshTexture::FaceViewSelection4( unsigned minCommonCameras, float fOutlierT return true; } -void MeshTexture::FixIsolatedComponents() +// create seam vertices and edges +void MeshTexture::CreateSeamVertices() { - 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); - } - } - } - } -} + // 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); -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; -} + // if (labelsInvalid[edge.i] != NO_ID || labelsInvalid[edge.j] != NO_ID ) + // continue; -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); + 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(); } // Native @@ -9197,1023 +8869,671 @@ void MeshTexture::GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLevel } } -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) +void MeshTexture::GlobalSeamLevelingExternalUV() { - int border = 2; - if (!bOriginFaceview) - border = 4; - - Mesh::TexCoordArr& faceTexcoords2 = existingTexcoords; - Mesh::TexIndexArr& faceTexindices2 = existingTexindices; + // 针对外部UV数据的全局接缝处理 + // 实现更温和的接缝处理算法 +} - faceTexcoords2.resize(faces.size()*3); - faceTexindices2.resize(faces.size()); +void MeshTexture::LocalSeamLevelingExternalUV() +{ + // 针对外部UV数据的局部接缝处理 + // 实现保留原始UV特征的接缝处理 +} - #ifdef TEXOPT_USE_OPENMP - // LOG_OUT() << "def TEXOPT_USE_OPENMP" << std::endl; +// 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; + } - // ===== 修改:只在非外部UV模式下执行投影计算 ===== + // 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++; + } + } - { - #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= 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())); - // 计算边向量 - 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); + SparseMat Gamma(rowsGamma, rowsX); + Gamma.setFromTriplets(rows.Begin(), rows.End()); + rows.Empty(); - // 处理退化三角形情况(面积接近0) - const float epsilon = 1e-6f; - if (std::abs(denom) < epsilon) - { - // DEBUG_EXTRA("PointInTriangle - Degenerate triangle, denom=%.10f", denom); - } - else - { - DEBUG_EXTRA("PointInTriangle Yes idxFace=%d", idxFace); - } - } - - } - } - // compute relative texture coordinates - 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; + // fill the matrix A and the coefficients for the Vector b of the linear equation system + IndexArr indices; + Colors vertexColors; + Colors coeffB; + for (const SeamVertex& seamVertex: seamVertices) { + if (seamVertex.patches.size() < 2) + continue; + seamVertex.SortByPatchIndex(indices); + vertexColors.resize(indices.size()); + FOREACH(i, indices) { + const SeamVertex::Patch& patch0 = seamVertex.patches[indices[i]]; + ASSERT(patch0.idxPatch < numPatches); + SampleImage sampler(images[texturePatches[patch0.idxPatch].label].image); + for (const SeamVertex::Patch::Edge& edge: patch0.edges) { + const SeamVertex& seamVertex1 = seamVertices[edge.idxSeamVertex]; + const SeamVertex::Patches::IDX idxPatch1(seamVertex1.patches.Find(patch0.idxPatch)); + ASSERT(idxPatch1 != SeamVertex::Patches::NO_INDEX); + const SeamVertex::Patch& patch1 = seamVertex1.patches[idxPatch1]; + sampler.AddEdge(patch0.proj, patch1.proj); } - 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 - ); + vertexColors[i] = sampler.GetColor(); + } + const VertexPatch2RowMap& vertpatch2row = vertpatch2rows[seamVertex.idxVertex]; + for (IDX i=0; i(std::numeric_limits::max())); - 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]; - - // 计算边向量 - 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(); + 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; + }); + // 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(); - } + // 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()); } } - { - // 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); + // 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); + for (const FIndex idxFace: texturePatch.faces) { - TexCoord* texcoords = faceTexcoords2.data()+idxFace*3; - for (int i=0; i<3; ++i) - texcoords[i] = TexCoord(0.5f, 0.5f); + 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); } - } + // dilate with one pixel width, in order to make sure patch border smooths out a little + imageAdj.DilateMean<1>(imageAdj, Color::ZERO); - LOG_OUT() << "First loop completed" << std::endl; + // Bruce + cv::Mat adjMat(imageAdj); + // cv::GaussianBlur(adjMat, adjMat, cv::Size(3,3), 0.5); - /* - TD_TIMER_STARTD(); - // perform seam leveling - if (texturePatches.size() > 2 && (bGlobalSeamLeveling || bLocalSeamLeveling)) { - // create seam vertices and edges - CreateSeamVertices(); + // 将原有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; - // perform global seam leveling - if (bGlobalSeamLeveling) { - TD_TIMER_STARTD(); - if (bUseExternalUV) { - // 外部UV数据可能需要更温和的接缝处理 - GlobalSeamLevelingExternalUV(); - } else { - GlobalSeamLeveling3(); - } - DEBUG_EXTRA("\tglobal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); - } + // 修改:在应用调整时进行边缘检测,避免过度调整 + // 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; - { - // merge texture patches with overlapping rectangles - for (unsigned i=0; i(r,c); + if (v.r == 0 && v.g == 0 && v.b == 0) 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--); - } - } - } - - 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}; - } - 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()); + const Color col(RGB2YCBCR(Color(v))); + // const Color acol(YCBCR2RGB(Color(col+a))); + Color acol = YCBCR2RGB(Color(col + a * edgeWeight)); // 应用边缘权重 - 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); - } - } + 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() << "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 - ); - } - } + /* + 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); } } - } - 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::GlobalSeamLeveling() +void MeshTexture::LocalSeamLeveling() { ASSERT(!seamVertices.empty()); const unsigned numPatches(texturePatches.size()-1); - // find the patch ID for each vertex - PatchIndices patchIndices(vertices.size()); - patchIndices.Memset(0); + + // Create a boolean array to mark invalid vertices + BoolArr vertexInvalid(vertices.size()); + vertexInvalid.Memset(false); 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; + if (labelsInvalid[f] != NO_ID) { + const Face& face = faces[f]; + for (int v=0; v<3; ++v) + vertexInvalid[face[v]] = true; + } } - // 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++; + // 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= 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; - } + // 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); } } - } - ASSERT(rows.size()/2 < static_cast(std::numeric_limits::max())); - - SparseMat Gamma(rowsGamma, rowsX); - Gamma.setFromTriplets(rows.Begin(), rows.End()); - rows.Empty(); - - // fill the matrix A and the coefficients for the Vector b of the linear equation system - IndexArr indices; - Colors vertexColors; - Colors coeffB; - for (const SeamVertex& seamVertex: seamVertices) { - if (seamVertex.patches.size() < 2) - continue; - seamVertex.SortByPatchIndex(indices); - vertexColors.resize(indices.size()); - FOREACH(i, indices) { - const SeamVertex::Patch& patch0 = seamVertex.patches[indices[i]]; - ASSERT(patch0.idxPatch < numPatches); - SampleImage sampler(images[texturePatches[patch0.idxPatch].label].image); - for (const SeamVertex::Patch::Edge& edge: patch0.edges) { - const SeamVertex& seamVertex1 = seamVertices[edge.idxSeamVertex]; - const SeamVertex::Patches::IDX idxPatch1(seamVertex1.patches.Find(patch0.idxPatch)); - ASSERT(idxPatch1 != SeamVertex::Patches::NO_INDEX); - const SeamVertex::Patch& patch1 = seamVertex1.patches[idxPatch1]; - sampler.AddEdge(patch0.proj, patch1.proj); + // 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) + 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; + } } - vertexColors[i] = sampler.GetColor(); - } - const VertexPatch2RowMap& vertpatch2row = vertpatch2rows[seamVertex.idxVertex]; - for (IDX i=0; i(sampler, patch.proj)/255.f, 1.f); } + const ImageRef pt(ROUND2INT(patch0.proj-offset)); + image(pt) = accumColor.Normalized(); + mask(pt) = border; } - } - ASSERT(coeffB.size() < static_cast(std::numeric_limits::max())); - - const MatIdx rowsA((MatIdx)coeffB.size()); - SparseMat A(rowsA, rowsX); - A.setFromTriplets(rows.Begin(), rows.End()); - rows.Release(); - - 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; - }); - - // 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(); - - // 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()); + // 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); + } } } +} - // adjust texture patches using the correction colors +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 i=0; i<(int)numPatches; ++i) { + for (int_t idx=0; idx<(int_t)numPatches; ++idx) { + TexturePatch& texturePatch = texturePatches[(uint32_t)idx]; #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); - + const Image& imageData = images[texturePatch.label]; + AABB2f aabb(true); 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 = faceTexcoords.data()+idxFace*3; + for (int i=0; i<3; ++i) { + texcoords[i] = imageData.camera.ProjectPointP(vertices[face[i]]); + ASSERT(imageData.image.isInsideWithBorder(texcoords[i], border)); + aabb.InsertFull(texcoords[i]); + } } + // compute relative texture coordinates + ASSERT(imageData.image.isInside(Point2f(aabb.ptMin))); + ASSERT(imageData.image.isInside(Point2f(aabb.ptMax))); + 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; + 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 = faceTexcoords.data()+idxFace*3; + for (int v=0; v<3; ++v) + texcoords[v] -= offset; + } + } + { + // 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) { + TexCoord* texcoords = faceTexcoords.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); - - // 将原有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; + LOG_OUT() << "First loop completed" << std::endl; + // perform seam leveling + if (texturePatches.size() > 2 && (bGlobalSeamLeveling || bLocalSeamLeveling)) { + // create seam vertices and edges + CreateSeamVertices(); - 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)); // 应用边缘权重 + // perform global seam leveling + if (bGlobalSeamLeveling) { + TD_TIMER_STARTD(); + GlobalSeamLeveling(); + DEBUG_ULTIMATE("\tglobal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); + } - 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); // 四舍五入 - } - } + // perform local seam leveling + if (bLocalSeamLeveling) { + TD_TIMER_STARTD(); + LocalSeamLeveling(); + DEBUG_ULTIMATE("\tlocal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); } - /* - 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); - } + } + + // 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; - // 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 0 && textureSize > maxTextureSize) + textureSize = maxTextureSize; + } - // Check if this texture patch contains any invalid vertices - bool hasInvalidVertex = false; - for (const FIndex idxFace: texturePatch.faces) { - const Face& face = faces[idxFace]; - for (int v=0; v<3; ++v) { - if (vertexInvalid[face[v]]) { - hasInvalidVertex = true; - break; + 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); } } - if (hasInvalidVertex) break; } - - // 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; + LOG_OUT() << "Third loop completed" << std::endl; + Mesh::FaceIdxArr emptyFaceIndexes; - // 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; + #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)); } - } 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) - 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(r,c); - for (int p=0; p<3; ++p) - v[p] = (uint8_t)CLAMP(ROUND2INT(a[p]*255.f), 0, 255); - } - } - } -} - -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 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; - } + 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()); @@ -10224,3804 +9544,1068 @@ void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeve 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; -} - -// 重新为面分配组件ID的函数 -bool MeshTexture::ReassignComponentForFace(FIndex faceIdx) -{ - if (faceIdx >= faces.GetSize()) { - return false; - } - - if (faceIdx >= components.size()) { - return false; - } - - uint32_t currentCompID = components[faceIdx]; - if (currentCompID != NO_ID && currentCompID < mapIdxPatch.GetSize()) { - uint32_t currentPatch = mapIdxPatch[currentCompID]; - if (currentPatch < texturePatches.size()) { - return true; // 已经是有效的组件 - } - } - - // 找到面的相邻面,看它们属于哪个组件 - 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.size()) { - uint32_t compID = components[adjFace]; - if (compID != NO_ID && compID < mapIdxPatch.GetSize()) { - neighborComponentCounts[compID]++; - } - } - } - } - - // 选择最常见的相邻组件 - 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) { - components[faceIdx] = bestCompID; - return true; - } - - return false; -} - -// 重新为面分配纹理块的函数 -bool MeshTexture::ReassignFaceToCorrectPatch(FIndex faceIdx) -{ - if (faceIdx >= faces.GetSize()) { - return false; - } - - if (faceIdx >= components.size()) { - return false; - } - - uint32_t compID = components[faceIdx]; - if (compID == NO_ID) { - // 如果没有组件ID,尝试找到最近的纹理块 - uint32_t patchIdx = FindNearestPatchForFaces({faceIdx}); - if (patchIdx != NO_ID) { - // 为这个面创建一个新的组件 - uint32_t newCompID = static_cast(components.size()); - components[faceIdx] = newCompID; - - // 扩展mapIdxPatch数组 - if (newCompID >= mapIdxPatch.GetSize()) { - mapIdxPatch.Resize(newCompID + 1); - } - mapIdxPatch[newCompID] = patchIdx; - return true; - } - return false; - } - - // 检查当前组件是否映射到有效的纹理块 - if (compID >= mapIdxPatch.GetSize()) { - return false; - } - - uint32_t currentPatch = mapIdxPatch[compID]; - if (currentPatch < texturePatches.size()) { - return true; // 已经有效 - } - - // 找到最近的纹理块 - uint32_t nearestPatch = FindNearestPatchForFaces({faceIdx}); - if (nearestPatch != NO_ID) { - mapIdxPatch[compID] = nearestPatch; - return true; - } - - return false; -} - -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 + 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; } - // 6. 为每个组件找到对应的纹理块 - std::vector> compPatchVotes(nextCompID); + // 确保输出目录存在 + #ifdef _WIN32 + _mkdir(outputDir.c_str()); + #else + mkdir(outputDir.c_str(), 0755); + #endif - for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { - if (faceIdx >= components.GetSize()) { + // 保存所有纹理 + for (size_t i = 0; i < generatedTextures.size(); ++i) { + if (generatedTextures[i].empty()) { + DEBUG_EXTRA("警告: 纹理 %zu 为空,跳过保存", i); 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; - } - } + // 生成文件名 + std::string filename = outputDir + "/texture_" + std::to_string(i) + ".png"; - // 如果还是NO_ID,使用默认值0 - if (mapIdxPatch[compID] == NO_ID) { - mapIdxPatch[compID] = 0; + // 使用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; } } - DEBUG_EXTRA("Seam data reinitialized: %u components, %u valid mappings", - nextCompID, mapIdxPatch.GetSize()); + return true; } -void MeshTexture::AssignComponentsToOrphanFaces() +bool MeshTexture::TextureWithExistingUV(const IIndexArr& views, int nIgnoreMaskLabel, + float fOutlierThreshold, unsigned nTextureSizeMultiple, + Pixel8U colEmpty, float fSharpnessWeight) { - DEBUG_EXTRA("Assigning components to orphan faces - improved version"); - - int assignedFaces = 0; + DEBUG_EXTRA("TextureWithExistingUV with multi-view blending"); + TD_TIMER_START(); - // 使用队列处理孤立面 - std::vector orphanFaces; - for (FIndex faceIdx = 0; faceIdx < faces.GetSize(); ++faceIdx) { - if (faceIdx >= components.GetSize() || components[faceIdx] == NO_ID) { - orphanFaces.push_back(faceIdx); - } + // 1. 验证输入 + if (scene.mesh.faceTexcoords.empty()) { + VERBOSE("error: mesh does not contain UV coordinates"); + return false; } - 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 (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; } - // 处理剩余的孤立面 - 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++; - } - } + // 2. 获取所有面的视图数据 + FaceDataViewArr facesDatas; + if (!ListCameraFaces(facesDatas, fOutlierThreshold, nIgnoreMaskLabel, views, false)) { + return false; } - 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; // 一致性权重 + DEBUG_EXTRA("Processing %zu faces with existing UV coordinates", scene.mesh.faces.size()); - // 步骤1: 计算每个面的候选视图质量 - std::vector>> faceCandidates(numFaces); + // 3. 为每个面选择最佳视图,但保留多个高质量视图用于融合 + std::vector> faceViews(faces.size()); + std::vector> faceViewWeights(faces.size()); + #ifdef _USE_OPENMP #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++; - } - } + for (int_t idxFace = 0; idxFace < (int_t)faces.size(); ++idxFace) { + #else + FOREACH(idxFace, faces) { + #endif + const FaceDataArr& faceDatas = facesDatas[idxFace]; + std::vector> viewScores; - 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 (const FaceData& data : faceDatas) { + if (!data.bInvalidFacesRelative) { + // 计算视图质量权重 + float weight = data.quality; - 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]]++; - } - } - } + // 添加视角与法线夹角的权重 + const Image& image = images[data.idxView]; + const Normal& faceNormal = scene.mesh.faceNormals[idxFace]; - // 选择最常见的相邻视图 - int bestNeighborView = -1; - int maxNeighborCount = 0; - for (const auto& countPair : neighborViewCounts) { - if (countPair.second > maxNeighborCount) { - maxNeighborCount = countPair.second; - bestNeighborView = countPair.first; - } - } + // 计算相机方向 + 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; + } - 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]; + // 法线与相机方向的点积(越大越好,表示面正对相机) + float dotProduct = faceNormal.dot(cameraForward); + float angleWeight = (dotProduct + 1.0f) * 0.5f; // 归一化到[0,1] - if (idxV0 > idxV1) std::swap(idxV0, idxV1); - uint64_t edgeKey = (static_cast(idxV0) << 32) | idxV1; + // 综合权重 + float finalWeight = weight * 0.7f + angleWeight * 0.3f; - 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); - } - } - } + viewScores.emplace_back(finalWeight, data.idxView); } } - // 只保留足够大的纹理块 - if (static_cast(currentPatch.size()) >= minPatchSize) { - patchFaces.push_back(std::move(currentPatch)); - patchCounter++; - } else { - // 小纹理块重新标记为未分配 - for (FIndex fid : currentPatch) { - patchAssignments[static_cast(fid)] = -1; + // 按权重排序,选择前N个视图 + std::sort(viewScores.begin(), viewScores.end(), + [](const auto& a, const auto& b) { return a.first > b.first; }); + + const int maxViews = 5; // 最多使用5个视图进行融合 + float totalWeight = 0.0f; + + 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; } } - } - - 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); + + // 归一化权重 + if (totalWeight > 0.0f) { + for (float& w : faceViewWeights[idxFace]) { + w /= totalWeight; + } } } - DEBUG_EXTRA("Found %zu unassigned faces", unassignedFaces.size()); + // 4. 生成纹理图集(使用多视图融合) + Mesh::Image8U3Arr textures = GenerateMultiViewTextureAtlas(facesDatas, faceViews, faceViewWeights, + nTextureSizeMultiple, colEmpty, fSharpnessWeight); - 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); - } - } - } - } - } + if (!textures.empty()) { + scene.mesh.texturesDiffuse = std::move(textures); - // 为未分配的面找到最近的纹理块 - 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; - } + // 修复: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; } - // 执行BFS - std::vector> assignments; + DEBUG_EXTRA("Successfully generated %zu texture atlases", scene.mesh.texturesDiffuse.size()); - 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)); - } - } - } + // 保存纹理 + std::string outputDir = "texture_output"; + if (SaveGeneratedTextures(scene.mesh.texturesDiffuse, outputDir)) { + DEBUG_EXTRA("Textures saved to: %s", outputDir.c_str()); } - // 将分配的面加入到对应的纹理块 - 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)); - } + return true; } - // 步骤3: 创建纹理块 - DEBUG_EXTRA("Creating texture patches structure with %zu patches", patchFaces.size()); + DEBUG_EXTRA("Texture generation failed"); + return false; +} + +Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlas( + const FaceDataViewArr& facesDatas, + const std::vector>& faceViews, + const std::vector>& faceViewWeights, + unsigned nTextureSizeMultiple, + Pixel8U colEmpty, + float fSharpnessWeight) +{ + DEBUG_EXTRA("Generating multi-view texture atlas"); - // 先计算有效纹理块的数量 - size_t validPatches = 0; - for (const auto& patch : patchFaces) { - if (!patch.empty()) { - validPatches++; - } + // 1. 分析UV布局 + AABB2f uvBounds(true); + FOREACH(i, scene.mesh.faceTexcoords) { + const TexCoord& uv = scene.mesh.faceTexcoords[i]; + uvBounds.InsertFull(uv); } - texturePatches.resize(validPatches + 1); // 修正: Resize() -> resize() + // 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("Allocated %zu texture patches (including invalid patch)", texturePatches.size()); + 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()); - size_t patchIdx = 0; - for (size_t patchID = 0; patchID < patchFaces.size(); ++patchID) { - if (patchFaces[patchID].empty()) { - DEBUG_EXTRA("Skipping empty patch %zu", patchID); + // 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; + + // 检查是否有可用视图 + if (faceViews[faceID].empty()) { 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]; - } + const Face& face = scene.mesh.faces[faceID]; + const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; - // 确定纹理块的视图 - std::unordered_map viewCounts; - for (FIndex fid : patch.faces) { - int viewID = faceViewData[static_cast(fid)].viewID; - if (viewID != -1) { - viewCounts[viewID]++; - } + // 计算面片在纹理空间中的边界框 + AABB2f faceUVBounds(true); + for (int i = 0; i < 3; ++i) { + faceUVBounds.InsertFull(uvCoords[i]); } - int dominantView = -1; - int maxCount = 0; - for (const auto& pair : viewCounts) { - if (pair.second > maxCount) { - maxCount = pair.second; - dominantView = pair.first; - } - } + 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)); - 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(); // 清空数组 + // 为当前面片预计算3D点的重心坐标映射 + std::vector texPoints; + std::vector pointColors; // 存储每个采样点的颜色 - for (int fid = 0; fid < numFaces; ++fid) { - if (faceViewData[fid].viewID == -1) { - invalidPatch.faces.push_back(static_cast(fid)); // 修正: Insert -> push_back + // 均匀采样面片内部 + 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); + + // 验证投影 + if (!ValidateProjection(worldPoint, sourceImage, imgPoint) || + !sourceImage.image.isInside(imgPoint) || + !sourceImage.camera.IsInFront(worldPoint)) { + continue; + } + + // 采样图像 + 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); + } + } } } - 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; + // 使用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)); + } - const Image& imageData = images[texturePatch.label]; - const cv::Mat& sourceImage = imageData.image; + std::vector triangleList; + subdiv.getTriangleList(triangleList); - 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]); - } + // 遍历三角形并填充 + 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]); - 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())); + // 获取三角形内的像素 + std::vector pixels = GetPixelsInTriangle(pt1, pt2, pt3, textureSize); - // 遍历纹理三角形内的所有像素 - 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; + for (const auto& pixel : pixels) { + if (pixel.x < 0 || pixel.x >= textureSize || + pixel.y < 0 || pixel.y >= textureSize) { + continue; + } + + const Point2f texCoord((float)pixel.x / textureSize, (float)pixel.y / textureSize); + + // 找到最近采样点(使用最近邻插值) + 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; } + } + + if (minDist < 25.0f / (textureSize * textureSize)) { // 距离阈值 + // 获取最近采样点的颜色 + const cv::Vec3f& newColor = pointColors[nearestIdx]; - // 计算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; - } + // 累加颜色和权重 + #ifdef _USE_OPENMP + #pragma omp atomic + #endif + weightAccum(pixel.y, pixel.x) += 1.0f; - // 高质量采样(双线性插值 + 各向异性过滤) - cv::Vec3f color = SampleHighQuality(sourceImage, imgPoint); + #ifdef _USE_OPENMP + #pragma omp atomic + #endif + sampleCount(pixel.y, pixel.x) += 1; - #ifdef TEXOPT_USE_OPENMP + #ifdef _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; // 记录主视图 + colorAccum(pixel.y, pixel.x) += newColor; } } } } } - - // 第二次遍历:填充缺失像素,尽量从相同视图采样 - 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) { + // 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) { - 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; + if (weight > 0.0f) { + // 计算加权平均颜色 + cv::Vec3f avgColor = colorAccum(y, x) / weight; - 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++; - } - } - } - } - } + // 转换为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]); - 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++; - } + textureAtlas(y, x) = finalColor; + } else { + // 保持背景色 + textureAtlas(y, x) = colEmpty; } } - - remainingPixels -= filled; - iterations++; - - if (filled == 0) { - // 如果无法填充,使用更宽松的条件 - break; - } } - // 填充最后剩余的像素 - if (remainingPixels > 0) { - cv::inpaint(texture, mask, texture, 3, cv::INPAINT_TELEA); + // 7. 第二次遍历:填充缝隙和未采样区域 + DEBUG_EXTRA("Filling gaps in texture atlas"); + FillTextureGapsMultiView(textureAtlas, weightAccum, sampleCount, colEmpty); + + // 8. 应用颜色校正和均匀化 + // ApplyColorCorrection(textureAtlas, 0.5f); // 中等强度的颜色校正 + + // 9. 应用锐化 + if (fSharpnessWeight > 0) { + // ApplySharpening(textureAtlas, fSharpnessWeight); } - DEBUG_EXTRA("Filled %d missing pixels in %d iterations", - cv::countNonZero(weightAccum == 0) - remainingPixels, iterations); + DEBUG_EXTRA("Multi-view texture atlas generation complete"); + return textures; } -float MeshTexture::ComputeViewQuality(FIndex idxFace, int viewID) + +void MeshTexture::ApplySimpleColorCorrection(cv::Mat& imgMat, float strength) { - const Face& face = faces[idxFace]; - const Image& imageData = images[viewID]; + if (strength <= 0) return; - // 计算投影面积 - TexCoord texCoords[3]; - for (int i = 0; i < 3; ++i) { - texCoords[i] = imageData.camera.ProjectPointP(vertices[face[i]]); - } + // 应用伽马校正 + cv::Mat temp; + imgMat.convertTo(temp, CV_32FC3, 1.0/255.0); - // 计算三角形面积 - 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; + // 调整对比度 + cv::Mat adjusted = temp.clone(); + float alpha = 1.0f + strength; // 对比度 + float beta = -0.1f * strength; // 亮度调整 - // 计算视角质量(法线夹角) - const Point3f& faceNormal = scene.mesh.faceNormals[idxFace]; + 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); + } + } - // 计算面中心 - const cv::Point3f faceCenter = (vertices[face[0]] + vertices[face[1]] + vertices[face[2]]) / 3.0f; + // 混合回原图 + cv::addWeighted(temp, 1.0f - strength, adjusted, strength, 0.0, temp); - // 计算相机中心到面中心的方向 - // 注意: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) - ); + // 转换回8位 + temp.convertTo(imgMat, CV_8UC3, 255.0); +} + +// 获取三角形内的所有像素 +std::vector MeshTexture::GetPixelsInTriangle(const Point2f& pt1_, + const Point2f& pt2_, + const Point2f& pt3_, + int textureSize) +{ + std::vector pixels; - const cv::Point3f viewDir = cameraCenter - faceCenter; + // 转换到cv::Point2f + cv::Point2f pt1(pt1_.x, pt1_.y); + cv::Point2f pt2(pt2_.x, pt2_.y); + cv::Point2f pt3(pt3_.x, pt3_.y); - // 归一化 - float viewLen = std::sqrt(viewDir.x * viewDir.x + - viewDir.y * viewDir.y + - viewDir.z * viewDir.z); + // 计算三角形边界框 + 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})); - 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; + // 遍历边界框内的像素 + 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); + } + } } - return 0.0f; + return pixels; } -float MeshTexture::ComputeFaceDistance(FIndex fid1, FIndex fid2) +// 检查点是否在三角形内 +bool MeshTexture::PointInTriangle2(const cv::Point2f& p, + const cv::Point2f& a, + const cv::Point2f& b, + const cv::Point2f& c) { - 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; + // 使用重心坐标法 + 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; - Point3f diff = center2 - center1; - return std::sqrt(diff.x * diff.x + diff.y * diff.y + diff.z * diff.z); + 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; + + return (alpha >= 0 && beta >= 0 && gamma >= 0); } -// 判断面是否在视图中可见 -// 判断面是否在视图中可见 -bool MeshTexture::IsFaceVisibleFromView(FIndex idxFace, int viewID) + + +// 检查点是否在三角形内(2D版本) +bool MeshTexture::PointInTriangle2D(const cv::Point2f& p, + const cv::Point2f& a, + const cv::Point2f& b, + const cv::Point2f& c) { - if (viewID < 0 || viewID >= (int)images.size()) { - return false; - } + // 使用重心坐标法 + 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; - const Face& face = faces[idxFace]; - const Image& imageData = images[viewID]; + 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 (int i = 0; i < 3; ++i) { - const cv::Point3f& vertex = vertices[face[i]]; - - // 将cv::Point3f转换为OpenMVS的Point3类型 - // Camera类使用double精度,但ProjectPointP是模板函数,支持多种类型 - cv::Point3d vertex3d(static_cast(vertex.x), - static_cast(vertex.y), - static_cast(vertex.z)); - - // 使用正确的ProjectPointP函数 - // 从Camera头文件可以看到ProjectPointP返回TPoint2 - cv::Point2d proj = imageData.camera.ProjectPointP(vertex3d); - - // 检查是否在图像范围内(添加边界容差) - 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; - } - - // 可选:检查深度(点在相机前方) - // 可以使用camera.PointDepth函数 - double depth = imageData.camera.PointDepth(vertex3d); - if (depth <= 0) { - return false; // 点在相机后面 - } - } + return (alpha >= 0 && beta >= 0 && gamma >= 0); +} + +#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) +{ + DEBUG_EXTRA("Filling gaps in multi-view texture atlas"); + TD_TIMER_START(); - // 检查面法线和视图方向的夹角 - const cv::Point3f& faceNormal = scene.mesh.faceNormals[idxFace]; + // 确保是3通道8位图像 + if (textureAtlas.type() != CV_8UC3) { + DEBUG_EXTRA("Error: textureAtlas is not CV_8UC3 type"); + return; + } - // 计算面中心 - 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); + int rows = textureAtlas.rows; + int cols = textureAtlas.cols; + int totalPixels = rows * cols; - // 计算相机中心 - const cv::Point3d& cameraCenter = imageData.camera.C; + DEBUG_EXTRA("Starting gap filling for %dx%d texture", cols, rows); - // 计算视图方向 - cv::Point3d viewDir = cameraCenter - faceCenter3d; - double viewLen = cv::norm(viewDir); + // 1. 快速创建有效掩码 + cv::Mat1b validMask(rows, cols, (unsigned char)0); + int validPixels = 0; - if (viewLen > 0) { - // 归一化 - viewDir /= viewLen; - - // 计算法线点积 - double cosAngle = faceNormal.x * viewDir.x + - faceNormal.y * viewDir.y + - faceNormal.z * viewDir.z; + #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); - // 如果夹角太大(接近90度),面可能不可见 - // 使用阈值cos(85°) ≈ 0.087 - if (cosAngle < 0.1) { - return false; + for (int x = 0; x < cols; ++x) { + bool valid = weightRow[x] > 0; + maskRow[x] = valid ? 255 : 0; + if (valid) ++validPixels; } } - return true; -} - -// 优化纹理接缝 -void MeshTexture::OptimizeTextureSeams(const std::vector& textures, - const std::vector>& seamPoints) -{ - DEBUG_EXTRA("Optimizing texture seams"); - - if (textures.empty() || seamPoints.empty()) { - return; - } + 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); - // 对每个纹理,在其边界处进行羽化 - for (size_t i = 0; i < textures.size(); ++i) { - cv::Mat texture = textures[i].clone(); - const std::vector& seams = seamPoints[i]; - - if (seams.empty()) { - continue; - } - - // 创建接缝掩模 - cv::Mat seamMask(texture.size(), CV_8UC1, cv::Scalar(0)); - - 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 (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; - } - } - } - } - } - - // 对掩模进行高斯模糊,创建平滑的过渡 - 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); - - // 计算3x3邻域的平均值 - cv::Vec3f sum(0, 0, 0); + // 2. 快速形态学填充 + if (validRatio > 0.7f) { + cv::Mat1b dilatedMask; + cv::Mat element3 = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3)); + cv::dilate(validMask, dilatedMask, element3); + + int smallGapsFilled = 0; + #pragma omp parallel for schedule(static) reduction(+:smallGapsFilled) + 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); + + for (int x = 0; x < cols; ++x) { + if (dilatedRow[x] && !validRow[x]) { + cv::Vec3b avgColor(0, 0, 0); int count = 0; - for (int dy = -1; dy <= 1; ++dy) { - for (int dx = -1; dx <= 1; ++dx) { + for (int dy = -1; dy <= 1 && count < 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 = -1; dx <= 1 && count < 2; ++dx) { int nx = x + dx; - int ny = y + dy; + if (nx < 0 || nx >= cols) continue; + if (dx == 0 && dy == 0) continue; - if (nx >= 0 && nx < texture.cols && ny >= 0 && ny < texture.rows) { - sum += cv::Vec3f(texture.at(ny, nx)); - ++count; + if (neighborMaskRow[nx]) { + avgColor += neighborTexRow[nx]; + count++; } } } 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) + textureRow[x] = cv::Vec3b( + avgColor[0] / count, + avgColor[1] / count, + avgColor[2] / count ); - texture.at(y, x) = blended; + smallGapsFilled++; } } } } - // 更新纹理 - const_cast(textures[i]) = texture; - } - - 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)}; - } - } + DEBUG_EXTRA("Fast morphology filled %d small gaps", smallGapsFilled); + dilatedMask.copyTo(validMask); } - return {NO_ID, NO_ID}; -} - -void MeshTexture::LocalSeamLevelingEnhanced() -{ - DEBUG_EXTRA("Starting enhanced local seam leveling (simplified)..."); - - // 简化版本:只做最基本的处理,避免内存问题 + // 3. 识别剩余的大缝隙区域 + cv::Mat1b remainingGaps = (validMask == 0); + int remainingGapArea = cv::countNonZero(remainingGaps); - if (seamEdges.empty()) { - DEBUG_EXTRA("No seam edges. Skipping local seam leveling."); + if (remainingGapArea == 0) { + DEBUG_EXTRA("No remaining gaps to fill"); return; } - if (faceTexcoords.GetSize() < faces.GetSize() * 3) { - DEBUG_EXTRA("ERROR: faceTexcoords too small. Cannot perform local seam leveling."); - return; - } + DEBUG_EXTRA("Remaining gaps: %d pixels (%.2f%% of total)", + remainingGapArea, static_cast(remainingGapArea) / totalPixels * 100.0f); - // 简单的平滑处理:对每个接缝边的纹理坐标进行平均 - int processedEdges = 0; + // 4. 使用下采样加速 + int downscaleFactor = 1; + cv::Mat downscaledAtlas, downscaledMask; - 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; + if (cols > 2048 || rows > 2048) { + downscaleFactor = 2; + cv::resize(textureAtlas, downscaledAtlas, + cv::Size(cols / downscaleFactor, rows / downscaleFactor), + 0, 0, cv::INTER_LINEAR); + cv::resize(remainingGaps, downscaledMask, + cv::Size(cols / downscaleFactor, rows / downscaleFactor), + 0, 0, cv::INTER_NEAREST); + cv::threshold(downscaledMask, downscaledMask, 127, 255, cv::THRESH_BINARY); - // 应用平滑 - 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); - } + DEBUG_EXTRA("Downscaled image to %dx%d for faster processing", + cols / downscaleFactor, rows / downscaleFactor); + } else { + textureAtlas.copyTo(downscaledAtlas); + remainingGaps.copyTo(downscaledMask); } - DEBUG_EXTRA("Enhanced local seam leveling completed. Processed %d seam edges.", processedEdges); -} -void MeshTexture::GlobalSeamLevelingEnhanced() -{ - DEBUG_EXTRA("Starting enhanced global seam leveling with memory safety..."); + // 5. 使用分块inpaint + int inpaintRadius = 3; + DEBUG_EXTRA("Starting block-based inpainting (TELEA algorithm, radius=%d)...", inpaintRadius); - if (seamVertices.empty()) { - DEBUG_EXTRA("No seam vertices found. Skipping global seam leveling."); - return; - } + cv::Mat inpaintedDownscaled; - if (seamEdges.empty()) { - DEBUG_EXTRA("No seam edges found. Skipping global seam leveling."); + // 分块处理,避免卡住 + bool success = InpaintWithBlocks(downscaledAtlas, downscaledMask, inpaintedDownscaled, inpaintRadius, 256); + + if (!success) { + DEBUG_EXTRA("Block-based inpaint failed, using fast alternative..."); + FastAlternativeGapFilling(textureAtlas, weightAccum, validMask); return; } - DEBUG_EXTRA("Processing %u seam vertices and %u seam edges", - seamVertices.GetSize(), seamEdges.GetSize()); + DEBUG_EXTRA("Block-based inpainting completed"); - // 1. 构建接缝图 - std::vector> seamGraph(seamVertices.GetSize()); - std::vector seamVertexPositions(seamVertices.GetSize()); + // 6. 上采样结果 + cv::Mat inpaintedFull; + if (downscaleFactor > 1) { + cv::resize(inpaintedDownscaled, inpaintedFull, + cv::Size(cols, rows), 0, 0, cv::INTER_LINEAR); + } else { + inpaintedDownscaled.copyTo(inpaintedFull); + } - // 收集接缝顶点位置 - for (uint32_t i = 0; i < seamVertices.GetSize(); ++i) { - const SeamVertex& sv = seamVertices[i]; - - // 检查顶点索引 - if (sv.idxVertex >= scene.mesh.vertices.size()) { - DEBUG_EXTRA("WARNING: Seam vertex %u has invalid vertex index %u", i, sv.idxVertex); - continue; - } - - // 检查patches是否为空 - if (sv.patches.empty()) { - DEBUG_EXTRA("WARNING: Seam vertex %u has no patches", i); - continue; - } - - // 计算接缝顶点的平均位置 - Point3f pos(0, 0, 0); - int count = 0; - - // 遍历patches - for (uint32_t patchIndex = 0; patchIndex < sv.patches.GetSize(); ++patchIndex) { - const SeamVertex::Patch& patch = sv.patches[patchIndex]; - uint32_t patchID = patch.idxPatch; - - 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++; + // 7. 填充缝隙像素 + int filledPixels = 0; + int updateInterval = std::max(1, rows / 20); + int lastProgress = -1; + + #pragma omp parallel for schedule(static) reduction(+:filledPixels) + for (int y = 0; y < rows; ++y) { + if (y % updateInterval == 0) { + int progress = static_cast(100.0f * y / rows); + if (progress % 5 == 0 && progress != lastProgress) { + #pragma omp critical + { + if (progress != lastProgress) { + lastProgress = progress; + DEBUG_EXTRA("Gap filling progress: %d%%", progress); } } } } - if (count > 0) { - seamVertexPositions[i] = pos / (float)count; - } else { - // 如果无法从关联的面计算位置,使用顶点自身位置 - seamVertexPositions[i] = scene.mesh.vertices[sv.idxVertex]; - } - } - - // 2. 构建接缝边连接 - for (uint32_t i = 0; i < seamEdges.GetSize(); ++i) { - const PairIdx& edge = seamEdges[i]; + const unsigned char* gapRow = remainingGaps.ptr(y); + const float* weightRow = weightAccum.ptr(y); + cv::Vec3b* textureRow = textureAtlas.ptr(y); + const cv::Vec3b* inpaintRow = inpaintedFull.ptr(y); - // 验证边有效性 - if (edge.i >= faces.GetSize() || edge.j >= faces.GetSize()) { - DEBUG_EXTRA("WARNING: Invalid seam edge %u: (%u, %u)", i, edge.i, edge.j); - continue; + for (int x = 0; x < cols; ++x) { + if (gapRow[x] && weightRow[x] <= 0) { + textureRow[x] = inpaintRow[x]; + filledPixels++; + } } - - // 找到边对应的接缝顶点 - // 这里简化处理,实际需要根据顶点位置匹配 - // 可以添加边连接 } - // 3. 构建并求解线性系统 - DEBUG_EXTRA("Building linear system for global seam leveling..."); - - // 这里简化处理,实际实现需要构建线性系统 - // 通常涉及求解以下形式的线性系统:A * x = b - // 其中x是需要求解的顶点位移 - - // 4. 应用全局变换 - int changedCount = 0; - - // 这里简化处理,实际需要应用求解得到的变换 - - DEBUG_EXTRA("Enhanced global seam leveling completed. Changed %d face labels.", changedCount); + DEBUG_EXTRA("Texture gaps filled: %d pixels", filledPixels); + DEBUG_EXTRA("Total gap filling time: %s", TD_TIMER_GET_FMT().c_str()); } - -// 合并重叠的纹理块 -void MeshTexture::MergeOverlappingPatches(Mesh::TexCoordArr& faceTexcoords2) +void MeshTexture::FastAlternativeGapFilling(cv::Mat& textureAtlas, + const cv::Mat1f& weightAccum, + const cv::Mat1b& validMask) { - DEBUG_EXTRA("Merging overlapping texture patches"); - - if (texturePatches.IsEmpty()) { - return; - } + DEBUG_EXTRA("Using fast alternative gap filling method"); - // 按视图分组纹理块 - 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); - } - } + int rows = textureAtlas.rows; + int cols = textureAtlas.cols; - // 对每个视图的纹理块进行合并 - for (auto& viewPatches : patchesByView) { - std::vector& patchIndices = viewPatches.second; - if (patchIndices.size() <= 1) { - continue; // 不需要合并 - } - - // 计算纹理块之间的重叠程度 - std::vector>> overlaps; + #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 < 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; + for (int x = 0; x < cols; ++x) { + if (weightRow[x] <= 0) { // 缝隙像素 + cv::Vec3b avgColor(0, 0, 0); + int count = 0; - // 计算两个纹理块的重叠区域 - 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 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); - if (overlapRatio > 0.1f) { // 重叠超过10% - overlaps.emplace_back(overlapRatio, - std::make_pair(patchIndices[i], patchIndices[j])); + 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++; + } + } + } + + 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 + ); } } } } - - // 按重叠程度排序 - 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); - } - } - - // 移除空的纹理块 - 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("Merged overlapping patches, new count: %d", texturePatches.size()); + DEBUG_EXTRA("Fast alternative gap filling completed"); } -// 打包纹理块 -void MeshTexture::PackTexturePatches(const Mesh::TexCoordArr& faceTexcoords2, - const Mesh::TexIndexArr& faceTexindices2, - std::vector& generatedTextures, - unsigned nTextureSizeMultiple, - unsigned nRectPackingHeuristic, - int maxTextureSize) +// 分块inpaint实现 +bool MeshTexture::InpaintWithBlocks(cv::Mat& image, const cv::Mat1b& mask, + cv::Mat& result, int radius, int blockSize) { - DEBUG_EXTRA("Packing texture patches with enhanced algorithm"); - - if (texturePatches.IsEmpty()) { - DEBUG_EXTRA("No texture patches to pack"); - return; - } + int rows = image.rows; + int cols = image.cols; - // 收集所有需要打包的纹理块 - 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]); - } - } - - if (validPatches.empty()) { - DEBUG_EXTRA("No valid texture patches to pack"); - return; - } + result.create(rows, cols, image.type()); - // 按视图分组纹理块 - std::unordered_map> patchesByView; - for (TexturePatch* patch : validPatches) { - patchesByView[patch->label].push_back(patch); - } + int numBlocksY = (rows + blockSize - 1) / blockSize; + int numBlocksX = (cols + blockSize - 1) / blockSize; + int totalBlocks = numBlocksY * numBlocksX; + int processedBlocks = 0; - // 为每个视图创建一个纹理图集 - generatedTextures.clear(); - std::vector> textureAtlases; + DEBUG_EXTRA("Processing inpaint in %d x %d blocks", numBlocksY, numBlocksX); - for (auto& viewPatches : patchesByView) { - std::vector& patches = viewPatches.second; - - // 对纹理块按大小排序(从大到小) - 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 totalArea = 0; - for (const cv::Rect& rect : rectangles) { - totalArea += rect.area(); - } - - // 估计纹理大小(考虑填充) - int estimatedSize = (int)std::ceil(std::sqrt(totalArea * 1.5f)); - - // 对齐到倍数 - if (nTextureSizeMultiple > 1) { - estimatedSize = ((estimatedSize + nTextureSizeMultiple - 1) / nTextureSizeMultiple) * nTextureSizeMultiple; - } - - // 限制最大大小 - 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]; + #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); - // 检查是否适合当前行 - if (x + rect.width > estimatedSize) { - // 换行 - x = 0; - y += maxRowHeight; - maxRowHeight = 0; - } + cv::Rect blockRect(x1, y1, x2 - x1, y2 - y1); - // 检查是否适合纹理 - if (y + rect.height > estimatedSize) { - // 纹理太小,需要调整 - DEBUG_EXTRA("Texture atlas too small, increasing size"); - // 这里可以增加纹理大小或使用更复杂的打包算法 - break; + 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)); } - // 放置纹理块 - patch->rect.x = x; - patch->rect.y = y; - x += rect.width; + #pragma omp atomic + processedBlocks++; - if (rect.height > maxRowHeight) { - maxRowHeight = rect.height; + 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); + } } } - - generatedTextures.push_back(texture); - textureAtlases.push_back(patches); } - DEBUG_EXTRA("Packed %zu texture atlases", generatedTextures.size()); + return true; } -// 自适应锐化 -void MeshTexture::ApplyAdaptiveSharpening(std::vector& textures, float fSharpnessWeight) +// 辅助函数:使用距离变换加速最近邻填充 +void MeshTexture::FillSmallGapsWithDistanceTransform(cv::Mat& textureAtlas, + const cv::Mat1f& weightAccum) { - if (fSharpnessWeight <= 0.0f || textures.empty()) { - return; + int rows = textureAtlas.rows; + int cols = textureAtlas.cols; + + // 创建有效像素掩码 + 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; + } } - DEBUG_EXTRA("Applying adaptive sharpening to textures (weight: %.2f)", fSharpnessWeight); + // 计算距离变换 + cv::Mat distance; + cv::distanceTransform(~validMask, distance, cv::DIST_L2, cv::DIST_MASK_PRECISE, CV_32F); - for (size_t i = 0; i < textures.size(); ++i) { - cv::Mat& texture = textures[i]; - if (texture.empty()) continue; - - // 计算图像的梯度幅度,用于自适应锐化 - 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); + // 找到最近的有效像素 + 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 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); - - cv::Vec3b& srcPixel = texture.at(y, x); - cv::Vec3b& blurPixel = blurred.at(y, x); - cv::Vec3b& dstPixel = sharpened.at(y, x); + 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; - for (int c = 0; c < 3; ++c) { - int value = static_cast(srcPixel[c] + weight * (srcPixel[c] - blurPixel[c])); - dstPixel[c] = cv::saturate_cast(value); + if (nearestY >= 0 && nearestY < rows && nearestX >= 0 && nearestX < cols) { + textureRow[x] = textureAtlas.at(nearestY, nearestX); } } } - - texture = sharpened; } - - DEBUG_EXTRA("Adaptive sharpening completed"); } -// 填充纹理空洞 -void MeshTexture::FillTextureHoles(std::vector& textures, Pixel8U colEmpty) +// 颜色校正函数 +void MeshTexture::ApplyColorCorrection(cv::Mat& image, float strength) { - if (textures.empty()) { - return; - } + if (strength <= 0) return; - DEBUG_EXTRA("Filling holes in textures"); + DEBUG_EXTRA("Applying color correction (strength: %.2f)", strength); + TD_TIMER_START(); - cv::Scalar emptyColor(colEmpty.r, colEmpty.g, colEmpty.b); + cv::Mat& imgMat = (cv::Mat&)image; - 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; - } - } - } - - 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); - } - } - } + if (strength < 0.3f) { + // 轻微的颜色调整 + ApplySimpleColorCorrection(image, strength); + return; } - DEBUG_EXTRA("Hole filling completed"); -} - -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) -{ - DEBUG_EXTRA("TextureWithExistingUV - 使用3D几何坐标作为桥梁"); - TD_TIMER_START(); + // 1. 转换为LAB颜色空间进行更自然的颜色调整 + cv::Mat labImage; + cv::cvtColor(imgMat, labImage, cv::COLOR_BGR2Lab); - // 1. 验证输入 - if (scene.mesh.faceTexcoords.empty()) { - VERBOSE("error: mesh does not contain UV coordinates"); - return false; - } + // 分离通道 + std::vector labChannels; + cv::split(labImage, labChannels); - if (existingTextures.empty()) { - VERBOSE("error: no existing texture data provided"); - return false; - } + // 2. 对亮度通道进行对比度拉伸 + cv::Mat& luminance = labChannels[0]; - DEBUG_EXTRA("Processing %zu faces with existing texture data", scene.mesh.faces.size()); + // 直方图均衡化 + cv::equalizeHist(luminance, luminance); - // 2. 为每个面选择最佳视图(从原始图像,而不是已有纹理) - FaceDataViewArr facesDatas; - if (!ListCameraFaces(facesDatas, fOutlierThreshold, nIgnoreMaskLabel, views, false)) { - 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); - // 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; - } + // 混合原始和平滑后的颜色通道 + cv::addWeighted(labChannels[1], 1.0f - strength, aChannel, strength, 0, labChannels[1]); + cv::addWeighted(labChannels[2], 1.0f - strength, bChannel, strength, 0, labChannels[2]); - // 4. 生成纹理图集 - Mesh::Image8U3Arr generatedTextures = GenerateTextureAtlasWith3DBridge( - faceLabels, views, existingTextures, existingTexcoords, existingTexindices, - nTextureSizeMultiple, colEmpty, fSharpnessWeight - ); + // 4. 合并通道 + cv::merge(labChannels, labImage); - 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; - } + // 5. 转换回BGR + cv::cvtColor(labImage, imgMat, cv::COLOR_Lab2BGR); - DEBUG_EXTRA("Texture generation failed"); - return false; + // 6. 应用自动颜色平衡 + ApplyAutoWhiteBalance(imgMat, strength * 0.5f); + + DEBUG_EXTRA("Color correction completed in %s", TD_TIMER_GET_FMT().c_str()); } -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) +void MeshTexture::ApplySharpening(Image8U3& image, float strength) { - DEBUG_EXTRA("GenerateTextureAtlasWith3DBridge - 使用3D几何坐标作为桥梁"); - - // 1. 分析外部UV布局 - AABB2f uvBounds(true); - FOREACH(i, scene.mesh.faceTexcoords) { - const TexCoord& uv = scene.mesh.faceTexcoords[i]; - uvBounds.InsertFull(uv); - } - - // 确保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); - } - } - } + if (strength <= 0) return; - // 计算纹理尺寸 - 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); + DEBUG_EXTRA("Applying sharpening (strength: %.2f)", strength); + TD_TIMER_START(); -// 创建目标纹理 - Mesh::Image8U3Arr textures; - Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); - // 注意:cv::Scalar 使用 BGR 顺序 - textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); + cv::Mat& imgMat = (cv::Mat&)image; - DEBUG_EXTRA("生成纹理图集: 尺寸=%dx%d, UV范围=[%.3f,%.3f]-[%.3f,%.3f]", - textureSize, textureSize, - uvBounds.ptMin.x(), uvBounds.ptMin.y(), - uvBounds.ptMax.x(), uvBounds.ptMax.y()); + // 1. 高斯模糊 + cv::Mat blurred; + cv::GaussianBlur(imgMat, blurred, cv::Size(0, 0), 3); - // 添加调试信息 - DEBUG_EXTRA("colEmpty: R=%d, G=%d, B=%d", colEmpty.r, colEmpty.g, colEmpty.b); - DEBUG_EXTRA("OpenCV图像通道顺序: BGR"); + // 2. 计算边缘掩码 + cv::Mat gray, edges; + cv::cvtColor(imgMat, gray, cv::COLOR_BGR2GRAY); + cv::Laplacian(gray, edges, CV_16S, 3); + cv::convertScaleAbs(edges, edges); - // 检查颜色通道顺序 - 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]); - } + // 3. 创建锐化掩码 + cv::Mat sharpenMask; + cv::threshold(edges, sharpenMask, 10, 255, cv::THRESH_BINARY); - // 3. 统计信息 - int processedFaces = 0; - int sampledPixels = 0; - int failedFaces = 0; + // 4. 对边缘区域应用反锐化掩蔽 + cv::Mat sharpened = imgMat.clone(); - // 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); - - // 计算重心坐标 - 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; + for (int y = 0; y < imgMat.rows; ++y) { + for (int x = 0; x < imgMat.cols; ++x) { + if (sharpenMask.at(y, x) > 0) { + // 反锐化掩蔽公式: sharpened = original + (original - blurred) * strength + cv::Vec3b origPixel = imgMat.at(y, x); + cv::Vec3b blurPixel = blurred.at(y, x); - // 采样四个点的颜色 - 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]); + 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); } - faceSampledPixels++; + sharpened.at(y, x) = sharpPixel; } } - - if (faceSampledPixels > 0) { - processedFaces++; - sampledPixels += faceSampledPixels; - } else { - failedFaces++; - } } - DEBUG_EXTRA("纹理采样完成: 成功 %d 个面, 失败 %d 个面, 采样 %d 像素", - processedFaces, failedFaces, sampledPixels); + // 5. 混合锐化后的图像 + cv::addWeighted(imgMat, 1.0f - strength, sharpened, strength, 0, imgMat); - // 5. 填充空洞 - if (processedFaces > 0 && sampledPixels > 0) { - // FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), textureSize, colEmpty); + // 6. 可选的边缘增强 + if (strength > 0.5f) { + cv::Mat kernel = (cv::Mat_(3,3) << + -1, -1, -1, + -1, 9, -1, + -1, -1, -1); + cv::filter2D(imgMat, imgMat, -1, kernel); } - // 6. 锐化处理 - if (fSharpnessWeight > 0) { - // ApplySharpening(textureAtlas, fSharpnessWeight); + 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); + } } - return textures; + // 5. 混合回原图 + cv::addWeighted(image, 1.0f - strength, balanced, strength, 0, image); } Point2f MeshTexture::ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage) { @@ -14404,225 +10988,301 @@ void GenerateDistanceFieldFast(cv::Mat& distanceField, cv::Mat& nearestFaceIdx, } } -void FillTextureGaps( - Image8U3& texture, - const Mesh::TexCoordArr& uvs, - FIndex faceCount, - int textureSize, - Pixel8U colEmpty -) +void FillTextureGaps(Image8U3& textureAtlas, const Mesh::TexCoordArr& faceTexcoords, + FIndex nFaces, const MeshTexture::LabelArr& faceLabels, + int textureSize, Pixel8U colEmpty) { - DEBUG_EXTRA("Filling texture gaps for %dx%d texture", texture.cols, texture.rows); + DEBUG_EXTRA("开始填充纹理间隙..."); - // 创建掩码图像 - cv::Mat mask = cv::Mat::zeros(texture.rows, texture.cols, CV_8UC1); + // 创建距离场 + cv::Mat distanceField(textureSize, textureSize, CV_32FC1, cv::Scalar(std::numeric_limits::max())); + cv::Mat nearestPixelIdx(textureSize, textureSize, CV_32SC1, cv::Scalar(-1)); - // 标记有纹理的像素 - 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); + // 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; + } } - - // 填充三角形区域 - 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); + } + + // 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); - // 检查是否在UV三角形内 - Point3f barycentric; - if (PointInTriangle(texPos, uv[0], uv[1], uv[2], barycentric)) { - mask.at(y, x) = 255; + for (int dy = -1; dy <= 1; dy += 2) { + for (int dx = -1; dx <= 1; dx += 2) { + int nx = x + dx * step; + int ny = y + dy * step; + + if (nx >= 0 && nx < textureSize && ny >= 0 && ny < textureSize) { + float neighborDist = distanceField.at(ny, nx); + float dist = neighborDist + sqrtf((dx*dx + dy*dy) * step * step); + + if (dist < minDist && nearestPixelIdx.at(ny, nx) != -1) { + minDist = dist; + bestIdx = nearestPixelIdx.at(ny, nx); + } + } + } + } + + if (bestIdx != -1 && minDist < distanceField.at(y, x)) { + distanceField.at(y, x) = minDist; + nearestPixelIdx.at(y, x) = bestIdx; } } } } - // 使用膨胀操作扩展掩码 - cv::Mat dilatedMask; - cv::dilate(mask, dilatedMask, cv::Mat(), cv::Point(-1, -1), 2); + // 3. 填充空白区域 + const float maxFillDistance = 5.0f; + int filledCount = 0; - // 对空洞区域进行填充 - cv::inpaint(texture, mask, texture, 3, cv::INPAINT_TELEA); + for (int y = 0; y < textureSize; ++y) { + for (int x = 0; x < textureSize; ++x) { + Pixel8U& pixel = textureAtlas(y, x); + if (pixel[0] == colEmpty[0] && pixel[1] == colEmpty[1] && pixel[2] == colEmpty[2]) { + int nearestIdx = nearestPixelIdx.at(y, x); + float dist = distanceField.at(y, x); + + if (nearestIdx != -1 && dist <= maxFillDistance) { + int nx = nearestIdx % textureSize; + int ny = nearestIdx / textureSize; + pixel = textureAtlas(ny, nx); + filledCount++; + } + } + } + } - DEBUG_EXTRA("Texture gap filling completed"); -} - -void ApplySharpening(Image8U3& texture, float weight) -{ - if (weight <= 0) return; + 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); + // } + // } - Image8U3 blurred; - cv::GaussianBlur(texture, blurred, cv::Size(0, 0), 1.5); - cv::addWeighted(texture, 1.0 + weight, blurred, -weight, 0, texture); + DEBUG_EXTRA("纹理间隙填充完成"); } -Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV( - const Mesh::Image8U3Arr& sourceTextures, // 已有纹理数组 - const Mesh::TexCoordArr& sourceTexcoords, // 已有UV坐标 - const Mesh::TexIndexArr& sourceTexindices, // 已有纹理索引 - unsigned nTextureSizeMultiple, - Pixel8U colEmpty, - float fSharpnessWeight -) +Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLabels, const IIndexArr& views, + unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) { - DEBUG_EXTRA("Generating texture atlas from existing UV data with %zu source textures", - sourceTextures.size()); - - // 1. 分析模型原始UV布局 + // 1. 分析整个模型的UV布局 AABB2f uvBounds(true); FOREACH(i, scene.mesh.faceTexcoords) { const TexCoord& uv = scene.mesh.faceTexcoords[i]; uvBounds.InsertFull(uv); } - // 确保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); + // 2. 根据UV范围确定纹理图集尺寸 + const int textureSize = ComputeOptimalTextureSize(uvBounds.ptMin, uvBounds.ptMax, nTextureSizeMultiple); - // 2. 创建单个纹理图集 + // 3. 创建单个纹理图集 Mesh::Image8U3Arr textures; Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); - DEBUG_EXTRA("Creating texture atlas: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]", + DEBUG_EXTRA("生成纹理图集: 尺寸=%dx%d, UV范围=[%.3f,%.3f]-[%.3f,%.3f]", textureSize, textureSize, - uvBounds.ptMin.x(), uvBounds.ptMin.y(), - uvBounds.ptMax.x(), uvBounds.ptMax.y()); - - // 3. 为每个面采样颜色 - int processedFaces = 0; - int failedFaces = 0; + uvBounds.ptMin.x(), uvBounds.ptMin.y(), uvBounds.ptMax.x(), uvBounds.ptMax.y()); + // 4. 为每个面片采样颜色并填充纹理图集 #ifdef _USE_OPENMP - #pragma omp parallel for schedule(dynamic) reduction(+:processedFaces, failedFaces) - #endif + #pragma omp parallel for schedule(dynamic) for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) { + #else + FOREACH(idxFace, scene.mesh.faces) { + #endif const FIndex faceID = (FIndex)idxFace; - const Face& face = scene.mesh.faces[faceID]; - - // 获取模型原始UV坐标 - const TexCoord* modelUVs = &scene.mesh.faceTexcoords[faceID * 3]; - - // 获取对应source纹理中的UV坐标 - const TexCoord* sourceUVs = &sourceTexcoords[faceID * 3]; - const TexIndex textureIdx = sourceTexindices.empty() ? 0 : sourceTexindices[faceID]; + const Label label = faceLabels[faceID]; + if (label == 0) continue; // 跳过无视图的面片 - if (textureIdx >= sourceTextures.size()) { - failedFaces++; - continue; - } + const IIndex idxView = label - 1; + if (idxView >= images.size()) continue; - const Image8U3& sourceTexture = sourceTextures[textureIdx]; + // 获取面的UV坐标和几何信息 + const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; + const Face& face = scene.mesh.faces[faceID]; + const Image& sourceImage = images[idxView]; - // 计算面片在目标纹理中的UV边界 + // 计算面片在纹理图集中的边界框 AABB2f faceUVBounds(true); for (int i = 0; i < 3; ++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)); + faceUVBounds.InsertFull(uvCoords[i]); } - // 转换为像素坐标 + // 将UV坐标转换到纹理像素坐标 const int startX = std::max(0, (int)(faceUVBounds.ptMin.x() * textureSize)); const int startY = std::max(0, (int)(faceUVBounds.ptMin.y() * textureSize)); const int endX = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.x() * textureSize)); const int endY = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.y() * textureSize)); - if (startX > endX || startY > endY) { - failedFaces++; - continue; - } - - // 对面片覆盖的每个像素进行采样 + // 对面片覆盖的每个纹理像素进行采样 for (int y = startY; y <= endY; ++y) { for (int x = startX; x <= endX; ++x) { const Point2f texCoord((float)x / textureSize, (float)y / textureSize); - // 1. 检查是否在模型UV三角形内 + // 1. 检查是否在三角形内 Point3f barycentric; - 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; + 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; + } } - - // 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; + // 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; + } + } + } + } } - - // 3. 从source纹理中采样颜色 - const cv::Vec3b color = sourceTexture.at( - (int)sourceTexCoord.y, (int)sourceTexCoord.x); - - // 4. 写入目标纹理 - textureAtlas.at(y, x) = color; } } - - processedFaces++; } - 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); - } + // 5. 添加后处理填充函数 + FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), faceLabels, textureSize, colEmpty); - // 5. 应用锐化 + // 6. 应用后处理 if (fSharpnessWeight > 0) { // ApplySharpening(textureAtlas, fSharpnessWeight); } - DEBUG_EXTRA("Generated texture atlas: %dx%d from %zu source textures", - textureSize, textureSize, sourceTextures.size()); + DEBUG_EXTRA("纹理图集生成完成: %u个面片, 纹理尺寸%dx%d", + scene.mesh.faces.size(), textureSize, textureSize); return textures; } + bool MeshTexture::ValidateProjection(const Vertex& worldPoint, const Image& sourceImage, Point2f imgPoint, float maxReprojectionError) { @@ -14857,32 +11517,70 @@ bool MeshTexture::PointInTriangle(const Point2f& p, const Point2f& a, const Poin return false; } -// 辅助函数:计算最佳纹理尺寸 -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); +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; // 默认回退尺寸 } - return size; + // 2. 基于UV覆盖范围计算基础纹理尺寸 + // 假设我们希望纹理密度为:每单位UV空间对应N个像素 + const float pixelsPerUV = 256.0f; // 可配置的密度系数 + int baseSizeX = static_cast(uvRangeX * pixelsPerUV); + int baseSizeY = static_cast(uvRangeY * pixelsPerUV); + + // 取较大者作为基础尺寸,以确保覆盖整个UV范围 + int baseSize = std::max(baseSizeX, baseSizeY); + + DEBUG_EXTRA("%d, %d", nTextureSizeMultiple, baseSize); + // 3. 应用纹理尺寸倍数约束 + baseSize = (baseSize + nTextureSizeMultiple - 1) / nTextureSizeMultiple * nTextureSizeMultiple; + + // 4. 设备能力限制 + int maxTextureSize = 4096; // 默认最大值 + int minTextureSize = 64; // 默认最小值 + + // 此处可查询设备实际支持的最大纹理尺寸 + // glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + + // 5. 质量等级调整(可根据OPT中的质量设置进行调整) + int qualityLevel = 1; // 默认中等质量 + switch (qualityLevel) { + case 0: // 低质量:缩小纹理尺寸 + baseSize = baseSize / 2; + break; + case 1: // 中等质量:保持原计算尺寸 + // baseSize 保持不变 + break; + case 2: // 高质量:适度增大尺寸(如有足够内存) + baseSize = std::min(baseSize * 2, maxTextureSize); + break; + } + + DEBUG_EXTRA("(UV范围: [%.3f, %.3f]), %d, %d, %d", uvRangeX, uvRangeY, nTextureSizeMultiple, baseSize, minTextureSize); + // 6. 最终钳制到有效范围 + baseSize = (std::max)(minTextureSize, (std::min)(baseSize, maxTextureSize)); + + // 7. 确保尺寸为2的幂次(兼容性考虑,可选) + int finalSize = 1; + while (finalSize < baseSize) { + finalSize <<= 1; + if (finalSize >= maxTextureSize) { + finalSize = maxTextureSize; + break; + } + } + + DEBUG_EXTRA("计算出的最优纹理尺寸: %d (UV范围: [%.3f, %.3f])", + finalSize, uvRangeX, uvRangeY); + + return finalSize; } // 保存遮挡数据到文件 @@ -15181,59 +11879,31 @@ 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); - - // 2. 使用预计算UV模式加载网格 - if (!mesh.Load(MAKE_PATH_SAFE(strUVMeshFileName), true)) { - VERBOSE("error: cannot load mesh file with UV coordinates"); - return false; - } - + // 使用预计算UV模式 + if (!mesh.Load(MAKE_PATH_SAFE(strUVMeshFileName), true)) { + VERBOSE("error: cannot load mesh file with UV coordinates"); + return false; // 注意:在成员函数中,返回 false 表示失败 + } + VERBOSE("2faceTexcoords.size=%d, faces.size=%d", mesh.faceTexcoords.size(), mesh.faces.size() * 3); - - 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(); + //* + // 确保网格包含UV坐标 + if (mesh.faceTexcoords.empty()) { + VERBOSE("error: the specified mesh does not contain UV coordinates"); + return false; + } + + // 使用新的纹理生成方法 + MeshTexture texture(*this, nResolutionLevel, nMinResolution); + if (!texture.TextureWithExistingUV(views, nIgnoreMaskLabel, fOutlierThreshold, nTextureSizeMultiple, colEmpty, fSharpnessWeight)){ + return false; + } + //*/ + + return true; + } // mesh.CheckUVValid();