You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何消除图像拼接线性融合中的重影问题

图像拼接线性融合重影问题求助

我在图像拼接流水线中采用线性融合方案,参考Stack Overflow上的《Blending does not remove seams in OpenCV》帖子实现。当前代码可正常运行,但拼接结果部分区域出现重影现象。因OpenCV自动拼接库处理相机实时视频流速度过慢,无法满足需求,特此求助解决重影问题。

实现代码

#include <iostream>
#include <string>
#include <algorithm>
#include <chrono>

#include "opencv2/opencv.hpp"
#include "opencv2/opencv_modules.hpp"
#include "opencv2/core/utility.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"

#include "opencv2/xfeatures2d.hpp"
#include "opencv2/xfeatures2d/nonfree.hpp"
#include "opencv2/xfeatures2d/cuda.hpp"
#include "opencv2/cudafeatures2d.hpp"
#include "opencv2/cudaarithm.hpp"

cv::Mat border(cv::Mat mask)
{
    cv::Mat gx;
    cv::Mat gy;

    cv::Sobel(mask, gx, CV_32F, 1, 0, 3);
    cv::Sobel(mask, gy, CV_32F, 0, 1, 3);

    cv::Mat border;
    cv::magnitude(gx, gy, border);

    return border > 100;
}

cv::Mat linearBlend2(cv::Mat image1, cv::Mat mask1, cv::Mat image2, cv::Mat mask2)
{
    cv::TickMeter tm;
    // === Init variable ===
    cv::Mat distResult, distMask1, distMask2, diskMaskSum, borderMask;
    cv::Mat imBlendedB, imBlendedG, imBlendedR, imgResult;
    double min, max;
    cv::Point minLoc, maxLoc;
    cv::Mat im1Float, im2Float;
    std::vector<cv::Mat> channels1, channels2, channelsBlended;

    // edited: find regions where no mask is set
    // compute the region where no mask is set at all, to use those color values unblended
    tm.start();
    cv::Mat bothMasks = mask1 | mask2;
    cv::Mat noMask = 255 - bothMasks;

    // create an image with equal alpha values:
    cv::Mat rawAlpha = cv::Mat(noMask.rows, noMask.cols, CV_32FC1);
    rawAlpha = 1.0f;
    tm.stop();
    std::cout << "Create mask = " << tm.getTimeMilli() << " ms\n";
    // === 1. Process Image 1 ===

    // invert the border, so that border values are 0 ... this is needed for the distance transform
    borderMask = 255 - border(mask1);

    // === a. Distance Transfrom ===
    tm.start();
    cv::distanceTransform(borderMask, distResult, cv::DIST_L2, 3);
    tm.stop();
    std::cout << "Distance mask 0  = " << tm.getTimeMilli() << " ms\n";
    cv::imwrite("DistanceMask0.jpg", distResult);

    // === b. scale distances to values between 0 and 1 ===
    tm.start();
    cv::minMaxLoc(distResult, &min, &max, &minLoc, &maxLoc, mask1 & (distResult > 0)); // edited: find min values > 0
    distResult = distResult * 1.0 / max;                                               // values between 0 and 1 since min val should alwaysbe 0
    tm.stop();
    std::cout << "Scale Distance mask 0  = " << tm.getTimeMilli() << " ms\n";

    // === c. mask the distance values to reduce information to masked regions ===
    tm.start();
    rawAlpha.copyTo(distMask1, noMask); // edited: where no mask is set, blend with equal values
    distResult.copyTo(distMask1, mask1);
    rawAlpha.copyTo(distMask1, mask1 & (255 - mask2)); // edited
    tm.stop();
    std::cout << "Copy Distance mask 0  = " << tm.getTimeMilli() << " ms\n";

    // === 2. Process Image 2 ===
    borderMask = 255 - border(mask2);

    // === a. Distance Transfrom ===
    cv::distanceTransform(borderMask, distResult, cv::DIST_L2, 3);
    cv::imwrite("DistanceMask1.jpg", distResult);

    // === b. scale distances to values between 0 and 1 ===
    cv::minMaxLoc(distResult, &min, &max, &minLoc, &maxLoc, mask2 & (distResult > 0)); // edited: find min values > 0
    distResult = distResult * 1.0 / max;                                               // values between 0 and 1

    // === c. mask the distance values to reduce information to masked regions ===
    rawAlpha.copyTo(distMask2, noMask); // edited: where no mask is set, blend with equal values
    distResult.copyTo(distMask2, mask2);
    rawAlpha.copyTo(distMask2, mask2 & (255 - mask1)); // edited

    // === 3. Combine / blend both image ===
    diskMaskSum = distMask1 + distMask2;

    // you have to convert the images to float to multiply with the weight
    tm.start();
    image1.convertTo(im1Float, distMask1.type());
    image2.convertTo(im2Float, distMask2.type());
    cv::split(im1Float, channels1);
    cv::split(im2Float, channels2);
    tm.stop();
    std::cout << "Split Image Channels   = " << tm.getTimeMilli() << " ms\n";

    cv::Mat im1Alpha;
    std::vector<cv::Mat> alpha1;
    cv::Mat im1AlphaB = distMask1.mul(channels1[0]);
    cv::Mat im1AlphaG = distMask1.mul(channels1[1]);
    cv::Mat im1AlphaR = distMask1.mul(channels1[2]);
    alpha1.push_back(im1AlphaB);
    alpha1.push_back(im1AlphaG);
    alpha1.push_back(im1AlphaR);
    cv::merge(alpha1, im1Alpha);
    cv::imshow("alpha1", im1Alpha / 255.0);
    cv::imwrite("AppliedMask0.jpg", im1Alpha);

    std::vector<cv::Mat> alpha2;
    cv::Mat im2Alpha;
    cv::Mat im2AlphaB = distMask2.mul(channels2[0]);
    cv::Mat im2AlphaG = distMask2.mul(channels2[1]);
    cv::Mat im2AlphaR = distMask2.mul(channels2[2]);
    alpha2.push_back(im2AlphaB);
    alpha2.push_back(im2AlphaG);
    alpha2.push_back(im2AlphaR);
    cv::merge(alpha2, im2Alpha);
    cv::imshow("alpha2", im2Alpha / 255.0);
    cv::imwrite("AppliedMask1.jpg", im2Alpha);

    // now sum both weighphted images and divide by the sum of the weights (linear combination)
    imBlendedB = (im1AlphaB + im2AlphaB) / diskMaskSum;
    imBlendedG = (im1AlphaG + im2AlphaG) / diskMaskSum;
    imBlendedR = (im1AlphaR + im2AlphaR) / diskMaskSum;
    channelsBlended.push_back(imBlendedB);
    channelsBlended.push_back(imBlendedG);
    channelsBlended.push_back(imBlendedR);

    // merge back to 3 channel image
    cv::Mat merged;
    cv::merge(channelsBlended, merged);

    // convert to 8UC3
    cv::Mat merged8U;
    merged.convertTo(merged8U, CV_8UC3);

    return merged8U;
}

