OpenCV开发笔记(八十):基于特征点匹配实现全景图片拼接

news2024/11/15 8:16:47

若该文为原创文章,转载请注明原文出处
本文章博客地址:https://hpzwl.blog.csdn.net/article/details/141790116

长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中…

OpenCV开发专栏(点击传送门)

上一篇:《OpenCV开发笔记(七十九):基于Stitcher类实现全景图片拼接》
下一篇:持续补充中…


前言

  一个摄像头视野不大的时候,我们希望进行两个视野合并,这样让正视的视野增大,从而可以看到更广阔的标准视野。拼接的方法分为两条路,第一条路是Sticher类,第二条思路是特征点匹配。
  本篇使用特征点匹配,进行两张图来视野合并拼接。


Demo

  100%的点匹配
  在这里插入图片描述

  换了一幅图:
  在这里插入图片描述

  所以,opencv传统的方式,对这些特征点是有一些要求的。(注意:这两张图使用sStitcher类实现全景图片拼接,是无法拼接成功的!!!)

两张图的拼接过程

步骤一:打开图片

  在这里插入图片描述

 cv::Mat leftImageMat = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/30.jpg");
 cv::Mat rightImageMat = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/31.jpg");

步骤二:提取特征点

  在这里插入图片描述

// 提取特征点
cv::Ptr<cv::xfeatures2d::SurfFeatureDetector> pSurfFeatureDetector = cv::xfeatures2d::SurfFeatureDetector::create();
std::vector<cv::KeyPoint> leftKeyPoints;
std::vector<cv::KeyPoint> rightKeyPoints;
cv::Mat leftMatch;
cv::Mat rightMatch;
std::vector<cv::DMatch> matches;
pSurfFeatureDetector->detectAndCompute(leftImageGrayMat, cv::Mat(), leftKeyPoints, leftMatch);
pSurfFeatureDetector->detectAndCompute(rightImageGrayMat, cv::Mat(), rightKeyPoints, rightMatch);

步骤三:暴力匹配

  在这里插入图片描述

// 暴力匹配
cv::Ptr<cv::FlannBasedMatcher> pFlannBasedMatcher = cv::FlannBasedMatcher::create();
pFlannBasedMatcher->match(leftMatch, rightMatch, matches);

步骤四:提取暴力匹配后比较好的点

  在这里插入图片描述

// 筛选匹配点, 根据match的距离从小到大排序
std::sort(matches.begin(), matches.end());
// 筛选匹配点,根据排序留下最好的匹配点
std::vector<cv::DMatch> goodMatchs;
// 阈值最小个数点
int count = 40;
// 阈值点个数 小于 总量的10% 则使用 总量的10%
int validPoints = (int)(matches.size() * 1.0f);
if(validPoints > count)
{
    count = validPoints;
}
// 所有匹配点小于阈值,那么就取所有点
if(matches.size() < count)
{
    count = matches.size();
}
// 将筛选出的点当作较好的点
for(int index = 0; index < count; index++)
{
    goodMatchs.push_back(matches.at(index));
}
// 匹配结果
cv::Mat matchedMat;
// 绘制结果, 注意顺序
cv::drawMatches(leftImageMat, leftKeyPoints, rightImageMat, rightKeyPoints, goodMatchs, matchedMat);
#if 1
cv::namedWindow("matchedMat", cv::WINDOW_NORMAL);
cv::resizeWindow("matchedMat", cv::Size(800, 300));
cv::imshow("matchedMat", matchedMat);
#endif

步骤五:计算变换矩阵

  在这里插入图片描述

// 准备匹配的点
std::vector<cv::Point2f> leftImagePoints;
std::vector<cv::Point2f> rightImagePoints;
for(int index = 0; index < goodMatchs.size(); index++)
{
    leftImagePoints.push_back(rightKeyPoints.at(goodMatchs.at(index).trainIdx).pt);
    rightImagePoints.push_back(leftKeyPoints.at(goodMatchs.at(index).queryIdx).pt);
}

// 使用暴力匹配的点计算透视变换矩阵
cv::Mat m = cv::findHomography(leftImagePoints, rightImagePoints, CV_RANSAC);

步骤六:计算第二张图变换后的图像的大小

  在这里插入图片描述

