From 81036ad095ac294b08e8e19cdd46d178f8b1be2a Mon Sep 17 00:00:00 2001 From: hesuicong Date: Mon, 20 Jul 2026 14:52:50 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E6=99=B0=E5=8C=96=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 319 +++++++++++++++++--------------------- 1 file changed, 139 insertions(+), 180 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 23e0e01..a224f36 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -717,7 +717,8 @@ public: const std::vector& visibleViews, int baseTexelSize); Pixel8U SampleImageBicubic(const Image8U3& img, const Point2f& pt); - + void ApplyUnsharpMask(cv::Mat& image, float strength); + void FillTextureGaps2(cv::Mat& texture, const cv::Mat1f& weights, Pixel8U colEmpty); Mesh::Image8U3Arr GenerateMultiViewTextureAtlasWithVirtualFaces( const VirtualFaceMap& virtualFaceMap, const VirtualFaceDataArr& virtualFaceDatas, // 改为 VirtualFaceDataArr @@ -15578,7 +15579,7 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( //* Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( const VirtualFaceMap& virtualFaceMap, - const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容 + const VirtualFaceDataArr& virtualFaceDatas, const std::vector>& faceViews, const std::vector>& faceViewWeights, unsigned nTextureSizeMultiple, @@ -15586,6 +15587,7 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( float fSharpnessWeight) { DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces (SHARPENED)"); + TD_TIMER_START(); // 1. 分析UV布局 AABB2f uvBounds(true); @@ -15603,25 +15605,24 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); - // 3. 创建纹理图集 + // ✅ 关键修改1:使用超分辨率渲染(2倍) + constexpr int RENDER_SCALE = 2; + const int renderSize = textureSize * RENDER_SCALE; + + // 3. 创建纹理图集(最终输出尺寸) Mesh::Image8U3Arr textures; Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); - // 4. 创建权重图、累积颜色图和采样计数图,用于混合 - cv::Mat1f weightAccum(textureSize, textureSize, 0.0f); - cv::Mat3f colorAccum(textureSize, textureSize, cv::Vec3f(0, 0, 0)); // 用浮点数累积颜色 - cv::Mat1i sampleCount(textureSize, textureSize, 0); // 采样计数,用于统计每个像素被采样了多少次 + // 4. 创建高分辨率累积缓冲区 + cv::Mat1f weightAccum(renderSize, renderSize, 0.0f); + cv::Mat3f colorAccum(renderSize, renderSize, cv::Vec3f(0, 0, 0)); - DEBUG_EXTRA("Texture atlas size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]", - textureSize, textureSize, + DEBUG_EXTRA("Texture atlas size: %dx%d, Render size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]", + textureSize, textureSize, renderSize, renderSize, uvBounds.ptMin.x(), uvBounds.ptMin.y(), uvBounds.ptMax.x(), uvBounds.ptMax.y()); - // ✅ 超采样参数 - const int SUPER_SAMPLE = 2; // 2x2超采样 - const float STEP = 1.0f / SUPER_SAMPLE; - // 5. 处理每个虚拟面 #ifdef _USE_OPENMP #pragma omp parallel for schedule(dynamic) @@ -15641,161 +15642,83 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( const Face& face = scene.mesh.faces[faceID]; const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; - // 计算面片在纹理空间中的边界框 - AABB2f faceUVBounds(true); + // ✅ 关键修改2:计算高分辨率下的边界框 + cv::Rect bbox; for (int i = 0; i < 3; ++i) { - faceUVBounds.InsertFull(uvCoords[i]); + int px = int(uvCoords[i].x * renderSize); + int py = int(uvCoords[i].y * renderSize); + if (bbox.empty()) + bbox = cv::Rect(px, py, 1, 1); + else + bbox |= cv::Rect(px, py, 1, 1); } + bbox &= cv::Rect(0, 0, renderSize, renderSize); - const int startX = std::max(0, (int)(faceUVBounds.ptMin.x() * textureSize)); - const int startY = std::max(0, (int)(faceUVBounds.ptMin.y() * textureSize)); - const int endX = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.x() * textureSize)); - const int endY = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.y() * textureSize)); - - // 为当前面片预计算3D点的重心坐标映射 - std::vector texPoints; - std::vector pointColors; // 存储每个采样点的颜色 + if (bbox.empty()) continue; - // ✅ 均匀采样面片内部(带超采样) - for (int y = startY; y <= endY; ++y) { - for (int x = startX; x <= endX; ++x) { - // ✅ 超采样循环:每个像素采样SUPER_SAMPLE x SUPER_SAMPLE次 - for (int sy = 0; sy < SUPER_SAMPLE; ++sy) { - for (int sx = 0; sx < SUPER_SAMPLE; ++sx) { - // 计算亚像素纹理坐标(在像素中心偏移) - float fx = x + (sx + 0.5f) * STEP; - float fy = y + (sy + 0.5f) * STEP; - const Point2f texCoord(fx / textureSize, fy / textureSize); - - // 计算重心坐标 - Point3f barycentric; - if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { - // 计算3D点 - const Vertex worldPoint = - scene.mesh.vertices[face[0]] * barycentric.x + - scene.mesh.vertices[face[1]] * barycentric.y + - scene.mesh.vertices[face[2]] * barycentric.z; - - // 为每个视图采样颜色 - cv::Vec3f accumColor(0, 0, 0); - float totalWeight = 0.0f; - - for (size_t viewIdx = 0; viewIdx < faceViews[idxVF].size(); ++viewIdx) { - const IIndex idxView = faceViews[idxVF][viewIdx]; - const float viewWeight = faceViewWeights[idxVF][viewIdx]; - - if (idxView >= images.size()) continue; - - const Image& sourceImage = images[idxView]; - - // 投影到图像 - Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage); - - // 验证投影 - if (!ValidateProjection(worldPoint, sourceImage, imgPoint) || - !sourceImage.image.isInside(imgPoint) || - !sourceImage.camera.IsInFront(worldPoint)) { - continue; - } - - // ✅ 使用高质量采样(双三次插值) - Sampler sampler; - Color color = sourceImage.image.sample(sampler, imgPoint); - - // 累积加权颜色(注意:OpenMVS的Color顺序是BGR) - accumColor[0] += color[0] * viewWeight; // B - accumColor[1] += color[1] * viewWeight; // G - accumColor[2] += color[2] * viewWeight; // R - totalWeight += viewWeight; - } - - if (totalWeight > 0.0f) { - // 平均颜色 - accumColor /= totalWeight; - - // 保存采样点和颜色 - texPoints.emplace_back(texCoord); - pointColors.push_back(accumColor); - } - } - } + // ✅ 关键修改3:直接在高分辨率下逐像素采样(不再使用Delaunay插值) + for (int y = bbox.y; y < bbox.y + bbox.height; ++y) { + for (int x = bbox.x; x < bbox.x + bbox.width; ++x) { + // 计算当前像素的UV坐标(在高分辨率空间) + Point2f texCoord( + static_cast(x) / renderSize, + static_cast(y) / renderSize + ); + + // 计算重心坐标 + Point3f barycentric; + if (!PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) { + continue; } - } - } - - // 使用Delaunay三角剖分在面片内部生成均匀采样 - if (texPoints.size() >= 3) { - // 创建Delaunay三角剖分 - cv::Subdiv2D subdiv(cv::Rect(0, 0, textureSize, textureSize)); - for (const auto& pt : texPoints) { - subdiv.insert(cv::Point2f(pt.x * textureSize, pt.y * textureSize)); - } - - std::vector triangleList; - subdiv.getTriangleList(triangleList); - - // 遍历三角形并填充 - for (const auto& t : triangleList) { - cv::Point2f pt1(t[0], t[1]); - cv::Point2f pt2(t[2], t[3]); - cv::Point2f pt3(t[4], t[5]); - // 获取三角形内的像素 - std::vector pixels = GetPixelsInTriangle(pt1, pt2, pt3, textureSize); + // 计算3D世界坐标 + const Vertex worldPoint = + scene.mesh.vertices[face[0]] * barycentric.x + + scene.mesh.vertices[face[1]] * barycentric.y + + scene.mesh.vertices[face[2]] * barycentric.z; - for (const auto& pixel : pixels) { - if (pixel.x < 0 || pixel.x >= textureSize || - pixel.y < 0 || pixel.y >= textureSize) { - continue; - } + // 累积来自所有视图的颜色 + cv::Vec3f accumColor(0, 0, 0); + float totalWeight = 0.0f; + + for (size_t viewIdx = 0; viewIdx < faceViews[idxVF].size(); ++viewIdx) { + const IIndex idxView = faceViews[idxVF][viewIdx]; + const float viewWeight = faceViewWeights[idxVF][viewIdx]; - const Point2f texCoord((float)pixel.x / textureSize, (float)pixel.y / textureSize); + if (idxView >= images.size()) continue; - // ✅ 改进:使用距离加权插值(替代最近邻) - cv::Vec3f weightedColor(0, 0, 0); - float totalWeight = 0.0f; - const float POWER = 2.0f; // 距离幂次(2表示平方反比) - const float MAX_DIST_SQ = 100.0f / (textureSize * textureSize); // 最大距离平方 + const Image& sourceImage = images[idxView]; - for (size_t i = 0; i < texPoints.size(); ++i) { - float dx = texPoints[i].x - texCoord.x; - float dy = texPoints[i].y - texCoord.y; - float distSq = dx*dx + dy*dy; // 平方距离 - - // 跳过距离过远的点 - if (distSq > MAX_DIST_SQ) continue; - - // 避免除零 - if (distSq < 1e-10f) distSq = 1e-10f; - - // 计算权重(距离越近权重越大) - float weight = 1.0f / std::pow(distSq, POWER/2.0f); - - weightedColor += pointColors[i] * weight; - totalWeight += weight; + // 投影到图像 + Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage); + + // 验证投影 + if (!ValidateProjection(worldPoint, sourceImage, imgPoint) || + !sourceImage.image.isInside(imgPoint) || + !sourceImage.camera.IsInFront(worldPoint)) { + continue; } - // ✅ 如果有权重,则计算加权平均颜色 - if (totalWeight > 0.0f) { - cv::Vec3f newColor = weightedColor / totalWeight; - - // 累加颜色和权重 - #ifdef _USE_OPENMP - #pragma omp atomic - #endif - weightAccum(pixel.y, pixel.x) += 1.0f; - - #ifdef _USE_OPENMP - #pragma omp atomic - #endif - sampleCount(pixel.y, pixel.x) += 1; - - #ifdef _USE_OPENMP - #pragma omp critical - #endif - { - colorAccum(pixel.y, pixel.x) += newColor; - } + Color color = sourceImage.image((int)imgPoint.x, (int)imgPoint.y); + + // ✅ 关键修改5:使用面积权重(而非简单计数) + const float areaWeight = viewWeight * (1.0f / (RENDER_SCALE * RENDER_SCALE)); + + // 累积加权颜色(BGR顺序) + accumColor[0] += color[0] * areaWeight; // B + accumColor[1] += color[1] * areaWeight; // G + accumColor[2] += color[2] * areaWeight; // R + totalWeight += areaWeight; + } + + // ✅ 关键修改6:原子累加(避免竞态条件) + if (totalWeight > 0.0f) { + #ifdef _USE_OPENMP + #pragma omp critical + #endif + { + colorAccum(y, x) += accumColor; + weightAccum(y, x) += totalWeight; } } } @@ -15803,45 +15726,81 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( } } - // 6. 应用权重归一化 - DEBUG_EXTRA("Applying weight normalization for virtual faces"); - for (int y = 0; y < textureSize; ++y) { - for (int x = 0; x < textureSize; ++x) { + // 6. 应用权重归一化(在高分辨率下) + DEBUG_EXTRA("Applying weight normalization at high resolution"); + cv::Mat3f hiResAtlas(renderSize, renderSize, cv::Vec3f(0, 0, 0)); + for (int y = 0; y < renderSize; ++y) { + for (int x = 0; x < renderSize; ++x) { float weight = weightAccum(y, x); if (weight > 0.0f) { - // 计算加权平均颜色 - cv::Vec3f avgColor = colorAccum(y, x) / weight; - - // 转换为Pixel8U(注意BGR顺序) - Pixel8U finalColor; - finalColor.b = (unsigned char)cv::saturate_cast(avgColor[0]); // B - finalColor.g = (unsigned char)cv::saturate_cast(avgColor[1]); // G - finalColor.r = (unsigned char)cv::saturate_cast(avgColor[2]); // R - - textureAtlas(y, x) = finalColor; + hiResAtlas(y, x) = colorAccum(y, x) / weight; } else { - // 保持背景色 - textureAtlas(y, x) = colEmpty; + // 对于未采样的像素,使用背景色 + hiResAtlas(y, x) = cv::Vec3f(colEmpty[2], colEmpty[1], colEmpty[0]); } } } - // 7. 填充缝隙和未采样区域 - DEBUG_EXTRA("Filling gaps in texture atlas for virtual faces"); + // 7. ✅ 关键修改7:高质量降采样到目标分辨率 + DEBUG_EXTRA("Downsampling from %dx%d to %dx%d", renderSize, renderSize, textureSize, textureSize); + cv::Mat3f downsampledAtlas; + cv::resize(hiResAtlas, downsampledAtlas, cv::Size(textureSize, textureSize), 0, 0, cv::INTER_CUBIC); + + // 8. 转换为8位纹理 + for (int y = 0; y < textureSize; ++y) { + for (int x = 0; x < textureSize; ++x) { + const cv::Vec3f& color = downsampledAtlas(y, x); + Pixel8U finalColor; + finalColor.b = (unsigned char)cv::saturate_cast(color[0]); + finalColor.g = (unsigned char)cv::saturate_cast(color[1]); + finalColor.r = (unsigned char)cv::saturate_cast(color[2]); + textureAtlas(y, x) = finalColor; + } + } + + // 9. 填充缝隙和未采样区域(可选) + DEBUG_EXTRA("Filling gaps in texture atlas"); cv::Mat textureMat = (cv::Mat&)textureAtlas; cv::Mat1f weightMat = weightAccum; - cv::Mat1i sampleMat = sampleCount; - // FillTextureGapsMultiView(textureMat, weightMat, sampleMat, colEmpty); - - // 8. 应用锐化(可选) + // FillTextureGaps2(textureMat, weightMat, colEmpty); + + // 10. ✅ 关键修改8:应用锐化(显著提升清晰度) if (fSharpnessWeight > 0) { - // ApplySharpening(textureMat, fSharpnessWeight); + DEBUG_EXTRA("Applying sharpening filter (weight: %.2f)", fSharpnessWeight); + ApplyUnsharpMask(textureMat, fSharpnessWeight); } - DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete"); + // 11. 可选:各向异性过滤 + #if TEXOPT_USE_ANISOTROPIC + const int anisoLevel = 8; + for (auto& tex : textures) { + tex.SetFilterMode(Texture::ANISOTROPIC); + tex.SetAnisotropy(anisoLevel); + } + #endif + + DEBUG_EXTRA("Multi-view texture atlas generation completed in %s", TD_TIMER_GET_FMT().c_str()); return textures; } +// 辅助函数:Unsharp Mask 锐化 +void MeshTexture::ApplyUnsharpMask(cv::Mat& image, float strength) { + cv::Mat blurred; + cv::GaussianBlur(image, blurred, cv::Size(0, 0), 2.0); + + cv::Mat sharpened; + cv::addWeighted(image, 1.0 + strength, blurred, -strength, 0, sharpened); + + // 限制像素值范围 + cv::max(cv::min(sharpened, 255), 0, image); +} + +// 辅助函数:填充纹理缝隙 +void MeshTexture::FillTextureGaps2(cv::Mat& texture, const cv::Mat1f& weights, Pixel8U colEmpty) { + // 实现缝隙填充逻辑 + // 可以使用膨胀、腐蚀或邻域传播算法 +} + // ✅ 单视图选择函数(面片级) bool MeshTexture::SelectBestSingleView( const Point3f& worldPos,