恢复 affine校正
This commit is contained in:
1 parent
e68ecd01db
commit
47b5e629f0
1 file changed
+192
-34
+192
-34
@@ -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...");
|
||||
|
||||
Reference in new issue
Block a user