// 计算第二张图的变换大小
cv::Point2f leftTopPoint2f;
cv::Point2f leftBottomPoint2f;
cv::Point2f rightTopPoint2f;
cv::Point2f rightBottomPoint2f;
cv::Mat H = m.clone();
cv::Mat src = leftImageMat.clone();
{
    cv::Mat V1;
    cv::Mat V2;
    // 左上角(0, 0, 1)
    double v2[3] = {0, 0, 1};
    // 变换后的坐标值
    double v1[3];
    //列向量
    V2 = cv::Mat(3, 1, CV_64FC1, v2);
    V1 = cv::Mat(3, 1, CV_64FC1, v1);
    V1 = H * V2;
    leftTopPoint2f.x = v1[0] / v1[2];
    leftTopPoint2f.y = v1[1] / v1[2];

    // 左下角(0, src.rows, 1)
    v2[0] = 0;
    v2[1] = src.rows;
    v2[2] = 1;
    V2 = cv::Mat(3, 1, CV_64FC1, v2);
    V1 = cv::Mat(3, 1, CV_64FC1, v1);
    V1 = H * V2;
    leftBottomPoint2f.x = v1[0] / v1[2];
    leftBottomPoint2f.y = v1[1] / v1[2];

    // 右上角(src.cols, 0, 1)
    v2[0] = src.cols;
    v2[1] = 0;
    v2[2] = 1;
    V2 = cv::Mat(3, 1, CV_64FC1, v2);
    V1 = cv::Mat(3, 1, CV_64FC1, v1);
    V1 = H * V2;
    rightTopPoint2f.x = v1[0] / v1[2];
    rightTopPoint2f.y = v1[1] / v1[2];

    // 右下角(src.cols,src.rows,1)
    v2[0] = src.cols;
    v2[1] = src.rows;
    v2[2] = 1;
    V2 = cv::Mat(3, 1, CV_64FC1, v2);
    V1 = cv::Mat(3, 1, CV_64FC1, v1);
    V1 = H * V2;
    rightBottomPoint2f.x = v1[0] / v1[2];
    rightBottomPoint2f.y = v1[1] / v1[2];
}

步骤七:对第二张图进行举证变化且变为目标大小

  在这里插入图片描述

// 图像变换
cv::Mat imageTransform1;
cv::warpPerspective(rightImageMat,                                          // 源图
                    imageTransform1,                                        // 变换后的输出图
                    m,                                                      // 变换矩阵
                    cv::Size(MAX(rightTopPoint2f.x, rightBottomPoint2f.x),  // 输出图像宽度
                             leftImageMat.rows));                           // 输出图像高度
cv::namedWindow("imageTransform1", cv::WINDOW_NORMAL);
cv::resizeWindow("imageTransform1", cv::Size(400, 300));
cv::imshow("imageTransform1", imageTransform1);

步骤八:进行融合

  在这里插入图片描述

  在这里插入图片描述

// 创建拼接后的图
int resultWidth = imageTransform1.cols;
int resultHeight = rightImageMat.rows;
if(imageTransform1.cols < leftImageMat.cols)
{
    resultWidth = leftImageMat.cols;
}
if(imageTransform1.rows < leftImageMat.rows)
{
    resultHeight = leftImageMat.rows;
}
cv::Mat resultMat(resultHeight, resultWidth, CV_8UC3);
resultMat.setTo(0);
···

// 开始拷贝
LOG << imageTransform1.cols << imageTransform1.rows;
LOG << "copy to";
LOG << resultMat.cols << resultMat.rows;
imageTransform1.copyTo(resultMat(cv::Rect(0, 0, imageTransform1.cols, imageTransform1.rows)));
LOG << rightImageMat.cols << rightImageMat.rows;
LOG << "copy to";
LOG << resultMat.cols << resultMat.rows;
leftImageMat.copyTo(resultMat(cv::Rect(0, 0, leftImageMat.cols, leftImageMat.rows)));

步骤九:对重叠区域进行渐进色过度

  由于实际效果出现重影,查阅相关资料,这部分可以消除一部分,但是需要深入研究,而且消除一部分也只是达到较可以的水平,并不能完全消除。
  cpp有兴趣读者,可以深入研究沟通这块。
  比较明确的方法就是查看Stitcher的源码研究,将其投影矩阵部分重叠消除的代码抠出来。


匹配点数调参测试

强制40个点

  在这里插入图片描述

