解决缝隙问题和填色问题
This commit is contained in:
1 parent
f7fccb2ae9
commit
6b67c75c7e
1 file changed
+515
-282
+515
-282
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user