From d82e3f491a812da8e38d0eb742d388681740d471 Mon Sep 17 00:00:00 2001 From: hesuicong Date: Wed, 15 Apr 2026 16:48:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86=E5=9D=90=E6=A0=87=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 381 ++++++++++++++++++++------------------ 1 file changed, 201 insertions(+), 180 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index fc53249..6e0311d 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -481,25 +481,8 @@ public: 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); 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 // 添加已有纹理索引参数 - ); - 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); 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); @@ -9946,19 +9929,11 @@ bool SaveGeneratedTextures(const Mesh::Image8U3Arr& generatedTextures, const std return true; } -bool MeshTexture::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 MeshTexture::TextureWithExistingUV(const IIndexArr& views, int nIgnoreMaskLabel, + float fOutlierThreshold, unsigned nTextureSizeMultiple, + Pixel8U colEmpty, float fSharpnessWeight) { - DEBUG_EXTRA("TextureWithExistingUV - Using existing texture data"); + DEBUG_EXTRA("TextureWithExistingUV 1"); TD_TIMER_START(); // 1. 验证输入 @@ -9967,50 +9942,69 @@ bool MeshTexture::TextureWithExistingUV( return false; } - if (existingTextures.empty() || existingTexcoords.empty()) { - VERBOSE("error: no existing texture data provided"); + 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; } - // 验证UV坐标数量匹配 - if (existingTexcoords.size() != scene.mesh.faceTexcoords.size()) { - VERBOSE("error: UV coordinate count mismatch: existing=%zu, mesh=%zu", - existingTexcoords.size(), scene.mesh.faceTexcoords.size()); + DEBUG_EXTRA("TextureWithExistingUV 2"); + + // 2. 为每个面选择最佳视图 + FaceDataViewArr facesDatas; + if (!ListCameraFaces(facesDatas, fOutlierThreshold, nIgnoreMaskLabel, views, false)) { return false; } - DEBUG_EXTRA("Processing %zu faces with existing texture data", scene.mesh.faces.size()); + DEBUG_EXTRA("TextureWithExistingUV 3 faceSize=%d", scene.mesh.faces.size()); - // 2. 生成纹理图集(从已有纹理采样,但使用模型原始UV) - Mesh::Image8U3Arr generatedTextures = GenerateTextureAtlasFromUV( - existingTextures, - existingTexcoords, - existingTexindices, - nTextureSizeMultiple, - colEmpty, - fSharpnessWeight - ); + // 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; + } + // 4. 生成纹理图集(使用模型原始UV坐标) + Mesh::Image8U3Arr generatedTextures = GenerateTextureAtlasFromUV( + faceLabels, views, nTextureSizeMultiple, colEmpty, fSharpnessWeight); + if (!generatedTextures.empty()) { 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(); + // 同时设置面的纹理索引 + scene.mesh.faceTexindices.resize(scene.mesh.faces.size()); + for (size_t i = 0; i < scene.mesh.faces.size(); ++i) { + scene.mesh.faceTexindices[i] = 0; // 所有面使用第一个纹理 } + + DEBUG_EXTRA("成功生成 %zu 个纹理,已赋值给 mesh", scene.mesh.texturesDiffuse.size()); - DEBUG_EXTRA("Generated %zu textures from existing data", - scene.mesh.texturesDiffuse.size()); + // 保存纹理到文件 + std::string outputDir = "texture_output"; + if (SaveGeneratedTextures(scene.mesh.texturesDiffuse, outputDir)) { + DEBUG_EXTRA("纹理已成功保存到目录: %s", outputDir.c_str()); + } return true; } - DEBUG_EXTRA("Texture generation failed"); + DEBUG_EXTRA("纹理生成失败"); return false; } @@ -10456,159 +10450,179 @@ void ApplySharpening(Image8U3& texture, float weight) cv::addWeighted(texture, 1.0 + weight, blurred, -weight, 0, texture); } -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); - } - } - } - - // 计算纹理尺寸 + // 2. 根据UV范围确定纹理图集尺寸 + // 修正这里:计算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. 创建单个纹理图集 + // 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], UV宽高=[%.3f,%.3f]", textureSize, textureSize, uvBounds.ptMin.x(), uvBounds.ptMin.y(), - uvBounds.ptMax.x(), uvBounds.ptMax.y()); - - // 3. 为每个面采样颜色 - int processedFaces = 0; - int failedFaces = 0; + uvBounds.ptMax.x(), uvBounds.ptMax.y(), + uvWidth, uvHeight); + // 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]; + const Label label = faceLabels[faceID]; + if (label == 0) continue; // 跳过无视图的面片 - // 获取模型原始UV坐标 - const TexCoord* modelUVs = &scene.mesh.faceTexcoords[faceID * 3]; + const IIndex idxView = label - 1; + if (idxView >= images.size()) continue; - // 获取对应source纹理中的UV坐标 - const TexCoord* sourceUVs = &sourceTexcoords[faceID * 3]; - const TexIndex textureIdx = sourceTexindices.empty() ? 0 : sourceTexindices[faceID]; - - if (textureIdx >= sourceTextures.size()) { - failedFaces++; - continue; - } - - const Image8U3& sourceTexture = sourceTextures[textureIdx]; + // 获取面的UV坐标和几何信息 + 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; + if (minDist <= edgeThreshold * 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); + // 5. 添加后处理填充函数 + // 修正这里:移除多余的 faceLabels 参数 + // FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), textureSize, colEmpty); - // 4. 填充纹理空隙 - if (processedFaces > 0) { - // FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), 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; } @@ -11170,37 +11184,44 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi // mesh.CheckUVValid(); DEBUG_EXTRA("TextureMesh %b, %s", bUseExistingUV, strUVMeshFileName.c_str()); - if (bUseExistingUV && !strUVMeshFileName.empty()) { - texture.GenerateTextureForUV(bGlobalSeamLeveling, bLocalSeamLeveling, nTextureSizeMultiple, nRectPackingHeuristic, colEmpty, fSharpnessWeight, maxTextureSize, baseFileName, bOriginFaceview, this); - - // 保存生成的纹理数据 - Mesh::Image8U3Arr existingTextures = texture.texturesDiffuseTemp; - Mesh::TexCoordArr existingTexcoords = mesh.faceTexcoords; // 保存临时计算的UV - Mesh::TexIndexArr existingTexindices = mesh.faceTexindices; // 保存纹理索引 - - VERBOSE("1faceTexcoords.size=%d, faces.size=%d", mesh.faceTexcoords.size(), mesh.faces.size() * 3); + if (bUseExistingUV && !strUVMeshFileName.empty()) { + // 1. 先加载包含原始UV坐标的网格 + VERBOSE("1faceTexcoords.size=%d, faces.size=%d", mesh.faceTexcoords.size(), mesh.faces.size() * 3); + // 使用预计算UV模式 if (!mesh.Load(MAKE_PATH_SAFE(strUVMeshFileName), true)) { VERBOSE("error: cannot load mesh file with UV coordinates"); - return false; // 注意:在成员函数中,返回 false 表示失败 + return false; } - VERBOSE("2faceTexcoords.size=%d, faces.size=%d", mesh.faceTexcoords.size(), mesh.faces.size() * 3); - // mesh.CheckUVValid(); - //* + VERBOSE("2faceTexcoords.size=%d, faces.size=%d", mesh.faceTexcoords.size(), mesh.faces.size() * 3); + // 确保网格包含UV坐标 if (mesh.faceTexcoords.empty()) { VERBOSE("error: the specified mesh does not contain UV coordinates"); return false; } - // 使用新的纹理生成方法 - MeshTexture texture2(*this, nResolutionLevel, nMinResolution); - if (!texture2.TextureWithExistingUV(views, nIgnoreMaskLabel, fOutlierThreshold, nTextureSizeMultiple, colEmpty, fSharpnessWeight, existingTextures, existingTexcoords, existingTexindices)){ + // 2. 使用原有的正常方法,但保持加载的UV坐标不变 + MeshTexture texture(*this, nResolutionLevel, nMinResolution); + + // 关键修改:确保纹理类使用当前网格的UV坐标 + texture.scene.mesh.faceTexcoords = mesh.faceTexcoords; // 使用原始UV坐标 + + if (!texture.TextureWithExistingUV(views, nIgnoreMaskLabel, fOutlierThreshold, + nTextureSizeMultiple, colEmpty, fSharpnessWeight)) { return false; } - //*/ - + + // 将生成的纹理赋值回场景网格 + mesh.texturesDiffuse = std::move(texture.scene.mesh.texturesDiffuse); + if (!mesh.texturesDiffuse.empty()) { + mesh.faceTexindices.resize(mesh.faces.size()); + for (size_t i = 0; i < mesh.faces.size(); ++i) { + mesh.faceTexindices[i] = 0; // 所有面使用第一个纹理 + } + } + return true; }