diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index a654420..4a54a8f 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -15369,7 +15369,7 @@ Pixel8U MeshTexture::BlendTopTwoViews( return result; } -//* +/* Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( const VirtualFaceMap& virtualFaceMap, const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容 @@ -15612,140 +15612,269 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( return textures; } //*/ -/* +//* Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( const VirtualFaceMap& virtualFaceMap, - const VirtualFaceDataArr&, + const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容 const std::vector>& faceViews, const std::vector>& faceViewWeights, unsigned nTextureSizeMultiple, Pixel8U colEmpty, - float) + float fSharpnessWeight) { - DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces"); - - // 1. UV边界 + DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces (SHARPENED)"); + + // 1. 分析UV布局 AABB2f uvBounds(true); - FOREACH(i, scene.mesh.faceTexcoords) - uvBounds.InsertFull(scene.mesh.faceTexcoords[i]); - - // 2. 纹理尺寸 + FOREACH(i, scene.mesh.faceTexcoords) { + const TexCoord& uv = scene.mesh.faceTexcoords[i]; + uvBounds.InsertFull(uv); + } + + // 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(textureAtlas.data); - - DEBUG_EXTRA("Texture atlas size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]", - textureSize, textureSize, - uvBounds.ptMin.x(), uvBounds.ptMin.y(), + + // 4. 创建权重图、累积颜色图和采样计数图,用于混合 + cv::Mat1f weightAccum(textureSize, textureSize, 0.0f); + cv::Mat3f colorAccum(textureSize, textureSize, cv::Vec3f(0, 0, 0)); // 用浮点数累积颜色 + cv::Mat1i sampleCount(textureSize, textureSize, 0); // 采样计数,用于统计每个像素被采样了多少次 + + 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并行) + + // ✅ 超采样参数 + const int SUPER_SAMPLE = 2; // 2x2超采样 + const float STEP = 1.0f / SUPER_SAMPLE; + + // 5. 处理每个虚拟面 #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; - + #else + for (size_t idxVF = 0; idxVF < virtualFaceMap.size(); ++idxVF) { + #endif const VirtualFace& vf = virtualFaceMap[idxVF]; - if (faceViews[idxVF].empty()) + + // 检查虚拟面是否有可用视图 + 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 TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3]; + + // 计算面片在纹理空间中的边界框 + AABB2f faceUVBounds(true); + for (int i = 0; i < 3; ++i) { + faceUVBounds.InsertFull(uvCoords[i]); } - - const Image& bestImg = images[viewInfo.best_view_id]; - - // 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; - - // 计算世界点 - 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 - ); - - // ✅ 只投影,不再选视图(使用面片选定的视图) - 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; - - // ✅ 写入纹理(无锁,后写覆盖先写) - data[y * cols + x] = color; + + 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; // 存储每个采样点的颜色 + + // ✅ 均匀采样面片内部(带超采样) + 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); + } + } + } + } + } + } + + // 使用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); + + for (const auto& pixel : pixels) { + if (pixel.x < 0 || pixel.x >= textureSize || + pixel.y < 0 || pixel.y >= textureSize) { + continue; + } + + const Point2f texCoord((float)pixel.x / textureSize, (float)pixel.y / textureSize); + + // ✅ 改进:使用距离加权插值(替代最近邻) + 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); // 最大距离平方 + + 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; + } + + // ✅ 如果有权重,则计算加权平均颜色 + 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; + } + } + } } } } } + + // 6. 应用权重归一化 + DEBUG_EXTRA("Applying weight normalization for virtual faces"); + for (int y = 0; y < textureSize; ++y) { + for (int x = 0; x < textureSize; ++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; + } else { + // 保持背景色 + textureAtlas(y, x) = colEmpty; + } + } + } + + // 7. 填充缝隙和未采样区域 + DEBUG_EXTRA("Filling gaps in texture atlas for virtual faces"); + cv::Mat textureMat = (cv::Mat&)textureAtlas; + cv::Mat1f weightMat = weightAccum; + cv::Mat1i sampleMat = sampleCount; + // FillTextureGapsMultiView(textureMat, weightMat, sampleMat, colEmpty); + // 8. 应用锐化(可选) + if (fSharpnessWeight > 0) { + // ApplySharpening(textureMat, fSharpnessWeight); + } + DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete"); return textures; }