|
|
|
|
@ -692,6 +692,13 @@ public:
@@ -692,6 +692,13 @@ public:
|
|
|
|
|
|
|
|
|
|
bool GetWorldPositionAndNormal(const Point2f& texCoord, |
|
|
|
|
Point3f& worldPos, Normal& normal, FIndex& faceID); |
|
|
|
|
float CalculatePixelDensity(const Camera& cam, const Point3d& pos); |
|
|
|
|
float CalculateLocalViewDensity(const Point3f& pos, |
|
|
|
|
const std::vector<IIndex>& visibleViews); |
|
|
|
|
int GetAdaptiveTexelSize(const Point3f& pos, |
|
|
|
|
const std::vector<IIndex>& visibleViews, |
|
|
|
|
int baseTexelSize); |
|
|
|
|
Pixel8U SampleImageBicubic(const Image8U3& img, const Point2f& pt); |
|
|
|
|
|
|
|
|
|
Mesh::Image8U3Arr GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
@ -15088,70 +15095,186 @@ bool MeshTexture::GetWorldPositionAndNormal(
@@ -15088,70 +15095,186 @@ bool MeshTexture::GetWorldPositionAndNormal(
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
float MeshTexture::CalculatePixelDensity(const Camera& cam, const Point3d& pos) { |
|
|
|
|
// 1. 点在相机坐标系下的坐标
|
|
|
|
|
Point3d posCam = cam.R * pos + cam.C; // 世界坐标转相机坐标
|
|
|
|
|
const double Z = posCam.z; |
|
|
|
|
if (Z <= DBL_EPSILON) return 0.0f; // 点在相机后
|
|
|
|
|
|
|
|
|
|
// 2. 透视投影的雅可比矩阵(描述世界空间变化到图像空间变化的映射)
|
|
|
|
|
// 投影公式:u = fx*(X/Z) + cx, v = fy*(Y/Z) + cy
|
|
|
|
|
const double fx = cam.K(0,0); |
|
|
|
|
const double fy = cam.K(1,1); |
|
|
|
|
const double invZ = 1.0 / Z; |
|
|
|
|
const double invZ2 = invZ * invZ; |
|
|
|
|
|
|
|
|
|
// 雅可比矩阵 J 的元素:∂u/∂X, ∂u/∂Y, ∂v/∂X, ∂v/∂Y
|
|
|
|
|
const double dudx = fx * invZ; |
|
|
|
|
const double dudy = 0.0; |
|
|
|
|
const double dvdx = 0.0; |
|
|
|
|
const double dvdy = fy * invZ; |
|
|
|
|
// (注:这里简化了Z方向的影响,因为切平面内Z的变化很小,不影响密度计算)
|
|
|
|
|
|
|
|
|
|
// 3. 面积缩放比 = 雅可比行列式的绝对值
|
|
|
|
|
const double scale = std::abs(dudx * dvdy - dudy * dvdx); |
|
|
|
|
return static_cast<float>(scale); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
float MeshTexture::CalculateLocalViewDensity(const Point3f& pos, |
|
|
|
|
const std::vector<IIndex>& visibleViews) { |
|
|
|
|
float density = 0.0f; |
|
|
|
|
for (IIndex viewId : visibleViews) { |
|
|
|
|
const Camera& cam = images[viewId].camera; |
|
|
|
|
// 计算该视图在pos处的像素密度
|
|
|
|
|
float pixelDensity = CalculatePixelDensity(cam, pos); |
|
|
|
|
density += pixelDensity; |
|
|
|
|
} |
|
|
|
|
return density; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int MeshTexture::GetAdaptiveTexelSize(const Point3f& pos, |
|
|
|
|
const std::vector<IIndex>& visibleViews, |
|
|
|
|
int baseTexelSize) { |
|
|
|
|
float density = CalculateLocalViewDensity(pos, visibleViews); |
|
|
|
|
|
|
|
|
|
// 密度越高,纹素越小(分辨率越高)
|
|
|
|
|
if (density > 10.0f) return baseTexelSize / 2; // 高密度区:2倍分辨率
|
|
|
|
|
else if (density > 5.0f) return baseTexelSize * 2/3; // 中高密度
|
|
|
|
|
else if (density > 2.0f) return baseTexelSize; // 正常密度
|
|
|
|
|
else return baseTexelSize * 2; // 低密度区:降低分辨率
|
|
|
|
|
} |
|
|
|
|
// 标准Keys立方滤波器(OpenCV/RC同款,a=-0.5)
|
|
|
|
|
static inline float CubicKernel(float x, float a = -0.5f) { |
|
|
|
|
x = std::abs(x); |
|
|
|
|
if (x <= 1.0f) { |
|
|
|
|
return (a + 2.0f) * x*x*x - (a + 3.0f) * x*x + 1.0f; |
|
|
|
|
} else if (x < 2.0f) { |
|
|
|
|
return a * x*x*x - 5.0f*a * x*x + 8.0f*a * x - 4.0f*a; |
|
|
|
|
} |
|
|
|
|
return 0.0f; |
|
|
|
|
} |
|
|
|
|
Pixel8U MeshTexture::SampleImageBicubic(const Image8U3& image, const Point2f& point) { |
|
|
|
|
const int w = image.cols; |
|
|
|
|
const int h = image.rows; |
|
|
|
|
|
|
|
|
|
const float x = CLAMP(point.x, 0.0f, (float)w - 1.001f); |
|
|
|
|
const float y = CLAMP(point.y, 0.0f, (float)h - 1.001f); |
|
|
|
|
|
|
|
|
|
const int ix = (int)std::floor(x); |
|
|
|
|
const int iy = (int)std::floor(y); |
|
|
|
|
const float dx = x - ix; |
|
|
|
|
const float dy = y - iy; |
|
|
|
|
|
|
|
|
|
// OpenCV Keys cubic (a=-0.75) - 和双线性同坐标系
|
|
|
|
|
auto cubic = [](float t) -> float { |
|
|
|
|
t = std::abs(t); |
|
|
|
|
if (t < 1.0f) |
|
|
|
|
return 1.0f - 2.0f*t*t + t*t*t; |
|
|
|
|
else if (t < 2.0f) |
|
|
|
|
return 4.0f - 8.0f*t + 5.0f*t*t - t*t*t; |
|
|
|
|
return 0.0f; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
float wx[4], wy[4]; |
|
|
|
|
for (int i = 0; i < 4; ++i) { |
|
|
|
|
wx[i] = cubic(dx - (i - 1)); |
|
|
|
|
wy[i] = cubic(dy - (i - 1)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
double sum[3] = {0, 0, 0}; |
|
|
|
|
double weight_sum = 0.0; |
|
|
|
|
|
|
|
|
|
for (int j = 0; j < 4; ++j) { |
|
|
|
|
const int py = CLAMP(iy + j - 1, 0, h - 1); |
|
|
|
|
for (int i = 0; i < 4; ++i) { |
|
|
|
|
const int px = CLAMP(ix + i - 1, 0, w - 1); |
|
|
|
|
|
|
|
|
|
// ✅ 关键:用 operator() 而不是 getPixel()
|
|
|
|
|
const Pixel8U& p = image(py, px); |
|
|
|
|
const float w = wx[i] * wy[j]; |
|
|
|
|
|
|
|
|
|
// ✅ 用 operator[] 访问通道(BGR顺序)
|
|
|
|
|
sum[0] += w * p[0]; // B
|
|
|
|
|
sum[1] += w * p[1]; // G
|
|
|
|
|
sum[2] += w * p[2]; // R
|
|
|
|
|
weight_sum += w; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (weight_sum > 1e-8) { |
|
|
|
|
sum[0] /= weight_sum; |
|
|
|
|
sum[1] /= weight_sum; |
|
|
|
|
sum[2] /= weight_sum; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Pixel8U result; |
|
|
|
|
result[0] = (uint8_t)CLAMP(sum[0], 0.0, 255.0); |
|
|
|
|
result[1] = (uint8_t)CLAMP(sum[1], 0.0, 255.0); |
|
|
|
|
result[2] = (uint8_t)CLAMP(sum[2], 0.0, 255.0); |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 计算视图缩放因子
|
|
|
|
|
float MeshTexture::CalculateViewScale(const Camera& cam, const Point3d& pos) { |
|
|
|
|
// ✅ 全部使用 double
|
|
|
|
|
Point2d proj = cam.ProjectPointP(pos); |
|
|
|
|
// Point2d proj_dx = cam.ProjectPointP(pos + Point3d(0.01, 0.0, 0.0));
|
|
|
|
|
// Point2d proj_dy = cam.ProjectPointP(pos + Point3d(0.0, 0.01, 0.0));
|
|
|
|
|
|
|
|
|
|
// const double pixelArea = std::abs(
|
|
|
|
|
// (proj_dx.x - proj.x) * (proj_dy.y - proj.y) -
|
|
|
|
|
// (proj_dx.y - proj.y) * (proj_dy.x - proj.x)
|
|
|
|
|
// );
|
|
|
|
|
constexpr double delta = 0.01; |
|
|
|
|
|
|
|
|
|
// // ✅ 返回尺度权重
|
|
|
|
|
// return static_cast<float>(std::min(1.0, pixelArea / 4.0));
|
|
|
|
|
return 0.0f; |
|
|
|
|
// ✅ 全部使用 SEACAVE::TPoint3<double>(即 Point3d)
|
|
|
|
|
const Point2d p0 = cam.ProjectPointP(pos); |
|
|
|
|
|
|
|
|
|
// ✅ 使用 Point3d 构造偏移点(不要用 cv::Point3d)
|
|
|
|
|
const Point2d p1 = cam.ProjectPointP( |
|
|
|
|
Point3d(pos.x + delta, pos.y, pos.z) |
|
|
|
|
); |
|
|
|
|
const Point2d p2 = cam.ProjectPointP( |
|
|
|
|
Point3d(pos.x, pos.y + delta, pos.z) |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// 计算投影后的微小平行四边形面积(像素²)
|
|
|
|
|
const double area = std::abs( |
|
|
|
|
(p1.x - p0.x) * (p2.y - p0.y) - |
|
|
|
|
(p1.y - p0.y) * (p2.x - p0.x) |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
return static_cast<float>(area); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool MeshTexture::SelectBestViewForTexel(const Point3f& worldPos, |
|
|
|
|
const Normal& normal, |
|
|
|
|
const Normal& /*normal*/, |
|
|
|
|
const std::vector<IIndex>& candidateViews, |
|
|
|
|
const std::vector<float>& viewWeights, |
|
|
|
|
TexelViewInfo& result) { |
|
|
|
|
const std::vector<float>& /*viewWeights*/, |
|
|
|
|
TexelViewInfo& result) |
|
|
|
|
{ |
|
|
|
|
result.best_weight = -1.0f; |
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < candidateViews.size(); ++i) { |
|
|
|
|
const IIndex viewId = candidateViews[i]; |
|
|
|
|
const Image& img = images[viewId]; |
|
|
|
|
|
|
|
|
|
// 1. 投影验证
|
|
|
|
|
Point2f proj = ProjectPointWithAutoCorrection(img.camera, worldPos, img); |
|
|
|
|
if (!ValidateProjection(worldPos, img, proj) || |
|
|
|
|
!img.image.isInside(proj) || |
|
|
|
|
!img.camera.IsInFront(worldPos)) { |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 2. 计算视角权重(关键:使用真实视角角度)
|
|
|
|
|
Point3f camCenter( |
|
|
|
|
static_cast<float>(img.camera.C.x), |
|
|
|
|
static_cast<float>(img.camera.C.y), |
|
|
|
|
static_cast<float>(img.camera.C.z) |
|
|
|
|
// ✅ 和 V1 完全一致
|
|
|
|
|
Point2f proj = ProjectPointWithAutoCorrection( |
|
|
|
|
img.camera, |
|
|
|
|
Vertex(worldPos.x, worldPos.y, worldPos.z), |
|
|
|
|
img |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
Point3f viewDir = camCenter - worldPos; |
|
|
|
|
|
|
|
|
|
viewDir = NormalizePoint3(viewDir); |
|
|
|
|
float cosAngle = normal.dot(viewDir); |
|
|
|
|
|
|
|
|
|
// 3. 计算尺度权重(避免远处视图的模糊)
|
|
|
|
|
float scale = CalculateViewScale(img.camera, worldPos); |
|
|
|
|
// ✅ 只用最基本、最安全的检查
|
|
|
|
|
if (!img.camera.IsInFront(Vertex(worldPos.x, worldPos.y, worldPos.z))) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
// 4. 综合评分
|
|
|
|
|
float score = viewWeights[i] * cosAngle * scale; |
|
|
|
|
if (!img.image.isInside(proj)) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
if (score > result.best_weight) { |
|
|
|
|
result.best_weight = score; |
|
|
|
|
// ✅ 先不选“最佳”,先选“第一个能用的”
|
|
|
|
|
result.best_weight = 1.0f; |
|
|
|
|
result.best_view_id = viewId; |
|
|
|
|
result.best_proj = proj; |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return result.best_weight > 0.1f; |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容
|
|
|
|
|
@ -15233,7 +15356,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
@@ -15233,7 +15356,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
|
|
|
|
for (int x = startX; x <= endX; ++x) { |
|
|
|
|
const Point2f texCoord((float)x / textureSize, (float)y / textureSize); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
// 计算重心坐标
|
|
|
|
|
Point3f barycentric; |
|
|
|
|
if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { |
|
|
|
|
@ -15284,39 +15406,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
@@ -15284,39 +15406,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
|
|
|
|
pointColors.push_back(accumColor); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
//*/
|
|
|
|
|
|
|
|
|
|
//*
|
|
|
|
|
// 获取3D位置和法线
|
|
|
|
|
Point3f worldPos; |
|
|
|
|
Normal normal; |
|
|
|
|
FIndex faceID; |
|
|
|
|
if (!GetWorldPositionAndNormal(texCoord, worldPos, normal, faceID)) continue; |
|
|
|
|
|
|
|
|
|
// 查找虚拟面
|
|
|
|
|
if (idxVF >= faceViews.size()) continue; |
|
|
|
|
|
|
|
|
|
// 1. 逐像素选图(关键!)
|
|
|
|
|
TexelViewInfo viewInfo; |
|
|
|
|
if (!SelectBestViewForTexel(worldPos, normal, |
|
|
|
|
faceViews[idxVF], |
|
|
|
|
faceViewWeights[idxVF], |
|
|
|
|
viewInfo)) { |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// // 2. 自适应纹素密度
|
|
|
|
|
// int adaptiveSize = GetAdaptiveTexelSize(worldPos,
|
|
|
|
|
// faceViews[idxVF],
|
|
|
|
|
// textureSize);
|
|
|
|
|
|
|
|
|
|
// // 3. 从最佳视图采样(使用双三次插值保持清晰度)
|
|
|
|
|
// const Image& bestImg = images[viewInfo.best_view_id];
|
|
|
|
|
// Pixel8U color = SampleImageBicubic(bestImg.image, viewInfo.best_proj);
|
|
|
|
|
|
|
|
|
|
// // 存储结果
|
|
|
|
|
// textureAtlas(y, x) = color;
|
|
|
|
|
//*/
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -15389,7 +15478,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
@@ -15389,7 +15478,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//*
|
|
|
|
|
// 6. 应用权重归一化
|
|
|
|
|
DEBUG_EXTRA("Applying weight normalization for virtual faces"); |
|
|
|
|
for (int y = 0; y < textureSize; ++y) { |
|
|
|
|
@ -15412,7 +15500,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
@@ -15412,7 +15500,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
//*/
|
|
|
|
|
|
|
|
|
|
// 7. 填充缝隙和未采样区域
|
|
|
|
|
DEBUG_EXTRA("Filling gaps in texture atlas for virtual faces"); |
|
|
|
|
@ -15429,6 +15516,122 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
@@ -15429,6 +15516,122 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
|
|
|
|
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete"); |
|
|
|
|
return textures; |
|
|
|
|
} |
|
|
|
|
*/ |
|
|
|
|
//*
|
|
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
const VirtualFaceDataArr& virtualFaceDatas, |
|
|
|
|
const std::vector<std::vector<IIndex>>& faceViews, |
|
|
|
|
const std::vector<std::vector<float>>& faceViewWeights, |
|
|
|
|
unsigned nTextureSizeMultiple, |
|
|
|
|
Pixel8U colEmpty, |
|
|
|
|
float fSharpnessWeight) |
|
|
|
|
{ |
|
|
|
|
DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces"); |
|
|
|
|
|
|
|
|
|
// 1. UV边界
|
|
|
|
|
AABB2f uvBounds(true); |
|
|
|
|
FOREACH(i, scene.mesh.faceTexcoords) |
|
|
|
|
uvBounds.InsertFull(scene.mesh.faceTexcoords[i]); |
|
|
|
|
|
|
|
|
|
// 2. 纹理尺寸
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); |
|
|
|
|
|
|
|
|
|
// 3. 创建纹理图集(只做这一件事)
|
|
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
|
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); |
|
|
|
|
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
|
|
|
|
|
// 缓存列数和数据指针(性能关键)
|
|
|
|
|
const int cols = textureAtlas.cols; |
|
|
|
|
Pixel8U* data = reinterpret_cast<Pixel8U*>(textureAtlas.data); |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Texture atlas size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]", |
|
|
|
|
textureSize, textureSize, |
|
|
|
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(), |
|
|
|
|
uvBounds.ptMax.x(), uvBounds.ptMax.y()); |
|
|
|
|
|
|
|
|
|
// 4. 遍历虚拟面(OpenMP安全,因为只写不读)
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
|
#endif |
|
|
|
|
for (int_t idxVF = 0; idxVF < (int_t)virtualFaceMap.size(); ++idxVF) { |
|
|
|
|
const VirtualFace& vf = virtualFaceMap[idxVF]; |
|
|
|
|
if (faceViews[idxVF].empty()) continue; |
|
|
|
|
|
|
|
|
|
for (FIndex faceID : vf.faces) { |
|
|
|
|
const Face& face = scene.mesh.faces[faceID]; |
|
|
|
|
const TexCoord* uv = &scene.mesh.faceTexcoords[faceID * 3]; |
|
|
|
|
const Vertex* vtx = &scene.mesh.vertices[face[0]]; |
|
|
|
|
const Normal& faceNormal = scene.mesh.faceNormals[faceID]; |
|
|
|
|
|
|
|
|
|
// UV边界框
|
|
|
|
|
AABB2f uvBox(true); |
|
|
|
|
uvBox.InsertFull(uv[0]); |
|
|
|
|
uvBox.InsertFull(uv[1]); |
|
|
|
|
uvBox.InsertFull(uv[2]); |
|
|
|
|
|
|
|
|
|
const int x0 = std::max(0, (int)(uvBox.ptMin.x() * textureSize)); |
|
|
|
|
const int y0 = std::max(0, (int)(uvBox.ptMin.y() * textureSize)); |
|
|
|
|
const int x1 = std::min(textureSize - 1, (int)(uvBox.ptMax.x() * textureSize)); |
|
|
|
|
const int y1 = std::min(textureSize - 1, (int)(uvBox.ptMax.y() * textureSize)); |
|
|
|
|
|
|
|
|
|
// 遍历纹素
|
|
|
|
|
for (int y = y0; y <= y1; ++y) { |
|
|
|
|
for (int x = x0; x <= x1; ++x) { |
|
|
|
|
const Point2f texCoord((float)x / textureSize, (float)y / textureSize); |
|
|
|
|
|
|
|
|
|
// 重心坐标测试
|
|
|
|
|
Point3f bary; |
|
|
|
|
if (!PointInTriangle(texCoord, uv[0], uv[1], uv[2], bary)) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
// 计算世界点(直接插值,O(1))
|
|
|
|
|
Point3d worldPos( |
|
|
|
|
vtx[0].x * bary.x + vtx[1].x * bary.y + vtx[2].x * bary.z, |
|
|
|
|
vtx[0].y * bary.x + vtx[1].y * bary.y + vtx[2].y * bary.z, |
|
|
|
|
vtx[0].z * bary.x + vtx[1].z * bary.y + vtx[2].z * bary.z |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// 逐像素选图
|
|
|
|
|
TexelViewInfo viewInfo; |
|
|
|
|
if (!SelectBestViewForTexel( |
|
|
|
|
Point3f(worldPos.x, worldPos.y, worldPos.z), |
|
|
|
|
faceNormal, |
|
|
|
|
faceViews[idxVF], |
|
|
|
|
faceViewWeights[idxVF], |
|
|
|
|
viewInfo)) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
// 采样并写入
|
|
|
|
|
const Image& bestImg = images[viewInfo.best_view_id]; |
|
|
|
|
// Pixel8U color = SampleImageBicubic(bestImg.image, viewInfo.best_proj);
|
|
|
|
|
Pixel8U color = SampleImageBilinear(bestImg.image, viewInfo.best_proj); |
|
|
|
|
|
|
|
|
|
// // ✅ 正确、官方、颜色100%一致的写法
|
|
|
|
|
// Pixel8U color;
|
|
|
|
|
// bestImg.image.sample(
|
|
|
|
|
// viewInfo.best_proj.x,
|
|
|
|
|
// viewInfo.best_proj.y,
|
|
|
|
|
// color,
|
|
|
|
|
// SEACAVE::IMAGE_SAMPLE_BICUBIC
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
|
|
data[y * cols + x] = color; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete"); |
|
|
|
|
return textures; |
|
|
|
|
} |
|
|
|
|
//*/
|
|
|
|
|
|
|
|
|
|
bool MeshTexture::TextureWithExistingUV( |
|
|
|
|
const IIndexArr& views, |
|
|
|
|
@ -16722,26 +16925,30 @@ cv::Vec3b MeshTexture::ConvertBGRtoRGBIfNeeded(const cv::Vec3b& bgrColor) {
@@ -16722,26 +16925,30 @@ cv::Vec3b MeshTexture::ConvertBGRtoRGBIfNeeded(const cv::Vec3b& bgrColor) {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Point2f MeshTexture::ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage) { |
|
|
|
|
Point2f MeshTexture::ProjectPointWithAutoCorrection(const Camera& camera, |
|
|
|
|
const Vertex& worldPoint, |
|
|
|
|
const Image& sourceImage) { |
|
|
|
|
// ✅ 只做一件事:严格投影
|
|
|
|
|
Point2f imgPoint = camera.ProjectPointP(worldPoint); |
|
|
|
|
|
|
|
|
|
// 检查投影点是否在有效范围内
|
|
|
|
|
// ✅ 只验证,不修改
|
|
|
|
|
if (!sourceImage.image.isInside(imgPoint)) { |
|
|
|
|
// 尝试不同的偏移量来找到最佳投影点
|
|
|
|
|
std::vector<Point2f> testOffsets = { |
|
|
|
|
Point2f(0, sourceImage.image.rows * 0.015f), // 当前使用的偏移
|
|
|
|
|
Point2f(0, -sourceImage.image.rows * 0.015f), // 反向偏移
|
|
|
|
|
Point2f(sourceImage.image.cols * 0.015f, 0), // 水平偏移
|
|
|
|
|
Point2f(0, 0) // 无偏移
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
for (const auto& offset : testOffsets) { |
|
|
|
|
Point2f testPoint = imgPoint + offset; |
|
|
|
|
if (sourceImage.image.isInside(testPoint) && camera.IsInFront(worldPoint)) { |
|
|
|
|
return testPoint; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// 记录调试信息,但不修改点
|
|
|
|
|
// DEBUG_EXTRA("投影点(%.3f, %.3f)超出图像范围(%d,%d)",
|
|
|
|
|
// imgPoint.x, imgPoint.y,
|
|
|
|
|
// sourceImage.image.width(),
|
|
|
|
|
// sourceImage.image.height());
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ✅ 可选:轻微的数值稳定性修正(不是偏移!)
|
|
|
|
|
// 例如:处理浮点精度导致的边界外一点的情况
|
|
|
|
|
const float epsilon = 0.001f; |
|
|
|
|
if (imgPoint.x < 0 && imgPoint.x > -epsilon) imgPoint.x = 0; |
|
|
|
|
if (imgPoint.y < 0 && imgPoint.y > -epsilon) imgPoint.y = 0; |
|
|
|
|
if (imgPoint.x >= sourceImage.image.width() - epsilon) |
|
|
|
|
imgPoint.x = static_cast<float>(sourceImage.image.width() - 1); |
|
|
|
|
if (imgPoint.y >= sourceImage.image.height() - epsilon) |
|
|
|
|
imgPoint.y = static_cast<float>(sourceImage.image.height() - 1); |
|
|
|
|
|
|
|
|
|
return imgPoint; |
|
|
|
|
} |
|
|
|
|
@ -17322,26 +17529,28 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(
@@ -17322,26 +17529,28 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool MeshTexture::ValidateProjection(const Vertex& worldPoint, |
|
|
|
|
const Image& sourceImage, Point2f imgPoint, |
|
|
|
|
const Image& sourceImage, |
|
|
|
|
Point2f imgPoint, |
|
|
|
|
float maxReprojectionError) { |
|
|
|
|
// 1. 前向投影:3D点 → 2D图像坐标
|
|
|
|
|
Point2f projectedPoint = sourceImage.camera.ProjectPointP(worldPoint); |
|
|
|
|
|
|
|
|
|
// 2. 计算重投影误差
|
|
|
|
|
float reprojectionError = norm(projectedPoint - imgPoint); |
|
|
|
|
|
|
|
|
|
// 3. 设置误差阈值
|
|
|
|
|
if (reprojectionError > maxReprojectionError) { |
|
|
|
|
DEBUG_EXTRA("重投影误差过大: %.3f像素,跳过该采样点", reprojectionError); |
|
|
|
|
// 1. 检查点是否在相机前方(最重要!)
|
|
|
|
|
if (!sourceImage.camera.IsInFront(worldPoint)) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 4. 视线方向一致性检查
|
|
|
|
|
if (!sourceImage.camera.IsInFront(worldPoint)) { |
|
|
|
|
DEBUG_EXTRA("点位于相机后方,跳过"); |
|
|
|
|
// 2. 检查投影点是否在图像内(允许微小的数值误差)
|
|
|
|
|
const int width = sourceImage.image.width(); |
|
|
|
|
const int height = sourceImage.image.height(); |
|
|
|
|
const float epsilon = 0.001f; |
|
|
|
|
|
|
|
|
|
if (imgPoint.x < -epsilon || imgPoint.x >= width + epsilon || |
|
|
|
|
imgPoint.y < -epsilon || imgPoint.y >= height + epsilon) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 3. ✅ 删除重投影误差检查!
|
|
|
|
|
// 原因:在纹理映射中,我们没有“观测点”
|
|
|
|
|
// 投影点本身就是理论值,不需要和自己比较
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -17497,61 +17706,42 @@ void MeshTexture::ProjectFaceToTexture(FIndex faceID, IIndex viewID,
@@ -17497,61 +17706,42 @@ void MeshTexture::ProjectFaceToTexture(FIndex faceID, IIndex viewID,
|
|
|
|
|
* @param bary 输出的重心坐标 (1-u-v, u, v) |
|
|
|
|
* @return bool 如果点在三角形内返回true,否则返回false |
|
|
|
|
*/ |
|
|
|
|
bool MeshTexture::PointInTriangle(const Point2f& p, const Point2f& a, const Point2f& b, const Point2f& c, Point3f& bary) |
|
|
|
|
{ |
|
|
|
|
// 添加调试输出
|
|
|
|
|
// DEBUG_EXTRA("PointInTriangle - Input: p(%.6f,%.6f), a(%.6f,%.6f), b(%.6f,%.6f), c(%.6f,%.6f)",
|
|
|
|
|
// p.x, p.y, a.x, a.y, b.x, b.y, c.x, c.y);
|
|
|
|
|
|
|
|
|
|
// 检查输入有效性
|
|
|
|
|
if (!std::isfinite(p.x) || !std::isfinite(p.y) || |
|
|
|
|
!std::isfinite(a.x) || !std::isfinite(a.y) || |
|
|
|
|
!std::isfinite(b.x) || !std::isfinite(b.y) || |
|
|
|
|
!std::isfinite(c.x) || !std::isfinite(c.y)) { |
|
|
|
|
// DEBUG_EXTRA("PointInTriangle - Invalid input coordinates");
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 计算边向量
|
|
|
|
|
Point2f v0 = b - a; |
|
|
|
|
Point2f v1 = c - a; |
|
|
|
|
Point2f v2 = p - a; |
|
|
|
|
|
|
|
|
|
// 计算必要的点积
|
|
|
|
|
float dot00 = v0.x * v0.x + v0.y * v0.y; |
|
|
|
|
float dot01 = v0.x * v1.x + v0.y * v1.y; |
|
|
|
|
float dot02 = v0.x * v2.x + v0.y * v2.y; |
|
|
|
|
float dot11 = v1.x * v1.x + v1.y * v1.y; |
|
|
|
|
float dot12 = v1.x * v2.x + v1.y * v2.y; |
|
|
|
|
|
|
|
|
|
// 计算分母(三角形平行四边形面积的两倍)
|
|
|
|
|
float denom = dot00 * dot11 - dot01 * dot01; |
|
|
|
|
|
|
|
|
|
// 处理退化三角形情况(面积接近0)
|
|
|
|
|
const float epsilon = 1e-10f; |
|
|
|
|
if (std::abs(denom) < epsilon) { |
|
|
|
|
// DEBUG_EXTRA("PointInTriangle - Degenerate triangle, denom=%.10f", denom);
|
|
|
|
|
// return false;
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 计算重心坐标参数
|
|
|
|
|
float invDenom = 1.0f / denom; |
|
|
|
|
float u = (dot11 * dot02 - dot01 * dot12) * invDenom; |
|
|
|
|
float v = (dot00 * dot12 - dot01 * dot02) * invDenom; |
|
|
|
|
|
|
|
|
|
// DEBUG_EXTRA("PointInTriangle - u=%.6f, v=%.6f, u+v=%.6f", u, v, u+v);
|
|
|
|
|
|
|
|
|
|
// 检查点是否在三角形内(使用更宽松的容差)
|
|
|
|
|
if (u >= -epsilon && v >= -epsilon && (u + v) <= 1.0f + epsilon) { |
|
|
|
|
// 点在三角形内,计算完整的重心坐标
|
|
|
|
|
bary.x = 1.0f - u - v; |
|
|
|
|
bary.y = u; |
|
|
|
|
bary.z = v; |
|
|
|
|
// DEBUG_EXTRA("PointInTriangle - Point INSIDE triangle, bary(%.3f,%.3f,%.3f)", bary.x, bary.y, bary.z);
|
|
|
|
|
/// 判断2D点是否位于三角形内部,并计算对应的重心坐标
|
|
|
|
|
/// @param p 待检测的2D点(纹理坐标,范围[0,1],Point2f=TPoint2<float>,x/y为公有成员)
|
|
|
|
|
/// @param a/b/c 三角形的三个顶点(纹理坐标,TexCoord=TPoint2<float>,x/y为公有成员)
|
|
|
|
|
/// @param bary 输出的重心坐标(u,v,w分别对应a,b,c的权重,Point3f=TPoint3<float>,x/y/z为公有成员)
|
|
|
|
|
/// @return 点在三角形内(含边界)返回true,否则false
|
|
|
|
|
bool MeshTexture::PointInTriangle(const Point2f& p, |
|
|
|
|
const TexCoord& a, |
|
|
|
|
const TexCoord& b, |
|
|
|
|
const TexCoord& c, |
|
|
|
|
Point3f& bary) |
|
|
|
|
{ |
|
|
|
|
// 直接访问顶点坐标公有成员,无函数调用开销,高频调用性能最优
|
|
|
|
|
const float ax = a.x, ay = a.y; |
|
|
|
|
const float bx = b.x, by = b.y; |
|
|
|
|
const float cx = c.x, cy = c.y; |
|
|
|
|
const float px = p.x, py = p.y; |
|
|
|
|
|
|
|
|
|
// 计算重心坐标行列式(三角形有向面积的2倍,过滤退化三角形)
|
|
|
|
|
const float det = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy); |
|
|
|
|
// 阈值1e-10f适配UV坐标精度,避免浮点误差导致的误判
|
|
|
|
|
if (std::abs(det) < 1e-10f) return false; |
|
|
|
|
|
|
|
|
|
const float invDet = 1.0f / det; |
|
|
|
|
// 计算重心坐标u(a的权重)、v(b的权重)
|
|
|
|
|
const float u = ((by - cy) * (px - cx) + (cx - bx) * (py - cy)) * invDet; |
|
|
|
|
const float v = ((cy - ay) * (px - cx) + (ax - cx) * (py - cy)) * invDet; |
|
|
|
|
const float w = 1.0f - u - v; |
|
|
|
|
|
|
|
|
|
// 宽松边界判断:允许微小数值误差,确保UV三角形边缘像素不被过滤(解决纹理空白核心逻辑)
|
|
|
|
|
constexpr float eps = 1e-6f; |
|
|
|
|
if (u >= -eps && v >= -eps && w >= -eps) { |
|
|
|
|
bary.x = u; // Point3f的x为公有成员,直接赋值
|
|
|
|
|
bary.y = v; // Point3f的y为公有成员,直接赋值
|
|
|
|
|
bary.z = w; // Point3f的z为公有成员,直接赋值
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// DEBUG_EXTRA("PointInTriangle - Point OUTSIDE triangle");
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|