From 84a492600fe3cd28f64d44a81d1c1fc01f1cbdc3 Mon Sep 17 00:00:00 2001 From: hesuicong Date: Tue, 14 Apr 2026 14:33:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=AD=E9=97=B4=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 193 ++++++++++++++++++++++++++++++++++---- 1 file changed, 176 insertions(+), 17 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 5523ebb..e1f7efc 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -483,6 +483,9 @@ public: void GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& baseFileName); bool TextureWithExistingUV(const IIndexArr& views, int nIgnoreMaskLabel, float fOutlierThreshold, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight); Mesh::Image8U3Arr GenerateTextureAtlasFromUV(const LabelArr& faceLabels, const IIndexArr& views, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight); + Mesh::Image8U3Arr GenerateTextureAtlasFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, int textureSize, Pixel8U colEmpty, float fSharpnessWeight); + Mesh::Image8U3Arr GenerateTextureAtlasFromImages(const LabelArr& faceLabels, const IIndexArr& views, int textureSize, Pixel8U colEmpty, float fSharpnessWeight); + Pixel8U SampleFromExistingTextureAtBarycentric(FIndex faceID, const Point3f& barycentric, const Image8U3& existingTexture); 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); Mesh::Image8U3Arr SampleFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight); @@ -10604,14 +10607,7 @@ void FillTextureGaps(Image8U3& textureAtlas, const Mesh::TexCoordArr& faceTexcoo Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLabels, const IIndexArr& views, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) { - // 如果已经有生成的纹理,直接返回 - if (!scene.mesh.texturesDiffuse.empty()) { - DEBUG_EXTRA("GenerateTextureAtlasFromUV: 使用已生成的纹理,数量: %zu", scene.mesh.texturesDiffuse.size()); - return scene.mesh.texturesDiffuse; - } - - // 否则,重新生成纹理 - DEBUG_EXTRA("GenerateTextureAtlasFromUV: 从图像重新生成纹理"); + DEBUG_EXTRA("GenerateTextureAtlasFromUV"); // 1. 分析整个模型的UV布局 AABB2f uvBounds(true); @@ -10632,7 +10628,144 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLa textureSize, textureSize, uvBounds.ptMin.x(), uvBounds.ptMin.y(), uvBounds.ptMax.x(), uvBounds.ptMax.y()); - // 4. 为每个面片采样颜色并填充纹理图集 + // 4. 检查是否有已生成的纹理可以使用 + bool useExistingTextures = !scene.mesh.texturesDiffuse.empty() && + !scene.mesh.faceTexindices.empty() && + scene.mesh.texturesDiffuse.size() == 1; // 假设只有一个纹理图集 + + if (useExistingTextures) { + DEBUG_EXTRA("从已有纹理采样生成新的纹理布局"); + return GenerateTextureAtlasFromExistingTextures(faceLabels, views, textureSize, colEmpty, fSharpnessWeight); + } + + // 5. 否则,从原始图像重新生成纹理 + DEBUG_EXTRA("从原始图像重新生成纹理图集"); + return GenerateTextureAtlasFromImages(faceLabels, views, textureSize, colEmpty, fSharpnessWeight); +} + +Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, + int textureSize, Pixel8U colEmpty, float fSharpnessWeight) +{ + Mesh::Image8U3Arr textures; + Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); + textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); + + // 获取已有的纹理 + const Image8U3& existingTexture = scene.mesh.texturesDiffuse[0]; + + // 为每个面片从已有纹理采样 + #ifdef _USE_OPENMP + #pragma omp parallel for schedule(dynamic) + for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) { + #else + FOREACH(idxFace, scene.mesh.faces) { + #endif + const FIndex faceID = (FIndex)idxFace; + const Label label = faceLabels[faceID]; + if (label == 0) continue; // 跳过无视图的面片 + + // 获取面的UV坐标 + const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; + + // 计算面片在纹理图集中的边界框 + AABB2f faceUVBounds(true); + for (int i = 0; i < 3; ++i) { + faceUVBounds.InsertFull(uvCoords[i]); + } + + // 将UV坐标转换到纹理像素坐标 + const int startX = std::max(0, (int)(faceUVBounds.ptMin.x() * textureSize)); + const int startY = std::max(0, (int)(faceUVBounds.ptMin.y() * textureSize)); + const int endX = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.x() * textureSize)); + const int endY = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.y() * textureSize)); + + // 对面片覆盖的每个纹理像素进行采样 + for (int y = startY; y <= endY; ++y) { + for (int x = startX; x <= endX; ++x) { + const Point2f texCoord((float)x / textureSize, (float)y / textureSize); + + // 1. 检查是否在三角形内 + Point3f barycentric; + if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { + // 从已有纹理中采样颜色 + Pixel8U sampledColor = SampleFromExistingTextureAtBarycentric(faceID, barycentric, existingTexture); + + // 将采样颜色写入纹理图集 + #ifdef _USE_OPENMP + #pragma omp critical + #endif + { + textureAtlas(y, x) = sampledColor; + } + } + // 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) { + + // 从已有纹理中采样颜色 + Pixel8U sampledColor = SampleFromExistingTextureAtBarycentric(faceID, edgeBarycentric, existingTexture); + + #ifdef _USE_OPENMP + #pragma omp critical + #endif + { + textureAtlas(y, x) = sampledColor; + } + } + } + } + } + } + } + + // 添加后处理填充函数 + FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), faceLabels, textureSize, colEmpty); + + // 应用后处理 + if (fSharpnessWeight > 0) { + // ApplySharpening(textureAtlas, fSharpnessWeight); + } + + DEBUG_EXTRA("从已有纹理生成纹理图集完成: 尺寸%dx%d, %u个面片", + textureSize, textureSize, scene.mesh.faces.size()); + + return textures; +} + +Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromImages(const LabelArr& faceLabels, const IIndexArr& views, + int textureSize, Pixel8U colEmpty, float fSharpnessWeight) +{ + Mesh::Image8U3Arr textures; + Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); + textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); + + // 为每个面片从原始图像采样颜色 #ifdef _USE_OPENMP #pragma omp parallel for schedule(dynamic) for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) { @@ -10671,7 +10804,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLa // 1. 检查是否在三角形内 Point3f barycentric; if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { - // 标准内部采样 // 计算3D空间中的对应点 const Vertex worldPoint = vertices[face[0]] * barycentric.x + @@ -10689,9 +10821,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLa // 检查投影是否在图像边界内 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; } @@ -10769,20 +10898,50 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLa } } - // 5. 添加后处理填充函数 + // 添加后处理填充函数 FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), faceLabels, textureSize, colEmpty); - // 6. 应用后处理 + // 应用后处理 if (fSharpnessWeight > 0) { // ApplySharpening(textureAtlas, fSharpnessWeight); } - DEBUG_EXTRA("纹理图集生成完成: %u个面片, 纹理尺寸%dx%d", - scene.mesh.faces.size(), textureSize, textureSize); + DEBUG_EXTRA("从原始图像生成纹理图集完成: 尺寸%dx%d, %u个面片", + textureSize, textureSize, scene.mesh.faces.size()); return textures; } +Pixel8U MeshTexture::SampleFromExistingTextureAtBarycentric(FIndex faceID, const Point3f& barycentric, const Image8U3& existingTexture) +{ + // 获取面在已有纹理中的UV坐标 + // 注意:这里假设每个面在已有纹理中也有对应的UV坐标 + // 我们需要从 scene.mesh.faceTexcoords 中获取这些UV坐标 + + if (faceID * 3 + 2 >= scene.mesh.faceTexcoords.size()) { + return Pixel8U(0, 0, 0); + } + + const TexCoord* existingUVs = &scene.mesh.faceTexcoords[faceID * 3]; + + // 使用重心坐标计算采样点的UV坐标 + Point2f texCoord( + existingUVs[0].x * barycentric.x + + existingUVs[1].x * barycentric.y + + existingUVs[2].x * barycentric.z, + existingUVs[0].y * barycentric.x + + existingUVs[1].y * barycentric.y + + existingUVs[2].y * barycentric.z + ); + + // 确保UV坐标在[0,1]范围内 + texCoord.x = std::max(0.0f, std::min(1.0f, texCoord.x)); + texCoord.y = std::max(0.0f, std::min(1.0f, texCoord.y)); + + // 从已有纹理中采样 + return SampleTextureBilinear(existingTexture, texCoord); +} + Mesh::Image8U3Arr MeshTexture::SampleFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) {