diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index fd47141..4f46ca1 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -9504,7 +9504,7 @@ void MeshTexture::GlobalSeamLeveling4() s_mn, s_mx, s_sum/cnt, o_mn, o_mx, o_sum/(cnt*3)); } - // ========== 应用颜色修正 ========== + // ========== 应用颜色修正 ========== correctedPatchImages.resize(numPatches); #ifdef TEXOPT_USE_OPENMP @@ -9527,7 +9527,7 @@ void MeshTexture::GlobalSeamLeveling4() // 初始化为 identity (scale=1, offset=0) for (int _r = 0; _r < imageAffine.rows; ++_r) for (int _c = 0; _c < imageAffine.cols; ++_c) - imageAffine(_r, _c) = Color(1.0f, 1.0f, 1.0f); // 只用 scale,offset 用另一个 + imageAffine(_r, _c) = Color(1.0f, 1.0f, 1.0f); // ★ 用两个 ColorMap 分别存 scale 和 offset ColorMap imageOffset(texturePatch.rect.size()); @@ -9535,27 +9535,63 @@ void MeshTexture::GlobalSeamLeveling4() for (int _c = 0; _c < imageOffset.cols; ++_c) imageOffset(_r, _c) = Color(0.0f, 0.0f, 0.0f); + // ★ 修改后的 RasterPatchAffine,带接缝软过渡 struct RasterPatchAffine { const TexCoord* tri; Color colors[3]; // scale Color colorsOffset[3]; // offset ColorMap& imageScale; ColorMap& imageOffset; - RasterPatchAffine(ColorMap& s, ColorMap& o) : imageScale(s), imageOffset(o) {} + const SeamVertex* seamVerticesPtr; + int numSeamVertices; + VIndex vertexIdx[3]; + + RasterPatchAffine(ColorMap& s, ColorMap& o, + const SeamVertex* sv, int nsv) + : imageScale(s), imageOffset(o), + seamVerticesPtr(sv), numSeamVertices(nsv) {} + inline cv::Size Size() const { return imageScale.size(); } + void operator()(const ImageRef& pt, const Point3f& bary) { Color s = colors[0]*bary.x + colors[1]*bary.y + colors[2]*bary.z; Color o = colorsOffset[0]*bary.x + colorsOffset[1]*bary.y + colorsOffset[2]*bary.z; if (std::isnan(s[0]) || std::isinf(s[0])) return; + + // ★ 计算到最近接缝的距离,用于软过渡 + float minDistToSeam = 1.0f; + for (int vi = 0; vi < 3; ++vi) { + VIndex vidx = vertexIdx[vi]; + for (int si = 0; si < numSeamVertices; ++si) { + if (seamVerticesPtr[si].idxVertex == vidx) { + float dist = 1.0f - (vi == 0 ? bary.x : (vi == 1 ? bary.y : bary.z)); + minDistToSeam = std::min(minDistToSeam, dist); + } + } + } + + // ★ 如果靠近接缝(距离 < 0.1),减弱修正强度 + if (minDistToSeam < 0.1f) { + float blend = minDistToSeam / 0.1f; + s = Color(1.0f, 1.0f, 1.0f) * (1.0f - blend) + s * blend; + o = Color(0.0f, 0.0f, 0.0f) * (1.0f - blend) + o * blend; + } + imageScale(pt) = s; imageOffset(pt) = o; } - } data(imageAffine, imageOffset); + } data(imageAffine, imageOffset, + seamVertices.Begin(), seamVertices.GetSize()); for (const FIndex idxFace : texturePatch.faces) { if (idxFace >= faces.GetSize()) continue; const Face& face = faces[idxFace]; data.tri = faceTexcoords.Begin() + idxFace * 3; + + // ★ 设置当前三角形的三个顶点索引 + data.vertexIdx[0] = face[0]; + data.vertexIdx[1] = face[1]; + data.vertexIdx[2] = face[2]; for (int v = 0; v < 3; ++v) { ASSERT(face[v] < vertices.size()); @@ -9563,7 +9599,7 @@ void MeshTexture::GlobalSeamLeveling4() if (search != vertpatch2rows[face[v]].end()) { const AffineParam& param = patchAffine[search->second]; data.colors[v] = Color(param.scale[0], param.scale[1], param.scale[2]); - data.colorsOffset[v] = Color(param.offset[0], param.offset[1], param.offset[2]); + data.colorsOffset[v] = Color(param.offset[0], param.offset[1], param.offset[2]); } else { data.colors[v] = Color(1.0f, 1.0f, 1.0f); data.colorsOffset[v] = Color(0.0f, 0.0f, 0.0f); @@ -9575,20 +9611,20 @@ void MeshTexture::GlobalSeamLeveling4() imageAffine.DilateMean<1>(imageAffine, Color(1.0f, 1.0f, 1.0f)); imageOffset.DilateMean<1>(imageOffset, Color(0.0f, 0.0f, 0.0f)); - // 应用: out = src * scale + offset * 255 - for (int r = 0; r < srcROI.rows; ++r) { - for (int c = 0; c < srcROI.cols; ++c) { - const Color& scale = imageAffine(r, c); - const Color& offset = imageOffset(r, c); - const cv::Vec3b& src = srcROI.at(r, c); - - outPatch.at(r, c) = cv::Vec3b( - (uint8_t)CLAMP(ROUND2INT(src[0] * scale[0] + offset[0] * 255.0f), 0, 255), - (uint8_t)CLAMP(ROUND2INT(src[1] * scale[1] + offset[1] * 255.0f), 0, 255), - (uint8_t)CLAMP(ROUND2INT(src[2] * scale[2] + offset[2] * 255.0f), 0, 255) - ); - } - } + // 应用: out = src * scale + offset * 255 + for (int r = 0; r < srcROI.rows; ++r) { + for (int c = 0; c < srcROI.cols; ++c) { + const Color& scale = imageAffine(r, c); + const Color& offset = imageOffset(r, c); + const cv::Vec3b& src = srcROI.at(r, c); + + outPatch.at(r, c) = cv::Vec3b( + (uint8_t)CLAMP(ROUND2INT(src[0] * scale[0] + offset[0] * 255.0f), 0, 255), + (uint8_t)CLAMP(ROUND2INT(src[1] * scale[1] + offset[1] * 255.0f), 0, 255), + (uint8_t)CLAMP(ROUND2INT(src[2] * scale[2] + offset[2] * 255.0f), 0, 255) + ); + } + } } // ========== 映射到按 texturePatch 索引的数组 ========== @@ -16024,13 +16060,13 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( // 从原图裁出 ROI cv::Mat patchImage = srcImg.image(tp.rect).clone(); - // 保存 ROI 诊断图 - if (pi == 0) { - cv::imwrite("/home/algo/Documents/openMVS/data/546893/out.546893/debug_patch0_roi.png", patchImage); - printf("[DIAG] patchImage size=%dx%d, nonZero=%d\n", - patchImage.cols, patchImage.rows, - cv::countNonZero(patchImage.reshape(1))); - } + // // 保存 ROI 诊断图 + // if (pi == 0) { + // cv::imwrite("/home/algo/Documents/openMVS/data/546893/out.546893/debug_patch0_roi.png", patchImage); + // printf("[DIAG] patchImage size=%dx%d, nonZero=%d\n", + // patchImage.cols, patchImage.rows, + // cv::countNonZero(patchImage.reshape(1))); + // } const RCPatch& rp = rcPatches[pi]; for (FIndex vfID : rp.faces) { @@ -16038,10 +16074,13 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( const VirtualFaceGeometry& geom = m_virtualFaceGeometries[vfID]; if (!geom.isValid) continue; - int minX = std::max(0, (int)floor(geom.uvBounds.ptMin.x() * textureSize)); - int maxX = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.x() * textureSize)); - int minY = std::max(0, (int)floor(geom.uvBounds.ptMin.y() * textureSize)); - int maxY = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.y() * textureSize)); + // 保持原来的包围盒外扩逻辑不变 + int pad = 4; + 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); + int maxY = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.y() * textureSize) + pad); + if (minX > maxX || minY > maxY) continue; int patchW = maxX - minX + 1, patchH = maxY - minY + 1; @@ -16151,13 +16190,13 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( cv::remap(patchImage, remapped, mapX, mapY, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); - // 保存 remapped 诊断图 - if (pi == 0 && !savedDebugRemapped) { - cv::imwrite("/home/algo/Documents/openMVS/data/546893/out.546893/debug_patch0_remapped.png", remapped); - savedDebugRemapped = true; - printf("[DIAG] Saved debug_patch0_remapped.png, nonZero=%d\n", - cv::countNonZero(remapped.reshape(1))); - } + // // 保存 remapped 诊断图 + // if (pi == 0 && !savedDebugRemapped) { + // cv::imwrite("/home/algo/Documents/openMVS/data/546893/out.546893/debug_patch0_remapped.png", remapped); + // savedDebugRemapped = true; + // printf("[DIAG] Saved debug_patch0_remapped.png, nonZero=%d\n", + // cv::countNonZero(remapped.reshape(1))); + // } // 写入 Atlas:【临时关闭仿射校正,解决偏蓝/反色】 for (int y = 0; y < patchH; ++y) { @@ -16200,7 +16239,94 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( if (localAtlas.at(y, x) != cv::Vec3b(0,0,0)) atlas.at(y, x) = localAtlas.at(y, x); + // ========== 写入空白三角形到文件 ========== + if (false) + { + // 收集所有没有被任何 rcPatch 覆盖的面片 + std::set coveredFaces; + for (size_t pi = 0; pi < rcPatches.size(); ++pi) { + if (rcToTexPatch[pi] == NO_ID) continue; + const TexturePatch& tp = texturePatches[rcToTexPatch[pi]]; + for (FIndex fid : tp.faces) { + coveredFaces.insert(fid); + } + } + + // 找出未被覆盖的面片(空白三角形) + std::vector emptyFaces; + for (FIndex fid = 0; fid < (FIndex)scene.mesh.faces.size(); ++fid) { + if (coveredFaces.find(fid) == coveredFaces.end()) { + emptyFaces.push_back(fid); + } + } + + // 写入文件 + if (!emptyFaces.empty()) { + std::string filename = "/home/algo/Documents/openMVS/data/546893/out.546893/mesh_empty_color_triangles.txt"; + std::ofstream out(filename); + if (out.is_open()) { + for (FIndex fid : emptyFaces) { + out << fid << "\n"; + } + out.close(); + DEBUG_EXTRA("Written %zu empty color triangles to %s", emptyFaces.size(), filename.c_str()); + } else { + DEBUG_EXTRA("Failed to open %s for writing empty triangles", filename.c_str()); + } + } else { + DEBUG_EXTRA("No empty color triangles found"); + } + } + DEBUG_EXTRA("Re-rasterization done (auto-detect H direction + ROI + affine correction)."); + + // ★★★★★ 在这里插入接缝模糊 ★★★★★ + { + // 找到所有接缝位置的像素(相邻 patch 交界处) + cv::Mat seamMask = cv::Mat::zeros(textureSize, textureSize, CV_8UC1); + + // 遍历 atlas,标记接缝像素 + for (int y = 1; y < textureSize - 1; ++y) { + for (int x = 1; x < textureSize - 1; ++x) { + cv::Vec3b center = atlas.at(y, x); + if (center == cv::Vec3b(0,0,0)) continue; + + // 检查 4 邻域是否有显著颜色差异 + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + if (dx == 0 && dy == 0) continue; + cv::Vec3b neighbor = atlas.at(y+dy, x+dx); + if (neighbor == cv::Vec3b(0,0,0)) continue; + + // 计算色差 + float diff = std::abs(center[0] - neighbor[0]) + + std::abs(center[1] - neighbor[1]) + + std::abs(center[2] - neighbor[2]); + if (diff > 80) { // 色差阈值(稍微提高避免误判) + seamMask.at(y, x) = 255; + break; + } + } + } + } + } + + // 对标记为接缝的像素做高斯模糊 + cv::Mat blurred; + cv::GaussianBlur(atlas, blurred, cv::Size(3, 3), 0.5); + + // 只替换接缝像素 + for (int y = 0; y < textureSize; ++y) { + for (int x = 0; x < textureSize; ++x) { + if (seamMask.at(y, x)) { + atlas.at(y, x) = blurred.at(y, x); + } + } + } + + DEBUG_EXTRA("Seam blur applied: masked %d pixels", cv::countNonZero(seamMask)); + } + // ★★★★★ 接缝模糊结束 ★★★★★ correctedPatchImages.clear(); // ========== 4. 清理成员变量 ========== @@ -16502,10 +16628,15 @@ bool MeshTexture::RasterizeVirtualFaces( float currentScore = virtualFaceViewWeights[i].empty() ? -1.0f : virtualFaceViewWeights[i][0]; - int minX = std::max(0, (int)floor(geom.uvBounds.ptMin.x() * textureSize)); - int maxX = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.x() * textureSize)); - int minY = std::max(0, (int)floor(geom.uvBounds.ptMin.y() * textureSize)); - int maxY = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.y() * textureSize)); + const float invSize = 1.0f / textureSize; + + // 保持原来的包围盒外扩逻辑不变 + int pad = 4; + 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); + int maxY = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.y() * textureSize) + pad); + if (minX > maxX || minY > maxY) continue; int patchW = maxX - minX + 1, patchH = maxY - minY + 1; @@ -16520,11 +16651,19 @@ bool MeshTexture::RasterizeVirtualFaces( 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]; - 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; + + float u = (float)x * invSize; + float v = (float)y * invSize; + + // ★ 简单外扩映射(保持 H,仅 UV 微移) + float w = H[6]*u + H[7]*v + 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; } } cv::Mat patch; @@ -17905,7 +18044,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( DEBUG_EXTRA("Selecting best views for %zu virtual faces", virtualFaceMap.size()); - // ---------- 准备禁用视图列表(禁用最后单个数字最大的【两个数值】的所有相机) ---------- + // ---------- 准备禁用视图列表(禁用最后一个单独数字 = 最大值的【所有】相机) ---------- std::unordered_set disabledViews; { struct ViewDigitPair { @@ -17913,11 +18052,11 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( int digit; }; std::vector viewDigits; - + for (IIndex idx = 0; idx < (IIndex)images.size(); ++idx) { const Image& img = images[idx]; if (!img.IsValid()) continue; - + // 提取文件名(不含路径和扩展名) std::string path = img.name; size_t lastSlash = path.find_last_of("/\\"); @@ -17925,7 +18064,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( size_t lastDot = path.find_last_of('.'); if (lastDot == std::string::npos) lastDot = path.size(); std::string strName = path.substr(start, lastDot - start); - + // 提取最后一个 _ 前面的最后一个数字字符 int digit = -1; size_t lastUnderscore = strName.find_last_of('_'); @@ -17938,43 +18077,28 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( if (pos == 0) break; } } - + if (digit >= 0) { viewDigits.push_back({idx, digit}); } } - - // ★ 找出最大的两个【不同】digit值 - std::set uniqueDigits; + + // ★ 找到最大的 digit 值 + int maxDigit = -1; for (const auto& vd : viewDigits) { - uniqueDigits.insert(vd.digit); + if (vd.digit > maxDigit) maxDigit = vd.digit; } - - std::vector topDigits; - auto it = uniqueDigits.rbegin(); - // 取最大的第一个 - if (it != uniqueDigits.rend()) { - topDigits.push_back(*it); - ++it; - } - // 取最大的第二个(次大) - if (it != uniqueDigits.rend()) { - topDigits.push_back(*it); - ++it; - } - - // ★ 禁用所有 digit 等于这两个最大值的视图 + + // ★ 禁用【所有】digit == maxDigit 的视图(不再只禁前 N 个) for (const auto& vd : viewDigits) { - if (std::find(topDigits.begin(), topDigits.end(), vd.digit) != topDigits.end()) { + if (vd.digit == maxDigit) { disabledViews.insert(vd.idx); - DEBUG_EXTRA("[DisabledView] idx=%d name=%s digit=%d (topDigits=[%d,%d])", - vd.idx, images[vd.idx].name.c_str(), vd.digit, - topDigits[0], topDigits.size() > 1 ? topDigits[1] : -1); + DEBUG_EXTRA("[DisabledView] idx=%d name=%s digit=%d (maxDigit=%d)", + vd.idx, images[vd.idx].name.c_str(), vd.digit, maxDigit); } } - DEBUG_EXTRA("[DisabledView] %zu views disabled (all with digit in [%d,%d])", - disabledViews.size(), - topDigits[0], topDigits.size() > 1 ? topDigits[1] : -1); + DEBUG_EXTRA("[DisabledView] %zu views disabled (all with digit=%d)", + disabledViews.size(), maxDigit); } // ---------- 初始化 ----------