From 5e59d7c60ba28106e97932bad73f1cc8c9132b8b Mon Sep 17 00:00:00 2001 From: hesuicong Date: Wed, 29 Apr 2026 14:35:19 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E7=99=BD=E5=B9=B3=E8=A1=A1?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 387 +++++++++++++++++++++++++++++--------- 1 file changed, 295 insertions(+), 92 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 6aa64ac..e362c87 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -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( 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( 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 viewColorSums(images.size(), cv::Vec3d(0, 0, 0)); + std::vector viewPixelCounts(images.size(), 0); + std::vector> viewColorSamples(images.size()); + + // 3. 第一次遍历:收集每个视图的颜色统计 + DEBUG_EXTRA("第一次遍历:收集视图颜色统计"); - // 检查源纹理颜色通道顺序 - if (!sourceTextures.empty()) { - const Image8U3& firstTex = sourceTextures[0]; - if (!firstTex.empty()) { - cv::Vec3b firstPixel = firstTex.at(0, 0); - DEBUG_EXTRA("源纹理[0]第一个像素: B=%d, G=%d, R=%d (OpenCV BGR顺序)", - firstPixel[0], firstPixel[1], firstPixel[2]); + #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 (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(0, 0); - DEBUG_EXTRA("images[0]第一个像素: B=%d, G=%d, R=%d (OpenCV BGR顺序)", - firstPixel[0], firstPixel[1], firstPixel[2]); + // 4. 计算每个视图的颜色调整因子 + std::vector 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( 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( 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( 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); + // 采样图像 + Pixel8U sampledColor = SampleImageBilinear(sourceImage.image, imgPoint); + + // 转换为BGR顺序用于颜色累加 + cv::Vec3f bgrColor( + sampledColor.b, // B + sampledColor.g, // G + sampledColor.r // R + ); - const float fx = imgPoint.x - x0; - const float fy = imgPoint.y - y0; - const float fx1 = 1.0f - fx; - const float fy1 = 1.0f - fy; + // 获取颜色调整因子 + 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)); + } + } - // 采样四个点的颜色 - const cv::Vec3b& c00 = sourceImage.image.at(y0, x0); - const cv::Vec3b& c01 = sourceImage.image.at(y0, x1); - const cv::Vec3b& c10 = sourceImage.image.at(y1, x0); - const cv::Vec3b& c11 = sourceImage.image.at(y1, x1); + // 应用颜色调整 + cv::Vec3f adjustedColor = bgrColor; + adjustedColor[0] *= adjust[0]; // B通道 + adjustedColor[1] *= adjust[1]; // G通道 + adjustedColor[2] *= adjust[2]; // R通道 - // 双线性插值 - cv::Vec3b sampledColor = - c00 * (fx1 * fy1) + - c01 * (fx * fy1) + - c10 * (fx1 * fy) + - c11 * (fx * fy); + // 限制颜色范围 + 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])); - // 修正2: 统一颜色通道处理 - // 方法1: 保持BGR顺序(与OpenCV一致) - // 如果最终显示为RGB,则需要在显示时转换 + // 累加颜色和权重 + float weight = 1.0f; + float& w = weightAccum(y, x); + cv::Vec3f& c = colorAccum(y, x); - // 方法2: 转换为RGB顺序存储 - cv::Vec3b finalColor = ConvertBGRtoRGBIfNeeded(sampledColor); + #ifdef _USE_OPENMP + #pragma omp atomic + #endif + w += weight; #ifdef _USE_OPENMP #pragma omp critical #endif { - // 使用转换后的颜色 - textureAtlas.at(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( 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(avgColor[2]); // R + finalColor.g = (unsigned char)cv::saturate_cast(avgColor[1]); // G + finalColor.b = (unsigned char)cv::saturate_cast(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(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(r); + pixel.g = (unsigned char)cv::saturate_cast(g); + pixel.b = (unsigned char)cv::saturate_cast(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(y, x) = cv::Vec3b(colEmpty.b, colEmpty.g, colEmpty.r); + continue; + } + + cv::Vec3b& detailPixel = detail.at(y, x); + cv::Vec3b blurPixel = blurred.at(y, x); + + // 计算细节 + detailPixel[0] = cv::saturate_cast(detailPixel[0] - blurPixel[0] + 128); + detailPixel[1] = cv::saturate_cast(detailPixel[1] - blurPixel[1] + 128); + detailPixel[2] = cv::saturate_cast(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(y, x); + cv::Vec3b detailPixel = detail.at(y, x); + + // 锐化 + pixelVec[0] = cv::saturate_cast(pixelVec[0] + (detailPixel[0] - 128) * alpha); + pixelVec[1] = cv::saturate_cast(pixelVec[1] + (detailPixel[1] - 128) * alpha); + pixelVec[2] = cv::saturate_cast(pixelVec[2] + (detailPixel[2] - 128) * alpha); + } + } +} + cv::Vec3b MeshTexture::ConvertBGRtoRGBIfNeeded(const cv::Vec3b& bgrColor) { // 根据配置决定是否转换 // 如果OpenCV图像是BGR顺序,而我们希望存储为RGB,则交换B和R