From 6922abe69299e207d50c906b3b8592128cf2741e Mon Sep 17 00:00:00 2001 From: hesuicong Date: Thu, 16 Jul 2026 16:56:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B8=85=E6=99=B0=E5=8C=96?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 272 ++++++++++++++++++++++++++++++-------- 1 file changed, 218 insertions(+), 54 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 9a96685..a654420 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -676,17 +676,27 @@ public: bool CheckUVContinuity(const std::vector& faceList); struct TexelViewInfo { - IIndex best_view_id; // 最佳视图ID - float best_weight; // 最佳权重 - Point2f best_proj; // 最佳投影坐标 - std::vector candidate_views; // 候选视图列表 + float best_weight; + IIndex best_view_id; + Point2f best_proj; }; - bool SelectBestViewForTexel(const Point3f& worldPos, - const Normal& normal, + bool SelectBestViewForTexel(const Point3f& worldPos, + const Normal& /*normal*/, const std::vector& candidateViews, const std::vector& viewWeights, - TexelViewInfo& result); + TexelViewInfo& result, + std::vector>& topViews); + bool SelectBestSingleView( + const Point3f& worldPos, + const Normal& /*normal*/, + const std::vector& candidateViews, + const std::vector& viewWeights, + TexelViewInfo& result); + Pixel8U BlendTopTwoViews( + const std::vector>& topViews, + const std::vector& candidateViews, + const std::vector& viewWeights); 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, const Normal& /*normal*/, const std::vector& candidateViews, - const std::vector& /*viewWeights*/, - TexelViewInfo& result) + const std::vector& viewWeights, + TexelViewInfo& result, + std::vector>& topViews) { + topViews.clear(); result.best_weight = -1.0f; + result.best_view_id = static_cast(-1); + struct CandidateView { + IIndex viewId; + Point2f proj; + float weight; + }; + std::vector candidates; + candidates.reserve(candidateViews.size()); + + // 收集所有有效候选视图 for (size_t i = 0; i < candidateViews.size(); ++i) { const IIndex viewId = candidateViews[i]; + if (viewId >= images.size()) + continue; + 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))) + // 严格的投影验证 + if (!img.image.isInside(proj)) + continue; + if (!img.camera.IsInFront(worldPos)) + continue; + if (!ValidateProjection(worldPos, img, proj)) continue; - if (!img.image.isInside(proj)) + const float w = viewWeights[i]; + if (w <= 0.1f) continue; - // ✅ 先不选“最佳”,先选“第一个能用的” - result.best_weight = 1.0f; - result.best_view_id = viewId; - result.best_proj = proj; - return true; + candidates.push_back({viewId, proj, w}); } - return false; + 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; + + return true; } -/* +Pixel8U MeshTexture::BlendTopTwoViews( + const std::vector>& topViews, + const std::vector& candidateViews, + const std::vector& 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(sumB / totalWeight); + result[1] = cv::saturate_cast(sumG / totalWeight); + result[2] = cv::saturate_cast(sumR / totalWeight); + return result; +} + +//* Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( const VirtualFaceMap& virtualFaceMap, const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容 @@ -15516,16 +15611,16 @@ 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 VirtualFaceDataArr&, const std::vector>& faceViews, const std::vector>& faceViewWeights, unsigned nTextureSizeMultiple, Pixel8U colEmpty, - float fSharpnessWeight) + float) { DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces"); @@ -15542,34 +15637,62 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); - // 3. 创建纹理图集(只做这一件事) + // 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(textureAtlas.data); + // 缓存列数和数据指针 + const int cols = textureAtlas.cols; + Pixel8U* data = reinterpret_cast(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安全,因为只写不读) + // 4. 遍历虚拟面(OpenMP并行) #ifdef _USE_OPENMP #pragma omp parallel for schedule(dynamic) #endif 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]; - if (faceViews[idxVF].empty()) continue; + if (faceViews[idxVF].empty()) + continue; for (FIndex faceID : vf.faces) { + // 防御性检查faceID + if (faceID >= scene.mesh.faces.size()) + continue; + 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]; + // ✅ 关键:用面片中心选择最佳视图(面片级选视图) + 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边界框 AABB2f uvBox(true); uvBox.InsertFull(uv[0]); @@ -15591,38 +15714,33 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( 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)) + // ✅ 只投影,不再选视图(使用面片选定的视图) + Point2f proj = ProjectPointWithAutoCorrection( + bestImg.camera, + Vertex(worldPos.x, worldPos.y, worldPos.z), + bestImg + ); + + if (!bestImg.image.isInside(proj)) + continue; + + Pixel8U color = SampleImageBicubic(bestImg.image, proj); + + // ✅ 检查是否为空色(避免写入无效像素) + if (color[0] == colEmpty.r && + color[1] == colEmpty.g && + color[2] == colEmpty.b) 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; + // ✅ 写入纹理(无锁,后写覆盖先写) + data[y * cols + x] = color; } } } @@ -15631,6 +15749,52 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete"); return textures; } + +// ✅ 单视图选择函数(面片级) +bool MeshTexture::SelectBestSingleView( + const Point3f& worldPos, + const Normal&, + const std::vector& candidateViews, + const std::vector& viewWeights, + TexelViewInfo& result) +{ + result.best_weight = -1.0f; + result.best_view_id = static_cast(-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(-1); +} //*/ bool MeshTexture::TextureWithExistingUV(