diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 7a514eb..8b9ba06 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -217,6 +217,9 @@ struct VirtualFaceGeometryData { }; typedef cList VirtualFaceGeometryArr; +static int g_sampleSuccess = 0; +static int g_sampleFail = 0; + struct MeshTexture { // used to render the surface to a view camera typedef TImage FaceMap; @@ -1165,6 +1168,46 @@ public: void BuildSeamEdgesFromRCPatches(); void SeamBlendingFromOriginalImages(Image8U3& atlas); + // ---- 全局光度校正(per-image gain,3 通道独立)---- + std::vector m_imageGains; // 每个视图一个 (gR,gG,gB) + bool m_gainsEstimated = false; + bool EstimateGlobalPhotometricCorrection(); + + inline cv::Vec3f SampleColorAtPoint(const cv::Mat& img, const Camera& cam, const Point3f& pt3D) { + Point2f pt2D = cam.ProjectPoint(Point3d(pt3D)); + + if (pt2D.x < 0 || pt2D.y < 0 || pt2D.x >= img.cols || pt2D.y >= img.rows) { + // 只打前 5 次失败,避免刷屏 + static int g_failLog = 0; + if (g_failLog < 5) { + VERBOSE("[SampleColor FAIL] pt3D=(%.3f,%.3f,%.3f) pt2D=(%.3f,%.3f) imgSize=%dx%d", + pt3D.x, pt3D.y, pt3D.z, pt2D.x, pt2D.y, img.cols, img.rows); + g_failLog++; + } + return cv::Vec3f(-1, -1, -1); + } + + cv::Vec3d sum(0, 0, 0); + int count = 0; + for (int dy = -1; dy <= 1; dy++) { + int y = (int)pt2D.y + dy; + if (y < 0 || y >= img.rows) continue; + for (int dx = -1; dx <= 1; dx++) { + int x = (int)pt2D.x + dx; + if (x < 0 || x >= img.cols) continue; + const cv::Vec3b& p = img.at(y, x); + sum[0] += p[0]; sum[1] += p[1]; sum[2] += p[2]; + count++; + } + } + + if (count == 0) return cv::Vec3f(-1, -1, -1); + + return cv::Vec3f((float)sum[0] / (count * 255.0f), + (float)sum[1] / (count * 255.0f), + (float)sum[2] / (count * 255.0f)); + } + // Bruce //* template @@ -1975,7 +2018,7 @@ bool MeshTexture::ListCameraFaces(FaceDataViewArr& facesDatas, float fOutlierThr // strName!="103_8" && strName!="112_8" && strName!="113_8" && // strName!="122_2" && strName!="123_2" && strName!="121_2") // if (strName!="122_2") - if (strName!="122_2" && strName!="123_2" && strName!="121_2") + if (strName!="104_8" && strName!="106_8") { continue; } @@ -14338,283 +14381,277 @@ bool MeshTexture::RasterizeVirtualFaces( Pixel8U colEmpty, Mesh::Image8U3Arr& outTextures) { - DEBUG_EXTRA("RC-style Rasterization Engine: Starting..."); + DEBUG_EXTRA("RC-style Rasterization Engine (simplified + photometric): Starting..."); TD_TIMER_START(); if (virtualFaceMap.empty() || virtualFaceViews.size() != virtualFaceMap.size()) return false; - // -------------------------------------------------- - // 1. UV 布局分析 - // -------------------------------------------------- + // 1. UV 布局 AABB2f uvBounds(true); - for (const TexCoord& uv : scene.mesh.faceTexcoords) - uvBounds.InsertFull(uv); - - float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x(); - float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y(); - if (uvWidth < 0.001f) uvWidth = 1.0f; - if (uvHeight < 0.001f) uvHeight = 1.0f; + for (const TexCoord& uv : scene.mesh.faceTexcoords) uvBounds.InsertFull(uv); + if ((uvBounds.ptMax.x() - uvBounds.ptMin.x()) < 0.001f) uvBounds.ptMax.x() = uvBounds.ptMin.x() + 1.0f; + if ((uvBounds.ptMax.y() - uvBounds.ptMin.y()) < 0.001f) uvBounds.ptMax.y() = uvBounds.ptMin.y() + 1.0f; - int textureSize = ComputeOptimalTextureSizeAdaptive( - virtualFaceMap, virtualFaceViews, nTextureSizeMultiple); + int textureSize = ComputeOptimalTextureSizeAdaptive(virtualFaceMap, virtualFaceViews, nTextureSizeMultiple); if (textureSize < 1024) textureSize = 1024; if (textureSize > 16384) textureSize = 16384; - // -------------------------------------------------- - // 2. 创建纹理 - // -------------------------------------------------- + // 2. atlas + score outTextures.emplace_back(textureSize, textureSize); Image8U3& atlas = outTextures.back(); atlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); - m_texelScores.assign(textureSize * textureSize, TexelScore{}); if (!ComputeVirtualFaceGeometry(virtualFaceMap)) { - DEBUG_EXTRA("Failed to compute virtual face geometries"); - return false; + DEBUG_EXTRA("Failed to compute virtual face geometries"); return false; } - currentTextureSize = textureSize; - // -------------------------------------------------- - // 3. RC 风格光栅化 - // -------------------------------------------------- + // 确保增益已估计(若外部未调用则在此兜底) + if (!m_gainsEstimated) EstimateGlobalPhotometricCorrection(); + + // 3. 逐三角形光栅化(直接写入,最高分胜出 + 光度校正) #ifdef _USE_OPENMP #pragma omp parallel for schedule(dynamic) #endif for (int i = 0; i < (int)virtualFaceMap.size(); ++i) { const VirtualFace& vf = virtualFaceMap[i]; const VirtualFaceGeometry& geom = m_virtualFaceGeometries[i]; - - if (!geom.isValid || vf.faces.empty() || virtualFaceViews[i].empty()) - continue; + if (!geom.isValid || vf.faces.empty() || virtualFaceViews[i].empty()) continue; IIndex viewID = virtualFaceViews[i][0]; if (viewID >= (IIndex)images.size()) continue; - const Image& srcImg = images[viewID]; - if (srcImg.image.empty() || srcImg.image.cols < 2 || srcImg.image.rows < 2) - continue; + if (srcImg.image.empty() || srcImg.image.cols < 2 || srcImg.image.rows < 2) continue; + + float currentScore = virtualFaceViewWeights[i].empty() ? -1.0f : virtualFaceViewWeights[i][0]; int minX = std::max(0, (int)floor(geom.uvBounds.ptMin.x() * textureSize)); int maxX = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.x() * textureSize)); int minY = std::max(0, (int)floor(geom.uvBounds.ptMin.y() * textureSize)); int maxY = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.y() * textureSize)); if (minX > maxX || minY > maxY) continue; + int patchW = maxX - minX + 1, patchH = maxY - minY + 1; - int patchW = maxX - minX + 1; - int patchH = maxY - minY + 1; - - cv::Mat mapX(patchH, patchW, CV_32FC1); - cv::Mat mapY(patchH, patchW, CV_32FC1); - + cv::Mat mapX(patchH, patchW, CV_32FC1), mapY(patchH, patchW, CV_32FC1); const float* H = geom.homography.ptr(); for (int y = minY; y <= maxY; ++y) { for (int x = minX; x <= maxX; ++x) { - float u = (float)x / (float)textureSize; - float v = (float)y / (float)textureSize; + float u = (float)x / (float)textureSize, v = (float)y / (float)textureSize; float w = H[6]*u + H[7]*v + H[8]; - if (std::abs(w) < 1e-12f) { - mapX.at(y-minY, x-minX) = -1.0f; - mapY.at(y-minY, x-minX) = -1.0f; - continue; - } - mapX.at(y-minY, x-minX) = (H[0]*u + H[1]*v + H[2]) / w; - mapY.at(y-minY, x-minX) = (H[3]*u + H[4]*v + H[5]) / w; + if (std::abs(w) < 1e-12f) { mapX.at(y-minY,x-minX)=-1; mapY.at(y-minY,x-minX)=-1; continue; } + mapX.at(y-minY,x-minX) = (H[0]*u+H[1]*v+H[2])/w; + mapY.at(y-minY,x-minX) = (H[3]*u+H[4]*v+H[5])/w; } } - cv::Mat patch; - cv::remap(srcImg.image, patch, mapX, mapY, - cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); + cv::remap(srcImg.image, patch, mapX, mapY, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); - #pragma omp critical + // 应用光度校正(在 patch 上原地乘 gain) + const cv::Vec3f& gain = (viewID < (IIndex)m_imageGains.size()) ? m_imageGains[viewID] : cv::Vec3f(1,1,1); + const bool applyGain = (gain != cv::Vec3f(1,1,1)); + if (applyGain) { + for (int y = 0; y < patchH; ++y) { + for (int x = 0; x < patchW; ++x) { + cv::Vec3b& p = patch.at(y, x); + if (p[0]==0 && p[1]==0 && p[2]==0) continue; + p[0] = cv::saturate_cast(p[0]*gain[0]); // B + p[1] = cv::saturate_cast(p[1]*gain[1]); // G + p[2] = cv::saturate_cast(p[2]*gain[2]); // R + } + } + } + + // 写入 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; - - int atlasX = x + minX; - int atlasY = y + minY; - if (atlasX < 0 || atlasX >= textureSize || - atlasY < 0 || atlasY >= textureSize) 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; - TexelScore& ts = m_texelScores[idx]; - float currentScore = virtualFaceViewWeights[i].empty() - ? -1.0f : virtualFaceViewWeights[i][0]; - - if (currentScore > ts.score) { + if (currentScore > m_texelScores[idx].score) { atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; - ts.score = currentScore; - ts.viewID = viewID; + m_texelScores[idx].score = currentScore; + m_texelScores[idx].viewID = viewID; } } } } } - - // ========== 接缝区域多视图加权融合 ========== - // 收集需要融合的面(出现在 seamEdges 中的面) - std::set seamFaceIndices; - for (const auto& edge : seamEdges) { - seamFaceIndices.insert((int)edge.i); - seamFaceIndices.insert((int)edge.j); - } - - if (!seamFaceIndices.empty()) { - DEBUG_EXTRA("[Blend] Fusing %zu seam faces...", seamFaceIndices.size()); - - for (int i : seamFaceIndices) { - if (i < 0 || i >= (int)virtualFaceMap.size()) continue; - const VirtualFace& vf = virtualFaceMap[i]; - if (vf.faces.empty() || virtualFaceViews[i].empty()) continue; - - IIndex refView = virtualFaceViews[i][0]; - const auto& candidateViews = virtualFaceViews[i]; - const auto& candidateWeights = virtualFaceViewWeights[i]; - - // 获取该面的 UV 包围盒 - const VirtualFaceGeometry& geom = m_virtualFaceGeometries[i]; - if (!geom.isValid) continue; - - int minX = std::max(0, (int)floor(geom.uvBounds.ptMin.x() * textureSize)); - int maxX = std::min(textureSize - 1, (int)ceil(geom.uvBounds.ptMax.x() * textureSize)); - int minY = std::max(0, (int)floor(geom.uvBounds.ptMin.y() * textureSize)); - int maxY = std::max(0, (int)ceil(geom.uvBounds.ptMax.y() * textureSize)); - if (minX > maxX || minY > maxY) continue; - - // 逐像素多视图融合 - for (int y = minY; y <= maxY; ++y) { - for (int x = minX; x <= maxX; ++x) { - size_t idx = y * textureSize + x; - if (m_texelScores[idx].viewID < 0) continue; - - // 只处理当前面贡献的像素(避免覆盖其他面) - // 用 score 判断:如果当前面不是胜者,跳过 - float myScore = candidateWeights.empty() ? 0.5f : candidateWeights[0]; - if (m_texelScores[idx].score > myScore + 0.01f) continue; - - // 计算 3D 点(从重心坐标) - // 简化:用该面第一个三角形 - FIndex fid = vf.faces[0]; - const Face& face = scene.mesh.faces[fid]; - const TexCoord* uvs = &scene.mesh.faceTexcoords[fid * 3]; - Point3f bary = Barycentric( - TexCoord((float)x / textureSize, (float)y / textureSize), - uvs[0], uvs[1], uvs[2]); - if (bary.x < 0 || bary.y < 0 || bary.z < 0) continue; - - Point3f pt3D = BaryTo3D(bary, - scene.mesh.vertices[face[0]], - scene.mesh.vertices[face[1]], - scene.mesh.vertices[face[2]]); - - // 多视图采样 - cv::Vec3f blended(0, 0, 0); - float totalW = 0.0f; - - for (size_t k = 0; k < candidateViews.size(); ++k) { - IIndex vid = candidateViews[k]; - float w = candidateWeights[k]; - if (w < 0.01f) continue; - - const Image& img = images[vid]; - Point2f pt2D = img.camera.ProjectPoint(Point3d(pt3D)); - if (pt2D.x < 0 || pt2D.y < 0 || - pt2D.x >= img.image.width() - 1 || - pt2D.y >= img.image.height() - 1) continue; - - Pixel8U c = SampleBilinear(img.image, pt2D); - cv::Vec3f color(c.r, c.g, c.b); - - // 局部增益:以 refView 为参考 - if (vid != refView) { - const Image& refImg = images[refView]; - Point2f refPt = refImg.camera.ProjectPoint(Point3d(pt3D)); - if (refPt.x >= 0 && refPt.y >= 0 && - refPt.x < refImg.image.width() - 1 && - refPt.y < refImg.image.height() - 1) { - Pixel8U rc = SampleBilinear(refImg.image, refPt); - cv::Vec3f refColor(rc.r, rc.g, rc.b); - // 加 1 防除零 - float gr = (refColor[0] + 1.0f) / (color[0] + 1.0f); - float gg = (refColor[1] + 1.0f) / (color[1] + 1.0f); - float gb = (refColor[2] + 1.0f) / (color[2] + 1.0f); - gr = std::max(0.5f, std::min(2.0f, gr)); - gg = std::max(0.5f, std::min(2.0f, gg)); - gb = std::max(0.5f, std::min(2.0f, gb)); - color = cv::Vec3f(color[0]*gr, color[1]*gg, color[2]*gb); - } - } - - blended += color * w; - totalW += w; - } - - if (totalW > 0.01f) { - blended /= totalW; - atlas(y, x) = Pixel8U( - (uint8_t)std::min(255.0f, blended[2]), // B - (uint8_t)std::min(255.0f, blended[1]), // G - (uint8_t)std::min(255.0f, blended[0]) // R - ); - } - } - } - } - DEBUG_EXTRA("[Blend] Multi-view seam fusion done"); - } - // ========== 接缝融合 END ========== m_texelScores.clear(); DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); + m_virtualFaceViewWeights = virtualFaceViewWeights; - m_virtualFaceViewWeights = virtualFaceViewWeights; - - // -------------------------------------------------- - // 4. 构建 RCPatch - // -------------------------------------------------- + // 4. rcPatches rcPatches.clear(); for (size_t i = 0; i < virtualFaceMap.size(); ++i) { if (virtualFaceViews[i].empty()) continue; const VirtualFaceGeometry& geom = m_virtualFaceGeometries[i]; if (!geom.isValid) continue; - - RCPatch patch; - patch.viewID = virtualFaceViews[i][0]; - patch.faces = { static_cast(i) }; - patch.uvMin = geom.uvBounds.ptMin; - patch.uvMax = geom.uvBounds.ptMax; + RCPatch patch; patch.viewID = virtualFaceViews[i][0]; patch.faces = {(FIndex)i}; + patch.uvMin = geom.uvBounds.ptMin; patch.uvMax = geom.uvBounds.ptMax; patch.rect = cv::Rect( - (int)(geom.uvBounds.ptMin.x() * textureSize), - (int)(geom.uvBounds.ptMin.y() * textureSize), - (int)((geom.uvBounds.ptMax.x() - geom.uvBounds.ptMin.x()) * textureSize) + 1, - (int)((geom.uvBounds.ptMax.y() - geom.uvBounds.ptMin.y()) * textureSize) + 1 - ); - patch.rect &= cv::Rect(0, 0, textureSize, textureSize); - if (patch.rect.width > 0 && patch.rect.height > 0) - rcPatches.push_back(patch); + (int)(geom.uvBounds.ptMin.x()*textureSize), (int)(geom.uvBounds.ptMin.y()*textureSize), + (int)((geom.uvBounds.ptMax.x()-geom.uvBounds.ptMin.x())*textureSize)+1, + (int)((geom.uvBounds.ptMax.y()-geom.uvBounds.ptMin.y())*textureSize)+1); + patch.rect &= cv::Rect(0,0,textureSize,textureSize); + if (patch.rect.width>0 && patch.rect.height>0) rcPatches.push_back(patch); } DEBUG_EXTRA("Created %zu RC patches", rcPatches.size()); - // -------------------------------------------------- - // 5. 构建接缝边 - // -------------------------------------------------- + // 5/6/7. 接缝 BuildSeamEdgesFromRCPatches(); - - // -------------------------------------------------- - // 6. 接缝融合(从原始图像采样) - // -------------------------------------------------- if (!seamEdges.empty()) { TD_TIMER_START(); SeamBlendingFromOriginalImages(atlas); - DEBUG_EXTRA("Seam blending completed: %zu edges (%s)", - seamEdges.size(), TD_TIMER_GET_FMT().c_str()); + DEBUG_EXTRA("Seam blending completed: %zu edges (%s)", seamEdges.size(), TD_TIMER_GET_FMT().c_str()); + } + return true; +} + +// ============================================================ +// 全局光度校正:估计每视图相对 gain (R/G/B 独立) +// 用点云中被多张图共同可见的点做灰度加权最小二乘, +// 再 BFS 以第 0 图为参考把 gain 传播到全图。 +// ============================================================ +bool MeshTexture::EstimateGlobalPhotometricCorrection() +{ + const size_t nImages = images.size(); + m_imageGains.assign(nImages, cv::Vec3f(1.f, 1.f, 1.f)); + m_gainsEstimated = false; + if (nImages < 2) return true; + + const PointCloud& pc = scene.pointcloud; + if (pc.points.empty() || pc.pointViews.empty()) { + DEBUG_EXTRA("Photometric: no point cloud or pointViews, skip"); + return true; + } + + // 确保 points 和 pointViews 数量一致 + const size_t nPoints = std::min(pc.points.size(), pc.pointViews.size()); + + struct ChanStat { double sA[3] = {0,0,0}, sB[3] = {0,0,0}; int n = 0; }; + std::unordered_map pairStats; + auto pairKey = [](IIndex a, IIndex b) -> uint64_t { + if (a > b) std::swap(a, b); + return ((uint64_t)(uint32_t)a << 32) | (uint32_t)b; + }; + + // 双线性采样 lambda + const auto sampleBilinear = [](const cv::Mat& img, const Point2f& p) -> cv::Vec3b { + int ix = (int)std::floor(p.x), iy = (int)std::floor(p.y); + float fx = p.x - ix, fy = p.y - iy; + if (ix < 0 || iy < 0 || ix + 1 >= img.cols || iy + 1 >= img.rows) + return cv::Vec3b(0, 0, 0); + const cv::Vec3b& c00 = img.at(iy, ix); + const cv::Vec3b& c10 = img.at(iy, ix + 1); + const cv::Vec3b& c01 = img.at(iy + 1, ix); + const cv::Vec3b& c11 = img.at(iy + 1, ix + 1); + return cv::Vec3b( + (uint8_t)((1-fy)*((1-fx)*c00[0]+(fx)*c10[0]) + fy*((1-fx)*c01[0]+(fx)*c11[0])), + (uint8_t)((1-fy)*((1-fx)*c00[1]+(fx)*c10[1]) + fy*((1-fx)*c01[1]+(fx)*c11[1])), + (uint8_t)((1-fy)*((1-fx)*c00[2]+(fx)*c10[2]) + fy*((1-fx)*c01[2]+(fx)*c11[2])) + ); + }; + + int sampledPairs = 0; + + // ✅ 用索引遍历,通过 pointViews[i] 取可见视图 + for (size_t pi = 0; pi < nPoints; ++pi) { + const PointCloud::Point& pt = pc.points[pi]; + const auto& views = pc.pointViews[pi]; // ← 关键修正 + + if (views.empty()) continue; + + // 收集有效的可见图像 + std::vector vis; + for (const auto& vid : views) { + if ((size_t)vid < nImages && !images[vid].image.empty()) + vis.push_back(vid); + } + if (vis.size() < 2) continue; + + const Point3d P3(pt.x, pt.y, pt.z); + + for (size_t i = 0; i < vis.size(); ++i) { + for (size_t j = i + 1; j < vis.size(); ++j) { + IIndex va = vis[i], vb = vis[j]; + const Image& imA = images[va]; + const Image& imB = images[vb]; + + Point2f pa = imA.camera.ProjectPoint(P3); + Point2f pb = imB.camera.ProjectPoint(P3); + + if (pa.x < 1 || pa.y < 1 || pa.x >= imA.image.cols - 1 || pa.y >= imA.image.rows - 1) continue; + if (pb.x < 1 || pb.y < 1 || pb.x >= imB.image.cols - 1 || pb.y >= imB.image.rows - 1) continue; + + cv::Vec3b ca = sampleBilinear(imA.image, pa); + cv::Vec3b cb = sampleBilinear(imB.image, pb); + + // 亮度过滤 + auto lum = [](const cv::Vec3b& c) { return 0.299*c[2] + 0.587*c[1] + 0.114*c[0]; }; + if (lum(ca) < 12 || lum(cb) < 12) continue; + if (ca[0]>250 && ca[1]>250 && ca[2]>250) continue; + if (cb[0]>250 && cb[1]>250 && cb[2]>250) continue; + + ChanStat& s = pairStats[pairKey(va, vb)]; + s.sA[0] += ca[0]; s.sA[1] += ca[1]; s.sA[2] += ca[2]; + s.sB[0] += cb[0]; s.sB[1] += cb[1]; s.sB[2] += cb[2]; + s.n++; + sampledPairs++; + } + } } + DEBUG_EXTRA("Photometric: sampled %d point-pair observations", sampledPairs); + if (pairStats.empty()) { + DEBUG_EXTRA("Photometric: no overlapping observations, skip"); + return true; + } + + // ---- 以下完全不变(建邻接表 + BFS 传播 gain)---- + std::vector>> adj(nImages); + for (const auto& kv : pairStats) { + if (kv.second.n < 8) continue; + IIndex a = (IIndex)(kv.first >> 32); + IIndex b = (IIndex)(kv.first & 0xFFFFFFFF); + const ChanStat& s = kv.second; + cv::Vec3f gB(1, 1, 1); + for (int c = 0; c < 3; ++c) { + double mA = s.sA[c] / s.n, mB = s.sB[c] / s.n; + if (mA > 1.0) gB[c] = (float)(mB / mA); + } + for (int c = 0; c < 3; ++c) gB[c] = std::max(0.25f, std::min(4.0f, gB[c])); + adj[a].emplace_back(b, gB); + adj[b].emplace_back(a, cv::Vec3f(1.f/gB[0], 1.f/gB[1], 1.f/gB[2])); + } + + std::vector vis(nImages, false); + std::queue q; q.push(0); vis[0] = true; + m_imageGains[0] = cv::Vec3f(1, 1, 1); + int reached = 1; + while (!q.empty()) { + IIndex cur = q.front(); q.pop(); + for (const auto& nb : adj[cur]) { + IIndex to = nb.first; + if (vis[to]) continue; + m_imageGains[to] = cv::Vec3f( + m_imageGains[cur][0] * nb.second[0], + m_imageGains[cur][1] * nb.second[1], + m_imageGains[cur][2] * nb.second[2]); + for (int c = 0; c < 3; ++c) + m_imageGains[to][c] = std::max(0.25f, std::min(4.0f, m_imageGains[to][c])); + vis[to] = true; reached++; + q.push(to); + } + } + m_gainsEstimated = true; + DEBUG_EXTRA("Photometric: gains estimated, %d/%zu images reached from ref 0", reached, nImages); return true; } @@ -14624,6 +14661,7 @@ bool MeshTexture::RasterizeVirtualFaces( void MeshTexture::BuildSeamEdgesFromRCPatches() { rcSeamEdges.clear(); + seamEdges.clear(); // ✅ 同时清空老的 // face → rcPatch 映射 std::vector faceToRCPatch(scene.mesh.faces.size(), NO_ID); @@ -14646,12 +14684,10 @@ void MeshTexture::BuildSeamEdgesFromRCPatches() uint32_t p1 = faceToRCPatch[f1]; if (p1 == NO_ID || p1 == p0) continue; - // ---------- 找共享边的两个顶点 ---------- const Mesh::Face& face0 = scene.mesh.faces[f0]; int v0_idx = e; int v1_idx = (e + 1) % 3; - // 在 f1 中找对应顶点 int idx0_in_f1 = -1, idx1_in_f1 = -1; for (int k = 0; k < 3; ++k) { if (scene.mesh.faces[f1][k] == face0[v0_idx]) idx0_in_f1 = k; @@ -14660,26 +14696,26 @@ void MeshTexture::BuildSeamEdgesFromRCPatches() if (idx0_in_f1 == -1 || idx1_in_f1 == -1) continue; - // ---------- ✅ UV 坐标(关键) ---------- const TexCoord& uv0_p0 = scene.mesh.faceTexcoords[f0 * 3 + v0_idx]; const TexCoord& uv1_p0 = scene.mesh.faceTexcoords[f0 * 3 + v1_idx]; - const TexCoord& uv0_p1 = scene.mesh.faceTexcoords[f1 * 3 + idx0_in_f1]; - const TexCoord& uv1_p1 = scene.mesh.faceTexcoords[f1 * 3 + idx1_in_f1]; - // ---------- 构建接缝边 ---------- + // ✅ 存进你自己的 rcSeamEdges SeamEdge edge; edge.rcPatchID0 = p0; edge.rcPatchID1 = p1; edge.faceID0 = f0; edge.faceID1 = f1; - edge.uv0 = (uv0_p0 + uv1_p0) * 0.5f; // patch0 边中点 - edge.uv1 = (uv0_p1 + uv1_p1) * 0.5f; // patch1 边中点 + edge.uv0 = uv0_p0; + edge.uv1 = uv1_p0; + rcSeamEdges.push_back(edge); // ✅ 改回 rcSeamEdges - rcSeamEdges.push_back(edge); + // ✅ 同步往老的 seamEdges 里塞 PairIdx(供 SeamBlendingFromOriginalImages 用) + seamEdges.Insert(PairIdx(f0, f1)); // 或 seamEdges.emplace_back(f0, f1); } } - DEBUG_EXTRA("Built %zu RC seam edges", rcSeamEdges.size()); + DEBUG_EXTRA("Built %zu RC seam edges, %zu legacy seam edges", + rcSeamEdges.size(), seamEdges.size()); } // ============================================================ @@ -15234,7 +15270,7 @@ int MeshTexture::ComputeOptimalTextureSizeAdaptive( } // ============================================================ -// 5. SelectBestViewsForVirtualFaces(带 patch 一致性传播) +// 5. SelectBestViewsForVirtualFaces(修复版) // ============================================================ bool MeshTexture::SelectBestViewsForVirtualFaces( VirtualFaceMap& virtualFaceMap, @@ -15242,7 +15278,63 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( float fRatioDataSmoothness, int nIgnoreMaskLabel, const IIndexArr& views) { + VERBOSE("4[EnterSelect] faceFaces.empty=%d faceFaces.size=%d faceNeighbors.empty=%d", + (int)scene.mesh.faceFaces.empty(), + (int)scene.mesh.faceFaces.size(), + (int)faceNeighbors.empty()); + DEBUG_EXTRA("Selecting best views for %zu virtual faces", virtualFaceMap.size()); + static std::vector g_avgColors; + +// ===== 从磁盘加载图像,计算全局平均颜色 ===== +static bool g_avgColorsComputed = false; +if (!g_avgColorsComputed) { + const size_t numViews = images.size(); // ← 你的变量名 + g_avgColors.resize(numViews, cv::Vec3f(-1, -1, -1)); + + for (size_t vid = 0; vid < numViews; ++vid) { + const std::string& imgPath = images[vid].name; // ← 你的字段名 + + cv::Mat img = cv::imread(imgPath, cv::IMREAD_COLOR); + if (img.empty()) { + VERBOSE("[AvgColor] view %zu: cannot load '%s'", vid, imgPath.c_str()); + continue; + } + + // 直接转 float 算均值,不 resize + cv::Mat imgF; + img.convertTo(imgF, CV_32FC3, 1.0 / 255.0); + cv::Scalar mean = cv::mean(imgF); + + g_avgColors[vid] = cv::Vec3f(mean[0], mean[1], mean[2]); + + VERBOSE("[AvgColor] view %zu: mean=(%.3f,%.3f,%.3f)", + vid, mean[0], mean[1], mean[2]); + } + + g_avgColorsComputed = true; + VERBOSE("[AvgColor] Done: [0]=(%.3f,%.3f,%.3f)", + g_avgColors[0][0], g_avgColors[0][1], g_avgColors[0][2]); +} + + if (g_avgColors.empty()) { + g_avgColors.resize(images.size(), cv::Vec3f(-1, -1, -1)); + for (size_t v = 0; v < images.size(); ++v) { + const cv::Mat& m = images[v].image; + if (m.empty()) continue; + cv::Scalar meanColor = cv::mean(m); + g_avgColors[v] = cv::Vec3f(meanColor[0] / 255.0f, meanColor[1] / 255.0f, meanColor[2] / 255.0f); + } + VERBOSE("[AvgColor] Precomputed average colors for %zu images", images.size()); + + // 调试:打印 g_avgColors 内容 + VERBOSE("[AvgColorDebug] g_avgColors.size=%d, [0]=(%.1f,%.1f,%.1f), [1]=(%.1f,%.1f,%.1f), [78]=(%.1f,%.1f,%.1f)", + (int)g_avgColors.size(), + g_avgColors[0][0], g_avgColors[0][1], g_avgColors[0][2], + g_avgColors[1][0], g_avgColors[1][1], g_avgColors[1][2], + g_avgColors[78][0], g_avgColors[78][1], g_avgColors[78][2]); + } + faceViews.resize(virtualFaceMap.size()); faceViewWeights.resize(virtualFaceMap.size()); @@ -15250,7 +15342,6 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( for (auto& v : faceViews) v.clear(); for (auto& w : faceViewWeights) w.clear(); - // 确保 faceNeighbors 已填充 if (faceNeighbors.empty()) { DEBUG_EXTRA("faceNeighbors empty, running ComputePureFaceVisibility first"); if (!ComputePureFaceVisibility(fOutlierThreshold, nIgnoreMaskLabel, views)) { @@ -15258,15 +15349,25 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( } } - // faceToView 映射 + 面评分存储 std::vector faceToView(scene.mesh.faces.size(), NO_ID); - std::vector faceScores(scene.mesh.faces.size(), -FLT_MAX); // ✅ 存储每个面的选图评分 + std::vector faceScores(scene.mesh.faces.size(), -FLT_MAX); + + if (scene.mesh.faceFaces.empty()) { + scene.mesh.ListIncidenteFaceFaces(); + } + + VERBOSE("5[AfterVis] faceFaces.empty=%d faceFaces.size=%d faceFaces[0]={%u,%u,%u}", + (int)scene.mesh.faceFaces.empty(), + (int)scene.mesh.faceFaces.size(), + scene.mesh.faceFaces.empty() ? 0xFFFFFFFF : scene.mesh.faceFaces[0][0], + scene.mesh.faceFaces.empty() ? 0xFFFFFFFF : scene.mesh.faceFaces[0][1], + scene.mesh.faceFaces.empty() ? 0xFFFFFFFF : scene.mesh.faceFaces[0][2]); // ---------- 1. 初始视图分配 ---------- size_t emptyCandidateViews = 0; size_t fallbackByCenterFace = 0; size_t successVF = 0; - + for (size_t i = 0; i < virtualFaceMap.size(); ++i) { const VirtualFace& vf = virtualFaceMap[i]; if (vf.faces.empty()) continue; @@ -15274,7 +15375,6 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( FIndex faceID = vf.faces[0]; if (faceID >= faceNeighbors.size()) continue; - // 收集候选视图 std::unordered_set candidateViews; for (FIndex fid : vf.faces) { if (fid >= faceNeighbors.size()) continue; @@ -15287,7 +15387,6 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( if (candidateViews.empty()) { ++emptyCandidateViews; - // 兜底:用第一个可用视图 IIndex forcedView = (!views.empty()) ? views[0] : (!images.empty()) ? 0 : NO_ID; if (forcedView != NO_ID) { @@ -15296,7 +15395,7 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( for (FIndex fid : vf.faces) { if (fid < faceToView.size()) { faceToView[fid] = forcedView; - faceScores[fid] = 1.0f; // 兜底视图给中等评分 + faceScores[fid] = 1.0f; } } ++fallbackByCenterFace; @@ -15308,17 +15407,14 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( const Point3f& v0 = scene.mesh.vertices[face[0]]; const Point3f& v1 = scene.mesh.vertices[face[1]]; const Point3f& v2 = scene.mesh.vertices[face[2]]; - Point3f faceCenter = (v0 + v1 + v2) / 3.0f; Point3f faceNormal = scene.mesh.faceNormals[faceID]; - float norm = std::sqrt(faceNormal.x * faceNormal.x + - faceNormal.y * faceNormal.y + - faceNormal.z * faceNormal.z); + float norm = std::sqrt(faceNormal.x * faceNormal.x + + faceNormal.y * faceNormal.y + + faceNormal.z * faceNormal.z); if (norm > FLT_EPSILON) { - faceNormal.x /= norm; - faceNormal.y /= norm; - faceNormal.z /= norm; + faceNormal.x /= norm; faceNormal.y /= norm; faceNormal.z /= norm; } else { faceNormal = Point3f(0, 0, 1); } @@ -15328,38 +15424,101 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( for (IIndex vid : candidateViews) { if (vid >= (IIndex)images.size()) continue; - + const Image& img = images[vid]; if (img.image.empty() || img.image.cols < 2 || img.image.rows < 2) continue; - + const Camera& cam = img.camera; - - // 1. 正面度评分 + float s_normal = ComputeViewNormalScore(faceNormal, cam, faceCenter); if (s_normal <= 0.0f) continue; - - // 2. 分辨率评分 + float s_resolution = ComputeResolutionScore( - cam, v0, v1, v2, - img.image.cols, img.image.rows - ); - - // 3. 遮挡惩罚 + cam, v0, v1, v2, img.image.cols, img.image.rows); + Point2f p0 = cam.ProjectPoint(Point3d(v0)); Point2f p1 = cam.ProjectPoint(Point3d(v1)); Point2f p2 = cam.ProjectPoint(Point3d(v2)); float s_occlusion = ComputeOcclusionPenalty( - p0, p1, p2, - img.image.cols, img.image.rows, - 8 - ); - - // 综合评分 - float score = 1.0f * s_normal - + 0.7f * s_resolution - - 0.3f * s_occlusion; - + p0, p1, p2, img.image.cols, img.image.rows, 8); + + float rawScore = 1.0f * s_normal + + 0.7f * s_resolution + - 0.3f * s_occlusion; + float score = rawScore; + + // ========== 颜色一致性代价 ========== + { + const float lambda = 200.35f; + + std::vector neighborColors; + if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) { + const Mesh::Face& topoNeighbors = scene.mesh.faceFaces[faceID]; + for (int k = 0; k < 3; ++k) { + FIndex nb = topoNeighbors[k]; + if (nb == NO_ID || nb >= (FIndex)faceToView.size()) continue; + if (!scene.mesh.faceFaces.empty() && faceID < scene.mesh.faceFaces.size()) { + const SEACAVE::TPoint3& nbrs = scene.mesh.faceFaces[faceID]; + for (int ni = 0; ni < 3; ++ni) { + unsigned int nbrFace = nbrs[ni]; + if (nbrFace == 0xFFFFFFFF) continue; // 边界边没有邻居(NO_ID) + if (nbrFace >= (unsigned int)faceNeighbors.size()) continue; + if (faceNeighbors[nbrFace].empty()) continue; + + unsigned int nbrView = (unsigned int)faceNeighbors[nbrFace][0]; + if (nbrView == 0xFFFFFFFF) continue; + if (nbrView < (unsigned int)g_avgColors.size()) { + neighborColors.push_back(g_avgColors[nbrView]); + } + } + } + } + } + + float colorDiff = 0.0f; + if (!neighborColors.empty()) { + cv::Vec3f candidateColor = g_avgColors[vid]; + // cv::Vec3f candidateColor = SampleColorAtPoint(images[vid].image, images[vid].camera, faceCenter); + if (faceID == 50000) { + if (candidateColor[0] < 0) { + VERBOSE("[ColorDebug] face=50000 vid=%d -> candidateColor FAILED (val=%.1f,%.1f,%.1f)", + (int)vid, candidateColor[0], candidateColor[1], candidateColor[2]); + } + // 同时打印 g_avgColors[vid] 的原始值 + if (vid < g_avgColors.size()) { + VERBOSE("[ColorDebug] g_avgColors[%d] = (%.1f,%.1f,%.1f)", + (int)vid, g_avgColors[vid][0], g_avgColors[vid][1], g_avgColors[vid][2]); + } else { + VERBOSE("[ColorDebug] vid=%d OUT OF BOUNDS! g_avgColors.size=%d", + (int)vid, (int)g_avgColors.size()); + } + } + if (candidateColor[0] >= 0) { + for (const auto& nc : neighborColors) { + cv::Vec3f d = candidateColor - nc; + colorDiff += std::sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2]); + } + colorDiff = (colorDiff / neighborColors.size()) / 1.732f; + colorDiff = std::min(colorDiff, 1.0f); + + // ✅ 修复:不再乘 score,避免放大效应 + score -= lambda * colorDiff; + } + } + + // ---- 调试日志(前20个面 + 每5万面)---- + static int g_dbg = 0; + g_dbg++; + if (g_dbg <= 20 || faceID % 50000 == 0) { + VERBOSE("[Score] face=%d view=%d rawScore=%.3f nbrColors=%d " + "colorDiff=%.3f penalty=%.3f finalScore=%.3f", + faceID, vid, rawScore, (int)neighborColors.size(), + colorDiff, lambda * colorDiff, score); + } + } + // ========== 颜色代价结束 ========== + if (score > bestScore) { bestScore = score; bestView = vid; @@ -15368,12 +15527,11 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( if (bestView != NO_ID) { faceViews[i].push_back(bestView); - faceViewWeights[i].push_back(bestScore); // ✅ 存储实际评分,不是固定值 - + faceViewWeights[i].push_back(bestScore); for (FIndex fid : vf.faces) { if (fid < faceToView.size()) { faceToView[fid] = bestView; - faceScores[fid] = bestScore; // ✅ 记录面的评分 + faceScores[fid] = bestScore; } } ++successVF; @@ -15381,32 +15539,23 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( } // ---------- 2. 改进的 Patch 一致性传播 ---------- - if (scene.mesh.faceFaces.empty()) { - scene.mesh.ListIncidenteFaceFaces(); - } const int PROPAGATION_ITER = 2; for (int iter = 0; iter < PROPAGATION_ITER; ++iter) { std::vector newFaceToView = faceToView; - std::vector newFaceScores = faceScores; // ✅ 同步更新评分 + std::vector newFaceScores = faceScores; for (FIndex fid = 0; fid < (FIndex)faceToView.size(); ++fid) { if (faceToView[fid] == NO_ID) continue; if (fid >= (FIndex)scene.mesh.faceFaces.size()) continue; const Mesh::Face& neighbors = scene.mesh.faceFaces[fid]; - - // ✅ 获取当前面的评分 float currentScore = faceScores[fid]; - - // ✅ 如果当前视图质量已经很高(>0.8),锁死,不参与传播 - if (currentScore > 0.8f) { - continue; - } + + if (currentScore > 0.8f) continue; // 锁死高质量面 std::unordered_map vote; vote[faceToView[fid]] = 1; - for (int k = 0; k < 3; ++k) { FIndex nb = neighbors[k]; if (nb == NO_ID) continue; @@ -15424,12 +15573,10 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( } } - // ✅ 改进的传播条件 if (maxVote >= 3 && majorityView != faceToView[fid]) { - // 获取多数视图在邻居中的平均评分 + // 多数视图在邻居中的平均评分 float majorityAvgScore = 0.0f; int count = 0; - for (int k = 0; k < 3; ++k) { FIndex nb = neighbors[k]; if (nb == NO_ID) continue; @@ -15438,40 +15585,85 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( count++; } } - - if (count > 0) { - majorityAvgScore /= count; + if (count > 0) majorityAvgScore /= count; + + // ========== 颜色一致性检查(安全闸,优先于评分)========== + bool colorBlocked = false; + { + cv::Vec3f majorityColor(0, 0, 0); + int colorCount = 0; + for (int k = 0; k < 3; ++k) { + FIndex nb = neighbors[k]; + if (nb == NO_ID || nb >= (FIndex)faceToView.size()) continue; + if (faceToView[nb] == majorityView) { + Point3f nbCenter = (scene.mesh.vertices[scene.mesh.faces[nb][0]] + + scene.mesh.vertices[scene.mesh.faces[nb][1]] + + scene.mesh.vertices[scene.mesh.faces[nb][2]]) / 3.0f; + // cv::Vec3f col = SampleColorAtPoint( + // images[majorityView].image, + // images[majorityView].camera, + // nbCenter + // ); + cv::Vec3f col = g_avgColors[majorityView]; + + if (col[0] >= 0) { + majorityColor += col; + colorCount++; + } + } + } + + if (colorCount > 0) { + majorityColor /= (float)colorCount; + Point3f curCenter = (scene.mesh.vertices[scene.mesh.faces[fid][0]] + + scene.mesh.vertices[scene.mesh.faces[fid][1]] + + scene.mesh.vertices[scene.mesh.faces[fid][2]]) / 3.0f; + // cv::Vec3f curColor = SampleColorAtPoint( + // images[faceToView[fid]].image, + // images[faceToView[fid]].camera, + // curCenter + // ); + cv::Vec3f curColor = g_avgColors[faceToView[fid]]; + if (curColor[0] >= 0) { + cv::Vec3f d = curColor - majorityColor; + float colorDiff = std::sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2]) / 1.732f; + + if (fid % 50000 == 0) { + VERBOSE("[Propagate] face=%d majorityView=%d curView=%d colorDiff=%.3f", + fid, majorityView, faceToView[fid], colorDiff); + } + + if (colorDiff > 0.4f) { + colorBlocked = true; // ✅ 阻断传播 + } + } + } } - - // ✅ 条件1:当前评分较低才考虑传播 - // ✅ 条件2:多数视图的评分与当前评分相差不大(防止引入更差的视图) - // ✅ 条件3:或者多数视图明显更好 + // ========== 颜色检查结束 ========== + + // ✅ 只有颜色没被阻断时才考虑评分条件 bool shouldPropagate = false; - - if (currentScore < 0.3f) { - // 当前评分很低,只要多数视图不是特别差就接受 - shouldPropagate = (majorityAvgScore > currentScore - 0.2f); - } else if (currentScore < 0.6f) { - // 当前评分中等,要求多数视图相当或更好 - shouldPropagate = (majorityAvgScore > currentScore - 0.1f); - } else { - // 当前评分较高(0.6-0.8之间),要求多数视图明显更好 - shouldPropagate = (majorityAvgScore > currentScore + 0.05f); - } - - // ✅ 额外保护:如果当前评分已经不错,且多数视图没有显著优势,保持原样 - if (currentScore > 0.6f && std::abs(majorityAvgScore - currentScore) < 0.1f) { - shouldPropagate = false; + if (!colorBlocked) { + if (currentScore < 0.3f) { + shouldPropagate = (majorityAvgScore > currentScore - 0.2f); + } else if (currentScore < 0.6f) { + shouldPropagate = (majorityAvgScore > currentScore - 0.1f); + } else { + shouldPropagate = (majorityAvgScore > currentScore + 0.05f); + } + if (currentScore > 0.6f && std::abs(majorityAvgScore - currentScore) < 0.1f) { + shouldPropagate = false; + } } - + if (shouldPropagate) { newFaceToView[fid] = majorityView; - newFaceScores[fid] = majorityAvgScore; // ✅ 更新为邻居的平均评分 + newFaceScores[fid] = majorityAvgScore; } } } faceToView.swap(newFaceToView); - faceScores.swap(newFaceScores); // ✅ 同步更新评分 + faceScores.swap(newFaceScores); } // ---------- 3. 写回 ---------- @@ -15482,10 +15674,9 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( FIndex firstFace = vf.faces[0]; if (firstFace < faceToView.size() && faceToView[firstFace] != NO_ID) { IIndex consistentView = faceToView[firstFace]; - float consistentScore = faceScores[firstFace]; // ✅ 使用更新后的评分 - + float consistentScore = faceScores[firstFace]; faceViews[i] = {consistentView}; - faceViewWeights[i] = {consistentScore}; // ✅ 更新权重为实际评分 + faceViewWeights[i] = {consistentScore}; } } @@ -19784,23 +19975,21 @@ bool Scene::TextureMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsi return false; } - // ✅ 确保拓扑信息存在 - if (mesh.faceFaces.empty()) { - mesh.ListIncidenteFaces(); - mesh.ListIncidenteFaceFaces(); - } - if (mesh.faceNormals.empty()) { - mesh.ComputeNormalFaces(); - } - if (mesh.vertexBoundary.empty()) { - mesh.ListBoundaryVertices(); - } + // ✅ 强制重建拓扑(不能信任 empty() 检查,因为 Load 可能只 resize 不清空) + mesh.faceFaces.clear(); // ← 关键!强制让它变 empty + // mesh.faceVertices.clear(); // ← ListIncidenteFaces 填充的 + mesh.ListIncidenteFaces(); + mesh.ListIncidenteFaceFaces(); + mesh.ComputeNormalFaces(); + mesh.ListBoundaryVertices(); // ✅ 1. 创建虚拟面(每三角形一个) MeshTexture::VirtualFaceMap virtualFaceMap; if (!texture.CreateVirtualFacesForExistingUV(virtualFaceMap)) return false; + texture.EstimateGlobalPhotometricCorrection(); + // // ✅ 2. 计算可见性(填充 faceNeighbors) // if (!texture.ComputePureFaceVisibility( // fOutlierThreshold, nIgnoreMaskLabel, views)) {