解决缝隙问题和填色问题

This commit is contained in:
hesuicong committed 2026-10-19 18:25:04 +08:00
1 parent f7fccb2ae9
commit 6b67c75c7e
1 file changed
+515 -282
+515 -282
View File
@@ -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<int> neighborVertices; // 邻接顶点
};
std::vector<VertexColorInfo> m_vertexColors;
std::vector<uint32_t> 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<cv::Mat> 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<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 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<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;
mapX.at<float>(y-minY,x-minX) = (H[0]*u+H[1]*v_coord+H[2])/w;
mapY.at<float>(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<float>(py - minY, px - minX) = imgPt.x;
mapY.at<float>(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<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 w = H[6]*u + H[7]*v + H[8];
if (std::abs(w) < 1e-12f) {
auxMapX.at<float>(y-minY,x-minX) = -1;
auxMapY.at<float>(y-minY,x-minX) = -1;
continue;
}
auxMapX.at<float>(y-minY,x-minX) = (H[0]*u+H[1]*v+H[2])/w;
auxMapY.at<float>(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<float>(py - minY, px - minX) = imgPt.x;
auxMapY.at<float>(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<cv::Vec3f>(y, x);
if (color[0] == 0.0f && color[1] == 0.0f && color[2] == 0.0f) {
filledLowFreq.at<cv::Vec3f>(y, x) = mainFloat.at<cv::Vec3f>(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<float>();
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<float>(y-minY,x-minX) = -1;
auxMapY.at<float>(y-minY,x-minX) = -1;
continue;
}
auxMapX.at<float>(y-minY,x-minX) = (H[0]*u+H[1]*v_coord+H[2])/w;
auxMapY.at<float>(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<float>(py - minY, px - minX) = imgPt.x;
auxMapY.at<float>(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<cv::Vec3f>(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>();
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<cv::Vec3f>((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<cv::Vec3f>(y, x);
if (color[0] == 0.0f && color[1] == 0.0f && color[2] == 0.0f) {
filledLowFreq.at<cv::Vec3f>(y, x) = mainFloat.at<cv::Vec3f>(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<cv::Vec3b>(y, x);
// float mx = mapX.at<float>(y, x);
// float my = mapY.at<float>(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<cv::Vec3b>(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<cv::Vec3b>(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<cv::Vec3b>(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<cv::Vec3b>(atlasY, atlasX) = color;
m_texelScores[idx].score = currentScore;
m_texelScores[idx].viewID = mainView;
m_texelPatchID[idx] = i;
}
}
}
}
}
// ========== OpenMP 循环结束,执行顶点级别颜色扩散 ==========
// BFS扩散
std::queue<VIndex> bfsQueue;
std::vector<bool> 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<int> 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;
}