diff --git a/libs/MVS/Scene.h b/libs/MVS/Scene.h index 2f67a26..3024d60 100644 --- a/libs/MVS/Scene.h +++ b/libs/MVS/Scene.h @@ -65,6 +65,7 @@ public: std::map> visible_faces_map; std::map> edge_faces_map; std::map> delete_edge_faces_map; + std::map> face_normal_visible_map; std::unordered_set face_visible_relative; public: @@ -163,6 +164,7 @@ public: std::unordered_set& face_visible_relative, std::map>& edge_faces_map, std::map>& delete_edge_faces_map, + std::map>& face_normal_visible_map, std::string& basePath); // Mesh texturing @@ -176,6 +178,7 @@ public: bool is_face_visible_relative(int face_index); bool is_face_edge(const std::string& image_name, int face_index); bool is_face_delete_edge(const std::string& image_name, int face_index); + bool is_face_normal_visible_map(const std::string& image_name, int face_index); void SegmentMeshBasedOnCurvature(Mesh::FaceIdxArr& regionMap, float curvatureThreshold); diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 4e0afbc..1de3c41 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -461,6 +461,7 @@ public: bool CreateVirtualFaces6(FaceDataViewArr& facesDatas, FaceDataViewArr& ComputeAverageQuality, VirtualFaceIdxsArr& virtualFaces, std::vector& isVirtualFace, unsigned minCommonCameras=2, float thMaxNormalDeviation=25.f) const; bool CreateVirtualFaces61(FaceDataViewArr& facesDatas, FaceDataViewArr& virtualFacesDatas, VirtualFaceIdxsArr& virtualFaces, std::vector& isVirtualFace, unsigned minCommonCameras=2, float thMaxNormalDeviation=25.f); bool CreateVirtualFaces62(FaceDataViewArr& facesDatas, FaceDataViewArr& ComputeAverageQuality, VirtualFaceIdxsArr& virtualFaces, std::vector& isVirtualFace, unsigned minCommonCameras=2, float thMaxNormalDeviation=25.f) const; + static std::string GetFileNameWithoutExtension(std::string& strPath); float CalculateBrightnessScore(const Image& imageData) const; bool IsFaceVisibleRelaxed(const FaceDataArr& faceDatas, const IIndexArr& selectedCams, float visibleRatioThreshold) const; bool CheckColorConsistency(const FaceDataArr& centerDatas, const FaceDataArr& candidateDatas) const; @@ -801,6 +802,8 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr } std::vector faceViewCount(faces.size(), 0); + std::vector> faceViewColors(faces.size()); // 存储每个面片在各个视图下的颜色 + std::vector> faceViewQualities(faces.size()); // 存储每个面片在各个视图下的质量 // extract array of faces viewed by each image IIndexArr views(_views); @@ -819,8 +822,8 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr #ifdef TEXOPT_USE_OPENMP bool bAbort(false); #pragma omp parallel for private(imageGradMag, mGrad, faceMap, depthMap) - for (int_t idx=0; idx<(int_t)views.size(); ++idx) { - + for (int_t idx=0; idx<(int_t)views.size(); ++idx) + { #pragma omp flush (bAbort) if (bAbort) { ++progress; @@ -929,160 +932,7 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr depthMap.create(imageData.GetSize()); std::unordered_set tempFaces; - if (false) - { - // viewDepthMaps[idxView] = depthMap; - for (int r = 0; r < depthMap.rows; ++r) { - for (int c = 0; c < depthMap.cols; ++c) { - // printf("1depthMap(r, c)=%f\n", depthMap(r, c)); - } - } - - RasterMesh::Triangle triangle; - RasterMesh rasterer1(*this, vertices, imageData.camera, depthMap, faceMap, scene.mesh, false); - - RasterMesh::TriangleRasterizer triangleRasterizer(triangle, rasterer1); - if (nIgnoreMaskLabel >= 0) { - // import mask - BitMatrix bmask; - // std::cout << "nIgnoreMaskLabel is open" << std::endl; - DepthEstimator::ImportIgnoreMask(imageData, imageData.GetSize(), (uint16_t)OPTDENSE::nIgnoreMaskLabel, bmask, &rasterer1.mask); - } else if (nIgnoreMaskLabel == -1) { - // creating mask to discard invalid regions created during image radial undistortion - rasterer1.mask = DetectInvalidImageRegions(imageData.image); - #if TD_VERBOSE != TD_VERBOSE_OFF - if (VERBOSITY_LEVEL > 2) - cv::imwrite(String::FormatString("umask%04d.png", idxView), rasterer1.mask); - #endif - } - - rasterer1.Clear(); - - #ifdef TEXOPT_USE_OPENMP - #pragma omp critical(invalid_faces_access) - #endif - { - std::lock_guard lock(*scene.mesh.invalidFaces.mtx); - scene.mesh.invalidFaces.data.clear(); - } - printf("imageData.name=%s\n", imageData.name.c_str()); - for (FIndex idxFace : cameraFaces) { - if (idxFace >= faces.size()) { - DEBUG_EXTRA("Invalid face index %u (max %u) in view %u", idxFace, faces.size()-1, idxView); - continue; - } - - // // 添加临界区保护 - // bool skipFace = false; - // #ifdef TEXOPT_USE_OPENMP - // #pragma omp critical(invalid_faces_access) - // #endif - // { - // std::lock_guard lock(scene.mesh.invalidFacesMutex); - // skipFace = (scene.mesh.invalidFaces.find(idxFace) != scene.mesh.invalidFaces.end()); - // } - - // if (skipFace) - // { - // continue; - // } - - rasterer1.validFace = true; - const Face& facet = faces[idxFace]; - rasterer1.idxFace = idxFace; - - if (scene.is_face_visible(strName.c_str(), idxFace)) - { - rasterer1.Project(facet, triangleRasterizer); - if (!rasterer1.validFace) - rasterer1.Project(facet, triangleRasterizer); - } - } - - // if (!bUseVirtualFaces) - // if (bUseVirtualFaces) - tempFaces = PerformLocalDepthConsistencyCheck(depthMap, faceMap, scene.mesh, idxView, strName); - - // RasterMesh rasterer2(*this, vertices, imageData.camera, depthMap, faceMap, scene.mesh, true); - RasterMesh rasterer2(*this, vertices, imageData.camera, depthMap, faceMap, std::ref(scene.mesh), true); - - for (int r = 0; r < depthMap.rows; ++r) { - for (int c = 0; c < depthMap.cols; ++c) { - // if (depthMap(r, c)> 999.0f) - // printf("2depthMap(r, c)=%f\n", depthMap(r, c)); - } - } - - RasterMesh::TriangleRasterizer triangleRasterizer2(triangle, rasterer2); - if (nIgnoreMaskLabel >= 0) { - // import mask - BitMatrix bmask; - // std::cout << "nIgnoreMaskLabel is open" << std::endl; - DepthEstimator::ImportIgnoreMask(imageData, imageData.GetSize(), (uint16_t)OPTDENSE::nIgnoreMaskLabel, bmask, &rasterer2.mask); - } else if (nIgnoreMaskLabel == -1) { - // creating mask to discard invalid regions created during image radial undistortion - rasterer2.mask = DetectInvalidImageRegions(imageData.image); - #if TD_VERBOSE != TD_VERBOSE_OFF - if (VERBOSITY_LEVEL > 2) - cv::imwrite(String::FormatString("umask%04d.png", idxView), rasterer2.mask); - #endif - } - - for (int r = 0; r < depthMap.rows; ++r) { - for (int c = 0; c < depthMap.cols; ++c) { - // if (depthMap(r, c)> 999.0f) - // printf("3depthMap(r, c)=%f, r=%d, c=%d, faceMap(r, c)=%d\n", depthMap(r, c), r, c, faceMap(r, c)); - } - } - // rasterer2.Clear(); - - for (FIndex idxFace : cameraFaces) { - // 添加面索引有效性检查 - if (idxFace >= faces.size()) { - DEBUG_EXTRA("Invalid face index %u (max %u) in view %u", idxFace, faces.size()-1, idxView); - continue; - } - - // 添加临界区保护 - bool skipFace = false; - #ifdef TEXOPT_USE_OPENMP - #pragma omp critical(invalid_faces_access) - #endif - { - std::lock_guard lock(*scene.mesh.invalidFaces.mtx); - skipFace = (scene.mesh.invalidFaces.data.find(idxFace) != scene.mesh.invalidFaces.data.end()); - } - - if (skipFace) - { - continue; - } - - if (tempFaces.find(idxFace) != tempFaces.end()) - { - } - else - { - continue; - } - - rasterer2.validFace = true; - const Face& facet = faces[idxFace]; - rasterer2.idxFace = idxFace; - // rasterer2.Project(facet, triangleRasterizer2); - // if (!rasterer2.validFace) - // rasterer2.Project(facet, triangleRasterizer2); - } - - for (int r = 0; r < depthMap.rows; ++r) { - for (int c = 0; c < depthMap.cols; ++c) { - // if (depthMap(r, c)> 999.0f) - // printf("4depthMap(r, c)=%f, r=%d, c=%d, faceMap(r, c)=%d\n", depthMap(r, c), r, c, faceMap(r, c)); - } - } - } - else { // RasterMesh rasterer(vertices, imageData.camera, depthMap, faceMap); RasterMesh rasterer(*this, vertices, imageData.camera, depthMap, faceMap, scene.mesh, false); @@ -1115,7 +965,11 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr skipVisibilityCheck = true; } - if (scene.is_face_visible(strName.c_str(), idxFace)) + bool is_face_visible = scene.is_face_visible(strName.c_str(), idxFace); + // bool is_face_normal_visible_map = scene.is_face_normal_visible_map(strName.c_str(), idxFace); + + // if (is_face_visible && is_face_normal_visible_map) + if (is_face_visible) // if (skipVisibilityCheck || scene.is_face_visible(strName.c_str(), idxFace)) { rasterer.Project(facet, triangleRasterizer); @@ -1250,6 +1104,13 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr if (depthMap(j,i)>999.0f) faceData.bInvalidFacesRelative = true; } + + #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA + // 保存视图颜色和质量 + Color pixelColor = imageData.image(j,i); + faceViewColors[idxFace].push_back(pixelColor); + faceViewQualities[idxFace].push_back(imageGradMag(j,i)); + #endif } } // adjust face quality with camera angle relative to face normal @@ -1262,8 +1123,15 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr const Vertex faceCenter((vertices[f[0]] + vertices[f[1]] + vertices[f[2]]) / 3.f); const Point3f camDir(Cast(imageData.camera.C) - faceCenter); const Normal& faceNormal = scene.mesh.faceNormals[idxFace]; + const float cosFaceCam(MAXF(0.001f, ComputeAngle(camDir.ptr(), faceNormal.ptr()))); + // const float MIN_COS_THRESHOLD = 0.1f; // 增大阈值,减少极端倾斜视角的影响 + // const float cosFaceCam(MAXF(MIN_COS_THRESHOLD, ComputeAngle(camDir.ptr(), faceNormal.ptr()))); + faceDatas.back().quality *= SQUARE(cosFaceCam); + // faceDatas.back().quality *= cosFaceCam; // 线性调整 + // faceDatas.back().quality *= pow(cosFaceCam, 0.5f); // 平方根调整 + // faceDatas.back().quality *= 1.0f - 0.5f * (1.0f - cosFaceCam); // 平滑调整 } #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA FOREACH(idxFace, areas) { @@ -1280,224 +1148,90 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr // Temp /* - for (int_t idx=0; idx<(int_t)views.size(); ++idx) - { - #pragma omp flush (bAbort) - if (bAbort) - { - ++progress; - continue; - } - const IIndex idxView(views[(IIndex)idx]); + // 第一步:计算每个面片的最终颜色(基于最接近平均颜色的3个视图) + #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA + Colors faceFinalColors; // 存储每个面片的最终颜色 + std::vector faceFinalQualities(faces.size(), 0.0f); // 存储每个面片的最终质量 - Image& imageData = images[idxView]; - if (!imageData.IsValid()) { - ++progress; + // 使用循环初始化 + for (size_t i = 0; i < faces.size(); ++i) { + faceFinalColors.push_back(Color::ZERO); + } + + for (FIndex idxFace = 0; idxFace < faces.size(); ++idxFace) { + const std::vector& colors = faceViewColors[idxFace]; + const std::vector& qualities = faceViewQualities[idxFace]; + + if (colors.empty()) { continue; } + + // 1. 计算所有视图颜色的平均值 + Color avgColor = Color::ZERO; + for (const Color& color : colors) { + avgColor += color; + } + avgColor /= (float)colors.size(); + + // 2. 计算每个视图颜色与平均值的差异 + std::vector> colorDistances; // (距离, 索引) + colorDistances.reserve(colors.size()); + + for (size_t i = 0; i < colors.size(); ++i) { + float distance = cv::norm(colors[i] - avgColor); + colorDistances.emplace_back(distance, i); + } + + // 3. 按距离排序(从小到大) + std::sort(colorDistances.begin(), colorDistances.end(), + [](const std::pair& a, const std::pair& b) { + return a.first < b.first; + }); + const Image& imageData = images[idxView]; std::string strPath = imageData.name; size_t lastSlash = strPath.find_last_of("/\\"); if (lastSlash == std::string::npos) lastSlash = 0; // 若无分隔符,从头开始 else lastSlash++; // 跳过分隔符 - // 查找扩展名分隔符 '.' 的位置 size_t lastDot = strPath.find_last_of('.'); if (lastDot == std::string::npos) lastDot = strPath.size(); // 若无扩展名,截到末尾 - // 截取文件名(不含路径和扩展名) std::string strName = strPath.substr(lastSlash, lastDot - lastSlash); + bool is_face_normal_visible_map = scene.is_face_normal_visible_map(strName.c_str(), idxFace); - // load image - unsigned level(nResolutionLevel); - const unsigned imageSize(imageData.RecomputeMaxResolution(level, nMinResolution)); - if ((imageData.image.empty() || MAXF(imageData.width,imageData.height) != imageSize) && !imageData.ReloadImage(imageSize)) { - #ifdef TEXOPT_USE_OPENMP - bAbort = true; - #pragma omp flush (bAbort) - continue; - #else - return false; - #endif - } - imageData.UpdateCamera(scene.platforms); - // compute gradient magnitude - imageData.image.toGray(imageGradMag, cv::COLOR_BGR2GRAY, true); - cv::Mat grad[2]; - mGrad[0].resize(imageGradMag.rows, imageGradMag.cols); - grad[0] = cv::Mat(imageGradMag.rows, imageGradMag.cols, cv::DataType::type, (void*)mGrad[0].data()); - mGrad[1].resize(imageGradMag.rows, imageGradMag.cols); - grad[1] = cv::Mat(imageGradMag.rows, imageGradMag.cols, cv::DataType::type, (void*)mGrad[1].data()); - - cv::Sobel(imageGradMag, grad[0], cv::DataType::type, 1, 0, 3, 1.0/8.0); - cv::Sobel(imageGradMag, grad[1], cv::DataType::type, 0, 1, 3, 1.0/8.0); - - (TImage::EMatMap)imageGradMag = (mGrad[0].cwiseAbs2()+mGrad[1].cwiseAbs2()).cwiseSqrt(); - // apply some blur on the gradient to lower noise/glossiness effects onto face-quality score - cv::GaussianBlur(imageGradMag, imageGradMag, cv::Size(15, 15), 0, 0, cv::BORDER_DEFAULT); - // select faces inside view frustum - Mesh::FaceIdxArr cameraFaces; - Mesh::FacesInserter inserter(cameraFaces); - const TFrustum frustum(Matrix3x4f(imageData.camera.P), (float)imageData.width, (float)imageData.height); - octree.Traverse(frustum, inserter); - // project all triangles in this view and keep the closest ones - faceMap.create(imageData.GetSize()); - depthMap.create(imageData.GetSize()); - - std::unordered_set tempFaces; - { - // RasterMesh rasterer(vertices, imageData.camera, depthMap, faceMap); - RasterMesh rasterer(*this, vertices, imageData.camera, depthMap, faceMap, scene.mesh, false); - RasterMesh::Triangle triangle; - RasterMesh::TriangleRasterizer triangleRasterizer(triangle, rasterer); - if (nIgnoreMaskLabel >= 0) { - // import mask - BitMatrix bmask; - // std::cout << "nIgnoreMaskLabel is open" << std::endl; - DepthEstimator::ImportIgnoreMask(imageData, imageData.GetSize(), (uint16_t)OPTDENSE::nIgnoreMaskLabel, bmask, &rasterer.mask); - } else if (nIgnoreMaskLabel == -1) { - // creating mask to discard invalid regions created during image radial undistortion - rasterer.mask = DetectInvalidImageRegions(imageData.image); - #if TD_VERBOSE != TD_VERBOSE_OFF - if (VERBOSITY_LEVEL > 2) - cv::imwrite(String::FormatString("umask%04d.png", idxView), rasterer.mask); - #endif - } - rasterer.Clear(); - for (FIndex idxFace : cameraFaces) { - rasterer.validFace = true; - const Face& facet = faces[idxFace]; - rasterer.idxFace = idxFace; - - bool bExistFace = facesDatas[idxFace].empty(); - - if (scene.is_face_visible(strName.c_str(), idxFace) || bExistFace) - { - rasterer.Project(facet, triangleRasterizer); - if (!rasterer.validFace) - rasterer.Project(facet, triangleRasterizer); - } - } - - tempFaces = PerformLocalDepthConsistencyCheck(depthMap, faceMap, scene.mesh, idxView, strName); - } - - // compute the projection area of visible faces - #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA - CLISTDEF0IDX(uint32_t,FIndex) areas(faces.size()); - areas.Memset(0); - #endif - - #ifdef TEXOPT_USE_OPENMP - #pragma omp critical - #endif + // 4. 只保留最接近的3个视图 + size_t numToKeep = colorDistances.size(); // std::min((size_t)3, colorDistances.size()); + Color finalColor = Color::ZERO; + float finalQuality = 0.0f; - { - for (int j=0; j 0)); - - if (idxFace == NO_ID) - continue; - - FaceDataArr& faceDatas = facesDatas[idxFace]; - - const Pixel8U& pixel = imageData.image(j, i); - // 假设是8位图像,RGB三个通道任一超过250即视为过曝 - if (pixel.r > 250 || pixel.g > 250 || pixel.b > 250) { - // continue; - } - - float brightnessPerceptual = (0.299f * pixel.r + 0.587f * pixel.g + 0.114f * pixel.b) / 255.0f; - // printf("brightnessPerceptual=%f\n", brightnessPerceptual); - - if (brightnessPerceptual>0.95f) - continue; - - { - if (depthMap(j,i)>999.0f) - { - #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA - uint32_t& area = areas[idxFace]; - if (area++ == 0) { - #else - if (faceDatas.empty() || faceDatas.back().idxView != idxView) { - #endif - // create new face-data - FaceData& faceData = faceDatas.emplace_back(); - faceData.idxView = idxView; - faceData.quality = imageGradMag(j,i); - #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA - faceData.color = imageData.image(j,i); - #endif - faceData.bInvalidFacesRelative = true; - } else { - // update face-data - ASSERT(!faceDatas.empty()); - FaceData& faceData = faceDatas.back(); - ASSERT(faceData.idxView == idxView); - faceData.quality = imageGradMag(j,i); - #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA - faceData.color = Color(imageData.image(j,i)); - #endif - faceData.bInvalidFacesRelative = true; - } - continue; - } - } - - uint32_t& area = areas[idxFace]; - if (area++ == 0) { - // create new face-data - FaceData& faceData = faceDatas.emplace_back(); - faceData.idxView = idxView; - faceData.quality = imageGradMag(j,i); - #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA - faceData.color = imageData.image(j,i); - #endif - if (depthMap(j,i)>999.0f) - faceData.bInvalidFacesRelative = true; - } else { - // update face-data - ASSERT(!faceDatas.empty()); - FaceData& faceData = faceDatas.back(); - ASSERT(faceData.idxView == idxView); - faceData.quality += imageGradMag(j,i); - #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA - faceData.color += Color(imageData.image(j,i)); - #endif - if (depthMap(j,i)>999.0f) - faceData.bInvalidFacesRelative = true; - } - } + for (size_t i = 0; i < numToKeep; ++i) { + int viewIdx = colorDistances[i].second; + finalColor += colors[viewIdx]; + finalQuality += qualities[viewIdx]; } - // adjust face quality with camera angle relative to face normal - // tries to increase chances of a camera with perpendicular view on the surface (smoothened normals) to be selected - FOREACH(idxFace, facesDatas) { - FaceDataArr& faceDatas = facesDatas[idxFace]; - if (faceDatas.empty() || faceDatas.back().idxView != idxView) - continue; - const Face& f = faces[idxFace]; - const Vertex faceCenter((vertices[f[0]] + vertices[f[1]] + vertices[f[2]]) / 3.f); - const Point3f camDir(Cast(imageData.camera.C) - faceCenter); - const Normal& faceNormal = scene.mesh.faceNormals[idxFace]; - const float cosFaceCam(MAXF(0.001f, ComputeAngle(camDir.ptr(), faceNormal.ptr()))); - faceDatas.back().quality *= SQUARE(cosFaceCam); - } - #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA - FOREACH(idxFace, areas) { - const uint32_t& area = areas[idxFace]; - if (area > 0) { - Color& color = facesDatas[idxFace].back().color; - color = RGB2YCBCR(Color(color * (1.f/(float)area))); - } - } - #endif + + // 5. 计算最终颜色和质量 + finalColor /= (float)numToKeep; + finalQuality /= (float)numToKeep; + + faceFinalColors[idxFace] = finalColor; + faceFinalQualities[idxFace] = finalQuality; + + // 第二步:更新facesDatas,只保留最终颜色和质量 + FaceDataArr& faceDatas = facesDatas[idxFace]; + if (!faceDatas.empty()) { + // 只保留第一个FaceData,并用最终颜色和质量更新它 + faceDatas.resize(1); + FaceData& faceData = faceDatas.back(); + faceData.quality = finalQuality; + faceData.color = finalColor; + // 注意:这里不再保留原始的idxView,因为颜色是多个视图的混合 + // 您可能需要根据需要调整idxView的值 } - ++progress; } - // */ + #endif + //*/ // Temp #ifdef TEXOPT_USE_OPENMP @@ -3299,8 +3033,9 @@ bool MeshTexture::CreateVirtualFaces6(FaceDataViewArr& facesDatas, FaceDataViewA // printf("CreateVirtualFace %s, %d, %f\n", strName.c_str(), virtualFaceCenterFaceID, angleLimit); if (angleDeg <= 45.0f) - { - filteredCams.push_back(idxView); + { + if (scene.is_face_normal_visible_map(strName, virtualFaceCenterFaceID)) + filteredCams.push_back(idxView); /* float brightnessScore = CalculateBrightnessScore(imageData); // 亮度评分函数 @@ -3323,7 +3058,9 @@ bool MeshTexture::CreateVirtualFaces6(FaceDataViewArr& facesDatas, FaceDataViewA } else { - filteredCams.push_back(idxView); + + if (scene.is_face_normal_visible_map(strName, virtualFaceCenterFaceID)) + filteredCams.push_back(idxView); /* float brightnessScore = CalculateBrightnessScore(imageData); // 亮度评分函数 @@ -3757,8 +3494,12 @@ bool MeshTexture::CreateVirtualFaces6(FaceDataViewArr& facesDatas, FaceDataViewA #endif // virtualFaceData.quality = avgQuality; // virtualFaceData.color = sortedLuminViews[0].second; + virtualFaceData.quality = medianQuality; virtualFaceData.color = medianColor; + + // virtualFaceData.quality = 0; + // virtualFaceData.color = Point3f::ZERO; } else { // 使用过滤后的视图重新计算平均值 @@ -3775,6 +3516,9 @@ bool MeshTexture::CreateVirtualFaces6(FaceDataViewArr& facesDatas, FaceDataViewA } virtualFaceData.quality = totalQuality2 / validIndices.size(); virtualFaceData.color = totalColor2 / validIndices.size(); + + // virtualFaceData.quality = 0; + // virtualFaceData.color = Point3f::ZERO; } } else if (validViewsSize>0&&validViewsSize<=2&&false) @@ -4450,6 +4194,35 @@ MeshTexture::FaceDataArr MeshTexture::MergeFaceData(const FaceDataArr& data1, co return mergedData; } +std::string MeshTexture::GetFileNameWithoutExtension(std::string& strPath) { + // 查找最后一个路径分隔符(支持'/'和'\') + size_t lastSlash = strPath.find_last_of("/\\"); + + // 确定文件名的起始位置 + if (lastSlash == std::string::npos) { + lastSlash = 0; // 没有路径分隔符,从字符串开头开始 + } else { + lastSlash++; // 跳过分隔符 + } + + // 查找最后一个扩展名分隔符'.'的位置 + size_t lastDot = strPath.find_last_of('.'); + + // 如果没有扩展名分隔符,则截取到字符串末尾 + if (lastDot == std::string::npos) { + lastDot = strPath.length(); + } + + // 截取文件名(不含路径和扩展名) + // 注意:如果lastDot <= lastSlash,说明点号在路径中,而不是在文件名中 + if (lastDot <= lastSlash) { + // 点号在路径中,没有扩展名 + return strPath.substr(lastSlash); + } else { + // 有扩展名,去掉扩展名 + return strPath.substr(lastSlash, lastDot - lastSlash); + } +} bool MeshTexture::CreateVirtualFaces62(FaceDataViewArr& facesDatas, FaceDataViewArr& virtualFacesDatas, VirtualFaceIdxsArr& virtualFaces, std::vector& isVirtualFace, unsigned minCommonCameras, float thMaxNormalDeviation) const { @@ -4647,7 +4420,10 @@ bool MeshTexture::CreateVirtualFaces62(FaceDataViewArr& facesDatas, FaceDataView } else { - filteredCams.push_back(idxView); + // if (angleDeg <= 30.0f) + { + filteredCams.push_back(idxView); + } /* float brightnessScore = CalculateBrightnessScore(imageData); // 亮度评分函数 @@ -4746,6 +4522,10 @@ bool MeshTexture::CreateVirtualFaces62(FaceDataViewArr& facesDatas, FaceDataView #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA virtualFaceData.color = Point3f::ZERO; #endif + + const Image& imageData = images[idxView]; + std::string strPath = imageData.name; + std::string strName = MeshTexture::GetFileNameWithoutExtension(strPath); int invalidQuality = 0; Color invalidColor = Point3f::ZERO; unsigned processedFaces(0); @@ -4818,7 +4598,9 @@ bool MeshTexture::CreateVirtualFaces62(FaceDataViewArr& facesDatas, FaceDataView if (colorDistance <= maxColorDeviation && luminanceDistance <= maxLuminanceDeviation) { - validIndices.push_back(n); + + if (scene.is_face_normal_visible_map(strName, virtualFaceCenterFaceID)) + validIndices.push_back(n); } else { @@ -4842,21 +4624,25 @@ bool MeshTexture::CreateVirtualFaces62(FaceDataViewArr& facesDatas, FaceDataView if (cosFaceToCenter>& std::unordered_set& face_visible_relative, std::map>& edge_faces_map, std::map>& delete_edge_faces_map, + std::map>& face_normal_visible_map, std::string& basePath) { printf("LoadVisibleFacesData %s\n", basePath.c_str()); std::ifstream mapFile(basePath + "_visible_faces_map.txt"); @@ -10668,6 +10471,24 @@ bool Scene::LoadVisibleFacesData(std::map>& } mapFile3.close(); + std::ifstream mapFile4(basePath + "_face_normal_visible_map.txt"); + if (!mapFile4.is_open()) { + return false; + } + + while (std::getline(mapFile4, line)) { + std::istringstream iss(line); + std::string image_name; + iss >> image_name; + std::unordered_set faces; + int face_index; + while (iss >> face_index) { + faces.insert(face_index); + } + face_normal_visible_map[image_name] = faces; + } + mapFile4.close(); + return true; } @@ -10766,7 +10587,7 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi // printf("basePath=%s\n", basePath.c_str()); - if (!LoadVisibleFacesData(visible_faces_map, face_visible_relative, edge_faces_map, delete_edge_faces_map, basePath)) + if (!LoadVisibleFacesData(visible_faces_map, face_visible_relative, edge_faces_map, delete_edge_faces_map, face_normal_visible_map, basePath)) { printf("LoadVisibleFacesData error\n"); } @@ -10920,6 +10741,20 @@ bool Scene::is_face_delete_edge(const std::string& image_name, int face_index) { return false; } +bool Scene::is_face_normal_visible_map(const std::string& image_name, int face_index) { + +#ifndef MASK_FACE_OCCLUSION + return true; +#endif + + auto it = face_normal_visible_map.find(image_name); + if (it != face_normal_visible_map.end()) { + return it->second.find(face_index) != it->second.end(); + } + return false; +} + + void Scene::SegmentMeshBasedOnCurvature(Mesh::FaceIdxArr& regionMap, float curvatureThreshold) { // 确保网格数据有效 if (mesh.faces.empty() || mesh.vertices.empty() || diff --git a/libs/MVS/mask_face_occlusion.py b/libs/MVS/mask_face_occlusion.py index e830053..f885628 100755 --- a/libs/MVS/mask_face_occlusion.py +++ b/libs/MVS/mask_face_occlusion.py @@ -1352,6 +1352,80 @@ class ModelProcessor: face_visible = v0_visible | v1_visible | v2_visible + # ============ 新增:法线夹角过滤 ============ + # 1. 获取面片法线(向量化计算,性能更好) + if not hasattr(self, 'face_normals_tensor'): + # 从mesh获取顶点和面片索引 + vertices = np.asarray(self.mesh.vertices) # 形状: (V, 3) + triangles = np.asarray(self.mesh.triangles) # 形状: (F, 3) + + # 向量化计算法线 + # 获取所有三角形的顶点 + v0 = vertices[triangles[:, 0]] # 形状: (F, 3) + v1 = vertices[triangles[:, 1]] # 形状: (F, 3) + v2 = vertices[triangles[:, 2]] # 形状: (F, 3) + + # 计算边向量 + edge1 = v1 - v0 + edge2 = v2 - v0 + + # 向量化叉积 + # np.cross支持批量计算 + normals = np.cross(edge1, edge2) + + # 计算法线长度 + norms = np.linalg.norm(normals, axis=1, keepdims=True) + + # 避免除零 + norms[norms < 1e-6] = 1.0 + + # 归一化 + normals = normals / norms + + # 将退化三角形的法线设为默认值 + mask = norms[:, 0] < 1e-6 + if np.any(mask): + normals[mask] = np.array([0.0, 0.0, 1.0]) + + # 转换为GPU张量 + self.face_normals_tensor = torch.tensor(normals, device=self.device, dtype=torch.float32) + + # 2. 计算摄像机方向(从面片中心指向摄像机) + # 获取面片顶点坐标 + v0_pos = self.vertices_tensor[v0_indices] + v1_pos = self.vertices_tensor[v1_indices] + v2_pos = self.vertices_tensor[v2_indices] + + # 计算面片中心 + face_centers = (v0_pos + v1_pos + v2_pos) / 3.0 + + # 计算摄像机方向向量 + camera_pos = torch.tensor(eye, device=self.device, dtype=torch.float32) + if len(camera_pos.shape) == 1: + camera_pos = camera_pos.unsqueeze(0) # 从(3)变为(1,3) + + # 方向:从面片中心指向摄像机 + view_dirs = camera_pos - face_centers + view_dirs = view_dirs / torch.norm(view_dirs, dim=1, keepdim=True) + + # 3. 计算法线与摄像机方向的夹角 + face_normals = self.face_normals_tensor + # 归一化法线 + face_normals = face_normals / torch.norm(face_normals, dim=1, keepdim=True) + + # 计算点积(余弦值) + cos_angles = torch.sum(face_normals * view_dirs, dim=1) + + # 计算角度(弧度转角度) + angles_deg = torch.acos(torch.clamp(cos_angles, -1.0, 1.0)) * 180.0 / torch.pi + + # 4. 创建法线可见性掩码(夹角 ≤ 45度) + normal_visible_mask = angles_deg <= 70.0 + + # 5. 结合遮挡可见性和法线可见性 + face_normal_visible = face_visible & normal_visible_mask + # ============ 法线夹角过滤结束 ============ + # 使用与CPU版本相同的后续处理 shrunk_visibility = self._shrink_face_visibility(face_visible.cpu().numpy(), 9) expanded_visibility = self._expand_face_visibility(face_visible.cpu().numpy(), 30) @@ -1359,7 +1433,7 @@ class ModelProcessor: expanded_edge = expanded_visibility & ~shrunk_visibility2 delete_edge = face_visible.cpu().numpy() & ~shrunk_visibility - return shrunk_visibility, expanded_edge, delete_edge + return shrunk_visibility, expanded_edge, delete_edge, face_normal_visible """ def _gen_depth_image_gpu(self, cam_data, render): @@ -1390,6 +1464,7 @@ class ModelProcessor: visible_faces_dict = {} edge_faces_dict = {} delete_edge_faces_dict = {} + face_normal_visible_dict = {} total_start = time.time() @@ -1415,13 +1490,14 @@ class ModelProcessor: # continue start_time = time.time() - face_visibility, face_edge, face_delete_edge = self._flag_model_gpu(camera_data) + face_visibility, face_edge, face_delete_edge, face_normal_visible = self._flag_model_gpu(camera_data) processing_time = time.time() - start_time visible_faces = np.where(face_visibility)[0].tolist() visible_faces_dict[img_name] = visible_faces edge_faces_dict[img_name] = np.where(face_edge)[0].tolist() delete_edge_faces_dict[img_name] = np.where(face_delete_edge)[0].tolist() + face_normal_visible_dict[img_name] = np.where(face_normal_visible.cpu().numpy())[0].tolist() print(f"图像 {img_name} 处理完成,耗时: {processing_time:.2f}秒,可见面数量{len(visible_faces)}") @@ -1429,12 +1505,13 @@ class ModelProcessor: print(f"所有图像处理完成,总耗时: {total_time:.2f}秒") print(f"平均每张图像耗时: {total_time/len(images):.2f}秒") - self.save_occlusion_data(visible_faces_dict, edge_faces_dict, delete_edge_faces_dict, self.asset_dir) + self.save_occlusion_data(visible_faces_dict, edge_faces_dict, delete_edge_faces_dict, face_normal_visible_dict, self.asset_dir) return { "result1": visible_faces_dict, "result2": edge_faces_dict, - "result3": delete_edge_faces_dict + "result3": delete_edge_faces_dict, + "result4": face_normal_visible_dict } #""" @@ -2019,7 +2096,8 @@ class ModelProcessor: def save_occlusion_data(self, result1: Dict[str, List[int]], result2: Dict[str, List[int]], - result3: Dict[str, List[int]], + result3: Dict[str, List[int]], + result4: Dict[str, List[int]], base_path: str) -> None: """ 保存遮挡数据到文件 @@ -2054,7 +2132,10 @@ class ModelProcessor: delete_edge_faces_map: Dict[str, Set[int]] = {} for image_name, face_list in result3.items(): delete_edge_faces_map[image_name] = set(face_list) - + + face_normal_visible_map: Dict[str, Set[int]] = {} + for image_name, face_list in result4.items(): + face_normal_visible_map[image_name] = set(face_list) # 保存 visible_faces_map try: @@ -2100,6 +2181,17 @@ class ModelProcessor: except IOError as e: print(f"Error writing delete_edge_faces_map file: {e}") + # 保存 face_normal_visible_map + try: + file_name = "_face_normal_visible_map.txt" + file_path = Path(base_path) / file_name + with open(file_path, "w", encoding='utf-8') as map_file4: + for image_name, face_set in face_normal_visible_map.items(): + line = image_name + " " + " ".join(str(face) for face in face_set) + "\n" + map_file4.write(line) + except IOError as e: + print(f"Error writing delete_edge_faces_map file: {e}") + def process(self): print("process")