cv::Mat StitchImages()
{
    /*
    * Image stitching pipeline:
    * */

    //    create vector and allocate it with 2 images
    std::vector<cv::Mat> imagesWarpedArray(2);
    std::vector<cv::Mat> maskWarpedArray(2);

    imagesWarpedArray[0] = cv::imread("../imagesWarpedArray0.jpg");
    imagesWarpedArray[1] = cv::imread("../imagesWarpedArray1.jpg");

    maskWarpedArray[0] = cv::imread("../maskWarpedArray0.jpg");
    maskWarpedArray[1] = cv::imread("../maskWarpedArray0.jpg");

    maskWarpedArray[0] = warpedMaskBase;
    maskWarpedArray[1] = warpedMaskSec;

    // blend image
    cv::Mat blendResult = linearBlend2(imagesWarpedArray[0], maskWarpedArray[0], imagesWarpedArray[1], maskWarpedArray[1]);

    cv::imshow("Result", blendResult);
    return blendResult;
}

int main(int argc, char **argv)
{
    cv::Mat result = StitchImages();
    cv::waitKey(0);
    return 0;
}

素材与结果

  • 变换后图像1
  • 变换后图像0
  • 变换后掩码0
  • 变换后掩码1
  • 拼接结果(存在重影)

问题排查与解决建议

1. 修复掩码加载的明显错误

在StitchImages函数中,存在掩码加载重复的问题:

maskWarpedArray[1] = cv::imread("../maskWarpedArray0.jpg"); // 错误:重复加载第一个掩码

应改为加载对应第二个图像的掩码文件,错误的掩码会直接导致融合区域计算失效,引发重影。

2. 提升图像对齐精度

重影的核心根源是重叠区域图像未精确对齐,线性融合无法弥补对齐误差:

  • 优化特征匹配流程:用RANSAC算法过滤错误匹配点,提升单应性矩阵求解精度;
  • 视频流场景采用帧间跟踪:用光流法跟踪特征点,避免每帧都做全量特征匹配,既提升速度又保证连续帧的对齐一致性;
  • 手动验证重叠区域:对比变换后图像的特征点位置,确认是否存在明显偏移。

3. 优化融合权重逻辑

当前基于距离变换的权重可能导致过渡区域过宽,放大对齐误差:

  • 缩小融合过渡宽度:调整距离变换的缩放比例,或用二次函数等更陡峭的曲线生成权重;
  • 尝试多频段融合:将图像分为低频(颜色)和高频(细节)通道分别融合,减少细节区域的重影,可简化层数保证实时性。

4. 实时性优化方案

针对相机视频流的速度需求,可做以下优化:

  • 启用CUDA加速:利用代码中已引入的CUDA模块,将特征提取、变换等步骤移至GPU执行;
  • 降分辨率预处理:在低分辨率下完成特征匹配与变换,再放大至原分辨率做融合;
  • 增量式拼接:仅处理当前帧与前一帧的重叠区域,无需每次处理全图。

内容的提问来源于stack exchange,提问作者Made Arya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:44:52