|
|
|
@ -601,6 +601,7 @@ public: |
|
|
|
Pixel8U colEmpty, |
|
|
|
Pixel8U colEmpty, |
|
|
|
float fSharpnessWeight |
|
|
|
float fSharpnessWeight |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
cv::Vec3b ConvertBGRtoRGBIfNeeded(const cv::Vec3b& bgrColor); |
|
|
|
Point2f ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage); |
|
|
|
Point2f ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage); |
|
|
|
Point2f ProjectPointRobust(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage, float searchRadius = 0.02f); |
|
|
|
Point2f ProjectPointRobust(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage, float searchRadius = 0.02f); |
|
|
|
bool ValidateProjection(const Vertex& worldPoint, const Image& sourceImage, Point2f imgPoint, float maxReprojectionError = 1.5f); |
|
|
|
bool ValidateProjection(const Vertex& worldPoint, const Image& sourceImage, Point2f imgPoint, float maxReprojectionError = 1.5f); |
|
|
|
@ -13737,10 +13738,18 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge( |
|
|
|
const float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y(); |
|
|
|
const float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y(); |
|
|
|
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); |
|
|
|
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); |
|
|
|
|
|
|
|
|
|
|
|
// 创建目标纹理
|
|
|
|
// 创建目标纹理
|
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); |
|
|
|
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); |
|
|
|
// 注意:cv::Scalar 使用 BGR 顺序
|
|
|
|
|
|
|
|
|
|
|
|
// 修正1: 明确颜色通道顺序
|
|
|
|
|
|
|
|
// Pixel8U通常是RGB顺序,OpenCV使用BGR,我们需要保持一致
|
|
|
|
|
|
|
|
// 这里明确指定RGB顺序
|
|
|
|
|
|
|
|
DEBUG_EXTRA("设置背景色: RGB(%d,%d,%d) -> BGR(%d,%d,%d)", |
|
|
|
|
|
|
|
colEmpty.r, colEmpty.g, colEmpty.b, |
|
|
|
|
|
|
|
colEmpty.b, colEmpty.g, colEmpty.r); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 使用BGR顺序(OpenCV默认)
|
|
|
|
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("生成纹理图集: 尺寸=%dx%d, UV范围=[%.3f,%.3f]-[%.3f,%.3f]", |
|
|
|
DEBUG_EXTRA("生成纹理图集: 尺寸=%dx%d, UV范围=[%.3f,%.3f]-[%.3f,%.3f]", |
|
|
|
@ -13748,17 +13757,30 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge( |
|
|
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(), |
|
|
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(), |
|
|
|
uvBounds.ptMax.x(), uvBounds.ptMax.y()); |
|
|
|
uvBounds.ptMax.x(), uvBounds.ptMax.y()); |
|
|
|
|
|
|
|
|
|
|
|
// 添加调试信息
|
|
|
|
// 添加颜色通道调试
|
|
|
|
DEBUG_EXTRA("colEmpty: R=%d, G=%d, B=%d", colEmpty.r, colEmpty.g, colEmpty.b); |
|
|
|
DEBUG_EXTRA("颜色通道配置:"); |
|
|
|
DEBUG_EXTRA("OpenCV图像通道顺序: BGR"); |
|
|
|
DEBUG_EXTRA(" - colEmpty: R=%d, G=%d, B=%d", colEmpty.r, colEmpty.g, colEmpty.b); |
|
|
|
|
|
|
|
DEBUG_EXTRA(" - OpenCV通道顺序: BGR"); |
|
|
|
|
|
|
|
|
|
|
|
// 检查颜色通道顺序
|
|
|
|
// 检查源纹理颜色通道顺序
|
|
|
|
if (!sourceTextures.empty()) { |
|
|
|
if (!sourceTextures.empty()) { |
|
|
|
const Image8U3& firstTex = sourceTextures[0]; |
|
|
|
const Image8U3& firstTex = sourceTextures[0]; |
|
|
|
|
|
|
|
if (!firstTex.empty()) { |
|
|
|
cv::Vec3b firstPixel = firstTex.at<cv::Vec3b>(0, 0); |
|
|
|
cv::Vec3b firstPixel = firstTex.at<cv::Vec3b>(0, 0); |
|
|
|
DEBUG_EXTRA("源纹理第一个像素: B=%d, G=%d, R=%d", |
|
|
|
DEBUG_EXTRA("源纹理[0]第一个像素: B=%d, G=%d, R=%d (OpenCV BGR顺序)", |
|
|
|
|
|
|
|
firstPixel[0], firstPixel[1], firstPixel[2]); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查images中的颜色通道
|
|
|
|
|
|
|
|
if (!images.empty()) { |
|
|
|
|
|
|
|
const Image& firstImg = images[0]; |
|
|
|
|
|
|
|
if (!firstImg.image.empty()) { |
|
|
|
|
|
|
|
cv::Vec3b firstPixel = firstImg.image.at<cv::Vec3b>(0, 0); |
|
|
|
|
|
|
|
DEBUG_EXTRA("images[0]第一个像素: B=%d, G=%d, R=%d (OpenCV BGR顺序)", |
|
|
|
firstPixel[0], firstPixel[1], firstPixel[2]); |
|
|
|
firstPixel[0], firstPixel[1], firstPixel[2]); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 3. 统计信息
|
|
|
|
// 3. 统计信息
|
|
|
|
int processedFaces = 0; |
|
|
|
int processedFaces = 0; |
|
|
|
@ -13787,17 +13809,7 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge( |
|
|
|
// 获取面的几何信息
|
|
|
|
// 获取面的几何信息
|
|
|
|
const TexCoord* meshUVs = &scene.mesh.faceTexcoords[faceID * 3]; |
|
|
|
const TexCoord* meshUVs = &scene.mesh.faceTexcoords[faceID * 3]; |
|
|
|
const Face& face = scene.mesh.faces[faceID]; |
|
|
|
const Face& face = scene.mesh.faces[faceID]; |
|
|
|
|
|
|
|
const Image& sourceImage = images[idxView]; |
|
|
|
// 获取源纹理信息
|
|
|
|
|
|
|
|
const TexCoord* srcUVs = &sourceTexcoords[faceID * 3]; |
|
|
|
|
|
|
|
const TexIndex textureIdx = sourceTexindices.empty() ? 0 : sourceTexindices[faceID]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (textureIdx >= sourceTextures.size()) { |
|
|
|
|
|
|
|
failedFaces++; |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const Image8U3& sourceTexture = sourceTextures[textureIdx]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算面的UV边界
|
|
|
|
// 计算面的UV边界
|
|
|
|
AABB2f faceBounds(true); |
|
|
|
AABB2f faceBounds(true); |
|
|
|
@ -13841,19 +13853,23 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge( |
|
|
|
vertices[face[1]] * barycentric.y + |
|
|
|
vertices[face[1]] * barycentric.y + |
|
|
|
vertices[face[2]] * barycentric.z; |
|
|
|
vertices[face[2]] * barycentric.z; |
|
|
|
|
|
|
|
|
|
|
|
// 方案A:从原始图像采样
|
|
|
|
// 从原始图像采样
|
|
|
|
Point2f imgPoint = ProjectPointWithAutoCorrection(images[idxView].camera, worldPoint, images[idxView]); |
|
|
|
Point2f imgPoint = sourceImage.camera.ProjectPointP(worldPoint); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 确保在图像范围内
|
|
|
|
|
|
|
|
imgPoint.x = CLAMP(imgPoint.x, 0.0f, (float)(sourceImage.image.cols - 1)); |
|
|
|
|
|
|
|
imgPoint.y = CLAMP(imgPoint.y, 0.0f, (float)(sourceImage.image.rows - 1)); |
|
|
|
|
|
|
|
|
|
|
|
if (imgPoint.x < 0 || imgPoint.x >= images[idxView].image.cols || |
|
|
|
if (imgPoint.x < 0 || imgPoint.x >= sourceImage.image.cols || |
|
|
|
imgPoint.y < 0 || imgPoint.y >= images[idxView].image.rows) { |
|
|
|
imgPoint.y < 0 || imgPoint.y >= sourceImage.image.rows) { |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 修正:使用双线性插值采样
|
|
|
|
// 双线性插值采样
|
|
|
|
const int x0 = (int)floor(imgPoint.x); |
|
|
|
const int x0 = (int)imgPoint.x; |
|
|
|
const int y0 = (int)floor(imgPoint.y); |
|
|
|
const int y0 = (int)imgPoint.y; |
|
|
|
const int x1 = std::min(x0 + 1, images[idxView].image.cols - 1); |
|
|
|
const int x1 = std::min(x0 + 1, sourceImage.image.cols - 1); |
|
|
|
const int y1 = std::min(y0 + 1, images[idxView].image.rows - 1); |
|
|
|
const int y1 = std::min(y0 + 1, sourceImage.image.rows - 1); |
|
|
|
|
|
|
|
|
|
|
|
const float fx = imgPoint.x - x0; |
|
|
|
const float fx = imgPoint.x - x0; |
|
|
|
const float fy = imgPoint.y - y0; |
|
|
|
const float fy = imgPoint.y - y0; |
|
|
|
@ -13861,30 +13877,39 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge( |
|
|
|
const float fy1 = 1.0f - fy; |
|
|
|
const float fy1 = 1.0f - fy; |
|
|
|
|
|
|
|
|
|
|
|
// 采样四个点的颜色
|
|
|
|
// 采样四个点的颜色
|
|
|
|
const cv::Vec3b& c00 = images[idxView].image.at<cv::Vec3b>(y0, x0); |
|
|
|
const cv::Vec3b& c00 = sourceImage.image.at<cv::Vec3b>(y0, x0); |
|
|
|
const cv::Vec3b& c01 = images[idxView].image.at<cv::Vec3b>(y0, x1); |
|
|
|
const cv::Vec3b& c01 = sourceImage.image.at<cv::Vec3b>(y0, x1); |
|
|
|
const cv::Vec3b& c10 = images[idxView].image.at<cv::Vec3b>(y1, x0); |
|
|
|
const cv::Vec3b& c10 = sourceImage.image.at<cv::Vec3b>(y1, x0); |
|
|
|
const cv::Vec3b& c11 = images[idxView].image.at<cv::Vec3b>(y1, x1); |
|
|
|
const cv::Vec3b& c11 = sourceImage.image.at<cv::Vec3b>(y1, x1); |
|
|
|
|
|
|
|
|
|
|
|
// 双线性插值
|
|
|
|
// 双线性插值
|
|
|
|
const cv::Vec3b color = |
|
|
|
cv::Vec3b sampledColor = |
|
|
|
c00 * (fx1 * fy1) + |
|
|
|
c00 * (fx1 * fy1) + |
|
|
|
c01 * (fx * fy1) + |
|
|
|
c01 * (fx * fy1) + |
|
|
|
c10 * (fx1 * fy) + |
|
|
|
c10 * (fx1 * fy) + |
|
|
|
c11 * (fx * fy); |
|
|
|
c11 * (fx * fy); |
|
|
|
|
|
|
|
|
|
|
|
// 注意:OpenCV是BGR顺序,而Pixel8U通常是RGB顺序
|
|
|
|
// 修正2: 统一颜色通道处理
|
|
|
|
// 这里我们需要确保颜色通道正确
|
|
|
|
// 方法1: 保持BGR顺序(与OpenCV一致)
|
|
|
|
|
|
|
|
// 如果最终显示为RGB,则需要在显示时转换
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 方法2: 转换为RGB顺序存储
|
|
|
|
|
|
|
|
cv::Vec3b finalColor = ConvertBGRtoRGBIfNeeded(sampledColor); |
|
|
|
|
|
|
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#pragma omp critical |
|
|
|
#pragma omp critical |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
{ |
|
|
|
{ |
|
|
|
// 方案1:直接使用BGR顺序(OpenCV默认)
|
|
|
|
// 使用转换后的颜色
|
|
|
|
// textureAtlas.at<cv::Vec3b>(y, x) = color;
|
|
|
|
textureAtlas.at<cv::Vec3b>(y, x) = finalColor; |
|
|
|
|
|
|
|
|
|
|
|
// 方案2:交换B和R通道(如果颜色偏蓝,说明B和R反了)
|
|
|
|
// 调试:记录前几个像素的颜色
|
|
|
|
// 将BGR转换为RGB
|
|
|
|
if (processedFaces == 0 && faceSampledPixels < 3) { |
|
|
|
textureAtlas.at<cv::Vec3b>(y, x) = cv::Vec3b(color[2], color[1], color[0]); |
|
|
|
DEBUG_EXTRA("像素[%d,%d] 颜色: 源图像BGR=(%d,%d,%d), 目标RGB=(%d,%d,%d)", |
|
|
|
|
|
|
|
x, y, |
|
|
|
|
|
|
|
sampledColor[0], sampledColor[1], sampledColor[2], |
|
|
|
|
|
|
|
finalColor[0], finalColor[1], finalColor[2]); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
faceSampledPixels++; |
|
|
|
faceSampledPixels++; |
|
|
|
@ -13904,17 +13929,58 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge( |
|
|
|
|
|
|
|
|
|
|
|
// 5. 填充空洞
|
|
|
|
// 5. 填充空洞
|
|
|
|
if (processedFaces > 0 && sampledPixels > 0) { |
|
|
|
if (processedFaces > 0 && sampledPixels > 0) { |
|
|
|
// FillTextureGaps(textureAtlas, scene.mesh.faceTexcoords, (FIndex)scene.mesh.faces.size(), textureSize, colEmpty);
|
|
|
|
// FillTextureGapsWithColorCorrection(textureAtlas, scene.mesh.faceTexcoords,
|
|
|
|
|
|
|
|
// (FIndex)scene.mesh.faces.size(), textureSize, colEmpty);
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 6. 锐化处理
|
|
|
|
// 6. 锐化处理
|
|
|
|
if (fSharpnessWeight > 0) { |
|
|
|
if (fSharpnessWeight > 0 && sampledPixels > 0) { |
|
|
|
// ApplySharpening(textureAtlas, fSharpnessWeight);
|
|
|
|
// ApplySharpeningWithColorBalance(textureAtlas, fSharpnessWeight);
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 7. 最终颜色检查
|
|
|
|
|
|
|
|
if (!textureAtlas.empty()) { |
|
|
|
|
|
|
|
cv::Scalar mean = cv::mean(textureAtlas); |
|
|
|
|
|
|
|
DEBUG_EXTRA("最终纹理平均颜色: B=%.1f, G=%.1f, R=%.1f", |
|
|
|
|
|
|
|
mean[0], mean[1], mean[2]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查颜色通道
|
|
|
|
|
|
|
|
int rgbCount[3] = {0, 0, 0}; |
|
|
|
|
|
|
|
int totalSamples = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < textureAtlas.rows && y < 10; y++) { |
|
|
|
|
|
|
|
for (int x = 0; x < textureAtlas.cols && x < 10; x++) { |
|
|
|
|
|
|
|
cv::Vec3b pixel = textureAtlas.at<cv::Vec3b>(y, x); |
|
|
|
|
|
|
|
if (pixel != cv::Vec3b(colEmpty.b, colEmpty.g, colEmpty.r)) { |
|
|
|
|
|
|
|
rgbCount[0] += pixel[0]; |
|
|
|
|
|
|
|
rgbCount[1] += pixel[1]; |
|
|
|
|
|
|
|
rgbCount[2] += pixel[2]; |
|
|
|
|
|
|
|
totalSamples++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (totalSamples > 0) { |
|
|
|
|
|
|
|
DEBUG_EXTRA("前10x10像素平均颜色: B=%d, G=%d, R=%d", |
|
|
|
|
|
|
|
rgbCount[0]/totalSamples, rgbCount[1]/totalSamples, rgbCount[2]/totalSamples); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return textures; |
|
|
|
return textures; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cv::Vec3b MeshTexture::ConvertBGRtoRGBIfNeeded(const cv::Vec3b& bgrColor) { |
|
|
|
|
|
|
|
// 根据配置决定是否转换
|
|
|
|
|
|
|
|
// 如果OpenCV图像是BGR顺序,而我们希望存储为RGB,则交换B和R
|
|
|
|
|
|
|
|
static bool bConvertBGRtoRGB = true; // 可根据需要调整
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (bConvertBGRtoRGB) { |
|
|
|
|
|
|
|
return cv::Vec3b(bgrColor[2], bgrColor[1], bgrColor[0]); // BGR->RGB
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return bgrColor; // 保持BGR
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Point2f MeshTexture::ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage) { |
|
|
|
Point2f MeshTexture::ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage) { |
|
|
|
Point2f imgPoint = camera.ProjectPointP(worldPoint); |
|
|
|
Point2f imgPoint = camera.ProjectPointP(worldPoint); |
|
|
|
|
|
|
|
|
|
|
|
|