中间版本
This commit is contained in:
1 parent
84a492600f
commit
1b93beb86a
1 file changed
+419
-291
+419
-291
@@ -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 <opencv2/imgcodecs.hpp>
|
||||
|
||||
// 保存生成的纹理图集
|
||||
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" : "其他");
|
||||
|
||||
// 保存所有纹理
|
||||
for (size_t i = 0; i < generatedTextures.size(); ++i) {
|
||||
if (generatedTextures[i].empty()) {
|
||||
DEBUG_EXTRA("警告: 纹理 %zu 为空,跳过保存", i);
|
||||
// 检查前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]);
|
||||
|
||||
// 保存用于验证
|
||||
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());
|
||||
|
||||
// 使用OpenCV保存图像
|
||||
if (cv::imwrite(filename, generatedTextures[i])) {
|
||||
DEBUG_EXTRA("成功保存纹理: %s (尺寸: %dx%d)",
|
||||
filename.c_str(),
|
||||
generatedTextures[i].cols,
|
||||
generatedTextures[i].rows);
|
||||
// 检查纹理内容
|
||||
DebugCheckTexture(texture, "before_save_" + std::to_string(i));
|
||||
|
||||
// 保存纹理
|
||||
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)
|
||||
{
|
||||
DEBUG_EXTRA("===== GenerateTextureAtlasFromExistingTextures 开始 =====");
|
||||
DEBUG_EXTRA("输入参数: textureSize=%d", textureSize);
|
||||
|
||||
// 验证纹理尺寸
|
||||
if (textureSize <= 0) {
|
||||
DEBUG_EXTRA("纹理尺寸无效,使用默认值1024");
|
||||
textureSize = 1024;
|
||||
}
|
||||
|
||||
// 创建纹理数组
|
||||
Mesh::Image8U3Arr textures;
|
||||
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize);
|
||||
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r));
|
||||
|
||||
// 获取已有的纹理
|
||||
const Image8U3& existingTexture = scene.mesh.texturesDiffuse[0];
|
||||
|
||||
// 为每个面片从已有纹理采样
|
||||
#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; // 跳过无视图的面片
|
||||
// 创建纹理图集
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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个像素的阈值
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 方法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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查纹理的实际像素值
|
||||
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%%",
|
||||
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<float>::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;
|
||||
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范围无效,返回默认尺寸
|
||||
if (uvRangeX <= 0.0f || uvRangeY <= 0.0f) {
|
||||
return 1024; // 默认回退尺寸
|
||||
// 检查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; // 返回固定尺寸
|
||||
}
|
||||
|
||||
// 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; // 默认最小值
|
||||
|
||||
// 此处可查询设备实际支持的最大纹理尺寸
|
||||
// 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;
|
||||
// 如果是归一化的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("(UV范围: [%.3f, %.3f]), %d, %d, %d", uvRangeX, uvRangeY, nTextureSizeMultiple, baseSize, minTextureSize);
|
||||
// 6. 最终钳制到有效范围
|
||||
baseSize = (std::max)(minTextureSize, (std::min)(baseSize, maxTextureSize));
|
||||
// 如果UV范围很小但不是归一化的
|
||||
int baseSize = (int)std::max(uvWidth, uvHeight) * 1024;
|
||||
int textureSize = ((baseSize + nTextureSizeMultiple - 1) / nTextureSizeMultiple) * nTextureSizeMultiple;
|
||||
textureSize = std::max(512, std::min(8192, textureSize));
|
||||
|
||||
// 7. 确保尺寸为2的幂次(兼容性考虑,可选)
|
||||
int finalSize = 1;
|
||||
while (finalSize < baseSize) {
|
||||
finalSize <<= 1;
|
||||
if (finalSize >= maxTextureSize) {
|
||||
finalSize = maxTextureSize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_EXTRA("计算出的最优纹理尺寸: %d (UV范围: [%.3f, %.3f])",
|
||||
finalSize, uvRangeX, uvRangeY);
|
||||
|
||||
return finalSize;
|
||||
DEBUG_EXTRA("计算纹理尺寸: baseSize=%d, 最终尺寸=%d", baseSize, textureSize);
|
||||
return textureSize;
|
||||
}
|
||||
|
||||
// 保存遮挡数据到文件
|
||||
|
||||
Reference in new issue
Block a user