添加清晰化功能
This commit is contained in:
1 parent
5229b4f284
commit
6922abe692
1 file changed
+211
-47
+211
-47
@@ -676,17 +676,27 @@ public:
|
|||||||
bool CheckUVContinuity(const std::vector<FIndex>& faceList);
|
bool CheckUVContinuity(const std::vector<FIndex>& faceList);
|
||||||
|
|
||||||
struct TexelViewInfo {
|
struct TexelViewInfo {
|
||||||
IIndex best_view_id; // 最佳视图ID
|
float best_weight;
|
||||||
float best_weight; // 最佳权重
|
IIndex best_view_id;
|
||||||
Point2f best_proj; // 最佳投影坐标
|
Point2f best_proj;
|
||||||
std::vector<IIndex> candidate_views; // 候选视图列表
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool SelectBestViewForTexel(const Point3f& worldPos,
|
bool SelectBestViewForTexel(const Point3f& worldPos,
|
||||||
const Normal& normal,
|
const Normal& /*normal*/,
|
||||||
|
const std::vector<IIndex>& candidateViews,
|
||||||
|
const std::vector<float>& viewWeights,
|
||||||
|
TexelViewInfo& result,
|
||||||
|
std::vector<std::pair<IIndex, Point2f>>& topViews);
|
||||||
|
bool SelectBestSingleView(
|
||||||
|
const Point3f& worldPos,
|
||||||
|
const Normal& /*normal*/,
|
||||||
const std::vector<IIndex>& candidateViews,
|
const std::vector<IIndex>& candidateViews,
|
||||||
const std::vector<float>& viewWeights,
|
const std::vector<float>& viewWeights,
|
||||||
TexelViewInfo& result);
|
TexelViewInfo& result);
|
||||||
|
Pixel8U BlendTopTwoViews(
|
||||||
|
const std::vector<std::pair<IIndex, Point2f>>& topViews,
|
||||||
|
const std::vector<IIndex>& candidateViews,
|
||||||
|
const std::vector<float>& viewWeights);
|
||||||
|
|
||||||
float CalculateViewScale(const Camera& cam, const Point3d& pos);
|
float CalculateViewScale(const Camera& cam, const Point3d& pos);
|
||||||
|
|
||||||
@@ -15241,40 +15251,125 @@ float MeshTexture::CalculateViewScale(const Camera& cam, const Point3d& pos) {
|
|||||||
bool MeshTexture::SelectBestViewForTexel(const Point3f& worldPos,
|
bool MeshTexture::SelectBestViewForTexel(const Point3f& worldPos,
|
||||||
const Normal& /*normal*/,
|
const Normal& /*normal*/,
|
||||||
const std::vector<IIndex>& candidateViews,
|
const std::vector<IIndex>& candidateViews,
|
||||||
const std::vector<float>& /*viewWeights*/,
|
const std::vector<float>& viewWeights,
|
||||||
TexelViewInfo& result)
|
TexelViewInfo& result,
|
||||||
|
std::vector<std::pair<IIndex, Point2f>>& topViews)
|
||||||
{
|
{
|
||||||
|
topViews.clear();
|
||||||
result.best_weight = -1.0f;
|
result.best_weight = -1.0f;
|
||||||
|
result.best_view_id = static_cast<IIndex>(-1);
|
||||||
|
|
||||||
|
struct CandidateView {
|
||||||
|
IIndex viewId;
|
||||||
|
Point2f proj;
|
||||||
|
float weight;
|
||||||
|
};
|
||||||
|
std::vector<CandidateView> candidates;
|
||||||
|
candidates.reserve(candidateViews.size());
|
||||||
|
|
||||||
|
// 收集所有有效候选视图
|
||||||
for (size_t i = 0; i < candidateViews.size(); ++i) {
|
for (size_t i = 0; i < candidateViews.size(); ++i) {
|
||||||
const IIndex viewId = candidateViews[i];
|
const IIndex viewId = candidateViews[i];
|
||||||
|
if (viewId >= images.size())
|
||||||
|
continue;
|
||||||
|
|
||||||
const Image& img = images[viewId];
|
const Image& img = images[viewId];
|
||||||
|
|
||||||
// ✅ 和 V1 完全一致
|
|
||||||
Point2f proj = ProjectPointWithAutoCorrection(
|
Point2f proj = ProjectPointWithAutoCorrection(
|
||||||
img.camera,
|
img.camera,
|
||||||
Vertex(worldPos.x, worldPos.y, worldPos.z),
|
Vertex(worldPos.x, worldPos.y, worldPos.z),
|
||||||
img
|
img
|
||||||
);
|
);
|
||||||
|
|
||||||
// ✅ 只用最基本、最安全的检查
|
// 严格的投影验证
|
||||||
if (!img.camera.IsInFront(Vertex(worldPos.x, worldPos.y, worldPos.z)))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!img.image.isInside(proj))
|
if (!img.image.isInside(proj))
|
||||||
continue;
|
continue;
|
||||||
|
if (!img.camera.IsInFront(worldPos))
|
||||||
|
continue;
|
||||||
|
if (!ValidateProjection(worldPos, img, proj))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const float w = viewWeights[i];
|
||||||
|
if (w <= 0.1f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
candidates.push_back({viewId, proj, w});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidates.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// 按权重降序排序
|
||||||
|
std::sort(candidates.begin(), candidates.end(),
|
||||||
|
[](const CandidateView& a, const CandidateView& b) {
|
||||||
|
return a.weight > b.weight;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 取前两个(如果有两个的话)
|
||||||
|
const int numViews = std::min(2, (int)candidates.size());
|
||||||
|
for (int i = 0; i < numViews; ++i) {
|
||||||
|
topViews.emplace_back(candidates[i].viewId, candidates[i].proj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置最佳视图信息
|
||||||
|
result.best_view_id = candidates[0].viewId;
|
||||||
|
result.best_proj = candidates[0].proj;
|
||||||
|
result.best_weight = candidates[0].weight;
|
||||||
|
|
||||||
// ✅ 先不选“最佳”,先选“第一个能用的”
|
|
||||||
result.best_weight = 1.0f;
|
|
||||||
result.best_view_id = viewId;
|
|
||||||
result.best_proj = proj;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
Pixel8U MeshTexture::BlendTopTwoViews(
|
||||||
|
const std::vector<std::pair<IIndex, Point2f>>& topViews,
|
||||||
|
const std::vector<IIndex>& candidateViews,
|
||||||
|
const std::vector<float>& viewWeights)
|
||||||
|
{
|
||||||
|
if (topViews.empty())
|
||||||
|
return Pixel8U(0, 0, 0);
|
||||||
|
|
||||||
|
// 单视图情况
|
||||||
|
if (topViews.size() == 1) {
|
||||||
|
const Image& img = images[topViews[0].first];
|
||||||
|
return SampleImageBicubic(img.image, topViews[0].second);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// 双视图融合
|
||||||
|
double sumB = 0, sumG = 0, sumR = 0;
|
||||||
|
float totalWeight = 0.0f;
|
||||||
|
|
||||||
|
for (const auto& view : topViews) {
|
||||||
|
const Image& img = images[view.first];
|
||||||
|
Pixel8U color = SampleImageBicubic(img.image, view.second);
|
||||||
|
|
||||||
|
// 查找对应权重
|
||||||
|
float w = 0.0f;
|
||||||
|
for (size_t i = 0; i < candidateViews.size(); ++i) {
|
||||||
|
if (candidateViews[i] == view.first) {
|
||||||
|
w = viewWeights[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (w <= 0.0f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sumB += color[0] * w;
|
||||||
|
sumG += color[1] * w;
|
||||||
|
sumR += color[2] * w;
|
||||||
|
totalWeight += w;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalWeight < 1e-6f)
|
||||||
|
return Pixel8U(0, 0, 0);
|
||||||
|
|
||||||
|
Pixel8U result;
|
||||||
|
result[0] = cv::saturate_cast<uchar>(sumB / totalWeight);
|
||||||
|
result[1] = cv::saturate_cast<uchar>(sumG / totalWeight);
|
||||||
|
result[2] = cv::saturate_cast<uchar>(sumR / totalWeight);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//*
|
||||||
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||||
const VirtualFaceMap& virtualFaceMap,
|
const VirtualFaceMap& virtualFaceMap,
|
||||||
const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容
|
const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容
|
||||||
@@ -15516,16 +15611,16 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
|||||||
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete");
|
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete");
|
||||||
return textures;
|
return textures;
|
||||||
}
|
}
|
||||||
*/
|
//*/
|
||||||
//*
|
/*
|
||||||
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||||
const VirtualFaceMap& virtualFaceMap,
|
const VirtualFaceMap& virtualFaceMap,
|
||||||
const VirtualFaceDataArr& virtualFaceDatas,
|
const VirtualFaceDataArr&,
|
||||||
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,
|
||||||
Pixel8U colEmpty,
|
Pixel8U colEmpty,
|
||||||
float fSharpnessWeight)
|
float)
|
||||||
{
|
{
|
||||||
DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces");
|
DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces");
|
||||||
|
|
||||||
@@ -15542,12 +15637,12 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
|||||||
|
|
||||||
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple);
|
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple);
|
||||||
|
|
||||||
// 3. 创建纹理图集(只做这一件事)
|
// 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));
|
||||||
|
|
||||||
// 缓存列数和数据指针(性能关键)
|
// 缓存列数和数据指针
|
||||||
const int cols = textureAtlas.cols;
|
const int cols = textureAtlas.cols;
|
||||||
Pixel8U* data = reinterpret_cast<Pixel8U*>(textureAtlas.data);
|
Pixel8U* data = reinterpret_cast<Pixel8U*>(textureAtlas.data);
|
||||||
|
|
||||||
@@ -15556,20 +15651,48 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
|||||||
uvBounds.ptMin.x(), uvBounds.ptMin.y(),
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(),
|
||||||
uvBounds.ptMax.x(), uvBounds.ptMax.y());
|
uvBounds.ptMax.x(), uvBounds.ptMax.y());
|
||||||
|
|
||||||
// 4. 遍历虚拟面(OpenMP安全,因为只写不读)
|
// 4. 遍历虚拟面(OpenMP并行)
|
||||||
#ifdef _USE_OPENMP
|
#ifdef _USE_OPENMP
|
||||||
#pragma omp parallel for schedule(dynamic)
|
#pragma omp parallel for schedule(dynamic)
|
||||||
#endif
|
#endif
|
||||||
for (int_t idxVF = 0; idxVF < (int_t)virtualFaceMap.size(); ++idxVF) {
|
for (int_t idxVF = 0; idxVF < (int_t)virtualFaceMap.size(); ++idxVF) {
|
||||||
|
// 边界检查(防御性编程)
|
||||||
|
if (idxVF >= (int_t)faceViews.size() || idxVF >= (int_t)faceViewWeights.size())
|
||||||
|
continue;
|
||||||
|
|
||||||
const VirtualFace& vf = virtualFaceMap[idxVF];
|
const VirtualFace& vf = virtualFaceMap[idxVF];
|
||||||
if (faceViews[idxVF].empty()) continue;
|
if (faceViews[idxVF].empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
for (FIndex faceID : vf.faces) {
|
for (FIndex faceID : vf.faces) {
|
||||||
|
// 防御性检查faceID
|
||||||
|
if (faceID >= scene.mesh.faces.size())
|
||||||
|
continue;
|
||||||
|
|
||||||
const Face& face = scene.mesh.faces[faceID];
|
const Face& face = scene.mesh.faces[faceID];
|
||||||
const TexCoord* uv = &scene.mesh.faceTexcoords[faceID * 3];
|
const TexCoord* uv = &scene.mesh.faceTexcoords[faceID * 3];
|
||||||
const Vertex* vtx = &scene.mesh.vertices[face[0]];
|
const Vertex* vtx = &scene.mesh.vertices[face[0]];
|
||||||
const Normal& faceNormal = scene.mesh.faceNormals[faceID];
|
const Normal& faceNormal = scene.mesh.faceNormals[faceID];
|
||||||
|
|
||||||
|
// ✅ 关键:用面片中心选择最佳视图(面片级选视图)
|
||||||
|
Point3d faceCenter(
|
||||||
|
(vtx[0].x + vtx[1].x + vtx[2].x) / 3.0,
|
||||||
|
(vtx[0].y + vtx[1].y + vtx[2].y) / 3.0,
|
||||||
|
(vtx[0].z + vtx[1].z + vtx[2].z) / 3.0
|
||||||
|
);
|
||||||
|
|
||||||
|
TexelViewInfo viewInfo;
|
||||||
|
if (!SelectBestSingleView(
|
||||||
|
Point3f(faceCenter.x, faceCenter.y, faceCenter.z),
|
||||||
|
faceNormal,
|
||||||
|
faceViews[idxVF],
|
||||||
|
faceViewWeights[idxVF],
|
||||||
|
viewInfo)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Image& bestImg = images[viewInfo.best_view_id];
|
||||||
|
|
||||||
// UV边界框
|
// UV边界框
|
||||||
AABB2f uvBox(true);
|
AABB2f uvBox(true);
|
||||||
uvBox.InsertFull(uv[0]);
|
uvBox.InsertFull(uv[0]);
|
||||||
@@ -15591,37 +15714,32 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
|||||||
if (!PointInTriangle(texCoord, uv[0], uv[1], uv[2], bary))
|
if (!PointInTriangle(texCoord, uv[0], uv[1], uv[2], bary))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// 计算世界点(直接插值,O(1))
|
// 计算世界点
|
||||||
Point3d worldPos(
|
Point3d worldPos(
|
||||||
vtx[0].x * bary.x + vtx[1].x * bary.y + vtx[2].x * bary.z,
|
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].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
|
vtx[0].z * bary.x + vtx[1].z * bary.y + vtx[2].z * bary.z
|
||||||
);
|
);
|
||||||
|
|
||||||
// 逐像素选图
|
// ✅ 只投影,不再选视图(使用面片选定的视图)
|
||||||
TexelViewInfo viewInfo;
|
Point2f proj = ProjectPointWithAutoCorrection(
|
||||||
if (!SelectBestViewForTexel(
|
bestImg.camera,
|
||||||
Point3f(worldPos.x, worldPos.y, worldPos.z),
|
Vertex(worldPos.x, worldPos.y, worldPos.z),
|
||||||
faceNormal,
|
bestImg
|
||||||
faceViews[idxVF],
|
);
|
||||||
faceViewWeights[idxVF],
|
|
||||||
viewInfo))
|
if (!bestImg.image.isInside(proj))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// 采样并写入
|
Pixel8U color = SampleImageBicubic(bestImg.image, proj);
|
||||||
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;
|
if (color[0] == colEmpty.r &&
|
||||||
// bestImg.image.sample(
|
color[1] == colEmpty.g &&
|
||||||
// viewInfo.best_proj.x,
|
color[2] == colEmpty.b)
|
||||||
// viewInfo.best_proj.y,
|
continue;
|
||||||
// color,
|
|
||||||
// SEACAVE::IMAGE_SAMPLE_BICUBIC
|
|
||||||
// );
|
|
||||||
|
|
||||||
|
// ✅ 写入纹理(无锁,后写覆盖先写)
|
||||||
data[y * cols + x] = color;
|
data[y * cols + x] = color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15631,6 +15749,52 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
|||||||
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete");
|
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete");
|
||||||
return textures;
|
return textures;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ 单视图选择函数(面片级)
|
||||||
|
bool MeshTexture::SelectBestSingleView(
|
||||||
|
const Point3f& worldPos,
|
||||||
|
const Normal&,
|
||||||
|
const std::vector<IIndex>& candidateViews,
|
||||||
|
const std::vector<float>& viewWeights,
|
||||||
|
TexelViewInfo& result)
|
||||||
|
{
|
||||||
|
result.best_weight = -1.0f;
|
||||||
|
result.best_view_id = static_cast<IIndex>(-1);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < candidateViews.size(); ++i) {
|
||||||
|
const IIndex viewId = candidateViews[i];
|
||||||
|
if (viewId >= images.size())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const Image& img = images[viewId];
|
||||||
|
|
||||||
|
Point2f proj = ProjectPointWithAutoCorrection(
|
||||||
|
img.camera,
|
||||||
|
Vertex(worldPos.x, worldPos.y, worldPos.z),
|
||||||
|
img
|
||||||
|
);
|
||||||
|
|
||||||
|
// 严格的投影验证
|
||||||
|
if (!img.image.isInside(proj))
|
||||||
|
continue;
|
||||||
|
if (!img.camera.IsInFront(worldPos))
|
||||||
|
continue;
|
||||||
|
if (!ValidateProjection(worldPos, img, proj))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const float w = viewWeights[i];
|
||||||
|
if (w <= 0.1f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (w > result.best_weight) {
|
||||||
|
result.best_weight = w;
|
||||||
|
result.best_view_id = viewId;
|
||||||
|
result.best_proj = proj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.best_view_id != static_cast<IIndex>(-1);
|
||||||
|
}
|
||||||
//*/
|
//*/
|
||||||
|
|
||||||
bool MeshTexture::TextureWithExistingUV(
|
bool MeshTexture::TextureWithExistingUV(
|
||||||
|
|||||||
Reference in new issue
Block a user