From b89bf91ccf1db435f4db7666ad2d89c37ad9636e Mon Sep 17 00:00:00 2001 From: hesuicong Date: Thu, 13 Aug 2026 17:42:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=A8=E5=B1=80=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 434 ++++++++++++++++++++++++++------------ 1 file changed, 295 insertions(+), 139 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 1cbda9b..fb302f0 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -1091,6 +1091,34 @@ public: void ProjectFaceToTexture(FIndex faceID, IIndex viewID, const TexCoord* uv, Image8U3& texture); bool PointInTriangle(const Point2f& p, const Point2f& a, const Point2f& b, const Point2f& c, Point3f& bary); int ComputeOptimalTextureSize(float uvWidth, float uvHeight, unsigned multiple); + std::vector> m_virtualFaceViewWeights; + // ===== Seam 相关结构 ===== + struct SeamEdge { + uint32_t rcPatchID0; + uint32_t rcPatchID1; + FIndex faceID0; // 对应第一个面的VirtualFaceGeometry索引 + FIndex faceID1; // 对应第二个面的VirtualFaceGeometry索引 + Point2f uv0; // atlas上边的起点UV + Point2f uv1; // atlas上边的终点UV + }; + + // ===== RCPatch(你已有的,确认包含以下字段)===== + struct RCPatch { + IIndex viewID; + cv::Rect rect; + std::vector faces; + Point2f uvMin, uvMax; + }; + + std::vector rcSeamEdges; + std::vector rcPatches; + int currentTextureSize; + + // ===== 函数声明 ===== + Color SampleImageBilinear(const cv::Mat& img, float x, float y); + void BuildSeamEdgesFromRCPatches(); + void SeamBlendingFromOriginalImages(Image8U3& atlas); + // Bruce //* template @@ -14254,7 +14282,7 @@ void MeshTexture::FillTextureHoles(std::vector& textures, Pixel8U colE } // ============================================================ -// 3. RC 风格光栅化主函数 +// 3. RC 风格光栅化主函数(含接缝优化) // ============================================================ bool MeshTexture::RasterizeVirtualFaces( const VirtualFaceMap& virtualFaceMap, @@ -14277,17 +14305,14 @@ bool MeshTexture::RasterizeVirtualFaces( for (const TexCoord& uv : scene.mesh.faceTexcoords) uvBounds.InsertFull(uv); - float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x(); + float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x(); float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y(); - if (uvWidth < 0.001f) uvWidth = 1.0f; + if (uvWidth < 0.001f) uvWidth = 1.0f; if (uvHeight < 0.001f) uvHeight = 1.0f; - // int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); - int textureSize = ComputeOptimalTextureSizeAdaptive( + int textureSize = ComputeOptimalTextureSizeAdaptive( virtualFaceMap, virtualFaceViews, nTextureSizeMultiple); - - // 兜底 - if (textureSize < 1024) textureSize = 1024; + if (textureSize < 1024) textureSize = 1024; if (textureSize > 16384) textureSize = 16384; // -------------------------------------------------- @@ -14297,17 +14322,17 @@ bool MeshTexture::RasterizeVirtualFaces( Image8U3& atlas = outTextures.back(); atlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); - // ✅ 初始化评分缓冲 - m_texelScores.assign(textureSize * textureSize, TexelScore{}); + m_texelScores.assign(textureSize * textureSize, TexelScore{}); - // ✅ 计算所有虚拟面的几何和映射矩阵 if (!ComputeVirtualFaceGeometry(virtualFaceMap)) { DEBUG_EXTRA("Failed to compute virtual face geometries"); return false; } + currentTextureSize = textureSize; + // -------------------------------------------------- - // 3. RC 风格光栅化:按虚拟面批量处理 + // 3. RC 风格光栅化 // -------------------------------------------------- #ifdef _USE_OPENMP #pragma omp parallel for schedule(dynamic) @@ -14326,129 +14351,277 @@ bool MeshTexture::RasterizeVirtualFaces( if (srcImg.image.empty() || srcImg.image.cols < 2 || srcImg.image.rows < 2) continue; - // ✅ UV 包围盒 → 纹理像素范围 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; int patchH = maxY - minY + 1; - // ✅ 映射矩阵 cv::Mat mapX(patchH, patchW, CV_32FC1); cv::Mat mapY(patchH, patchW, CV_32FC1); - // ✅ 直接展开 H 系数(无临时 Mat,RC 标准写法) 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 w = H[6] * u + H[7] * v + H[8]; - - // ✅ 数值保护(防止除零) + 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; + mapX.at(y-minY, x-minX) = -1.0f; + mapY.at(y-minY, x-minX) = -1.0f; continue; } - - float imgX = (H[0] * u + H[1] * v + H[2]) / w; - float imgY = (H[3] * u + H[4] * v + H[5]) / w; - - mapX.at(y - minY, x - minX) = imgX; - mapY.at(y - minY, x - minX) = imgY; + 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; } } - // ✅ 一次性 remap 整个 patch cv::Mat patch; cv::remap(srcImg.image, patch, mapX, mapY, - cv::INTER_LINEAR, cv::BORDER_CONSTANT, - cv::Scalar(0, 0, 0)); + cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0)); - // ✅ 拷贝到 atlas(OpenMP critical 区) - #pragma omp critical - { - for (int y = 0; y < patchH; ++y) { - for (int x = 0; x < patchW; ++x) { - // 在 RasterizeVirtualFaces 的像素写入循环中 - // ✅ 只保留最核心的逻辑,不做任何衰减 + #pragma omp critical + { + 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; + + 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) { + atlas(atlasY, atlasX) = Pixel8U{color[2], color[1], color[0]}; + ts.score = currentScore; + ts.viewID = viewID; + } + } + } + } + } - cv::Vec3b color = patch.at(y, x); - if (color[0] == 0 && color[1] == 0 && color[2] == 0) - continue; + m_texelScores.clear(); + DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); - int atlasX = x + minX; - int atlasY = y + minY; - if (atlasX < 0 || atlasX >= textureSize || - atlasY < 0 || atlasY >= textureSize) - continue; + m_virtualFaceViewWeights = virtualFaceViewWeights; - size_t idx = atlasY * textureSize + atlasX; - TexelScore& ts = m_texelScores[idx]; + // -------------------------------------------------- + // 4. 构建 RCPatch + // -------------------------------------------------- + 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; + 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); + } + DEBUG_EXTRA("Created %zu RC patches", rcPatches.size()); - float currentScore = virtualFaceViewWeights[i].empty() - ? -1.0f - : virtualFaceViewWeights[i][0]; + // -------------------------------------------------- + // 5. 构建接缝边 + // -------------------------------------------------- + BuildSeamEdgesFromRCPatches(); - // ✅ 正常写入,不衰减,不修改 - if (currentScore > ts.score) { - atlas(atlasY, atlasX) = - Pixel8U{color[2], color[1], color[0]}; - ts.score = currentScore; - ts.viewID = viewID; - } - } - } - } + // -------------------------------------------------- + // 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()); } - m_texelScores.clear(); + return true; +} - DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); - - // ✅ 新增:接缝融合后处理 - if (true) { // 可配置开关 - TD_TIMER_START(); - - // 1. 检测需要融合的接缝 - std::vector seams; - if (!DetectSeamsForBlending(virtualFaceMap, virtualFaceViews, seams)) { - DEBUG_EXTRA("Seam detection failed"); - return true; // 不影响主流程 +// ============================================================ +// 构建接缝边(基于 rcPatches + faceFaces) +// ============================================================ +void MeshTexture::BuildSeamEdgesFromRCPatches() +{ + rcSeamEdges.clear(); + + // face → rcPatch 映射 + std::vector faceToRCPatch(scene.mesh.faces.size(), NO_ID); + for (uint32_t pi = 0; pi < rcPatches.size(); ++pi) { + for (FIndex f : rcPatches[pi].faces) { + if (f < (FIndex)faceToRCPatch.size()) + faceToRCPatch[f] = pi; } - // // 2. 方向性高斯羽化 - // FeatherSeams(seams, textureSize, atlas); + } - // 2. 对每个接缝进行融合 - for (const SeamInfo& seam : seams) { - const VirtualFaceGeometry& geomA = m_virtualFaceGeometries[seam.faceA]; - const VirtualFaceGeometry& geomB = m_virtualFaceGeometries[seam.faceB]; - - // 提取接缝两侧像素 - std::vector pixelsA, pixelsB; - ExtractSeamPixels(seam, geomA, geomB, textureSize, pixelsA, pixelsB); - - if (!pixelsA.empty() || !pixelsB.empty()) { - // 执行融合 - BlendSeamPixels(seam, pixelsA, pixelsB, geomA, geomB, textureSize, atlas); + for (FIndex f0 = 0; f0 < (FIndex)scene.mesh.faces.size(); ++f0) { + uint32_t p0 = faceToRCPatch[f0]; + if (p0 == NO_ID) continue; + + const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[f0]; + for (int e = 0; e < 3; ++e) { + FIndex f1 = neighbors[e]; + if (f1 == NO_ID) continue; + + 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; + if (scene.mesh.faces[f1][k] == face0[v1_idx]) idx1_in_f1 = k; } + 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]; + + // ---------- 构建接缝边 ---------- + 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 边中点 + + rcSeamEdges.push_back(edge); } - - DEBUG_EXTRA("Seam blending completed: %zu seams processed (%s)", - seams.size(), TD_TIMER_GET_FMT().c_str()); } - DEBUG_EXTRA("RC-style Rasterization completed: %s", TD_TIMER_GET_FMT().c_str()); - return true; + DEBUG_EXTRA("Built %zu RC seam edges", rcSeamEdges.size()); } +// ============================================================ +// 接缝融合:从原始图像采样 + YCrCb 羽化 +// ============================================================ +void MeshTexture::SeamBlendingFromOriginalImages(Image8U3& atlas) +{ + const int R = 6; // 过渡半径(像素),头发区域建议≥6 + + for (const SeamEdge& edge : rcSeamEdges) { + if (edge.rcPatchID0 >= rcPatches.size() || edge.rcPatchID1 >= rcPatches.size()) + continue; + if (edge.faceID0 >= (FIndex)m_virtualFaceGeometries.size() || + edge.faceID1 >= (FIndex)m_virtualFaceGeometries.size()) + continue; + + const RCPatch& patch0 = rcPatches[edge.rcPatchID0]; + const RCPatch& patch1 = rcPatches[edge.rcPatchID1]; + if (patch0.viewID >= (IIndex)images.size() || patch1.viewID >= (IIndex)images.size()) + continue; + + const cv::Mat& img0 = images[patch0.viewID].image; + const cv::Mat& img1 = images[patch1.viewID].image; + if (img0.empty() || img1.empty()) continue; + + // 1. 取两个面对应的单应矩阵(光栅化时用的同一个!) + const VirtualFaceGeometry& geom0 = m_virtualFaceGeometries[edge.faceID0]; + const VirtualFaceGeometry& geom1 = m_virtualFaceGeometries[edge.faceID1]; + if (!geom0.isValid || !geom1.isValid) continue; + + // 2. 计算边的方向和垂直法向(atlas UV空间) + Point2f edgeDir = edge.uv1 - edge.uv0; + float edgeLenUV = std::sqrt(edgeDir.x*edgeDir.x + edgeDir.y*edgeDir.y); + if (edgeLenUV < 1e-6f) continue; + Point2f edgeDirNorm = edgeDir / edgeLenUV; + Point2f perpDir(-edgeDirNorm.y, edgeDirNorm.x); // 垂直于边的法向 + + // 3. 沿边多采样(长边必须多采,避免漏采高频细节) + int numEdgeSamples = std::max(1, (int)(edgeLenUV * currentTextureSize)); + for (int s = 0; s < numEdgeSamples; ++s) { + float tEdge = (float)s / (float)numEdgeSamples; + Point2f edgeUV = edge.uv0 * (1.0f - tEdge) + edge.uv1 * tEdge; // atlas上的边点UV + + // 4. 沿垂直边的方向扩展过渡带(核心!) + for (int r = -R; r <= R; ++r) { + float tBlend = (float)(r + R) / (2.0f * R); // 混合权重:r<0偏patch0,r>0偏patch1 + float blendW = 1.0f - std::abs(tBlend - 0.5f) * 2.0f; // 中心权重高 + + // atlas UV偏移(垂直于边的方向) + float offsetUV = (float)r / (float)currentTextureSize; + Point2f curUV = edgeUV + perpDir * offsetUV; + + // 5. 用单应矩阵把atlas UV映射到两个视图的图像坐标(和光栅化逻辑完全一致!) + // 映射patch0的视图 + float w0 = geom0.homography.at(2,0)*curUV.x + geom0.homography.at(2,1)*curUV.y + geom0.homography.at(2,2); + if (std::abs(w0) < 1e-12f) continue; + float imgX0 = (geom0.homography.at(0,0)*curUV.x + geom0.homography.at(0,1)*curUV.y + geom0.homography.at(0,2)) / w0; + float imgY0 = (geom0.homography.at(1,0)*curUV.x + geom0.homography.at(1,1)*curUV.y + geom0.homography.at(1,2)) / w0; + + // 映射patch1的视图 + float w1 = geom1.homography.at(2,0)*curUV.x + geom1.homography.at(2,1)*curUV.y + geom1.homography.at(2,2); + if (std::abs(w1) < 1e-12f) continue; + float imgX1 = (geom1.homography.at(0,0)*curUV.x + geom1.homography.at(0,1)*curUV.y + geom1.homography.at(0,2)) / w1; + float imgY1 = (geom1.homography.at(1,0)*curUV.x + geom1.homography.at(1,1)*curUV.y + geom1.homography.at(1,2)) / w1; + + // 6. 从两个原始视图采样同一个三维点的颜色 + Color col0 = RGB2YCBCR(SampleImageBilinear(img0, imgX0, imgY0)); + Color col1 = RGB2YCBCR(SampleImageBilinear(img1, imgX1, imgY1)); + + // 7. 转换到atlas像素坐标 + int px = (int)(curUV.x * currentTextureSize); + int py = (int)(curUV.y * currentTextureSize); + if (px < 0 || px >= currentTextureSize || py < 0 || py >= currentTextureSize) + continue; + + // 8. 结合评分系统:只覆盖评分更低的像素(避免破坏最优视图) + size_t idx = py * currentTextureSize + px; + if (idx >= m_texelScores.size()) continue; + float currentScore = m_texelScores[idx].score; + float edgeScore0 = m_virtualFaceViewWeights[edge.faceID0].empty() ? -1 : m_virtualFaceViewWeights[edge.faceID0][0]; + float edgeScore1 = m_virtualFaceViewWeights[edge.faceID1].empty() ? -1 : m_virtualFaceViewWeights[edge.faceID1][0]; + // 只有当混合后的权重对应的视图评分更高时才覆盖 + if (tBlend < 0.5f && edgeScore0 <= currentScore) continue; + if (tBlend >= 0.5f && edgeScore1 <= currentScore) continue; + + // 9. 混合颜色(YCrCb空间,避免亮度偏移) + Color mixedYCbCr = col0 * (1.0f - tBlend) + col1 * tBlend; + Color mixedRGB = YCBCR2RGB(mixedYCbCr); + + // 10. 写入atlas + Pixel8U& p = atlas(py, px); + p = Pixel8U( + CLAMP((int)roundf(mixedRGB.x), 0, 255), + CLAMP((int)roundf(mixedRGB.y), 0, 255), + CLAMP((int)roundf(mixedRGB.z), 0, 255) + ); + } + } + } +} + float MeshTexture::EstimatePixelSize(const Point3f& faceCenter, const Normal& faceNormal, const Image& image) { const Camera& cam = image.camera; @@ -18193,52 +18366,6 @@ bool PointInTriangle(const Point2f& p, const Point2f& a, const Point2f& b, const barycentric.y >= 0 && barycentric.y <= 1 && barycentric.z >= 0 && barycentric.z <= 1); } -// 辅助函数:从图像中双线性插值采样颜色 -// 修正颜色顺序和边界处理 -Pixel8U SampleImageBilinear(const Image8U3& image, const Point2f& point) { - // 边界检查,防止越界 - float x = CLAMP(point.x, 0.0f, (float)(image.cols - 1)); - float y = CLAMP(point.y, 0.0f, (float)(image.rows - 1)); - - int x0 = (int)floor(x); - int y0 = (int)floor(y); - int x1 = std::min(x0 + 1, image.cols - 1); - int y1 = std::min(y0 + 1, image.rows - 1); - - // 确保x0,y0不会超出下界 - x0 = std::max(0, x0); - y0 = std::max(0, y0); - - float dx = x - x0; - float dy = y - y0; - float dx1 = 1.0f - dx; - float dy1 = 1.0f - dy; - - // 获取四个角点的像素 - const Pixel8U& p00 = image(y0, x0); - const Pixel8U& p01 = image(y0, x1); - const Pixel8U& p10 = image(y1, x0); - const Pixel8U& p11 = image(y1, x1); - - // 方法1:使用结构体成员访问(推荐) - float b = p00.b * dx1 * dy1 + p01.b * dx * dy1 + p10.b * dx1 * dy + p11.b * dx * dy; - float g = p00.g * dx1 * dy1 + p01.g * dx * dy1 + p10.g * dx1 * dy + p11.g * dx * dy; - float r = p00.r * dx1 * dy1 + p01.r * dx * dy1 + p10.r * dx1 * dy + p11.r * dx * dy; - - /* - // 方法2:如果Pixel8U支持[]操作符 - // 注意:OpenMVS的Pixel8U可能是BGR或RGB顺序,需要根据实际情况调整 - float b = p00[0] * dx1 * dy1 + p01[0] * dx * dy1 + p10[0] * dx1 * dy + p11[0] * dx * dy; - float g = p00[1] * dx1 * dy1 + p01[1] * dx * dy1 + p10[1] * dx1 * dy + p11[1] * dx * dy; - float r = p00[2] * dx1 * dy1 + p01[2] * dx * dy1 + p10[2] * dx1 * dy + p11[2] * dx * dy; - */ - - return Pixel8U( - (unsigned char)CLAMP(b, 0.0f, 255.0f), - (unsigned char)CLAMP(g, 0.0f, 255.0f), - (unsigned char)CLAMP(r, 0.0f, 255.0f) - ); -} void FillTextureGaps2(Image8U3& textureAtlas, const Mesh::TexCoordArr& faceTexcoords, FIndex nFaces, const MeshTexture::LabelArr& faceLabels, @@ -18721,6 +18848,35 @@ bool MeshTexture::ValidateProjection(const Vertex& worldPoint, return true; } +// ============================================================ +// 双线性采样(从原始图像按 UV 采样) +// ============================================================ +MeshTexture::Color MeshTexture::SampleImageBilinear(const cv::Mat& img, float x, float y) +{ + if (img.empty() || x < 0 || x >= img.cols-1 || y < 0 || y >= img.rows-1) + return Color(0,0,0); + + int xi = (int)x; + int yi = (int)y; + float dx = x - xi; + float dy = y - yi; + + cv::Vec3b p00 = img.at(yi, xi); + cv::Vec3b p01 = img.at(yi+1, xi); + cv::Vec3b p10 = img.at(yi, xi+1); + cv::Vec3b p11 = img.at(yi+1, xi+1); + + Color c; + for (int k = 0; k < 3; ++k) { + float v00 = (k == 0) ? p00[2] : (k == 1) ? p00[1] : p00[0]; + float v01 = (k == 0) ? p01[2] : (k == 1) ? p01[1] : p01[0]; + float v10 = (k == 0) ? p10[2] : (k == 1) ? p10[1] : p10[0]; + float v11 = (k == 0) ? p11[2] : (k == 1) ? p11[1] : p11[0]; + c[k] = v00*(1-dx)*(1-dy) + v01*(1-dx)*dy + v10*dx*(1-dy) + v11*dx*dy; + } + return c; +} + Pixel8U MeshTexture::SampleImageBilinear(const Image8U3& image, const Point2f& point) { const int x1 = (int)point.x; const int y1 = (int)point.y;