diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index edf9757..334c357 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -795,6 +795,16 @@ public: const Mesh::TexCoordArr& existingTexcoords, // 添加已有UV参数 const Mesh::TexIndexArr& existingTexindices // 添加已有纹理索引参数 ); + + // 在 MeshTexture 类定义中添加 + struct VertexColorInfo { + bool visibleInAnyView = false; // 至少在1个视图中可见 + cv::Vec3f color; // 最终颜色(扩散后) + cv::Vec3f mainColor; // 主视图颜色 + std::vector neighborVertices; // 邻接顶点 + }; + std::vector m_vertexColors; + std::vector faceToPatchID; // faceID → 新 patchID 映射 void MergeSameViewPatches(); void BuildSeamEdgesFromFaceToPatchID(); @@ -16417,9 +16427,6 @@ bool MeshTexture::RasterizeVirtualFaces( Image8U3& atlas = outTextures.back(); atlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); - // Pixel8U emptyMarker(255, 0, 255); // BGR: (255, 0, 255) → 品红 - // atlas.setTo(cv::Scalar(emptyMarker.b, emptyMarker.g, emptyMarker.r)); - m_texelScores.assign(textureSize * textureSize, TexelScore{}); m_texelPatchID.assign(textureSize * textureSize, NO_ID); @@ -16431,14 +16438,40 @@ bool MeshTexture::RasterizeVirtualFaces( } currentTextureSize = textureSize; - VERBOSE("[Raster] Propagating main views for consistency..."); - PropagateMainViews(virtualFaceMap, virtualFaceViews, virtualFaceViewWeights, faceViewsHighFreq); + VERBOSE("[Raster] Propagating main views for consistency..."); + PropagateMainViews(virtualFaceMap, virtualFaceViews, virtualFaceViewWeights, faceViewsHighFreq); if (!m_gainsEstimated) EstimateGlobalPhotometricCorrection(); + // ★★★ 提前初始化顶点颜色结构 ★★★ + m_vertexColors.resize(scene.mesh.vertices.size()); + for (auto& vc : m_vertexColors) { + vc.visibleInAnyView = false; + vc.color = cv::Vec3f(0, 0, 0); + vc.mainColor = cv::Vec3f(0, 0, 0); + vc.neighborVertices.clear(); + } + + // 建立顶点邻接关系 + for (FIndex fi = 0; fi < faces.size(); ++fi) { + const Face& f = faces[fi]; + for (int j = 0; j < 3; ++j) { + int v0 = f[j]; + int v1 = f[(j + 1) % 3]; + + auto& nb0 = m_vertexColors[v0].neighborVertices; + if (std::find(nb0.begin(), nb0.end(), v1) == nb0.end()) + nb0.push_back(v1); + + auto& nb1 = m_vertexColors[v1].neighborVertices; + if (std::find(nb1.begin(), nb1.end(), v0) == nb1.end()) + nb1.push_back(v0); + } + } + VERBOSE("[Raster] Step 2: starting patch loop, total faces=%zu", virtualFaceMap.size()); - // ========== 新增:预加载所有图像到浮点缓存(加速多次采样)========== + // ========== 预加载所有图像到浮点缓存 ========== std::vector imagesFloat(images.size()); for (size_t vi = 0; vi < images.size(); vi++) { if (!images[vi].image.empty()) { @@ -16446,26 +16479,10 @@ bool MeshTexture::RasterizeVirtualFaces( } } - // ★★★ 在循环外面预先计算全局 homography ★★★ - cv::Mat globalH; - IIndex globalView = NO_ID; - for (int ii = 0; ii < (int)virtualFaceMap.size(); ++ii) { - const VirtualFaceGeometry& g = m_virtualFaceGeometries[ii]; - if (g.isValid && !virtualFaceViews[ii].empty()) { - globalView = virtualFaceViews[ii][0]; - auto it = g.viewHomographies.find(globalView); - if (it != g.viewHomographies.end()) { - globalH = it->second.clone(); - break; - } - } - } - DEBUG_EXTRA("[TEST] Using global view %d for all faces", globalView); - // 3. 逐三角形光栅化(分频带合成版) -#ifdef _USE_OPENMP -#pragma omp parallel for schedule(dynamic) -#endif + #ifdef _USE_OPENMP + #pragma omp parallel for schedule(dynamic) + #endif for (int i = 0; i < (int)virtualFaceMap.size(); ++i) { if (i % 10000 == 0) @@ -16476,13 +16493,12 @@ bool MeshTexture::RasterizeVirtualFaces( if (!geom.isValid || vf.faces.empty() || virtualFaceViews[i].empty()) continue; // ========== 分频带视图获取 ========== - IIndex mainView = faceViewsHighFreq[i]; // 高频视图(主视图) - const auto& lowFreqViews = faceViewsLowFreq[i]; // 低频视图列表 + IIndex mainView = faceViewsHighFreq[i]; + const auto& lowFreqViews = faceViewsLowFreq[i]; if (mainView == NO_ID || mainView >= (IIndex)images.size()) continue; if (images[mainView].image.empty()) continue; - // 确保低频视图也有效 bool hasLowFreqViews = false; for (const auto& lfv : lowFreqViews) { if (lfv.viewId < (IIndex)images.size() && !images[lfv.viewId].image.empty()) { @@ -16493,7 +16509,7 @@ bool MeshTexture::RasterizeVirtualFaces( float currentScore = virtualFaceViewWeights[i].empty() ? -1.0f : virtualFaceViewWeights[i][0]; - int pad = 8; + int pad = 8; int minX = std::max(0, (int)floor(geom.uvBounds.ptMin.x() * textureSize - pad)); int maxX = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.x() * textureSize + pad)); int minY = std::max(0, (int)floor(geom.uvBounds.ptMin.y() * textureSize - pad)); @@ -16507,67 +16523,52 @@ bool MeshTexture::RasterizeVirtualFaces( continue; } - // ========== 用重心坐标 + 3D 投影替代 homography ========== + // ========== 用重心坐标 + 3D 投影 ========== cv::Mat mapX(patchH, patchW, CV_32FC1), mapY(patchH, patchW, CV_32FC1); mapX.setTo(-1); mapY.setTo(-1); - // 获取该虚拟面对应的原始三角面 if (vf.faces.empty()) continue; FIndex faceIdx = vf.faces[0]; const Face& face = scene.mesh.faces[faceIdx]; - // 提取 3D 顶点和 UV - Point3f v[3]; - TexCoord uv[3]; - for (int j = 0; j < 3; ++j) { - v[j] = scene.mesh.vertices[face[j]]; - uv[j] = scene.mesh.faceTexcoords[faceIdx * 3 + j]; // 注意这里的索引方式 - } + Point3f v[3]; + TexCoord uv[3]; + for (int j = 0; j < 3; ++j) { + v[j] = scene.mesh.vertices[face[j]]; + uv[j] = scene.mesh.faceTexcoords[faceIdx * 3 + j]; + } - // 获取相机参数 const Camera& cam = images[mainView].camera; bool camValid = (cam.K(0,0) > 0 && cam.K(1,1) > 0); if (!camValid) { - // fallback 到 homography const float* H = geom.homography.ptr(); for (int y = minY; y <= maxY; ++y) { for (int x = minX; x <= maxX; ++x) { - float u = (float)x / (float)textureSize, v = (float)y / (float)textureSize; - float w = H[6]*u + H[7]*v + H[8]; + float u = (float)x / (float)textureSize, v_coord = (float)y / (float)textureSize; + float w = H[6]*u + H[7]*v_coord + H[8]; if (std::abs(w) < 1e-12f) { mapX.at(y-minY,x-minX)=-1; mapY.at(y-minY,x-minX)=-1; continue; } - mapX.at(y-minY,x-minX) = (H[0]*u+H[1]*v+H[2])/w; - mapY.at(y-minY,x-minX) = (H[3]*u+H[4]*v+H[5])/w; + mapX.at(y-minY,x-minX) = (H[0]*u+H[1]*v_coord+H[2])/w; + mapY.at(y-minY,x-minX) = (H[3]*u+H[4]*v_coord+H[5])/w; } } } else { - // 对 patch 内每个像素做重心坐标投影 for (int py = minY; py <= maxY; ++py) { for (int px = minX; px <= maxX; ++px) { float u = (float)px / (float)textureSize; float v_coord = (float)py / (float)textureSize; - // 计算重心坐标 - float b0, b1, b2; - bool inside = ComputeBarycentric(u, v_coord, uv[0], uv[1], uv[2], b0, b1, b2); - - // // 统一用宽松容差判断 - // const float EPS_BARY = 1e-2f; - // if (b0 < -EPS_BARY || b1 < -EPS_BARY || b2 < -EPS_BARY) { - // continue; // 真正不在三角形内 - // } - // // 否则继续执行(即使 ComputeBarycentric 返回 false,但坐标只是略微越界) + float b0, b1, b2; + bool inside = ComputeBarycentric(u, v_coord, uv[0], uv[1], uv[2], b0, b1, b2); + if (!inside) continue; - // 3D 插值点 Point3f P = v[0] * b0 + v[1] * b1 + v[2] * b2; - // 投影到图像 - Point2f imgPt = cam.ProjectPointP(P); - // 检查是否在图像有效范围内 - if (imgPt.x < 0 || imgPt.x >= images[mainView].image.cols || - imgPt.y < 0 || imgPt.y >= images[mainView].image.rows) { - continue; - } + Point2f imgPt = cam.ProjectPointP(P); + if (imgPt.x < 0 || imgPt.x >= images[mainView].image.cols || + imgPt.y < 0 || imgPt.y >= images[mainView].image.rows) { + continue; + } mapX.at(py - minY, px - minX) = imgPt.x; mapY.at(py - minY, px - minX) = imgPt.y; @@ -16575,177 +16576,215 @@ bool MeshTexture::RasterizeVirtualFaces( } } - // ========== 从主视图采样(全分辨率)========== + // ========== 从主视图采样 ========== cv::Mat mainPatch; cv::remap(images[mainView].image, mainPatch, mapX, mapY, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); - // ========== 从辅助视图采样(每个视图用自己的 3D 投影)========== - cv::Mat lowFreqPatch(patchH, patchW, CV_32FC3, cv::Scalar(0,0,0)); - if (hasLowFreqViews) { - float totalWeight = 0; - for (const auto& lfv : lowFreqViews) { - if (lfv.viewId >= (IIndex)images.size() || images[lfv.viewId].image.empty()) continue; - - // ★★★ 每个辅助视图用自己的相机做 3D 投影 ★★★ - const Camera& auxCam = images[lfv.viewId].camera; - bool auxCamValid = (auxCam.K(0,0) > 0 && auxCam.K(1,1) > 0); - - cv::Mat auxMapX(patchH, patchW, CV_32FC1), auxMapY(patchH, patchW, CV_32FC1); - auxMapX.setTo(-1); - auxMapY.setTo(-1); - - if (!auxCamValid) { - // fallback 到该视图的 homography - const auto& geom = m_virtualFaceGeometries[i]; - auto it = geom.viewHomographies.find(lfv.viewId); - if (it == geom.viewHomographies.end()) continue; - const float* H = it->second.ptr(); - for (int y = minY; y <= maxY; ++y) { - for (int x = minX; x <= maxX; ++x) { - float u = (float)x / (float)textureSize, v = (float)y / (float)textureSize; - float w = H[6]*u + H[7]*v + H[8]; - if (std::abs(w) < 1e-12f) { - auxMapX.at(y-minY,x-minX) = -1; - auxMapY.at(y-minY,x-minX) = -1; - continue; - } - auxMapX.at(y-minY,x-minX) = (H[0]*u+H[1]*v+H[2])/w; - auxMapY.at(y-minY,x-minX) = (H[3]*u+H[4]*v+H[5])/w; - } - } - } else { - // 用辅助视图自己的相机做 3D 投影 - for (int py = minY; py <= maxY; ++py) { - for (int px = minX; px <= maxX; ++px) { - float u = (float)px / (float)textureSize; - float v_coord = (float)py / (float)textureSize; - - // 计算重心坐标 - float b0, b1, b2; - bool inside = ComputeBarycentric(u, v_coord, uv[0], uv[1], uv[2], b0, b1, b2); - if (!inside) - continue; - - // // 统一用宽松容差判断 - // const float EPS_BARY = 1e-2f; - // if (b0 < -EPS_BARY || b1 < -EPS_BARY || b2 < -EPS_BARY) { - // continue; // 真正不在三角形内 - // } - - Point3f P = v[0] * b0 + v[1] * b1 + v[2] * b2; - - Point2f imgPt = auxCam.ProjectPointP(P); - if (imgPt.x < 0 || imgPt.x >= images[lfv.viewId].image.cols || - imgPt.y < 0 || imgPt.y >= images[lfv.viewId].image.rows) { - continue; - } - - auxMapX.at(py - minY, px - minX) = imgPt.x; - auxMapY.at(py - minY, px - minX) = imgPt.y; - } - } - } - - cv::Mat auxPatch; - cv::remap(images[lfv.viewId].image, auxPatch, auxMapX, auxMapY, - cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); - - cv::Mat auxFloat; - auxPatch.convertTo(auxFloat, CV_32FC3, 1.0f / 255.0f); - - lowFreqPatch += auxFloat * lfv.weight; - totalWeight += lfv.weight; - } - if (totalWeight > 0) { - lowFreqPatch /= totalWeight; - } else { - cv::Mat mainFloat; - mainPatch.convertTo(mainFloat, CV_32FC3, 1.0f / 255.0f); - lowFreqPatch = mainFloat.clone(); - } - } else { - cv::Mat mainFloat; - mainPatch.convertTo(mainFloat, CV_32FC3, 1.0f / 255.0f); - lowFreqPatch = mainFloat.clone(); - } - - // ========== 多视图高频混合 ========== - cv::Mat mainFloat; - mainPatch.convertTo(mainFloat, CV_32FC3, 1.0f / 255.0f); - - // 主视图高频 - cv::Mat mainLowFreq; - cv::GaussianBlur(mainFloat, mainLowFreq, cv::Size(31, 31), 10.0); - cv::Mat highFreqAccum = (mainFloat - mainLowFreq) * 0.6f; - float highFreqTotalWeight = 0.6f; - - // 辅助视图也贡献高频(也用主视图的 mapX/mapY) - if (hasLowFreqViews) { - for (const auto& lfv : lowFreqViews) { - if (lfv.viewId == mainView) continue; - if (lfv.viewId >= (IIndex)images.size() || images[lfv.viewId].image.empty()) continue; - - // ★★★ 用主视图的 mapX/mapY(3D投影的那个),不是各视图自己的homography ★★★ - cv::Mat auxPatch; - cv::remap(images[lfv.viewId].image, auxPatch, mapX, mapY, - cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); - - cv::Mat auxFloat; - auxPatch.convertTo(auxFloat, CV_32FC3, 1.0f / 255.0f); - - cv::Mat auxLowFreq; - cv::GaussianBlur(auxFloat, auxLowFreq, cv::Size(31, 31), 10.0); - - float auxWeight = lfv.weight * 0.4f; - highFreqAccum += (auxFloat - auxLowFreq) * auxWeight; - highFreqTotalWeight += auxWeight; - } - } - - if (highFreqTotalWeight > 0) { - highFreqAccum /= highFreqTotalWeight; - } - - cv::Mat highFreq = highFreqAccum; - - // 在合成之前,对 lowFreqPatch 再做一次轻微平滑 - // cv::GaussianBlur(lowFreqPatch, lowFreqPatch, cv::Size(5, 5), 1.0); - - // ========== 合成:低频用多视图融合,高频用主视图细节 ========== - // cv::Mat finalPatch = lowFreqPatch + highFreq; - // cv::Mat finalPatch = lowFreqPatch; - // cv::Mat finalPatch = highFreq.clone(); - // finalPatch = cv::Mat::zeros(finalPatch.size(), finalPatch.type()); - - // cv::Mat finalPatch = mainFloat; // 只用主视图 - // cv::Mat finalPatch = lowFreqPatch * 0.3f + highFreq * 0.7f; - // 低频只做主视图,不做多视图平均(避免发灰) - // cv::Mat finalPatch = mainFloat * 0.7f + highFreq * 0.3f; - // cv::Mat finalPatch = lowFreqPatch; - - // 先填空洞:lowFreqPatch 中黑色空洞用 mainFloat 填充 - cv::Mat filledLowFreq = lowFreqPatch.clone(); - for (int y = 0; y < patchH; ++y) { - for (int x = 0; x < patchW; ++x) { - cv::Vec3f color = filledLowFreq.at(y, x); - if (color[0] == 0.0f && color[1] == 0.0f && color[2] == 0.0f) { - filledLowFreq.at(y, x) = mainFloat.at(y, x); - } - } - } - - // 再与 mainFloat 混合(低频占主导,主视图补充细节) - cv::Mat finalPatch = filledLowFreq * 0.7f + mainFloat * 0.3f; - // cv::Mat finalPatch = filledLowFreq * 0.85f + highFreq * 0.15f; + // ========== 从辅助视图采样 ========== + cv::Mat lowFreqPatch(patchH, patchW, CV_32FC3, cv::Scalar(0,0,0)); + if (hasLowFreqViews) { + float totalWeight = 0; + for (const auto& lfv : lowFreqViews) { + if (lfv.viewId >= (IIndex)images.size() || images[lfv.viewId].image.empty()) continue; + + const Camera& auxCam = images[lfv.viewId].camera; + bool auxCamValid = (auxCam.K(0,0) > 0 && auxCam.K(1,1) > 0); + + cv::Mat auxMapX(patchH, patchW, CV_32FC1), auxMapY(patchH, patchW, CV_32FC1); + auxMapX.setTo(-1); + auxMapY.setTo(-1); + + if (!auxCamValid) { + const auto& geomRef = m_virtualFaceGeometries[i]; + auto it = geomRef.viewHomographies.find(lfv.viewId); + if (it == geomRef.viewHomographies.end()) continue; + const float* H = it->second.ptr(); + for (int y = minY; y <= maxY; ++y) { + for (int x = minX; x <= maxX; ++x) { + float u = (float)x / (float)textureSize, v_coord = (float)y / (float)textureSize; + float w = H[6]*u + H[7]*v_coord + H[8]; + if (std::abs(w) < 1e-12f) { + auxMapX.at(y-minY,x-minX) = -1; + auxMapY.at(y-minY,x-minX) = -1; + continue; + } + auxMapX.at(y-minY,x-minX) = (H[0]*u+H[1]*v_coord+H[2])/w; + auxMapY.at(y-minY,x-minX) = (H[3]*u+H[4]*v_coord+H[5])/w; + } + } + } else { + for (int py = minY; py <= maxY; ++py) { + for (int px = minX; px <= maxX; ++px) { + float u = (float)px / (float)textureSize; + float v_coord = (float)py / (float)textureSize; + + float b0, b1, b2; + bool inside = ComputeBarycentric(u, v_coord, uv[0], uv[1], uv[2], b0, b1, b2); + if (!inside) continue; + + Point3f P = v[0] * b0 + v[1] * b1 + v[2] * b2; + + Point2f imgPt = auxCam.ProjectPointP(P); + if (imgPt.x < 0 || imgPt.x >= images[lfv.viewId].image.cols || + imgPt.y < 0 || imgPt.y >= images[lfv.viewId].image.rows) { + continue; + } + + auxMapX.at(py - minY, px - minX) = imgPt.x; + auxMapY.at(py - minY, px - minX) = imgPt.y; + } + } + } + + cv::Mat auxPatch; + cv::remap(images[lfv.viewId].image, auxPatch, auxMapX, auxMapY, + cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); + + cv::Mat auxFloat; + auxPatch.convertTo(auxFloat, CV_32FC3, 1.0f / 255.0f); + + lowFreqPatch += auxFloat * lfv.weight; + totalWeight += lfv.weight; + } + if (totalWeight > 0) { + lowFreqPatch /= totalWeight; + } else { + cv::Mat mainFloat; + mainPatch.convertTo(mainFloat, CV_32FC3, 1.0f / 255.0f); + lowFreqPatch = mainFloat.clone(); + } + } else { + cv::Mat mainFloat; + mainPatch.convertTo(mainFloat, CV_32FC3, 1.0f / 255.0f); + lowFreqPatch = mainFloat.clone(); + } - // 对高频做 soft clamp,防止个别极端值拉白发白 - cv::Mat highFreqClamped; - cv::max(highFreq, -0.2f, highFreqClamped); - cv::min(highFreqClamped, 0.2f, highFreqClamped); + // ========== 多视图高频混合 ========== + cv::Mat mainFloat; + mainPatch.convertTo(mainFloat, CV_32FC3, 1.0f / 255.0f); - // cv::Mat finalPatch = filledLowFreq * 0.8f + highFreqClamped * 0.2f; - // cv::Mat finalPatch = mainFloat; - // cv::Mat finalPatch = lowFreqPatch; + cv::Mat mainLowFreq; + cv::GaussianBlur(mainFloat, mainLowFreq, cv::Size(31, 31), 10.0); + cv::Mat highFreqAccum = (mainFloat - mainLowFreq) * 0.6f; + float highFreqTotalWeight = 0.6f; + + if (hasLowFreqViews) { + for (const auto& lfv : lowFreqViews) { + if (lfv.viewId == mainView) continue; + if (lfv.viewId >= (IIndex)images.size() || images[lfv.viewId].image.empty()) continue; + + cv::Mat auxPatch; + cv::remap(images[lfv.viewId].image, auxPatch, mapX, mapY, + cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); + + cv::Mat auxFloat; + auxPatch.convertTo(auxFloat, CV_32FC3, 1.0f / 255.0f); + + cv::Mat auxLowFreq; + cv::GaussianBlur(auxFloat, auxLowFreq, cv::Size(31, 31), 10.0); + + float auxWeight = lfv.weight * 0.4f; + highFreqAccum += (auxFloat - auxLowFreq) * auxWeight; + highFreqTotalWeight += auxWeight; + } + } + + if (highFreqTotalWeight > 0) { + highFreqAccum /= highFreqTotalWeight; + } + + cv::Mat highFreq = highFreqAccum; + +// ========== 每个顶点独立判断可见性(增强版)========== +bool vertexVisible[3] = {false, false, false}; +cv::Vec3f vertexBestColor[3] = {cv::Vec3f(0,0,0), cv::Vec3f(0,0,0), cv::Vec3f(0,0,0)}; + +// 检查主视图 +for (int j = 0; j < 3; ++j) { + int vx = (int)(uv[j].x * textureSize) - minX; + int vy = (int)(uv[j].y * textureSize) - minY; + if (vx >= 0 && vx < patchW && vy >= 0 && vy < patchH) { + cv::Vec3f color = mainFloat.at(vy, vx); + if (color[0] > 0.0f || color[1] > 0.0f || color[2] > 0.0f) { + vertexVisible[j] = true; + vertexBestColor[j] = color; + } + } +} + +// 如果主视图不可见,检查辅助视图 +if (hasLowFreqViews && (!vertexVisible[0] || !vertexVisible[1] || !vertexVisible[2])) { + for (const auto& lfv : lowFreqViews) { + if (lfv.viewId >= (IIndex)images.size() || images[lfv.viewId].image.empty()) continue; + + // 对该辅助视图重新采样该顶点位置 + const Camera& auxCam = images[lfv.viewId].camera; + bool auxCamValid = (auxCam.K(0,0) > 0 && auxCam.K(1,1) > 0); + + for (int j = 0; j < 3; ++j) { + if (vertexVisible[j]) continue; // 已经可见就跳过 + + // 用3D投影得到该顶点在辅助视图中的像素位置 + Point2f imgPt; + if (auxCamValid) { + imgPt = auxCam.ProjectPointP(v[j]); + } else { + // 用 homography fallback + const auto& geomRef = m_virtualFaceGeometries[i]; + auto it = geomRef.viewHomographies.find(lfv.viewId); + if (it == geomRef.viewHomographies.end()) continue; + + const float* H = it->second.ptr(); + float u = uv[j].x; + float v_coord = uv[j].y; + float w = H[6]*u + H[7]*v_coord + H[8]; + if (std::abs(w) < 1e-12f) continue; + + float projX = (H[0]*u + H[1]*v_coord + H[2]) / w; + float projY = (H[3]*u + H[4]*v_coord + H[5]) / w; + imgPt = Point2f(projX, projY); + } + + if (imgPt.x >= 0 && imgPt.x < images[lfv.viewId].image.cols && + imgPt.y >= 0 && imgPt.y < images[lfv.viewId].image.rows) { + // 采样该像素颜色 + cv::Vec3f auxColor = imagesFloat[lfv.viewId].at((int)imgPt.y, (int)imgPt.x); + if (auxColor[0] > 0.0f || auxColor[1] > 0.0f || auxColor[2] > 0.0f) { + vertexVisible[j] = true; + vertexBestColor[j] = auxColor; + } + } + } + + // 如果三个顶点都已可见,提前退出 + if (vertexVisible[0] && vertexVisible[1] && vertexVisible[2]) + break; + } +} + +#pragma omp critical (vertex_visibility) +{ + for (int j = 0; j < 3; ++j) { + VIndex vi = face[j]; + if (vi < m_vertexColors.size() && vertexVisible[j]) { + m_vertexColors[vi].visibleInAnyView = true; + m_vertexColors[vi].mainColor = vertexBestColor[j]; + } + } +} + + // ========== 空洞填充与合成 ========== + cv::Mat filledLowFreq = lowFreqPatch.clone(); + for (int y = 0; y < patchH; ++y) { + for (int x = 0; x < patchW; ++x) { + cv::Vec3f color = filledLowFreq.at(y, x); + if (color[0] == 0.0f && color[1] == 0.0f && color[2] == 0.0f) { + filledLowFreq.at(y, x) = mainFloat.at(y, x); + } + } + } + + cv::Mat finalPatch = filledLowFreq * 0.7f + mainFloat * 0.3f; // 钳制到 [0, 1] cv::Mat clampedPatch; @@ -16756,54 +16795,255 @@ bool MeshTexture::RasterizeVirtualFaces( cv::Mat finalPatch8U; clampedPatch.convertTo(finalPatch8U, CV_8UC3, 255.0f); - // 在写入 atlas 时,对边界像素做衰减混合 - #pragma omp critical (atlas_write) - { - for (int y = 0; y < patchH; ++y) { - for (int x = 0; x < patchW; ++x) { - cv::Vec3b color = finalPatch8U.at(y, x); - - // float mx = mapX.at(y, x); - // float my = mapY.at(y, x); - // if (mx < 0 || my < 0) continue; // 真正的空洞才跳过 + // ========== 写入 atlas ========== + #pragma omp critical (atlas_write) + { + for (int y = 0; y < patchH; ++y) { + for (int x = 0; x < patchW; ++x) { + cv::Vec3b color = finalPatch8U.at(y, x); - if (color[0] == 0 && color[1] == 0 && color[2] == 0) continue; + if (color[0] == 0 && color[1] == 0 && color[2] == 0) continue; - int atlasX = x + minX, atlasY = y + minY; - if (atlasX<0||atlasX>=textureSize||atlasY<0||atlasY>=textureSize) continue; - size_t idx = atlasY * textureSize + atlasX; + int atlasX = x + minX, atlasY = y + minY; + if (atlasX<0||atlasX>=textureSize||atlasY<0||atlasY>=textureSize) continue; + size_t idx = atlasY * textureSize + atlasX; - // ===== 新增:边界衰减权重 ===== - float weight = 1.0f; - float distToEdgeX = std::min((float)x, (float)(patchW - 1 - x)); - float distToEdgeY = std::min((float)y, (float)(patchH - 1 - y)); - float minDist = std::min(distToEdgeX, distToEdgeY); - - if (minDist < pad) { - // 边界 8 像素内做线性衰减 - weight = minDist / (float)pad; - weight = weight * weight; // 平方衰减更平滑 - } - - // 如果已有更高分的 texel,跳过(保持高分优先) - if (currentScore > m_texelScores[idx].score) { - // // 如果有旧值,做加权混合 - // if (m_texelScores[idx].score > 0 && weight < 1.0f) { - // cv::Vec3b oldColor = atlas.at(atlasY, atlasX); - // float oldWeight = 1.0f - weight; - // color[0] = (uchar)(color[0] * weight + oldColor[0] * oldWeight); - // color[1] = (uchar)(color[1] * weight + oldColor[1] * oldWeight); - // color[2] = (uchar)(color[2] * weight + oldColor[2] * oldWeight); - // } - - atlas.at(atlasY, atlasX) = color; - m_texelScores[idx].score = currentScore; - m_texelScores[idx].viewID = mainView; - m_texelPatchID[idx] = i; - } - } - } - } + // 边界衰减权重 + float weight = 1.0f; + float distToEdgeX = std::min((float)x, (float)(patchW - 1 - x)); + float distToEdgeY = std::min((float)y, (float)(patchH - 1 - y)); + float minDist = std::min(distToEdgeX, distToEdgeY); + + if (minDist < pad) { + weight = minDist / (float)pad; + weight = weight * weight; + } + + if (currentScore > m_texelScores[idx].score) { + atlas.at(atlasY, atlasX) = color; + m_texelScores[idx].score = currentScore; + m_texelScores[idx].viewID = mainView; + m_texelPatchID[idx] = i; + } + } + } + } + } + + // ========== OpenMP 循环结束,执行顶点级别颜色扩散 ========== + + // BFS扩散 + std::queue bfsQueue; + std::vector visited(m_vertexColors.size(), false); + + for (VIndex vi = 0; vi < m_vertexColors.size(); ++vi) { + if (m_vertexColors[vi].visibleInAnyView) { + m_vertexColors[vi].color = m_vertexColors[vi].mainColor; + visited[vi] = true; + bfsQueue.push(vi); + } + } + + while (!bfsQueue.empty()) { + VIndex cur = bfsQueue.front(); + bfsQueue.pop(); + + for (int nb : m_vertexColors[cur].neighborVertices) { + if (!visited[nb]) { + m_vertexColors[nb].color = m_vertexColors[cur].color; + visited[nb] = true; + bfsQueue.push(nb); + } + } + } + + // 处理孤立顶点 + for (VIndex vi = 0; vi < m_vertexColors.size(); ++vi) { + if (!visited[vi]) { + cv::Vec3f avgColor(0, 0, 0); + int count = 0; + for (int nb : m_vertexColors[vi].neighborVertices) { + if (visited[nb]) { + avgColor += m_vertexColors[nb].color; + count++; + } + } + if (count > 0) { + m_vertexColors[vi].color = avgColor / (float)count; + } else { + m_vertexColors[vi].color = cv::Vec3f(0.35f, 0.65f, 0.95f); + } + } + } + // ★★★ 在这里插入:强制给仍然为黑色的顶点赋色 ★★★ + for (VIndex vi = 0; vi < m_vertexColors.size(); ++vi) { + if (m_vertexColors[vi].color[0] == 0.0f && + m_vertexColors[vi].color[1] == 0.0f && + m_vertexColors[vi].color[2] == 0.0f) { + + cv::Vec3f avgColor(0, 0, 0); + int count = 0; + for (int nb : m_vertexColors[vi].neighborVertices) { + if (m_vertexColors[nb].color[0] > 0.0f || + m_vertexColors[nb].color[1] > 0.0f || + m_vertexColors[nb].color[2] > 0.0f) { + avgColor += m_vertexColors[nb].color; + count++; + } + } + if (count > 0) { + m_vertexColors[vi].color = avgColor / (float)count; + } else { + // 实在找不到,用主视图平均色 + m_vertexColors[vi].color = cv::Vec3f(0.35f, 0.55f, 0.75f); // 灰色保底 + } + } + } + + DEBUG_EXTRA("Vertex color diffusion: %zu vertices, %zu visible, %zu invisible", + m_vertexColors.size(), + std::count(visited.begin(), visited.end(), true), + std::count(visited.begin(), visited.end(), false)); + + // ========== 用顶点颜色修复空洞 ========== + #ifdef _USE_OPENMP + #pragma omp parallel for schedule(dynamic) + #endif + for (int atlasY = 0; atlasY < textureSize; ++atlasY) { + for (int atlasX = 0; atlasX < textureSize; ++atlasX) { + size_t idx = atlasY * textureSize + atlasX; + + // 只处理空洞像素 + Pixel8U px = atlas(atlasY, atlasX); + if (px[0] != colEmpty.b || px[1] != colEmpty.g || px[2] != colEmpty.r) + continue; + + // 找到这个像素属于哪个面 + int patchID = m_texelPatchID[idx]; +if (patchID >= (int)virtualFaceMap.size()) + continue; + +if (patchID == NO_ID) { + // 尝试找最近的已填充像素 + int bestDist = INT_MAX; + int bestPatchID = NO_ID; + int searchRad = 16; + for (int dy = -searchRad; dy <= searchRad; ++dy) { + for (int dx = -searchRad; dx <= searchRad; ++dx) { + int sx = atlasX + dx, sy = atlasY + dy; + if (sx < 0 || sx >= textureSize || sy < 0 || sy >= textureSize) continue; + size_t sidx = sy * textureSize + sx; + if (m_texelPatchID[sidx] != NO_ID) { + int dist = dx*dx + dy*dy; + if (dist < bestDist) { + bestDist = dist; + bestPatchID = m_texelPatchID[sidx]; + } + } + } + } + if (bestPatchID != NO_ID) { + patchID = bestPatchID; + } else { + continue; // 实在找不到归属面,跳过 + } +} + + const VirtualFace& vf = virtualFaceMap[patchID]; + if (vf.faces.empty()) continue; + + FIndex faceIdx = vf.faces[0]; + const Face& face = scene.mesh.faces[faceIdx]; + + // 获取顶点颜色 + cv::Vec3f vertexColors[3]; + for (int j = 0; j < 3; ++j) { + VIndex vi = face[j]; + vertexColors[j] = (vi < m_vertexColors.size()) ? + m_vertexColors[vi].color : cv::Vec3f(0.5f, 0.5f, 0.5f); + } + + // 计算 UV 坐标 + float u = (float)atlasX / (float)textureSize; + float v_coord = (float)atlasY / (float)textureSize; + + // 获取该面的 UV + TexCoord uv[3]; + for (int j = 0; j < 3; ++j) { + uv[j] = scene.mesh.faceTexcoords[faceIdx * 3 + j]; + } + + // 重心坐标插值 + float b0, b1, b2; + bool inside = ComputeBarycentric(u, v_coord, uv[0], uv[1], uv[2], b0, b1, b2); + + cv::Vec3f fillColor; + if (inside) { + fillColor = vertexColors[0] * b0 + vertexColors[1] * b1 + vertexColors[2] * b2; + } else { + // 最近顶点 + float minDist = FLT_MAX; + int bestVertex = 0; + for (int j = 0; j < 3; ++j) { + float du = u - uv[j].x; + float dv = v_coord - uv[j].y; + float dist = du * du + dv * dv; + if (dist < minDist) { minDist = dist; bestVertex = j; } + } + fillColor = vertexColors[bestVertex]; + } + +// ★★★ 新增:检查 fillColor 是否有效(接近黑色)★★★ + bool fillIsBlack = (fillColor[0] < 0.05f && fillColor[1] < 0.05f && fillColor[2] < 0.05f); + if (fillIsBlack) { + // Fallback:从邻近非空洞像素采样 + cv::Vec3f sampledColor(0, 0, 0); + int sampleCount = 0; + int sampleRad = 5; + + for (int dy = -sampleRad; dy <= sampleRad && sampleCount < 16; ++dy) { + for (int dx = -sampleRad; dx <= sampleRad && sampleCount < 16; ++dx) { + int sx = atlasX + dx, sy = atlasY + dy; + if (sx < 0 || sx >= textureSize || sy < 0 || sy >= textureSize) continue; + + Pixel8U spx = atlas(sy, sx); + // 检查是否是非空洞像素(与 colEmpty 比较) + if (spx[0] != colEmpty.b || spx[1] != colEmpty.g || spx[2] != colEmpty.r) { + sampledColor[0] += spx[2] / 255.0f; // R + sampledColor[1] += spx[1] / 255.0f; // G + sampledColor[2] += spx[0] / 255.0f; // B + sampleCount++; + } + } + } + + if (sampleCount > 0) { + fillColor = sampledColor / (float)sampleCount; + } else { + // 实在不行,用该面的平均色 + if (patchID < (int)patchAvgColor.size()) { + fillColor = cv::Vec3f( + patchAvgColor[patchID][0] / 255.0f, + patchAvgColor[patchID][1] / 255.0f, + patchAvgColor[patchID][2] / 255.0f + ); + } else { + fillColor = cv::Vec3f(0.50f, 0.55f, 0.65f); // 最终保底 + } + } + } + + // 钳制并写入 + fillColor[0] = std::max(0.0f, std::min(1.0f, fillColor[0])); + fillColor[1] = std::max(0.0f, std::min(1.0f, fillColor[1])); + fillColor[2] = std::max(0.0f, std::min(1.0f, fillColor[2])); + + atlas(atlasY, atlasX) = Pixel8U( + (uchar)(fillColor[2] * 255.0f), + (uchar)(fillColor[1] * 255.0f), + (uchar)(fillColor[0] * 255.0f) + ); + } } VERBOSE("[Raster] Step 3: rasterization done, building RC patches..."); @@ -16829,7 +17069,7 @@ bool MeshTexture::RasterizeVirtualFaces( } DEBUG_EXTRA("Created %zu RC patches", rcPatches.size()); - // ★ 统计每个 patch 的平均色 + // 统计每个 patch 的平均色 const int NP = (int)rcPatches.size(); patchAvgColor.assign(NP, Color(0,0,0)); std::vector pixelCount(NP, 0); @@ -16871,13 +17111,6 @@ bool MeshTexture::RasterizeVirtualFaces( if (m_texelPatchID[i] != NO_ID) validPixels++; DEBUG_EXTRA("m_texelPatchID: %d valid pixels out of %zu", validPixels, m_texelPatchID.size()); - // cv::imwrite("/home/algo/Documents/openMVS/data/546893/out.546893/debug_atlas_before_return.png", atlas); - - cv::Mat mask; - cv::compare(atlas, cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r), mask, cv::CMP_EQ); - cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(9, 9)); - // cv::morphologyEx(atlas, atlas, cv::MORPH_CLOSE, kernel); - return true; }