diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 47e3af6..f40993d 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -836,6 +836,169 @@ public: std::vector patchPixelCount; // ========== 辅助函数 ========== + + // 在 MeshTexture 类的 public 区域添加 + + // 每个图像的 Lab 颜色统计 + struct LabStats { + cv::Vec3f mean; // L, a, b 均值 + cv::Vec3f stddev; // L, a, b 标准差 + bool valid = false; + }; + std::vector m_labStats; + IIndex m_refViewID = NO_ID; + + // 计算所有图像的 Lab 统计 + inline void ComputeLabStats() { + TD_TIMER_START(); + + m_labStats.resize(images.size()); + m_refViewID = NO_ID; + + // 第一遍:计算每个图像的统计 + float bestExposure = FLT_MAX; + + for (size_t vid = 0; vid < images.size(); ++vid) { + const Image& img = images[vid]; + if (!img.IsValid() || img.image.empty()) { + m_labStats[vid].valid = false; + continue; + } + + // 转 Lab 空间 + cv::Mat lab; + cv::cvtColor(img.image, lab, cv::COLOR_BGR2Lab); + lab.convertTo(lab, CV_32FC3); + + // 计算均值和标准差 + cv::Scalar mean, stddev; + cv::meanStdDev(lab, mean, stddev); + + m_labStats[vid].mean = cv::Vec3f(mean[0], mean[1], mean[2]); + m_labStats[vid].stddev = cv::Vec3f(stddev[0], stddev[1], stddev[2]); + m_labStats[vid].valid = true; + + // 选择 L 均值最接近 50 的作为参考视图 + float exposure = std::abs(mean[0] - 50.0f); + if (exposure < bestExposure) { + bestExposure = exposure; + m_refViewID = (IIndex)vid; + } + + DEBUG_EXTRA("[LabStats] View %d: L=%.1f±%.1f, a=%.1f±%.1f, b=%.1f±%.1f", + (int)vid, + mean[0], stddev[0], + mean[1], stddev[1], + mean[2], stddev[2]); + } + + DEBUG_EXTRA("[LabStats] Computed for %zu images, reference view = %d (%s)", + m_labStats.size(), + (int)m_refViewID, + m_refViewID != NO_ID ? images[m_refViewID].name.c_str() : "none"); + + DEBUG_EXTRA("[LabStats] Time: %s", TD_TIMER_GET_FMT().c_str()); + } + + // 从原始图像采样某点的颜色(BGR,8位) + inline cv::Vec3b SampleRawColor(IIndex vid, const Point3f& pt) const { + if (vid >= (IIndex)images.size()) { + return cv::Vec3b(0, 0, 0); + } + + const Image& img = images[vid]; + if (img.image.empty()) { + return cv::Vec3b(0, 0, 0); + } + + const Camera& cam = img.camera; + + // 投影到图像坐标 + Point2f imgPt = cam.ProjectPoint(Point3d(pt)); + + // 边界检查 + if (imgPt.x < 0 || imgPt.x >= img.image.cols - 1 || + imgPt.y < 0 || imgPt.y >= img.image.rows - 1) { + return cv::Vec3b(0, 0, 0); + } + + // 双线性插值采样 + int x = (int)imgPt.x; + int y = (int)imgPt.y; + float fx = imgPt.x - x; + float fy = imgPt.y - y; + + cv::Vec3b c00 = img.image.at(y, x); + cv::Vec3b c10 = img.image.at(y, x + 1); + cv::Vec3b c01 = img.image.at(y + 1, x); + cv::Vec3b c11 = img.image.at(y + 1, x + 1); + + cv::Vec3b result; + for (int ch = 0; ch < 3; ++ch) { + float val = (c00[ch] * (1-fx) + c10[ch] * fx) * (1-fy) + + (c01[ch] * (1-fx) + c11[ ch] * fx) * fy; + result[ch] = cv::saturate_cast(val); + } + + return result; + } + + // 颜色迁移函数:将像素从源视图迁移到参考视图的颜色空间 + inline cv::Vec3b TransferPixelColorSafe(const cv::Vec3b& srcPixel, IIndex srcViewID, IIndex refViewID, + const LabStats& srcStats, const LabStats& refStats, + float strength = 0.6f) { + if (srcViewID == refViewID || !srcStats.valid || !refStats.valid) + return srcPixel; + + // 1. 转 Lab (32位浮点) + cv::Mat pixelMat(1, 1, CV_8UC3); + pixelMat.at(0,0) = srcPixel; + cv::Mat labMat; + cv::cvtColor(pixelMat, labMat, cv::COLOR_BGR2Lab); + labMat.convertTo(labMat, CV_32FC3); + cv::Vec3f labVal = labMat.at(0,0); + cv::Vec3f transferred = labVal; + + // 保护:标准差过小会导致除零或噪声放大 + auto safeStd = [](float s) { return std::max(s, 2.0f); }; // Lab空间标准差阈值放宽 + + // 2. L 通道:对齐亮度(带强度因子,防止过暗) + float srcLMean = srcStats.mean[0], refLMean = refStats.mean[0]; + float srcLStd = safeStd(srcStats.stddev[0]), refLStd = safeStd(refStats.stddev[0]); + + float zL = (labVal[0] - srcLMean) / srcLStd; + zL = std::max(-2.0f, std::min(2.0f, zL)); // 截断极端值 + float newL = zL * refLStd + refLMean; + + // 混合原图L与迁移L(strength=0.6 表示60%迁移,保留40%原貌) + transferred[0] = labVal[0] * (1.0f - strength) + newL * strength; + transferred[0] = std::max(0.0f, std::min(100.0f, transferred[0])); // 32位Lab L范围0-100 + + // 3. a/b 通道:仅做轻微缩放(不做均值平移,防止变灰!) + for (int ch = 1; ch <= 2; ++ch) { + float srcMean = srcStats.mean[ch]; + float refMean = refStats.mean[ch]; // 通常接近128(8位)或0(32位) + float srcStd = safeStd(srcStats.stddev[ch]); + float refStd = safeStd(refStats.stddev[ch]); + + float z = (labVal[ch] - srcMean) / srcStd; + z = std::max(-2.0f, std::min(2.0f, z)); + + // 仅缩放标准差,均值保持原图均值(保留色相) + float newAB = srcMean + z * (refStd / srcStd) * strength; + transferred[ch] = labVal[ch] * (1.0f - strength) + newAB * strength; + transferred[ch] = std::max(-127.0f, std::min(127.0f, transferred[ch])); + } + + // 4. 转回 BGR (确保流程:32F -> 8U -> BGR) + labMat.at(0,0) = transferred; + labMat.convertTo(labMat, CV_8UC3); + cv::Mat bgrMat; + cv::cvtColor(labMat, bgrMat, cv::COLOR_Lab2BGR); + + return bgrMat.at(0,0); + } + // 双线性采样(适配 SEACAVE::TImage) static inline Pixel8U SampleBilinear(const Image8U3& img, const Point2f& pt) { float x = pt.x * img.width() - 0.5f; @@ -16210,8 +16373,8 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( // ★ 强制原样写入(正确图的效果) localAtlas.at(atlasY, atlasX) = color; - - /* + + //* // ★ 若后续需恢复仿射,请先打印参数确认: // printf("ac: kb=%.2f kg=%.2f kr=%.2f bb=%.2f bg=%.2f br=%.2f\n", ac.kb, ac.kg, ac.kr, ac.bb, ac.bg, ac.br); // 注意:OpenCV 是 BGR,确保 k/bb 对应 BGR 而非 RGB @@ -16227,7 +16390,7 @@ void MeshTexture::ApplyGlobalSeamLevelingOnRCPatches( } else { localAtlas.at(atlasY, atlasX) = color; } - */ + //*/ } } } @@ -16573,6 +16736,11 @@ bool MeshTexture::RasterizeVirtualFaces( DEBUG_EXTRA("RC-style Rasterization Engine (simplified + photometric): Starting..."); TD_TIMER_START(); + // ★ 新增:计算 Lab 统计(如果还没算过) + if (m_labStats.empty()) { + ComputeLabStats(); + } + if (virtualFaceMap.empty() || virtualFaceViews.size() != virtualFaceMap.size()) return false; @@ -16691,43 +16859,33 @@ bool MeshTexture::RasterizeVirtualFaces( } #endif - // 写入 atlas(最高分胜出) - #pragma omp critical (atlas_write) - { - for (int y = 0; y < patchH; ++y) { - for (int x = 0; x < patchW; ++x) { - cv::Vec3b color = patch.at(y, x); + // 写入 atlas(最高分胜出) + #pragma omp critical (atlas_write) + { + for (int y = 0; y < patchH; ++y) { + for (int x = 0; x < patchW; ++x) { + cv::Vec3b color = patch.at(y, x); - if (color[0] == 0 && color[1] == 0 && color[2] == 0) continue; + if (color[0] == 0 && color[1] == 0 && color[2] == 0) continue; - int atlasX = x + minX, atlasY = y + minY; - if (atlasX<0||atlasX>=textureSize||atlasY<0||atlasY>=textureSize) continue; - size_t idx = atlasY * textureSize + atlasX; + int atlasX = x + minX, atlasY = y + minY; + if (atlasX<0||atlasX>=textureSize||atlasY<0||atlasY>=textureSize) continue; + size_t idx = atlasY * textureSize + atlasX; - // ★ 已删除错误放在这里的 m_texelPatchID.assign(...) - - if (currentScore > m_texelScores[idx].score) { - color[0] = cv::saturate_cast(std::min(255.0f, color[0] * 1.0f)); - color[1] = cv::saturate_cast(std::min(255.0f, color[1] * 1.0f)); - color[2] = cv::saturate_cast(std::min(255.0f, color[2] * 1.0f)); - - // atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; + if (currentScore > m_texelScores[idx].score) { + // ★ 直接写入原始颜色,不做迁移 if (atlasY >= 0 && atlasY < atlas.rows && atlasX >= 0 && atlasX < atlas.cols) { - // 用 cv::Mat 的 at 方法,它内部有边界检查(Debug 模式下) cv::Vec3b& pixel = atlas.at(atlasY, atlasX); - // pixel[0] = color[2]; - // pixel[1] = color[1]; - // pixel[2] = color[0]; - - pixel = color; // 原样写入,不再交换 + pixel = color; // 直接用 patch 的颜色 } - m_texelScores[idx].score = currentScore; - m_texelScores[idx].viewID = viewID; - m_texelPatchID[idx] = i; // ★ 记录这个像素属于 patch i - } - } - } - } + + m_texelScores[idx].score = currentScore; + m_texelScores[idx].viewID = viewID; + m_texelPatchID[idx] = i; + } + } + } + } } VERBOSE("[Raster] Step 3: rasterization done, building RC patches...");