From f787e874aa7b5fc8d24fd6fe5bc24df138d9a197 Mon Sep 17 00:00:00 2001 From: hesuicong Date: Fri, 17 Apr 2026 10:30:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=9E=E9=80=80=E5=88=B0=E4=B8=8A=E4=B8=AA?= =?UTF-8?q?=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 | 378 +++++++++++++++++++++++++++++++++----- 1 file changed, 329 insertions(+), 49 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 09643f0..f0c2c65 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -492,6 +492,16 @@ public: const Mesh::TexCoordArr& existingTexcoords, // 添加已有UV参数 const Mesh::TexIndexArr& existingTexindices // 添加已有纹理索引参数 ); + 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坐标 @@ -9945,6 +9955,26 @@ bool SaveGeneratedTextures(const Mesh::Image8U3Arr& generatedTextures, const std 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::TextureWithExistingUV( const IIndexArr& views, @@ -9953,12 +9983,11 @@ bool MeshTexture::TextureWithExistingUV( unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight, - const Mesh::Image8U3Arr& existingTextures, // 添加已有纹理参数 - const Mesh::TexCoordArr& existingTexcoords, // 添加已有UV参数 - const Mesh::TexIndexArr& existingTexindices // 添加已有纹理索引参数 -) + const Mesh::Image8U3Arr& existingTextures, + const Mesh::TexCoordArr& existingTexcoords, + const Mesh::TexIndexArr& existingTexindices) { - DEBUG_EXTRA("TextureWithExistingUV - Using existing texture data"); + DEBUG_EXTRA("TextureWithExistingUV - 使用3D几何坐标作为桥梁"); TD_TIMER_START(); // 1. 验证输入 @@ -9967,38 +9996,64 @@ bool MeshTexture::TextureWithExistingUV( return false; } - if (existingTextures.empty() || existingTexcoords.empty()) { + if (existingTextures.empty()) { VERBOSE("error: no existing texture data provided"); 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("Processing %zu faces with existing texture data", scene.mesh.faces.size()); + + // 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()); + // 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; + } - // 2. 生成纹理图集(从已有纹理采样,但使用模型原始UV) - Mesh::Image8U3Arr generatedTextures = GenerateTextureAtlasFromUV( - existingTextures, - existingTexcoords, - existingTexindices, - nTextureSizeMultiple, - colEmpty, - fSharpnessWeight + // 4. 生成纹理图集 + Mesh::Image8U3Arr generatedTextures = GenerateTextureAtlasWith3DBridge( + faceLabels, views, existingTextures, existingTexcoords, existingTexindices, + nTextureSizeMultiple, colEmpty, fSharpnessWeight ); 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; // 所有面使用第一个纹理 + scene.mesh.faceTexindices[i] = 0; } } else { scene.mesh.faceTexindices.Release(); @@ -10006,7 +10061,6 @@ bool MeshTexture::TextureWithExistingUV( DEBUG_EXTRA("Generated %zu textures from existing data", scene.mesh.texturesDiffuse.size()); - return true; } @@ -10014,6 +10068,221 @@ bool MeshTexture::TextureWithExistingUV( return false; } +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) +{ + 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); + } + } + } + + // 计算纹理尺寸 + 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); + +// 创建目标纹理 + Mesh::Image8U3Arr textures; + Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); + // 注意:cv::Scalar 使用 BGR 顺序 + textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); + + DEBUG_EXTRA("生成纹理图集: 尺寸=%dx%d, UV范围=[%.3f,%.3f]-[%.3f,%.3f]", + textureSize, textureSize, + uvBounds.ptMin.x(), uvBounds.ptMin.y(), + uvBounds.ptMax.x(), uvBounds.ptMax.y()); + + // 添加调试信息 + DEBUG_EXTRA("colEmpty: R=%d, G=%d, B=%d", colEmpty.r, colEmpty.g, colEmpty.b); + DEBUG_EXTRA("OpenCV图像通道顺序: BGR"); + + // 检查颜色通道顺序 + if (!sourceTextures.empty()) { + const Image8U3& firstTex = sourceTextures[0]; + cv::Vec3b firstPixel = firstTex.at(0, 0); + DEBUG_EXTRA("源纹理第一个像素: B=%d, G=%d, R=%d", + firstPixel[0], firstPixel[1], firstPixel[2]); + } + + // 3. 统计信息 + int processedFaces = 0; + int sampledPixels = 0; + int failedFaces = 0; + + // 4. 为每个面采样 + #ifdef _USE_OPENMP + #pragma omp parallel for schedule(dynamic) reduction(+:processedFaces, sampledPixels, failedFaces) + #endif + for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) { + const FIndex faceID = (FIndex)idxFace; + const Label label = faceLabels[faceID]; + + if (label == 0) { + failedFaces++; + continue; + } + + const IIndex idxView = label - 1; + if (idxView >= images.size()) { + failedFaces++; + continue; + } + + // 获取面的几何信息 + const TexCoord* meshUVs = &scene.mesh.faceTexcoords[faceID * 3]; + const Face& face = scene.mesh.faces[faceID]; + + // 获取源纹理信息 + const TexCoord* srcUVs = &sourceTexcoords[faceID * 3]; + const TexIndex textureIdx = sourceTexindices.empty() ? 0 : sourceTexindices[faceID]; + + if (textureIdx >= sourceTextures.size()) { + failedFaces++; + continue; + } + + const Image8U3& sourceTexture = sourceTextures[textureIdx]; + + // 计算面的UV边界 + AABB2f faceBounds(true); + for (int i = 0; i < 3; ++i) { + faceBounds.InsertFull(meshUVs[i]); + } + + // 转换为像素坐标 + int startX = (int)(faceBounds.ptMin.x() * textureSize); + int startY = (int)(faceBounds.ptMin.y() * textureSize); + int endX = (int)(faceBounds.ptMax.x() * textureSize); + int endY = (int)(faceBounds.ptMax.y() * textureSize); + + // 边界检查 + startX = std::max(0, std::min(startX, textureSize - 1)); + startY = std::max(0, std::min(startY, textureSize - 1)); + endX = std::max(0, std::min(endX, textureSize - 1)); + endY = std::max(0, std::min(endY, textureSize - 1)); + + if (startX >= endX || startY >= endY) { + failedFaces++; + continue; + } + + int faceSampledPixels = 0; + + // 采样纹理 + for (int y = startY; y <= endY; ++y) { + for (int x = startX; x <= endX; ++x) { + const Point2f texCoord((x + 0.5f) / textureSize, (y + 0.5f) / textureSize); + + // 计算重心坐标 + Point3f barycentric; + if (!PointInTriangle(texCoord, meshUVs[0], meshUVs[1], meshUVs[2], barycentric)) { + continue; + } + + // 计算3D点 + const Vertex worldPoint = + vertices[face[0]] * barycentric.x + + vertices[face[1]] * barycentric.y + + vertices[face[2]] * barycentric.z; + + // 方案A:从原始图像采样 + Point2f imgPoint = ProjectPointWithAutoCorrection(images[idxView].camera, worldPoint, images[idxView]); + + if (imgPoint.x < 0 || imgPoint.x >= images[idxView].image.cols || + imgPoint.y < 0 || imgPoint.y >= images[idxView].image.rows) { + continue; + } + + // 修正:使用双线性插值采样 + const int x0 = (int)floor(imgPoint.x); + const int y0 = (int)floor(imgPoint.y); + const int x1 = std::min(x0 + 1, images[idxView].image.cols - 1); + const int y1 = std::min(y0 + 1, images[idxView].image.rows - 1); + + const float fx = imgPoint.x - x0; + const float fy = imgPoint.y - y0; + const float fx1 = 1.0f - fx; + const float fy1 = 1.0f - fy; + + // 采样四个点的颜色 + const cv::Vec3b& c00 = images[idxView].image.at(y0, x0); + const cv::Vec3b& c01 = images[idxView].image.at(y0, x1); + const cv::Vec3b& c10 = images[idxView].image.at(y1, x0); + const cv::Vec3b& c11 = images[idxView].image.at(y1, x1); + + // 双线性插值 + const cv::Vec3b color = + c00 * (fx1 * fy1) + + c01 * (fx * fy1) + + c10 * (fx1 * fy) + + c11 * (fx * fy); + + // 注意:OpenCV是BGR顺序,而Pixel8U通常是RGB顺序 + // 这里我们需要确保颜色通道正确 + #ifdef _USE_OPENMP + #pragma omp critical + #endif + { + // 方案1:直接使用BGR顺序(OpenCV默认) + // textureAtlas.at(y, x) = color; + + // 方案2:交换B和R通道(如果颜色偏蓝,说明B和R反了) + // 将BGR转换为RGB + textureAtlas.at(y, x) = cv::Vec3b(color[2], color[1], color[0]); + } + + faceSampledPixels++; + } + } + + if (faceSampledPixels > 0) { + processedFaces++; + sampledPixels += faceSampledPixels; + } else { + failedFaces++; + } + } + + DEBUG_EXTRA("纹理采样完成: 成功 %d 个面, 失败 %d 个面, 采样 %d 像素", + processedFaces, failedFaces, sampledPixels); + + // 5. 填充空洞 + if (processedFaces > 0 && sampledPixels > 0) { + // FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), textureSize, colEmpty); + } + + // 6. 锐化处理 + if (fSharpnessWeight > 0) { + // ApplySharpening(textureAtlas, fSharpnessWeight); + } + + return textures; +} + Point2f MeshTexture::ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage) { Point2f imgPoint = camera.ProjectPointP(worldPoint); @@ -11171,41 +11440,52 @@ 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); - + + texture.GenerateTextureForUV(bGlobalSeamLeveling, bLocalSeamLeveling, nTextureSizeMultiple, + nRectPackingHeuristic, colEmpty, fSharpnessWeight, maxTextureSize, + baseFileName, bOriginFaceview, this, existingTexcoords, existingTexindices); + // 保存生成的纹理数据 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); - // 使用预计算UV模式 - if (!mesh.Load(MAKE_PATH_SAFE(strUVMeshFileName), true)) { - VERBOSE("error: cannot load mesh file with UV coordinates"); - return false; // 注意:在成员函数中,返回 false 表示失败 - } - + + // 2. 使用预计算UV模式加载网格 + if (!mesh.Load(MAKE_PATH_SAFE(strUVMeshFileName), true)) { + VERBOSE("error: cannot load mesh file with UV coordinates"); + return false; + } + VERBOSE("2faceTexcoords.size=%d, faces.size=%d", mesh.faceTexcoords.size(), mesh.faces.size() * 3); - // mesh.CheckUVValid(); - //* - // 确保网格包含UV坐标 - if (mesh.faceTexcoords.empty()) { - VERBOSE("error: the specified mesh does not contain UV coordinates"); - return false; - } - - // 使用新的纹理生成方法 - MeshTexture texture2(*this, nResolutionLevel, nMinResolution); - if (!texture2.TextureWithExistingUV(views, nIgnoreMaskLabel, fOutlierThreshold, nTextureSizeMultiple, colEmpty, fSharpnessWeight, existingTextures, existingTexcoords, existingTexindices)){ - return false; - } - //*/ - - return true; - } + + if (mesh.faceTexcoords.empty()) { + VERBOSE("error: the specified mesh does not contain UV coordinates"); + return false; + } + + // 3. 使用新的纹理生成方法 + MeshTexture texture2(*this, nResolutionLevel, nMinResolution); + + // 关键:确保几何信息正确 + texture2.scene.mesh.vertices = mesh.vertices; + texture2.scene.mesh.faces = mesh.faces; + texture2.scene.mesh.faceTexcoords = mesh.faceTexcoords; // 使用外部UV + + if (!texture2.TextureWithExistingUV(views, nIgnoreMaskLabel, fOutlierThreshold, + nTextureSizeMultiple, colEmpty, fSharpnessWeight, + existingTextures, existingTexcoords, existingTexindices)) { + return false; + } + + // 4. 将生成的纹理赋值回场景网格 + mesh.texturesDiffuse = std::move(texture2.scene.mesh.texturesDiffuse); + mesh.faceTexindices = std::move(texture2.scene.mesh.faceTexindices); + + return true; + } // mesh.CheckUVValid();