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

若该文为原创文章,转载请注明原文出处

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

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

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

上一篇:《OpenCV开发笔记(七十九):基于Stitcher类实现全景图片拼接

下一篇:持续补充中...

前言

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

本篇使用特征点匹配,进行两张图来视野合并拼接。

Demo

100%的点匹配

换了一幅图:

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

两张图的拼接过程

步骤一:打开图片

cpp 复制代码
 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");

步骤二:提取特征点

cpp 复制代码
// 提取特征点
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);

步骤三:暴力匹配

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

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

cpp 复制代码
// 筛选匹配点, 根据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

步骤五:计算变换矩阵

cpp 复制代码
// 准备匹配的点
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);

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

cpp 复制代码
// 计算第二张图的变换大小
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];
}

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

cpp 复制代码
// 图像变换
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);

步骤八:进行融合

cpp 复制代码
// 创建拼接后的图
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

cpp 复制代码
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

cpp 复制代码
// 该函数结合了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

cpp 复制代码
static Ptr<BFMatcher> create()

初始化暴力匹配实例。

函数cv::FlannBasedMatcher::match

cpp 复制代码
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

cpp 复制代码
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 的矩阵,它将一个平面上的点映射到另一个平面(可能是同一个平面,但通常是在不同的视角或条件下)上的点。这在计算机视觉和图像处理中非常有用,比如图像拼接、增强现实、相机姿态估计等领域。

cpp 复制代码
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

cpp 复制代码
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

cpp 复制代码
void cv::Mat::copyTo(OutputArray m, InputArray mask=noArray()) const;

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

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

Demo源码

cpp 复制代码
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

相关推荐
秣厉科技2 小时前
【秣厉科技】LabVIEW工具包——OpenCV 教程(14):features2d 基本应用
科技·opencv·labview
xc101117 小时前
树莓派5从零开发至脱机脚本运行教程——6.实战篇
人工智能·opencv·计算机视觉
www_pp_18 小时前
# 基于 OpenCV 的选择题自动批改系统实现
人工智能·opencv·计算机视觉
不吃香菜?19 小时前
OpenCV图像处理基础到进阶之高阶操作
图像处理·人工智能·opencv
不吃香菜?20 小时前
opencv图像处理之指纹验证
人工智能·opencv·计算机视觉
郝YH是人间理想20 小时前
OpenCV基础——轮廓检测、模板匹配、图像均衡化
开发语言·图像处理·人工智能·python·opencv·计算机视觉
qp1 天前
18.OpenCV图像卷积及其模糊滤波应用详解
人工智能·opencv·计算机视觉
郝YH是人间理想1 天前
OpenCV基础——梯度计算、边缘检测、图像金字塔
开发语言·人工智能·python·opencv·计算机视觉
qp1 天前
19.OpenCV图像二值化
人工智能·opencv·计算机视觉
放羊郎2 天前
OpenCV、YOLO与大模型的区别与关系
人工智能·opencv·yolo·大模型