匹配探测到的10%的点

   在这里插入图片描述

匹配探测到的60%的点

  在这里插入图片描述

匹配探测到的100%的点

  在这里插入图片描述


函数原型

函数static Ptr create

static Ptr<SURF> create(double hessianThreshold=100,
                        int nOctaves = 4,
                        int nOctaveLayers = 3,
                        bool extended = false,
                        bool upright = false);
  • 参数一:double类型的hessianThreshold,默认值100,用于SURF的hessian关键点检测器的阈值;
  • 参数二:int类型的nOctaves,默认值4,关键点检测器将使用的金字塔倍频程数;
  • 参数三:int类型的nOctaveLayers,默认值3,每八度音阶的层数。3是D.Lowe纸张中使用的值。这个八度音阶数是根据图像分辨率自动计算出来的;
  • 参数四:bool类型的extended,扩展描述符标志(true-使用扩展的128个元素描述符;false-使用64个元素描述符),默认false;
  • 参数五:bool类型的upright,向上向右或旋转的特征标志(true-不计算特征的方向;faslse-计算方向),默认false;

函数xfeatures2d::SURT::detectAndCompute

// 该函数结合了detect和compute,参照detect和compute函数参数
void xfeatures2d::SURT::detectAndCompute( InputArray image,
                                          InputArray mask,
                                          std::vector<KeyPoint>& keypoints,
                                          OutputArray descriptors,
                                          bool useProvidedKeypoints=false );

  detectAndCompute函数是OpenCV库中用于特征检测与计算的函数,其原型根据不同的特征检测器(如SIFT、SURF、ORB等)可能略有不同,但大体上遵循相似的参数结构。

  • 参数一:InputArray类型的image,输入cv::Mat;
  • 参数二:InputArray类型的mask,输入cv::Mat,不需要时,直接可以cv::Mat()即可;
  • 参数三:std::Vector类型的keypoints,原图的关键点;
  • 参数四:OutputArray类型的descriptors,计算描述符;
  • 参数五:如果设置为true,则函数会假设keypoints向量中已经包含了一些关键点,函数将只计算这些点的描述子,而不是重新检测关键点。

函数cv::FlannBasedMatcher::create

static Ptr<BFMatcher> create()

  初始化暴力匹配实例。

函数cv::FlannBasedMatcher::match

void BFMatcher::match(InputArray queryDescriptors,
                    InputArray trainDescriptors,
                    std::vector<DMatch>& matches,
                    InputArray mask=noArray() ) const;
  • 参数一:InputArray类型的queryDescriptors,查询描述符集,一般cv::Mat,某个特征提取的描述符。
  • 参数二:InputArray类型的trainDescriptors,训练描述符集,此处输入的应该是没有加入到类对象集合种的(该类有训练的数据集合),一般cv::Mat,某个特征提取的描述符。
  • 参数三:std::vector类型的matches。如果在掩码中屏蔽了查询描述符,则不会为此添加匹配项描述符。因此,匹配项的大小可能小于查询描述符计数。
  • 参数四:InputArray类型的mask,指定输入查询和训练矩阵之间允许的匹配的掩码描述符。

函数cv::drawMatches

void drawMatches( InputArray img1,
                  const std::vector<KeyPoint>& keypoints1,
                  InputArray img2,
                  const std::vector<KeyPoint>& keypoints2,
                  const std::vector<DMatch>& matches1to2,
                  InputOutputArray outImg,
                  const Scalar& matchColor=Scalar::all(-1),
                  const Scalar& singlePointColor=Scalar::all(-1),
                  const std::vector<char>& matchesMask=std::vector<char>(),
                  int flags=DrawMatchesFlags::DEFAULT );
  • 参数一:InputArray类型的img1,图像1。
  • 参数二:std::vector类型的keypoints1,图像1的关键点。
  • 参数三:InputArray类型的img2,图像2。
  • 参数四:std::vector类型的keypoints2,图像2的关键点。
  • 参数五:std::vector类型的matchers1to2,从第一个图像匹配到第二个图像,这意味着keypoints1[i]在keypoints2中有一个对应的点[matches[i]]。
  • 参数六:InputOutputArray类型的outImg,为空时,默认并排绘制输出图像以及连接关键点;若不为空,则在图像上绘制关系点。
  • 参数七:Scalar类型的matcherColor,匹配颜色匹配(线和连接的关键点)的颜色。如果颜色为cv::Scalar::all(-1),则为随机颜色。
  • 参数八:Scalar类型的singlePointColor,颜色单个关键点(圆)的颜色,这意味着关键点没有匹配到的则认是该颜色。
  • 参数九:std::vector类型的matchersMask,确定绘制的匹配项目,若是为空,则表示全部绘制。
  • 参数十:int类型的flags,查看枚举DrawMatchesFlags,如下:
      在这里插入图片描述

