单像素清晰化保存
This commit is contained in:
1 parent
d95c2fc8ff
commit
5229b4f284
1 file changed
+366
-176
+366
-176
@@ -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(
|
||||
return false;
|
||||
}
|
||||
|
||||
// 计算视图缩放因子
|
||||
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)
|
||||
// );
|
||||
|
||||
// // ✅ 返回尺度权重
|
||||
// return static_cast<float>(std::min(1.0, pixelArea / 4.0));
|
||||
return 0.0f;
|
||||
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);
|
||||
}
|
||||
|
||||
bool MeshTexture::SelectBestViewForTexel(const Point3f& worldPos,
|
||||
const Normal& normal,
|
||||
const std::vector<IIndex>& candidateViews,
|
||||
const std::vector<float>& viewWeights,
|
||||
TexelViewInfo& result) {
|
||||
result.best_weight = -1.0f;
|
||||
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);
|
||||
|
||||
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)
|
||||
);
|
||||
|
||||
Point3f viewDir = camCenter - worldPos;
|
||||
|
||||
viewDir = NormalizePoint3(viewDir);
|
||||
float cosAngle = normal.dot(viewDir);
|
||||
|
||||
// 3. 计算尺度权重(避免远处视图的模糊)
|
||||
float scale = CalculateViewScale(img.camera, worldPos);
|
||||
|
||||
// 4. 综合评分
|
||||
float score = viewWeights[i] * cosAngle * scale;
|
||||
|
||||
if (score > result.best_weight) {
|
||||
result.best_weight = score;
|
||||
result.best_view_id = viewId;
|
||||
result.best_proj = proj;
|
||||
// 密度越高,纹素越小(分辨率越高)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return result.best_weight > 0.1f;
|
||||
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) {
|
||||
constexpr double delta = 0.01;
|
||||
|
||||
// ✅ 全部使用 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 std::vector<IIndex>& candidateViews,
|
||||
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];
|
||||
|
||||
// ✅ 和 V1 完全一致
|
||||
Point2f proj = ProjectPointWithAutoCorrection(
|
||||
img.camera,
|
||||
Vertex(worldPos.x, worldPos.y, worldPos.z),
|
||||
img
|
||||
);
|
||||
|
||||
// ✅ 只用最基本、最安全的检查
|
||||
if (!img.camera.IsInFront(Vertex(worldPos.x, worldPos.y, worldPos.z)))
|
||||
continue;
|
||||
|
||||
if (!img.image.isInside(proj))
|
||||
continue;
|
||||
|
||||
// ✅ 先不选“最佳”,先选“第一个能用的”
|
||||
result.best_weight = 1.0f;
|
||||
result.best_view_id = viewId;
|
||||
result.best_proj = proj;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||
const VirtualFaceMap& virtualFaceMap,
|
||||
const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容
|
||||
@@ -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(
|
||||
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(
|
||||
}
|
||||
}
|
||||
|
||||
//*
|
||||
// 6. 应用权重归一化
|
||||
DEBUG_EXTRA("Applying weight normalization for virtual faces");
|
||||
for (int y = 0; y < textureSize; ++y) {
|
||||
@@ -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(
|
||||
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,27 +16925,31 @@ 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(
|
||||
}
|
||||
|
||||
bool MeshTexture::ValidateProjection(const Vertex& worldPoint,
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 视线方向一致性检查
|
||||
const Image& sourceImage,
|
||||
Point2f imgPoint,
|
||||
float maxReprojectionError) {
|
||||
// 1. 检查点是否在相机前方(最重要!)
|
||||
if (!sourceImage.camera.IsInFront(worldPoint)) {
|
||||
DEBUG_EXTRA("点位于相机后方,跳过");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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,
|
||||
* @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)
|
||||
/// 判断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)
|
||||
{
|
||||
// 添加调试输出
|
||||
// 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);
|
||||
// 直接访问顶点坐标公有成员,无函数调用开销,高频调用性能最优
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user