|
|
|
|
@ -593,6 +593,8 @@ public:
@@ -593,6 +593,8 @@ public:
|
|
|
|
|
unsigned nTextureSizeMultiple, |
|
|
|
|
Pixel8U colEmpty, |
|
|
|
|
float fSharpnessWeight); |
|
|
|
|
void ApplyGlobalColorCorrection(Image8U3& texture, Pixel8U colEmpty, float strength); |
|
|
|
|
void ApplySoftSharpening(Image8U3& texture, float strength, Pixel8U colEmpty); |
|
|
|
|
Mesh::Image8U3Arr GenerateTextureAtlasFromUV( |
|
|
|
|
const Mesh::Image8U3Arr& sourceTextures, // 已有纹理数组
|
|
|
|
|
const Mesh::TexCoordArr& sourceTexcoords, // 已有UV坐标
|
|
|
|
|
@ -13711,7 +13713,7 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge(
@@ -13711,7 +13713,7 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge(
|
|
|
|
|
Pixel8U colEmpty, |
|
|
|
|
float fSharpnessWeight) |
|
|
|
|
{ |
|
|
|
|
DEBUG_EXTRA("GenerateTextureAtlasWith3DBridge - 使用3D几何坐标作为桥梁"); |
|
|
|
|
DEBUG_EXTRA("GenerateTextureAtlasWith3DBridge - 使用3D几何坐标作为桥梁,修复白平衡问题"); |
|
|
|
|
|
|
|
|
|
// 1. 分析外部UV布局
|
|
|
|
|
AABB2f uvBounds(true); |
|
|
|
|
@ -13742,52 +13744,112 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge(
@@ -13742,52 +13744,112 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge(
|
|
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
|
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); |
|
|
|
|
|
|
|
|
|
// 修正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); |
|
|
|
|
// 使用统一的背景色设置
|
|
|
|
|
DEBUG_EXTRA("设置背景色: RGB(%d,%d,%d)", colEmpty.r, colEmpty.g, colEmpty.b); |
|
|
|
|
|
|
|
|
|
// 使用BGR顺序(OpenCV默认)
|
|
|
|
|
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
// 注意:Image8U3的setTo使用cv::Scalar,是BGR顺序
|
|
|
|
|
// 但colEmpty是RGB顺序,需要转换
|
|
|
|
|
cv::Scalar cvEmpty(colEmpty.b, colEmpty.g, colEmpty.r); |
|
|
|
|
textureAtlas.setTo(cvEmpty); |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("生成纹理图集: 尺寸=%dx%d, UV范围=[%.3f,%.3f]-[%.3f,%.3f]", |
|
|
|
|
textureSize, textureSize, |
|
|
|
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(), |
|
|
|
|
uvBounds.ptMax.x(), uvBounds.ptMax.y()); |
|
|
|
|
|
|
|
|
|
// 添加颜色通道调试
|
|
|
|
|
DEBUG_EXTRA("颜色通道配置:"); |
|
|
|
|
DEBUG_EXTRA(" - colEmpty: R=%d, G=%d, B=%d", colEmpty.r, colEmpty.g, colEmpty.b); |
|
|
|
|
DEBUG_EXTRA(" - OpenCV通道顺序: BGR"); |
|
|
|
|
// 2. 为每个视图创建颜色统计
|
|
|
|
|
std::vector<cv::Vec3d> viewColorSums(images.size(), cv::Vec3d(0, 0, 0)); |
|
|
|
|
std::vector<int> viewPixelCounts(images.size(), 0); |
|
|
|
|
std::vector<std::vector<cv::Vec3b>> viewColorSamples(images.size()); |
|
|
|
|
|
|
|
|
|
// 3. 第一次遍历:收集每个视图的颜色统计
|
|
|
|
|
DEBUG_EXTRA("第一次遍历:收集视图颜色统计"); |
|
|
|
|
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
|
#endif |
|
|
|
|
for (int_t idxFace = 0; idxFace < (int_t)scene.mesh.faces.size(); ++idxFace) { |
|
|
|
|
const FIndex faceID = (FIndex)idxFace; |
|
|
|
|
const Label label = faceLabels[faceID]; |
|
|
|
|
|
|
|
|
|
if (label == 0) continue; |
|
|
|
|
const IIndex idxView = label - 1; |
|
|
|
|
if (idxView >= images.size()) continue; |
|
|
|
|
|
|
|
|
|
const Image& sourceImage = images[idxView]; |
|
|
|
|
const TexCoord* meshUVs = &scene.mesh.faceTexcoords[faceID * 3]; |
|
|
|
|
const Face& face = scene.mesh.faces[faceID]; |
|
|
|
|
|
|
|
|
|
// 在面的中心采样几个点
|
|
|
|
|
for (int i = 0; i < 3; ++i) { // 采样三个顶点
|
|
|
|
|
const Vertex& worldPoint = vertices[face[i]]; |
|
|
|
|
|
|
|
|
|
// 检查3D点是否在相机前方
|
|
|
|
|
if (!sourceImage.camera.IsInFront(worldPoint)) { |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Point2f imgPoint = sourceImage.camera.ProjectPointP(worldPoint); |
|
|
|
|
|
|
|
|
|
// 检查源纹理颜色通道顺序
|
|
|
|
|
if (!sourceTextures.empty()) { |
|
|
|
|
const Image8U3& firstTex = sourceTextures[0]; |
|
|
|
|
if (!firstTex.empty()) { |
|
|
|
|
cv::Vec3b firstPixel = firstTex.at<cv::Vec3b>(0, 0); |
|
|
|
|
DEBUG_EXTRA("源纹理[0]第一个像素: B=%d, G=%d, R=%d (OpenCV BGR顺序)", |
|
|
|
|
firstPixel[0], firstPixel[1], firstPixel[2]); |
|
|
|
|
// 确保在图像范围内
|
|
|
|
|
if (imgPoint.x < 0 || imgPoint.x >= sourceImage.image.cols || |
|
|
|
|
imgPoint.y < 0 || imgPoint.y >= sourceImage.image.rows) { |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 采样图像
|
|
|
|
|
Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint); |
|
|
|
|
|
|
|
|
|
// 存储采样颜色
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
|
#pragma omp critical |
|
|
|
|
#endif |
|
|
|
|
{ |
|
|
|
|
viewColorSamples[idxView].push_back(cv::Vec3b(sampledColor.b, sampledColor.g, sampledColor.r)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 检查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]); |
|
|
|
|
// 4. 计算每个视图的颜色调整因子
|
|
|
|
|
std::vector<cv::Vec3d> viewColorAdjustments(images.size(), cv::Vec3d(1.0, 1.0, 1.0)); |
|
|
|
|
cv::Vec3d globalMean(0, 0, 0); |
|
|
|
|
int globalSampleCount = 0; |
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < images.size(); ++i) { |
|
|
|
|
if (viewColorSamples[i].empty()) continue; |
|
|
|
|
|
|
|
|
|
cv::Vec3d sum(0, 0, 0); |
|
|
|
|
for (const auto& pixel : viewColorSamples[i]) { |
|
|
|
|
sum[0] += pixel[0]; // B
|
|
|
|
|
sum[1] += pixel[1]; // G
|
|
|
|
|
sum[2] += pixel[2]; // R
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cv::Vec3d mean = sum / (double)viewColorSamples[i].size(); |
|
|
|
|
globalMean += sum; |
|
|
|
|
globalSampleCount += viewColorSamples[i].size(); |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("视图 %zu: 平均颜色 B=%.1f, G=%.1f, R=%.1f, 样本数=%zu", |
|
|
|
|
i, mean[0], mean[1], mean[2], viewColorSamples[i].size()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 3. 统计信息
|
|
|
|
|
// 计算全局平均颜色
|
|
|
|
|
if (globalSampleCount > 0) { |
|
|
|
|
globalMean /= globalSampleCount; |
|
|
|
|
DEBUG_EXTRA("全局平均颜色: B=%.1f, G=%.1f, R=%.1f", |
|
|
|
|
globalMean[0], globalMean[1], globalMean[2]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 5. 采样纹理
|
|
|
|
|
DEBUG_EXTRA("第二次遍历:采样纹理并应用颜色校正"); |
|
|
|
|
|
|
|
|
|
cv::Mat1f weightAccum(textureSize, textureSize, 0.0f); |
|
|
|
|
cv::Mat3f colorAccum(textureSize, textureSize, cv::Vec3f(0, 0, 0)); |
|
|
|
|
|
|
|
|
|
int processedFaces = 0; |
|
|
|
|
int sampledPixels = 0; |
|
|
|
|
int failedFaces = 0; |
|
|
|
|
|
|
|
|
|
// 4. 为每个面采样
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
|
#pragma omp parallel for schedule(dynamic) reduction(+:processedFaces, sampledPixels, failedFaces) |
|
|
|
|
#endif |
|
|
|
|
@ -13806,7 +13868,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge(
@@ -13806,7 +13868,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge(
|
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 获取面的几何信息
|
|
|
|
|
const TexCoord* meshUVs = &scene.mesh.faceTexcoords[faceID * 3]; |
|
|
|
|
const Face& face = scene.mesh.faces[faceID]; |
|
|
|
|
const Image& sourceImage = images[idxView]; |
|
|
|
|
@ -13817,13 +13878,11 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge(
@@ -13817,13 +13878,11 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge(
|
|
|
|
|
faceBounds.InsertFull(meshUVs[i]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 转换为像素坐标
|
|
|
|
|
int startX = (int)(faceBounds.ptMin.x() * textureSize); |
|
|
|
|
int startY = (int)(faceBounds.ptMin.y() * textureSize); |
|
|
|
|
int endX = (int)(faceBounds.ptMax.x() * textureSize); |
|
|
|
|
int endY = (int)(faceBounds.ptMax.y() * textureSize); |
|
|
|
|
|
|
|
|
|
// 边界检查
|
|
|
|
|
startX = std::max(0, std::min(startX, textureSize - 1)); |
|
|
|
|
startY = std::max(0, std::min(startY, textureSize - 1)); |
|
|
|
|
endX = std::max(0, std::min(endX, textureSize - 1)); |
|
|
|
|
@ -13853,63 +13912,78 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge(
@@ -13853,63 +13912,78 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge(
|
|
|
|
|
vertices[face[1]] * barycentric.y + |
|
|
|
|
vertices[face[2]] * barycentric.z; |
|
|
|
|
|
|
|
|
|
// 检查3D点是否在相机前方
|
|
|
|
|
if (!sourceImage.camera.IsInFront(worldPoint)) { |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 从原始图像采样
|
|
|
|
|
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 >= sourceImage.image.cols || |
|
|
|
|
imgPoint.y < 0 || imgPoint.y >= sourceImage.image.rows) { |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 双线性插值采样
|
|
|
|
|
const int x0 = (int)imgPoint.x; |
|
|
|
|
const int y0 = (int)imgPoint.y; |
|
|
|
|
const int x1 = std::min(x0 + 1, sourceImage.image.cols - 1); |
|
|
|
|
const int y1 = std::min(y0 + 1, sourceImage.image.rows - 1); |
|
|
|
|
|
|
|
|
|
const float fx = imgPoint.x - x0; |
|
|
|
|
const float fy = imgPoint.y - y0; |
|
|
|
|
const float fx1 = 1.0f - fx; |
|
|
|
|
const float fy1 = 1.0f - fy; |
|
|
|
|
|
|
|
|
|
// 采样四个点的颜色
|
|
|
|
|
const cv::Vec3b& c00 = sourceImage.image.at<cv::Vec3b>(y0, x0); |
|
|
|
|
const cv::Vec3b& c01 = sourceImage.image.at<cv::Vec3b>(y0, x1); |
|
|
|
|
const cv::Vec3b& c10 = sourceImage.image.at<cv::Vec3b>(y1, x0); |
|
|
|
|
const cv::Vec3b& c11 = sourceImage.image.at<cv::Vec3b>(y1, x1); |
|
|
|
|
|
|
|
|
|
// 双线性插值
|
|
|
|
|
cv::Vec3b sampledColor = |
|
|
|
|
c00 * (fx1 * fy1) + |
|
|
|
|
c01 * (fx * fy1) + |
|
|
|
|
c10 * (fx1 * fy) + |
|
|
|
|
c11 * (fx * fy); |
|
|
|
|
|
|
|
|
|
// 修正2: 统一颜色通道处理
|
|
|
|
|
// 方法1: 保持BGR顺序(与OpenCV一致)
|
|
|
|
|
// 如果最终显示为RGB,则需要在显示时转换
|
|
|
|
|
|
|
|
|
|
// 方法2: 转换为RGB顺序存储
|
|
|
|
|
cv::Vec3b finalColor = ConvertBGRtoRGBIfNeeded(sampledColor); |
|
|
|
|
// 采样图像
|
|
|
|
|
Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint); |
|
|
|
|
|
|
|
|
|
// 转换为BGR顺序用于颜色累加
|
|
|
|
|
cv::Vec3f bgrColor( |
|
|
|
|
sampledColor.b, // B
|
|
|
|
|
sampledColor.g, // G
|
|
|
|
|
sampledColor.r // R
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// 获取颜色调整因子
|
|
|
|
|
cv::Vec3d adjust(1.0, 1.0, 1.0); |
|
|
|
|
if (globalSampleCount > 0) { |
|
|
|
|
// 计算颜色调整因子
|
|
|
|
|
double avgGray = (globalMean[0] + globalMean[1] + globalMean[2]) / 3.0; |
|
|
|
|
if (avgGray > 0) { |
|
|
|
|
adjust[0] = globalMean[0] / avgGray; // B调整因子
|
|
|
|
|
adjust[1] = globalMean[1] / avgGray; // G调整因子
|
|
|
|
|
adjust[2] = globalMean[2] / avgGray; // R调整因子
|
|
|
|
|
|
|
|
|
|
// 限制调整幅度
|
|
|
|
|
const double maxAdjust = 1.2; |
|
|
|
|
const double minAdjust = 0.833; // 1/1.2
|
|
|
|
|
|
|
|
|
|
adjust[0] = std::max(minAdjust, std::min(adjust[0], maxAdjust)); |
|
|
|
|
adjust[1] = std::max(minAdjust, std::min(adjust[1], maxAdjust)); |
|
|
|
|
adjust[2] = std::max(minAdjust, std::min(adjust[2], maxAdjust)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 应用颜色调整
|
|
|
|
|
cv::Vec3f adjustedColor = bgrColor; |
|
|
|
|
adjustedColor[0] *= adjust[0]; // B通道
|
|
|
|
|
adjustedColor[1] *= adjust[1]; // G通道
|
|
|
|
|
adjustedColor[2] *= adjust[2]; // R通道
|
|
|
|
|
|
|
|
|
|
// 限制颜色范围
|
|
|
|
|
adjustedColor[0] = std::max(0.0f, std::min(255.0f, adjustedColor[0])); |
|
|
|
|
adjustedColor[1] = std::max(0.0f, std::min(255.0f, adjustedColor[1])); |
|
|
|
|
adjustedColor[2] = std::max(0.0f, std::min(255.0f, adjustedColor[2])); |
|
|
|
|
|
|
|
|
|
// 累加颜色和权重
|
|
|
|
|
float weight = 1.0f; |
|
|
|
|
float& w = weightAccum(y, x); |
|
|
|
|
cv::Vec3f& c = colorAccum(y, x); |
|
|
|
|
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
|
#pragma omp atomic |
|
|
|
|
#endif |
|
|
|
|
w += weight; |
|
|
|
|
|
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
|
#pragma omp critical |
|
|
|
|
#endif |
|
|
|
|
{ |
|
|
|
|
// 使用转换后的颜色
|
|
|
|
|
textureAtlas.at<cv::Vec3b>(y, x) = finalColor; |
|
|
|
|
|
|
|
|
|
// 调试:记录前几个像素的颜色
|
|
|
|
|
if (processedFaces == 0 && faceSampledPixels < 3) { |
|
|
|
|
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]); |
|
|
|
|
} |
|
|
|
|
c[0] += adjustedColor[0] * weight; |
|
|
|
|
c[1] += adjustedColor[1] * weight; |
|
|
|
|
c[2] += adjustedColor[2] * weight; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
faceSampledPixels++; |
|
|
|
|
@ -13927,48 +14001,177 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge(
@@ -13927,48 +14001,177 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasWith3DBridge(
|
|
|
|
|
DEBUG_EXTRA("纹理采样完成: 成功 %d 个面, 失败 %d 个面, 采样 %d 像素", |
|
|
|
|
processedFaces, failedFaces, sampledPixels); |
|
|
|
|
|
|
|
|
|
// 5. 填充空洞
|
|
|
|
|
// 6. 应用权重归一化
|
|
|
|
|
DEBUG_EXTRA("应用权重归一化"); |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
// 从BGR转回RGB顺序
|
|
|
|
|
Pixel8U finalColor; |
|
|
|
|
finalColor.r = (unsigned char)cv::saturate_cast<uchar>(avgColor[2]); // R
|
|
|
|
|
finalColor.g = (unsigned char)cv::saturate_cast<uchar>(avgColor[1]); // G
|
|
|
|
|
finalColor.b = (unsigned char)cv::saturate_cast<uchar>(avgColor[0]); // B
|
|
|
|
|
|
|
|
|
|
textureAtlas(y, x) = finalColor; |
|
|
|
|
} else { |
|
|
|
|
textureAtlas(y, x) = colEmpty; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 7. 应用全局颜色校正
|
|
|
|
|
if (processedFaces > 0 && sampledPixels > 0) { |
|
|
|
|
// FillTextureGapsWithColorCorrection(textureAtlas, scene.mesh.faceTexcoords,
|
|
|
|
|
// (FIndex)scene.mesh.faces.size(), textureSize, colEmpty);
|
|
|
|
|
ApplyGlobalColorCorrection(textureAtlas, colEmpty, 0.8f); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 6. 锐化处理
|
|
|
|
|
// 8. 锐化处理
|
|
|
|
|
if (fSharpnessWeight > 0 && sampledPixels > 0) { |
|
|
|
|
// ApplySharpeningWithColorBalance(textureAtlas, fSharpnessWeight);
|
|
|
|
|
DEBUG_EXTRA("应用锐化处理"); |
|
|
|
|
ApplySoftSharpening(textureAtlas, fSharpnessWeight, colEmpty); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 7. 最终颜色检查
|
|
|
|
|
// 9. 最终颜色检查
|
|
|
|
|
if (!textureAtlas.empty()) { |
|
|
|
|
cv::Scalar mean = cv::mean(textureAtlas); |
|
|
|
|
DEBUG_EXTRA("最终纹理平均颜色: B=%.1f, G=%.1f, R=%.1f", |
|
|
|
|
DEBUG_EXTRA("最终纹理平均颜色: B=%.1f, G=%.1f, R=%.1f (OpenCV BGR顺序)", |
|
|
|
|
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++; |
|
|
|
|
double rSum = 0, gSum = 0, bSum = 0; |
|
|
|
|
int count = 0; |
|
|
|
|
|
|
|
|
|
for (int y = 0; y < textureAtlas.rows; ++y) { |
|
|
|
|
for (int x = 0; x < textureAtlas.cols; ++x) { |
|
|
|
|
Pixel8U pixel = textureAtlas(y, x); |
|
|
|
|
if (pixel != colEmpty) { |
|
|
|
|
rSum += pixel.r; |
|
|
|
|
gSum += pixel.g; |
|
|
|
|
bSum += pixel.b; |
|
|
|
|
count++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (totalSamples > 0) { |
|
|
|
|
DEBUG_EXTRA("前10x10像素平均颜色: B=%d, G=%d, R=%d", |
|
|
|
|
rgbCount[0]/totalSamples, rgbCount[1]/totalSamples, rgbCount[2]/totalSamples); |
|
|
|
|
if (count > 0) { |
|
|
|
|
DEBUG_EXTRA("有效像素RGB平均值: R=%.1f, G=%.1f, B=%.1f", |
|
|
|
|
rSum / count, gSum / count, bSum / count); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return textures; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void MeshTexture::ApplyGlobalColorCorrection(Image8U3& texture, Pixel8U colEmpty, float strength) { |
|
|
|
|
if (strength <= 0) return; |
|
|
|
|
|
|
|
|
|
cv::Mat& textureMat = (cv::Mat&)texture; |
|
|
|
|
|
|
|
|
|
// 统计非背景像素
|
|
|
|
|
cv::Vec3d sum(0, 0, 0); |
|
|
|
|
int count = 0; |
|
|
|
|
|
|
|
|
|
for (int y = 0; y < texture.rows; ++y) { |
|
|
|
|
for (int x = 0; x < texture.cols; ++x) { |
|
|
|
|
Pixel8U pixel = texture(y, x); |
|
|
|
|
if (pixel != colEmpty) { |
|
|
|
|
// texture是RGB顺序
|
|
|
|
|
sum[0] += pixel.r; // R
|
|
|
|
|
sum[1] += pixel.g; // G
|
|
|
|
|
sum[2] += pixel.b; // B
|
|
|
|
|
count++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (count == 0) return; |
|
|
|
|
|
|
|
|
|
cv::Vec3d mean = sum / count; |
|
|
|
|
double avgGray = (mean[0] + mean[1] + mean[2]) / 3.0; |
|
|
|
|
|
|
|
|
|
// 计算增益因子
|
|
|
|
|
double gainR = 1.0, gainG = 1.0, gainB = 1.0; |
|
|
|
|
if (avgGray > 0) { |
|
|
|
|
gainR = avgGray / mean[0]; |
|
|
|
|
gainG = avgGray / mean[1]; |
|
|
|
|
gainB = avgGray / mean[2]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 限制调整幅度
|
|
|
|
|
gainR = std::max(0.9, std::min(gainR, 1.1)); |
|
|
|
|
gainG = std::max(0.9, std::min(gainG, 1.1)); |
|
|
|
|
gainB = std::max(0.9, std::min(gainB, 1.1)); |
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("全局颜色校正: 平均颜色 R=%.1f, G=%.1f, B=%.1f", mean[0], mean[1], mean[2]); |
|
|
|
|
DEBUG_EXTRA("全局颜色校正: 增益因子 R=%.3f, G=%.3f, B=%.3f", gainR, gainG, gainB); |
|
|
|
|
|
|
|
|
|
// 应用颜色校正
|
|
|
|
|
for (int y = 0; y < texture.rows; ++y) { |
|
|
|
|
for (int x = 0; x < texture.cols; ++x) { |
|
|
|
|
Pixel8U& pixel = texture(y, x); |
|
|
|
|
if (pixel == colEmpty) continue; |
|
|
|
|
|
|
|
|
|
// 线性混合原始颜色和校正颜色
|
|
|
|
|
double r = pixel.r * (1.0 - strength) + pixel.r * gainR * strength; |
|
|
|
|
double g = pixel.g * (1.0 - strength) + pixel.g * gainG * strength; |
|
|
|
|
double b = pixel.b * (1.0 - strength) + pixel.b * gainB * strength; |
|
|
|
|
|
|
|
|
|
pixel.r = (unsigned char)cv::saturate_cast<uchar>(r); |
|
|
|
|
pixel.g = (unsigned char)cv::saturate_cast<uchar>(g); |
|
|
|
|
pixel.b = (unsigned char)cv::saturate_cast<uchar>(b); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void MeshTexture::ApplySoftSharpening(Image8U3& texture, float strength, Pixel8U colEmpty) { |
|
|
|
|
if (strength <= 0) return; |
|
|
|
|
|
|
|
|
|
cv::Mat& textureMat = (cv::Mat&)texture; |
|
|
|
|
|
|
|
|
|
// 使用轻微的高斯模糊
|
|
|
|
|
cv::Mat blurred; |
|
|
|
|
cv::GaussianBlur(textureMat, blurred, cv::Size(3, 3), 0.5); |
|
|
|
|
|
|
|
|
|
// 计算细节层
|
|
|
|
|
cv::Mat detail = textureMat.clone(); |
|
|
|
|
for (int y = 0; y < texture.rows; ++y) { |
|
|
|
|
for (int x = 0; x < texture.cols; ++x) { |
|
|
|
|
Pixel8U pixel = texture(y, x); |
|
|
|
|
if (pixel == colEmpty) { |
|
|
|
|
detail.at<cv::Vec3b>(y, x) = cv::Vec3b(colEmpty.b, colEmpty.g, colEmpty.r); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cv::Vec3b& detailPixel = detail.at<cv::Vec3b>(y, x); |
|
|
|
|
cv::Vec3b blurPixel = blurred.at<cv::Vec3b>(y, x); |
|
|
|
|
|
|
|
|
|
// 计算细节
|
|
|
|
|
detailPixel[0] = cv::saturate_cast<uchar>(detailPixel[0] - blurPixel[0] + 128); |
|
|
|
|
detailPixel[1] = cv::saturate_cast<uchar>(detailPixel[1] - blurPixel[1] + 128); |
|
|
|
|
detailPixel[2] = cv::saturate_cast<uchar>(detailPixel[2] - blurPixel[2] + 128); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 轻微锐化
|
|
|
|
|
float alpha = 0.5f * strength; |
|
|
|
|
for (int y = 0; y < texture.rows; ++y) { |
|
|
|
|
for (int x = 0; x < texture.cols; ++x) { |
|
|
|
|
Pixel8U pixel = texture(y, x); |
|
|
|
|
if (pixel == colEmpty) continue; |
|
|
|
|
|
|
|
|
|
cv::Vec3b& pixelVec = textureMat.at<cv::Vec3b>(y, x); |
|
|
|
|
cv::Vec3b detailPixel = detail.at<cv::Vec3b>(y, x); |
|
|
|
|
|
|
|
|
|
// 锐化
|
|
|
|
|
pixelVec[0] = cv::saturate_cast<uchar>(pixelVec[0] + (detailPixel[0] - 128) * alpha); |
|
|
|
|
pixelVec[1] = cv::saturate_cast<uchar>(pixelVec[1] + (detailPixel[1] - 128) * alpha); |
|
|
|
|
pixelVec[2] = cv::saturate_cast<uchar>(pixelVec[2] + (detailPixel[2] - 128) * alpha); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cv::Vec3b MeshTexture::ConvertBGRtoRGBIfNeeded(const cv::Vec3b& bgrColor) { |
|
|
|
|
// 根据配置决定是否转换
|
|
|
|
|
// 如果OpenCV图像是BGR顺序,而我们希望存储为RGB,则交换B和R
|
|
|
|
|
|