拆解高低频,并对低频融合,高频单视图
This commit is contained in:
1 parent
56fcf290ba
commit
4d5f371e99
1 file changed
+328
-109
+328
-109
@@ -552,11 +552,14 @@ struct MeshTexture {
|
||||
Point3f center; // 面片中心(世界坐标)
|
||||
Point3f normal; // 面片法线
|
||||
AABB2f uvBounds; // UV 包围盒
|
||||
cv::Mat1f homography; // 3x3 单应矩阵(从 UV 到视图)
|
||||
cv::Mat1f homography; // 3x3 单应矩阵(从 UV 到视图),主视图 homography
|
||||
bool isValid = false;
|
||||
|
||||
std::vector<FIndex> neighborFaces; // 邻接面ID
|
||||
std::vector<int> sharedEdgeIdx; // 共享边索引 (0,1,2)
|
||||
|
||||
// ===== 新增:存储所有视图的 homography =====
|
||||
std::unordered_map<IIndex, cv::Mat> viewHomographies;
|
||||
};
|
||||
|
||||
struct SeamBlendParams {
|
||||
@@ -726,6 +729,16 @@ public:
|
||||
std::vector<std::vector<IIndex>> faceNeighbors;
|
||||
// VirtualFaceGeometryArr m_virtualFaceGeometries;
|
||||
|
||||
// 高频视图:每个面一个主视图(用于提取细节)
|
||||
std::vector<IIndex> faceViewsHighFreq;
|
||||
|
||||
// 低频视图:每个面 2-3 个辅助视图 + 权重(用于颜色融合)
|
||||
struct LowFreqView {
|
||||
IIndex viewId;
|
||||
float weight;
|
||||
};
|
||||
std::vector<std::vector<LowFreqView>> faceViewsLowFreq;
|
||||
|
||||
inline Point3f NormalizePoint3(Point3f& p)
|
||||
{
|
||||
const float l = std::sqrt(
|
||||
@@ -799,6 +812,9 @@ public:
|
||||
unsigned nTextureSizeMultiple,
|
||||
Pixel8U colEmpty,
|
||||
Mesh::Image8U3Arr& outTextures);
|
||||
|
||||
cv::Mat GetHomographyForView(int virtualFaceIdx, IIndex viewId) const;
|
||||
|
||||
void BuildStandardSeamDataFromRCPatches(
|
||||
const VirtualFaceMap& virtualFaceMap,
|
||||
const std::vector<std::vector<IIndex>>& virtualFaceViews,
|
||||
@@ -828,6 +844,32 @@ public:
|
||||
std::vector<int> patchPixelCount;
|
||||
// ========== 辅助函数 ==========
|
||||
|
||||
// 1. 构建高斯金字塔(逐层降采样模糊)
|
||||
static inline std::vector<cv::Mat> buildGaussianPyramid(const cv::Mat& img, int levels) {
|
||||
std::vector<cv::Mat> pyramid;
|
||||
pyramid.push_back(img);
|
||||
for (int i = 1; i < levels; i++) {
|
||||
cv::Mat blurred, down;
|
||||
cv::GaussianBlur(pyramid[i-1], blurred, cv::Size(5,5), 1.0);
|
||||
cv::resize(blurred, down, cv::Size(), 0.5, 0.5);
|
||||
pyramid.push_back(down);
|
||||
}
|
||||
return pyramid;
|
||||
}
|
||||
|
||||
// 2. 构建拉普拉斯金字塔(每层 = 当前层 - 上一层上采样)
|
||||
static inline std::vector<cv::Mat> buildLaplacianPyramid(const cv::Mat& img, int levels) {
|
||||
auto gp = buildGaussianPyramid(img, levels);
|
||||
std::vector<cv::Mat> lp;
|
||||
for (int i = 0; i < levels - 1; i++) {
|
||||
cv::Mat up;
|
||||
cv::resize(gp[i+1], up, gp[i].size());
|
||||
lp.push_back(gp[i] - up); // 高频细节
|
||||
}
|
||||
lp.push_back(gp.back()); // 最低频(残差)
|
||||
return lp;
|
||||
}
|
||||
|
||||
// 双线性采样(适配 SEACAVE::TImage<Pixel8U>)
|
||||
static inline Pixel8U SampleBilinear(const Image8U3& img, const Point2f& pt) {
|
||||
float x = pt.x * img.width() - 0.5f;
|
||||
@@ -16233,9 +16275,20 @@ void MeshTexture::RunSeamLevelingOnAtlas(Image8U3& atlas, int textureSize)
|
||||
images.pop_back();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 3. RC 风格光栅化主函数(含接缝优化)
|
||||
// ============================================================
|
||||
cv::Mat MeshTexture::GetHomographyForView(int virtualFaceIdx, IIndex viewId) const
|
||||
{
|
||||
if (virtualFaceIdx < 0 || virtualFaceIdx >= (int)m_virtualFaceGeometries.size())
|
||||
return cv::Mat();
|
||||
|
||||
const auto& geom = m_virtualFaceGeometries[virtualFaceIdx];
|
||||
auto it = geom.viewHomographies.find(viewId);
|
||||
if (it != geom.viewHomographies.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return cv::Mat();
|
||||
}
|
||||
|
||||
bool MeshTexture::RasterizeVirtualFaces(
|
||||
const VirtualFaceMap& virtualFaceMap,
|
||||
const std::vector<std::vector<IIndex>>& virtualFaceViews,
|
||||
@@ -16259,15 +16312,13 @@ bool MeshTexture::RasterizeVirtualFaces(
|
||||
int textureSize = ComputeOptimalTextureSizeAdaptive(virtualFaceMap, virtualFaceViews, nTextureSizeMultiple);
|
||||
if (textureSize < 1024) textureSize = 1024;
|
||||
if (textureSize > 16384) textureSize = 16384;
|
||||
textureSize = std::max(textureSize, 4096); // 至少 4096
|
||||
textureSize = std::max(textureSize, 4096);
|
||||
|
||||
// 2. atlas + score
|
||||
outTextures.emplace_back(textureSize, textureSize);
|
||||
Image8U3& atlas = outTextures.back();
|
||||
atlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r));
|
||||
m_texelScores.assign(textureSize * textureSize, TexelScore{});
|
||||
|
||||
// ★ 修复:m_texelPatchID 只初始化一次,移到循环外面
|
||||
m_texelPatchID.assign(textureSize * textureSize, NO_ID);
|
||||
|
||||
VERBOSE("[Raster] Step 1: initializing buffers... textureSize=%d, scores=%zu, patchID=%zu",
|
||||
@@ -16282,7 +16333,15 @@ bool MeshTexture::RasterizeVirtualFaces(
|
||||
|
||||
VERBOSE("[Raster] Step 2: starting patch loop, total faces=%zu", virtualFaceMap.size());
|
||||
|
||||
// 3. 逐三角形光栅化
|
||||
// ========== 新增:预加载所有图像到浮点缓存(加速多次采样)==========
|
||||
std::vector<cv::Mat> imagesFloat(images.size());
|
||||
for (size_t vi = 0; vi < images.size(); vi++) {
|
||||
if (!images[vi].image.empty()) {
|
||||
images[vi].image.convertTo(imagesFloat[vi], CV_32FC3, 1.0f / 255.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 逐三角形光栅化(分频带合成版)
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp parallel for schedule(dynamic)
|
||||
#endif
|
||||
@@ -16295,10 +16354,21 @@ bool MeshTexture::RasterizeVirtualFaces(
|
||||
const VirtualFaceGeometry& geom = m_virtualFaceGeometries[i];
|
||||
if (!geom.isValid || vf.faces.empty() || virtualFaceViews[i].empty()) continue;
|
||||
|
||||
IIndex viewID = virtualFaceViews[i][0];
|
||||
if (viewID >= (IIndex)images.size()) continue;
|
||||
const Image& srcImg = images[viewID];
|
||||
if (srcImg.image.empty() || srcImg.image.cols < 2 || srcImg.image.rows < 2) continue;
|
||||
// ========== 分频带视图获取 ==========
|
||||
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()) {
|
||||
hasLowFreqViews = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float currentScore = virtualFaceViewWeights[i].empty() ? -1.0f : virtualFaceViewWeights[i][0];
|
||||
|
||||
@@ -16309,13 +16379,13 @@ bool MeshTexture::RasterizeVirtualFaces(
|
||||
if (minX > maxX || minY > maxY) continue;
|
||||
int patchW = maxX - minX + 1, patchH = maxY - minY + 1;
|
||||
|
||||
// ★ Guard:跳过异常巨大的 patch
|
||||
if (patchW > textureSize || patchH > textureSize ||
|
||||
(int64_t)patchW * patchH > (int64_t)textureSize * textureSize / 5) {
|
||||
DEBUG_EXTRA("[Raster] face %d has HUGE rect: %dx%d, skipping", i, patchW, patchH);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ========== 构建主视图的映射 ==========
|
||||
cv::Mat mapX(patchH, patchW, CV_32FC1), mapY(patchH, patchW, CV_32FC1);
|
||||
const float* H = geom.homography.ptr<float>();
|
||||
for (int y = minY; y <= maxY; ++y) {
|
||||
@@ -16327,62 +16397,110 @@ bool MeshTexture::RasterizeVirtualFaces(
|
||||
mapY.at<float>(y-minY,x-minX) = (H[3]*u+H[4]*v+H[5])/w;
|
||||
}
|
||||
}
|
||||
cv::Mat patch;
|
||||
cv::remap(srcImg.image, patch, mapX, mapY, cv::INTER_NEAREST, cv::BORDER_CONSTANT, cv::Scalar(0,0,0));
|
||||
|
||||
// ============================================================
|
||||
// ★ 光度校正:光栅化阶段不再应用 gain
|
||||
// GSL4 会统一做全局颜色优化,这里乘了会导致双重校正
|
||||
// 如果后续不走 GSL4 流程,可以把下面 false 改为 true 恢复
|
||||
// ============================================================
|
||||
#if 0 // ← 改为 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));
|
||||
if (applyGain) {
|
||||
for (int y = 0; y < patchH; ++y) {
|
||||
for (int x = 0; x < patchW; ++x) {
|
||||
cv::Vec3b& p = patch.at<cv::Vec3b>(y, x);
|
||||
if (p[0]==0 && p[1]==0 && p[2]==0) continue;
|
||||
p[0] = cv::saturate_cast<uchar>(p[0]*gain[0]);
|
||||
p[1] = cv::saturate_cast<uchar>(p[1]*gain[1]);
|
||||
p[2] = cv::saturate_cast<uchar>(p[2]*gain[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// ========== 从主视图采样(全分辨率)==========
|
||||
cv::Mat mainPatch;
|
||||
cv::remap(images[mainView].image, mainPatch, mapX, mapY, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0));
|
||||
|
||||
// 写入 atlas(最高分胜出)
|
||||
// ========== 从辅助视图采样(各自用自己的 homography)==========
|
||||
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;
|
||||
|
||||
// ===== 获取该视图自己的 homography =====
|
||||
const auto& geom = m_virtualFaceGeometries[i];
|
||||
auto it = geom.viewHomographies.find(lfv.viewId);
|
||||
if (it == geom.viewHomographies.end()) continue;
|
||||
const cv::Mat& H_aux = it->second;
|
||||
|
||||
// ===== 用该视图的 homography 构建映射 =====
|
||||
cv::Mat auxMapX(patchH, patchW, CV_32FC1), auxMapY(patchH, patchW, CV_32FC1);
|
||||
const float* H = H_aux.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;
|
||||
}
|
||||
}
|
||||
|
||||
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), 2.0); // σ=10
|
||||
|
||||
cv::Mat highFreq = mainFloat - mainLowFreq; // 高频 = 原图 - 低频
|
||||
|
||||
// ========== 合成:低频用多视图融合,高频用主视图细节 ==========
|
||||
cv::Mat finalPatch = lowFreqPatch + highFreq;
|
||||
// cv::Mat finalPatch = lowFreqPatch.clone();
|
||||
|
||||
// 钳制到 [0, 1]
|
||||
cv::Mat clampedPatch;
|
||||
cv::max(finalPatch, 0.0f, clampedPatch);
|
||||
cv::min(clampedPatch, 1.0f, clampedPatch);
|
||||
|
||||
// 转回 8UC3
|
||||
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 = patch.at<cv::Vec3b>(y, x);
|
||||
|
||||
cv::Vec3b color = finalPatch8U.at<cv::Vec3b>(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<uchar>(std::min(255.0f, color[0] * 1.0f));
|
||||
color[1] = cv::saturate_cast<uchar>(std::min(255.0f, color[1] * 1.0f));
|
||||
color[2] = cv::saturate_cast<uchar>(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<cv::Vec3b>(atlasY, atlasX);
|
||||
pixel[0] = color[2];
|
||||
pixel[1] = color[1];
|
||||
pixel[2] = color[0];
|
||||
}
|
||||
if (atlasY >= 0 && atlasY < atlas.rows && atlasX >= 0 && atlasX < atlas.cols) {
|
||||
cv::Vec3b& pixel = atlas.at<cv::Vec3b>(atlasY, atlasX);
|
||||
pixel[0] = color[2];
|
||||
pixel[1] = color[1];
|
||||
pixel[2] = color[0];
|
||||
}
|
||||
m_texelScores[idx].score = currentScore;
|
||||
m_texelScores[idx].viewID = viewID;
|
||||
m_texelPatchID[idx] = i; // ★ 记录这个像素属于 patch i
|
||||
m_texelScores[idx].viewID = mainView;
|
||||
m_texelPatchID[idx] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16444,38 +16562,16 @@ bool MeshTexture::RasterizeVirtualFaces(
|
||||
|
||||
DEBUG_EXTRA("Patch avg color computed: %d patches, %d with pixels",
|
||||
NP, NP - std::count(pixelCount.begin(), pixelCount.end(), 0));
|
||||
|
||||
|
||||
// for (int iter = 0; iter < 3; ++iter) {
|
||||
for (int iter = 0; iter < 1; ++iter) {
|
||||
AlignPatchColors(atlas, m_texelPatchID, textureSize);
|
||||
}
|
||||
|
||||
for (int iter = 0; iter < 1; ++iter) {
|
||||
AlignPatchColors(atlas, m_texelPatchID, textureSize);
|
||||
}
|
||||
|
||||
// // 5/6/7. 接缝
|
||||
// BuildSeamEdgesFromRCPatches();
|
||||
// if (!rcSeamEdges.empty()) {
|
||||
// DEBUG_EXTRA("Before GlobalAlign: rcSeamEdges=%zu, patchAvgColor=%zu",
|
||||
// rcSeamEdges.size(), patchAvgColor.size());
|
||||
// GlobalPatchColorAlignment(atlas, textureSize);
|
||||
// LocalSeamBlending(atlas, textureSize);
|
||||
// }
|
||||
int validPixels = 0;
|
||||
for (size_t i = 0; i < m_texelPatchID.size(); ++i)
|
||||
if (m_texelPatchID[i] != NO_ID) validPixels++;
|
||||
DEBUG_EXTRA("m_texelPatchID: %d valid pixels out of %zu", validPixels, m_texelPatchID.size());
|
||||
|
||||
// if (!seamEdges.empty()) {
|
||||
// TD_TIMER_START();
|
||||
// SeamBlendingFromOriginalImages(atlas);
|
||||
// DEBUG_EXTRA("Seam blending completed: %zu edges (%s)", seamEdges.size(), TD_TIMER_GET_FMT().c_str());
|
||||
// }
|
||||
|
||||
// 在 rasterization 循环结束后、SmoothAtlasPatchBoundaries 之前加:
|
||||
int validPixels = 0;
|
||||
for (size_t i = 0; i < m_texelPatchID.size(); ++i)
|
||||
if (m_texelPatchID[i] != NO_ID) validPixels++;
|
||||
DEBUG_EXTRA("m_texelPatchID: %d valid pixels out of %zu", validPixels, m_texelPatchID.size());
|
||||
|
||||
// SmoothAtlasPatchBoundaries(atlas, textureSize);
|
||||
|
||||
// AlignPatchColors(atlas, m_texelPatchID, textureSize);
|
||||
// FeatherTextureSeams(atlas, m_texelPatchID, textureSize, 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17681,14 +17777,7 @@ cv::Vec3f MeshTexture::SampleFaceCenterColor(IIndex vid, const Point3f& faceCent
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 5. SelectBestViewsForVirtualFaces(修复版)
|
||||
// ============================================================
|
||||
// ============================================================
|
||||
// 5. SelectBestViewsForVirtualFaces(局部采样颜色版)
|
||||
// ============================================================
|
||||
// 前置条件:MeshTexture 类中需声明并定义 SampleFaceCenterColor:
|
||||
// cv::Vec3f SampleFaceCenterColor(IIndex vid, const Point3f& faceCenter) const;
|
||||
// 实现见下方附录
|
||||
// 5. SelectBestViewsForVirtualFaces(局部采样颜色版 + Top-K视图)
|
||||
// ============================================================
|
||||
bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
VirtualFaceMap& virtualFaceMap,
|
||||
@@ -17706,8 +17795,11 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
// ---------- 初始化 ----------
|
||||
faceViews.resize(virtualFaceMap.size());
|
||||
faceViewWeights.resize(virtualFaceMap.size());
|
||||
faceViewsHighFreq.resize(virtualFaceMap.size(), NO_ID);
|
||||
faceViewsLowFreq.resize(virtualFaceMap.size());
|
||||
for (auto& v : faceViews) v.clear();
|
||||
for (auto& w : faceViewWeights) w.clear();
|
||||
for (auto& v : faceViewsLowFreq) v.clear();
|
||||
|
||||
if (faceNeighbors.empty()) {
|
||||
DEBUG_EXTRA("faceNeighbors empty, running ComputePureFaceVisibility first");
|
||||
@@ -17753,7 +17845,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
[](size_t s, const auto& v) { return s + v.size(); }) / faceNeighbors.size() : 0);
|
||||
}
|
||||
|
||||
// ---------- 1. 初始视图分配 ----------
|
||||
// ---------- 1. 初始视图分配(带 Top-K 存储)----------
|
||||
size_t emptyCandidateViews = 0;
|
||||
size_t fallbackByCenterFace = 0;
|
||||
size_t successVF = 0;
|
||||
@@ -17809,6 +17901,13 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
faceNormal = Point3f(0, 0, 1);
|
||||
}
|
||||
|
||||
// ★★★ 存储所有候选视图的分数(用于 Top-K 选取)★★★
|
||||
struct ViewScore {
|
||||
IIndex viewId;
|
||||
float score;
|
||||
};
|
||||
std::vector<ViewScore> allScores;
|
||||
|
||||
IIndex bestView = NO_ID;
|
||||
float bestScore = -FLT_MAX;
|
||||
|
||||
@@ -17840,15 +17939,10 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
|
||||
// ========== 颜色一致性代价(局部采样版)==========
|
||||
{
|
||||
const float lambda = 0.5f; // 从 100 降到 0.5
|
||||
const float lambda = 0.5f;
|
||||
|
||||
// 采样当前候选视图下面中心的颜色
|
||||
cv::Vec3f candidateColor = SampleFaceCenterColor(vid, faceCenter);
|
||||
if (candidateColor[0] < 0) {
|
||||
// 投影失败,跳过颜色代价但不 return
|
||||
// 保持 score 不变
|
||||
} else {
|
||||
// 收集邻居颜色:已分配的面,采样邻居面中心在邻居视图下的颜色
|
||||
if (candidateColor[0] >= 0) {
|
||||
std::vector<cv::Vec3f> neighborColors;
|
||||
if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) {
|
||||
const Mesh::FaceFaces& topoNeighbors = scene.mesh.faceFaces[faceID];
|
||||
@@ -17864,7 +17958,6 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
}
|
||||
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]] +
|
||||
@@ -17900,6 +17993,9 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
}
|
||||
// ========== 颜色代价结束 ==========
|
||||
|
||||
// ★★★ 存储所有候选视图的分数 ★★★
|
||||
allScores.push_back({vid, score});
|
||||
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestView = vid;
|
||||
@@ -17907,8 +18003,18 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
}
|
||||
|
||||
if (bestView != NO_ID) {
|
||||
faceViews[i].push_back(bestView);
|
||||
faceViewWeights[i].push_back(bestScore);
|
||||
// ★★★ 改为存储 Top-K 视图(最多 4 个)★★★
|
||||
const int TOP_K = 4;
|
||||
std::sort(allScores.begin(), allScores.end(),
|
||||
[](const ViewScore& a, const ViewScore& b) { return a.score > b.score; });
|
||||
|
||||
int numTop = std::min(TOP_K, (int)allScores.size());
|
||||
for (int k = 0; k < numTop; ++k) {
|
||||
faceViews[i].push_back(allScores[k].viewId);
|
||||
faceViewWeights[i].push_back(allScores[k].score);
|
||||
}
|
||||
|
||||
// 只把最佳视图写入 faceToView(用于一致性传播)
|
||||
for (FIndex fid : vf.faces) {
|
||||
if (fid < faceToView.size()) {
|
||||
faceToView[fid] = bestView;
|
||||
@@ -17976,7 +18082,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
faceScores.swap(newFaceScores);
|
||||
}
|
||||
|
||||
// ---------- 3. 写回 ----------
|
||||
// ---------- 3. 写回(只更新 faceToView 中的主视图)----------
|
||||
for (size_t i = 0; i < virtualFaceMap.size(); ++i) {
|
||||
const VirtualFace& vf = virtualFaceMap[i];
|
||||
if (vf.faces.empty() || faceViews[i].empty()) continue;
|
||||
@@ -17985,8 +18091,24 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
if (firstFace < faceToView.size() && faceToView[firstFace] != NO_ID) {
|
||||
IIndex consistentView = faceToView[firstFace];
|
||||
float consistentScore = faceScores[firstFace];
|
||||
faceViews[i] = {consistentView};
|
||||
faceViewWeights[i] = {consistentScore};
|
||||
|
||||
// ★★★ 更新 Top-K 中的第一个为主视图 ★★★
|
||||
// 检查 consistentView 是否在 faceViews[i] 中
|
||||
bool found = false;
|
||||
for (size_t k = 0; k < faceViews[i].size(); ++k) {
|
||||
if (faceViews[i][k] == consistentView) {
|
||||
// 把它移到最前面
|
||||
std::swap(faceViews[i][0], faceViews[i][k]);
|
||||
std::swap(faceViewWeights[i][0], faceViewWeights[i][k]);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// 如果不在列表中,替换第一个
|
||||
faceViews[i][0] = consistentView;
|
||||
faceViewWeights[i][0] = consistentScore;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18019,6 +18141,83 @@ bool MeshTexture::SelectBestViewsForVirtualFaces(
|
||||
DEBUG_EXTRA("Fallback by center face : %zu", fallbackByCenterFace);
|
||||
DEBUG_EXTRA("=================================================");
|
||||
|
||||
// ========== 分频带视图赋值 ==========
|
||||
faceViewsHighFreq.resize(virtualFaceMap.size(), NO_ID);
|
||||
faceViewsLowFreq.resize(virtualFaceMap.size());
|
||||
|
||||
for (size_t i = 0; i < virtualFaceMap.size(); ++i) {
|
||||
if (faceViews[i].empty()) {
|
||||
faceViewsHighFreq[i] = NO_ID;
|
||||
faceViewsLowFreq[i].clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 高频视图 = 主视图(得分最高的视图)
|
||||
faceViewsHighFreq[i] = faceViews[i][0];
|
||||
|
||||
// 低频视图 = 辅助视图(从 faceViews 中取第 2-4 名)
|
||||
faceViewsLowFreq[i].clear();
|
||||
if (faceViews[i].size() >= 2) {
|
||||
// ★★★ 加入颜色相似度过滤 ★★★
|
||||
// 先采样主视图的面中心颜色
|
||||
FIndex faceID = virtualFaceMap[i].faces[0];
|
||||
const Mesh::Face& face = scene.mesh.faces[faceID];
|
||||
Point3f faceCenter = (scene.mesh.vertices[face[0]] +
|
||||
scene.mesh.vertices[face[1]] +
|
||||
scene.mesh.vertices[face[2]]) / 3.0f;
|
||||
cv::Vec3f mainColor = SampleFaceCenterColor(faceViewsHighFreq[i], faceCenter);
|
||||
|
||||
const float colorThreshold = 0.15f; // 颜色差异阈值,可调
|
||||
|
||||
// 最多取 3 个辅助视图
|
||||
int numAux = std::min(3, (int)faceViews[i].size() - 1);
|
||||
for (int j = 0; j < numAux; j++) {
|
||||
IIndex auxView = faceViews[i][j + 1]; // 第 2、3、4 名
|
||||
|
||||
// 检查颜色相似度
|
||||
cv::Vec3f auxColor = SampleFaceCenterColor(auxView, faceCenter);
|
||||
if (auxColor[0] >= 0) {
|
||||
float colorDiff = std::sqrt(
|
||||
(mainColor[0]-auxColor[0])*(mainColor[0]-auxColor[0]) +
|
||||
(mainColor[1]-auxColor[1])*(mainColor[1]-auxColor[1]) +
|
||||
(mainColor[2]-auxColor[2])*(mainColor[2]-auxColor[2]));
|
||||
|
||||
// 颜色差异太大则跳过
|
||||
if (colorDiff > colorThreshold) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算权重:基于得分比例
|
||||
float weight = faceViewWeights[i][j + 1];
|
||||
if (faceViewWeights[i][0] > 0) {
|
||||
weight /= faceViewWeights[i][0]; // 相对主视图的权重
|
||||
}
|
||||
weight = std::max(0.1f, std::min(1.0f, weight)); // 钳制到 [0.1, 1.0]
|
||||
|
||||
faceViewsLowFreq[i].push_back({auxView, weight});
|
||||
}
|
||||
|
||||
// 归一化权重
|
||||
float totalWeight = 0;
|
||||
for (const auto& lfv : faceViewsLowFreq[i]) {
|
||||
totalWeight += lfv.weight;
|
||||
}
|
||||
if (totalWeight > 0) {
|
||||
for (auto& lfv : faceViewsLowFreq[i]) {
|
||||
lfv.weight /= totalWeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_EXTRA("Frequency-band views assigned: highFreq=%zu, lowFreq=%.2f avg per face",
|
||||
faceViewsHighFreq.size(),
|
||||
faceViewsLowFreq.empty() ? 0.0f :
|
||||
(float)std::accumulate(faceViewsLowFreq.begin(), faceViewsLowFreq.end(), 0,
|
||||
[](int sum, const auto& v) { return sum + v.size(); }) / faceViewsLowFreq.size());
|
||||
// ========== 分频带视图赋值结束 ==========
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -18061,8 +18260,28 @@ bool MeshTexture::ComputeVirtualFaceGeometry(const VirtualFaceMap& virtualFaceMa
|
||||
}
|
||||
}
|
||||
|
||||
IIndex viewID = faceViews[i][0];
|
||||
geom.isValid = ComputeHomographyForVirtualFace(vf, viewID, geom);
|
||||
// ===== 主视图 homography(已有)=====
|
||||
IIndex mainViewID = faceViews[i][0];
|
||||
geom.isValid = ComputeHomographyForVirtualFace(vf, mainViewID, geom);
|
||||
if (!geom.isValid) continue;
|
||||
|
||||
// ===== 新增:将主视图 homography 存入 map =====
|
||||
geom.viewHomographies[mainViewID] = geom.homography.clone();
|
||||
|
||||
// ===== 新增:为低频视图计算 homography =====
|
||||
if (i < (int)faceViewsLowFreq.size()) {
|
||||
for (const auto& lfv : faceViewsLowFreq[i]) {
|
||||
IIndex auxViewID = lfv.viewId;
|
||||
if (auxViewID == mainViewID) continue; // 主视图已算过
|
||||
if (auxViewID >= (IIndex)images.size() || images[auxViewID].image.empty()) continue;
|
||||
|
||||
// 复用已有的 ComputeHomographyForVirtualFace
|
||||
VirtualFaceGeometry tmpGeom;
|
||||
if (ComputeHomographyForVirtualFace(vf, auxViewID, tmpGeom)) {
|
||||
geom.viewHomographies[auxViewID] = tmpGeom.homography.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
Reference in new issue
Block a user