Browse Source

中间版本

ManualUV
hesuicong 4 weeks ago
parent
commit
1b93beb86a
  1. 690
      libs/MVS/SceneTexture.cpp

690
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 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区域的像素值
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<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]);
// 保存所有纹理 // 保存用于验证
for (size_t i = 0; i < generatedTextures.size(); ++i) { std::string filename = "debug_" + name + ".png";
if (generatedTextures[i].empty()) { if (cv::imwrite(filename, texture)) {
DEBUG_EXTRA("警告: 纹理 %zu 为空,跳过保存", i); 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));
// 保存纹理
bool success = cv::imwrite(filename, texture);
if (success) {
DEBUG_EXTRA("纹理 %zu 保存成功", i);
// 使用OpenCV保存图像 // 验证保存的文件
if (cv::imwrite(filename, generatedTextures[i])) { cv::Mat loaded = cv::imread(filename);
DEBUG_EXTRA("成功保存纹理: %s (尺寸: %dx%d)", if (!loaded.empty()) {
filename.c_str(), DEBUG_EXTRA("重新加载验证: 尺寸=%dx%d, 通道数=%d",
generatedTextures[i].cols, loaded.rows, loaded.cols, loaded.channels());
generatedTextures[i].rows); } else {
DEBUG_EXTRA("警告: 保存的文件无法重新加载!");
}
} else { } else {
DEBUG_EXTRA("错误: 无法保存纹理到 %s", filename.c_str()); DEBUG_EXTRA("错误: 纹理 %zu 保存失败!", i);
return false;
// 尝试使用不同格式
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]; Mesh::Image8U3Arr textures;
// 计算面片在纹理图集中的边界框 // 创建纹理图集
AABB2f faceUVBounds(true); try {
for (int i = 0; i < 3; ++i) { Mesh::Image8U3Arr textures;
faceUVBounds.InsertFull(uvCoords[i]); cv::Mat textureMat(textureSize, textureSize, CV_8UC3);
} Image8U3& textureAtlas = textures.emplace_back(std::move(textureMat));
// 将UV坐标转换到纹理像素坐标 DEBUG_EXTRA("纹理对象创建: 地址=%p, 尺寸=%dx%d, 通道数=%d, 类型=%d",
const int startX = std::max(0, (int)(faceUVBounds.ptMin.x() * textureSize)); &textureAtlas, textureAtlas.rows, textureAtlas.cols,
const int startY = std::max(0, (int)(faceUVBounds.ptMin.y() * textureSize)); textureAtlas.channels(), textureAtlas.type());
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 (textureAtlas.empty()) {
for (int y = startY; y <= endY; ++y) { DEBUG_EXTRA("错误: 纹理对象为空!");
for (int x = startX; x <= endX; ++x) { return textures;
const Point2f texCoord((float)x / textureSize, (float)y / textureSize); }
// 1. 检查是否在三角形内 // 使用不同颜色填充纹理以便调试
Point3f barycentric; DEBUG_EXTRA("开始填充测试纹理...");
if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) {
// 从已有纹理中采样颜色 // 方法1: 使用纯色填充整个纹理
Pixel8U sampledColor = SampleFromExistingTextureAtBarycentric(faceID, barycentric, existingTexture); 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);
}
}
}
// 将采样颜色写入纹理图集 // 检查纹理的实际像素值
#ifdef _USE_OPENMP int nonBluePixels = 0;
#pragma omp critical for (int y = 0; y < textureAtlas.rows; ++y) {
#endif for (int x = 0; x < textureAtlas.cols; ++x) {
{ const cv::Vec3b& pixel = textureAtlas.at<cv::Vec3b>(y, x);
textureAtlas(y, x) = sampledColor; if (!(pixel[0] == 255 && pixel[1] == 0 && pixel[2] == 0)) { // 如果不是纯蓝色
} nonBluePixels++;
} }
// 2. 如果不在三角形内,检查是否在三角形边缘附近 }
else { }
// 计算到三角形边缘的最近距离
float minDist = std::numeric_limits<float>::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;
}
}
// 如果距离小于阈值,进行边缘填充 DEBUG_EXTRA("纹理像素统计: 总像素=%d, 非蓝色像素=%d, 填充率=%.2f%%",
const float edgeThreshold = 1.0f / textureSize; // 1个像素的阈值 textureAtlas.rows * textureAtlas.cols,
if (minDist <= edgeThreshold * 2) { // 填充边缘周围2个像素 nonBluePixels,
// 在最近点计算重心坐标 (nonBluePixels * 100.0) / (textureAtlas.rows * textureAtlas.cols));
Point3f edgeBarycentric = BarycentricFromPoint(closestPoint, uvCoords[0], uvCoords[1], uvCoords[2]);
if (edgeBarycentric.x >= 0 && edgeBarycentric.x <= 1 && // 保存纹理到文件
edgeBarycentric.y >= 0 && edgeBarycentric.y <= 1 && std::string debugFilename = "debug_test_texture_" + std::to_string(textureSize) + ".png";
edgeBarycentric.z >= 0 && edgeBarycentric.z <= 1) { bool saveResult = cv::imwrite(debugFilename, textureAtlas);
// 从已有纹理中采样颜色 if (saveResult) {
Pixel8U sampledColor = SampleFromExistingTextureAtBarycentric(faceID, edgeBarycentric, existingTexture); DEBUG_EXTRA("测试纹理已保存到: %s", debugFilename.c_str());
#ifdef _USE_OPENMP // 验证保存的纹理
#pragma omp critical cv::Mat loaded = cv::imread(debugFilename);
#endif if (!loaded.empty()) {
{ DEBUG_EXTRA("重新加载纹理成功: 尺寸=%dx%d, 通道数=%d",
textureAtlas(y, x) = sampledColor; 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());
} }
} }
}
// 添加后处理填充函数 // 返回纹理
FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), faceLabels, textureSize, colEmpty); DEBUG_EXTRA("纹理生成完成,返回 %zu 个纹理", textures.size());
return textures;
// 应用后处理 } catch (const cv::Exception& e) {
if (fSharpnessWeight > 0) { DEBUG_EXTRA("OpenCV异常: %s", e.what());
// ApplySharpening(textureAtlas, fSharpnessWeight); return textures;
} catch (const std::exception& e) {
DEBUG_EXTRA("标准异常: %s", e.what());
return textures;
} }
DEBUG_EXTRA("从已有纹理生成纹理图集完成: 尺寸%dx%d, %u个面片",
textureSize, textureSize, scene.mesh.faces.size());
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 || Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint);
imgPoint.y < -100 || imgPoint.y > sourceImage.image.rows + 100) {
continue;
}
// 检查投影有效性 // 写入纹理图集
if (!sourceImage.image.isInside(imgPoint) || #ifdef _USE_OPENMP
!sourceImage.camera.IsInFront(worldPoint)) { #pragma omp critical
continue; #endif
{
textureAtlas(y, x) = sampledColor;
sampledPixels++;
}
} }
}
}
}
// 从源图像采样颜色(使用双线性插值) processedFaces++;
Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint); }
// 将采样颜色写入纹理图集 DEBUG_EXTRA("纹理生成统计: 处理了%d个面片, 采样了%d个像素", processedFaces, sampledPixels);
#ifdef _USE_OPENMP
#pragma omp critical
#endif
{
textureAtlas(y, x) = sampledColor;
}
}
// 2. 如果不在三角形内,检查是否在三角形边缘附近
else {
// 计算到三角形边缘的最近距离
float minDist = std::numeric_limits<float>::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个像素的阈值 int coloredPixels = 0;
if (minDist <= edgeThreshold * 2) { // 填充边缘周围2个像素 for (int y = 0; y < textureAtlas.rows; ++y) {
// 在最近点计算重心坐标 for (int x = 0; x < textureAtlas.cols; ++x) {
Point3f edgeBarycentric = BarycentricFromPoint(closestPoint, uvCoords[0], uvCoords[1], uvCoords[2]); const Pixel8U& pixel = textureAtlas(y, x);
if (!(pixel[0] == colEmpty.b && pixel[1] == colEmpty.g && pixel[2] == colEmpty.r)) {
coloredPixels++;
}
}
}
if (edgeBarycentric.x >= 0 && edgeBarycentric.x <= 1 && DEBUG_EXTRA("纹理填充率: %d/%d (%.2f%%)",
edgeBarycentric.y >= 0 && edgeBarycentric.y <= 1 && coloredPixels, textureAtlas.rows * textureAtlas.cols,
edgeBarycentric.z >= 0 && edgeBarycentric.z <= 1) { (coloredPixels * 100.0) / (textureAtlas.rows * textureAtlas.cols));
const Vertex worldPoint = ValidateGeneratedTexture(textureAtlas, "生成的纹理");
vertices[face[0]] * edgeBarycentric.x +
vertices[face[1]] * edgeBarycentric.y +
vertices[face[2]] * edgeBarycentric.z;
Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage); return textures;
}
if (ValidateProjection(worldPoint, sourceImage, imgPoint) && void MeshTexture::ValidateGeneratedTexture(const Image8U3& texture, const std::string& name)
sourceImage.image.isInside(imgPoint) && {
sourceImage.camera.IsInFront(worldPoint)) { DEBUG_EXTRA("验证纹理: %s", name.c_str());
Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint); if (texture.empty()) {
DEBUG_EXTRA("纹理为空!");
return;
}
#ifdef _USE_OPENMP int totalPixels = texture.rows * texture.cols;
#pragma omp critical int coloredPixels = 0;
#endif int emptyPixels = 0;
{ Pixel8U firstColor = texture(0, 0);
textureAtlas(y, x) = sampledColor; 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("纹理检查结果:");
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范围是否过大,需要归一化
float uvWidth = uvMax.x - uvMin.x;
// 如果UV范围无效,返回默认尺寸 float uvHeight = uvMax.y - uvMin.y;
if (uvRangeX <= 0.0f || uvRangeY <= 0.0f) {
return 1024; // 默认回退尺寸 // 如果UV范围非常大,可能是非归一化坐标
} if (uvWidth > 100.0f || uvHeight > 100.0f) {
DEBUG_EXTRA("警告: UV坐标范围过大,可能需要进行归一化");
// 2. 基于UV覆盖范围计算基础纹理尺寸 DEBUG_EXTRA("UV宽度=%.3f, 高度=%.3f,使用固定尺寸4096", uvWidth, uvHeight);
// 假设我们希望纹理密度为:每单位UV空间对应N个像素 return 4096; // 返回固定尺寸
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; // 默认最小值
// 此处可查询设备实际支持的最大纹理尺寸
// 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;
} }
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;
} }
// 保存遮挡数据到文件 // 保存遮挡数据到文件

Loading…
Cancel
Save