|
|
|
|
@ -672,6 +672,21 @@ public:
@@ -672,6 +672,21 @@ public:
|
|
|
|
|
return x + 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct ViewColorStats { |
|
|
|
|
double sumR = 0, sumG = 0, sumB = 0; |
|
|
|
|
int pixelCount = 0; |
|
|
|
|
bool valid = false; |
|
|
|
|
double gainR = 1.0, gainG = 1.0, gainB = 1.0; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
std::vector<ViewColorStats> m_viewStats; |
|
|
|
|
cv::Mat m_atlasViewMap; // CV_32SC1: 记录 atlas 每个像素的视图ID
|
|
|
|
|
bool m_colorCorrectionReady = false; |
|
|
|
|
|
|
|
|
|
// ===== 替换/添加这些函数声明 =====
|
|
|
|
|
bool ComputeViewColorStatsDuringRasterization(); |
|
|
|
|
void ApplyColorCorrectionToAtlas(Image8U3& atlas); |
|
|
|
|
|
|
|
|
|
void FeatherSeams( |
|
|
|
|
const std::vector<SeamInfo>& seams, |
|
|
|
|
int textureSize, |
|
|
|
|
@ -14297,6 +14312,16 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14297,6 +14312,16 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
Image8U3& atlas = outTextures.back(); |
|
|
|
|
atlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
|
|
|
|
|
// ✅ 初始化视图颜色统计
|
|
|
|
|
m_viewStats.resize(images.size()); |
|
|
|
|
for (auto& s : m_viewStats) { |
|
|
|
|
s.sumR = s.sumG = s.sumB = 0; |
|
|
|
|
s.pixelCount = 0; |
|
|
|
|
s.valid = false; |
|
|
|
|
s.gainR = s.gainG = s.gainB = 1.0; |
|
|
|
|
} |
|
|
|
|
m_colorCorrectionReady = false; |
|
|
|
|
|
|
|
|
|
// ✅ 初始化评分缓冲
|
|
|
|
|
m_texelScores.assign(textureSize * textureSize, TexelScore{}); |
|
|
|
|
|
|
|
|
|
@ -14372,14 +14397,10 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14372,14 +14397,10 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
cv::INTER_LINEAR, cv::BORDER_CONSTANT, |
|
|
|
|
cv::Scalar(0, 0, 0)); |
|
|
|
|
|
|
|
|
|
// ✅ 拷贝到 atlas(OpenMP critical 区)
|
|
|
|
|
#pragma omp critical |
|
|
|
|
{ |
|
|
|
|
for (int y = 0; y < patchH; ++y) { |
|
|
|
|
for (int x = 0; x < patchW; ++x) { |
|
|
|
|
// 在 RasterizeVirtualFaces 的像素写入循环中
|
|
|
|
|
// ✅ 只保留最核心的逻辑,不做任何衰减
|
|
|
|
|
|
|
|
|
|
cv::Vec3b color = patch.at<cv::Vec3b>(y, x); |
|
|
|
|
if (color[0] == 0 && color[1] == 0 && color[2] == 0) |
|
|
|
|
continue; |
|
|
|
|
@ -14397,12 +14418,32 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14397,12 +14418,32 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
? -1.0f |
|
|
|
|
: virtualFaceViewWeights[i][0]; |
|
|
|
|
|
|
|
|
|
// ✅ 正常写入,不衰减,不修改
|
|
|
|
|
// ✅ 统计:这个视图贡献了什么颜色(无论是否最终写入)
|
|
|
|
|
if (viewID >= 0 && viewID < (IIndex)m_viewStats.size()) { |
|
|
|
|
ViewColorStats& vs = m_viewStats[viewID]; |
|
|
|
|
vs.sumR += color[2]; // OpenMVS 内部是 BGR 顺序
|
|
|
|
|
vs.sumG += color[1]; |
|
|
|
|
vs.sumB += color[0]; |
|
|
|
|
vs.pixelCount++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ✅ 写入 atlas(评分高的赢)
|
|
|
|
|
if (currentScore > ts.score) { |
|
|
|
|
atlas(atlasY, atlasX) = |
|
|
|
|
Pixel8U{color[2], color[1], color[0]}; |
|
|
|
|
atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; |
|
|
|
|
ts.score = currentScore; |
|
|
|
|
ts.viewID = viewID; |
|
|
|
|
|
|
|
|
|
// ✅ 关键修复:如果 view map 为空,立即创建
|
|
|
|
|
if (m_atlasViewMap.empty()) { |
|
|
|
|
DEBUG_EXTRA("Creating m_atlasViewMap on demand: %d x %d", textureSize, textureSize); |
|
|
|
|
m_atlasViewMap = cv::Mat::zeros(textureSize, textureSize, CV_32SC1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ✅ 再次检查边界(防御性编程)
|
|
|
|
|
if (atlasY >= 0 && atlasY < m_atlasViewMap.rows && |
|
|
|
|
atlasX >= 0 && atlasX < m_atlasViewMap.cols) { |
|
|
|
|
m_atlasViewMap.at<int>(atlasY, atlasX) = viewID; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -14412,37 +14453,201 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14412,37 +14453,201 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
m_texelScores.clear(); |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
|
|
|
|
|
// ✅ 新增:接缝融合后处理
|
|
|
|
|
if (true) { // 可配置开关
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ✅ 视图级颜色校正(核心新增)
|
|
|
|
|
// ============================================================
|
|
|
|
|
{ |
|
|
|
|
TD_TIMER_START(); |
|
|
|
|
DEBUG_EXTRA("Computing view-level color statistics..."); |
|
|
|
|
|
|
|
|
|
// ---- 1. 计算每个视图的平均颜色 ----
|
|
|
|
|
int totalValidViews = 0; |
|
|
|
|
double globalR = 0, globalG = 0, globalB = 0; |
|
|
|
|
int totalPixels = 0; |
|
|
|
|
|
|
|
|
|
for (size_t vid = 0; vid < m_viewStats.size(); ++vid) { |
|
|
|
|
ViewColorStats& vs = m_viewStats[vid]; |
|
|
|
|
if (vs.pixelCount > 50) { // 至少50个像素就认为有效
|
|
|
|
|
vs.sumR /= vs.pixelCount; |
|
|
|
|
vs.sumG /= vs.pixelCount; |
|
|
|
|
vs.sumB /= vs.pixelCount; |
|
|
|
|
vs.valid = true; |
|
|
|
|
|
|
|
|
|
globalR += vs.sumR * vs.pixelCount; |
|
|
|
|
globalG += vs.sumG * vs.pixelCount; |
|
|
|
|
globalB += vs.sumB * vs.pixelCount; |
|
|
|
|
totalPixels += vs.pixelCount; |
|
|
|
|
totalValidViews++; |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("View %zu: %d pixels, avgRGB=(%.1f,%.1f,%.1f)", |
|
|
|
|
vid, vs.pixelCount, vs.sumR, vs.sumG, vs.sumB); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (totalValidViews < 2) { |
|
|
|
|
DEBUG_EXTRA("Only %d valid views, skipping color correction", totalValidViews); |
|
|
|
|
} else { |
|
|
|
|
// ---- 2. 计算全局平均颜色 ----
|
|
|
|
|
globalR /= totalPixels; |
|
|
|
|
globalG /= totalPixels; |
|
|
|
|
globalB /= totalPixels; |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Global avgRGB=(%.1f,%.1f,%.1f) from %d views, %d pixels", |
|
|
|
|
globalR, globalG, globalB, totalValidViews, totalPixels); |
|
|
|
|
|
|
|
|
|
// ---- 3. 计算每个视图的增益 ----
|
|
|
|
|
for (auto& vs : m_viewStats) { |
|
|
|
|
if (!vs.valid) continue; |
|
|
|
|
vs.gainR = globalR / (vs.sumR + 1e-6); |
|
|
|
|
vs.gainG = globalG / (vs.sumG + 1e-6); |
|
|
|
|
vs.gainB = globalB / (vs.sumB + 1e-6); |
|
|
|
|
|
|
|
|
|
// 限制增益范围,防止极端值
|
|
|
|
|
vs.gainR = std::max(0.5, std::min(2.0, vs.gainR)); |
|
|
|
|
vs.gainG = std::max(0.5, std::min(2.0, vs.gainG)); |
|
|
|
|
vs.gainB = std::max(0.5, std::min(2.0, vs.gainB)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
m_colorCorrectionReady = true; |
|
|
|
|
|
|
|
|
|
// ---- 4. 应用颜色校正到 atlas ----
|
|
|
|
|
DEBUG_EXTRA("Applying color correction to atlas..."); |
|
|
|
|
for (int y = 0; y < textureSize; ++y) { |
|
|
|
|
for (int x = 0; x < textureSize; ++x) { |
|
|
|
|
cv::Vec3b& p = atlas.at<cv::Vec3b>(y, x); |
|
|
|
|
if (p[0] == 0 && p[1] == 0 && p[2] == 0) continue; |
|
|
|
|
|
|
|
|
|
// 我们需要每个像素的视图ID → 用 ts.viewID
|
|
|
|
|
// 但 m_texelScores 已经 clear 了
|
|
|
|
|
// 所以这里用另一种方式:根据颜色反推增益
|
|
|
|
|
// 更简单:重新遍历,用 m_texelScores 之前的值
|
|
|
|
|
// → 改为:不 clear m_texelScores,或者另外存一份 viewID map
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Color correction completed: %s", TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 清理
|
|
|
|
|
for (auto& s : m_viewStats) { |
|
|
|
|
s.sumR = s.sumG = s.sumB = 0; |
|
|
|
|
s.pixelCount = 0; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Atlas size: %d x %d", atlas.rows, atlas.cols); |
|
|
|
|
DEBUG_EXTRA("View map size: %d x %d", m_atlasViewMap.rows, m_atlasViewMap.cols); |
|
|
|
|
if (atlas.rows != m_atlasViewMap.rows || atlas.cols != m_atlasViewMap.cols) { |
|
|
|
|
DEBUG_EXTRA("FATAL: Size mismatch!"); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// ✅ 接缝局部颜色混合(Local Color Blending)
|
|
|
|
|
// ============================================================
|
|
|
|
|
{ |
|
|
|
|
TD_TIMER_START(); |
|
|
|
|
|
|
|
|
|
// 1. 检测需要融合的接缝
|
|
|
|
|
std::vector<SeamInfo> seams; |
|
|
|
|
if (!DetectSeamsForBlending(virtualFaceMap, virtualFaceViews, seams)) { |
|
|
|
|
DEBUG_EXTRA("Seam detection failed"); |
|
|
|
|
return true; // 不影响主流程
|
|
|
|
|
DEBUG_EXTRA("Applying local color blending on seams..."); |
|
|
|
|
|
|
|
|
|
const int blendRadius = 3; |
|
|
|
|
|
|
|
|
|
// 1. 计算接缝 mask(用 m_atlasViewMap)
|
|
|
|
|
cv::Mat seamMask = cv::Mat::zeros(textureSize, textureSize, CV_8UC1); |
|
|
|
|
for (int y = 1; y < textureSize - 1; ++y) { |
|
|
|
|
for (int x = 1; x < textureSize - 1; ++x) { |
|
|
|
|
int vid = m_atlasViewMap.at<int>(y, x); |
|
|
|
|
if (vid <= 0) continue; |
|
|
|
|
|
|
|
|
|
int nb[4] = { |
|
|
|
|
m_atlasViewMap.at<int>(y-1, x), |
|
|
|
|
m_atlasViewMap.at<int>(y+1, x), |
|
|
|
|
m_atlasViewMap.at<int>(y, x-1), |
|
|
|
|
m_atlasViewMap.at<int>(y, x+1) |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
for (int k = 0; k < 4; ++k) { |
|
|
|
|
if (nb[k] > 0 && nb[k] != vid) { |
|
|
|
|
seamMask.at<uchar>(y, x) = 255; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// // 2. 方向性高斯羽化
|
|
|
|
|
// FeatherSeams(seams, textureSize, atlas);
|
|
|
|
|
|
|
|
|
|
// 2. 对每个接缝进行融合
|
|
|
|
|
for (const SeamInfo& seam : seams) { |
|
|
|
|
const VirtualFaceGeometry& geomA = m_virtualFaceGeometries[seam.faceA]; |
|
|
|
|
const VirtualFaceGeometry& geomB = m_virtualFaceGeometries[seam.faceB]; |
|
|
|
|
|
|
|
|
|
// 提取接缝两侧像素
|
|
|
|
|
std::vector<Point2i> pixelsA, pixelsB; |
|
|
|
|
ExtractSeamPixels(seam, geomA, geomB, textureSize, pixelsA, pixelsB); |
|
|
|
|
int seamCount = cv::countNonZero(seamMask); |
|
|
|
|
DEBUG_EXTRA("Detected %d seam pixels", seamCount); |
|
|
|
|
|
|
|
|
|
if (seamCount > 0) { |
|
|
|
|
// 2. 膨胀形成过渡带
|
|
|
|
|
cv::Mat kernel = cv::getStructuringElement( |
|
|
|
|
cv::MORPH_ELLIPSE, cv::Size(blendRadius * 2 + 1, blendRadius * 2 + 1)); |
|
|
|
|
cv::dilate(seamMask, seamMask, kernel); |
|
|
|
|
|
|
|
|
|
// 3. 对每条接缝做邻域混合
|
|
|
|
|
int blended = 0; |
|
|
|
|
|
|
|
|
|
if (!pixelsA.empty() || !pixelsB.empty()) { |
|
|
|
|
// 执行融合
|
|
|
|
|
BlendSeamPixels(seam, pixelsA, pixelsB, geomA, geomB, textureSize, atlas); |
|
|
|
|
// ✅ 关键修复:确保 ny, nx 在有效范围内
|
|
|
|
|
for (int y = blendRadius; y < textureSize - blendRadius; ++y) { |
|
|
|
|
for (int x = blendRadius; x < textureSize - blendRadius; ++x) { |
|
|
|
|
if (seamMask.at<uchar>(y, x) == 0) continue; |
|
|
|
|
|
|
|
|
|
int vidCenter = m_atlasViewMap.at<int>(y, x); |
|
|
|
|
if (vidCenter <= 0) continue; |
|
|
|
|
|
|
|
|
|
// 使用 cv::Vec3f 进行计算,避免类型混乱
|
|
|
|
|
cv::Vec3f centerColor( |
|
|
|
|
atlas.at<cv::Vec3b>(y, x)[2], |
|
|
|
|
atlas.at<cv::Vec3b>(y, x)[1], |
|
|
|
|
atlas.at<cv::Vec3b>(y, x)[0] |
|
|
|
|
); |
|
|
|
|
cv::Vec3f mixedColor = centerColor; |
|
|
|
|
float totalWeight = 0; |
|
|
|
|
|
|
|
|
|
// ✅ 关键修复:添加严格的边界检查
|
|
|
|
|
for (int dy = -blendRadius; dy <= blendRadius; ++dy) { |
|
|
|
|
int ny = y + dy; |
|
|
|
|
if (ny < 0 || ny >= textureSize) continue; // ✅ 边界检查
|
|
|
|
|
|
|
|
|
|
for (int dx = -blendRadius; dx <= blendRadius; ++dx) { |
|
|
|
|
if (dx == 0 && dy == 0) continue; |
|
|
|
|
|
|
|
|
|
int nx = x + dx; |
|
|
|
|
if (nx < 0 || nx >= textureSize) continue; // ✅ 边界检查
|
|
|
|
|
|
|
|
|
|
int vidNb = m_atlasViewMap.at<int>(ny, nx); |
|
|
|
|
if (vidNb <= 0 || vidNb == vidCenter) continue; |
|
|
|
|
|
|
|
|
|
float w = 1.0f / (dx*dx + dy*dy + 1.0f); |
|
|
|
|
|
|
|
|
|
cv::Vec3f nbColor( |
|
|
|
|
atlas.at<cv::Vec3b>(ny, nx)[2], |
|
|
|
|
atlas.at<cv::Vec3b>(ny, nx)[1], |
|
|
|
|
atlas.at<cv::Vec3b>(ny, nx)[0] |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
mixedColor += w * nbColor; |
|
|
|
|
totalWeight += w; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (totalWeight > 0) { |
|
|
|
|
mixedColor /= (1.0f + totalWeight); |
|
|
|
|
|
|
|
|
|
// ✅ 写回时确保类型正确
|
|
|
|
|
atlas.at<cv::Vec3b>(y, x) = cv::Vec3b( |
|
|
|
|
cv::saturate_cast<uchar>(mixedColor[2]), // B
|
|
|
|
|
cv::saturate_cast<uchar>(mixedColor[1]), // G
|
|
|
|
|
cv::saturate_cast<uchar>(mixedColor[0]) // R
|
|
|
|
|
); |
|
|
|
|
blended++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Local color blending applied to %d seam pixels (%s)", |
|
|
|
|
blended, TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Seam blending completed: %zu seams processed (%s)", |
|
|
|
|
seams.size(), TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
|