函数cv::findHomography

  cv::findHomography 是 OpenCV 库中的一个函数,用于在两组二维点之间寻找单应性矩阵(Homography Matrix)。单应性矩阵是一个 3x3 的矩阵,它将一个平面上的点映射到另一个平面(可能是同一个平面,但通常是在不同的视角或条件下)上的点。这在计算机视觉和图像处理中非常有用,比如图像拼接、增强现实、相机姿态估计等领域。

cv::Mat cv::findHomography(InputArray srcPoints,  
                        InputArray dstPoints,  
                        int method = 0,  
                        double ransacReprojThresh = 3,  
                        OutputArray mask = noArray(),  
                        const int maxIters = 2000,  
                        const double confidence = 0.995);
  • 参数一:srcPoints,源图像中的点集,通常是 std::vectorcv::Point2f 或 cv::Mat 类型,其中每行代表一个点的 x 和 y 坐标。
  • 参数二:dstPoints:目标图像中对应的点集,与 srcPoints 格式相同。
  • 参数三:method:单应性矩阵的计算方法。常见的值有 0(使用所有点,默认方法)、cv::RANSAC(RANdom SAmple Consensus,基于随机样本一致性的鲁棒方法)、cv::LMEDS(Least-Median of Squares,基于最小中值平方的方法,对噪声和异常值有较好的鲁棒性)等。
  • 参数四:ransacReprojThresh,当 method 设置为 cv::RANSAC 或 cv::RANSAC_FIXED_POINT 时,此参数指定了重投影误差的最大阈值,用于判断点是否为内点。
  • 参数五:mask,输出参数,一个与输入点集同样大小的掩码,用于指示哪些点被认为是内点(即,在计算单应性矩阵时使用的点)。
  • 参数六:maxIters,当 method 为 cv::RANSAC 或 cv::LMEDS 时,此参数指定了算法的最大迭代次数。
  • 参数七:confidence,当 method 为 cv::RANSAC 时,此参数指定了算法计算出的单应性矩阵的置信度(即在估计的模型是正确模型的概率)。

函数cv::warpPerspective

void cv::warpPerspective(InputArray src,  
                         OutputArray dst,  
                         InputArray M,  
                         Size dsize,  
                         int flags = INTER_LINEAR,  
                         int borderMode = BORDER_CONSTANT,  
                         const Scalar& borderValue = Scalar());
  • 参数一:src,输入图像,即要进行透视变换的原始图像。
  • 参数二:dst,输出图像,即透视变换后的图像。这个图像将与 dsize 指定的尺寸相同。
  • 参数三:M,3x3 的透视变换矩阵。这个矩阵定义了如何将 src 中的点映射到 dst 中的新位置。
  • 参数四:dsize,输出图像的尺寸。这个参数是必需的,因为透视变换可能会改变图像的大小。
  • 参数五:flags,插值方法。默认为 INTER_LINEAR,表示双线性插值。其他选项包括 INTER_NEAREST(最近邻插值)、INTER_CUBIC(双三次插值)等。
  • 参数六:borderMode,边界像素的外推方法。默认为 BORDER_CONSTANT,表示用恒定的值(borderValue)填充边界外的像素。其他选项包括 BORDER_REPLICATE(复制边缘像素)、BORDER_REFLECT(反射边缘像素)等。
  • 参数七:borderValue,当 borderMode 为 BORDER_CONSTANT 时,用于填充边界像素的值。默认为 Scalar(),即黑色。

函数cv::Mat::copyTo

