diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 2edfe6d..2cce5a1 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -82,6 +82,31 @@ using namespace MVS; #if TEXOPT_INFERENCE == TEXOPT_INFERENCE_LBP #include "../Math/LBP.h" namespace MVS { + + +// 2. 重心坐标计算(就地实现) +inline bool ComputeBarycentricCoords(const Point2f& p, + const Point2f& a, + const Point2f& b, + const Point2f& c, + Point2f& uv) { + const Point2f v0 = b - a; + const Point2f v1 = c - a; + const Point2f v2 = p - a; + const float d00 = v0.dot(v0); + const float d01 = v0.dot(v1); + const float d11 = v1.dot(v1); + const float d20 = v2.dot(v0); + const float d21 = v2.dot(v1); + const float denom = d00 * d11 - d01 * d01; + if (fabs(denom) < 1e-10f) return false; + const float v = (d11 * d20 - d01 * d21) / denom; + const float w = (d00 * d21 - d01 * d20) / denom; + uv.x = 1.0f - v - w; // u + uv.y = v; // v + return true; +} + typedef LBPInference::NodeID NodeID; // Potts model as smoothness function LBPInference::EnergyType STCALL SmoothnessPotts(LBPInference::NodeID, LBPInference::NodeID, LBPInference::LabelID l1, LBPInference::LabelID l2) { @@ -472,6 +497,7 @@ public: bool CreateVirtualFaces63(FaceDataViewArr& facesDatas, FaceDataViewArr& virtualFacesDatas, VirtualFaceIdxsArr& virtualFaces, std::vector& isVirtualFace, unsigned minCommonCameras=2, float thMaxNormalDeviation=25.f) const; bool CreateVirtualFaces64(FaceDataViewArr& facesDatas, FaceDataViewArr& virtualFacesDatas, VirtualFaceIdxsArr& virtualFaces, std::vector& isVirtualFace, unsigned minCommonCameras=2, float thMaxNormalDeviation=25.f) const; bool CreateVirtualFaces65(FaceDataViewArr& facesDatas, FaceDataViewArr& virtualFacesDatas, VirtualFaceIdxsArr& virtualFaces, std::vector& isVirtualFace, unsigned minCommonCameras=2, float thMaxNormalDeviation=25.f) const; + void CreateVirtualFaces8(const FaceDataViewArr& facesDatas, FaceDataViewArr& virtualFacesDatas, VirtualFaceIdxsArr& virtualFaces, unsigned minCommonCameras=2, float thMaxNormalDeviation=25.f) const; static std::string GetFileNameWithoutExtension(std::string& strPath); float CalculateBrightnessScore(const Image& imageData) const; @@ -504,7 +530,7 @@ public: void LocalSeamLeveling(); void LocalSeamLeveling3(); void GenerateTexture(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& baseFileName, bool bOriginFaceview); - void GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& baseFileName); + void GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& baseFileName, bool bOriginFaceview); // Bruce //* @@ -605,7 +631,18 @@ public: return distances[distances.size() / 2]; } + // 在 MeshTexture 类的 public 或 private 区域添加: + struct FaceCandidates { + IIndex primaryView = NO_ID; + std::vector candidateViews; // Top-N 视图索引 + std::vector candidateWeights; // 对应的权重 + }; + + // 成员变量 + cList faceCandidates; + //*/ + /* // 采用ITU-R BT.601标准系数,增加数值稳定性处理 template @@ -6820,7 +6857,7 @@ bool MeshTexture::CreateVirtualFaces65(FaceDataViewArr& facesDatas, FaceDataView const Image& imageData = images[index]; std::string strPath = imageData.name; std::string strName = MeshTexture::GetFileNameWithoutExtension(strPath); - printf("Top-K index=%d, strName=%s, score=%f\n", index, strName.c_str(), score); + // printf("Top-K index=%d, strName=%s, score=%f\n", index, strName.c_str(), score); auto it_view = mapViewCoverageData.find(index); if (it_view == mapViewCoverageData.end()) { @@ -7455,6 +7492,121 @@ bool MeshTexture::CreateVirtualFaces65(FaceDataViewArr& facesDatas, FaceDataView return true; } + +// build virtual faces with: +// - similar normal +// - high percentage of common images that see them +void MeshTexture::CreateVirtualFaces8(const FaceDataViewArr& facesDatas, FaceDataViewArr& virtualFacesDatas, VirtualFaceIdxsArr& virtualFaces, unsigned minCommonCameras, float thMaxNormalDeviation) const +{ + const float ratioAngleToQuality(0.67f); + const float cosMaxNormalDeviation(COS(FD2R(thMaxNormalDeviation))); + Mesh::FaceIdxArr remainingFaces(faces.size()); + std::iota(remainingFaces.begin(), remainingFaces.end(), 0); + std::vector selectedFaces(faces.size(), false); + cQueue currentVirtualFaceQueue; + std::unordered_set queuedFaces; + do { + const FIndex startPos = RAND() % remainingFaces.size(); + const FIndex virtualFaceCenterFaceID = remainingFaces[startPos]; + ASSERT(currentVirtualFaceQueue.IsEmpty()); + const Normal& normalCenter = scene.mesh.faceNormals[virtualFaceCenterFaceID]; + const FaceDataArr& centerFaceDatas = facesDatas[virtualFaceCenterFaceID]; + // select the common cameras + Mesh::FaceIdxArr virtualFace; + FaceDataArr virtualFaceDatas; + if (centerFaceDatas.empty()) { + virtualFace.emplace_back(virtualFaceCenterFaceID); + selectedFaces[virtualFaceCenterFaceID] = true; + const auto posToErase = remainingFaces.FindFirst(virtualFaceCenterFaceID); + ASSERT(posToErase != Mesh::FaceIdxArr::NO_INDEX); + remainingFaces.RemoveAtMove(posToErase); + } else { + const IIndexArr selectedCams = SelectBestViews(centerFaceDatas, virtualFaceCenterFaceID, minCommonCameras, ratioAngleToQuality); + currentVirtualFaceQueue.AddTail(virtualFaceCenterFaceID); + queuedFaces.clear(); + do { + const FIndex currentFaceId = currentVirtualFaceQueue.GetHead(); + currentVirtualFaceQueue.PopHead(); + // check for condition to add in current virtual face + // normal angle smaller than thMaxNormalDeviation degrees + const Normal& faceNormal = scene.mesh.faceNormals[currentFaceId]; + const float cosFaceToCenter(ComputeAngleN(normalCenter.ptr(), faceNormal.ptr())); + if (cosFaceToCenter < cosMaxNormalDeviation) + continue; + // check if current face is seen by all cameras in selectedCams + ASSERT(!selectedCams.empty()); + if (!IsFaceVisible(facesDatas[currentFaceId], selectedCams)) + continue; + // remove it from remaining faces and add it to the virtual face + { + const auto posToErase = remainingFaces.FindFirst(currentFaceId); + ASSERT(posToErase != Mesh::FaceIdxArr::NO_INDEX); + remainingFaces.RemoveAtMove(posToErase); + selectedFaces[currentFaceId] = true; + virtualFace.push_back(currentFaceId); + } + // add all new neighbors to the queue + const Mesh::FaceFaces& ffaces = faceFaces[currentFaceId]; + for (int i = 0; i < 3; ++i) { + const FIndex fIdx = ffaces[i]; + if (fIdx == NO_ID) + continue; + if (!selectedFaces[fIdx] && queuedFaces.find(fIdx) == queuedFaces.end()) { + currentVirtualFaceQueue.AddTail(fIdx); + queuedFaces.emplace(fIdx); + } + } + } while (!currentVirtualFaceQueue.IsEmpty()); + // compute virtual face quality and create virtual face + for (IIndex idxView: selectedCams) { + FaceData& virtualFaceData = virtualFaceDatas.emplace_back(); + virtualFaceData.quality = 0; + virtualFaceData.idxView = idxView; + #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA + virtualFaceData.color = Point3f::ZERO; + #endif + unsigned processedFaces(0); + for (FIndex fid : virtualFace) { + const FaceDataArr& faceDatas = facesDatas[fid]; + for (FaceData& faceData: faceDatas) { + if (faceData.idxView == idxView) { + virtualFaceData.quality += faceData.quality; + #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA + virtualFaceData.color += faceData.color; + #endif + ++processedFaces; + break; + } + } + } + ASSERT(processedFaces > 0); + + // 修改方案 B:使用第 20% 分位(更鲁棒) + // 收集所有 quality,排序,取较低的那个 + std::vector qualities; + for (FIndex fid : virtualFace) { + const FaceDataArr& faceDatas = facesDatas[fid]; + for (FaceData& faceData: faceDatas) { + if (faceData.idxView == idxView) { + qualities.push_back(faceData.quality); + break; + } + } + } + std::sort(qualities.begin(), qualities.end()); + virtualFaceData.quality = qualities[qualities.size() * 0.2]; // 取第20% + + #if TEXOPT_FACEOUTLIER != TEXOPT_FACEOUTLIER_NA + virtualFaceData.color /= processedFaces; + #endif + } + ASSERT(!virtualFaceDatas.empty()); + } + virtualFacesDatas.emplace_back(std::move(virtualFaceDatas)); + virtualFaces.emplace_back(std::move(virtualFace)); + } while (!remainingFaces.empty()); +} + /** * 计算图像亮度评分,专为纹理生成优化 * @param imageData 输入图像(支持单通道和三通道) @@ -9759,10 +9911,11 @@ bool MeshTexture::FaceViewSelection3( unsigned minCommonCameras, float fOutlierT // CreateVirtualFaces6(facesDatas, virtualFacesDatas, virtualFaces, isVirtualFace, minCommonCameras); // CreateVirtualFaces61(facesDatas, virtualFacesDatas, virtualFaces, isVirtualFace, minCommonCameras); // CreateVirtualFaces62(facesDatas, virtualFacesDatas, virtualFaces, isVirtualFace, minCommonCameras); - CreateVirtualFaces65(facesDatas, virtualFacesDatas, virtualFaces, isVirtualFace, minCommonCameras); + // CreateVirtualFaces65(facesDatas, virtualFacesDatas, virtualFaces, isVirtualFace, minCommonCameras); + CreateVirtualFaces8(facesDatas, virtualFacesDatas, virtualFaces, minCommonCameras); TD_TIMER_STARTD(); // CreateVirtualFaces7(facesDatas, virtualFacesDatas, virtualFaces, isVirtualFace, minCommonCameras); - DEBUG_EXTRA("CreateVirtualFaces completed: %s", TD_TIMER_GET_FMT().c_str()); + // DEBUG_EXTRA("CreateVirtualFaces completed: %s", TD_TIMER_GET_FMT().c_str()); size_t controlCounter(0); FOREACH(idxVF, virtualFaces) { @@ -10791,7 +10944,59 @@ bool MeshTexture::FaceViewSelection3( unsigned minCommonCameras, float fOutlierT while (mapIdxPatch.size() <= nComponents) mapIdxPatch.Insert(numPatches); } + + // --- 新增:构建每个面的候选视图列表 --- + faceCandidates.resize(faces.size()); + const int TOP_N = 3; // 取前3个最佳视图 + + FOREACH(f, faces) { + faceCandidates[f].primaryView = labels[f]; + + if (labels[f] == NO_ID || f >= facesDatas.size() || facesDatas[f].empty()) + continue; + + // 1. 收集所有可见视图,并按质量排序 + std::vector> viewQualities; + viewQualities.reserve(facesDatas[f].size()); + for (const FaceData& fd : facesDatas[f]) { + if (!fd.bInvalidFacesRelative) { + viewQualities.emplace_back(fd.quality, fd.idxView); + } + } + + if (viewQualities.empty()) + continue; + + // 按质量降序排序 + std::sort(viewQualities.begin(), viewQualities.end(), + [](const auto& a, const auto& b) { return a.first > b.first; }); + + // 2. 选取 Top-N,使用 Softmax 计算权重(关键:避免模糊!) + const size_t n = std::min(TOP_N, (int)viewQualities.size()); + faceCandidates[f].candidateViews.resize(n); + faceCandidates[f].candidateWeights.resize(n); + + // Softmax 权重计算(temperature 控制锐度) + const float temperature = 0.05f; // 越小越锐利,0.05f 是个不错的起点 + float max_q = viewQualities[0].first; + float sum = 0.0f; + + for (size_t i = 0; i < n; ++i) { + faceCandidates[f].candidateViews[i] = viewQualities[i].second; + float w = exp((viewQualities[i].first - max_q) / temperature); + faceCandidates[f].candidateWeights[i] = w; + sum += w; + } + + // 归一化 + if (sum > 0) { + for (size_t i = 0; i < n; ++i) { + faceCandidates[f].candidateWeights[i] /= sum; + } + } + } } + return true; } @@ -12791,13 +12996,16 @@ void MeshTexture::LocalSeamLeveling() } } -void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& basename) +void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeveling, unsigned nTextureSizeMultiple, unsigned nRectPackingHeuristic, Pixel8U colEmpty, float fSharpnessWeight, int maxTextureSize, const SEACAVE::String& basename, bool bOriginFaceview) { // Bruce - bGlobalSeamLeveling = false; - bLocalSeamLeveling = false; + // bGlobalSeamLeveling = false; + // bLocalSeamLeveling = false; // project patches in the corresponding view and compute texture-coordinates and bounding-box - const int border(2); + // Bruce + int border = 2; + if (!bOriginFaceview) + border = 4; faceTexcoords.resize(faces.size()*3); faceTexindices.resize(faces.size()); #ifdef TEXOPT_USE_OPENMP @@ -12824,10 +13032,30 @@ void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeve // compute relative texture coordinates ASSERT(imageData.image.isInside(Point2f(aabb.ptMin))); ASSERT(imageData.image.isInside(Point2f(aabb.ptMax))); - texturePatch.rect.x = FLOOR2INT(aabb.ptMin[0])-border; - texturePatch.rect.y = FLOOR2INT(aabb.ptMin[1])-border; - texturePatch.rect.width = CEIL2INT(aabb.ptMax[0]-aabb.ptMin[0])+border*2; - texturePatch.rect.height = CEIL2INT(aabb.ptMax[1]-aabb.ptMin[1])+border*2; + + if (bOriginFaceview) + { + texturePatch.rect.x = FLOOR2INT(aabb.ptMin[0])-border; + texturePatch.rect.y = FLOOR2INT(aabb.ptMin[1])-border; + texturePatch.rect.width = CEIL2INT(aabb.ptMax[0]-aabb.ptMin[0])+border*2; + texturePatch.rect.height = CEIL2INT(aabb.ptMax[1]-aabb.ptMin[1])+border*2; + } + else + { + texturePatch.rect.x = std::max(0, FLOOR2INT(aabb.ptMin[0])-border); + texturePatch.rect.y = std::max(0, FLOOR2INT(aabb.ptMin[1])-border); + // 限制尺寸不超过图像实际范围 + const cv::Mat& img = images[texturePatch.label].image; + texturePatch.rect.width = std::min( + CEIL2INT(aabb.ptMax[0]-aabb.ptMin[0])+border*2, + img.cols - texturePatch.rect.x + ); + texturePatch.rect.height = std::min( + CEIL2INT(aabb.ptMax[1]-aabb.ptMin[1])+border*2, + img.rows - texturePatch.rect.y + ); + } + ASSERT(imageData.image.isInside(texturePatch.rect.tl())); ASSERT(imageData.image.isInside(texturePatch.rect.br())); const TexCoord offset(texturePatch.rect.tl()); @@ -12851,6 +13079,8 @@ void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeve LOG_OUT() << "First loop completed" << std::endl; + + TD_TIMER_STARTD(); // perform seam leveling if (texturePatches.size() > 2 && (bGlobalSeamLeveling || bLocalSeamLeveling)) { // create seam vertices and edges @@ -12859,17 +13089,19 @@ void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeve // perform global seam leveling if (bGlobalSeamLeveling) { TD_TIMER_STARTD(); - GlobalSeamLeveling(); - DEBUG_ULTIMATE("\tglobal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); + GlobalSeamLeveling3(); + DEBUG_EXTRA("\tglobal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); } // perform local seam leveling if (bLocalSeamLeveling) { TD_TIMER_STARTD(); - LocalSeamLeveling(); - DEBUG_ULTIMATE("\tlocal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); + // LocalSeamLeveling(); + LocalSeamLeveling3(); + DEBUG_EXTRA("\tlocal seam leveling completed (%s)", TD_TIMER_GET_FMT().c_str()); } } + DEBUG_EXTRA("seam (%s)", TD_TIMER_GET_FMT().c_str()); // merge texture patches with overlapping rectangles for (unsigned i=0; i 0 && (texturePatches[i].rect.width > maxTextureSize || texturePatches[i].rect.height > maxTextureSize)) { @@ -12917,10 +13154,17 @@ void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeve // pack patches: one pack per texture file CLISTDEF2IDX(RectsBinPack::RectWIdxArr, TexIndex) placedRects; { // increase texture size till all patches fit - const unsigned typeRectsBinPack(nRectPackingHeuristic/100); - const unsigned typeSplit((nRectPackingHeuristic-typeRectsBinPack*100)/10); - const unsigned typeHeuristic(nRectPackingHeuristic%10); - int textureSize = 0; + // Bruce + unsigned typeRectsBinPack(nRectPackingHeuristic/100); + unsigned typeSplit((nRectPackingHeuristic-typeRectsBinPack*100)/10); + unsigned typeHeuristic(nRectPackingHeuristic%10); + if (!bOriginFaceview && false) + { + typeRectsBinPack = 1; + typeSplit = 0; + typeHeuristic = 1; + } + int textureSize = 0; while (!unplacedRects.empty()) { TD_TIMER_STARTD(); if (textureSize == 0) { @@ -12951,6 +13195,7 @@ void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeve if (textureSize == maxTextureSize || unplacedRects.empty()) { // create texture image placedRects.emplace_back(std::move(newPlacedRects)); + // Pixel8U colEmpty2=Pixel8U(0,0,255); texturesDiffuse.emplace_back(textureSize, textureSize).setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); textureSize = 0; } else { @@ -12982,23 +13227,150 @@ void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeve (rect.height == texturePatch.rect.width && rect.width == texturePatch.rect.height)); int x(0), y(1); if (texturePatch.label != NO_ID) { - const Image& imageData = images[texturePatch.label]; - cv::Mat patch(imageData.image(texturePatch.rect)); + // --- 多视图混合采样(参考 RC 逻辑) --- + // 1. 获取目标纹理块 + cv::Mat dstMat = texturesDiffuse[idxTexture](rect); + + // 2. 如果需要旋转(OpenMVS 的内部优化) + cv::Mat patchBuffer; // 用于存放旋转后的中间结果 + cv::Mat* targetMat = &dstMat; if (rect.width != texturePatch.rect.width) { - // flip patch and texture-coordinates - patch = patch.t(); + patchBuffer.create(rect.height, rect.width, dstMat.type()); + targetMat = &patchBuffer; x = 1; y = 0; + } else { + patchBuffer = dstMat; // 指向同一块内存 + } + + // 遍历 Patch 中的每一个三角形进行光栅化 + for (const FIndex idxFace : texturePatch.faces) { + if (idxFace >= this->faceCandidates.size() || + this->faceCandidates[idxFace].candidateViews.empty()) + continue; + + const Face& face = faces[idxFace]; + const TexCoord* texcoords = faceTexcoords.data() + idxFace * 3; + + // 手动计算三角形包围盒(不使用 AABB2i) + int minX = INT_MAX, minY = INT_MAX, maxX = INT_MIN, maxY = INT_MIN; + for (int i = 0; i < 3; ++i) { + Point2f pt(texcoords[i][0], texcoords[i][1]); + int x = FLOOR2INT(pt.x); + int y = FLOOR2INT(pt.y); + minX = std::min(minX, x); + minY = std::min(minY, y); + maxX = std::max(maxX, x); + maxY = std::max(maxY, y); + } + + // 裁剪到 Patch 边界 + minX = std::max(minX, 0); + minY = std::max(minY, 0); + maxX = std::min(maxX, targetMat->cols - 1); + maxY = std::min(maxY, targetMat->rows - 1); + if (minX > maxX || minY > maxY) continue; + + // 预存 UV + Point2f uvTri[3]; + for (int i = 0; i < 3; ++i) + uvTri[i] = Point2f(texcoords[i][0], texcoords[i][1]); + + // 光栅化 + for (int py = minY; py <= maxY; ++py) { + for (int px = minX; px <= maxX; ++px) { + Point2f P(px + 0.5f, py + 0.5f); + Point2f bary; + if (!ComputeBarycentricCoords(P, uvTri[0], uvTri[1], uvTri[2], bary)) + continue; + + float u = bary.x; + float v = bary.y; + float w = 1.0f - u - v; + if (u < 0 || v < 0 || w < 0) continue; + + const auto& candidates = this->faceCandidates[idxFace]; + + // 多视图加权采样(完全自包含,不依赖任何外部类型) + float acc_r = 0.0f, acc_g = 0.0f, acc_b = 0.0f, acc_w = 0.0f; + + for (size_t k = 0; k < candidates.candidateViews.size(); ++k) { + const IIndex viewID = candidates.candidateViews[k]; + float weight = candidates.candidateWeights[k]; + + const Image& candImage = images[viewID]; + + // 插值 3D 点 + Point3f pt3D = + vertices[face[0]] * w + + vertices[face[1]] * u + + vertices[face[2]] * v; + + Point2f uv_cand = candImage.camera.ProjectPointP(pt3D); + + if (!candImage.image.isInsideWithBorder(uv_cand, border)) + continue; + + // 双线性插值(直接使用 cv::Vec3f,避免 Color 类型) + const cv::Mat& src = candImage.image; + const int ix = cvFloor(uv_cand.x); + const int iy = cvFloor(uv_cand.y); + const float dx = uv_cand.x - ix; + const float dy = uv_cand.y - iy; + + // 边界保护 + const int ix1 = std::min(ix + 1, src.cols - 1); + const int iy1 = std::min(iy + 1, src.rows - 1); + + // 读取四个像素(使用 cv::Vec3f,OpenMVS 的图像通常是 CV_32FC3) + const cv::Vec3f& c00 = src.at(iy, ix); + const cv::Vec3f& c01 = src.at(iy, ix1); + const cv::Vec3f& c10 = src.at(iy1, ix); + const cv::Vec3f& c11 = src.at(iy1, ix1); + + // 双线性插值公式 + const float inv_a = (1.0f - dx) * (1.0f - dy); + const float inv_b = dx * (1.0f - dy); + const float inv_c = (1.0f - dx) * dy; + const float inv_d = dx * dy; + + const float r = c00[0] * inv_a + c01[0] * inv_b + c10[0] * inv_c + c11[0] * inv_d; + const float g = c00[1] * inv_a + c01[1] * inv_b + c10[1] * inv_c + c11[1] * inv_d; + const float b = c00[2] * inv_a + c01[2] * inv_b + c10[2] * inv_c + c11[2] * inv_d; + + acc_r += r * weight; + acc_g += g * weight; + acc_b += b * weight; + acc_w += weight; + } + + // 写入最终颜色 + if (acc_w > 0.0f) { + const float inv_w = 1.0f / acc_w; + Pixel8U finalColor8u; + finalColor8u.r = cv::saturate_cast(acc_r * inv_w * 255.f); + finalColor8u.g = cv::saturate_cast(acc_g * inv_w * 255.f); + finalColor8u.b = cv::saturate_cast(acc_b * inv_w * 255.f); + targetMat->at(py, px) = finalColor8u; + } + } + } + } + + // 7. 如果之前发生了旋转,转置回去 + if (rect.width != texturePatch.rect.width) { + cv::transpose(patchBuffer, dstMat); } - patch.copyTo(texturesDiffuse[idxTexture](rect)); } else { + //* auto it = texturePatch.faces.begin(); while (it != texturePatch.faces.end()) { emptyFaceIndexes.push_back(*it); ++it; } + //*/ } // compute final texture coordinates const TexCoord offset(rect.tl()); @@ -13017,6 +13389,7 @@ void MeshTexture::GenerateTexture2(bool bGlobalSeamLeveling, bool bLocalSeamLeve } if (texturesDiffuse.size() == 1) faceTexindices.Release(); + // apply some sharpening if (fSharpnessWeight > 0) { constexpr double sigma = 1.5;