回退到上个版本
This commit is contained in:
1 parent
01ef0f2a1d
commit
f787e874aa
1 file changed
+331
-51
+331
-51
@@ -492,6 +492,16 @@ public:
|
||||
const Mesh::TexCoordArr& existingTexcoords, // 添加已有UV参数
|
||||
const Mesh::TexIndexArr& existingTexindices // 添加已有纹理索引参数
|
||||
);
|
||||
void CheckColorChannels(const Image8U3& texture, const std::string& name);
|
||||
Mesh::Image8U3Arr GenerateTextureAtlasWith3DBridge(
|
||||
const LabelArr& faceLabels,
|
||||
const IIndexArr& views,
|
||||
const Mesh::Image8U3Arr& sourceTextures,
|
||||
const Mesh::TexCoordArr& sourceTexcoords,
|
||||
const Mesh::TexIndexArr& sourceTexindices,
|
||||
unsigned nTextureSizeMultiple,
|
||||
Pixel8U colEmpty,
|
||||
float fSharpnessWeight);
|
||||
Mesh::Image8U3Arr GenerateTextureAtlasFromUV(
|
||||
const Mesh::Image8U3Arr& sourceTextures, // 已有纹理数组
|
||||
const Mesh::TexCoordArr& sourceTexcoords, // 已有UV坐标
|
||||
@@ -9945,6 +9955,26 @@ bool SaveGeneratedTextures(const Mesh::Image8U3Arr& generatedTextures, const std
|
||||
|
||||
return true;
|
||||
}
|
||||
void MeshTexture::CheckColorChannels(const Image8U3& texture, const std::string& name) {
|
||||
if (texture.empty()) {
|
||||
DEBUG_EXTRA("%s: 纹理为空", name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查左上角几个像素的颜色
|
||||
for (int y = 0; y < std::min(3, texture.rows); ++y) {
|
||||
for (int x = 0; x < std::min(3, texture.cols); ++x) {
|
||||
const cv::Vec3b& pixel = texture.at<cv::Vec3b>(y, x);
|
||||
DEBUG_EXTRA("%s[%d,%d]: B=%d, G=%d, R=%d",
|
||||
name.c_str(), x, y, pixel[0], pixel[1], pixel[2]);
|
||||
}
|
||||
}
|
||||
|
||||
// 计算平均颜色
|
||||
cv::Scalar mean = cv::mean(texture);
|
||||
DEBUG_EXTRA("%s 平均颜色: B=%.1f, G=%.1f, R=%.1f",
|
||||
name.c_str(), mean[0], mean[1], mean[2]);
|
||||
}
|
||||
|
||||
bool MeshTexture::TextureWithExistingUV(
|
||||
const IIndexArr& views,
|
||||
@@ -9953,12 +9983,11 @@ bool MeshTexture::TextureWithExistingUV(
|
||||
unsigned nTextureSizeMultiple,
|
||||
Pixel8U colEmpty,
|
||||
float fSharpnessWeight,
|
||||
const Mesh::Image8U3Arr& existingTextures, // 添加已有纹理参数
|
||||
const Mesh::TexCoordArr& existingTexcoords, // 添加已有UV参数
|
||||
const Mesh::TexIndexArr& existingTexindices // 添加已有纹理索引参数
|
||||
)
|
||||
const Mesh::Image8U3Arr& existingTextures,
|
||||
const Mesh::TexCoordArr& existingTexcoords,
|
||||
const Mesh::TexIndexArr& existingTexindices)
|
||||
{
|
||||
DEBUG_EXTRA("TextureWithExistingUV - Using existing texture data");
|
||||
DEBUG_EXTRA("TextureWithExistingUV - 使用3D几何坐标作为桥梁");
|
||||
TD_TIMER_START();
|
||||
|
||||
// 1. 验证输入
|
||||
@@ -9967,38 +9996,64 @@ bool MeshTexture::TextureWithExistingUV(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (existingTextures.empty() || existingTexcoords.empty()) {
|
||||
if (existingTextures.empty()) {
|
||||
VERBOSE("error: no existing texture data provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证UV坐标数量匹配
|
||||
if (existingTexcoords.size() != scene.mesh.faceTexcoords.size()) {
|
||||
VERBOSE("error: UV coordinate count mismatch: existing=%zu, mesh=%zu",
|
||||
existingTexcoords.size(), scene.mesh.faceTexcoords.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_EXTRA("Processing %zu faces with existing texture data", scene.mesh.faces.size());
|
||||
|
||||
// 2. 生成纹理图集(从已有纹理采样,但使用模型原始UV)
|
||||
Mesh::Image8U3Arr generatedTextures = GenerateTextureAtlasFromUV(
|
||||
existingTextures,
|
||||
existingTexcoords,
|
||||
existingTexindices,
|
||||
nTextureSizeMultiple,
|
||||
colEmpty,
|
||||
fSharpnessWeight
|
||||
// 2. 为每个面选择最佳视图(从原始图像,而不是已有纹理)
|
||||
FaceDataViewArr facesDatas;
|
||||
if (!ListCameraFaces(facesDatas, fOutlierThreshold, nIgnoreMaskLabel, views, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 为每个面分配最佳视图
|
||||
LabelArr faceLabels(scene.mesh.faces.size());
|
||||
FOREACH(idxFace, scene.mesh.faces) {
|
||||
const FaceDataArr& faceDatas = facesDatas[idxFace];
|
||||
if (faceDatas.empty()) {
|
||||
faceLabels[idxFace] = 0; // 无视图可用
|
||||
continue;
|
||||
}
|
||||
|
||||
// 选择质量最高的视图
|
||||
float bestQuality = -1;
|
||||
IIndex bestView = NO_ID;
|
||||
for (const FaceData& data : faceDatas) {
|
||||
if (data.quality > bestQuality && !data.bInvalidFacesRelative) {
|
||||
bestQuality = data.quality;
|
||||
bestView = data.idxView;
|
||||
}
|
||||
}
|
||||
|
||||
faceLabels[idxFace] = (bestView != NO_ID) ? (bestView + 1) : 0;
|
||||
}
|
||||
|
||||
// 4. 生成纹理图集
|
||||
Mesh::Image8U3Arr generatedTextures = GenerateTextureAtlasWith3DBridge(
|
||||
faceLabels, views, existingTextures, existingTexcoords, existingTexindices,
|
||||
nTextureSizeMultiple, colEmpty, fSharpnessWeight
|
||||
);
|
||||
|
||||
if (!generatedTextures.empty()) {
|
||||
// 检查颜色通道
|
||||
CheckColorChannels(generatedTextures[0], "生成的纹理");
|
||||
|
||||
// 检查源纹理颜色通道
|
||||
if (!existingTextures.empty()) {
|
||||
CheckColorChannels(existingTextures[0], "源纹理");
|
||||
}
|
||||
|
||||
// 保存纹理
|
||||
scene.mesh.texturesDiffuse = std::move(generatedTextures);
|
||||
|
||||
// 设置面的纹理索引
|
||||
if (scene.mesh.texturesDiffuse.size() > 1) {
|
||||
scene.mesh.faceTexindices.resize(scene.mesh.faces.size());
|
||||
for (size_t i = 0; i < scene.mesh.faces.size(); ++i) {
|
||||
scene.mesh.faceTexindices[i] = 0; // 所有面使用第一个纹理
|
||||
scene.mesh.faceTexindices[i] = 0;
|
||||
}
|
||||
} else {
|
||||
scene.mesh.faceTexindices.Release();
|
||||
@@ -10006,7 +10061,6 @@ bool MeshTexture::TextureWithExistingUV(
|
||||
|
||||
DEBUG_EXTRA("Generated %zu textures from existing data",
|
||||
scene.mesh.texturesDiffuse.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10014,6 +10068,221 @@ bool MeshTexture::TextureWithExistingUV(
|
||||
return false;
|
||||
}
|
||||
|
||||
Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge(
|
||||
const LabelArr& faceLabels,
|
||||
const IIndexArr& views,
|
||||
const Mesh::Image8U3Arr& sourceTextures,
|
||||
const Mesh::TexCoordArr& sourceTexcoords,
|
||||
const Mesh::TexIndexArr& sourceTexindices,
|
||||
unsigned nTextureSizeMultiple,
|
||||
Pixel8U colEmpty,
|
||||
float fSharpnessWeight)
|
||||
{
|
||||
DEBUG_EXTRA("GenerateTextureAtlasWith3DBridge - 使用3D几何坐标作为桥梁");
|
||||
|
||||
// 1. 分析外部UV布局
|
||||
AABB2f uvBounds(true);
|
||||
FOREACH(i, scene.mesh.faceTexcoords) {
|
||||
const TexCoord& uv = scene.mesh.faceTexcoords[i];
|
||||
uvBounds.InsertFull(uv);
|
||||
}
|
||||
|
||||
// 确保UV在[0,1]范围内
|
||||
if (uvBounds.ptMin.x() < 0 || uvBounds.ptMin.y() < 0 ||
|
||||
uvBounds.ptMax.x() > 1 || uvBounds.ptMax.y() > 1) {
|
||||
DEBUG_EXTRA("UV超出[0,1]范围,进行归一化");
|
||||
uvBounds = AABB2f(true);
|
||||
for (size_t i = 0; i < scene.mesh.faceTexcoords.size(); i += 3) {
|
||||
for (int v = 0; v < 3; ++v) {
|
||||
const TexCoord& uv = scene.mesh.faceTexcoords[i + v];
|
||||
uvBounds.InsertFull(uv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 计算纹理尺寸
|
||||
const float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x();
|
||||
const float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y();
|
||||
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple);
|
||||
|
||||
// 创建目标纹理
|
||||
Mesh::Image8U3Arr textures;
|
||||
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize);
|
||||
// 注意:cv::Scalar 使用 BGR 顺序
|
||||
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());
|
||||
|
||||
// 添加调试信息
|
||||
DEBUG_EXTRA("colEmpty: R=%d, G=%d, B=%d", colEmpty.r, colEmpty.g, colEmpty.b);
|
||||
DEBUG_EXTRA("OpenCV图像通道顺序: BGR");
|
||||
|
||||
// 检查颜色通道顺序
|
||||
if (!sourceTextures.empty()) {
|
||||
const Image8U3& firstTex = sourceTextures[0];
|
||||
cv::Vec3b firstPixel = firstTex.at<cv::Vec3b>(0, 0);
|
||||
DEBUG_EXTRA("源纹理第一个像素: B=%d, G=%d, R=%d",
|
||||
firstPixel[0], firstPixel[1], firstPixel[2]);
|
||||
}
|
||||
|
||||
// 3. 统计信息
|
||||
int processedFaces = 0;
|
||||
int sampledPixels = 0;
|
||||
int failedFaces = 0;
|
||||
|
||||
// 4. 为每个面采样
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp parallel for schedule(dynamic) reduction(+:processedFaces, sampledPixels, failedFaces)
|
||||
#endif
|
||||
for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) {
|
||||
const FIndex faceID = (FIndex)idxFace;
|
||||
const Label label = faceLabels[faceID];
|
||||
|
||||
if (label == 0) {
|
||||
failedFaces++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const IIndex idxView = label - 1;
|
||||
if (idxView >= images.size()) {
|
||||
failedFaces++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取面的几何信息
|
||||
const TexCoord* meshUVs = &scene.mesh.faceTexcoords[faceID * 3];
|
||||
const Face& face = scene.mesh.faces[faceID];
|
||||
|
||||
// 获取源纹理信息
|
||||
const TexCoord* srcUVs = &sourceTexcoords[faceID * 3];
|
||||
const TexIndex textureIdx = sourceTexindices.empty() ? 0 : sourceTexindices[faceID];
|
||||
|
||||
if (textureIdx >= sourceTextures.size()) {
|
||||
failedFaces++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const Image8U3& sourceTexture = sourceTextures[textureIdx];
|
||||
|
||||
// 计算面的UV边界
|
||||
AABB2f faceBounds(true);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
faceBounds.InsertFull(meshUVs[i]);
|
||||
}
|
||||
|
||||
// 转换为像素坐标
|
||||
int startX = (int)(faceBounds.ptMin.x() * textureSize);
|
||||
int startY = (int)(faceBounds.ptMin.y() * textureSize);
|
||||
int endX = (int)(faceBounds.ptMax.x() * textureSize);
|
||||
int endY = (int)(faceBounds.ptMax.y() * textureSize);
|
||||
|
||||
// 边界检查
|
||||
startX = std::max(0, std::min(startX, textureSize - 1));
|
||||
startY = std::max(0, std::min(startY, textureSize - 1));
|
||||
endX = std::max(0, std::min(endX, textureSize - 1));
|
||||
endY = std::max(0, std::min(endY, textureSize - 1));
|
||||
|
||||
if (startX >= endX || startY >= endY) {
|
||||
failedFaces++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int faceSampledPixels = 0;
|
||||
|
||||
// 采样纹理
|
||||
for (int y = startY; y <= endY; ++y) {
|
||||
for (int x = startX; x <= endX; ++x) {
|
||||
const Point2f texCoord((x + 0.5f) / textureSize, (y + 0.5f) / textureSize);
|
||||
|
||||
// 计算重心坐标
|
||||
Point3f barycentric;
|
||||
if (!PointInTriangle(texCoord, meshUVs[0], meshUVs[1], meshUVs[2], barycentric)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 计算3D点
|
||||
const Vertex worldPoint =
|
||||
vertices[face[0]] * barycentric.x +
|
||||
vertices[face[1]] * barycentric.y +
|
||||
vertices[face[2]] * barycentric.z;
|
||||
|
||||
// 方案A:从原始图像采样
|
||||
Point2f imgPoint = ProjectPointWithAutoCorrection(images[idxView].camera, worldPoint, images[idxView]);
|
||||
|
||||
if (imgPoint.x < 0 || imgPoint.x >= images[idxView].image.cols ||
|
||||
imgPoint.y < 0 || imgPoint.y >= images[idxView].image.rows) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 修正:使用双线性插值采样
|
||||
const int x0 = (int)floor(imgPoint.x);
|
||||
const int y0 = (int)floor(imgPoint.y);
|
||||
const int x1 = std::min(x0 + 1, images[idxView].image.cols - 1);
|
||||
const int y1 = std::min(y0 + 1, images[idxView].image.rows - 1);
|
||||
|
||||
const float fx = imgPoint.x - x0;
|
||||
const float fy = imgPoint.y - y0;
|
||||
const float fx1 = 1.0f - fx;
|
||||
const float fy1 = 1.0f - fy;
|
||||
|
||||
// 采样四个点的颜色
|
||||
const cv::Vec3b& c00 = images[idxView].image.at<cv::Vec3b>(y0, x0);
|
||||
const cv::Vec3b& c01 = images[idxView].image.at<cv::Vec3b>(y0, x1);
|
||||
const cv::Vec3b& c10 = images[idxView].image.at<cv::Vec3b>(y1, x0);
|
||||
const cv::Vec3b& c11 = images[idxView].image.at<cv::Vec3b>(y1, x1);
|
||||
|
||||
// 双线性插值
|
||||
const cv::Vec3b color =
|
||||
c00 * (fx1 * fy1) +
|
||||
c01 * (fx * fy1) +
|
||||
c10 * (fx1 * fy) +
|
||||
c11 * (fx * fy);
|
||||
|
||||
// 注意:OpenCV是BGR顺序,而Pixel8U通常是RGB顺序
|
||||
// 这里我们需要确保颜色通道正确
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp critical
|
||||
#endif
|
||||
{
|
||||
// 方案1:直接使用BGR顺序(OpenCV默认)
|
||||
// textureAtlas.at<cv::Vec3b>(y, x) = color;
|
||||
|
||||
// 方案2:交换B和R通道(如果颜色偏蓝,说明B和R反了)
|
||||
// 将BGR转换为RGB
|
||||
textureAtlas.at<cv::Vec3b>(y, x) = cv::Vec3b(color[2], color[1], color[0]);
|
||||
}
|
||||
|
||||
faceSampledPixels++;
|
||||
}
|
||||
}
|
||||
|
||||
if (faceSampledPixels > 0) {
|
||||
processedFaces++;
|
||||
sampledPixels += faceSampledPixels;
|
||||
} else {
|
||||
failedFaces++;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_EXTRA("纹理采样完成: 成功 %d 个面, 失败 %d 个面, 采样 %d 像素",
|
||||
processedFaces, failedFaces, sampledPixels);
|
||||
|
||||
// 5. 填充空洞
|
||||
if (processedFaces > 0 && sampledPixels > 0) {
|
||||
// FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), textureSize, colEmpty);
|
||||
}
|
||||
|
||||
// 6. 锐化处理
|
||||
if (fSharpnessWeight > 0) {
|
||||
// ApplySharpening(textureAtlas, fSharpnessWeight);
|
||||
}
|
||||
|
||||
return textures;
|
||||
}
|
||||
|
||||
Point2f MeshTexture::ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage) {
|
||||
Point2f imgPoint = camera.ProjectPointP(worldPoint);
|
||||
|
||||
@@ -11171,41 +11440,52 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi
|
||||
|
||||
DEBUG_EXTRA("TextureMesh %b, %s", bUseExistingUV, strUVMeshFileName.c_str());
|
||||
if (bUseExistingUV && !strUVMeshFileName.empty()) {
|
||||
// 1. 生成临时纹理和UV
|
||||
Mesh::TexCoordArr existingTexcoords;
|
||||
Mesh::TexIndexArr existingTexindices;
|
||||
|
||||
texture.GenerateTextureForUV(bGlobalSeamLeveling, bLocalSeamLeveling, nTextureSizeMultiple, nRectPackingHeuristic, colEmpty, fSharpnessWeight, maxTextureSize, baseFileName, bOriginFaceview, this, existingTexcoords, existingTexindices);
|
||||
|
||||
|
||||
texture.GenerateTextureForUV(bGlobalSeamLeveling, bLocalSeamLeveling, nTextureSizeMultiple,
|
||||
nRectPackingHeuristic, colEmpty, fSharpnessWeight, maxTextureSize,
|
||||
baseFileName, bOriginFaceview, this, existingTexcoords, existingTexindices);
|
||||
|
||||
// 保存生成的纹理数据
|
||||
Mesh::Image8U3Arr existingTextures = texture.texturesDiffuseTemp;
|
||||
// Mesh::TexCoordArr existingTexcoords = mesh.faceTexcoords; // 保存临时计算的UV
|
||||
// Mesh::TexIndexArr existingTexindices = mesh.faceTexindices; // 保存纹理索引
|
||||
|
||||
VERBOSE("1faceTexcoords.size=%d, faces.size=%d", mesh.faceTexcoords.size(), mesh.faces.size() * 3);
|
||||
// 使用预计算UV模式
|
||||
if (!mesh.Load(MAKE_PATH_SAFE(strUVMeshFileName), true)) {
|
||||
VERBOSE("error: cannot load mesh file with UV coordinates");
|
||||
return false; // 注意:在成员函数中,返回 false 表示失败
|
||||
}
|
||||
|
||||
|
||||
// 2. 使用预计算UV模式加载网格
|
||||
if (!mesh.Load(MAKE_PATH_SAFE(strUVMeshFileName), true)) {
|
||||
VERBOSE("error: cannot load mesh file with UV coordinates");
|
||||
return false;
|
||||
}
|
||||
|
||||
VERBOSE("2faceTexcoords.size=%d, faces.size=%d", mesh.faceTexcoords.size(), mesh.faces.size() * 3);
|
||||
// mesh.CheckUVValid();
|
||||
//*
|
||||
// 确保网格包含UV坐标
|
||||
if (mesh.faceTexcoords.empty()) {
|
||||
VERBOSE("error: the specified mesh does not contain UV coordinates");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用新的纹理生成方法
|
||||
MeshTexture texture2(*this, nResolutionLevel, nMinResolution);
|
||||
if (!texture2.TextureWithExistingUV(views, nIgnoreMaskLabel, fOutlierThreshold, nTextureSizeMultiple, colEmpty, fSharpnessWeight, existingTextures, existingTexcoords, existingTexindices)){
|
||||
return false;
|
||||
}
|
||||
//*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mesh.faceTexcoords.empty()) {
|
||||
VERBOSE("error: the specified mesh does not contain UV coordinates");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 使用新的纹理生成方法
|
||||
MeshTexture texture2(*this, nResolutionLevel, nMinResolution);
|
||||
|
||||
// 关键:确保几何信息正确
|
||||
texture2.scene.mesh.vertices = mesh.vertices;
|
||||
texture2.scene.mesh.faces = mesh.faces;
|
||||
texture2.scene.mesh.faceTexcoords = mesh.faceTexcoords; // 使用外部UV
|
||||
|
||||
if (!texture2.TextureWithExistingUV(views, nIgnoreMaskLabel, fOutlierThreshold,
|
||||
nTextureSizeMultiple, colEmpty, fSharpnessWeight,
|
||||
existingTextures, existingTexcoords, existingTexindices)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 将生成的纹理赋值回场景网格
|
||||
mesh.texturesDiffuse = std::move(texture2.scene.mesh.texturesDiffuse);
|
||||
mesh.faceTexindices = std::move(texture2.scene.mesh.faceTexindices);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// mesh.CheckUVValid();
|
||||
|
||||
|
||||
Reference in new issue
Block a user