diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index e1f7efc..78d27c4 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -485,6 +485,7 @@ public: 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); + void ValidateGeneratedTexture(const Image8U3& texture, const std::string& name); 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); @@ -9904,42 +9905,110 @@ void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeve #include -// 保存生成的纹理图集 -bool SaveGeneratedTextures(const Mesh::Image8U3Arr& generatedTextures, const std::string& outputDir) { - if (generatedTextures.empty()) { - DEBUG_EXTRA("错误: 没有纹理可保存"); - return false; + +void DebugCheckTexture(const cv::Mat& texture, const std::string& name) +{ + if (texture.empty()) { + DEBUG_EXTRA("%s: 纹理为空!", name.c_str()); + return; } - // 确保输出目录存在 - #ifdef _WIN32 - _mkdir(outputDir.c_str()); - #else - mkdir(outputDir.c_str(), 0755); - #endif + DEBUG_EXTRA("%s 检查:", name.c_str()); + DEBUG_EXTRA(" - 尺寸: %dx%d", texture.rows, texture.cols); + DEBUG_EXTRA(" - 通道数: %d", texture.channels()); + DEBUG_EXTRA(" - 类型: %d (CV_8UC3=%d)", texture.type(), CV_8UC3); + DEBUG_EXTRA(" - 数据类型: %s", + texture.depth() == CV_8U ? "CV_8U" : + texture.depth() == CV_16U ? "CV_16U" : + texture.depth() == CV_32F ? "CV_32F" : "其他"); + + // 检查前5x5区域的像素值 + int checkSize = std::min(5, std::min(texture.rows, texture.cols)); + DEBUG_EXTRA(" - 前%d个像素值:", checkSize * checkSize); + + for (int y = 0; y < checkSize; ++y) { + std::string rowStr = " "; + for (int x = 0; x < checkSize; ++x) { + if (texture.channels() == 3) { + cv::Vec3b pixel = texture.at(y, x); + rowStr += cv::format("(%3d,%3d,%3d) ", pixel[0], pixel[1], pixel[2]); + } else if (texture.channels() == 1) { + uint8_t pixel = texture.at(y, x); + rowStr += cv::format("%3d ", pixel); + } + } + DEBUG_EXTRA("%s", rowStr.c_str()); + } + + // 计算统计信息 + double minVal, maxVal; + cv::minMaxLoc(texture, &minVal, &maxVal); + DEBUG_EXTRA(" - 像素值范围: %.0f ~ %.0f", minVal, maxVal); + + cv::Scalar mean, stddev; + cv::meanStdDev(texture, mean, stddev); + DEBUG_EXTRA(" - 平均值: (%.1f,%.1f,%.1f,%.1f)", mean[0], mean[1], mean[2], mean[3]); + DEBUG_EXTRA(" - 标准差: (%.1f,%.1f,%.1f,%.1f)", stddev[0], stddev[1], stddev[2], stddev[3]); - // 保存所有纹理 - for (size_t i = 0; i < generatedTextures.size(); ++i) { - if (generatedTextures[i].empty()) { - DEBUG_EXTRA("警告: 纹理 %zu 为空,跳过保存", i); + // 保存用于验证 + std::string filename = "debug_" + name + ".png"; + if (cv::imwrite(filename, texture)) { + DEBUG_EXTRA(" - 已保存到: %s", filename.c_str()); + } else { + DEBUG_EXTRA(" - 保存失败!"); + } +} + +// 保存生成的纹理图集 +bool SaveGeneratedTextures(const Mesh::Image8U3Arr& textures, const std::string& outputDir) +{ + DEBUG_EXTRA("===== SaveGeneratedTextures 开始 ====="); + DEBUG_EXTRA("输出目录: %s", outputDir.c_str()); + DEBUG_EXTRA("纹理数量: %zu", textures.size()); + + + for (size_t i = 0; i < textures.size(); ++i) { + const cv::Mat& texture = textures[i]; + + if (texture.empty()) { + DEBUG_EXTRA("纹理 %zu 为空,跳过保存", i); continue; } - // 生成文件名 std::string filename = outputDir + "/texture_" + std::to_string(i) + ".png"; + DEBUG_EXTRA("保存纹理 %zu: 尺寸=%dx%d, 通道数=%d, 保存到: %s", + i, texture.rows, texture.cols, texture.channels(), filename.c_str()); + + // 检查纹理内容 + DebugCheckTexture(texture, "before_save_" + std::to_string(i)); - // 使用OpenCV保存图像 - if (cv::imwrite(filename, generatedTextures[i])) { - DEBUG_EXTRA("成功保存纹理: %s (尺寸: %dx%d)", - filename.c_str(), - generatedTextures[i].cols, - generatedTextures[i].rows); + // 保存纹理 + bool success = cv::imwrite(filename, texture); + + if (success) { + DEBUG_EXTRA("纹理 %zu 保存成功", i); + + // 验证保存的文件 + cv::Mat loaded = cv::imread(filename); + if (!loaded.empty()) { + DEBUG_EXTRA("重新加载验证: 尺寸=%dx%d, 通道数=%d", + loaded.rows, loaded.cols, loaded.channels()); + } else { + DEBUG_EXTRA("警告: 保存的文件无法重新加载!"); + } } else { - DEBUG_EXTRA("错误: 无法保存纹理到 %s", filename.c_str()); - return false; + DEBUG_EXTRA("错误: 纹理 %zu 保存失败!", i); + + // 尝试使用不同格式 + std::string jpgFilename = outputDir + "/texture_" + std::to_string(i) + ".jpg"; + if (cv::imwrite(jpgFilename, texture)) { + DEBUG_EXTRA("使用JPG格式保存成功: %s", jpgFilename.c_str()); + success = true; + } } } + DEBUG_EXTRA("===== SaveGeneratedTextures 结束 ====="); return true; } @@ -9996,6 +10065,19 @@ bool MeshTexture::TextureWithExistingUV(const IIndexArr& views, int nIgnoreMaskL // 4. 生成纹理图集 Mesh::Image8U3Arr generatedTextures = GenerateTextureAtlasFromUV(faceLabels, views, nTextureSizeMultiple, colEmpty, fSharpnessWeight); + DEBUG_EXTRA("纹理生成完成,返回了 %zu 个纹理", generatedTextures.size()); + + if (!generatedTextures.empty()) { + for (size_t i = 0; i < generatedTextures.size(); ++i) { + DebugCheckTexture(generatedTextures[i], "generated_texture_" + std::to_string(i)); + } + + scene.mesh.texturesDiffuse = std::move(generatedTextures); + DEBUG_EXTRA("已将纹理赋值给 scene.mesh.texturesDiffuse"); + } else { + DEBUG_EXTRA("警告: 生成的纹理数组为空!"); + } + if (!generatedTextures.empty()) { scene.mesh.texturesDiffuse = std::move(generatedTextures); @@ -10604,10 +10686,12 @@ void FillTextureGaps(Image8U3& textureAtlas, const Mesh::TexCoordArr& faceTexcoo DEBUG_EXTRA("纹理间隙填充完成"); } +// 在 GenerateTextureAtlasFromUV 函数中添加 Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLabels, const IIndexArr& views, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) { - DEBUG_EXTRA("GenerateTextureAtlasFromUV"); + DEBUG_EXTRA("===== GenerateTextureAtlasFromUV 被调用 ====="); + DEBUG_EXTRA("输入参数: nTextureSizeMultiple=%u", nTextureSizeMultiple); // 1. 分析整个模型的UV布局 AABB2f uvBounds(true); @@ -10616,158 +10700,196 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLa uvBounds.InsertFull(uv); } + DEBUG_EXTRA("UV范围: 最小=(%.3f,%.3f), 最大=(%.3f,%.3f)", + uvBounds.ptMin.x(), uvBounds.ptMin.y(), + uvBounds.ptMax.x(), uvBounds.ptMax.y()); + // 2. 根据UV范围确定纹理图集尺寸 const int textureSize = ComputeOptimalTextureSize(uvBounds.ptMin, uvBounds.ptMax, nTextureSizeMultiple); + DEBUG_EXTRA("计算得到的纹理尺寸: %d", textureSize); - // 3. 创建单个纹理图集 - Mesh::Image8U3Arr textures; - Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); - textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); - - DEBUG_EXTRA("生成纹理图集: 尺寸=%dx%d, UV范围=[%.3f,%.3f]-[%.3f,%.3f]", - textureSize, textureSize, - uvBounds.ptMin.x(), uvBounds.ptMin.y(), uvBounds.ptMax.x(), uvBounds.ptMax.y()); - - // 4. 检查是否有已生成的纹理可以使用 + // 3. 检查是否有已生成的纹理可以使用 bool useExistingTextures = !scene.mesh.texturesDiffuse.empty() && - !scene.mesh.faceTexindices.empty() && - scene.mesh.texturesDiffuse.size() == 1; // 假设只有一个纹理图集 + !scene.mesh.faceTexindices.empty(); + + DEBUG_EXTRA("纹理检查: 已有纹理数量=%zu, 使用现有纹理=%s", + scene.mesh.texturesDiffuse.size(), + useExistingTextures ? "是" : "否"); if (useExistingTextures) { - DEBUG_EXTRA("从已有纹理采样生成新的纹理布局"); - return GenerateTextureAtlasFromExistingTextures(faceLabels, views, textureSize, colEmpty, fSharpnessWeight); + DEBUG_EXTRA("调用 GenerateTextureAtlasFromExistingTextures"); + auto result = GenerateTextureAtlasFromExistingTextures(faceLabels, views, textureSize, colEmpty, fSharpnessWeight); + DEBUG_EXTRA("GenerateTextureAtlasFromExistingTextures 返回 %zu 个纹理", result.size()); + return result; + } else { + DEBUG_EXTRA("调用 GenerateTextureAtlasFromImages"); + auto result = GenerateTextureAtlasFromImages(faceLabels, views, textureSize, colEmpty, fSharpnessWeight); + DEBUG_EXTRA("GenerateTextureAtlasFromImages 返回 %zu 个纹理", result.size()); + return result; } - - // 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)); + DEBUG_EXTRA("===== GenerateTextureAtlasFromExistingTextures 开始 ====="); + DEBUG_EXTRA("输入参数: textureSize=%d", textureSize); - // 获取已有的纹理 - const Image8U3& existingTexture = scene.mesh.texturesDiffuse[0]; + // 验证纹理尺寸 + if (textureSize <= 0) { + DEBUG_EXTRA("纹理尺寸无效,使用默认值1024"); + textureSize = 1024; + } - // 为每个面片从已有纹理采样 - #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; // 跳过无视图的面片 + // 创建纹理数组 + Mesh::Image8U3Arr textures; + + // 创建纹理图集 + try { + Mesh::Image8U3Arr textures; + cv::Mat textureMat(textureSize, textureSize, CV_8UC3); + Image8U3& textureAtlas = textures.emplace_back(std::move(textureMat)); - // 获取面的UV坐标 - const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; + DEBUG_EXTRA("纹理对象创建: 地址=%p, 尺寸=%dx%d, 通道数=%d, 类型=%d", + &textureAtlas, textureAtlas.rows, textureAtlas.cols, + textureAtlas.channels(), textureAtlas.type()); - // 计算面片在纹理图集中的边界框 - AABB2f faceUVBounds(true); - for (int i = 0; i < 3; ++i) { - faceUVBounds.InsertFull(uvCoords[i]); + if (textureAtlas.empty()) { + DEBUG_EXTRA("错误: 纹理对象为空!"); + return textures; } - // 将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)); + // 使用不同颜色填充纹理以便调试 + DEBUG_EXTRA("开始填充测试纹理..."); - // 对面片覆盖的每个纹理像素进行采样 - 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; - } + // 方法1: 使用纯色填充整个纹理 + textureAtlas.setTo(cv::Scalar(255, 0, 0)); // 蓝色 (OpenCV是BGR顺序) + DEBUG_EXTRA("纹理已填充为蓝色"); + + // 方法2: 绘制一个绿色的矩形 + int rectSize = textureSize / 2; + cv::Rect centerRect(textureSize/4, textureSize/4, rectSize, rectSize); + cv::rectangle(textureAtlas, centerRect, cv::Scalar(0, 255, 0), -1); // 绿色 + DEBUG_EXTRA("在中心绘制了绿色矩形: x=%d, y=%d, width=%d, height=%d", + centerRect.x, centerRect.y, centerRect.width, centerRect.height); + + // 方法3: 绘制一个红色的圆形 + cv::Point center(textureSize/2, textureSize/2); + int radius = textureSize / 8; + cv::circle(textureAtlas, center, radius, cv::Scalar(0, 0, 255), -1); // 红色 + DEBUG_EXTRA("在中心绘制了红色圆形: 中心=(%d,%d), 半径=%d", + center.x, center.y, radius); + + // 方法4: 绘制一些文字 + std::string text = "Test Texture"; + int fontFace = cv::FONT_HERSHEY_SIMPLEX; + double fontScale = 2.0; + int thickness = 3; + cv::Size textSize = cv::getTextSize(text, fontFace, fontScale, thickness, nullptr); + + cv::Point textOrg((textureSize - textSize.width) / 2, textureSize / 2 + textSize.height); + cv::putText(textureAtlas, text, textOrg, fontFace, fontScale, cv::Scalar(255, 255, 255), thickness); + DEBUG_EXTRA("添加了文字: '%s' 位置=(%d,%d)", text.c_str(), textOrg.x, textOrg.y); + + // 方法5: 绘制一个棋盘图案以便确认像素值 + int squareSize = textureSize / 8; + for (int y = 0; y < textureSize; y += squareSize) { + for (int x = 0; x < textureSize; x += squareSize) { + if ((x / squareSize + y / squareSize) % 2 == 0) { + cv::Rect square(x, y, squareSize, squareSize); + cv::rectangle(textureAtlas, square, cv::Scalar(200, 200, 200), -1); } - // 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; - } - } - } + } + } + + // 检查纹理的实际像素值 + int nonBluePixels = 0; + for (int y = 0; y < textureAtlas.rows; ++y) { + for (int x = 0; x < textureAtlas.cols; ++x) { + const cv::Vec3b& pixel = textureAtlas.at(y, x); + if (!(pixel[0] == 255 && pixel[1] == 0 && pixel[2] == 0)) { // 如果不是纯蓝色 + nonBluePixels++; } } } + + DEBUG_EXTRA("纹理像素统计: 总像素=%d, 非蓝色像素=%d, 填充率=%.2f%%", + textureAtlas.rows * textureAtlas.cols, + nonBluePixels, + (nonBluePixels * 100.0) / (textureAtlas.rows * textureAtlas.cols)); + + // 保存纹理到文件 + std::string debugFilename = "debug_test_texture_" + std::to_string(textureSize) + ".png"; + bool saveResult = cv::imwrite(debugFilename, textureAtlas); + + if (saveResult) { + DEBUG_EXTRA("测试纹理已保存到: %s", debugFilename.c_str()); + + // 验证保存的纹理 + cv::Mat loaded = cv::imread(debugFilename); + if (!loaded.empty()) { + DEBUG_EXTRA("重新加载纹理成功: 尺寸=%dx%d, 通道数=%d", + loaded.rows, loaded.cols, loaded.channels()); + } else { + DEBUG_EXTRA("警告: 保存的纹理无法重新加载!"); + } + } else { + DEBUG_EXTRA("错误: 无法保存测试纹理到文件!"); + + // 尝试不同的保存路径 + debugFilename = "/tmp/debug_test_texture.png"; + saveResult = cv::imwrite(debugFilename, textureAtlas); + if (saveResult) { + DEBUG_EXTRA("测试纹理已保存到: %s", debugFilename.c_str()); + } + } + + // 返回纹理 + DEBUG_EXTRA("纹理生成完成,返回 %zu 个纹理", textures.size()); + return textures; + + } catch (const cv::Exception& e) { + DEBUG_EXTRA("OpenCV异常: %s", e.what()); + return textures; + } catch (const std::exception& e) { + DEBUG_EXTRA("标准异常: %s", e.what()); + return textures; } - - // 添加后处理填充函数 - 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); + textures.push_back(Image8U3()); + Image8U3& textureAtlas = textures.back(); + textureAtlas.create(textureSize, textureSize); textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); + // 计算整个模型的UV范围 + AABB2f globalUVBounds(true); + FOREACH(i, scene.mesh.faceTexcoords) { + const TexCoord& uv = scene.mesh.faceTexcoords[i]; + globalUVBounds.InsertFull(uv); + } + + float uvWidth = globalUVBounds.ptMax.x() - globalUVBounds.ptMin.x(); + float uvHeight = globalUVBounds.ptMax.y() - globalUVBounds.ptMin.y(); + + DEBUG_EXTRA("全局UV范围: 最小=(%.3f,%.3f), 最大=(%.3f,%.3f), 宽度=%.3f, 高度=%.3f", + globalUVBounds.ptMin.x(), globalUVBounds.ptMin.y(), + globalUVBounds.ptMax.x(), globalUVBounds.ptMax.y(), + uvWidth, uvHeight); + + bool normalizeUV = (uvWidth > 100.0f || uvHeight > 100.0f); + DEBUG_EXTRA("是否需要归一化UV坐标: %s", normalizeUV ? "是" : "否"); + // 为每个面片从原始图像采样颜色 + int processedFaces = 0; + int sampledPixels = 0; + #ifdef _USE_OPENMP - #pragma omp parallel for schedule(dynamic) + #pragma omp parallel for schedule(dynamic) reduction(+:processedFaces,sampledPixels) for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) { #else FOREACH(idxFace, scene.mesh.faces) { @@ -10779,15 +10901,34 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromImages(const LabelArr& fa const IIndex idxView = label - 1; if (idxView >= images.size()) continue; - // 获取面的UV坐标和几何信息 + // 获取面的UV坐标 + if (faceID * 3 + 2 >= scene.mesh.faceTexcoords.size()) continue; + const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; const Face& face = scene.mesh.faces[faceID]; const Image& sourceImage = images[idxView]; - // 计算面片在纹理图集中的边界框 + // 将UV坐标归一化到[0,1]范围 + TexCoord normalizedUVs[3]; + for (int i = 0; i < 3; ++i) { + if (normalizeUV) { + // 归一化到[0,1]范围 + normalizedUVs[i].x = (uvCoords[i].x - globalUVBounds.ptMin.x()) / uvWidth; + normalizedUVs[i].y = (uvCoords[i].y - globalUVBounds.ptMin.y()) / uvHeight; + } else { + // 如果UV已经是[0,1]范围内,直接使用 + normalizedUVs[i] = uvCoords[i]; + } + + // 确保在[0,1]范围内 + normalizedUVs[i].x = std::max(0.0f, std::min(1.0f, normalizedUVs[i].x)); + normalizedUVs[i].y = std::max(0.0f, std::min(1.0f, normalizedUVs[i].y)); + } + + // 计算归一化后的UV边界框 AABB2f faceUVBounds(true); for (int i = 0; i < 3; ++i) { - faceUVBounds.InsertFull(uvCoords[i]); + faceUVBounds.InsertFull(normalizedUVs[i]); } // 将UV坐标转换到纹理像素坐标 @@ -10796,14 +10937,19 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromImages(const LabelArr& fa 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) { + 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. 检查是否在三角形内 + // 检查是否在三角形内 Point3f barycentric; - if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { + if (PointInTriangle(texCoord, normalizedUVs[0], normalizedUVs[1], normalizedUVs[2], barycentric)) { // 计算3D空间中的对应点 const Vertex worldPoint = vertices[face[0]] * barycentric.x + @@ -10813,105 +10959,116 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromImages(const LabelArr& fa // 将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) { - 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. 如果不在三角形内,检查是否在三角形边缘附近 - 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]; + // 验证投影有效性 + if (sourceImage.image.isInside(imgPoint) && + sourceImage.camera.IsInFront(worldPoint)) { - Point2f proj = ProjectPointToLineSegment(texCoord, p1, p2); - float dist = cv::norm(texCoord - proj); + // 从源图像采样颜色 + Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint); - 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; - } - } + // 写入纹理图集 + #ifdef _USE_OPENMP + #pragma omp critical + #endif + { + textureAtlas(y, x) = sampledColor; + sampledPixels++; } } } } } + + processedFaces++; } - // 添加后处理填充函数 - FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), faceLabels, textureSize, colEmpty); + DEBUG_EXTRA("纹理生成统计: 处理了%d个面片, 采样了%d个像素", processedFaces, sampledPixels); - // 应用后处理 - if (fSharpnessWeight > 0) { - // ApplySharpening(textureAtlas, fSharpnessWeight); + // 检查生成的纹理 + int coloredPixels = 0; + for (int y = 0; y < textureAtlas.rows; ++y) { + for (int x = 0; x < textureAtlas.cols; ++x) { + const Pixel8U& pixel = textureAtlas(y, x); + if (!(pixel[0] == colEmpty.b && pixel[1] == colEmpty.g && pixel[2] == colEmpty.r)) { + coloredPixels++; + } + } } - DEBUG_EXTRA("从原始图像生成纹理图集完成: 尺寸%dx%d, %u个面片", - textureSize, textureSize, scene.mesh.faces.size()); + DEBUG_EXTRA("纹理填充率: %d/%d (%.2f%%)", + coloredPixels, textureAtlas.rows * textureAtlas.cols, + (coloredPixels * 100.0) / (textureAtlas.rows * textureAtlas.cols)); + ValidateGeneratedTexture(textureAtlas, "生成的纹理"); + return textures; } +void MeshTexture::ValidateGeneratedTexture(const Image8U3& texture, const std::string& name) +{ + DEBUG_EXTRA("验证纹理: %s", name.c_str()); + + if (texture.empty()) { + DEBUG_EXTRA("纹理为空!"); + return; + } + + int totalPixels = texture.rows * texture.cols; + int coloredPixels = 0; + int emptyPixels = 0; + Pixel8U firstColor = texture(0, 0); + bool allSameColor = true; + + // 检查前1000个像素 + int checkCount = std::min(1000, totalPixels); + for (int i = 0; i < checkCount; ++i) { + int y = i / texture.cols; + int x = i % texture.cols; + const Pixel8U& pixel = texture(y, x); + + if (pixel[0] == 39 && pixel[1] == 127 && pixel[2] == 255) { + emptyPixels++; + } else { + coloredPixels++; + } + + if (pixel[0] != firstColor[0] || pixel[1] != firstColor[1] || pixel[2] != firstColor[2]) { + allSameColor = false; + } + } + + DEBUG_EXTRA("纹理检查结果:"); + DEBUG_EXTRA(" - 总像素: %d", totalPixels); + DEBUG_EXTRA(" - 检查了前 %d 个像素", checkCount); + DEBUG_EXTRA(" - 有颜色像素: %d", coloredPixels); + DEBUG_EXTRA(" - 空白像素: %d", emptyPixels); + DEBUG_EXTRA(" - 所有像素颜色相同: %s", allSameColor ? "是" : "否"); + + if (allSameColor) { + DEBUG_EXTRA(" - 统一颜色: RGB(%d, %d, %d)", + firstColor[2], firstColor[1], firstColor[0]); // OpenCV是BGR顺序 + } + + // 检查纹理中心区域 + int centerX = texture.cols / 2; + int centerY = texture.rows / 2; + int radius = 10; + + DEBUG_EXTRA("中心区域(%d±%d, %d±%d)像素:", centerX, radius, centerY, radius); + for (int y = centerY - radius; y <= centerY + radius; ++y) { + if (y < 0 || y >= texture.rows) continue; + + std::string rowStr = " "; + for (int x = centerX - radius; x <= centerX + radius; ++x) { + if (x < 0 || x >= texture.cols) continue; + + const Pixel8U& pixel = texture(y, x); + rowStr += cv::format("(%3d,%3d,%3d) ", pixel[0], pixel[1], pixel[2]); + } + DEBUG_EXTRA("%s", rowStr.c_str()); + } +} + Pixel8U MeshTexture::SampleFromExistingTextureAtBarycentric(FIndex faceID, const Point3f& barycentric, const Image8U3& existingTexture) { // 获取面在已有纹理中的UV坐标 @@ -11317,70 +11474,41 @@ bool MeshTexture::PointInTriangle(const Point2f& p, const Point2f& a, const Poin return false; } -int MeshTexture::ComputeOptimalTextureSize(const Point2f& uvMin, const Point2f& uvMax, unsigned nTextureSizeMultiple) { - - // 1. 计算UV坐标的实际覆盖范围 - const float uvRangeX = uvMax.x - uvMin.x; - const float uvRangeY = uvMax.y - uvMin.y; - - - // 如果UV范围无效,返回默认尺寸 - if (uvRangeX <= 0.0f || uvRangeY <= 0.0f) { - return 1024; // 默认回退尺寸 - } - - // 2. 基于UV覆盖范围计算基础纹理尺寸 - // 假设我们希望纹理密度为:每单位UV空间对应N个像素 - const float pixelsPerUV = 256.0f; // 可配置的密度系数 - int baseSizeX = static_cast(uvRangeX * pixelsPerUV); - int baseSizeY = static_cast(uvRangeY * pixelsPerUV); - - // 取较大者作为基础尺寸,以确保覆盖整个UV范围 - int baseSize = std::max(baseSizeX, baseSizeY); - - DEBUG_EXTRA("%d, %d", nTextureSizeMultiple, baseSize); - // 3. 应用纹理尺寸倍数约束 - baseSize = (baseSize + nTextureSizeMultiple - 1) / nTextureSizeMultiple * nTextureSizeMultiple; - - // 4. 设备能力限制 - int maxTextureSize = 4096; // 默认最大值 - int minTextureSize = 64; // 默认最小值 - - // 此处可查询设备实际支持的最大纹理尺寸 - // glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); - - // 5. 质量等级调整(可根据OPT中的质量设置进行调整) - int qualityLevel = 1; // 默认中等质量 - switch (qualityLevel) { - case 0: // 低质量:缩小纹理尺寸 - baseSize = baseSize / 2; - break; - case 1: // 中等质量:保持原计算尺寸 - // baseSize 保持不变 - break; - case 2: // 高质量:适度增大尺寸(如有足够内存) - baseSize = std::min(baseSize * 2, maxTextureSize); - break; +int MeshTexture::ComputeOptimalTextureSize(const Point2f& uvMin, const Point2f& uvMax, unsigned nTextureSizeMultiple) +{ + DEBUG_EXTRA("原始UV范围: 最小=(%.3f,%.3f), 最大=(%.3f,%.3f)", + uvMin.x, uvMin.y, uvMax.x, uvMax.y); + + // 检查UV范围是否过大,需要归一化 + float uvWidth = uvMax.x - uvMin.x; + float uvHeight = uvMax.y - uvMin.y; + + // 如果UV范围非常大,可能是非归一化坐标 + if (uvWidth > 100.0f || uvHeight > 100.0f) { + DEBUG_EXTRA("警告: UV坐标范围过大,可能需要进行归一化"); + DEBUG_EXTRA("UV宽度=%.3f, 高度=%.3f,使用固定尺寸4096", uvWidth, uvHeight); + return 4096; // 返回固定尺寸 } - 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; - } + // 如果是归一化的UV坐标(在[0,1]范围内) + if (uvMin.x >= 0.0f && uvMin.y >= 0.0f && + uvMax.x <= 1.0f && uvMax.y <= 1.0f) { + // 归一化坐标,使用最大范围 + float maxRange = std::max(uvWidth, uvHeight); + int baseSize = (int)(maxRange * 4096.0f); + int textureSize = ((baseSize + nTextureSizeMultiple - 1) / nTextureSizeMultiple) * nTextureSizeMultiple; + textureSize = std::max(512, std::min(8192, textureSize)); + DEBUG_EXTRA("归一化UV,计算尺寸: %.3f*4096=%d -> %d", maxRange, baseSize, textureSize); + return textureSize; } - DEBUG_EXTRA("计算出的最优纹理尺寸: %d (UV范围: [%.3f, %.3f])", - finalSize, uvRangeX, uvRangeY); + // 如果UV范围很小但不是归一化的 + int baseSize = (int)std::max(uvWidth, uvHeight) * 1024; + int textureSize = ((baseSize + nTextureSizeMultiple - 1) / nTextureSizeMultiple) * nTextureSizeMultiple; + textureSize = std::max(512, std::min(8192, textureSize)); - return finalSize; + DEBUG_EXTRA("计算纹理尺寸: baseSize=%d, 最终尺寸=%d", baseSize, textureSize); + return textureSize; } // 保存遮挡数据到文件