|
|
|
|
@ -1091,6 +1091,34 @@ public:
@@ -1091,6 +1091,34 @@ public:
|
|
|
|
|
void ProjectFaceToTexture(FIndex faceID, IIndex viewID, const TexCoord* uv, Image8U3& texture); |
|
|
|
|
bool PointInTriangle(const Point2f& p, const Point2f& a, const Point2f& b, const Point2f& c, Point3f& bary); |
|
|
|
|
int ComputeOptimalTextureSize(float uvWidth, float uvHeight, unsigned multiple); |
|
|
|
|
std::vector<std::vector<float>> m_virtualFaceViewWeights; |
|
|
|
|
// ===== Seam 相关结构 =====
|
|
|
|
|
struct SeamEdge { |
|
|
|
|
uint32_t rcPatchID0; |
|
|
|
|
uint32_t rcPatchID1; |
|
|
|
|
FIndex faceID0; // 对应第一个面的VirtualFaceGeometry索引
|
|
|
|
|
FIndex faceID1; // 对应第二个面的VirtualFaceGeometry索引
|
|
|
|
|
Point2f uv0; // atlas上边的起点UV
|
|
|
|
|
Point2f uv1; // atlas上边的终点UV
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// ===== RCPatch(你已有的,确认包含以下字段)=====
|
|
|
|
|
struct RCPatch { |
|
|
|
|
IIndex viewID; |
|
|
|
|
cv::Rect rect; |
|
|
|
|
std::vector<FIndex> faces; |
|
|
|
|
Point2f uvMin, uvMax; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
std::vector<SeamEdge> rcSeamEdges; |
|
|
|
|
std::vector<RCPatch> rcPatches; |
|
|
|
|
int currentTextureSize; |
|
|
|
|
|
|
|
|
|
// ===== 函数声明 =====
|
|
|
|
|
Color SampleImageBilinear(const cv::Mat& img, float x, float y); |
|
|
|
|
void BuildSeamEdgesFromRCPatches(); |
|
|
|
|
void SeamBlendingFromOriginalImages(Image8U3& atlas); |
|
|
|
|
|
|
|
|
|
// Bruce
|
|
|
|
|
//*
|
|
|
|
|
template <typename PIXEL> |
|
|
|
|
@ -14254,7 +14282,7 @@ void MeshTexture::FillTextureHoles(std::vector<Image8U3>& textures, Pixel8U colE
@@ -14254,7 +14282,7 @@ void MeshTexture::FillTextureHoles(std::vector<Image8U3>& textures, Pixel8U colE
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// 3. RC 风格光栅化主函数
|
|
|
|
|
// 3. RC 风格光栅化主函数(含接缝优化)
|
|
|
|
|
// ============================================================
|
|
|
|
|
bool MeshTexture::RasterizeVirtualFaces( |
|
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
|
@ -14277,17 +14305,14 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14277,17 +14305,14 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
for (const TexCoord& uv : scene.mesh.faceTexcoords) |
|
|
|
|
uvBounds.InsertFull(uv); |
|
|
|
|
|
|
|
|
|
float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x(); |
|
|
|
|
float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x(); |
|
|
|
|
float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y(); |
|
|
|
|
if (uvWidth < 0.001f) uvWidth = 1.0f; |
|
|
|
|
if (uvWidth < 0.001f) uvWidth = 1.0f; |
|
|
|
|
if (uvHeight < 0.001f) uvHeight = 1.0f; |
|
|
|
|
|
|
|
|
|
// int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple);
|
|
|
|
|
int textureSize = ComputeOptimalTextureSizeAdaptive( |
|
|
|
|
int textureSize = ComputeOptimalTextureSizeAdaptive( |
|
|
|
|
virtualFaceMap, virtualFaceViews, nTextureSizeMultiple); |
|
|
|
|
|
|
|
|
|
// 兜底
|
|
|
|
|
if (textureSize < 1024) textureSize = 1024; |
|
|
|
|
if (textureSize < 1024) textureSize = 1024; |
|
|
|
|
if (textureSize > 16384) textureSize = 16384; |
|
|
|
|
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
@ -14297,17 +14322,17 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14297,17 +14322,17 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
Image8U3& atlas = outTextures.back(); |
|
|
|
|
atlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
|
|
|
|
|
// ✅ 初始化评分缓冲
|
|
|
|
|
m_texelScores.assign(textureSize * textureSize, TexelScore{}); |
|
|
|
|
m_texelScores.assign(textureSize * textureSize, TexelScore{}); |
|
|
|
|
|
|
|
|
|
// ✅ 计算所有虚拟面的几何和映射矩阵
|
|
|
|
|
if (!ComputeVirtualFaceGeometry(virtualFaceMap)) { |
|
|
|
|
DEBUG_EXTRA("Failed to compute virtual face geometries"); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
currentTextureSize = textureSize; |
|
|
|
|
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
// 3. RC 风格光栅化:按虚拟面批量处理
|
|
|
|
|
// 3. RC 风格光栅化
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
|
@ -14326,129 +14351,277 @@ bool MeshTexture::RasterizeVirtualFaces(
@@ -14326,129 +14351,277 @@ bool MeshTexture::RasterizeVirtualFaces(
|
|
|
|
|
if (srcImg.image.empty() || srcImg.image.cols < 2 || srcImg.image.rows < 2) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
// ✅ UV 包围盒 → 纹理像素范围
|
|
|
|
|
int minX = std::max(0, (int)floor(geom.uvBounds.ptMin.x() * textureSize)); |
|
|
|
|
int maxX = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.x() * textureSize)); |
|
|
|
|
int minY = std::max(0, (int)floor(geom.uvBounds.ptMin.y() * textureSize)); |
|
|
|
|
int maxY = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.y() * textureSize)); |
|
|
|
|
|
|
|
|
|
if (minX > maxX || minY > maxY) continue; |
|
|
|
|
|
|
|
|
|
int patchW = maxX - minX + 1; |
|
|
|
|
int patchH = maxY - minY + 1; |
|
|
|
|
|
|
|
|
|
// ✅ 映射矩阵
|
|
|
|
|
cv::Mat mapX(patchH, patchW, CV_32FC1); |
|
|
|
|
cv::Mat mapY(patchH, patchW, CV_32FC1); |
|
|
|
|
|
|
|
|
|
// ✅ 直接展开 H 系数(无临时 Mat,RC 标准写法)
|
|
|
|
|
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; |
|
|
|
|
float v = (float)y / (float)textureSize; |
|
|
|
|
|
|
|
|
|
float w = H[6] * u + H[7] * v + H[8]; |
|
|
|
|
|
|
|
|
|
// ✅ 数值保护(防止除零)
|
|
|
|
|
float w = H[6]*u + H[7]*v + H[8]; |
|
|
|
|
if (std::abs(w) < 1e-12f) { |
|
|
|
|
mapX.at<float>(y - minY, x - minX) = -1.0f; |
|
|
|
|
mapY.at<float>(y - minY, x - minX) = -1.0f; |
|
|
|
|
mapX.at<float>(y-minY, x-minX) = -1.0f; |
|
|
|
|
mapY.at<float>(y-minY, x-minX) = -1.0f; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
float imgX = (H[0] * u + H[1] * v + H[2]) / w; |
|
|
|
|
float imgY = (H[3] * u + H[4] * v + H[5]) / w; |
|
|
|
|
|
|
|
|
|
mapX.at<float>(y - minY, x - minX) = imgX; |
|
|
|
|
mapY.at<float>(y - minY, x - minX) = imgY; |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ✅ 一次性 remap 整个 patch
|
|
|
|
|
cv::Mat patch; |
|
|
|
|
cv::remap(srcImg.image, patch, mapX, mapY, |
|
|
|
|
cv::INTER_LINEAR, cv::BORDER_CONSTANT, |
|
|
|
|
cv::Scalar(0, 0, 0)); |
|
|
|
|
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 的像素写入循环中
|
|
|
|
|
// ✅ 只保留最核心的逻辑,不做任何衰减
|
|
|
|
|
#pragma omp critical |
|
|
|
|
{ |
|
|
|
|
for (int y = 0; y < patchH; ++y) { |
|
|
|
|
for (int x = 0; x < patchW; ++x) { |
|
|
|
|
cv::Vec3b color = patch.at<cv::Vec3b>(y, x); |
|
|
|
|
if (color[0] == 0 && color[1] == 0 && color[2] == 0) continue; |
|
|
|
|
|
|
|
|
|
int atlasX = x + minX; |
|
|
|
|
int atlasY = y + minY; |
|
|
|
|
if (atlasX < 0 || atlasX >= textureSize || |
|
|
|
|
atlasY < 0 || atlasY >= textureSize) continue; |
|
|
|
|
|
|
|
|
|
size_t idx = atlasY * textureSize + atlasX; |
|
|
|
|
TexelScore& ts = m_texelScores[idx]; |
|
|
|
|
float currentScore = virtualFaceViewWeights[i].empty() |
|
|
|
|
? -1.0f : virtualFaceViewWeights[i][0]; |
|
|
|
|
|
|
|
|
|
if (currentScore > ts.score) { |
|
|
|
|
atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; |
|
|
|
|
ts.score = currentScore; |
|
|
|
|
ts.viewID = viewID; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cv::Vec3b color = patch.at<cv::Vec3b>(y, x); |
|
|
|
|
if (color[0] == 0 && color[1] == 0 && color[2] == 0) |
|
|
|
|
continue; |
|
|
|
|
m_texelScores.clear(); |
|
|
|
|
DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
|
|
|
|
|
int atlasX = x + minX; |
|
|
|
|
int atlasY = y + minY; |
|
|
|
|
if (atlasX < 0 || atlasX >= textureSize || |
|
|
|
|
atlasY < 0 || atlasY >= textureSize) |
|
|
|
|
continue; |
|
|
|
|
m_virtualFaceViewWeights = virtualFaceViewWeights; |
|
|
|
|
|
|
|
|
|
size_t idx = atlasY * textureSize + atlasX; |
|
|
|
|
TexelScore& ts = m_texelScores[idx]; |
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
// 4. 构建 RCPatch
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
rcPatches.clear(); |
|
|
|
|
for (size_t i = 0; i < virtualFaceMap.size(); ++i) { |
|
|
|
|
if (virtualFaceViews[i].empty()) continue; |
|
|
|
|
const VirtualFaceGeometry& geom = m_virtualFaceGeometries[i]; |
|
|
|
|
if (!geom.isValid) continue; |
|
|
|
|
|
|
|
|
|
RCPatch patch; |
|
|
|
|
patch.viewID = virtualFaceViews[i][0]; |
|
|
|
|
patch.faces = { static_cast<FIndex>(i) }; |
|
|
|
|
patch.uvMin = geom.uvBounds.ptMin; |
|
|
|
|
patch.uvMax = geom.uvBounds.ptMax; |
|
|
|
|
patch.rect = cv::Rect( |
|
|
|
|
(int)(geom.uvBounds.ptMin.x() * textureSize), |
|
|
|
|
(int)(geom.uvBounds.ptMin.y() * textureSize), |
|
|
|
|
(int)((geom.uvBounds.ptMax.x() - geom.uvBounds.ptMin.x()) * textureSize) + 1, |
|
|
|
|
(int)((geom.uvBounds.ptMax.y() - geom.uvBounds.ptMin.y()) * textureSize) + 1 |
|
|
|
|
); |
|
|
|
|
patch.rect &= cv::Rect(0, 0, textureSize, textureSize); |
|
|
|
|
if (patch.rect.width > 0 && patch.rect.height > 0) |
|
|
|
|
rcPatches.push_back(patch); |
|
|
|
|
} |
|
|
|
|
DEBUG_EXTRA("Created %zu RC patches", rcPatches.size()); |
|
|
|
|
|
|
|
|
|
float currentScore = virtualFaceViewWeights[i].empty() |
|
|
|
|
? -1.0f |
|
|
|
|
: virtualFaceViewWeights[i][0]; |
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
// 5. 构建接缝边
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
BuildSeamEdgesFromRCPatches(); |
|
|
|
|
|
|
|
|
|
// ✅ 正常写入,不衰减,不修改
|
|
|
|
|
if (currentScore > ts.score) { |
|
|
|
|
atlas(atlasY, atlasX) = |
|
|
|
|
Pixel8U{color[2], color[1], color[0]}; |
|
|
|
|
ts.score = currentScore; |
|
|
|
|
ts.viewID = viewID; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
// 6. 接缝融合(从原始图像采样)
|
|
|
|
|
// --------------------------------------------------
|
|
|
|
|
if (!seamEdges.empty()) { |
|
|
|
|
TD_TIMER_START(); |
|
|
|
|
SeamBlendingFromOriginalImages(atlas); |
|
|
|
|
DEBUG_EXTRA("Seam blending completed: %zu edges (%s)", |
|
|
|
|
seamEdges.size(), TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
m_texelScores.clear(); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
|
|
|
|
|
// ✅ 新增:接缝融合后处理
|
|
|
|
|
if (true) { // 可配置开关
|
|
|
|
|
TD_TIMER_START(); |
|
|
|
|
|
|
|
|
|
// 1. 检测需要融合的接缝
|
|
|
|
|
std::vector<SeamInfo> seams; |
|
|
|
|
if (!DetectSeamsForBlending(virtualFaceMap, virtualFaceViews, seams)) { |
|
|
|
|
DEBUG_EXTRA("Seam detection failed"); |
|
|
|
|
return true; // 不影响主流程
|
|
|
|
|
// ============================================================
|
|
|
|
|
// 构建接缝边(基于 rcPatches + faceFaces)
|
|
|
|
|
// ============================================================
|
|
|
|
|
void MeshTexture::BuildSeamEdgesFromRCPatches() |
|
|
|
|
{ |
|
|
|
|
rcSeamEdges.clear(); |
|
|
|
|
|
|
|
|
|
// face → rcPatch 映射
|
|
|
|
|
std::vector<uint32_t> faceToRCPatch(scene.mesh.faces.size(), NO_ID); |
|
|
|
|
for (uint32_t pi = 0; pi < rcPatches.size(); ++pi) { |
|
|
|
|
for (FIndex f : rcPatches[pi].faces) { |
|
|
|
|
if (f < (FIndex)faceToRCPatch.size()) |
|
|
|
|
faceToRCPatch[f] = pi; |
|
|
|
|
} |
|
|
|
|
// // 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); |
|
|
|
|
|
|
|
|
|
if (!pixelsA.empty() || !pixelsB.empty()) { |
|
|
|
|
// 执行融合
|
|
|
|
|
BlendSeamPixels(seam, pixelsA, pixelsB, geomA, geomB, textureSize, atlas); |
|
|
|
|
for (FIndex f0 = 0; f0 < (FIndex)scene.mesh.faces.size(); ++f0) { |
|
|
|
|
uint32_t p0 = faceToRCPatch[f0]; |
|
|
|
|
if (p0 == NO_ID) continue; |
|
|
|
|
|
|
|
|
|
const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[f0]; |
|
|
|
|
for (int e = 0; e < 3; ++e) { |
|
|
|
|
FIndex f1 = neighbors[e]; |
|
|
|
|
if (f1 == NO_ID) continue; |
|
|
|
|
|
|
|
|
|
uint32_t p1 = faceToRCPatch[f1]; |
|
|
|
|
if (p1 == NO_ID || p1 == p0) continue; |
|
|
|
|
|
|
|
|
|
// ---------- 找共享边的两个顶点 ----------
|
|
|
|
|
const Mesh::Face& face0 = scene.mesh.faces[f0]; |
|
|
|
|
int v0_idx = e; |
|
|
|
|
int v1_idx = (e + 1) % 3; |
|
|
|
|
|
|
|
|
|
// 在 f1 中找对应顶点
|
|
|
|
|
int idx0_in_f1 = -1, idx1_in_f1 = -1; |
|
|
|
|
for (int k = 0; k < 3; ++k) { |
|
|
|
|
if (scene.mesh.faces[f1][k] == face0[v0_idx]) idx0_in_f1 = k; |
|
|
|
|
if (scene.mesh.faces[f1][k] == face0[v1_idx]) idx1_in_f1 = k; |
|
|
|
|
} |
|
|
|
|
if (idx0_in_f1 == -1 || idx1_in_f1 == -1) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
// ---------- ✅ UV 坐标(关键) ----------
|
|
|
|
|
const TexCoord& uv0_p0 = scene.mesh.faceTexcoords[f0 * 3 + v0_idx]; |
|
|
|
|
const TexCoord& uv1_p0 = scene.mesh.faceTexcoords[f0 * 3 + v1_idx]; |
|
|
|
|
const TexCoord& uv0_p1 = scene.mesh.faceTexcoords[f1 * 3 + idx0_in_f1]; |
|
|
|
|
const TexCoord& uv1_p1 = scene.mesh.faceTexcoords[f1 * 3 + idx1_in_f1]; |
|
|
|
|
|
|
|
|
|
// ---------- 构建接缝边 ----------
|
|
|
|
|
SeamEdge edge; |
|
|
|
|
edge.rcPatchID0 = p0; |
|
|
|
|
edge.rcPatchID1 = p1; |
|
|
|
|
edge.faceID0 = f0; |
|
|
|
|
edge.faceID1 = f1; |
|
|
|
|
edge.uv0 = (uv0_p0 + uv1_p0) * 0.5f; // patch0 边中点
|
|
|
|
|
edge.uv1 = (uv0_p1 + uv1_p1) * 0.5f; // patch1 边中点
|
|
|
|
|
|
|
|
|
|
rcSeamEdges.push_back(edge); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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()); |
|
|
|
|
return true; |
|
|
|
|
DEBUG_EXTRA("Built %zu RC seam edges", rcSeamEdges.size()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// 接缝融合:从原始图像采样 + YCrCb 羽化
|
|
|
|
|
// ============================================================
|
|
|
|
|
void MeshTexture::SeamBlendingFromOriginalImages(Image8U3& atlas) |
|
|
|
|
{ |
|
|
|
|
const int R = 6; // 过渡半径(像素),头发区域建议≥6
|
|
|
|
|
|
|
|
|
|
for (const SeamEdge& edge : rcSeamEdges) { |
|
|
|
|
if (edge.rcPatchID0 >= rcPatches.size() || edge.rcPatchID1 >= rcPatches.size()) |
|
|
|
|
continue; |
|
|
|
|
if (edge.faceID0 >= (FIndex)m_virtualFaceGeometries.size() || |
|
|
|
|
edge.faceID1 >= (FIndex)m_virtualFaceGeometries.size()) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
const RCPatch& patch0 = rcPatches[edge.rcPatchID0]; |
|
|
|
|
const RCPatch& patch1 = rcPatches[edge.rcPatchID1]; |
|
|
|
|
if (patch0.viewID >= (IIndex)images.size() || patch1.viewID >= (IIndex)images.size()) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
const cv::Mat& img0 = images[patch0.viewID].image; |
|
|
|
|
const cv::Mat& img1 = images[patch1.viewID].image; |
|
|
|
|
if (img0.empty() || img1.empty()) continue; |
|
|
|
|
|
|
|
|
|
// 1. 取两个面对应的单应矩阵(光栅化时用的同一个!)
|
|
|
|
|
const VirtualFaceGeometry& geom0 = m_virtualFaceGeometries[edge.faceID0]; |
|
|
|
|
const VirtualFaceGeometry& geom1 = m_virtualFaceGeometries[edge.faceID1]; |
|
|
|
|
if (!geom0.isValid || !geom1.isValid) continue; |
|
|
|
|
|
|
|
|
|
// 2. 计算边的方向和垂直法向(atlas UV空间)
|
|
|
|
|
Point2f edgeDir = edge.uv1 - edge.uv0; |
|
|
|
|
float edgeLenUV = std::sqrt(edgeDir.x*edgeDir.x + edgeDir.y*edgeDir.y); |
|
|
|
|
if (edgeLenUV < 1e-6f) continue; |
|
|
|
|
Point2f edgeDirNorm = edgeDir / edgeLenUV; |
|
|
|
|
Point2f perpDir(-edgeDirNorm.y, edgeDirNorm.x); // 垂直于边的法向
|
|
|
|
|
|
|
|
|
|
// 3. 沿边多采样(长边必须多采,避免漏采高频细节)
|
|
|
|
|
int numEdgeSamples = std::max(1, (int)(edgeLenUV * currentTextureSize)); |
|
|
|
|
for (int s = 0; s < numEdgeSamples; ++s) { |
|
|
|
|
float tEdge = (float)s / (float)numEdgeSamples; |
|
|
|
|
Point2f edgeUV = edge.uv0 * (1.0f - tEdge) + edge.uv1 * tEdge; // atlas上的边点UV
|
|
|
|
|
|
|
|
|
|
// 4. 沿垂直边的方向扩展过渡带(核心!)
|
|
|
|
|
for (int r = -R; r <= R; ++r) { |
|
|
|
|
float tBlend = (float)(r + R) / (2.0f * R); // 混合权重:r<0偏patch0,r>0偏patch1
|
|
|
|
|
float blendW = 1.0f - std::abs(tBlend - 0.5f) * 2.0f; // 中心权重高
|
|
|
|
|
|
|
|
|
|
// atlas UV偏移(垂直于边的方向)
|
|
|
|
|
float offsetUV = (float)r / (float)currentTextureSize; |
|
|
|
|
Point2f curUV = edgeUV + perpDir * offsetUV; |
|
|
|
|
|
|
|
|
|
// 5. 用单应矩阵把atlas UV映射到两个视图的图像坐标(和光栅化逻辑完全一致!)
|
|
|
|
|
// 映射patch0的视图
|
|
|
|
|
float w0 = geom0.homography.at<float>(2,0)*curUV.x + geom0.homography.at<float>(2,1)*curUV.y + geom0.homography.at<float>(2,2); |
|
|
|
|
if (std::abs(w0) < 1e-12f) continue; |
|
|
|
|
float imgX0 = (geom0.homography.at<float>(0,0)*curUV.x + geom0.homography.at<float>(0,1)*curUV.y + geom0.homography.at<float>(0,2)) / w0; |
|
|
|
|
float imgY0 = (geom0.homography.at<float>(1,0)*curUV.x + geom0.homography.at<float>(1,1)*curUV.y + geom0.homography.at<float>(1,2)) / w0; |
|
|
|
|
|
|
|
|
|
// 映射patch1的视图
|
|
|
|
|
float w1 = geom1.homography.at<float>(2,0)*curUV.x + geom1.homography.at<float>(2,1)*curUV.y + geom1.homography.at<float>(2,2); |
|
|
|
|
if (std::abs(w1) < 1e-12f) continue; |
|
|
|
|
float imgX1 = (geom1.homography.at<float>(0,0)*curUV.x + geom1.homography.at<float>(0,1)*curUV.y + geom1.homography.at<float>(0,2)) / w1; |
|
|
|
|
float imgY1 = (geom1.homography.at<float>(1,0)*curUV.x + geom1.homography.at<float>(1,1)*curUV.y + geom1.homography.at<float>(1,2)) / w1; |
|
|
|
|
|
|
|
|
|
// 6. 从两个原始视图采样同一个三维点的颜色
|
|
|
|
|
Color col0 = RGB2YCBCR(SampleImageBilinear(img0, imgX0, imgY0)); |
|
|
|
|
Color col1 = RGB2YCBCR(SampleImageBilinear(img1, imgX1, imgY1)); |
|
|
|
|
|
|
|
|
|
// 7. 转换到atlas像素坐标
|
|
|
|
|
int px = (int)(curUV.x * currentTextureSize); |
|
|
|
|
int py = (int)(curUV.y * currentTextureSize); |
|
|
|
|
if (px < 0 || px >= currentTextureSize || py < 0 || py >= currentTextureSize) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
// 8. 结合评分系统:只覆盖评分更低的像素(避免破坏最优视图)
|
|
|
|
|
size_t idx = py * currentTextureSize + px; |
|
|
|
|
if (idx >= m_texelScores.size()) continue; |
|
|
|
|
float currentScore = m_texelScores[idx].score; |
|
|
|
|
float edgeScore0 = m_virtualFaceViewWeights[edge.faceID0].empty() ? -1 : m_virtualFaceViewWeights[edge.faceID0][0]; |
|
|
|
|
float edgeScore1 = m_virtualFaceViewWeights[edge.faceID1].empty() ? -1 : m_virtualFaceViewWeights[edge.faceID1][0]; |
|
|
|
|
// 只有当混合后的权重对应的视图评分更高时才覆盖
|
|
|
|
|
if (tBlend < 0.5f && edgeScore0 <= currentScore) continue; |
|
|
|
|
if (tBlend >= 0.5f && edgeScore1 <= currentScore) continue; |
|
|
|
|
|
|
|
|
|
// 9. 混合颜色(YCrCb空间,避免亮度偏移)
|
|
|
|
|
Color mixedYCbCr = col0 * (1.0f - tBlend) + col1 * tBlend; |
|
|
|
|
Color mixedRGB = YCBCR2RGB(mixedYCbCr); |
|
|
|
|
|
|
|
|
|
// 10. 写入atlas
|
|
|
|
|
Pixel8U& p = atlas(py, px); |
|
|
|
|
p = Pixel8U( |
|
|
|
|
CLAMP((int)roundf(mixedRGB.x), 0, 255), |
|
|
|
|
CLAMP((int)roundf(mixedRGB.y), 0, 255), |
|
|
|
|
CLAMP((int)roundf(mixedRGB.z), 0, 255) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
float MeshTexture::EstimatePixelSize(const Point3f& faceCenter, const Normal& faceNormal, |
|
|
|
|
const Image& image) { |
|
|
|
|
const Camera& cam = image.camera; |
|
|
|
|
@ -18193,52 +18366,6 @@ bool PointInTriangle(const Point2f& p, const Point2f& a, const Point2f& b, const
@@ -18193,52 +18366,6 @@ bool PointInTriangle(const Point2f& p, const Point2f& a, const Point2f& b, const
|
|
|
|
|
barycentric.y >= 0 && barycentric.y <= 1 && |
|
|
|
|
barycentric.z >= 0 && barycentric.z <= 1); |
|
|
|
|
} |
|
|
|
|
// 辅助函数:从图像中双线性插值采样颜色
|
|
|
|
|
// 修正颜色顺序和边界处理
|
|
|
|
|
Pixel8U SampleImageBilinear(const Image8U3& image, const Point2f& point) { |
|
|
|
|
// 边界检查,防止越界
|
|
|
|
|
float x = CLAMP(point.x, 0.0f, (float)(image.cols - 1)); |
|
|
|
|
float y = CLAMP(point.y, 0.0f, (float)(image.rows - 1)); |
|
|
|
|
|
|
|
|
|
int x0 = (int)floor(x); |
|
|
|
|
int y0 = (int)floor(y); |
|
|
|
|
int x1 = std::min(x0 + 1, image.cols - 1); |
|
|
|
|
int y1 = std::min(y0 + 1, image.rows - 1); |
|
|
|
|
|
|
|
|
|
// 确保x0,y0不会超出下界
|
|
|
|
|
x0 = std::max(0, x0); |
|
|
|
|
y0 = std::max(0, y0); |
|
|
|
|
|
|
|
|
|
float dx = x - x0; |
|
|
|
|
float dy = y - y0; |
|
|
|
|
float dx1 = 1.0f - dx; |
|
|
|
|
float dy1 = 1.0f - dy; |
|
|
|
|
|
|
|
|
|
// 获取四个角点的像素
|
|
|
|
|
const Pixel8U& p00 = image(y0, x0); |
|
|
|
|
const Pixel8U& p01 = image(y0, x1); |
|
|
|
|
const Pixel8U& p10 = image(y1, x0); |
|
|
|
|
const Pixel8U& p11 = image(y1, x1); |
|
|
|
|
|
|
|
|
|
// 方法1:使用结构体成员访问(推荐)
|
|
|
|
|
float b = p00.b * dx1 * dy1 + p01.b * dx * dy1 + p10.b * dx1 * dy + p11.b * dx * dy; |
|
|
|
|
float g = p00.g * dx1 * dy1 + p01.g * dx * dy1 + p10.g * dx1 * dy + p11.g * dx * dy; |
|
|
|
|
float r = p00.r * dx1 * dy1 + p01.r * dx * dy1 + p10.r * dx1 * dy + p11.r * dx * dy; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
// 方法2:如果Pixel8U支持[]操作符
|
|
|
|
|
// 注意:OpenMVS的Pixel8U可能是BGR或RGB顺序,需要根据实际情况调整
|
|
|
|
|
float b = p00[0] * dx1 * dy1 + p01[0] * dx * dy1 + p10[0] * dx1 * dy + p11[0] * dx * dy; |
|
|
|
|
float g = p00[1] * dx1 * dy1 + p01[1] * dx * dy1 + p10[1] * dx1 * dy + p11[1] * dx * dy; |
|
|
|
|
float r = p00[2] * dx1 * dy1 + p01[2] * dx * dy1 + p10[2] * dx1 * dy + p11[2] * dx * dy; |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
return Pixel8U( |
|
|
|
|
(unsigned char)CLAMP(b, 0.0f, 255.0f), |
|
|
|
|
(unsigned char)CLAMP(g, 0.0f, 255.0f), |
|
|
|
|
(unsigned char)CLAMP(r, 0.0f, 255.0f) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void FillTextureGaps2(Image8U3& textureAtlas, const Mesh::TexCoordArr& faceTexcoords, |
|
|
|
|
FIndex nFaces, const MeshTexture::LabelArr& faceLabels, |
|
|
|
|
@ -18721,6 +18848,35 @@ bool MeshTexture::ValidateProjection(const Vertex& worldPoint,
@@ -18721,6 +18848,35 @@ bool MeshTexture::ValidateProjection(const Vertex& worldPoint,
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// 双线性采样(从原始图像按 UV 采样)
|
|
|
|
|
// ============================================================
|
|
|
|
|
MeshTexture::Color MeshTexture::SampleImageBilinear(const cv::Mat& img, float x, float y) |
|
|
|
|
{ |
|
|
|
|
if (img.empty() || x < 0 || x >= img.cols-1 || y < 0 || y >= img.rows-1) |
|
|
|
|
return Color(0,0,0); |
|
|
|
|
|
|
|
|
|
int xi = (int)x; |
|
|
|
|
int yi = (int)y; |
|
|
|
|
float dx = x - xi; |
|
|
|
|
float dy = y - yi; |
|
|
|
|
|
|
|
|
|
cv::Vec3b p00 = img.at<cv::Vec3b>(yi, xi); |
|
|
|
|
cv::Vec3b p01 = img.at<cv::Vec3b>(yi+1, xi); |
|
|
|
|
cv::Vec3b p10 = img.at<cv::Vec3b>(yi, xi+1); |
|
|
|
|
cv::Vec3b p11 = img.at<cv::Vec3b>(yi+1, xi+1); |
|
|
|
|
|
|
|
|
|
Color c; |
|
|
|
|
for (int k = 0; k < 3; ++k) { |
|
|
|
|
float v00 = (k == 0) ? p00[2] : (k == 1) ? p00[1] : p00[0]; |
|
|
|
|
float v01 = (k == 0) ? p01[2] : (k == 1) ? p01[1] : p01[0]; |
|
|
|
|
float v10 = (k == 0) ? p10[2] : (k == 1) ? p10[1] : p10[0]; |
|
|
|
|
float v11 = (k == 0) ? p11[2] : (k == 1) ? p11[1] : p11[0]; |
|
|
|
|
c[k] = v00*(1-dx)*(1-dy) + v01*(1-dx)*dy + v10*dx*(1-dy) + v11*dx*dy; |
|
|
|
|
} |
|
|
|
|
return c; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Pixel8U MeshTexture::SampleImageBilinear(const Image8U3& image, const Point2f& point) { |
|
|
|
|
const int x1 = (int)point.x; |
|
|
|
|
const int y1 = (int)point.y; |
|
|
|
|
|