当前位置: 首页 > article >正文

落地基于特征的图像拼接

针对一张图不同角度的两张图片进行拼接,首先思路进行特征点提取之后进行暴力匹配,将两幅图的特征点进行对比。

#include <opencv2/opencv.hpp>
#include <iostream>
#define RATIO    0.8
using namespace std;
using namespace cv;

void linspace(Mat& image, float begin, float finish, int number, Mat &mask);
void generate_mask(Mat &img, Mat &mask);
int main(int argc, char** argv) {
    Mat left = imread("C:/newword/image/36.jpg");
    Mat right = imread("C:/newword/image/37.jpg");
    if (left.empty() || right.empty()) {
        printf("could not load image...\n");
        return -1;
    }

    // 提取特征点与描述子
    vector<KeyPoint> keypoints_right, keypoints_left;
    Mat descriptors_right, descriptors_left;
    auto detector = AKAZE::create();
    detector->detectAndCompute(left, Mat(), keypoints_left, descriptors_left);
    detector->detectAndCompute(right, Mat(), keypoints_right, descriptors_right);

    // 暴力匹配
    vector<DMatch> matches;
    auto matcher = DescriptorMatcher::create(DescriptorMatcher::BRUTEFORCE);

    // 发现匹配
    std::vector< std::vector<DMatch> > knn_matches;
    matcher->knnMatch(descriptors_left, descriptors_right, knn_matches, 2);
    const float ratio_thresh = 0.5f;
    std::vector<DMatch> good_matches;
    for (size_t i = 0; i < knn_matches.size(); i++)
    {
        if (knn_matches[i][0].distance < ratio_thresh * knn_matches[i][1].distance)//distance代表两点之间的距离度量。意思就是,第一匹配点的值小于第二匹配点的值乘以0.7f被看作为符合配对需求的对吗
        {
            good_matches.push_back(knn_matches[i][0]);
        }
    }
    printf("total good match points : %d\n", good_matches.size());

Mat dst;
drawMatches(left, keypoints_left, right, keypoints_right, good_matches, dst);
imshow("output", dst);
imwrite("C:/newword/21.png", dst);



    //-- Localize the object
    std::vector<Point2f> left_pts;
    std::vector<Point2f> right_pts;
    for (size_t i = 0; i < good_matches.size(); i++)
    {
        // 收集所有好的匹配点
        left_pts.push_back(keypoints_left[good_matches[i].queryIdx].pt);
        right_pts.push_back(keypoints_right[good_matches[i].trainIdx].pt);
    }

    // 配准与对齐,对齐到第一张
    Mat H = findHomography(right_pts, left_pts, RANSAC);

    // 获取全景图大小
    int h = max(left.rows, right.rows);
    int w = left.cols + right.cols;
    Mat panorama_01 = Mat::zeros(Size(w, h), CV_8UC3);
    Rect roi;
    roi.x = 0;
    roi.y = 0;
    roi.width = left.cols;
    roi.height = left.rows;

    // 获取左侧与右侧对齐图像
    left.copyTo(panorama_01(roi));
    imwrite("C:/newword/22.png", panorama_01);
    Mat panorama_02;
    warpPerspective(right, panorama_02, H, Size(w, h));//将侧面视角转换为正面视角
    imwrite("C:/newword/23.png", panorama_02);

    // 计算融合重叠区域mask
    Mat mask = Mat::zeros(Size(w, h), CV_8UC1);
    generate_mask(panorama_02, mask);//对转化后的图像进行掩码处理

    // 创建遮罩层并根据mask完成权重初始化--两张图片平滑过渡
    Mat mask1 = Mat::ones(Size(w, h), CV_32FC1);
    Mat mask2 = Mat::ones(Size(w, h), CV_32FC1);

    // left mask
    linspace(mask1, 1, 0, left.cols, mask);//目的将函数左面图像从1渐变到0以便完成图像的融合函数linspace(mask1, 1, 0, left.cols, mask)中,在mask1矩阵上,从值1开始,以一定的步长递减到0,递减的步数由left.cols指定,mask可能是用于辅助计算或存储中间结果等。即通过这个函数调用,mask1自身会被修改为一个具有线性渐变值的矩阵,而不是将mask1渐变到mask上。

    // right mask
    linspace(mask2, 0, 1, left.cols, mask);目的将函数右面图像从0渐变到1以便完成图像的融合
    imshow("mask1", mask1);
    imshow("mask2", mask2);

    // 左侧融合
    Mat m1;
    vector<Mat> mv;
    mv.push_back(mask1); //线性渐变值的矩阵
    mv.push_back(mask1);
    mv.push_back(mask1);
    merge(mv, m1);//使用merge函数将向量mv中的三个单通道掩码合并成一个三通道掩码M1
    panorama_01.convertTo(panorama_01, CV_32F);//    这行代码将 panorama_01 的数据类型从 CV_8UC3(8 位无符号整数,三通道)转换为 CV_32F(32 位浮点数)。这是因为 multiply 函数要求参与乘法运算的两个矩阵数据类型一致,而 mask1 是 CV_32FC1 类型,扩展后的 m1 也是 CV_32F 类型,所以需要将 panorama_01 转换为相同的数据类型。
    multiply(panorama_01, m1, panorama_01);//    multiply 函数用于对 panorama_01 和 m1 进行逐元素乘法运算,并将结果存储回 panorama_01。由于 m1 是一个掩码,其中的值表示不同位置的权重,通过与 panorama_01 相乘,实现了对 panorama_01 图像不同位置的加权操作。在 mask1 中值较大的区域,panorama_01 相应位置的像素值在相乘后保留得更多;而在 mask1 中值较小的区域,panorama_01 相应位置的像素值在相乘后会被削弱。这样做的目的通常是为了在后续的图像融合过程中,控制左侧图像不同区域的贡献程度,以实现平滑的融合效果。

    // 右侧融合
    mv.clear();
    mv.push_back(mask2);
    mv.push_back(mask2);
    mv.push_back(mask2);
    Mat m2;
    merge(mv, m2);
    panorama_02.convertTo(panorama_02, CV_32F);
    multiply(panorama_02, m2, panorama_02);

    // 合并全景图
    Mat panorama;
    add(panorama_01, panorama_02, panorama);
    panorama.convertTo(panorama, CV_8U);
    imwrite("C:/newword/28.png", panorama);
    waitKey(0);
    return 0;
}