void cv::Mat::copyTo(OutputArray m, InputArray mask=noArray()) const;

  cv::Mat::copyTo 是 OpenCV 库中 cv::Mat 类的一个成员函数,用于将一个矩阵(图像、数组等)的内容复制到另一个矩阵中。这个函数非常有用,因为它允许你复制矩阵的全部或部分数据,同时可以选择性地更新目标矩阵的大小和类型。

  • 参数一:输出矩阵m,即复制内容的目标矩阵。如果 m 的大小和类型与源矩阵(调用 copyTo 的矩阵)不同,m 会被重新分配以匹配源矩阵的大小和类型。
  • 参数二(可选):mask:一个与源矩阵同样大小的8位单通道矩阵,用于指定哪些元素应该被复制到目标矩阵中。如果 mask 的某个位置的值非零,则源矩阵对应位置的元素会被复制到目标矩阵;如果为零,则目标矩阵对应位置的元素不会被修改(保持原样或初始化为零,取决于目标矩阵的初始状态)。

Demo源码

void OpenCVManager::testSplicingImages()
{
    cv::Mat leftImageMat = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/30.jpg");
    cv::Mat rightImageMat = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/31.jpg");

#if 0
    // 对图片进行缩放,测试其拼接耗时
    cv::resize(mat1, mat1, cv::Size(1920, 1080));
    cv::resize(mat2, mat2, cv::Size(1920, 1080));
#endif

    //灰度图转换
    cv::Mat leftImageGrayMat;
    cv::Mat rightImageGrayMat;

    cv::cvtColor(leftImageMat, leftImageGrayMat, CV_RGB2GRAY);
    cv::cvtColor(rightImageMat, rightImageGrayMat, CV_RGB2GRAY);

    // 提取特征点
    cv::Ptr<cv::xfeatures2d::SurfFeatureDetector> pSurfFeatureDetector = cv::xfeatures2d::SurfFeatureDetector::create();
    std::vector<cv::KeyPoint> leftKeyPoints;
    std::vector<cv::KeyPoint> rightKeyPoints;
    cv::Mat leftMatch;
    cv::Mat rightMatch;
    std::vector<cv::DMatch> matches;
    pSurfFeatureDetector->detectAndCompute(leftImageGrayMat, cv::Mat(), leftKeyPoints, leftMatch);
    pSurfFeatureDetector->detectAndCompute(rightImageGrayMat, cv::Mat(), rightKeyPoints, rightMatch);
    // 暴力匹配
    cv::Ptr<cv::FlannBasedMatcher> pFlannBasedMatcher = cv::FlannBasedMatcher::create();
    pFlannBasedMatcher->match(leftMatch, rightMatch, matches);
    // 筛选匹配点, 根据match的距离从小到大排序
    std::sort(matches.begin(), matches.end());
    // 筛选匹配点,根据排序留下最好的匹配点
    std::vector<cv::DMatch> goodMatchs;
    // 阈值最小个数点
    int count = 40;
    // 阈值点个数 小于 总量的10% 则使用 总量的10%
    int validPoints = (int)(matches.size() * 1.0f);
    if(validPoints > count)
    {
        count = validPoints;
    }
    // 所有匹配点小于阈值,那么就取所有点
    if(matches.size() < count)
    {
        count = matches.size();
    }
    // 将筛选出的点当作较好的点
    for(int index = 0; index < count; index++)
    {
        goodMatchs.push_back(matches.at(index));
    }
    // 匹配结果
    cv::Mat matchedMat;
    // 绘制结果, 注意顺序
    cv::drawMatches(leftImageMat, leftKeyPoints, rightImageMat, rightKeyPoints, goodMatchs, matchedMat);
#if 1
    cv::namedWindow("matchedMat", cv::WINDOW_NORMAL);
    cv::resizeWindow("matchedMat", cv::Size(800, 300));
    cv::imshow("matchedMat", matchedMat);
#endif

    // 准备匹配的点
    std::vector<cv::Point2f> leftImagePoints;
    std::vector<cv::Point2f> rightImagePoints;
    for(int index = 0; index < goodMatchs.size(); index++)
    {
        leftImagePoints.push_back(rightKeyPoints.at(goodMatchs.at(index).trainIdx).pt);
        rightImagePoints.push_back(leftKeyPoints.at(goodMatchs.at(index).queryIdx).pt);
    }

    // 使用暴力匹配的点计算透视变换矩阵
    cv::Mat m = cv::findHomography(leftImagePoints, rightImagePoints, CV_RANSAC);

    // 计算第二张图的变换大小
    cv::Point2f leftTopPoint2f;
    cv::Point2f leftBottomPoint2f;
    cv::Point2f rightTopPoint2f;
    cv::Point2f rightBottomPoint2f;
    cv::Mat H = m.clone();
    cv::Mat src = leftImageMat.clone();
    {
        cv::Mat V1;
        cv::Mat V2;
        // 左上角(0, 0, 1)
        double v2[3] = {0, 0, 1};
        // 变换后的坐标值
        double v1[3];
        //列向量
        V2 = cv::Mat(3, 1, CV_64FC1, v2);
        V1 = cv::Mat(3, 1, CV_64FC1, v1);
        V1 = H * V2;
        leftTopPoint2f.x = v1[0] / v1[2];
        leftTopPoint2f.y = v1[1] / v1[2];

        // 左下角(0, src.rows, 1)
        v2[0] = 0;
        v2[1] = src.rows;
        v2[2] = 1;
        V2 = cv::Mat(3, 1, CV_64FC1, v2);
        V1 = cv::Mat(3, 1, CV_64FC1, v1);
        V1 = H * V2;
        leftBottomPoint2f.x = v1[0] / v1[2];
        leftBottomPoint2f.y = v1[1] / v1[2];

        // 右上角(src.cols, 0, 1)
        v2[0] = src.cols;
        v2[1] = 0;
        v2[2] = 1;
        V2 = cv::Mat(3, 1, CV_64FC1, v2);
        V1 = cv::Mat(3, 1, CV_64FC1, v1);
        V1 = H * V2;
        rightTopPoint2f.x = v1[0] / v1[2];
        rightTopPoint2f.y = v1[1] / v1[2];

        // 右下角(src.cols,src.rows,1)
        v2[0] = src.cols;
        v2[1] = src.rows;
        v2[2] = 1;
        V2 = cv::Mat(3, 1, CV_64FC1, v2);
        V1 = cv::Mat(3, 1, CV_64FC1, v1);
        V1 = H * V2;
        rightBottomPoint2f.x = v1[0] / v1[2];
        rightBottomPoint2f.y = v1[1] / v1[2];
    }

    // 图像变换
    cv::Mat imageTransform1;
    cv::warpPerspective(rightImageMat,                                          // 源图
                        imageTransform1,                                        // 变换后的输出图
                        m,                                                      // 变换矩阵
                        cv::Size(MAX(rightTopPoint2f.x, rightBottomPoint2f.x),  // 输出图像宽度
                                 leftImageMat.rows));                           // 输出图像高度
    cv::namedWindow("imageTransform1", cv::WINDOW_NORMAL);
    cv::resizeWindow("imageTransform1", cv::Size(400, 300));
    cv::imshow("imageTransform1", imageTransform1);

    // 创建拼接后的图
    int resultWidth = imageTransform1.cols;
    int resultHeight = rightImageMat.rows;
    if(imageTransform1.cols < leftImageMat.cols)
    {
        resultWidth = leftImageMat.cols;
    }
    if(imageTransform1.rows < leftImageMat.rows)
    {
        resultHeight = leftImageMat.rows;
    }
    cv::Mat resultMat(resultHeight, resultWidth, CV_8UC3);
    resultMat.setTo(0);

    // 开始拷贝
    LOG << imageTransform1.cols << imageTransform1.rows;
    LOG << "copy to";
    LOG << resultMat.cols << resultMat.rows;
    imageTransform1.copyTo(resultMat(cv::Rect(0, 0, imageTransform1.cols, imageTransform1.rows)));
    LOG << rightImageMat.cols << rightImageMat.rows;
    LOG << "copy to";
    LOG << resultMat.cols << resultMat.rows;
    leftImageMat.copyTo(resultMat(cv::Rect(0, 0, leftImageMat.cols, leftImageMat.rows)));

    cv::namedWindow("rightImageMat", cv::WINDOW_NORMAL);
    cv::resizeWindow("rightImageMat", cv::Size(400, 300));
    cv::imshow("rightImageMat", rightImageMat);

    cv::namedWindow("leftImageMat", cv::WINDOW_NORMAL);
    cv::resizeWindow("leftImageMat", cv::Size(400, 300));
    cv::imshow("leftImageMat", leftImageMat);


    cv::namedWindow("resultMat", cv::WINDOW_NORMAL);
    cv::resizeWindow("resultMat", cv::Size(400, 300));
    cv::imshow("resultMat", resultMat);

    cv::waitKey(0);
}

