|
|
|
@ -723,6 +723,79 @@ public: |
|
|
|
Pixel8U SampleImageBicubic(const Image8U3& img, const Point2f& pt); |
|
|
|
Pixel8U SampleImageBicubic(const Image8U3& img, const Point2f& pt); |
|
|
|
void ApplyUnsharpMask(cv::Mat& image, float strength); |
|
|
|
void ApplyUnsharpMask(cv::Mat& image, float strength); |
|
|
|
void FillTextureGaps2(cv::Mat& texture, const cv::Mat1f& weights, Pixel8U colEmpty); |
|
|
|
void FillTextureGaps2(cv::Mat& texture, const cv::Mat1f& weights, Pixel8U colEmpty); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
// 辅助函数:双线性采样
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
inline Color BilinearSample(const Image8U3& img, const Point2f& p) { |
|
|
|
|
|
|
|
const int w = img.width(); |
|
|
|
|
|
|
|
const int h = img.height(); |
|
|
|
|
|
|
|
const float fx = p.x - 0.5f; // 像素中心对齐
|
|
|
|
|
|
|
|
const float fy = p.y - 0.5f; |
|
|
|
|
|
|
|
int x0 = (int)std::floor(fx); |
|
|
|
|
|
|
|
int y0 = (int)std::floor(fy); |
|
|
|
|
|
|
|
float dx = fx - x0; |
|
|
|
|
|
|
|
float dy = fy - y0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto get = [&](int x, int y) -> Color { |
|
|
|
|
|
|
|
x = std::max(0, std::min(x, w - 1)); |
|
|
|
|
|
|
|
y = std::max(0, std::min(y, h - 1)); |
|
|
|
|
|
|
|
return img(x, y); |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Color c00 = get(x0, y0); |
|
|
|
|
|
|
|
Color c10 = get(x0 + 1, y0); |
|
|
|
|
|
|
|
Color c01 = get(x0, y0 + 1); |
|
|
|
|
|
|
|
Color c11 = get(x0 + 1, y0 + 1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return c00 * ((1.0f - dx) * (1.0f - dy)) |
|
|
|
|
|
|
|
+ c10 * (dx * (1.0f - dy)) |
|
|
|
|
|
|
|
+ c01 * ((1.0f - dx) * dy) |
|
|
|
|
|
|
|
+ c11 * (dx * dy); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
// 辅助函数:透视校正插值权重
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
inline Point3f ComputePerspectiveBarycentric( |
|
|
|
|
|
|
|
const Point2f& uv, const TexCoord* uvCoords, |
|
|
|
|
|
|
|
const Point3f* worldVerts, float* w) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// 标准重心坐标
|
|
|
|
|
|
|
|
const float denom = 1.0f / ((uvCoords[1].y - uvCoords[2].y) * (uvCoords[0].x - uvCoords[2].x) |
|
|
|
|
|
|
|
+ (uvCoords[2].x - uvCoords[1].x) * (uvCoords[0].y - uvCoords[2].y)); |
|
|
|
|
|
|
|
const float b0 = ((uvCoords[1].y - uvCoords[2].y) * (uv.x - uvCoords[2].x) |
|
|
|
|
|
|
|
+ (uvCoords[2].x - uvCoords[1].x) * (uv.y - uvCoords[2].y)) * denom; |
|
|
|
|
|
|
|
const float b1 = ((uvCoords[2].y - uvCoords[0].y) * (uv.x - uvCoords[2].x) |
|
|
|
|
|
|
|
+ (uvCoords[0].x - uvCoords[2].x) * (uv.y - uvCoords[2].y)) * denom; |
|
|
|
|
|
|
|
const float b2 = 1.0f - b0 - b1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 透视校正:需要深度(Z)
|
|
|
|
|
|
|
|
w[0] = b0; |
|
|
|
|
|
|
|
w[1] = b1; |
|
|
|
|
|
|
|
w[2] = b2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Point3f( |
|
|
|
|
|
|
|
worldVerts[0].x * b0 + worldVerts[1].x * b1 + worldVerts[2].x * b2, |
|
|
|
|
|
|
|
worldVerts[0].y * b0 + worldVerts[1].y * b1 + worldVerts[2].y * b2, |
|
|
|
|
|
|
|
worldVerts[0].z * b0 + worldVerts[1].z * b1 + worldVerts[2].z * b2 |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
// 辅助函数:ProjectPoint(透视投影到图像坐标)
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
inline Point2f ProjectToImage(const RMatrix& R, const Point3f& C, const Point3f& worldPt) { |
|
|
|
|
|
|
|
// R: 相机旋转, C: 相机中心
|
|
|
|
|
|
|
|
Point3f camPt( |
|
|
|
|
|
|
|
R(0,0)*(worldPt.x-C.x) + R(0,1)*(worldPt.y-C.y) + R(0,2)*(worldPt.z-C.z), |
|
|
|
|
|
|
|
R(1,0)*(worldPt.x-C.x) + R(1,1)*(worldPt.y-C.y) + R(1,2)*(worldPt.z-C.z), |
|
|
|
|
|
|
|
R(2,0)*(worldPt.x-C.x) + R(2,1)*(worldPt.y-C.y) + R(2,2)*(worldPt.z-C.z) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
if (camPt.z <= 0) return Point2f(-1, -1); // 在相机后面
|
|
|
|
|
|
|
|
return Point2f(camPt.x / camPt.z, camPt.y / camPt.z); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Mesh::Image8U3Arr GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
Mesh::Image8U3Arr GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
const VirtualFaceDataArr& virtualFaceDatas, // 改为 VirtualFaceDataArr
|
|
|
|
const VirtualFaceDataArr& virtualFaceDatas, // 改为 VirtualFaceDataArr
|
|
|
|
@ -15809,200 +15882,225 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
Pixel8U colEmpty, |
|
|
|
Pixel8U colEmpty, |
|
|
|
float fSharpnessWeight) |
|
|
|
float fSharpnessWeight) |
|
|
|
{ |
|
|
|
{ |
|
|
|
DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces (SHARPENED)"); |
|
|
|
DEBUG_EXTRA("Generating SINGLE-VIEW + Bilinear + 1:1 Rasterized texture atlas"); |
|
|
|
TD_TIMER_START(); |
|
|
|
TD_TIMER_START(); |
|
|
|
|
|
|
|
|
|
|
|
// 1. 分析UV布局
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
// 1. 计算 UV 边界和纹理尺寸
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
AABB2f uvBounds(true); |
|
|
|
AABB2f uvBounds(true); |
|
|
|
FOREACH(i, scene.mesh.faceTexcoords) { |
|
|
|
for (const auto& tc : scene.mesh.faceTexcoords) { |
|
|
|
const TexCoord& uv = scene.mesh.faceTexcoords[i]; |
|
|
|
uvBounds.InsertFull(tc); |
|
|
|
uvBounds.InsertFull(uv); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (uvBounds.ptMax.x() - uvBounds.ptMin.x() < 0.001f) |
|
|
|
|
|
|
|
uvBounds.ptMax.x() = uvBounds.ptMin.x() + 1.0f; |
|
|
|
|
|
|
|
if (uvBounds.ptMax.y() - uvBounds.ptMin.y() < 0.001f) |
|
|
|
|
|
|
|
uvBounds.ptMax.y() = uvBounds.ptMin.y() + 1.0f; |
|
|
|
|
|
|
|
|
|
|
|
// 2. 计算纹理尺寸
|
|
|
|
const float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x(); |
|
|
|
float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x(); |
|
|
|
const float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y(); |
|
|
|
float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (uvWidth < 0.001f) uvWidth = 1.0f; |
|
|
|
|
|
|
|
if (uvHeight < 0.001f) uvHeight = 1.0f; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1:1 渲染,不超分
|
|
|
|
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); |
|
|
|
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 关键修改1:使用超分辨率渲染(2倍)
|
|
|
|
DEBUG_EXTRA("UV bounds: [%.4f,%.4f] -> [%.4f,%.4f], textureSize=%d", |
|
|
|
constexpr int RENDER_SCALE = 2; |
|
|
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(), |
|
|
|
const int renderSize = textureSize * RENDER_SCALE; |
|
|
|
uvBounds.ptMax.x(), uvBounds.ptMax.y(), |
|
|
|
|
|
|
|
textureSize); |
|
|
|
|
|
|
|
|
|
|
|
// 3. 创建纹理图集(最终输出尺寸)
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
// 2. 创建输出纹理(单张)
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); |
|
|
|
Image8U3& atlas = textures.emplace_back(textureSize, textureSize); |
|
|
|
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
atlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
|
|
|
|
|
|
|
// 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, Render size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]", |
|
|
|
// 权重图(记录每个像素是否被写入)
|
|
|
|
textureSize, textureSize, renderSize, renderSize, |
|
|
|
cv::Mat1f weightMap(textureSize, textureSize, 0.0f); |
|
|
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(), |
|
|
|
|
|
|
|
uvBounds.ptMax.x(), uvBounds.ptMax.y()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 处理每个虚拟面
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
// 3. 逐虚拟面光栅化(单视图 + 双线性 + 透视校正)
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
for (int_t idxVF = 0; idxVF < (int_t)virtualFaceMap.size(); ++idxVF) { |
|
|
|
|
|
|
|
#else |
|
|
|
|
|
|
|
for (size_t idxVF = 0; idxVF < virtualFaceMap.size(); ++idxVF) { |
|
|
|
|
|
|
|
#endif |
|
|
|
#endif |
|
|
|
|
|
|
|
for (int_t idxVF = 0; idxVF < (int_t)virtualFaceMap.size(); ++idxVF) { |
|
|
|
const VirtualFace& vf = virtualFaceMap[idxVF]; |
|
|
|
const VirtualFace& vf = virtualFaceMap[idxVF]; |
|
|
|
|
|
|
|
|
|
|
|
// 检查虚拟面是否有可用视图
|
|
|
|
if (vf.faces.empty()) continue; |
|
|
|
if (faceViews[idxVF].empty()) { |
|
|
|
if (idxVF >= (int_t)faceViews.size() || faceViews[idxVF].empty()) continue; |
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 只取权重最大的那一个视图(单视图策略)
|
|
|
|
|
|
|
|
size_t bestViewIdx = 0; |
|
|
|
|
|
|
|
float bestW = faceViewWeights[idxVF][0]; |
|
|
|
|
|
|
|
for (size_t vi = 1; vi < faceViewWeights[idxVF].size(); ++vi) { |
|
|
|
|
|
|
|
if (faceViewWeights[idxVF][vi] > bestW) { |
|
|
|
|
|
|
|
bestW = faceViewWeights[idxVF][vi]; |
|
|
|
|
|
|
|
bestViewIdx = vi; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const IIndex idxView = faceViews[idxVF][bestViewIdx]; |
|
|
|
|
|
|
|
if (idxView >= (IIndex)images.size()) continue; |
|
|
|
|
|
|
|
|
|
|
|
// 处理虚拟面中的每个原始面片
|
|
|
|
const Image& srcImage = images[idxView]; |
|
|
|
|
|
|
|
const int srcW = srcImage.image.width(); |
|
|
|
|
|
|
|
const int srcH = srcImage.image.height(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 相机参数
|
|
|
|
|
|
|
|
const RMatrix& R = srcImage.camera.R; |
|
|
|
|
|
|
|
const Point3f& C = srcImage.camera.C; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ====================================================================
|
|
|
|
|
|
|
|
// 对每个面片光栅化
|
|
|
|
|
|
|
|
// ====================================================================
|
|
|
|
for (FIndex faceID : vf.faces) { |
|
|
|
for (FIndex faceID : vf.faces) { |
|
|
|
|
|
|
|
if (faceID >= (FIndex)scene.mesh.faces.size()) continue; |
|
|
|
|
|
|
|
|
|
|
|
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]; |
|
|
|
|
|
|
|
const Point3f* worldVerts[3] = { |
|
|
|
|
|
|
|
&scene.mesh.vertices[face[0]], |
|
|
|
|
|
|
|
&scene.mesh.vertices[face[1]], |
|
|
|
|
|
|
|
&scene.mesh.vertices[face[2]] |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 关键修改2:计算高分辨率下的边界框
|
|
|
|
// ---- 计算纹理空间 bounding box ----
|
|
|
|
cv::Rect bbox; |
|
|
|
int minX = textureSize, minY = textureSize, maxX = 0, maxY = 0; |
|
|
|
for (int i = 0; i < 3; ++i) { |
|
|
|
for (int i = 0; i < 3; ++i) { |
|
|
|
int px = int(uvCoords[i].x * renderSize); |
|
|
|
int px = (int)(uvCoords[i].x * textureSize); |
|
|
|
int py = int(uvCoords[i].y * renderSize); |
|
|
|
int py = (int)(uvCoords[i].y * textureSize); |
|
|
|
if (bbox.empty()) |
|
|
|
minX = std::min(minX, px); maxX = std::max(maxX, px); |
|
|
|
bbox = cv::Rect(px, py, 1, 1); |
|
|
|
minY = std::min(minY, py); maxY = std::max(maxY, py); |
|
|
|
else |
|
|
|
|
|
|
|
bbox |= cv::Rect(px, py, 1, 1); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
bbox &= cv::Rect(0, 0, renderSize, renderSize); |
|
|
|
minX = std::max(0, minX - 1); |
|
|
|
|
|
|
|
minY = std::max(0, minY - 1); |
|
|
|
|
|
|
|
maxX = std::min(textureSize - 1, maxX + 1); |
|
|
|
|
|
|
|
maxY = std::min(textureSize - 1, maxY + 1); |
|
|
|
|
|
|
|
|
|
|
|
if (bbox.empty()) continue; |
|
|
|
if (maxX < minX || maxY < minY) continue; |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 关键修改3:直接在高分辨率下逐像素采样(不再使用Delaunay插值)
|
|
|
|
// ---- 预计算 3 个顶点在图像中的投影位置 ----
|
|
|
|
for (int y = bbox.y; y < bbox.y + bbox.height; ++y) { |
|
|
|
Point2f imgPts[3]; |
|
|
|
for (int x = bbox.x; x < bbox.x + bbox.width; ++x) { |
|
|
|
bool validProj = true; |
|
|
|
// 计算当前像素的UV坐标(在高分辨率空间)
|
|
|
|
for (int i = 0; i < 3; ++i) { |
|
|
|
Point2f texCoord( |
|
|
|
imgPts[i] = ProjectToImage(R, C, *worldVerts[i]); |
|
|
|
static_cast<float>(x) / renderSize, |
|
|
|
if (imgPts[i].x < 0 || imgPts[i].y < 0 || |
|
|
|
static_cast<float>(y) / renderSize |
|
|
|
imgPts[i].x >= srcW || imgPts[i].y >= srcH) { |
|
|
|
); |
|
|
|
validProj = false; |
|
|
|
|
|
|
|
break; |
|
|
|
// 计算重心坐标
|
|
|
|
|
|
|
|
Point3f barycentric; |
|
|
|
|
|
|
|
if (!PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!validProj) continue; |
|
|
|
|
|
|
|
|
|
|
|
// 计算3D世界坐标
|
|
|
|
// ---- 逐像素光栅化 ----
|
|
|
|
const Vertex worldPoint = |
|
|
|
for (int y = minY; y <= maxY; ++y) { |
|
|
|
scene.mesh.vertices[face[0]] * barycentric.x + |
|
|
|
for (int x = minX; x <= maxX; ++x) { |
|
|
|
scene.mesh.vertices[face[1]] * barycentric.y + |
|
|
|
Point2f uv( |
|
|
|
scene.mesh.vertices[face[2]] * barycentric.z; |
|
|
|
(float)x / (float)textureSize, |
|
|
|
|
|
|
|
(float)y / (float)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]; |
|
|
|
// 重心坐标
|
|
|
|
|
|
|
|
float w[3]; |
|
|
|
|
|
|
|
// 用面积法计算重心坐标
|
|
|
|
|
|
|
|
auto edgeFunc = [](const Point2f& a, const Point2f& b, const Point2f& c) { |
|
|
|
|
|
|
|
return (c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x); |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
float area = edgeFunc(uvCoords[0], uvCoords[1], uvCoords[2]); |
|
|
|
|
|
|
|
if (std::abs(area) < 1e-10f) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
w[0] = edgeFunc(uvCoords[1], uvCoords[2], uv) / area; |
|
|
|
|
|
|
|
w[1] = edgeFunc(uvCoords[2], uvCoords[0], uv) / area; |
|
|
|
|
|
|
|
w[2] = 1.0f - w[0] - w[1]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 不在三角形内
|
|
|
|
|
|
|
|
if (w[0] < 0 || w[1] < 0 || w[2] < 0) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 透视校正插值 ----
|
|
|
|
|
|
|
|
// 用重心坐标插值世界坐标
|
|
|
|
|
|
|
|
Point3f worldPt( |
|
|
|
|
|
|
|
worldVerts[0]->x * w[0] + worldVerts[1]->x * w[1] + worldVerts[2]->x * w[2], |
|
|
|
|
|
|
|
worldVerts[0]->y * w[0] + worldVerts[1]->y * w[1] + worldVerts[2]->y * w[2], |
|
|
|
|
|
|
|
worldVerts[0]->z * w[0] + worldVerts[1]->z * w[1] + worldVerts[2]->z * w[2] |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// 投影到图像
|
|
|
|
// 投影到图像
|
|
|
|
Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage); |
|
|
|
Point2f imgPt = ProjectToImage(R, C, worldPt); |
|
|
|
|
|
|
|
if (imgPt.x < 0 || imgPt.y < 0 || |
|
|
|
|
|
|
|
imgPt.x >= srcW - 1 || imgPt.y >= srcH - 1) continue; |
|
|
|
|
|
|
|
|
|
|
|
// 验证投影
|
|
|
|
// ---- ✅ 双线性采样 ----
|
|
|
|
if (!ValidateProjection(worldPoint, sourceImage, imgPoint) || |
|
|
|
Color color = BilinearSample(srcImage.image, imgPt); |
|
|
|
!sourceImage.image.isInside(imgPoint) || |
|
|
|
|
|
|
|
!sourceImage.camera.IsInFront(worldPoint)) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Color color = sourceImage.image((int)imgPoint.x, (int)imgPoint.y); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 关键修改5:使用面积权重(而非简单计数)
|
|
|
|
|
|
|
|
const float areaWeight = viewWeight * (1.0f / (RENDER_SCALE * RENDER_SCALE)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 累积加权颜色(BGR顺序)
|
|
|
|
// ---- 写入 atlas(临界区保护) ----
|
|
|
|
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 |
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#pragma omp critical |
|
|
|
#pragma omp critical |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
{ |
|
|
|
{ |
|
|
|
colorAccum(y, x) += accumColor; |
|
|
|
Pixel8U& dst = atlas(y, x); |
|
|
|
weightAccum(y, x) += totalWeight; |
|
|
|
// 如果是第一个写入者,直接赋值;否则覆盖(单视图无混合)
|
|
|
|
|
|
|
|
if (weightMap(y, x) == 0.0f) { |
|
|
|
|
|
|
|
dst.b = (unsigned char)color[0]; |
|
|
|
|
|
|
|
dst.g = (unsigned char)color[1]; |
|
|
|
|
|
|
|
dst.r = (unsigned char)color[2]; |
|
|
|
|
|
|
|
weightMap(y, x) = 1.0f; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 单视图策略:不混合,后写入者覆盖(或反之)
|
|
|
|
|
|
|
|
// 如果需要"先到先得",用上面的 if
|
|
|
|
|
|
|
|
// 如果需要"最佳视图覆盖",去掉 if 直接用下面:
|
|
|
|
|
|
|
|
// dst.b = (unsigned char)color[0];
|
|
|
|
|
|
|
|
// dst.g = (unsigned char)color[1];
|
|
|
|
|
|
|
|
// dst.r = (unsigned char)color[2];
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 6. 应用权重归一化(在高分辨率下)
|
|
|
|
// ========================================================================
|
|
|
|
DEBUG_EXTRA("Applying weight normalization at high resolution"); |
|
|
|
// 4. 缝隙填充(可选,用形态学膨胀)
|
|
|
|
cv::Mat3f hiResAtlas(renderSize, renderSize, cv::Vec3f(0, 0, 0)); |
|
|
|
// ========================================================================
|
|
|
|
for (int y = 0; y < renderSize; ++y) { |
|
|
|
DEBUG_EXTRA("Filling small gaps..."); |
|
|
|
for (int x = 0; x < renderSize; ++x) { |
|
|
|
cv::Mat atlasMat = (cv::Mat&)atlas; |
|
|
|
float weight = weightAccum(y, x); |
|
|
|
cv::Mat mask = (weightMap > 0.5f); |
|
|
|
if (weight > 0.0f) { |
|
|
|
cv::Mat atlasF; |
|
|
|
hiResAtlas(y, x) = colorAccum(y, x) / weight; |
|
|
|
atlasMat.convertTo(atlasF, CV_32FC3); |
|
|
|
} else { |
|
|
|
|
|
|
|
// 对于未采样的像素,使用背景色
|
|
|
|
|
|
|
|
hiResAtlas(y, x) = cv::Vec3f(colEmpty[2], colEmpty[1], colEmpty[0]); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 7. ✅ 关键修改7:高质量降采样到目标分辨率
|
|
|
|
// 膨胀 mask
|
|
|
|
DEBUG_EXTRA("Downsampling from %dx%d to %dx%d", renderSize, renderSize, textureSize, textureSize); |
|
|
|
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); |
|
|
|
cv::Mat3f downsampledAtlas; |
|
|
|
cv::Mat dilatedMask; |
|
|
|
cv::resize(hiResAtlas, downsampledAtlas, cv::Size(textureSize, textureSize), 0, 0, cv::INTER_CUBIC); |
|
|
|
cv::dilate(mask, dilatedMask, kernel); |
|
|
|
|
|
|
|
|
|
|
|
// 8. 转换为8位纹理
|
|
|
|
// 对未填充区域做距离变换 + 插值
|
|
|
|
|
|
|
|
cv::Mat distMap, nearestLoc; |
|
|
|
|
|
|
|
cv::distanceTransform(mask, distMap, nearestLoc, cv::DIST_L2, 5); |
|
|
|
for (int y = 0; y < textureSize; ++y) { |
|
|
|
for (int y = 0; y < textureSize; ++y) { |
|
|
|
for (int x = 0; x < textureSize; ++x) { |
|
|
|
for (int x = 0; x < textureSize; ++x) { |
|
|
|
const cv::Vec3f& color = downsampledAtlas(y, x); |
|
|
|
if (weightMap(y, x) == 0.0f && dilatedMask.at<uchar>(y, x)) { |
|
|
|
Pixel8U finalColor; |
|
|
|
// 用最近的有效像素填充
|
|
|
|
finalColor.b = (unsigned char)cv::saturate_cast<uchar>(color[0]); |
|
|
|
int nearestIdx = nearestLoc.at<int>(y, x); |
|
|
|
finalColor.g = (unsigned char)cv::saturate_cast<uchar>(color[1]); |
|
|
|
int ny = nearestIdx / textureSize; |
|
|
|
finalColor.r = (unsigned char)cv::saturate_cast<uchar>(color[2]); |
|
|
|
int nx = nearestIdx % textureSize; |
|
|
|
textureAtlas(y, x) = finalColor; |
|
|
|
atlasMat.at<cv::Vec3b>(y, x) = atlasMat.at<cv::Vec3b>(ny, nx); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 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 |
|
|
|
// 5. 可选锐化(轻微)
|
|
|
|
const int anisoLevel = 8; |
|
|
|
// ========================================================================
|
|
|
|
for (auto& tex : textures) { |
|
|
|
if (fSharpnessWeight > 0) { |
|
|
|
tex.SetFilterMode(Texture::ANISOTROPIC); |
|
|
|
DEBUG_EXTRA("Applying mild sharpening (weight: %.2f)", fSharpnessWeight); |
|
|
|
tex.SetAnisotropy(anisoLevel); |
|
|
|
cv::Mat kernel = (cv::Mat_<float>(3, 3) << |
|
|
|
|
|
|
|
0, -fSharpnessWeight, 0, |
|
|
|
|
|
|
|
-fSharpnessWeight, 1 + 4 * fSharpnessWeight, -fSharpnessWeight, |
|
|
|
|
|
|
|
0, -fSharpnessWeight, 0); |
|
|
|
|
|
|
|
cv::Mat sharpened; |
|
|
|
|
|
|
|
cv::filter2D(atlasMat, sharpened, CV_8UC3, kernel); |
|
|
|
|
|
|
|
atlasMat = sharpened; |
|
|
|
} |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Multi-view texture atlas generation completed in %s", TD_TIMER_GET_FMT().c_str()); |
|
|
|
DEBUG_EXTRA("Single-view texture atlas completed in %s", TD_TIMER_GET_FMT().c_str()); |
|
|
|
return textures; |
|
|
|
return textures; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|