|
|
|
@ -483,6 +483,9 @@ public: |
|
|
|
void GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& baseFileName); |
|
|
|
void GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& baseFileName); |
|
|
|
bool TextureWithExistingUV(const IIndexArr& views, int nIgnoreMaskLabel, float fOutlierThreshold, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight); |
|
|
|
bool TextureWithExistingUV(const IIndexArr& views, int nIgnoreMaskLabel, float fOutlierThreshold, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight); |
|
|
|
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 GenerateTextureAtlasFromImages(const LabelArr& faceLabels, const IIndexArr& views, int textureSize, Pixel8U colEmpty, float fSharpnessWeight); |
|
|
|
|
|
|
|
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); |
|
|
|
Mesh::Image8U3Arr SampleFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight); |
|
|
|
Mesh::Image8U3Arr SampleFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight); |
|
|
|
@ -10604,14 +10607,7 @@ void FillTextureGaps(Image8U3& textureAtlas, const Mesh::TexCoordArr& faceTexcoo |
|
|
|
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"); |
|
|
|
if (!scene.mesh.texturesDiffuse.empty()) { |
|
|
|
|
|
|
|
DEBUG_EXTRA("GenerateTextureAtlasFromUV: 使用已生成的纹理,数量: %zu", scene.mesh.texturesDiffuse.size()); |
|
|
|
|
|
|
|
return scene.mesh.texturesDiffuse; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 否则,重新生成纹理
|
|
|
|
|
|
|
|
DEBUG_EXTRA("GenerateTextureAtlasFromUV: 从图像重新生成纹理"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 分析整个模型的UV布局
|
|
|
|
// 1. 分析整个模型的UV布局
|
|
|
|
AABB2f uvBounds(true); |
|
|
|
AABB2f uvBounds(true); |
|
|
|
@ -10632,7 +10628,144 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLa |
|
|
|
textureSize, textureSize, |
|
|
|
textureSize, textureSize, |
|
|
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(), uvBounds.ptMax.x(), uvBounds.ptMax.y()); |
|
|
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(), uvBounds.ptMax.x(), uvBounds.ptMax.y()); |
|
|
|
|
|
|
|
|
|
|
|
// 4. 为每个面片采样颜色并填充纹理图集
|
|
|
|
// 4. 检查是否有已生成的纹理可以使用
|
|
|
|
|
|
|
|
bool useExistingTextures = !scene.mesh.texturesDiffuse.empty() && |
|
|
|
|
|
|
|
!scene.mesh.faceTexindices.empty() && |
|
|
|
|
|
|
|
scene.mesh.texturesDiffuse.size() == 1; // 假设只有一个纹理图集
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (useExistingTextures) { |
|
|
|
|
|
|
|
DEBUG_EXTRA("从已有纹理采样生成新的纹理布局"); |
|
|
|
|
|
|
|
return GenerateTextureAtlasFromExistingTextures(faceLabels, views, textureSize, colEmpty, fSharpnessWeight); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 否则,从原始图像重新生成纹理
|
|
|
|
|
|
|
|
DEBUG_EXTRA("从原始图像重新生成纹理图集"); |
|
|
|
|
|
|
|
return GenerateTextureAtlasFromImages(faceLabels, views, textureSize, colEmpty, fSharpnessWeight); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, |
|
|
|
|
|
|
|
int textureSize, Pixel8U colEmpty, float fSharpnessWeight) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
|
|
|
|
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); |
|
|
|
|
|
|
|
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取已有的纹理
|
|
|
|
|
|
|
|
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; // 跳过无视图的面片
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取面的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)); |
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 添加后处理填充函数
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 为每个面片从原始图像采样颜色
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
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) { |
|
|
|
@ -10671,7 +10804,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLa |
|
|
|
// 1. 检查是否在三角形内
|
|
|
|
// 1. 检查是否在三角形内
|
|
|
|
Point3f barycentric; |
|
|
|
Point3f barycentric; |
|
|
|
if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { |
|
|
|
if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { |
|
|
|
// 标准内部采样
|
|
|
|
|
|
|
|
// 计算3D空间中的对应点
|
|
|
|
// 计算3D空间中的对应点
|
|
|
|
const Vertex worldPoint = |
|
|
|
const Vertex worldPoint = |
|
|
|
vertices[face[0]] * barycentric.x + |
|
|
|
vertices[face[0]] * barycentric.x + |
|
|
|
@ -10689,9 +10821,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLa |
|
|
|
// 检查投影是否在图像边界内
|
|
|
|
// 检查投影是否在图像边界内
|
|
|
|
if (imgPoint.x < -100 || imgPoint.x > sourceImage.image.cols + 100 || |
|
|
|
if (imgPoint.x < -100 || imgPoint.x > sourceImage.image.cols + 100 || |
|
|
|
imgPoint.y < -100 || imgPoint.y > sourceImage.image.rows + 100) { |
|
|
|
imgPoint.y < -100 || imgPoint.y > sourceImage.image.rows + 100) { |
|
|
|
// 投影异常,记录日志用于调试
|
|
|
|
|
|
|
|
DEBUG_EXTRA("异常投影: 图像点(%.1f,%.1f) 超出图像范围(%dx%d)", |
|
|
|
|
|
|
|
imgPoint.x, imgPoint.y, sourceImage.image.cols, sourceImage.image.rows); |
|
|
|
|
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -10769,20 +10898,50 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(const LabelArr& faceLa |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 5. 添加后处理填充函数
|
|
|
|
// 添加后处理填充函数
|
|
|
|
FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), faceLabels, textureSize, colEmpty); |
|
|
|
FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), faceLabels, textureSize, colEmpty); |
|
|
|
|
|
|
|
|
|
|
|
// 6. 应用后处理
|
|
|
|
// 应用后处理
|
|
|
|
if (fSharpnessWeight > 0) { |
|
|
|
if (fSharpnessWeight > 0) { |
|
|
|
// ApplySharpening(textureAtlas, fSharpnessWeight);
|
|
|
|
// ApplySharpening(textureAtlas, fSharpnessWeight);
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("纹理图集生成完成: %u个面片, 纹理尺寸%dx%d", |
|
|
|
DEBUG_EXTRA("从原始图像生成纹理图集完成: 尺寸%dx%d, %u个面片", |
|
|
|
scene.mesh.faces.size(), textureSize, textureSize); |
|
|
|
textureSize, textureSize, scene.mesh.faces.size()); |
|
|
|
|
|
|
|
|
|
|
|
return textures; |
|
|
|
return textures; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Pixel8U MeshTexture::SampleFromExistingTextureAtBarycentric(FIndex faceID, const Point3f& barycentric, const Image8U3& existingTexture) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// 获取面在已有纹理中的UV坐标
|
|
|
|
|
|
|
|
// 注意:这里假设每个面在已有纹理中也有对应的UV坐标
|
|
|
|
|
|
|
|
// 我们需要从 scene.mesh.faceTexcoords 中获取这些UV坐标
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (faceID * 3 + 2 >= scene.mesh.faceTexcoords.size()) { |
|
|
|
|
|
|
|
return Pixel8U(0, 0, 0); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const TexCoord* existingUVs = &scene.mesh.faceTexcoords[faceID * 3]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 使用重心坐标计算采样点的UV坐标
|
|
|
|
|
|
|
|
Point2f texCoord( |
|
|
|
|
|
|
|
existingUVs[0].x * barycentric.x + |
|
|
|
|
|
|
|
existingUVs[1].x * barycentric.y + |
|
|
|
|
|
|
|
existingUVs[2].x * barycentric.z, |
|
|
|
|
|
|
|
existingUVs[0].y * barycentric.x + |
|
|
|
|
|
|
|
existingUVs[1].y * barycentric.y + |
|
|
|
|
|
|
|
existingUVs[2].y * barycentric.z |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 确保UV坐标在[0,1]范围内
|
|
|
|
|
|
|
|
texCoord.x = std::max(0.0f, std::min(1.0f, texCoord.x)); |
|
|
|
|
|
|
|
texCoord.y = std::max(0.0f, std::min(1.0f, texCoord.y)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 从已有纹理中采样
|
|
|
|
|
|
|
|
return SampleTextureBilinear(existingTexture, texCoord); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Mesh::Image8U3Arr MeshTexture::SampleFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, |
|
|
|
Mesh::Image8U3Arr MeshTexture::SampleFromExistingTextures(const LabelArr& faceLabels, const IIndexArr& views, |
|
|
|
unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) |
|
|
|
unsigned nTextureSizeMultiple, Pixel8U colEmpty, float fSharpnessWeight) |
|
|
|
{ |
|
|
|
{ |
|
|
|
|