void generate_mask(Mat &img, Mat &mask) {
    int w = img.cols;
    int h = img.rows;
    for (int row = 0; row < h; row++) {
        for (int col = 0; col < w; col++) {
            Vec3b p = img.at<Vec3b>(row, col);
            int b = p[0];
            int g = p[1];
            int r = p[2];
            if (b == g && g == r && r == 0) {
                mask.at<uchar>(row, col) = 255;
            }
        }
    }
    imwrite("C:/newword/29.png", mask);
}

void linspace(Mat& image, float begin, float finish, int w1, Mat &mask) {
    int offsetx = 0;
    float interval = 0;
    float delta = 0;
    for (int i = 0; i < image.rows; i++) {
        offsetx = 0;
        interval = 0;
        delta = 0;
        for (int j = 0; j < image.cols; j++) {
            int pv = mask.at<uchar>(i, j);
            if (pv == 0 && offsetx == 0) {
                offsetx = j;
                delta = w1 - offsetx;
                interval = (finish - begin) / (delta - 1);
                image.at<float>(i, j) = begin + (j - offsetx)*interval;
            }
            else if (pv == 0 && offsetx > 0 && (j - offsetx) < delta) {
                image.at<float>(i, j) = begin + (j - offsetx)*interval;
            }
        }
    }
}


http://www.kler.cn/a/525186.html

相关文章:

  • java求职学习day18
  • 解决Oracle SQL语句性能问题(10.5)——常用Hint及语法(7)(其他Hint)
  • 【机器学习】自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测
  • JVM--类加载器
  • 使用kitty terminal遇到的‘xterm-kitty‘: unknown terminal type.
  • Python3 【函数】:见证算法的优雅与力量
  • 研发的立足之本到底是啥?
  • 跨平台物联网漏洞挖掘算法评估框架设计与实现文献综述之Gemini
  • 我的求职之路合集
  • zsh安装插件
  • Vue演练场基础知识(七)插槽
  • sentence_transformers安装
  • BGP分解实验·15——路由阻尼(抑制/衰减)实验
  • 关于Java的HttpURLConnection重定向问题 响应码303
  • 《DeepSeek R1:开启AI推理新时代》
  • C++实现2025刘谦魔术(勺子 筷子 杯子)
  • 第十六届蓝桥杯大赛软件赛(编程类)知识点大纲
  • 25年1月-A组(萌新)- 云朵工厂
  • 本地部署Deepseek R1
  • S价标准价与V价移动平均价的逻辑,以SAP MM采购订单收货、发票校验过程举例
  • 【Valgrind】安装报错: 报错有未满足的依赖关系: libc6,libc6-dbg
  • 【硬件测试】基于FPGA的QPSK+帧同步系统开发与硬件片内测试,包含高斯信道,误码统计,可设置SNR
  • 网络爬虫学习:应用selenium获取Edge浏览器版本号,自动下载对应版本msedgedriver,确保Edge浏览器顺利打开。
  • Vim安装与配置教程(解决软件包Vim没有安装可候选)
  • 【make】makefile变量全解
  • DeepSeek-R1 本地部署模型流程