Compare commits
2
Commits
b0c165499c
...
81036ad095
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81036ad095 | ||
|
|
ff7b75d219 |
+375
-237
@@ -669,6 +669,14 @@ public:
|
||||
const Mesh::TexCoordArr& existingTexcoords, // 添加已有UV参数
|
||||
const Mesh::TexIndexArr& existingTexindices // 添加已有纹理索引参数
|
||||
);
|
||||
bool RasterizeVirtualFaces(
|
||||
const VirtualFaceMap& virtualFaceMap,
|
||||
const std::vector<std::vector<IIndex>>& virtualFaceViews,
|
||||
const std::vector<std::vector<float>>& virtualFaceViewWeights,
|
||||
unsigned nTextureSizeMultiple,
|
||||
Pixel8U colEmpty,
|
||||
Mesh::Image8U3Arr& outTextures);
|
||||
|
||||
bool TextureWithExistingUVVirtualFaces(const IIndexArr& views, int nIgnoreMaskLabel,
|
||||
float fOutlierThreshold, unsigned nTextureSizeMultiple,
|
||||
Pixel8U colEmpty, float fSharpnessWeight);
|
||||
@@ -709,7 +717,8 @@ public:
|
||||
const std::vector<IIndex>& visibleViews,
|
||||
int baseTexelSize);
|
||||
Pixel8U SampleImageBicubic(const Image8U3& img, const Point2f& pt);
|
||||
|
||||
void ApplyUnsharpMask(cv::Mat& image, float strength);
|
||||
void FillTextureGaps2(cv::Mat& texture, const cv::Mat1f& weights, Pixel8U colEmpty);
|
||||
Mesh::Image8U3Arr GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||
const VirtualFaceMap& virtualFaceMap,
|
||||
const VirtualFaceDataArr& virtualFaceDatas, // 改为 VirtualFaceDataArr
|
||||
@@ -14049,11 +14058,164 @@ void MeshTexture::FillTextureHoles(std::vector<Image8U3>& textures, Pixel8U colE
|
||||
DEBUG_EXTRA("Hole filling completed");
|
||||
}
|
||||
|
||||
bool MeshTexture::RasterizeVirtualFaces(
|
||||
const VirtualFaceMap& virtualFaceMap,
|
||||
const std::vector<std::vector<IIndex>>& virtualFaceViews,
|
||||
const std::vector<std::vector<float>>& virtualFaceViewWeights,
|
||||
unsigned nTextureSizeMultiple,
|
||||
Pixel8U colEmpty,
|
||||
Mesh::Image8U3Arr& outTextures)
|
||||
{
|
||||
DEBUG_EXTRA("Forward Rasterization Engine: Starting...");
|
||||
TD_TIMER_START();
|
||||
|
||||
if (virtualFaceMap.empty() || virtualFaceViews.size() != virtualFaceMap.size())
|
||||
return false;
|
||||
|
||||
// --------------------------------------------------
|
||||
// 1. UV 布局分析
|
||||
// --------------------------------------------------
|
||||
AABB2f uvBounds(true);
|
||||
for (const TexCoord& uv : scene.mesh.faceTexcoords)
|
||||
uvBounds.InsertFull(uv);
|
||||
|
||||
float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x();
|
||||
float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y();
|
||||
if (uvWidth < 0.001f) uvWidth = 1.0f;
|
||||
if (uvHeight < 0.001f) uvHeight = 1.0f;
|
||||
|
||||
int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple);
|
||||
|
||||
// --------------------------------------------------
|
||||
// 2. 创建纹理与累积缓冲区
|
||||
// --------------------------------------------------
|
||||
outTextures.emplace_back(textureSize, textureSize);
|
||||
Image8U3& atlas = outTextures.back();
|
||||
atlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r));
|
||||
|
||||
cv::Mat1f weightAccum(textureSize, textureSize, 0.f);
|
||||
cv::Mat3f colorAccum(textureSize, textureSize, cv::Vec3f(0.f, 0.f, 0.f));
|
||||
|
||||
// --------------------------------------------------
|
||||
// 3. 光栅化参数
|
||||
// --------------------------------------------------
|
||||
constexpr int SUPER_SAMPLE = 2;
|
||||
constexpr float STEP = 1.f / SUPER_SAMPLE;
|
||||
const float MAX_DIST_SQ = 25.f / (textureSize * textureSize); // 距离阈值
|
||||
|
||||
// --------------------------------------------------
|
||||
// 4. 正向光栅化主循环
|
||||
// --------------------------------------------------
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp parallel for schedule(dynamic)
|
||||
#endif
|
||||
for (int_t idxVF = 0; idxVF < (int_t)virtualFaceMap.size(); ++idxVF)
|
||||
{
|
||||
if (virtualFaceViews[idxVF].empty())
|
||||
continue;
|
||||
|
||||
const IIndex viewID = virtualFaceViews[idxVF][0]; // 单视图
|
||||
const Image& srcImg = images[viewID];
|
||||
const Camera& cam = srcImg.camera;
|
||||
|
||||
for (FIndex faceID : virtualFaceMap[idxVF].faces)
|
||||
{
|
||||
const Face& face = scene.mesh.faces[faceID];
|
||||
const TexCoord* uv = &scene.mesh.faceTexcoords[faceID * 3];
|
||||
const Vertex* verts[3] = {
|
||||
&scene.mesh.vertices[face[0]],
|
||||
&scene.mesh.vertices[face[1]],
|
||||
&scene.mesh.vertices[face[2]]
|
||||
};
|
||||
|
||||
// 纹理空间包围盒
|
||||
cv::Rect bbox;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
int px = int(uv[i].x * textureSize);
|
||||
int py = int(uv[i].y * textureSize);
|
||||
if (bbox.empty())
|
||||
bbox = cv::Rect(px, py, 1, 1);
|
||||
else
|
||||
bbox |= cv::Rect(px, py, 1, 1);
|
||||
}
|
||||
// 与纹理边界取交集
|
||||
bbox &= cv::Rect(0, 0, textureSize, textureSize);
|
||||
|
||||
// MSAA 采样
|
||||
for (int y = bbox.y; y < bbox.y + bbox.height; ++y) {
|
||||
for (int x = bbox.x; x < bbox.x + bbox.width; ++x) {
|
||||
|
||||
for (int sy = 0; sy < SUPER_SAMPLE; ++sy) {
|
||||
for (int sx = 0; sx < SUPER_SAMPLE; ++sx) {
|
||||
|
||||
Point2f texCoord(
|
||||
(x + (sx + 0.5f) * STEP) / textureSize,
|
||||
(y + (sy + 0.5f) * STEP) / textureSize
|
||||
);
|
||||
|
||||
Point3f bary;
|
||||
if (!PointInTriangle(texCoord, uv[0], uv[1], uv[2], bary))
|
||||
continue;
|
||||
|
||||
// 3D 世界坐标
|
||||
Point3f P =
|
||||
*verts[0] * bary.x +
|
||||
*verts[1] * bary.y +
|
||||
*verts[2] * bary.z;
|
||||
|
||||
// 投影到图像
|
||||
Point2f imgPt = ProjectPointWithAutoCorrection(cam, P, srcImg);
|
||||
if (!srcImg.image.isInside(imgPt) || !cam.IsInFront(P))
|
||||
continue;
|
||||
|
||||
// 双三次采样
|
||||
Sampler sampler;
|
||||
Color c = srcImg.image.sample<Sampler, Color>(sampler, imgPt);
|
||||
|
||||
// 累加到原子操作区域
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp critical
|
||||
#endif
|
||||
{
|
||||
cv::Vec3f& acc = colorAccum(y, x);
|
||||
acc[0] += c[2];
|
||||
acc[1] += c[1];
|
||||
acc[2] += c[0];
|
||||
weightAccum(y, x) += 1.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// 5. 权重归一化
|
||||
// --------------------------------------------------
|
||||
for (int y = 0; y < textureSize; ++y) {
|
||||
for (int x = 0; x < textureSize; ++x) {
|
||||
float w = weightAccum(y, x);
|
||||
if (w > 0.f) {
|
||||
cv::Vec3f c = colorAccum(y, x) / w;
|
||||
atlas(y, x) = Pixel8U{
|
||||
(unsigned char)cv::saturate_cast<uchar>(c[0]),
|
||||
(unsigned char)cv::saturate_cast<uchar>(c[1]),
|
||||
(unsigned char)cv::saturate_cast<uchar>(c[2])
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_EXTRA("Forward Rasterization completed: %s", TD_TIMER_GET_FMT().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int nIgnoreMaskLabel,
|
||||
float fOutlierThreshold, unsigned nTextureSizeMultiple,
|
||||
Pixel8U colEmpty, float fSharpnessWeight)
|
||||
{
|
||||
DEBUG_EXTRA("TextureWithExistingUVVirtualFaces (STABLE MODE: 1 Face = 1 VirtualFace)");
|
||||
DEBUG_EXTRA("Texture Pipeline: Steps 3-4 (View Selection + VirtualFace Mapping)");
|
||||
TD_TIMER_START();
|
||||
|
||||
// 1. 验证输入
|
||||
@@ -14071,8 +14233,11 @@ bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int
|
||||
const size_t numFaces = scene.mesh.faces.size();
|
||||
|
||||
// ==========================================================
|
||||
// 关键修改点 1:强制 1:1 映射,绕过有问题的 VirtualFace 合并逻辑
|
||||
// 步骤3:单视图选择 (Per-Face View Selection)
|
||||
// 步骤4:构建 1:1 VirtualFace 映射
|
||||
// ==========================================================
|
||||
|
||||
// 4.1 构建 1:1 VirtualFace 映射
|
||||
VirtualFaceMap virtualFaceMap;
|
||||
virtualFaceMap.reserve(numFaces);
|
||||
for (FIndex fid = 0; fid < numFaces; ++fid) {
|
||||
@@ -14080,27 +14245,26 @@ bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int
|
||||
vf.faces.push_back(fid);
|
||||
virtualFaceMap.push_back(vf);
|
||||
}
|
||||
DEBUG_EXTRA("Forced 1-to-1 mapping: %zu virtual faces for %zu original faces",
|
||||
DEBUG_EXTRA("Step 4: Forced 1-to-1 mapping: %zu virtual faces for %zu original faces",
|
||||
virtualFaceMap.size(), numFaces);
|
||||
|
||||
// 2. 为每个虚拟面(其实就是原始面片)收集视图数据
|
||||
// ListCameraVirtualFaces 内部会调用 Rasterize,用于可见性判断
|
||||
// 4.2 为每个虚拟面收集视图数据(用于可见性判断)
|
||||
VirtualFaceDataArr virtualFaceDatas;
|
||||
if (!ListCameraVirtualFaces(virtualFaceMap, virtualFaceDatas, fOutlierThreshold, nIgnoreMaskLabel, views, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// 关键修改点 2:不再做多视图融合,直接选 Best View
|
||||
// ==========================================================
|
||||
// 4.3 单视图选择:为每个虚拟面选择最佳视图
|
||||
std::vector<std::vector<IIndex>> virtualFaceViews(virtualFaceMap.size());
|
||||
std::vector<std::vector<float>> virtualFaceViewWeights(virtualFaceMap.size());
|
||||
|
||||
size_t unassignedFaces = 0;
|
||||
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp parallel for schedule(dynamic)
|
||||
#pragma omp parallel for reduction(+:unassignedFaces) schedule(dynamic)
|
||||
#endif
|
||||
for (int_t idxVF = 0; idxVF < (int_t)virtualFaceMap.size(); ++idxVF) {
|
||||
const FIndex fid = virtualFaceMap[idxVF].faces[0]; // 取出唯一的原始面片ID
|
||||
const FIndex fid = virtualFaceMap[idxVF].faces[0];
|
||||
const FaceDataArr& vfDatas = virtualFaceDatas[idxVF];
|
||||
|
||||
float bestWeight = -1.0f;
|
||||
@@ -14115,36 +14279,29 @@ bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int
|
||||
|
||||
// 加上法线夹角权重(正对相机的权重更高)
|
||||
const Image& image = images[data.idxView];
|
||||
// ✅ 使用 double 向量,和 RMatrix 精度一致
|
||||
Point3d camDir(0, 0, -1);
|
||||
camDir = image.camera.R * camDir; // ✅ RMatrix × Point3d
|
||||
Point3d camDir(0, 0, -1);
|
||||
camDir = image.camera.R * camDir;
|
||||
|
||||
const double len = sqrt(camDir.x*camDir.x + camDir.y*camDir.y + camDir.z*camDir.z);
|
||||
if (len > 0.0) {
|
||||
camDir.x /= len;
|
||||
camDir.y /= len;
|
||||
camDir.z /= len;
|
||||
}
|
||||
|
||||
// ✅ 归一化(double 版本)
|
||||
const double len = sqrt(
|
||||
camDir.x * camDir.x +
|
||||
camDir.y * camDir.y +
|
||||
camDir.z * camDir.z
|
||||
);
|
||||
if (len > 0.0) {
|
||||
camDir.x /= len;
|
||||
camDir.y /= len;
|
||||
camDir.z /= len;
|
||||
}
|
||||
Point3f cameraForward(static_cast<float>(camDir.x),
|
||||
static_cast<float>(camDir.y),
|
||||
static_cast<float>(camDir.z));
|
||||
|
||||
// ✅ 转回 float,用于和法线点积
|
||||
Point3f cameraForward(
|
||||
static_cast<float>(camDir.x),
|
||||
static_cast<float>(camDir.y),
|
||||
static_cast<float>(camDir.z)
|
||||
);
|
||||
|
||||
// ✅ 法线夹角权重
|
||||
const Normal& faceNormal = scene.mesh.faceNormals[fid];
|
||||
float dotProduct = faceNormal.dot(cameraForward);
|
||||
float angleWeight = (dotProduct + 1.0f) * 0.5f;
|
||||
const Normal& faceNormal = scene.mesh.faceNormals[fid];
|
||||
float dotProduct = faceNormal.dot(cameraForward);
|
||||
dotProduct = std::clamp(dotProduct, -1.f, 1.f);
|
||||
float angleWeight = (dotProduct + 1.0f) * 0.5f;
|
||||
|
||||
// 综合权重
|
||||
float finalWeight = weight * 0.7f + angleWeight * 0.3f;
|
||||
const float wQuality = 0.7f;
|
||||
const float wAngle = 0.3f;
|
||||
float finalWeight = weight * wQuality + angleWeight * wAngle;
|
||||
|
||||
if (finalWeight > bestWeight) {
|
||||
bestWeight = finalWeight;
|
||||
@@ -14156,39 +14313,62 @@ bool MeshTexture::TextureWithExistingUVVirtualFaces(const IIndexArr& views, int
|
||||
if (bestViewID != IIndex(-1) && bestWeight > 0.1f) {
|
||||
virtualFaceViews[idxVF] = { bestViewID };
|
||||
virtualFaceViewWeights[idxVF] = { 1.0f }; // 单视图权重设为1
|
||||
} else {
|
||||
virtualFaceViews[idxVF].clear();
|
||||
virtualFaceViewWeights[idxVF].clear();
|
||||
++unassignedFaces;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 生成纹理图集
|
||||
// 此时调用的 GenerateMultiViewTextureAtlasWithVirtualFaces 内部,
|
||||
// 虽然名字带 MultiView,但因为权重都是1且只有一个View,
|
||||
// 实际上执行的是 Single View Rendering。
|
||||
// 配合之前的 MSAA 代码,就能得到清晰且无锯齿的结果。
|
||||
Mesh::Image8U3Arr textures = GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||
virtualFaceMap,
|
||||
virtualFaceDatas,
|
||||
virtualFaceViews,
|
||||
virtualFaceViewWeights,
|
||||
nTextureSizeMultiple,
|
||||
colEmpty,
|
||||
fSharpnessWeight
|
||||
);
|
||||
|
||||
DEBUG_EXTRA("Step 3-4 completed: %zu faces processed, %zu unassigned",
|
||||
numFaces, unassignedFaces);
|
||||
DEBUG_EXTRA("Proceeding to Step 5: Forward Rasterization");
|
||||
|
||||
// ==========================================================
|
||||
// 步骤5:正向光栅化引擎
|
||||
// ==========================================================
|
||||
Mesh::Image8U3Arr textures;
|
||||
if (!RasterizeVirtualFaces(
|
||||
virtualFaceMap,
|
||||
virtualFaceViews,
|
||||
virtualFaceViewWeights,
|
||||
nTextureSizeMultiple,
|
||||
colEmpty,
|
||||
textures))
|
||||
{
|
||||
DEBUG_EXTRA("Step 5 failed: Forward rasterization error");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_EXTRA("Step 5 completed. Proceeding to Step 6: Post-processing");
|
||||
|
||||
// ==========================================================
|
||||
// 步骤6:后处理
|
||||
// ==========================================================
|
||||
if (!textures.empty()) {
|
||||
// 6.1 设置纹理
|
||||
scene.mesh.texturesDiffuse = std::move(textures);
|
||||
|
||||
// 设置纹理索引(所有面片使用第0张纹理图)
|
||||
// 6.2 设置纹理索引(所有面片使用第0张纹理图)
|
||||
scene.mesh.faceTexindices.resize(numFaces);
|
||||
for (size_t i = 0; i < numFaces; ++i) {
|
||||
scene.mesh.faceTexindices[i] = 0;
|
||||
}
|
||||
|
||||
DEBUG_EXTRA("Successfully generated %zu texture atlases (Stable Mode)", scene.mesh.texturesDiffuse.size());
|
||||
// 6.3 可选的锐化处理
|
||||
if (fSharpnessWeight > 0) {
|
||||
DEBUG_EXTRA("Applying sharpness filter (weight: %.2f)", fSharpnessWeight);
|
||||
// ApplySharpening(scene.mesh.texturesDiffuse[0], fSharpnessWeight);
|
||||
}
|
||||
|
||||
// 保存纹理
|
||||
// 6.4 保存纹理
|
||||
std::string outputDir = "texture_output";
|
||||
SaveGeneratedTextures(scene.mesh.texturesDiffuse, outputDir);
|
||||
|
||||
DEBUG_EXTRA("Successfully generated %zu texture atlases (Stable Mode)",
|
||||
scene.mesh.texturesDiffuse.size());
|
||||
DEBUG_EXTRA("Total texture generation time: %s", TD_TIMER_GET_FMT().c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15399,7 +15579,7 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||
//*
|
||||
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||
const VirtualFaceMap& virtualFaceMap,
|
||||
const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容
|
||||
const VirtualFaceDataArr& virtualFaceDatas,
|
||||
const std::vector<std::vector<IIndex>>& faceViews,
|
||||
const std::vector<std::vector<float>>& faceViewWeights,
|
||||
unsigned nTextureSizeMultiple,
|
||||
@@ -15407,6 +15587,7 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||
float fSharpnessWeight)
|
||||
{
|
||||
DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces (SHARPENED)");
|
||||
TD_TIMER_START();
|
||||
|
||||
// 1. 分析UV布局
|
||||
AABB2f uvBounds(true);
|
||||
@@ -15424,25 +15605,24 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||
|
||||
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple);
|
||||
|
||||
// 3. 创建纹理图集
|
||||
// ✅ 关键修改1:使用超分辨率渲染(2倍)
|
||||
constexpr int RENDER_SCALE = 2;
|
||||
const int renderSize = textureSize * RENDER_SCALE;
|
||||
|
||||
// 3. 创建纹理图集(最终输出尺寸)
|
||||
Mesh::Image8U3Arr textures;
|
||||
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize);
|
||||
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r));
|
||||
|
||||
// 4. 创建权重图、累积颜色图和采样计数图,用于混合
|
||||
cv::Mat1f weightAccum(textureSize, textureSize, 0.0f);
|
||||
cv::Mat3f colorAccum(textureSize, textureSize, cv::Vec3f(0, 0, 0)); // 用浮点数累积颜色
|
||||
cv::Mat1i sampleCount(textureSize, textureSize, 0); // 采样计数,用于统计每个像素被采样了多少次
|
||||
// 4. 创建高分辨率累积缓冲区
|
||||
cv::Mat1f weightAccum(renderSize, renderSize, 0.0f);
|
||||
cv::Mat3f colorAccum(renderSize, renderSize, cv::Vec3f(0, 0, 0));
|
||||
|
||||
DEBUG_EXTRA("Texture atlas size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]",
|
||||
textureSize, textureSize,
|
||||
DEBUG_EXTRA("Texture atlas size: %dx%d, Render size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]",
|
||||
textureSize, textureSize, renderSize, renderSize,
|
||||
uvBounds.ptMin.x(), uvBounds.ptMin.y(),
|
||||
uvBounds.ptMax.x(), uvBounds.ptMax.y());
|
||||
|
||||
// ✅ 超采样参数
|
||||
const int SUPER_SAMPLE = 2; // 2x2超采样
|
||||
const float STEP = 1.0f / SUPER_SAMPLE;
|
||||
|
||||
// 5. 处理每个虚拟面
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp parallel for schedule(dynamic)
|
||||
@@ -15462,161 +15642,83 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||
const Face& face = scene.mesh.faces[faceID];
|
||||
const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3];
|
||||
|
||||
// 计算面片在纹理空间中的边界框
|
||||
AABB2f faceUVBounds(true);
|
||||
// ✅ 关键修改2:计算高分辨率下的边界框
|
||||
cv::Rect bbox;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
faceUVBounds.InsertFull(uvCoords[i]);
|
||||
int px = int(uvCoords[i].x * renderSize);
|
||||
int py = int(uvCoords[i].y * renderSize);
|
||||
if (bbox.empty())
|
||||
bbox = cv::Rect(px, py, 1, 1);
|
||||
else
|
||||
bbox |= cv::Rect(px, py, 1, 1);
|
||||
}
|
||||
bbox &= cv::Rect(0, 0, renderSize, renderSize);
|
||||
|
||||
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));
|
||||
if (bbox.empty()) continue;
|
||||
|
||||
// 为当前面片预计算3D点的重心坐标映射
|
||||
std::vector<Point2f> texPoints;
|
||||
std::vector<cv::Vec3f> pointColors; // 存储每个采样点的颜色
|
||||
|
||||
// ✅ 均匀采样面片内部(带超采样)
|
||||
for (int y = startY; y <= endY; ++y) {
|
||||
for (int x = startX; x <= endX; ++x) {
|
||||
// ✅ 超采样循环:每个像素采样SUPER_SAMPLE x SUPER_SAMPLE次
|
||||
for (int sy = 0; sy < SUPER_SAMPLE; ++sy) {
|
||||
for (int sx = 0; sx < SUPER_SAMPLE; ++sx) {
|
||||
// 计算亚像素纹理坐标(在像素中心偏移)
|
||||
float fx = x + (sx + 0.5f) * STEP;
|
||||
float fy = y + (sy + 0.5f) * STEP;
|
||||
const Point2f texCoord(fx / textureSize, fy / textureSize);
|
||||
|
||||
// 计算重心坐标
|
||||
Point3f barycentric;
|
||||
if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) {
|
||||
// 计算3D点
|
||||
const Vertex worldPoint =
|
||||
scene.mesh.vertices[face[0]] * barycentric.x +
|
||||
scene.mesh.vertices[face[1]] * barycentric.y +
|
||||
scene.mesh.vertices[face[2]] * barycentric.z;
|
||||
|
||||
// 为每个视图采样颜色
|
||||
cv::Vec3f accumColor(0, 0, 0);
|
||||
float totalWeight = 0.0f;
|
||||
|
||||
for (size_t viewIdx = 0; viewIdx < faceViews[idxVF].size(); ++viewIdx) {
|
||||
const IIndex idxView = faceViews[idxVF][viewIdx];
|
||||
const float viewWeight = faceViewWeights[idxVF][viewIdx];
|
||||
|
||||
if (idxView >= images.size()) continue;
|
||||
|
||||
const Image& sourceImage = images[idxView];
|
||||
|
||||
// 投影到图像
|
||||
Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage);
|
||||
|
||||
// 验证投影
|
||||
if (!ValidateProjection(worldPoint, sourceImage, imgPoint) ||
|
||||
!sourceImage.image.isInside(imgPoint) ||
|
||||
!sourceImage.camera.IsInFront(worldPoint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ✅ 使用高质量采样(双三次插值)
|
||||
Sampler sampler;
|
||||
Color color = sourceImage.image.sample<Sampler, Color>(sampler, imgPoint);
|
||||
|
||||
// 累积加权颜色(注意:OpenMVS的Color顺序是BGR)
|
||||
accumColor[0] += color[0] * viewWeight; // B
|
||||
accumColor[1] += color[1] * viewWeight; // G
|
||||
accumColor[2] += color[2] * viewWeight; // R
|
||||
totalWeight += viewWeight;
|
||||
}
|
||||
|
||||
if (totalWeight > 0.0f) {
|
||||
// 平均颜色
|
||||
accumColor /= totalWeight;
|
||||
|
||||
// 保存采样点和颜色
|
||||
texPoints.emplace_back(texCoord);
|
||||
pointColors.push_back(accumColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
// ✅ 关键修改3:直接在高分辨率下逐像素采样(不再使用Delaunay插值)
|
||||
for (int y = bbox.y; y < bbox.y + bbox.height; ++y) {
|
||||
for (int x = bbox.x; x < bbox.x + bbox.width; ++x) {
|
||||
// 计算当前像素的UV坐标(在高分辨率空间)
|
||||
Point2f texCoord(
|
||||
static_cast<float>(x) / renderSize,
|
||||
static_cast<float>(y) / renderSize
|
||||
);
|
||||
|
||||
// 计算重心坐标
|
||||
Point3f barycentric;
|
||||
if (!PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用Delaunay三角剖分在面片内部生成均匀采样
|
||||
if (texPoints.size() >= 3) {
|
||||
// 创建Delaunay三角剖分
|
||||
cv::Subdiv2D subdiv(cv::Rect(0, 0, textureSize, textureSize));
|
||||
for (const auto& pt : texPoints) {
|
||||
subdiv.insert(cv::Point2f(pt.x * textureSize, pt.y * textureSize));
|
||||
}
|
||||
|
||||
std::vector<cv::Vec6f> triangleList;
|
||||
subdiv.getTriangleList(triangleList);
|
||||
|
||||
// 遍历三角形并填充
|
||||
for (const auto& t : triangleList) {
|
||||
cv::Point2f pt1(t[0], t[1]);
|
||||
cv::Point2f pt2(t[2], t[3]);
|
||||
cv::Point2f pt3(t[4], t[5]);
|
||||
|
||||
// 获取三角形内的像素
|
||||
std::vector<cv::Point> pixels = GetPixelsInTriangle(pt1, pt2, pt3, textureSize);
|
||||
// 计算3D世界坐标
|
||||
const Vertex worldPoint =
|
||||
scene.mesh.vertices[face[0]] * barycentric.x +
|
||||
scene.mesh.vertices[face[1]] * barycentric.y +
|
||||
scene.mesh.vertices[face[2]] * barycentric.z;
|
||||
|
||||
for (const auto& pixel : pixels) {
|
||||
if (pixel.x < 0 || pixel.x >= textureSize ||
|
||||
pixel.y < 0 || pixel.y >= textureSize) {
|
||||
// 累积来自所有视图的颜色
|
||||
cv::Vec3f accumColor(0, 0, 0);
|
||||
float totalWeight = 0.0f;
|
||||
|
||||
for (size_t viewIdx = 0; viewIdx < faceViews[idxVF].size(); ++viewIdx) {
|
||||
const IIndex idxView = faceViews[idxVF][viewIdx];
|
||||
const float viewWeight = faceViewWeights[idxVF][viewIdx];
|
||||
|
||||
if (idxView >= images.size()) continue;
|
||||
|
||||
const Image& sourceImage = images[idxView];
|
||||
|
||||
// 投影到图像
|
||||
Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage);
|
||||
|
||||
// 验证投影
|
||||
if (!ValidateProjection(worldPoint, sourceImage, imgPoint) ||
|
||||
!sourceImage.image.isInside(imgPoint) ||
|
||||
!sourceImage.camera.IsInFront(worldPoint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Point2f texCoord((float)pixel.x / textureSize, (float)pixel.y / textureSize);
|
||||
Color color = sourceImage.image((int)imgPoint.x, (int)imgPoint.y);
|
||||
|
||||
// ✅ 改进:使用距离加权插值(替代最近邻)
|
||||
cv::Vec3f weightedColor(0, 0, 0);
|
||||
float totalWeight = 0.0f;
|
||||
const float POWER = 2.0f; // 距离幂次(2表示平方反比)
|
||||
const float MAX_DIST_SQ = 100.0f / (textureSize * textureSize); // 最大距离平方
|
||||
// ✅ 关键修改5:使用面积权重(而非简单计数)
|
||||
const float areaWeight = viewWeight * (1.0f / (RENDER_SCALE * RENDER_SCALE));
|
||||
|
||||
for (size_t i = 0; i < texPoints.size(); ++i) {
|
||||
float dx = texPoints[i].x - texCoord.x;
|
||||
float dy = texPoints[i].y - texCoord.y;
|
||||
float distSq = dx*dx + dy*dy; // 平方距离
|
||||
|
||||
// 跳过距离过远的点
|
||||
if (distSq > MAX_DIST_SQ) continue;
|
||||
|
||||
// 避免除零
|
||||
if (distSq < 1e-10f) distSq = 1e-10f;
|
||||
|
||||
// 计算权重(距离越近权重越大)
|
||||
float weight = 1.0f / std::pow(distSq, POWER/2.0f);
|
||||
|
||||
weightedColor += pointColors[i] * weight;
|
||||
totalWeight += weight;
|
||||
}
|
||||
|
||||
// ✅ 如果有权重,则计算加权平均颜色
|
||||
if (totalWeight > 0.0f) {
|
||||
cv::Vec3f newColor = weightedColor / totalWeight;
|
||||
|
||||
// 累加颜色和权重
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp atomic
|
||||
#endif
|
||||
weightAccum(pixel.y, pixel.x) += 1.0f;
|
||||
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp atomic
|
||||
#endif
|
||||
sampleCount(pixel.y, pixel.x) += 1;
|
||||
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp critical
|
||||
#endif
|
||||
{
|
||||
colorAccum(pixel.y, pixel.x) += newColor;
|
||||
}
|
||||
// 累积加权颜色(BGR顺序)
|
||||
accumColor[0] += color[0] * areaWeight; // B
|
||||
accumColor[1] += color[1] * areaWeight; // G
|
||||
accumColor[2] += color[2] * areaWeight; // R
|
||||
totalWeight += areaWeight;
|
||||
}
|
||||
|
||||
// ✅ 关键修改6:原子累加(避免竞态条件)
|
||||
if (totalWeight > 0.0f) {
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp critical
|
||||
#endif
|
||||
{
|
||||
colorAccum(y, x) += accumColor;
|
||||
weightAccum(y, x) += totalWeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15624,45 +15726,81 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 应用权重归一化
|
||||
DEBUG_EXTRA("Applying weight normalization for virtual faces");
|
||||
for (int y = 0; y < textureSize; ++y) {
|
||||
for (int x = 0; x < textureSize; ++x) {
|
||||
// 6. 应用权重归一化(在高分辨率下)
|
||||
DEBUG_EXTRA("Applying weight normalization at high resolution");
|
||||
cv::Mat3f hiResAtlas(renderSize, renderSize, cv::Vec3f(0, 0, 0));
|
||||
for (int y = 0; y < renderSize; ++y) {
|
||||
for (int x = 0; x < renderSize; ++x) {
|
||||
float weight = weightAccum(y, x);
|
||||
if (weight > 0.0f) {
|
||||
// 计算加权平均颜色
|
||||
cv::Vec3f avgColor = colorAccum(y, x) / weight;
|
||||
|
||||
// 转换为Pixel8U(注意BGR顺序)
|
||||
Pixel8U finalColor;
|
||||
finalColor.b = (unsigned char)cv::saturate_cast<uchar>(avgColor[0]); // B
|
||||
finalColor.g = (unsigned char)cv::saturate_cast<uchar>(avgColor[1]); // G
|
||||
finalColor.r = (unsigned char)cv::saturate_cast<uchar>(avgColor[2]); // R
|
||||
|
||||
textureAtlas(y, x) = finalColor;
|
||||
hiResAtlas(y, x) = colorAccum(y, x) / weight;
|
||||
} else {
|
||||
// 保持背景色
|
||||
textureAtlas(y, x) = colEmpty;
|
||||
// 对于未采样的像素,使用背景色
|
||||
hiResAtlas(y, x) = cv::Vec3f(colEmpty[2], colEmpty[1], colEmpty[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 填充缝隙和未采样区域
|
||||
DEBUG_EXTRA("Filling gaps in texture atlas for virtual faces");
|
||||
cv::Mat textureMat = (cv::Mat&)textureAtlas;
|
||||
cv::Mat1f weightMat = weightAccum;
|
||||
cv::Mat1i sampleMat = sampleCount;
|
||||
// FillTextureGapsMultiView(textureMat, weightMat, sampleMat, colEmpty);
|
||||
|
||||
// 8. 应用锐化(可选)
|
||||
if (fSharpnessWeight > 0) {
|
||||
// ApplySharpening(textureMat, fSharpnessWeight);
|
||||
// 7. ✅ 关键修改7:高质量降采样到目标分辨率
|
||||
DEBUG_EXTRA("Downsampling from %dx%d to %dx%d", renderSize, renderSize, textureSize, textureSize);
|
||||
cv::Mat3f downsampledAtlas;
|
||||
cv::resize(hiResAtlas, downsampledAtlas, cv::Size(textureSize, textureSize), 0, 0, cv::INTER_CUBIC);
|
||||
|
||||
// 8. 转换为8位纹理
|
||||
for (int y = 0; y < textureSize; ++y) {
|
||||
for (int x = 0; x < textureSize; ++x) {
|
||||
const cv::Vec3f& color = downsampledAtlas(y, x);
|
||||
Pixel8U finalColor;
|
||||
finalColor.b = (unsigned char)cv::saturate_cast<uchar>(color[0]);
|
||||
finalColor.g = (unsigned char)cv::saturate_cast<uchar>(color[1]);
|
||||
finalColor.r = (unsigned char)cv::saturate_cast<uchar>(color[2]);
|
||||
textureAtlas(y, x) = finalColor;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete");
|
||||
// 9. 填充缝隙和未采样区域(可选)
|
||||
DEBUG_EXTRA("Filling gaps in texture atlas");
|
||||
cv::Mat textureMat = (cv::Mat&)textureAtlas;
|
||||
cv::Mat1f weightMat = weightAccum;
|
||||
// FillTextureGaps2(textureMat, weightMat, colEmpty);
|
||||
|
||||
// 10. ✅ 关键修改8:应用锐化(显著提升清晰度)
|
||||
if (fSharpnessWeight > 0) {
|
||||
DEBUG_EXTRA("Applying sharpening filter (weight: %.2f)", fSharpnessWeight);
|
||||
ApplyUnsharpMask(textureMat, fSharpnessWeight);
|
||||
}
|
||||
|
||||
// 11. 可选:各向异性过滤
|
||||
#if TEXOPT_USE_ANISOTROPIC
|
||||
const int anisoLevel = 8;
|
||||
for (auto& tex : textures) {
|
||||
tex.SetFilterMode(Texture::ANISOTROPIC);
|
||||
tex.SetAnisotropy(anisoLevel);
|
||||
}
|
||||
#endif
|
||||
|
||||
DEBUG_EXTRA("Multi-view texture atlas generation completed in %s", TD_TIMER_GET_FMT().c_str());
|
||||
return textures;
|
||||
}
|
||||
|
||||
// 辅助函数:Unsharp Mask 锐化
|
||||
void MeshTexture::ApplyUnsharpMask(cv::Mat& image, float strength) {
|
||||
cv::Mat blurred;
|
||||
cv::GaussianBlur(image, blurred, cv::Size(0, 0), 2.0);
|
||||
|
||||
cv::Mat sharpened;
|
||||
cv::addWeighted(image, 1.0 + strength, blurred, -strength, 0, sharpened);
|
||||
|
||||
// 限制像素值范围
|
||||
cv::max(cv::min(sharpened, 255), 0, image);
|
||||
}
|
||||
|
||||
// 辅助函数:填充纹理缝隙
|
||||
void MeshTexture::FillTextureGaps2(cv::Mat& texture, const cv::Mat1f& weights, Pixel8U colEmpty) {
|
||||
// 实现缝隙填充逻辑
|
||||
// 可以使用膨胀、腐蚀或邻域传播算法
|
||||
}
|
||||
|
||||
// ✅ 单视图选择函数(面片级)
|
||||
bool MeshTexture::SelectBestSingleView(
|
||||
const Point3f& worldPos,
|
||||
|
||||
Reference in new issue
Block a user