入坑

入坑一:cv::Mat.copyTo函数崩溃

问题

  cv::Mat的copyTo函数崩溃

原因

  被复制的源文件mat和函数里面的目标mat都需要宽高,类型相等。

解决

  是因为计算最终的图像错误导致的,纠正逻辑代码。
  在这里插入图片描述


上一篇:《OpenCV开发笔记(七十九):基于Stitcher类实现全景图片拼接》
下一篇:持续补充中…


本文章博客地址:https://hpzwl.blog.csdn.net/article/details/141790116

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2097293.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YOLOS:大道至简,直接使用预训练ViT进行实时目标检测 | NeurIPS 2021

论文探索了在中型ImageNet-1k数据集上预训练的普通ViT到更具挑战性的COCO目标检测基准的可迁移性&#xff0c;提出了基于Vision Transformer的You Only Look at One Sequence(YOLOS)目标检测模型。在具有挑战性的COCO目标检测基准上的实验结果表明&#xff0c;2D目标检测可以以…

Windows系统通过WSL2安装Ubuntu22.04系统及图形化界面

1.通过WSL2安装Ubuntu22.04系统及图形化界面 WSL&#xff08;Windows Subsystem for Linux&#xff09;是一个为Windows用户设计的兼容层&#xff0c;它允许用户在Windows10和Windows11操作系统上直接运行GNU/Linux环境。WSL提供了一个微软开发的Linux兼容内核接口&#xff0c…

