开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 58|回复: 1
收起左侧

[技术专题] Opencv SIFT特征点匹配 模版匹配 小地图拼接【实现论证】

[复制链接]
结帖率:88% (80/91)
发表于 前天 14:43 | 显示全部楼层 |阅读模式   江苏省苏州市
本帖最后由 z13228604287 于 2025-5-24 11:00 编辑
1.bmp 2.bmp 3.bmp 4.bmp
1.gif

特征匹配时候背景复杂的背景准确  背景单一的时候识别错误模版匹配相对  特征点匹配  处理复杂背效果也可以   消耗比特征高   
它图像单一时  稳定性 比 特征好很多  也有误判  可以接收

在小地图拼接   模版匹配优于特征匹配,  大图拼接 特征匹配 优于模版匹配  



[C++] 纯文本查看 复制代码
#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
#include <algorithm>

using namespace cv;
using namespace std;

// 计算并显示一个图像在另一个图像中的重叠区域
Point2f calculateOverlapRegion(
    const Mat srcImage,         // 源图像
    const Mat dstImage,         // 目标图像
    const vector<Point2f> srcPoints, // 源图像上的特征点
    const vector<Point2f> dstPoints, // 目标图像上的特征点
    const string windowName     // 显示窗口名称
) {
    // 检查输入点集是否有效(计算单应性矩阵至少需要4个点对)
    if (srcPoints.size() < 4 || dstPoints.size() < 4) {
        cerr << "错误: 计算单应性矩阵需要至少4个点对" << endl;
        return Point2f();
    }

    // 计算单应性矩阵(用于图像配准和变换)
    Mat H = findHomography(srcPoints, dstPoints, RANSAC, 5.0);

    // 检查单应性矩阵是否有效
    if (H.empty()) {
        cerr << "错误: 无法计算有效的单应性矩阵" << endl;
        return Point2f();
    }

    // 获取源图像的四个角点坐标
    vector<Point2f> corners1 = {
        Point2f(0, 0),                   // 左上角
        Point2f(0, srcImage.rows - 1),   // 左下角
        Point2f(srcImage.cols - 1, srcImage.rows - 1), // 右下角
        Point2f(srcImage.cols - 1, 0)    // 右上角
    };

    // 将源图像的角点通过单应性矩阵映射到目标图像坐标系
    vector<Point2f> corners2(4);
    perspectiveTransform(corners1, corners2, H);

    // 计算重叠区域边界(确保在目标图像范围内)
    vector<float> x_coords, y_coords;
    for (const auto& pt : corners2) {
        x_coords.push_back(pt.x);
        y_coords.push_back(pt.y);
    }

    // 计算重叠区域的最小和最大坐标(确保在目标图像边界内)
    float x_min = max(0.0f, *min_element(x_coords.begin(), x_coords.end()));
    float y_min = max(0.0f, *min_element(y_coords.begin(), y_coords.end()));
    float x_max = min((float)dstImage.cols - 1, *max_element(x_coords.begin(), x_coords.end()));
    float y_max = min((float)dstImage.rows - 1, *max_element(y_coords.begin(), y_coords.end()));

    // 返回重叠区域的左上角坐标
    return Point2i(x_min, y_min);
}

