模糊版本清晰化
This commit is contained in:
1 parent
6922abe692
commit
93ee28a3ca
1 file changed
+232
-103
+232
-103
@@ -15369,7 +15369,7 @@ Pixel8U MeshTexture::BlendTopTwoViews(
|
||||
return result;
|
||||
}
|
||||
|
||||
//*
|
||||
/*
|
||||
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||
const VirtualFaceMap& virtualFaceMap,
|
||||
const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容
|
||||
@@ -15612,140 +15612,269 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||
return textures;
|
||||
}
|
||||
//*/
|
||||
/*
|
||||
//*
|
||||
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
|
||||
const VirtualFaceMap& virtualFaceMap,
|
||||
const VirtualFaceDataArr&,
|
||||
const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容
|
||||
const std::vector<std::vector<IIndex>>& faceViews,
|
||||
const std::vector<std::vector<float>>& faceViewWeights,
|
||||
unsigned nTextureSizeMultiple,
|
||||
Pixel8U colEmpty,
|
||||
float)
|
||||
float fSharpnessWeight)
|
||||
{
|
||||
DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces");
|
||||
|
||||
// 1. UV边界
|
||||
DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces (SHARPENED)");
|
||||
|
||||
// 1. 分析UV布局
|
||||
AABB2f uvBounds(true);
|
||||
FOREACH(i, scene.mesh.faceTexcoords)
|
||||
uvBounds.InsertFull(scene.mesh.faceTexcoords[i]);
|
||||
|
||||
// 2. 纹理尺寸
|
||||
FOREACH(i, scene.mesh.faceTexcoords) {
|
||||
const TexCoord& uv = scene.mesh.faceTexcoords[i];
|
||||
uvBounds.InsertFull(uv);
|
||||
}
|
||||
|
||||
// 2. 计算纹理尺寸
|
||||
float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x();
|
||||
float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y();
|
||||
|
||||
if (uvWidth < 0.001f) uvWidth = 1.0f;
|
||||
if (uvHeight < 0.001f) uvHeight = 1.0f;
|
||||
|
||||
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple);
|
||||
|
||||
|
||||
// 3. 创建纹理图集
|
||||
Mesh::Image8U3Arr textures;
|
||||
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize);
|
||||
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r));
|
||||
|
||||
// 缓存列数和数据指针
|
||||
const int cols = textureAtlas.cols;
|
||||
Pixel8U* data = reinterpret_cast<Pixel8U*>(textureAtlas.data);
|
||||
|
||||
DEBUG_EXTRA("Texture atlas size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]",
|
||||
textureSize, textureSize,
|
||||
uvBounds.ptMin.x(), uvBounds.ptMin.y(),
|
||||
|
||||
// 4. 创建权重图、累积颜色图和采样计数图,用于混合
|
||||
cv::Mat1f weightAccum(textureSize, textureSize, 0.0f);
|
||||
cv::Mat3f colorAccum(textureSize, textureSize, cv::Vec3f(0, 0, 0)); // 用浮点数累积颜色
|
||||
cv::Mat1i sampleCount(textureSize, textureSize, 0); // 采样计数,用于统计每个像素被采样了多少次
|
||||
|
||||
DEBUG_EXTRA("Texture atlas size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]",
|
||||
textureSize, textureSize,
|
||||
uvBounds.ptMin.x(), uvBounds.ptMin.y(),
|
||||
uvBounds.ptMax.x(), uvBounds.ptMax.y());
|
||||
|
||||
// 4. 遍历虚拟面(OpenMP并行)
|
||||
|
||||
// ✅ 超采样参数
|
||||
const int SUPER_SAMPLE = 2; // 2x2超采样
|
||||
const float STEP = 1.0f / SUPER_SAMPLE;
|
||||
|
||||
// 5. 处理每个虚拟面
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp parallel for schedule(dynamic)
|
||||
#endif
|
||||
for (int_t idxVF = 0; idxVF < (int_t)virtualFaceMap.size(); ++idxVF) {
|
||||
// 边界检查(防御性编程)
|
||||
if (idxVF >= (int_t)faceViews.size() || idxVF >= (int_t)faceViewWeights.size())
|
||||
continue;
|
||||
|
||||
#else
|
||||
for (size_t idxVF = 0; idxVF < virtualFaceMap.size(); ++idxVF) {
|
||||
#endif
|
||||
const VirtualFace& vf = virtualFaceMap[idxVF];
|
||||
if (faceViews[idxVF].empty())
|
||||
|
||||
// 检查虚拟面是否有可用视图
|
||||
if (faceViews[idxVF].empty()) {
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
// 处理虚拟面中的每个原始面片
|
||||
for (FIndex faceID : vf.faces) {
|
||||
// 防御性检查faceID
|
||||
if (faceID >= scene.mesh.faces.size())
|
||||
continue;
|
||||
|
||||
const Face& face = scene.mesh.faces[faceID];
|
||||
const TexCoord* uv = &scene.mesh.faceTexcoords[faceID * 3];
|
||||
const Vertex* vtx = &scene.mesh.vertices[face[0]];
|
||||
const Normal& faceNormal = scene.mesh.faceNormals[faceID];
|
||||
|
||||
// ✅ 关键:用面片中心选择最佳视图(面片级选视图)
|
||||
Point3d faceCenter(
|
||||
(vtx[0].x + vtx[1].x + vtx[2].x) / 3.0,
|
||||
(vtx[0].y + vtx[1].y + vtx[2].y) / 3.0,
|
||||
(vtx[0].z + vtx[1].z + vtx[2].z) / 3.0
|
||||
);
|
||||
|
||||
TexelViewInfo viewInfo;
|
||||
if (!SelectBestSingleView(
|
||||
Point3f(faceCenter.x, faceCenter.y, faceCenter.z),
|
||||
faceNormal,
|
||||
faceViews[idxVF],
|
||||
faceViewWeights[idxVF],
|
||||
viewInfo)) {
|
||||
continue;
|
||||
const TexCoord* uvCoords = &scene.mesh.faceTexcoords[faceID * 3];
|
||||
|
||||
// 计算面片在纹理空间中的边界框
|
||||
AABB2f faceUVBounds(true);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
faceUVBounds.InsertFull(uvCoords[i]);
|
||||
}
|
||||
|
||||
const Image& bestImg = images[viewInfo.best_view_id];
|
||||
|
||||
// UV边界框
|
||||
AABB2f uvBox(true);
|
||||
uvBox.InsertFull(uv[0]);
|
||||
uvBox.InsertFull(uv[1]);
|
||||
uvBox.InsertFull(uv[2]);
|
||||
|
||||
const int x0 = std::max(0, (int)(uvBox.ptMin.x() * textureSize));
|
||||
const int y0 = std::max(0, (int)(uvBox.ptMin.y() * textureSize));
|
||||
const int x1 = std::min(textureSize - 1, (int)(uvBox.ptMax.x() * textureSize));
|
||||
const int y1 = std::min(textureSize - 1, (int)(uvBox.ptMax.y() * textureSize));
|
||||
|
||||
// 遍历纹素
|
||||
for (int y = y0; y <= y1; ++y) {
|
||||
for (int x = x0; x <= x1; ++x) {
|
||||
const Point2f texCoord((float)x / textureSize, (float)y / textureSize);
|
||||
|
||||
// 重心坐标测试
|
||||
Point3f bary;
|
||||
if (!PointInTriangle(texCoord, uv[0], uv[1], uv[2], bary))
|
||||
continue;
|
||||
|
||||
// 计算世界点
|
||||
Point3d worldPos(
|
||||
vtx[0].x * bary.x + vtx[1].x * bary.y + vtx[2].x * bary.z,
|
||||
vtx[0].y * bary.x + vtx[1].y * bary.y + vtx[2].y * bary.z,
|
||||
vtx[0].z * bary.x + vtx[1].z * bary.y + vtx[2].z * bary.z
|
||||
);
|
||||
|
||||
// ✅ 只投影,不再选视图(使用面片选定的视图)
|
||||
Point2f proj = ProjectPointWithAutoCorrection(
|
||||
bestImg.camera,
|
||||
Vertex(worldPos.x, worldPos.y, worldPos.z),
|
||||
bestImg
|
||||
);
|
||||
|
||||
if (!bestImg.image.isInside(proj))
|
||||
continue;
|
||||
|
||||
Pixel8U color = SampleImageBicubic(bestImg.image, proj);
|
||||
|
||||
// ✅ 检查是否为空色(避免写入无效像素)
|
||||
if (color[0] == colEmpty.r &&
|
||||
color[1] == colEmpty.g &&
|
||||
color[2] == colEmpty.b)
|
||||
continue;
|
||||
|
||||
// ✅ 写入纹理(无锁,后写覆盖先写)
|
||||
data[y * cols + x] = color;
|
||||
|
||||
const int startX = std::max(0, (int)(faceUVBounds.ptMin.x() * textureSize));
|
||||
const int startY = std::max(0, (int)(faceUVBounds.ptMin.y() * textureSize));
|
||||
const int endX = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.x() * textureSize));
|
||||
const int endY = std::min(textureSize - 1, (int)(faceUVBounds.ptMax.y() * textureSize));
|
||||
|
||||
// 为当前面片预计算3D点的重心坐标映射
|
||||
std::vector<Point2f> texPoints;
|
||||
std::vector<cv::Vec3f> pointColors; // 存储每个采样点的颜色
|
||||
|
||||
// ✅ 均匀采样面片内部(带超采样)
|
||||
for (int y = startY; y <= endY; ++y) {
|
||||
for (int x = startX; x <= endX; ++x) {
|
||||
// ✅ 超采样循环:每个像素采样SUPER_SAMPLE x SUPER_SAMPLE次
|
||||
for (int sy = 0; sy < SUPER_SAMPLE; ++sy) {
|
||||
for (int sx = 0; sx < SUPER_SAMPLE; ++sx) {
|
||||
// 计算亚像素纹理坐标(在像素中心偏移)
|
||||
float fx = x + (sx + 0.5f) * STEP;
|
||||
float fy = y + (sy + 0.5f) * STEP;
|
||||
const Point2f texCoord(fx / textureSize, fy / textureSize);
|
||||
|
||||
// 计算重心坐标
|
||||
Point3f barycentric;
|
||||
if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) {
|
||||
// 计算3D点
|
||||
const Vertex worldPoint =
|
||||
scene.mesh.vertices[face[0]] * barycentric.x +
|
||||
scene.mesh.vertices[face[1]] * barycentric.y +
|
||||
scene.mesh.vertices[face[2]] * barycentric.z;
|
||||
|
||||
// 为每个视图采样颜色
|
||||
cv::Vec3f accumColor(0, 0, 0);
|
||||
float totalWeight = 0.0f;
|
||||
|
||||
for (size_t viewIdx = 0; viewIdx < faceViews[idxVF].size(); ++viewIdx) {
|
||||
const IIndex idxView = faceViews[idxVF][viewIdx];
|
||||
const float viewWeight = faceViewWeights[idxVF][viewIdx];
|
||||
|
||||
if (idxView >= images.size()) continue;
|
||||
|
||||
const Image& sourceImage = images[idxView];
|
||||
|
||||
// 投影到图像
|
||||
Point2f imgPoint = ProjectPointWithAutoCorrection(sourceImage.camera, worldPoint, sourceImage);
|
||||
|
||||
// 验证投影
|
||||
if (!ValidateProjection(worldPoint, sourceImage, imgPoint) ||
|
||||
!sourceImage.image.isInside(imgPoint) ||
|
||||
!sourceImage.camera.IsInFront(worldPoint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ✅ 使用高质量采样(双三次插值)
|
||||
Sampler sampler;
|
||||
Color color = sourceImage.image.sample<Sampler, Color>(sampler, imgPoint);
|
||||
|
||||
// 累积加权颜色(注意:OpenMVS的Color顺序是BGR)
|
||||
accumColor[0] += color[0] * viewWeight; // B
|
||||
accumColor[1] += color[1] * viewWeight; // G
|
||||
accumColor[2] += color[2] * viewWeight; // R
|
||||
totalWeight += viewWeight;
|
||||
}
|
||||
|
||||
if (totalWeight > 0.0f) {
|
||||
// 平均颜色
|
||||
accumColor /= totalWeight;
|
||||
|
||||
// 保存采样点和颜色
|
||||
texPoints.emplace_back(texCoord);
|
||||
pointColors.push_back(accumColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用Delaunay三角剖分在面片内部生成均匀采样
|
||||
if (texPoints.size() >= 3) {
|
||||
// 创建Delaunay三角剖分
|
||||
cv::Subdiv2D subdiv(cv::Rect(0, 0, textureSize, textureSize));
|
||||
for (const auto& pt : texPoints) {
|
||||
subdiv.insert(cv::Point2f(pt.x * textureSize, pt.y * textureSize));
|
||||
}
|
||||
|
||||
std::vector<cv::Vec6f> triangleList;
|
||||
subdiv.getTriangleList(triangleList);
|
||||
|
||||
// 遍历三角形并填充
|
||||
for (const auto& t : triangleList) {
|
||||
cv::Point2f pt1(t[0], t[1]);
|
||||
cv::Point2f pt2(t[2], t[3]);
|
||||
cv::Point2f pt3(t[4], t[5]);
|
||||
|
||||
// 获取三角形内的像素
|
||||
std::vector<cv::Point> pixels = GetPixelsInTriangle(pt1, pt2, pt3, textureSize);
|
||||
|
||||
for (const auto& pixel : pixels) {
|
||||
if (pixel.x < 0 || pixel.x >= textureSize ||
|
||||
pixel.y < 0 || pixel.y >= textureSize) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Point2f texCoord((float)pixel.x / textureSize, (float)pixel.y / textureSize);
|
||||
|
||||
// ✅ 改进:使用距离加权插值(替代最近邻)
|
||||
cv::Vec3f weightedColor(0, 0, 0);
|
||||
float totalWeight = 0.0f;
|
||||
const float POWER = 2.0f; // 距离幂次(2表示平方反比)
|
||||
const float MAX_DIST_SQ = 100.0f / (textureSize * textureSize); // 最大距离平方
|
||||
|
||||
for (size_t i = 0; i < texPoints.size(); ++i) {
|
||||
float dx = texPoints[i].x - texCoord.x;
|
||||
float dy = texPoints[i].y - texCoord.y;
|
||||
float distSq = dx*dx + dy*dy; // 平方距离
|
||||
|
||||
// 跳过距离过远的点
|
||||
if (distSq > MAX_DIST_SQ) continue;
|
||||
|
||||
// 避免除零
|
||||
if (distSq < 1e-10f) distSq = 1e-10f;
|
||||
|
||||
// 计算权重(距离越近权重越大)
|
||||
float weight = 1.0f / std::pow(distSq, POWER/2.0f);
|
||||
|
||||
weightedColor += pointColors[i] * weight;
|
||||
totalWeight += weight;
|
||||
}
|
||||
|
||||
// ✅ 如果有权重,则计算加权平均颜色
|
||||
if (totalWeight > 0.0f) {
|
||||
cv::Vec3f newColor = weightedColor / totalWeight;
|
||||
|
||||
// 累加颜色和权重
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp atomic
|
||||
#endif
|
||||
weightAccum(pixel.y, pixel.x) += 1.0f;
|
||||
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp atomic
|
||||
#endif
|
||||
sampleCount(pixel.y, pixel.x) += 1;
|
||||
|
||||
#ifdef _USE_OPENMP
|
||||
#pragma omp critical
|
||||
#endif
|
||||
{
|
||||
colorAccum(pixel.y, pixel.x) += newColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 应用权重归一化
|
||||
DEBUG_EXTRA("Applying weight normalization for virtual faces");
|
||||
for (int y = 0; y < textureSize; ++y) {
|
||||
for (int x = 0; x < textureSize; ++x) {
|
||||
float weight = weightAccum(y, x);
|
||||
if (weight > 0.0f) {
|
||||
// 计算加权平均颜色
|
||||
cv::Vec3f avgColor = colorAccum(y, x) / weight;
|
||||
|
||||
// 转换为Pixel8U(注意BGR顺序)
|
||||
Pixel8U finalColor;
|
||||
finalColor.b = (unsigned char)cv::saturate_cast<uchar>(avgColor[0]); // B
|
||||
finalColor.g = (unsigned char)cv::saturate_cast<uchar>(avgColor[1]); // G
|
||||
finalColor.r = (unsigned char)cv::saturate_cast<uchar>(avgColor[2]); // R
|
||||
|
||||
textureAtlas(y, x) = finalColor;
|
||||
} else {
|
||||
// 保持背景色
|
||||
textureAtlas(y, x) = colEmpty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 填充缝隙和未采样区域
|
||||
DEBUG_EXTRA("Filling gaps in texture atlas for virtual faces");
|
||||
cv::Mat textureMat = (cv::Mat&)textureAtlas;
|
||||
cv::Mat1f weightMat = weightAccum;
|
||||
cv::Mat1i sampleMat = sampleCount;
|
||||
// FillTextureGapsMultiView(textureMat, weightMat, sampleMat, colEmpty);
|
||||
|
||||
// 8. 应用锐化(可选)
|
||||
if (fSharpnessWeight > 0) {
|
||||
// ApplySharpening(textureMat, fSharpnessWeight);
|
||||
}
|
||||
|
||||
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete");
|
||||
return textures;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user