MySQL中的时间与当前时间相差8个小时,两步解决。

前言&#xff1a; 有朋友后台私信问&#xff0c;我服务器中的MySQL时间与本地时间差了8个小时&#xff0c;我在给表中字段设置根据时间戳自动更新时会出现时间与当前时间不符的原因。灰常简单&#xff0c;一起来看看吧。 第一步&#xff1a; 在MySQL中&#xff0c;输入命令&…

基于asp.net软件缺陷跟踪系统设计与实现

系统分析 本章讲述了本软件缺陷跟踪系统的前期分析方法和分析结论。 3.1 可靠性分析 在软件研发企业或研发团队中&#xff0c;开发人员和测试人员所应用的软件缺陷管理的主要方法流程是&#xff1a; 1&#xff0e;测试人员发现软件存在的缺陷&#xff0c;填写缺陷报告&…

【正点原子K210连载】第三十四章 image图像滤波实验 摘自【正点原子】DNK210使用指南-CanMV版指南

第三十四章 image图像滤波实验 在上一章节中&#xff0c;介绍了image模块中元素绘制方法给的使用&#xff0c;本章将继续介绍image模块中图像滤波方法的使用。通过本章的学习&#xff0c;读者将学习到image模块中图像滤波的使用。 本章分为如下几个小节&#xff1a; 34.1 imag…

Transformer直接预测完整数学表达式,推理速度提高多个数量级

前言 来自 Mata AI、法国索邦大学、巴黎高师的研究者成功让 Transformer 直接预测出完整的数学表达式。 转载自丨机器之心 符号回归&#xff0c;即根据观察函数值来预测函数数学表达式的任务&#xff0c;通常涉及两步过程&#xff1a;预测表达式的「主干」并选择数值常数&am…

jfif怎么改成jpg格式?这几种转换方法请务必学会!

jfif怎么改成jpg格式&#xff1f;JFIF&#xff0c;作为一种相对不常见的图像存储格式&#xff0c;其在实际应用中确实存在一系列不容忽视的局限&#xff0c;首要问题在于&#xff0c;当尝试将JFIF图片转换为其他格式时&#xff0c;往往会伴随着图像压缩的副作用&#xff0c;这意…

Vue学习笔记 一

Vue学习笔记 1、Vue基础指令 1.1 什么是Vue? Vue.js 是一套响应式的 JavaScript 开发库。Vue.js 自问世以来所受关注度不断提高,在现在的市场上,Vue.js 是非常流行的 JavaScript 技术开发框架之一。 Vue是一款国产前端框架,它的作者尤雨溪(Evan You)是一位美籍华人,…

Android使用addr2line分析Native Crash