// 检测两个图像之间的重叠区域并计算对齐点
Point2i detectOverlap(Mat img1, Mat img2, int BLOCK_SIZE, float distance = 0.5f,
    int minGoodMatches = 4,       // 最小良好匹配点数量,低于此值将被视为匹配失败
    double featureThreshold = 0.04, // SIFT特征检测阈值,控制检测到的特征点数量
    double edgeThreshold = 10.0)  // SIFT边缘响应阈值,控制边缘特征点的过滤
{
    // 转换为灰度图(特征检测通常在灰度图像上进行)
    Mat gray1, gray2;
    cvtColor(img1, gray1, COLOR_BGR2GRAY);
    cvtColor(img2, gray2, COLOR_BGR2GRAY);

    // 使用SIFT特征检测器和描述符(用于图像特征提取和匹配)
    Ptr<SIFT> sift = SIFT::create(0, 3, featureThreshold, edgeThreshold);
    vector<KeyPoint> kp1, kp2;  // 关键点容器
    Mat des1, des2;             // 特征描述符矩阵
    sift->detectAndCompute(gray1, noArray(), kp1, des1);
    sift->detectAndCompute(gray2, noArray(), kp2, des2);

    // 屏蔽中心区域的关键点(避免中心区域特征主导匹配结果)
    int centerX1 = gray1.cols / 2;
    int centerY1 = gray1.rows / 2;
    vector<KeyPoint> filteredKp1;
    for (const auto& kp : kp1) {
        if (abs(kp.pt.x - centerX1) > BLOCK_SIZE / 2.0 ||
            abs(kp.pt.y - centerY1) > BLOCK_SIZE / 2.0) {
            filteredKp1.push_back(kp);
        }
    }

    int centerX2 = gray2.cols / 2;
    int centerY2 = gray2.rows / 2;
    vector<KeyPoint> filteredKp2;
    for (const auto& kp : kp2) {
        if (abs(kp.pt.x - centerX2) > BLOCK_SIZE / 2.0 ||
            abs(kp.pt.y - centerY2) > BLOCK_SIZE / 2.0) {
            filteredKp2.push_back(kp);
        }
    }

    // 使用FLANN匹配器进行特征点匹配(快速最近邻搜索库)
    FlannBasedMatcher matcher;
    vector<vector<DMatch>> knnMatches;
    matcher.knnMatch(des1, des2, knnMatches, 2);

    // 检查描述符是否为空
    if (des1.empty() || des2.empty()) {
        cerr << "错误: 特征描述符为空" << endl;
        return Point2f();
    }

    // 应用比率测试过滤匹配点(保留最佳匹配)
    vector<DMatch> goodMatches;
    for (size_t i = 0; i < knnMatches.size(); i++) {
        if (knnMatches[0].distance < distance * knnMatches[1].distance) {
            goodMatches.push_back(knnMatches[0]);
        }
    }

    // 检查良好匹配点数量是否足够
    if (goodMatches.size() < minGoodMatches) {
        cerr << "错误: 良好匹配点数量不足 (" << goodMatches.size()
            << " < " << minGoodMatches << ")" << endl;
        return Point2f();
    }

    // 提取匹配点的坐标
    vector<Point2f> pts1, pts2;
    for (const auto& match : goodMatches) {
        pts1.push_back(kp1[match.queryIdx].pt);
        pts2.push_back(kp2[match.trainIdx].pt);
    }

    // 计算两个方向的重叠区域并返回对齐点
    Point2i points1 = calculateOverlapRegion(img2, img1, pts2, pts1, "重叠区域1");
    Point2i points2 = calculateOverlapRegion(img1, img2, pts1, pts2, "重叠区域2");

    // 计算最终对齐点(两点之间的差异)
    Point2i points;
    points.x = points1.x - points2.x;
    points.y = points1.y - points2.y;
    return points;
}

// 创建中心区域蒙版(用于屏蔽图像中心区域)
Mat createCenterMask(int width, int height, int blockSize) {
    // 创建全白蒙版(255表示不屏蔽)
    Mat mask = Mat(height, width, CV_8UC1, Scalar(255));

    // 计算中心区域
    int centerX = width / 2;
    int centerY = height / 2;
    int halfBlock = blockSize / 2;

    // 确定中心区域的边界(确保不超出图像范围)
    int x1 = max(0, centerX - halfBlock);
    int y1 = max(0, centerY - halfBlock);
    int x2 = min(width - 1, centerX + halfBlock);
    int y2 = min(height - 1, centerY + halfBlock);

    // 将中心区域设为黑色(0表示屏蔽)
    if (x2 > x1 && y2 > y1) {
        mask(Rect(x1, y1, x2 - x1, y2 - y1)).setTo(0);
    }

    return mask;
}

