|
|
|
@ -485,6 +485,7 @@ public: |
|
|
|
Mesh::Image8U3Arr GenerateTextureAtlasFromUV(const LabelArr& faceLabels, const IIndexArr& views, 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 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); |
|
|
|
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); |
|
|
|
Pixel8U SampleFromExistingTextureAtBarycentric(FIndex faceID, const Point3f& barycentric, const Image8U3& existingTexture); |
|
|
|
Point2f ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage); |
|
|
|
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); |
|
|
|
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 <opencv2/imgcodecs.hpp> |
|
|
|
#include <opencv2/imgcodecs.hpp> |
|
|
|
|
|
|
|
|
|
|
|
// 保存生成的纹理图集
|
|
|
|
|
|
|
|
bool SaveGeneratedTextures(const Mesh::Image8U3Arr& generatedTextures, const std::string& outputDir) { |
|
|
|
void DebugCheckTexture(const cv::Mat& texture, const std::string& name) |
|
|
|
if (generatedTextures.empty()) { |
|
|
|
{ |
|
|
|
DEBUG_EXTRA("错误: 没有纹理可保存"); |
|
|
|
if (texture.empty()) { |
|
|
|
return false; |
|
|
|
DEBUG_EXTRA("%s: 纹理为空!", name.c_str()); |
|
|
|
|
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 确保输出目录存在
|
|
|
|
DEBUG_EXTRA("%s 检查:", name.c_str()); |
|
|
|
#ifdef _WIN32 |
|
|
|
DEBUG_EXTRA(" - 尺寸: %dx%d", texture.rows, texture.cols); |
|
|
|
_mkdir(outputDir.c_str()); |
|
|
|
DEBUG_EXTRA(" - 通道数: %d", texture.channels()); |
|
|
|
#else |
|
|
|
DEBUG_EXTRA(" - 类型: %d (CV_8UC3=%d)", texture.type(), CV_8UC3); |
|
|
|
mkdir(outputDir.c_str(), 0755); |
|
|
|
DEBUG_EXTRA(" - 数据类型: %s", |
|
|
|
#endif |
|
|
|
texture.depth() == CV_8U ? "CV_8U" : |
|
|
|
|
|
|
|
texture.depth() == CV_16U ? "CV_16U" : |
|
|
|
|
|
|
|
texture.depth() == CV_32F ? "CV_32F" : "其他"); |
|
|
|
|
|
|
|
|
|
|
|
// 保存所有纹理
|
|
|
|
// 检查前5x5区域的像素值
|
|
|
|
for (size_t i = 0; i < generatedTextures.size(); ++i) { |
|
|
|
int checkSize = std::min(5, std::min(texture.rows, texture.cols)); |
|
|
|
if (generatedTextures[i].empty()) { |
|
|
|
DEBUG_EXTRA(" - 前%d个像素值:", checkSize * checkSize); |
|
|
|
DEBUG_EXTRA("警告: 纹理 %zu 为空,跳过保存", i); |
|
|
|
|
|
|
|
|
|
|
|
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<cv::Vec3b>(y, x); |
|
|
|
|
|
|
|
rowStr += cv::format("(%3d,%3d,%3d) ", pixel[0], pixel[1], pixel[2]); |
|
|
|
|
|
|
|
} else if (texture.channels() == 1) { |
|
|
|
|
|
|
|
uint8_t pixel = texture.at<uint8_t>(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]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 保存用于验证
|
|
|
|
|
|
|
|
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; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 生成文件名
|
|
|
|
|
|
|
|
std::string filename = outputDir + "/texture_" + std::to_string(i) + ".png"; |
|
|
|
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])) { |
|
|
|
bool success = cv::imwrite(filename, texture); |
|
|
|
DEBUG_EXTRA("成功保存纹理: %s (尺寸: %dx%d)", |
|
|
|
|
|
|
|
filename.c_str(), |
|
|
|
if (success) { |
|
|
|
generatedTextures[i].cols, |
|
|
|
DEBUG_EXTRA("纹理 %zu 保存成功", i); |
|
|
|
generatedTextures[i].rows); |
|
|
|
|
|
|
|
|
|
|
|
// 验证保存的文件
|
|
|
|
|
|
|
|
cv::Mat loaded = cv::imread(filename); |
|
|
|
|
|
|
|
if (!loaded.empty()) { |
|
|
|
|
|
|
|
DEBUG_EXTRA("重新加载验证: 尺寸=%dx%d, 通道数=%d", |
|
|
|
|
|
|
|
loaded.rows, loaded.cols, loaded.channels()); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
DEBUG_EXTRA("错误: 无法保存纹理到 %s", filename.c_str()); |
|
|
|
DEBUG_EXTRA("警告: 保存的文件无法重新加载!"); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
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; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -9996,6 +10065,19 @@ bool MeshTexture::TextureWithExistingUV(const IIndexArr& views, int nIgnoreMaskL |
|
|
|
// 4. 生成纹理图集
|
|
|
|
// 4. 生成纹理图集
|
|
|
|
Mesh::Image8U3Arr generatedTextures = GenerateTextureAtlasFromUV(faceLabels, views, nTextureSizeMultiple, colEmpty, fSharpnessWeight); |
|
|
|
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()) { |
|
|
|
if (!generatedTextures.empty()) { |
|
|
|
scene.mesh.texturesDiffuse = std::move(generatedTextures); |
|
|
|
scene.mesh.texturesDiffuse = std::move(generatedTextures); |
|
|
|
|
|
|
|
|
|
|
|
@ -10604,10 +10686,12 @@ void FillTextureGaps(Image8U3& textureAtlas, const Mesh::TexCoordArr& faceTexcoo |
|
|
|
DEBUG_EXTRA("纹理间隙填充完成"); |
|
|
|
DEBUG_EXTRA("纹理间隙填充完成"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 在 GenerateTextureAtlasFromUV 函数中添加
|
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLabels, const IIndexArr& views, |
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLabels, const IIndexArr& views, |
|
|
|
unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) |
|
|
|
unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) |
|
|
|
{ |
|
|
|
{ |
|
|
|
DEBUG_EXTRA("GenerateTextureAtlasFromUV"); |
|
|
|
DEBUG_EXTRA("===== GenerateTextureAtlasFromUV 被调用 ====="); |
|
|
|
|
|
|
|
DEBUG_EXTRA("输入参数: nTextureSizeMultiple=%u", nTextureSizeMultiple); |
|
|
|
|
|
|
|
|
|
|
|
// 1. 分析整个模型的UV布局
|
|
|
|
// 1. 分析整个模型的UV布局
|
|
|
|
AABB2f uvBounds(true); |
|
|
|
AABB2f uvBounds(true); |
|
|
|
@ -10616,158 +10700,196 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLa |
|
|
|
uvBounds.InsertFull(uv); |
|
|
|
uvBounds.InsertFull(uv); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("UV范围: 最小=(%.3f,%.3f), 最大=(%.3f,%.3f)", |
|
|
|
|
|
|
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(), |
|
|
|
|
|
|
|
uvBounds.ptMax.x(), uvBounds.ptMax.y()); |
|
|
|
|
|
|
|
|
|
|
|
// 2. 根据UV范围确定纹理图集尺寸
|
|
|
|
// 2. 根据UV范围确定纹理图集尺寸
|
|
|
|
const int textureSize = ComputeOptimalTextureSize(uvBounds.ptMin, uvBounds.ptMax, nTextureSizeMultiple); |
|
|
|
const int textureSize = ComputeOptimalTextureSize(uvBounds.ptMin, uvBounds.ptMax, nTextureSizeMultiple); |
|
|
|
|
|
|
|
DEBUG_EXTRA("计算得到的纹理尺寸: %d", textureSize); |
|
|
|
|
|
|
|
|
|
|
|
// 3. 创建单个纹理图集
|
|
|
|
// 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. 检查是否有已生成的纹理可以使用
|
|
|
|
|
|
|
|
bool useExistingTextures = !scene.mesh.texturesDiffuse.empty() && |
|
|
|
bool useExistingTextures = !scene.mesh.texturesDiffuse.empty() && |
|
|
|
!scene.mesh.faceTexindices.empty() && |
|
|
|
!scene.mesh.faceTexindices.empty(); |
|
|
|
scene.mesh.texturesDiffuse.size() == 1; // 假设只有一个纹理图集
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("纹理检查: 已有纹理数量=%zu, 使用现有纹理=%s", |
|
|
|
|
|
|
|
scene.mesh.texturesDiffuse.size(), |
|
|
|
|
|
|
|
useExistingTextures ? "是" : "否"); |
|
|
|
|
|
|
|
|
|
|
|
if (useExistingTextures) { |
|
|
|
if (useExistingTextures) { |
|
|
|
DEBUG_EXTRA("从已有纹理采样生成新的纹理布局"); |
|
|
|
DEBUG_EXTRA("调用 GenerateTextureAtlasFromExistingTextures"); |
|
|
|
return GenerateTextureAtlasFromExistingTextures(faceLabels, views, textureSize, colEmpty, fSharpnessWeight); |
|
|
|
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, |
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, |
|
|
|
int textureSize, Pixel8U colEmpty, float fSharpnessWeight) |
|
|
|
int textureSize, Pixel8U colEmpty, float fSharpnessWeight) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
DEBUG_EXTRA("===== GenerateTextureAtlasFromExistingTextures 开始 ====="); |
|
|
|
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); |
|
|
|
DEBUG_EXTRA("输入参数: textureSize=%d", textureSize); |
|
|
|
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取已有的纹理
|
|
|
|
// 验证纹理尺寸
|
|
|
|
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; // 跳过无视图的面片
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取面的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)); |
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
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) { |
|
|
|
try { |
|
|
|
for (int x = startX; x <= endX; ++x) { |
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
const Point2f texCoord((float)x / textureSize, (float)y / textureSize); |
|
|
|
cv::Mat textureMat(textureSize, textureSize, CV_8UC3); |
|
|
|
|
|
|
|
Image8U3& textureAtlas = textures.emplace_back(std::move(textureMat)); |
|
|
|
|
|
|
|
|
|
|
|
// 1. 检查是否在三角形内
|
|
|
|
DEBUG_EXTRA("纹理对象创建: 地址=%p, 尺寸=%dx%d, 通道数=%d, 类型=%d", |
|
|
|
Point3f barycentric; |
|
|
|
&textureAtlas, textureAtlas.rows, textureAtlas.cols, |
|
|
|
if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { |
|
|
|
textureAtlas.channels(), textureAtlas.type()); |
|
|
|
// 从已有纹理中采样颜色
|
|
|
|
|
|
|
|
Pixel8U sampledColor = SampleFromExistingTextureAtBarycentric(faceID, barycentric, existingTexture); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 将采样颜色写入纹理图集
|
|
|
|
if (textureAtlas.empty()) { |
|
|
|
#ifdef _USE_OPENMP |
|
|
|
DEBUG_EXTRA("错误: 纹理对象为空!"); |
|
|
|
#pragma omp critical |
|
|
|
return textures; |
|
|
|
#endif |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
textureAtlas(y, x) = sampledColor; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
// 2. 如果不在三角形内,检查是否在三角形边缘附近
|
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
// 计算到三角形边缘的最近距离
|
|
|
|
|
|
|
|
float minDist = std::numeric_limits<float>::max(); |
|
|
|
|
|
|
|
Point2f closestPoint; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查三条边
|
|
|
|
// 使用不同颜色填充纹理以便调试
|
|
|
|
for (int i = 0; i < 3; ++i) { |
|
|
|
DEBUG_EXTRA("开始填充测试纹理..."); |
|
|
|
const Point2f& p1 = uvCoords[i]; |
|
|
|
|
|
|
|
const Point2f& p2 = uvCoords[(i + 1) % 3]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Point2f proj = ProjectPointToLineSegment(texCoord, p1, p2); |
|
|
|
// 方法1: 使用纯色填充整个纹理
|
|
|
|
float dist = cv::norm(texCoord - proj); |
|
|
|
textureAtlas.setTo(cv::Scalar(255, 0, 0)); // 蓝色 (OpenCV是BGR顺序)
|
|
|
|
|
|
|
|
DEBUG_EXTRA("纹理已填充为蓝色"); |
|
|
|
|
|
|
|
|
|
|
|
if (dist < minDist) { |
|
|
|
// 方法2: 绘制一个绿色的矩形
|
|
|
|
minDist = dist; |
|
|
|
int rectSize = textureSize / 2; |
|
|
|
closestPoint = proj; |
|
|
|
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: 绘制一个红色的圆形
|
|
|
|
const float edgeThreshold = 1.0f / textureSize; // 1个像素的阈值
|
|
|
|
cv::Point center(textureSize/2, textureSize/2); |
|
|
|
if (minDist <= edgeThreshold * 2) { // 填充边缘周围2个像素
|
|
|
|
int radius = textureSize / 8; |
|
|
|
// 在最近点计算重心坐标
|
|
|
|
cv::circle(textureAtlas, center, radius, cv::Scalar(0, 0, 255), -1); // 红色
|
|
|
|
Point3f edgeBarycentric = BarycentricFromPoint(closestPoint, uvCoords[0], uvCoords[1], uvCoords[2]); |
|
|
|
DEBUG_EXTRA("在中心绘制了红色圆形: 中心=(%d,%d), 半径=%d", |
|
|
|
|
|
|
|
center.x, center.y, radius); |
|
|
|
|
|
|
|
|
|
|
|
if (edgeBarycentric.x >= 0 && edgeBarycentric.x <= 1 && |
|
|
|
// 方法4: 绘制一些文字
|
|
|
|
edgeBarycentric.y >= 0 && edgeBarycentric.y <= 1 && |
|
|
|
std::string text = "Test Texture"; |
|
|
|
edgeBarycentric.z >= 0 && edgeBarycentric.z <= 1) { |
|
|
|
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); |
|
|
|
Pixel8U sampledColor = SampleFromExistingTextureAtBarycentric(faceID, edgeBarycentric, existingTexture); |
|
|
|
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); |
|
|
|
|
|
|
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
// 方法5: 绘制一个棋盘图案以便确认像素值
|
|
|
|
#pragma omp critical |
|
|
|
int squareSize = textureSize / 8; |
|
|
|
#endif |
|
|
|
for (int y = 0; y < textureSize; y += squareSize) { |
|
|
|
{ |
|
|
|
for (int x = 0; x < textureSize; x += squareSize) { |
|
|
|
textureAtlas(y, x) = sampledColor; |
|
|
|
if ((x / squareSize + y / squareSize) % 2 == 0) { |
|
|
|
} |
|
|
|
cv::Rect square(x, y, squareSize, squareSize); |
|
|
|
|
|
|
|
cv::rectangle(textureAtlas, square, cv::Scalar(200, 200, 200), -1); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查纹理的实际像素值
|
|
|
|
|
|
|
|
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<cv::Vec3b>(y, x); |
|
|
|
|
|
|
|
if (!(pixel[0] == 255 && pixel[1] == 0 && pixel[2] == 0)) { // 如果不是纯蓝色
|
|
|
|
|
|
|
|
nonBluePixels++; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 添加后处理填充函数
|
|
|
|
DEBUG_EXTRA("纹理像素统计: 总像素=%d, 非蓝色像素=%d, 填充率=%.2f%%", |
|
|
|
FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), faceLabels, textureSize, colEmpty); |
|
|
|
textureAtlas.rows * textureAtlas.cols, |
|
|
|
|
|
|
|
nonBluePixels, |
|
|
|
|
|
|
|
(nonBluePixels * 100.0) / (textureAtlas.rows * textureAtlas.cols)); |
|
|
|
|
|
|
|
|
|
|
|
// 应用后处理
|
|
|
|
// 保存纹理到文件
|
|
|
|
if (fSharpnessWeight > 0) { |
|
|
|
std::string debugFilename = "debug_test_texture_" + std::to_string(textureSize) + ".png"; |
|
|
|
// ApplySharpening(textureAtlas, fSharpnessWeight);
|
|
|
|
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("错误: 无法保存测试纹理到文件!"); |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("从已有纹理生成纹理图集完成: 尺寸%dx%d, %u个面片", |
|
|
|
// 尝试不同的保存路径
|
|
|
|
textureSize, textureSize, scene.mesh.faces.size()); |
|
|
|
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; |
|
|
|
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; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromImages(const LabelArr& faceLabels, const IIndexArr& views, |
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromImages(const LabelArr& faceLabels, const IIndexArr& views, |
|
|
|
int textureSize, Pixel8U colEmpty, float fSharpnessWeight) |
|
|
|
int textureSize, Pixel8U colEmpty, float fSharpnessWeight) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
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)); |
|
|
|
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 |
|
|
|
#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) { |
|
|
|
for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) { |
|
|
|
#else |
|
|
|
#else |
|
|
|
FOREACH(idxFace, scene.mesh.faces) { |
|
|
|
FOREACH(idxFace, scene.mesh.faces) { |
|
|
|
@ -10779,15 +10901,34 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromImages(const LabelArr& fa |
|
|
|
const IIndex idxView = label - 1; |
|
|
|
const IIndex idxView = label - 1; |
|
|
|
if (idxView >= images.size()) continue; |
|
|
|
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 TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; |
|
|
|
const Face& face = scene.mesh.faces[faceID]; |
|
|
|
const Face& face = scene.mesh.faces[faceID]; |
|
|
|
const Image& sourceImage = images[idxView]; |
|
|
|
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); |
|
|
|
AABB2f faceUVBounds(true); |
|
|
|
for (int i = 0; i < 3; ++i) { |
|
|
|
for (int i = 0; i < 3; ++i) { |
|
|
|
faceUVBounds.InsertFull(uvCoords[i]); |
|
|
|
faceUVBounds.InsertFull(normalizedUVs[i]); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 将UV坐标转换到纹理像素坐标
|
|
|
|
// 将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 endX = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.x() * textureSize)); |
|
|
|
const int endY = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.y() * 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 y = startY; y <= endY; ++y) { |
|
|
|
for (int x = startX; x <= endX; ++x) { |
|
|
|
for (int x = startX; x <= endX; ++x) { |
|
|
|
const Point2f texCoord((float)x / textureSize, (float)y / textureSize); |
|
|
|
const Point2f texCoord((float)x / textureSize, (float)y / textureSize); |
|
|
|
|
|
|
|
|
|
|
|
// 1. 检查是否在三角形内
|
|
|
|
// 检查是否在三角形内
|
|
|
|
Point3f barycentric; |
|
|
|
Point3f barycentric; |
|
|
|
if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { |
|
|
|
if (PointInTriangle(texCoord, normalizedUVs[0], normalizedUVs[1], normalizedUVs[2], barycentric)) { |
|
|
|
// 计算3D空间中的对应点
|
|
|
|
// 计算3D空间中的对应点
|
|
|
|
const Vertex worldPoint = |
|
|
|
const Vertex worldPoint = |
|
|
|
vertices[face[0]] * barycentric.x + |
|
|
|
vertices[face[0]] * barycentric.x + |
|
|
|
@ -10813,103 +10959,114 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromImages(const LabelArr& fa |
|
|
|
// 将3D点投影到源图像
|
|
|
|
// 将3D点投影到源图像
|
|
|
|
Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage); |
|
|
|
Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage); |
|
|
|
|
|
|
|
|
|
|
|
// 验证投影的有效性
|
|
|
|
// 验证投影有效性
|
|
|
|
if (!ValidateProjection(worldPoint, sourceImage, imgPoint)) { |
|
|
|
if (sourceImage.image.isInside(imgPoint) && |
|
|
|
continue; // 跳过几何不一致的采样点
|
|
|
|
sourceImage.camera.IsInFront(worldPoint)) { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查投影是否在图像边界内
|
|
|
|
|
|
|
|
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); |
|
|
|
Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint); |
|
|
|
|
|
|
|
|
|
|
|
// 将采样颜色写入纹理图集
|
|
|
|
// 写入纹理图集
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#pragma omp critical |
|
|
|
#pragma omp critical |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
{ |
|
|
|
{ |
|
|
|
textureAtlas(y, x) = sampledColor; |
|
|
|
textureAtlas(y, x) = sampledColor; |
|
|
|
|
|
|
|
sampledPixels++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// 2. 如果不在三角形内,检查是否在三角形边缘附近
|
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
// 计算到三角形边缘的最近距离
|
|
|
|
|
|
|
|
float minDist = std::numeric_limits<float>::max(); |
|
|
|
|
|
|
|
Point2f closestPoint; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查三条边
|
|
|
|
processedFaces++; |
|
|
|
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); |
|
|
|
DEBUG_EXTRA("纹理生成统计: 处理了%d个面片, 采样了%d个像素", processedFaces, sampledPixels); |
|
|
|
float dist = cv::norm(texCoord - proj); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (dist < minDist) { |
|
|
|
// 检查生成的纹理
|
|
|
|
minDist = dist; |
|
|
|
int coloredPixels = 0; |
|
|
|
closestPoint = proj; |
|
|
|
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("纹理填充率: %d/%d (%.2f%%)", |
|
|
|
const float edgeThreshold = 1.0f / textureSize; // 1个像素的阈值
|
|
|
|
coloredPixels, textureAtlas.rows * textureAtlas.cols, |
|
|
|
if (minDist <= edgeThreshold * 2) { // 填充边缘周围2个像素
|
|
|
|
(coloredPixels * 100.0) / (textureAtlas.rows * textureAtlas.cols)); |
|
|
|
// 在最近点计算重心坐标
|
|
|
|
|
|
|
|
Point3f edgeBarycentric = BarycentricFromPoint(closestPoint, uvCoords[0], uvCoords[1], uvCoords[2]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (edgeBarycentric.x >= 0 && edgeBarycentric.x <= 1 && |
|
|
|
ValidateGeneratedTexture(textureAtlas, "生成的纹理"); |
|
|
|
edgeBarycentric.y >= 0 && edgeBarycentric.y <= 1 && |
|
|
|
|
|
|
|
edgeBarycentric.z >= 0 && edgeBarycentric.z <= 1) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const Vertex worldPoint = |
|
|
|
return textures; |
|
|
|
vertices[face[0]] * edgeBarycentric.x + |
|
|
|
} |
|
|
|
vertices[face[1]] * edgeBarycentric.y + |
|
|
|
|
|
|
|
vertices[face[2]] * edgeBarycentric.z; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage); |
|
|
|
void MeshTexture::ValidateGeneratedTexture(const Image8U3& texture, const std::string& name) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
DEBUG_EXTRA("验证纹理: %s", name.c_str()); |
|
|
|
|
|
|
|
|
|
|
|
if (ValidateProjection(worldPoint, sourceImage, imgPoint) && |
|
|
|
if (texture.empty()) { |
|
|
|
sourceImage.image.isInside(imgPoint) && |
|
|
|
DEBUG_EXTRA("纹理为空!"); |
|
|
|
sourceImage.camera.IsInFront(worldPoint)) { |
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint); |
|
|
|
int totalPixels = texture.rows * texture.cols; |
|
|
|
|
|
|
|
int coloredPixels = 0; |
|
|
|
|
|
|
|
int emptyPixels = 0; |
|
|
|
|
|
|
|
Pixel8U firstColor = texture(0, 0); |
|
|
|
|
|
|
|
bool allSameColor = true; |
|
|
|
|
|
|
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
// 检查前1000个像素
|
|
|
|
#pragma omp critical |
|
|
|
int checkCount = std::min(1000, totalPixels); |
|
|
|
#endif |
|
|
|
for (int i = 0; i < checkCount; ++i) { |
|
|
|
{ |
|
|
|
int y = i / texture.cols; |
|
|
|
textureAtlas(y, x) = sampledColor; |
|
|
|
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("纹理检查结果:"); |
|
|
|
FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), faceLabels, textureSize, colEmpty); |
|
|
|
DEBUG_EXTRA(" - 总像素: %d", totalPixels); |
|
|
|
|
|
|
|
DEBUG_EXTRA(" - 检查了前 %d 个像素", checkCount); |
|
|
|
|
|
|
|
DEBUG_EXTRA(" - 有颜色像素: %d", coloredPixels); |
|
|
|
|
|
|
|
DEBUG_EXTRA(" - 空白像素: %d", emptyPixels); |
|
|
|
|
|
|
|
DEBUG_EXTRA(" - 所有像素颜色相同: %s", allSameColor ? "是" : "否"); |
|
|
|
|
|
|
|
|
|
|
|
// 应用后处理
|
|
|
|
if (allSameColor) { |
|
|
|
if (fSharpnessWeight > 0) { |
|
|
|
DEBUG_EXTRA(" - 统一颜色: RGB(%d, %d, %d)", |
|
|
|
// ApplySharpening(textureAtlas, fSharpnessWeight);
|
|
|
|
firstColor[2], firstColor[1], firstColor[0]); // OpenCV是BGR顺序
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("从原始图像生成纹理图集完成: 尺寸%dx%d, %u个面片", |
|
|
|
// 检查纹理中心区域
|
|
|
|
textureSize, textureSize, scene.mesh.faces.size()); |
|
|
|
int centerX = texture.cols / 2; |
|
|
|
|
|
|
|
int centerY = texture.rows / 2; |
|
|
|
|
|
|
|
int radius = 10; |
|
|
|
|
|
|
|
|
|
|
|
return textures; |
|
|
|
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) |
|
|
|
Pixel8U MeshTexture::SampleFromExistingTextureAtBarycentric(FIndex faceID, const Point3f& barycentric, const Image8U3& existingTexture) |
|
|
|
@ -11317,70 +11474,41 @@ bool MeshTexture::PointInTriangle(const Point2f& p, const Point2f& a, const Poin |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int MeshTexture::ComputeOptimalTextureSize(const Point2f& uvMin, const Point2f& uvMax, unsigned nTextureSizeMultiple) { |
|
|
|
int MeshTexture::ComputeOptimalTextureSize(const Point2f& uvMin, const Point2f& uvMax, unsigned nTextureSizeMultiple) |
|
|
|
|
|
|
|
{ |
|
|
|
// 1. 计算UV坐标的实际覆盖范围
|
|
|
|
DEBUG_EXTRA("原始UV范围: 最小=(%.3f,%.3f), 最大=(%.3f,%.3f)", |
|
|
|
const float uvRangeX = uvMax.x - uvMin.x; |
|
|
|
uvMin.x, uvMin.y, uvMax.x, uvMax.y); |
|
|
|
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<int>(uvRangeX * pixelsPerUV); |
|
|
|
|
|
|
|
int baseSizeY = static_cast<int>(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; // 默认最小值
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 此处可查询设备实际支持的最大纹理尺寸
|
|
|
|
// 检查UV范围是否过大,需要归一化
|
|
|
|
// glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
|
|
|
|
float uvWidth = uvMax.x - uvMin.x; |
|
|
|
|
|
|
|
float uvHeight = uvMax.y - uvMin.y; |
|
|
|
|
|
|
|
|
|
|
|
// 5. 质量等级调整(可根据OPT中的质量设置进行调整)
|
|
|
|
// 如果UV范围非常大,可能是非归一化坐标
|
|
|
|
int qualityLevel = 1; // 默认中等质量
|
|
|
|
if (uvWidth > 100.0f || uvHeight > 100.0f) { |
|
|
|
switch (qualityLevel) { |
|
|
|
DEBUG_EXTRA("警告: UV坐标范围过大,可能需要进行归一化"); |
|
|
|
case 0: // 低质量:缩小纹理尺寸
|
|
|
|
DEBUG_EXTRA("UV宽度=%.3f, 高度=%.3f,使用固定尺寸4096", uvWidth, uvHeight); |
|
|
|
baseSize = baseSize / 2; |
|
|
|
return 4096; // 返回固定尺寸
|
|
|
|
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); |
|
|
|
// 如果是归一化的UV坐标(在[0,1]范围内)
|
|
|
|
// 6. 最终钳制到有效范围
|
|
|
|
if (uvMin.x >= 0.0f && uvMin.y >= 0.0f && |
|
|
|
baseSize = (std::max)(minTextureSize, (std::min)(baseSize, maxTextureSize)); |
|
|
|
uvMax.x <= 1.0f && uvMax.y <= 1.0f) { |
|
|
|
|
|
|
|
// 归一化坐标,使用最大范围
|
|
|
|
// 7. 确保尺寸为2的幂次(兼容性考虑,可选)
|
|
|
|
float maxRange = std::max(uvWidth, uvHeight); |
|
|
|
int finalSize = 1; |
|
|
|
int baseSize = (int)(maxRange * 4096.0f); |
|
|
|
while (finalSize < baseSize) { |
|
|
|
int textureSize = ((baseSize + nTextureSizeMultiple - 1) / nTextureSizeMultiple) * nTextureSizeMultiple; |
|
|
|
finalSize <<= 1; |
|
|
|
textureSize = std::max(512, std::min(8192, textureSize)); |
|
|
|
if (finalSize >= maxTextureSize) { |
|
|
|
DEBUG_EXTRA("归一化UV,计算尺寸: %.3f*4096=%d -> %d", maxRange, baseSize, textureSize); |
|
|
|
finalSize = maxTextureSize; |
|
|
|
return textureSize; |
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("计算出的最优纹理尺寸: %d (UV范围: [%.3f, %.3f])", |
|
|
|
// 如果UV范围很小但不是归一化的
|
|
|
|
finalSize, uvRangeX, uvRangeY); |
|
|
|
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; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 保存遮挡数据到文件
|
|
|
|
// 保存遮挡数据到文件
|
|
|
|
|