diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 4f46ca1..d2617b5 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -16211,7 +16211,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( // ★ 强制原样写入(正确图的效果) localAtlas.at(atlasY, atlasX) = color; - /* + //* // ★ 若后续需恢复仿射,请先打印参数确认: // printf("ac: kb=%.2f kg=%.2f kr=%.2f bb=%.2f bg=%.2f br=%.2f\n", ac.kb, ac.kg, ac.kr, ac.bb, ac.bg, ac.br); // 注意:OpenCV 是 BGR,确保 k/bb 对应 BGR 而非 RGB @@ -16227,7 +16227,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( } else { localAtlas.at(atlasY, atlasX) = color; } - */ + //*/ } } } @@ -16313,7 +16313,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( // 对标记为接缝的像素做高斯模糊 cv::Mat blurred; - cv::GaussianBlur(atlas, blurred, cv::Size(3, 3), 0.5); + cv::GaussianBlur(atlas, blurred, cv::Size(5, 5), 1.5); // 更大核 // 只替换接缝像素 for (int y = 0; y < textureSize; ++y) { @@ -16674,7 +16674,7 @@ bool MeshTexture::RasterizeVirtualFaces( // GSL4 会统一做全局颜色优化,这里乘了会导致双重校正 // 如果后续不走 GSL4 流程,可以把下面 false 改为 true 恢复 // ============================================================ -#if 0 // ← 改为 1 可临时恢复光栅化时乘 gain +#if 1 // ← 改为 1 可临时恢复光栅化时乘 gain // 应用光度校正 const cv::Vec3f& gain = (viewID < (IIndex)m_imageGains.size()) ? m_imageGains[viewID] : cv::Vec3f(1,1,1); const bool applyGain = (gain != cv::Vec3f(1,1,1)); @@ -16689,45 +16689,31 @@ bool MeshTexture::RasterizeVirtualFaces( } } } -#endif +#endif - // 写入 atlas(最高分胜出) - #pragma omp critical (atlas_write) - { - for (int y = 0; y < patchH; ++y) { - for (int x = 0; x < patchW; ++x) { - cv::Vec3b color = patch.at(y, x); + // 写入 atlas(最高分胜出,不做过程混合) + #pragma omp critical (atlas_write) + { + for (int y = 0; y < patchH; ++y) { + for (int x = 0; x < patchW; ++x) { + cv::Vec3b color = patch.at(y, x); - 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; - - // ★ 已删除错误放在这里的 m_texelPatchID.assign(...) - - if (currentScore > m_texelScores[idx].score) { - color[0] = cv::saturate_cast(std::min(255.0f, color[0] * 1.0f)); - color[1] = cv::saturate_cast(std::min(255.0f, color[1] * 1.0f)); - color[2] = cv::saturate_cast(std::min(255.0f, color[2] * 1.0f)); - - // atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; - if (atlasY >= 0 && atlasY < atlas.rows && atlasX >= 0 && atlasX < atlas.cols) { - // 用 cv::Mat 的 at 方法,它内部有边界检查(Debug 模式下) - cv::Vec3b& pixel = atlas.at(atlasY, atlasX); - // pixel[0] = color[2]; - // pixel[1] = color[1]; - // pixel[2] = color[0]; + if (color[0] == 0 && color[1] == 0 && color[2] == 0) continue; - pixel = color; // 原样写入,不再交换 - } - m_texelScores[idx].score = currentScore; - m_texelScores[idx].viewID = viewID; - m_texelPatchID[idx] = i; // ★ 记录这个像素属于 patch i - } - } - } - } + int atlasX = x + minX, atlasY = y + minY; + if (atlasX < 0 || atlasX >= textureSize || atlasY < 0 || atlasY >= textureSize) continue; + size_t idx = atlasY * textureSize + atlasX; + + if (currentScore > m_texelScores[idx].score) { + // 原样写入,不做任何混合 + atlas.at(atlasY, atlasX) = color; + m_texelScores[idx].score = currentScore; + m_texelScores[idx].viewID = viewID; + m_texelPatchID[idx] = i; + } + } + } + } } VERBOSE("[Raster] Step 3: rasterization done, building RC patches..."); @@ -16817,6 +16803,77 @@ bool MeshTexture::RasterizeVirtualFaces( // AlignPatchColors(atlas, m_texelPatchID, textureSize); // FeatherTextureSeams(atlas, m_texelPatchID, textureSize, 3); + // ★★★ 后处理:接缝平滑 ★★★ + { + Image8U3 smoothSrc = atlas.clone(); + int seamPixelCount = 0; + + for (int y = 1; y < textureSize - 1; ++y) { + for (int x = 1; x < textureSize - 1; ++x) { + size_t centerIdx = y * textureSize + x; + int centerPatch = m_texelPatchID[centerIdx]; + if (centerPatch == NO_ID) continue; + + // 检查 4 邻域是否有不同 patch + bool isSeam = false; + int neighbors[4] = { + m_texelPatchID[(y-1) * textureSize + x], + m_texelPatchID[(y+1) * textureSize + x], + m_texelPatchID[y * textureSize + (x-1)], + m_texelPatchID[y * textureSize + (x+1)] + }; + + for (int k = 0; k < 4; ++k) { + if (neighbors[k] != NO_ID && neighbors[k] != centerPatch) { + isSeam = true; + break; + } + } + + if (isSeam) { + cv::Vec3i sum(0, 0, 0); + int count = 0; + + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + int nx = x + dx, ny = y + dy; + if (nx < 0 || nx >= textureSize || ny < 0 || ny >= textureSize) continue; + + size_t nidx = ny * textureSize + nx; + int nPatch = m_texelPatchID[nidx]; + + if (nPatch != NO_ID && nPatch != centerPatch) { + cv::Vec3b nc = smoothSrc.at(ny, nx); + sum[0] += nc[0]; + sum[1] += nc[1]; + sum[2] += nc[2]; + count++; + } + } + } + + if (count > 0) { + cv::Vec3b avg( + cv::saturate_cast(sum[0] / count), + cv::saturate_cast(sum[1] / count), + cv::saturate_cast(sum[2] / count) + ); + + cv::Vec3b original = smoothSrc.at(y, x); + + // 混合:50% 原始 + 50% 邻居平均 + atlas.at(y, x) = cv::Vec3b( + cv::saturate_cast(original[0] * 0.5f + avg[0] * 0.5f), + cv::saturate_cast(original[1] * 0.5f + avg[1] * 0.5f), + cv::saturate_cast(original[2] * 0.5f + avg[2] * 0.5f) + ); + seamPixelCount++; + } + } + } + } + DEBUG_EXTRA("Post-process seam smoothing done: %d seam pixels processed.", seamPixelCount); + } return true; } @@ -18013,20 +18070,57 @@ int MeshTexture::ComputeOptimalTextureSizeAdaptive( cv::Vec3f MeshTexture::SampleFaceCenterColor(IIndex vid, const Point3f& faceCenter) { const Image& img = images[vid]; if (img.image.empty()) return cv::Vec3f(-1, -1, -1); + Point2f proj = img.camera.ProjectPoint(Point3d(faceCenter)); - int px = (int)(proj.x + 0.5f), py = (int)(proj.y + 0.5f); - if (px < 0 || py < 0 || px >= img.image.cols || py >= img.image.rows) - return cv::Vec3f(-1, -1, -1); - const cv::Vec3b& c = img.image.at(py, px); - return cv::Vec3f(c[2]/255.0f, c[1]/255.0f, c[0]/255.0f); // BGR→RGB, normalize + int cx = (int)(proj.x + 0.5f), cy = (int)(proj.y + 0.5f); + + // ★★★ 3x3 区域平均采样 ★★★ + cv::Vec3f sum(0, 0, 0); + int validCount = 0; + + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + int sx = cx + dx; + int sy = cy + dy; + + // 边界检查 + if (sx < 0 || sx >= img.image.cols || sy < 0 || sy >= img.image.rows) + continue; + + const cv::Vec3b& c = img.image.at(sy, sx); + + // 跳过黑色/无效像素(假设纯黑(0,0,0)为无效) + if (c[0] == 0 && c[1] == 0 && c[2] == 0) + continue; + + sum[0] += c[2]; // B→R + sum[1] += c[1]; // G→G + sum[2] += c[0]; // R→B + validCount++; + } + } + + // 如果 3x3 区域内全部无效,退回单点采样 + if (validCount == 0) { + if (cx < 0 || cy < 0 || cx >= img.image.cols || cy >= img.image.rows) + return cv::Vec3f(-1, -1, -1); + const cv::Vec3b& c = img.image.at(cy, cx); + if (c[0] == 0 && c[1] == 0 && c[2] == 0) + return cv::Vec3f(-1, -1, -1); + return cv::Vec3f(c[2]/255.0f, c[1]/255.0f, c[0]/255.0f); + } + + // 返回归一化的平均值 + return cv::Vec3f( + sum[0] / (validCount * 255.0f), + sum[1] / (validCount * 255.0f), + sum[2] / (validCount * 255.0f) + ); } // ============================================================ // 5. SelectBestViewsForVirtualFaces(修复版) // ============================================================ -// ============================================================ -// 5. SelectBestViewsForVirtualFaces(局部采样颜色版) -// ============================================================ // 前置条件:MeshTexture 类中需声明并定义 SampleFaceCenterColor: // cv::Vec3f SampleFaceCenterColor(IIndex vid, const Point3f& faceCenter) const; // 实现见下方附录 @@ -18253,67 +18347,80 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( - 0.1f * s_occlusion; float score = rawScore; - // ========== 颜色一致性代价(局部采样版)========== - { - const float lambda = 1.0f; // 从 100 降到 0.5 - - // 采样当前候选视图下面中心的颜色 - cv::Vec3f candidateColor = SampleFaceCenterColor(vid, faceCenter); - if (candidateColor[0] < 0) { - // 投影失败,跳过颜色代价但不 return - // 保持 score 不变 - } else { - // 收集邻居颜色:已分配的面,采样邻居面中心在邻居视图下的颜色 - std::vector neighborColors; - if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) { - const Mesh::FaceFaces& topoNeighbors = scene.mesh.faceFaces[faceID]; - for (int k = 0; k < 3; ++k) { - FIndex nb = topoNeighbors[k]; - if (nb == NO_ID || nb >= (FIndex)faceToView.size()) continue; - - IIndex nbView = faceToView[nb]; - if (nbView == NO_ID) { - if (nb < (FIndex)faceNeighbors.size() && !faceNeighbors[nb].empty()) { - nbView = faceNeighbors[nb][0]; - } - } - if (nbView == NO_ID || nbView >= (IIndex)images.size()) continue; - - // 计算邻居面中心 - const Mesh::Face& nbFace = scene.mesh.faces[nb]; - Point3f nbCenter = (scene.mesh.vertices[nbFace[0]] + - scene.mesh.vertices[nbFace[1]] + - scene.mesh.vertices[nbFace[2]]) / 3.0f; +// ========== 颜色一致性代价(动态阈值版)========== +{ + // ★ 判断是否为头发区域(法线朝下 + 位置偏高) + bool isHair = (faceNormal.y < -0.3f); // 法线向下,典型头发区域 + // 可根据模型调整:也可加 faceCenter.y > 某个高度阈值 + + // ★ 动态参数:头发区域放宽限制 + float dynamicLambda = isHair ? 60.0f : 120.0f; + float dynamicMaxDiff = isHair ? 40.0f : 18.0f; // 头发放宽到 40 + + // 采样当前候选视图下面中心的颜色(3x3 平均已在 SampleFaceCenterColor 中实现) + cv::Vec3f candidateColor = SampleFaceCenterColor(vid, faceCenter); + if (candidateColor[0] < 0) { + // 投影失败,跳过颜色代价 + } else { + // 收集邻居颜色 + std::vector neighborColors; + if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) { + const Mesh::FaceFaces& topoNeighbors = scene.mesh.faceFaces[faceID]; + for (int k = 0; k < 3; ++k) { + FIndex nb = topoNeighbors[k]; + if (nb == NO_ID || nb >= (FIndex)faceToView.size()) continue; - cv::Vec3f nbColor = SampleFaceCenterColor(nbView, nbCenter); - if (nbColor[0] >= 0) { - neighborColors.push_back(nbColor); - } - } + IIndex nbView = faceToView[nb]; + if (nbView == NO_ID) { + if (nb < (FIndex)faceNeighbors.size() && !faceNeighbors[nb].empty()) { + nbView = faceNeighbors[nb][0]; } + } + if (nbView == NO_ID || nbView >= (IIndex)images.size()) continue; - float colorDiff = 0.0f; - if (!neighborColors.empty()) { - for (const auto& nc : neighborColors) { - cv::Vec3f d = candidateColor - nc; - colorDiff += std::sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2]); - } - colorDiff = colorDiff / neighborColors.size(); - colorDiff = std::min(colorDiff, 1.0f); + const Mesh::Face& nbFace = scene.mesh.faces[nb]; + Point3f nbCenter = (scene.mesh.vertices[nbFace[0]] + + scene.mesh.vertices[nbFace[1]] + + scene.mesh.vertices[nbFace[2]]) / 3.0f; - score -= lambda * colorDiff; - } + cv::Vec3f nbColor = SampleFaceCenterColor(nbView, nbCenter); + if (nbColor[0] >= 0) { + neighborColors.push_back(nbColor); } + } + } - // ---- 调试日志(前20个面 + 每5万面)---- - static int g_dbg = 0; - g_dbg++; - if (g_dbg <= 20 || faceID % 50000 == 0) { - VERBOSE("[Score] face=%d view=%d rawScore=%.3f finalScore=%.3f", - faceID, vid, rawScore, score); - } + float colorDiff = 0.0f; + if (!neighborColors.empty()) { + // ★ 使用中位数代替平均,抗高光干扰 + std::vector diffs; + for (const auto& nc : neighborColors) { + cv::Vec3f d = candidateColor - nc; + diffs.push_back(std::sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2])); } - // ========== 颜色代价结束 ========== + std::sort(diffs.begin(), diffs.end()); + colorDiff = diffs[diffs.size() / 2]; // 中位数 + + // ★ 平滑惩罚:平方衰减,小差异不罚,大差异渐重 + float normDiff = std::min(colorDiff / dynamicMaxDiff, 1.0f); + score -= dynamicLambda * normDiff * normDiff; + + // ★ 极端的才重罚(避免硬截断导致碎片) + if (colorDiff > dynamicMaxDiff * 1.8f) { + score -= 300.0f; + } + } + + // ---- 调试日志 ---- + static int g_dbg = 0; + g_dbg++; + if (g_dbg <= 20 || faceID % 50000 == 0) { + VERBOSE("[Score] face=%d view=%d rawScore=%.3f finalScore=%.3f colorDiff=%.1f isHair=%d", + faceID, vid, rawScore, score, colorDiff, (int)isHair); + } + } +} +// ========== 颜色代价结束 ========== if (score > bestScore) { bestScore = score; @@ -18334,62 +18441,64 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( } } - // ---------- 2. 改进的 Patch 一致性传播 ---------- - const int PROPAGATION_ITER = 6; - for (int iter = 0; iter < PROPAGATION_ITER; ++iter) { - std::vector newFaceToView = faceToView; - std::vector newFaceScores = faceScores; + // ---------- 2. 改进的 Patch 一致性传播(削弱版)---------- + const int PROPAGATION_ITER = 3; // 从 6 降为 3,减少过度平滑 + for (int iter = 0; iter < PROPAGATION_ITER; ++iter) { + std::vector newFaceToView = faceToView; + std::vector newFaceScores = faceScores; - for (FIndex fid = 0; fid < (FIndex)faceToView.size(); ++fid) { - if (faceToView[fid] == NO_ID) continue; - if (fid >= (FIndex)scene.mesh.faceFaces.size()) continue; + for (FIndex fid = 0; fid < (FIndex)faceToView.size(); ++fid) { + if (faceToView[fid] == NO_ID) continue; + if (fid >= (FIndex)scene.mesh.faceFaces.size()) continue; - const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[fid]; - float currentScore = faceScores[fid]; + const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[fid]; + float currentScore = faceScores[fid]; - if (currentScore > 0.95f) continue; + // ★ 提高跳过门槛:高分面片不参与传播 + if (currentScore > 0.85f) continue; // 原 0.95,降低到 0.85 - std::unordered_map vote; - vote[faceToView[fid]] = 1; - for (int k = 0; k < 3; ++k) { - FIndex nb = neighbors[k]; - if (nb == NO_ID) continue; - if (nb < (FIndex)faceToView.size() && faceToView[nb] != NO_ID) { - vote[faceToView[nb]]++; - } - } + std::unordered_map vote; + vote[faceToView[fid]] = 1; + for (int k = 0; k < 3; ++k) { + FIndex nb = neighbors[k]; + if (nb == NO_ID) continue; + if (nb < (FIndex)faceToView.size() && faceToView[nb] != NO_ID) { + vote[faceToView[nb]]++; + } + } - IIndex majorityView = faceToView[fid]; - int maxVote = 0; - for (const auto& v : vote) { - if (v.second > maxVote) { - maxVote = v.second; - majorityView = v.first; - } - } + IIndex majorityView = faceToView[fid]; + int maxVote = 0; + for (const auto& v : vote) { + if (v.second > maxVote) { + maxVote = v.second; + majorityView = v.first; + } + } - if (maxVote >= 2 && majorityView != faceToView[fid]) { - float majorityAvgScore = 0.0f; - int count = 0; - for (int k = 0; k < 3; ++k) { - FIndex nb = neighbors[k]; - if (nb == NO_ID) continue; - if (nb < (FIndex)faceToView.size() && faceToView[nb] == majorityView) { - majorityAvgScore += faceScores[nb]; - count++; - } - } - if (count > 0) majorityAvgScore /= count; + if (maxVote >= 2 && majorityView != faceToView[fid]) { + float majorityAvgScore = 0.0f; + int count = 0; + for (int k = 0; k < 3; ++k) { + FIndex nb = neighbors[k]; + if (nb == NO_ID) continue; + if (nb < (FIndex)faceToView.size() && faceToView[nb] == majorityView) { + majorityAvgScore += faceScores[nb]; + count++; + } + } + if (count > 0) majorityAvgScore /= count; - if (majorityAvgScore > currentScore - 0.5f) { - newFaceToView[fid] = majorityView; - newFaceScores[fid] = majorityAvgScore; - } - } - } - faceToView.swap(newFaceToView); - faceScores.swap(newFaceScores); - } + // ★ 提高切换门槛:多数派质量必须明显更好才切换 + if (majorityAvgScore > currentScore - 0.2f && majorityAvgScore > 0.75f) { + newFaceToView[fid] = majorityView; + newFaceScores[fid] = majorityAvgScore; + } + } + } + faceToView.swap(newFaceToView); + faceScores.swap(newFaceScores); + } // ---------- 3. 写回 ---------- for (size_t i = 0; i < virtualFaceMap.size(); ++i) {