|
|
|
|
@ -9527,7 +9527,7 @@ void MeshTexture::GlobalSeamLeveling4()
@@ -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,28 +9535,64 @@ void MeshTexture::GlobalSeamLeveling4()
@@ -9535,28 +9535,64 @@ 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()); |
|
|
|
|
auto search = vertpatch2rows[face[v]].find(idxPatch); |
|
|
|
|
@ -16024,13 +16060,13 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
@@ -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(
@@ -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(
@@ -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(
@@ -16200,7 +16239,94 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches(
|
|
|
|
|
if (localAtlas.at<cv::Vec3b>(y, x) != cv::Vec3b(0,0,0)) |
|
|
|
|
atlas.at<cv::Vec3b>(y, x) = localAtlas.at<cv::Vec3b>(y, x); |
|
|
|
|
|
|
|
|
|
// ========== 写入空白三角形到文件 ==========
|
|
|
|
|
if (false) |
|
|
|
|
{ |
|
|
|
|
// 收集所有没有被任何 rcPatch 覆盖的面片
|
|
|
|
|
std::set<FIndex> 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<FIndex> 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<cv::Vec3b>(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<cv::Vec3b>(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<uint8_t>(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<uint8_t>(y, x)) { |
|
|
|
|
atlas.at<cv::Vec3b>(y, x) = blurred.at<cv::Vec3b>(y, x); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Seam blur applied: masked %d pixels", cv::countNonZero(seamMask)); |
|
|
|
|
} |
|
|
|
|
// ★★★★★ 接缝模糊结束 ★★★★★
|
|
|
|
|
correctedPatchImages.clear(); |
|
|
|
|
|
|
|
|
|
// ========== 4. 清理成员变量 ==========
|
|
|
|
|
@ -16502,10 +16628,15 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -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,9 +16651,17 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -16520,9 +16651,17 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
const float* H = geom.homography.ptr<float>(); |
|
|
|
|
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 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<float>(y-minY,x-minX)=-1; mapY.at<float>(y-minY,x-minX)=-1; continue; } |
|
|
|
|
if (std::abs(w) < 1e-12f) { |
|
|
|
|
mapX.at<float>(y-minY,x-minX) = -1; |
|
|
|
|
mapY.at<float>(y-minY,x-minX) = -1; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
mapX.at<float>(y-minY,x-minX) = (H[0]*u + H[1]*v + H[2])/w; |
|
|
|
|
mapY.at<float>(y-minY,x-minX) = (H[3]*u + H[4]*v + H[5])/w; |
|
|
|
|
} |
|
|
|
|
@ -17905,7 +18044,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
@@ -17905,7 +18044,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Selecting best views for %zu virtual faces", virtualFaceMap.size()); |
|
|
|
|
|
|
|
|
|
// ---------- 准备禁用视图列表(禁用最后单个数字最大的【两个数值】的所有相机) ----------
|
|
|
|
|
// ---------- 准备禁用视图列表(禁用最后一个单独数字 = 最大值的【所有】相机) ----------
|
|
|
|
|
std::unordered_set<IIndex> disabledViews; |
|
|
|
|
{ |
|
|
|
|
struct ViewDigitPair { |
|
|
|
|
@ -17944,37 +18083,22 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
@@ -17944,37 +18083,22 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ★ 找出最大的两个【不同】digit值
|
|
|
|
|
std::set<int> uniqueDigits; |
|
|
|
|
// ★ 找到最大的 digit 值
|
|
|
|
|
int maxDigit = -1; |
|
|
|
|
for (const auto& vd : viewDigits) { |
|
|
|
|
uniqueDigits.insert(vd.digit); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
std::vector<int> topDigits; |
|
|
|
|
auto it = uniqueDigits.rbegin(); |
|
|
|
|
// 取最大的第一个
|
|
|
|
|
if (it != uniqueDigits.rend()) { |
|
|
|
|
topDigits.push_back(*it); |
|
|
|
|
++it; |
|
|
|
|
} |
|
|
|
|
// 取最大的第二个(次大)
|
|
|
|
|
if (it != uniqueDigits.rend()) { |
|
|
|
|
topDigits.push_back(*it); |
|
|
|
|
++it; |
|
|
|
|
if (vd.digit > maxDigit) maxDigit = vd.digit; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ★ 禁用所有 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); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ---------- 初始化 ----------
|
|
|
|
|
|