NDK提供的工具将函数地址解析为具体的函数名和行数才能进一步分析问题。 常用的地址转换工具有addr2line、ndk-stack等&#xff0c;个人比较喜欢addr2line&#xff0c;所以接下来介绍下该工具的基本使用方式 日常使用过程中&#xff0c;只需要关注-C -f -e三个参数即可 // -…

LaViT:这也行,微软提出直接用上一层的注意力权重生成当前层的注意力权重 | CVPR 2024

Less-Attention Vision Transformer利用了在多头自注意力&#xff08;MHSA&#xff09;块中计算的依赖关系&#xff0c;通过重复使用先前MSA块的注意力来绕过注意力计算&#xff0c;还额外增加了一个简单的保持对角性的损失函数&#xff0c;旨在促进注意力矩阵在表示标记之间关…

注意力机制(Attention mechanism)(中篇)

模型的输入是一组向量&#xff0c;它可以是文字&#xff0c;可以是语音&#xff0c;可以是图。而输出有三种可能性&#xff0c; 第一种可能性是每一个向量都有一个对应的标签。如图1所示&#xff0c;当模型看到输入是4个向 量的时候&#xff0c;它就要输出4个标签。如果是回归问…

React项目通过jsmind实现思维导图以及相关功能

jsMind jsMind 是一个用于显示和编辑思维导图的纯 JavaScript 类库。它基于 Canvas 和 SVG 进行设计&#xff0c;能够在现代浏览器中高效地运行。jsMind 以 BSD 协议开源&#xff0c;这意味着可以在遵守该协议的前提下&#xff0c;将其嵌入到任何项目中使用。 功能特点 jsMi…

从0到1搭建用户管理系统

手把手教你搭建前后端框架 新手对于很多成熟框架&#xff0c;不知道如何搭建的&#xff0c;不知道如何实现等等&#xff0c;忙碌之余&#xff0c;写了一篇博客 手把手教你搭建前后端框架源码&#xff0c; springbootmysqlelementuivue 从0到1&#xff0c;搭建springboot框架&am…

windows下mysql启动失败无报错(已解决)

环境&#xff1a;win server 2012R2 mysql版本&#xff1a;8.0 今天使用net stop mysql;net start mysql进行重启&#xff0c;发现重启失败&#xff0c;而且提示服务没有任何错误。 接着就检查了所有的常见问题&#xff08;如端口占用、配置文件路径错误等&#xff09;仍然无…

024、架构_资源_主机

摘要 在主机管理界面,可查看 GoldenDB 使用的主机信息,可对主机执行新增、编辑、删除操作。 主机列表 选择菜单[资源管理→主机管理],进入主机列表界面。 新增主机(单个、批量) 新增主机流程: 添加

hexo d 报错

上学期四月份左右用hexogithub搭了一个简单的博客&#xff0c;开学回来发现运行hexo d就报错&#xff0c;试了好几种网上的方法解决。&#xff08;虽然好使了&#xff0c;但还是没弄明白啥原理&#xff0c;难道是token过期就不好使吗&#xff1f;&#xff09; 奇妙的解决方案 …

WD100电磁铁-国家实验室方案

根据合肥国家实验室磁场要求&#xff0c;上海天端实业有限公司设计制造WD100电磁铁&#xff0c;参数如下 水冷WD100电磁铁参数资料 产品规格 水冷型WD-100型电磁铁&#xff0c;卧式座放&#xff0c;磁场方向水平&#xff1b; 气隙双向可调&#xff0c;可调范围0-100mm&am…

【GeoScenePro】Generic Server Error

错误 解决方案 在portal中进行知识图谱许可授权

C语言指针进阶一:(字符指针,数组指针,指针数组,函数指针)

字符指针 在指针类型中我们知道字符指针类型&#xff0c;一般就是用于 char 的&#xff1a; int main() {char ch w;char* p &ch;*p a;return 0;} 还有一种使用方法&#xff1a; int main() {const char* pstr "hello bit";printf("%s\n", p…

亚马逊、temu自养号采购大额下单需要解决哪些技术要点?

自养号采购下单在跨境电商平台中是一个复杂且需要精细操作的过程&#xff0c;主要涉及多个技术要点以确保账号安全、提高下单成功率&#xff0c;并避免被平台风控系统识别。以下是需要解决的关键技术要点&#xff1a; 1. 纯净的测评环境 服务器与IP&#xff1a;使用高纯净度的…