int main() {
    // 定义图像路径数组(待拼接的图像)
    const vector<string> imagePaths = {
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\1.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\2.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\3.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\4.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\5.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\6.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\7.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\8.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\9.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\10.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\11.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\12.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\13.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\14.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\15.bmp",
        "C:\\Users\\Administrator\\Desktop\\新建文件夹 (2)\\16.bmp"
    };

    // 创建显示窗口
    namedWindow("拼接结果", WINDOW_NORMAL);

    bool shouldExit = false;  // 退出标志

    while (!shouldExit) {
        Mat MapCollage;        // 最终拼接图像
        Point2i AlignmentPoint; // 对齐点
        double proportion = 6.0; // 图像缩放比例
        int MapSize = 8000;    // 拼接图像大小
        int BLOCK_SIZE = 50;   // 屏蔽中心区域大小
        Mat CacheMap;          // 缓存上一张图像

        // 读取并处理图像序列
        for (const auto& path : imagePaths) {
            // 检查是否有按键事件,任意键跳出循环
            if (waitKey(1) >= 0) {
                shouldExit = true;
                break;
            }

            // 读取图像
            Mat img = imread(path);
            if (img.empty()) {
                cerr << "无法加载图像: " << path << endl;
                continue; // 跳过当前图像,继续处理下一个
            }

            // 调整图像大小
            cv::resize(img, img, cv::Size(), proportion, proportion, cv::INTER_CUBIC);

            // 第一张图像处理(作为基准)
            if (MapCollage.empty()) {
                // 计算图像居中放置的位置
                int x = (MapSize - img.cols) / 2;
                int y = (MapSize - img.rows) / 2;
                int width = img.cols;
                int height = img.rows;

                // 创建空白拼接图像
                MapCollage = Mat::zeros(MapSize, MapSize, CV_8UC3);

                // 检查ROI是否越界
                if (x < 0 || y < 0 || x + width > MapCollage.cols || y + height > MapCollage.rows) {
                    MapCollage.release();
                    continue; // 跳过当前图像,继续处理下一个
                }

                // 创建感兴趣区域(ROI)并将图像复制到中心位置
                Rect roi(x, y, width, height);
                Mat targetRegion = MapCollage(roi);
                Mat Mask = createCenterMask(img.cols, img.rows, BLOCK_SIZE);
                img(Rect(0, 0, width, height)).copyTo(targetRegion, Mask);

                // 显示拼接结果
                imshow("拼接结果", MapCollage);

                // 记录对齐点(左上角坐标)
                AlignmentPoint.x = x;
                AlignmentPoint.y = y;

                std::cout << "[x:" << x << " y:" << y << "]" << std::endl;

                cv::waitKey(1000);
            }
            else {
                // 非第一张图像,计算与上一张图像的重叠区域并确定对齐点
                Point2i points = detectOverlap(CacheMap, img, 5);

                // 更新对齐点
                AlignmentPoint.x += points.x;
                AlignmentPoint.y += points.y;

                // 计算图像放置的位置
                int x = AlignmentPoint.x;
                int y = AlignmentPoint.y;

                std::cout << "[x:" << x << " y:" << y << "]" << std::endl;
                int width = img.cols;
                int height = img.rows;

                // 检查ROI是否越界
                if (x < 0 || y < 0 || x + width > MapCollage.cols || y + height > MapCollage.rows) {
                    continue; // 跳过当前图像,继续处理下一个
                }

                // 创建感兴趣区域并将图像复制到对应位置
                Rect roi(x, y, width, height);
                Mat targetRegion = MapCollage(roi);
                Mat Mask = createCenterMask(img.cols, img.rows, BLOCK_SIZE);
                img(Rect(0, 0, width, height)).copyTo(targetRegion, Mask);

                // 显示拼接结果
                imshow("拼接结果", MapCollage);

                cv::waitKey(1000);
            }

            // 缓存当前图像,用于下一次比对
            CacheMap = img.clone();
        }

        // 清屏(控制台)
        system("cls");

        // 等待用户按键决定是否继续或退出
        std::cout << "按任意键继续,ESC键退出..." << std::endl;
        if (waitKey(3000) == 27) { // ESC键的ASCII码是27
            shouldExit = true;
        }
    }

    return 0;
}



游客,如果您要查看本帖隐藏内容请回复


结帖率:0% (0/1)

签到天数: 20 天

发表于 8 小时前 | 显示全部楼层   广东省广州市
RE: Opencv SIFT特征点匹配 模版匹配 小地图拼接【实现论证】 [修改]
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报电话0663-3422125,QQ: 793400750,邮箱:wp@125.la
网站简介:精易论坛成立于2009年,是一个程序设计学习交流技术论坛,隶属于揭阳市揭东区精易科技有限公司所有。
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备12094385号-1) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表