|
|
|
@ -717,7 +717,8 @@ public: |
|
|
|
const std::vector<IIndex>& visibleViews, |
|
|
|
const std::vector<IIndex>& visibleViews, |
|
|
|
int baseTexelSize); |
|
|
|
int baseTexelSize); |
|
|
|
Pixel8U SampleImageBicubic(const Image8U3& img, const Point2f& pt); |
|
|
|
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( |
|
|
|
Mesh::Image8U3Arr GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
const VirtualFaceDataArr& virtualFaceDatas, // 改为 VirtualFaceDataArr
|
|
|
|
const VirtualFaceDataArr& virtualFaceDatas, // 改为 VirtualFaceDataArr
|
|
|
|
@ -15578,7 +15579,7 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
//*
|
|
|
|
//*
|
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容
|
|
|
|
const VirtualFaceDataArr& virtualFaceDatas, |
|
|
|
const std::vector<std::vector<IIndex>>& faceViews, |
|
|
|
const std::vector<std::vector<IIndex>>& faceViews, |
|
|
|
const std::vector<std::vector<float>>& faceViewWeights, |
|
|
|
const std::vector<std::vector<float>>& faceViewWeights, |
|
|
|
unsigned nTextureSizeMultiple, |
|
|
|
unsigned nTextureSizeMultiple, |
|
|
|
@ -15586,6 +15587,7 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
float fSharpnessWeight) |
|
|
|
float fSharpnessWeight) |
|
|
|
{ |
|
|
|
{ |
|
|
|
DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces (SHARPENED)"); |
|
|
|
DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces (SHARPENED)"); |
|
|
|
|
|
|
|
TD_TIMER_START(); |
|
|
|
|
|
|
|
|
|
|
|
// 1. 分析UV布局
|
|
|
|
// 1. 分析UV布局
|
|
|
|
AABB2f uvBounds(true); |
|
|
|
AABB2f uvBounds(true); |
|
|
|
@ -15603,25 +15605,24 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
|
|
|
|
|
|
|
|
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); |
|
|
|
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; |
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); |
|
|
|
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); |
|
|
|
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
|
|
|
|
|
|
|
// 4. 创建权重图、累积颜色图和采样计数图,用于混合
|
|
|
|
// 4. 创建高分辨率累积缓冲区
|
|
|
|
cv::Mat1f weightAccum(textureSize, textureSize, 0.0f); |
|
|
|
cv::Mat1f weightAccum(renderSize, renderSize, 0.0f); |
|
|
|
cv::Mat3f colorAccum(textureSize, textureSize, cv::Vec3f(0, 0, 0)); // 用浮点数累积颜色
|
|
|
|
cv::Mat3f colorAccum(renderSize, renderSize, cv::Vec3f(0, 0, 0)); |
|
|
|
cv::Mat1i sampleCount(textureSize, textureSize, 0); // 采样计数,用于统计每个像素被采样了多少次
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Texture atlas size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]", |
|
|
|
DEBUG_EXTRA("Texture atlas size: %dx%d, Render size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]", |
|
|
|
textureSize, textureSize, |
|
|
|
textureSize, textureSize, renderSize, renderSize, |
|
|
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(), |
|
|
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(), |
|
|
|
uvBounds.ptMax.x(), uvBounds.ptMax.y()); |
|
|
|
uvBounds.ptMax.x(), uvBounds.ptMax.y()); |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 超采样参数
|
|
|
|
|
|
|
|
const int SUPER_SAMPLE = 2; // 2x2超采样
|
|
|
|
|
|
|
|
const float STEP = 1.0f / SUPER_SAMPLE; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 处理每个虚拟面
|
|
|
|
// 5. 处理每个虚拟面
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
@ -15641,161 +15642,83 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
const Face& face = scene.mesh.faces[faceID]; |
|
|
|
const Face& face = scene.mesh.faces[faceID]; |
|
|
|
const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; |
|
|
|
const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; |
|
|
|
|
|
|
|
|
|
|
|
// 计算面片在纹理空间中的边界框
|
|
|
|
// ✅ 关键修改2:计算高分辨率下的边界框
|
|
|
|
AABB2f faceUVBounds(true); |
|
|
|
cv::Rect bbox; |
|
|
|
for (int i = 0; i < 3; ++i) { |
|
|
|
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)); |
|
|
|
if (bbox.empty()) continue; |
|
|
|
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)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 为当前面片预计算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) { |
|
|
|
// ✅ 关键修改3:直接在高分辨率下逐像素采样(不再使用Delaunay插值)
|
|
|
|
// 平均颜色
|
|
|
|
for (int y = bbox.y; y < bbox.y + bbox.height; ++y) { |
|
|
|
accumColor /= totalWeight; |
|
|
|
for (int x = bbox.x; x < bbox.x + bbox.width; ++x) { |
|
|
|
|
|
|
|
// 计算当前像素的UV坐标(在高分辨率空间)
|
|
|
|
|
|
|
|
Point2f texCoord( |
|
|
|
|
|
|
|
static_cast<float>(x) / renderSize, |
|
|
|
|
|
|
|
static_cast<float>(y) / renderSize |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// 保存采样点和颜色
|
|
|
|
// 计算重心坐标
|
|
|
|
texPoints.emplace_back(texCoord); |
|
|
|
Point3f barycentric; |
|
|
|
pointColors.push_back(accumColor); |
|
|
|
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); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const auto& pixel : pixels) { |
|
|
|
|
|
|
|
if (pixel.x < 0 || pixel.x >= textureSize || |
|
|
|
|
|
|
|
pixel.y < 0 || pixel.y >= textureSize) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const Point2f texCoord((float)pixel.x / textureSize, (float)pixel.y / 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; |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 改进:使用距离加权插值(替代最近邻)
|
|
|
|
// 累积来自所有视图的颜色
|
|
|
|
cv::Vec3f weightedColor(0, 0, 0); |
|
|
|
cv::Vec3f accumColor(0, 0, 0); |
|
|
|
float totalWeight = 0.0f; |
|
|
|
float totalWeight = 0.0f; |
|
|
|
const float POWER = 2.0f; // 距离幂次(2表示平方反比)
|
|
|
|
|
|
|
|
const float MAX_DIST_SQ = 100.0f / (textureSize * textureSize); // 最大距离平方
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < texPoints.size(); ++i) { |
|
|
|
for (size_t viewIdx = 0; viewIdx < faceViews[idxVF].size(); ++viewIdx) { |
|
|
|
float dx = texPoints[i].x - texCoord.x; |
|
|
|
const IIndex idxView = faceViews[idxVF][viewIdx]; |
|
|
|
float dy = texPoints[i].y - texCoord.y; |
|
|
|
const float viewWeight = faceViewWeights[idxVF][viewIdx]; |
|
|
|
float distSq = dx*dx + dy*dy; // 平方距离
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 跳过距离过远的点
|
|
|
|
if (idxView >= images.size()) continue; |
|
|
|
if (distSq > MAX_DIST_SQ) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 避免除零
|
|
|
|
const Image& sourceImage = images[idxView]; |
|
|
|
if (distSq < 1e-10f) distSq = 1e-10f; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算权重(距离越近权重越大)
|
|
|
|
// 投影到图像
|
|
|
|
float weight = 1.0f / std::pow(distSq, POWER/2.0f); |
|
|
|
Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage); |
|
|
|
|
|
|
|
|
|
|
|
weightedColor += pointColors[i] * weight; |
|
|
|
// 验证投影
|
|
|
|
totalWeight += weight; |
|
|
|
if (!ValidateProjection(worldPoint, sourceImage, imgPoint) || |
|
|
|
|
|
|
|
!sourceImage.image.isInside(imgPoint) || |
|
|
|
|
|
|
|
!sourceImage.camera.IsInFront(worldPoint)) { |
|
|
|
|
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 如果有权重,则计算加权平均颜色
|
|
|
|
Color color = sourceImage.image((int)imgPoint.x, (int)imgPoint.y); |
|
|
|
if (totalWeight > 0.0f) { |
|
|
|
|
|
|
|
cv::Vec3f newColor = weightedColor / totalWeight; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 累加颜色和权重
|
|
|
|
// ✅ 关键修改5:使用面积权重(而非简单计数)
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
const float areaWeight = viewWeight * (1.0f / (RENDER_SCALE * RENDER_SCALE)); |
|
|
|
#pragma omp atomic |
|
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
weightAccum(pixel.y, pixel.x) += 1.0f; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
// 累积加权颜色(BGR顺序)
|
|
|
|
#pragma omp atomic |
|
|
|
accumColor[0] += color[0] * areaWeight; // B
|
|
|
|
#endif |
|
|
|
accumColor[1] += color[1] * areaWeight; // G
|
|
|
|
sampleCount(pixel.y, pixel.x) += 1; |
|
|
|
accumColor[2] += color[2] * areaWeight; // R
|
|
|
|
|
|
|
|
totalWeight += areaWeight; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
// ✅ 关键修改6:原子累加(避免竞态条件)
|
|
|
|
#pragma omp critical |
|
|
|
if (totalWeight > 0.0f) { |
|
|
|
#endif |
|
|
|
#ifdef _USE_OPENMP |
|
|
|
{ |
|
|
|
#pragma omp critical |
|
|
|
colorAccum(pixel.y, pixel.x) += newColor; |
|
|
|
#endif |
|
|
|
} |
|
|
|
{ |
|
|
|
|
|
|
|
colorAccum(y, x) += accumColor; |
|
|
|
|
|
|
|
weightAccum(y, x) += totalWeight; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -15803,45 +15726,81 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 6. 应用权重归一化
|
|
|
|
// 6. 应用权重归一化(在高分辨率下)
|
|
|
|
DEBUG_EXTRA("Applying weight normalization for virtual faces"); |
|
|
|
DEBUG_EXTRA("Applying weight normalization at high resolution"); |
|
|
|
for (int y = 0; y < textureSize; ++y) { |
|
|
|
cv::Mat3f hiResAtlas(renderSize, renderSize, cv::Vec3f(0, 0, 0)); |
|
|
|
for (int x = 0; x < textureSize; ++x) { |
|
|
|
for (int y = 0; y < renderSize; ++y) { |
|
|
|
|
|
|
|
for (int x = 0; x < renderSize; ++x) { |
|
|
|
float weight = weightAccum(y, x); |
|
|
|
float weight = weightAccum(y, x); |
|
|
|
if (weight > 0.0f) { |
|
|
|
if (weight > 0.0f) { |
|
|
|
// 计算加权平均颜色
|
|
|
|
hiResAtlas(y, x) = colorAccum(y, x) / weight; |
|
|
|
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; |
|
|
|
|
|
|
|
} else { |
|
|
|
} else { |
|
|
|
// 保持背景色
|
|
|
|
// 对于未采样的像素,使用背景色
|
|
|
|
textureAtlas(y, x) = colEmpty; |
|
|
|
hiResAtlas(y, x) = cv::Vec3f(colEmpty[2], colEmpty[1], colEmpty[0]); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 7. 填充缝隙和未采样区域
|
|
|
|
// 7. ✅ 关键修改7:高质量降采样到目标分辨率
|
|
|
|
DEBUG_EXTRA("Filling gaps in texture atlas for virtual faces"); |
|
|
|
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; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 9. 填充缝隙和未采样区域(可选)
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Filling gaps in texture atlas"); |
|
|
|
cv::Mat textureMat = (cv::Mat&)textureAtlas; |
|
|
|
cv::Mat textureMat = (cv::Mat&)textureAtlas; |
|
|
|
cv::Mat1f weightMat = weightAccum; |
|
|
|
cv::Mat1f weightMat = weightAccum; |
|
|
|
cv::Mat1i sampleMat = sampleCount; |
|
|
|
// FillTextureGaps2(textureMat, weightMat, colEmpty);
|
|
|
|
// FillTextureGapsMultiView(textureMat, weightMat, sampleMat, colEmpty);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 8. 应用锐化(可选)
|
|
|
|
// 10. ✅ 关键修改8:应用锐化(显著提升清晰度)
|
|
|
|
if (fSharpnessWeight > 0) { |
|
|
|
if (fSharpnessWeight > 0) { |
|
|
|
// ApplySharpening(textureMat, fSharpnessWeight);
|
|
|
|
DEBUG_EXTRA("Applying sharpening filter (weight: %.2f)", fSharpnessWeight); |
|
|
|
|
|
|
|
ApplyUnsharpMask(textureMat, fSharpnessWeight); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete"); |
|
|
|
// 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; |
|
|
|
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( |
|
|
|
bool MeshTexture::SelectBestSingleView( |
|
|
|
const Point3f& worldPos, |
|
|
|
const Point3f& worldPos, |
|
|
|
|