恢复 affine校正

This commit is contained in:
hesuicong committed 2026-09-28 18:47:37 +08:00
1 parent e68ecd01db
commit 47b5e629f0
1 file changed
+192 -34
+192 -34
View File
@@ -836,6 +836,169 @@ public:
std::vector<int> patchPixelCount;
// ========== 辅助函数 ==========
// 在 MeshTexture 类的 public 区域添加
// 每个图像的 Lab 颜色统计
struct LabStats {
cv::Vec3f mean; // L, a, b 均值
cv::Vec3f stddev; // L, a, b 标准差
bool valid = false;
};
std::vector<LabStats> 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<cv::Vec3b>(y, x);
cv::Vec3b c10 = img.image.at<cv::Vec3b>(y, x + 1);
cv::Vec3b c01 = img.image.at<cv::Vec3b>(y + 1, x);
cv::Vec3b c11 = img.image.at<cv::Vec3b>(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<uchar>(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<cv::Vec3b>(0,0) = srcPixel;
cv::Mat labMat;
cv::cvtColor(pixelMat, labMat, cv::COLOR_BGR2Lab);
labMat.convertTo(labMat, CV_32FC3);
cv::Vec3f labVal = labMat.at<cv::Vec3f>(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<cv::Vec3f>(0,0) = transferred;
labMat.convertTo(labMat, CV_8UC3);
cv::Mat bgrMat;
cv::cvtColor(labMat, bgrMat, cv::COLOR_Lab2BGR);
return bgrMat.at<cv::Vec3b>(0,0);
}
// 双线性采样(适配 SEACAVE::TImage<Pixel8U>)
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<cv::Vec3b>(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<cv::Vec3b>(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<cv::Vec3b>(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<cv::Vec3b>(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<uchar>(std::min(255.0f, color[0] * 1.0f));
color[1] = cv::saturate_cast<uchar>(std::min(255.0f, color[1] * 1.0f));
color[2] = cv::saturate_cast<uchar>(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<cv::Vec3b>(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...");