开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

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

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


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

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

查看: 1424|回复: 38
收起左侧

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

[复制链接]
结帖率:89% (81/91)
发表于 2025-5-23 14:43:19 | 显示全部楼层 |阅读模式   江苏省苏州市
本帖最后由 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;
}



#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
#include <algorithm>

using namespace cv;
using namespace std;

/**
* 裁剪图像中心区域并返回裁剪信息
* @param src 输入图像
* @param cropRatio 裁剪比例(0.0-1.0),表示裁剪后图像占原始图像的比例
* @param[out] pixelsCroppedX 水平方向裁剪的总像素数
* @param[out] pixelsCroppedY 垂直方向裁剪的总像素数
* @return 裁剪后的中心区域图像
*/
Mat cropCenter(const Mat& src, float cropRatio, int& pixelsCroppedX, int& pixelsCroppedY) {
    // 确保输入图像有效
    if (src.empty()) {
        cerr << "错误:输入图像为空!" << endl;
        pixelsCroppedX = 0;
        pixelsCroppedY = 0;
        return Mat();
    }

    // 确保裁剪比例在有效范围内
    cropRatio = max(0.0f, min(1.0f, cropRatio));

    // 获取图像尺寸
    int width = src.cols;
    int height = src.rows;

    // 计算裁剪区域的大小
    int cropWidth = static_cast<int>(width * cropRatio);
    int cropHeight = static_cast<int>(height * cropRatio);

    // 计算裁剪区域的起始坐标(居中裁剪)
    int startX = (width - cropWidth) / 2;
    int startY = (height - cropHeight) / 2;

    pixelsCroppedX = startX;
    pixelsCroppedY = startY;

    // 创建裁剪区域的矩形
    Rect cropRect(startX, startY, cropWidth, cropHeight);

    // 裁剪图像并返回
    return src(cropRect).clone();
}

// 创建中心区域蒙版(用于屏蔽图像中心区域)
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;
}

// 判断匹配方法是否支持掩码
bool isMaskSupported(int method) {
    return method == TM_SQDIFF || method == TM_SQDIFF_NORMED ||
        method == TM_CCORR_NORMED || method == TM_CCOEFF_NORMED;
}

/**
* 执行模板匹配
* @param sourceImage 源图像
* @param templateImage 模板图像
* @param cropRatio 裁剪比例,用于裁剪模板图像中心区域
* @param blockSize 屏蔽中心区域大小
* @param matchLocation 输出参数,最佳匹配位置
* @param method 模板匹配方法,如CV_TM_CCOEFF_NORMED等
* @return 匹配分数,范围0-1,1表示最佳匹配
*/
double performTemplateMatching(const cv::Mat sourceImage, const cv::Mat templateImage,
    float cropRatio, int blockSize, cv::Point& matchLocation,
    int method = TM_CCOEFF_NORMED) {
    // 确保输入图像有效
    if (sourceImage.empty() || templateImage.empty()) {
        std::cerr << "错误:源图像或模板图像为空!" << std::endl;
        return -1.0;
    }

    // 确保模板尺寸不大于源图像
    if (templateImage.rows > sourceImage.rows || templateImage.cols > sourceImage.cols) {
        std::cerr << "错误:模板尺寸大于源图像!" << std::endl;
        return -1.0;
    }

    int pixelsCroppedX, pixelsCroppedY;
    cv::Mat templateCropped = cropCenter(templateImage, cropRatio, pixelsCroppedX, pixelsCroppedY);

    // 检查是否成功提取子模板
    if (templateCropped.empty()) {
        std::cerr << "错误:未能提取有效模板!" << std::endl;
        return -1.0;
    }

    cv::Mat mask;
    // 创建掩码并检查是否支持使用掩码
    if (isMaskSupported(method)) {
        mask = createCenterMask(templateCropped.cols, templateCropped.rows, blockSize);
        if (mask.empty()) {
            std::cerr << "错误:未能创建有效掩码!" << std::endl;
            return -1.0;
        }
    }
    else {
        std::cout << "警告:所选匹配方法不支持掩码,将不使用掩码进行匹配" << std::endl;
    }

    // 创建结果矩阵
    cv::Mat result;
    try {
        // 执行模板匹配
        if (isMaskSupported(method) && !mask.empty()) {
            cv::matchTemplate(sourceImage, templateCropped, result, method, mask);
        }
        else {
            cv::matchTemplate(sourceImage, templateCropped, result, method);
        }
    }
    catch (const cv::Exception& e) {
        std::cerr << "匹配过程中发生异常: " << e.what() << std::endl;
        return -1.0;
    }

    // 查找最佳匹配位置
    double minVal, maxVal;
    cv::Point minLoc, maxLoc;
    cv::minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc);
    // 分数
    double score;

    // 根据匹配方法确定最佳匹配位置
    if (method == TM_SQDIFF || method == TM_SQDIFF_NORMED) {
        matchLocation = minLoc;
        score = 1 - minVal;
    }
    else {
        matchLocation = maxLoc;
        score=maxVal;
    }


    // 绘制匹配结果(可选)
    cv::Mat displayImage = sourceImage.clone();
    cv::rectangle(displayImage, cv::Rect(matchLocation, templateCropped.size()),
        cv::Scalar(0, 255, 0), 2, cv::LINE_AA);
    cv::imshow("匹配结果", displayImage);
    cv::waitKey(1000);

    // 调整匹配位置(考虑裁剪的偏移)
    matchLocation.x -= pixelsCroppedX;
    matchLocation.y -= pixelsCroppedY;

    return score;
}

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"
    };

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

    bool shouldExit = false;  // 退出标志

    while (!shouldExit) {
        Mat mapCollage;        // 最终拼接图像
        Point2i alignmentPoint; // 对齐点
        double proportion = 2.0; // 图像缩放比例
        int mapSize = 2000;    // 拼接图像大小
        int blockSize = 20;    // 屏蔽中心区域大小
        float cropRatio = 0.5f;
        int matchMethod = TM_CCOEFF_NORMED; // 默认使用归一化相关系数匹配方法

        // 显示可用的匹配方法
        std::cout << "可用的模板匹配方法:" << std::endl;
        std::cout << "1: CV_TM_SQDIFF" << std::endl;
        std::cout << "2: CV_TM_SQDIFF_NORMED" << std::endl;
        std::cout << "3: CV_TM_CCORR" << std::endl;
        std::cout << "4: CV_TM_CCORR_NORMED" << std::endl;
        std::cout << "5: CV_TM_CCOEFF (默认)" << std::endl;
        std::cout << "6: CV_TM_CCOEFF_NORMED" << std::endl;
        std::cout << "请选择匹配方法(1-6): ";

        int methodChoice;
        std::cin >> methodChoice;

        // 处理用户选择
        switch (methodChoice) {
        case 1: matchMethod = TM_SQDIFF; break;
        case 2: matchMethod = TM_SQDIFF_NORMED; break;
        case 3: matchMethod = TM_CCORR; break;
        case 4: matchMethod = TM_CCORR_NORMED; break;
        case 5: matchMethod = TM_CCOEFF; break;
        case 6: matchMethod = TM_CCOEFF_NORMED; break;
        default:
            std::cout << "无效选择,使用默认方法 CV_TM_CCOEFF_NORMED" << std::endl;
            matchMethod = TM_CCOEFF_NORMED;
        }


        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) {
                    cerr << "警告:第一张图像位置越界,无法初始化拼接图像" << endl;
                    mapCollage.release();
                    // 添加明确的错误处理,避免继续执行
                    shouldExit = true;
                    break;
                }

                // 创建感兴趣区域(ROI)并将图像复制到中心位置
                Rect roi(x, y, width, height);
                Mat targetRegion = mapCollage(roi);
                Mat mask = createCenterMask(img.cols, img.rows, blockSize);
                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 {
                Point matchLocation;
                double score = performTemplateMatching(cacheMap, img, cropRatio, blockSize, matchLocation, matchMethod);

                if (score < 0) {
                    cerr << "错误:模板匹配失败,跳过当前图像" << endl;
                    continue;
                }

                std::cout << "匹配分数:" << score << " [" << matchLocation.x << "," << matchLocation.y << "]" << std::endl;

                // 更新对齐点
                alignmentPoint.x += matchLocation.x;
                alignmentPoint.y += matchLocation.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) {
                    std::cout << "警告:图像位置越界,跳过当前图像" << std::endl;
                    continue; // 跳过当前图像,继续处理下一个
                }

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

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

                cv::waitKey(3000);
            }

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

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

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

    return 0;
}

结帖率:89% (81/91)

签到天数: 6 天

 楼主| 发表于 2025-5-26 20:06:00 | 显示全部楼层   江苏省苏州市
  
窗口程序集名保 留  保 留备 注
程序集1   
子程序名返回值类型公开备 注
_启动子程序整数型 本子程序在程序启动后最先执行
变量名类 型静态数组备 注
图片集数组容器 
小地图数据矩阵类 
小地图 = 视觉_图像解码 ( #图片1, #读图_彩色模式 )
图片集.加入数据矩阵类 (小地图)
小地图 = 视觉_图像解码 ( #图片2, #读图_彩色模式 )
图片集.加入数据矩阵类 (小地图)
小地图 = 视觉_图像解码 ( #图片3, #读图_彩色模式 )
图片集.加入数据矩阵类 (小地图)
小地图 = 视觉_图像解码 ( #图片4, #读图_彩色模式 )
图片集.加入数据矩阵类 (小地图)
小地图 = 视觉_图像解码 ( #图片5, #读图_彩色模式 )
图片集.加入数据矩阵类 (小地图)
小地图 = 视觉_图像解码 ( #图片6, #读图_彩色模式 )
图片集.加入数据矩阵类 (小地图)
小地图 = 视觉_图像解码 ( #图片7, #读图_彩色模式 )
图片集.加入数据矩阵类 (小地图)
小地图 = 视觉_图像解码 ( #图片8, #读图_彩色模式 )
图片集.加入数据矩阵类 (小地图)
小地图 = 视觉_图像解码 ( #图片9, #读图_彩色模式 )
图片集.加入数据矩阵类 (小地图)
小地图 = 视觉_图像解码 ( #图片10, #读图_彩色模式 )
图片集.加入数据矩阵类 (小地图)
小地图 = 视觉_图像解码 ( #图片11, #读图_彩色模式 )
图片集.加入数据矩阵类 (小地图)
小地图 = 视觉_图像解码 ( #图片12, #读图_彩色模式 )
图片集.加入数据矩阵类 (小地图)
' 模拟匹配拼接
模版匹配拼接 (图片集, 1500, 20, 2, 0.5, #匹配_归一化相关_标准 )
返回 (0)
子程序名返回值类型公开备 注
模版匹配拼接  
参数名类 型参考可空数组备 注
图片集数组容器
拼接尺寸整数型拼接图像最终大小  要足够大
中心尺寸整数型屏蔽中心区域大小
缩放双精度小数型图像缩放比例
裁剪比例小数型裁剪比例(0.0-1.0)
匹配模式整数型
变量名类 型静态数组备 注
应该退出逻辑型退出标志
拼接图像数据矩阵类最终拼接图像
小地图数据矩阵类 
对齐点坐标二维整型结构 
匹配位置坐标二维整型结构 
缓存图数据矩阵类缓存上一张图像
i整数型 
尺寸变换图数据矩阵类 
x整数型 
y整数型 
整数型 
整数型 
感兴区域矩形整型结构 
目标区域数据矩阵类 
掩码数据矩阵类 
分数双精度小数型 
应该退出 = 假
' 创建显示窗口
视觉_创建窗口 (“拼接结果”, #窗口_普通模式 )
判断循环首 (应该退出 = )
计次循环首 (图片集.成员数 (), i)
小地图 = 图片集.取数据矩阵类 (i)
如果真 (小地图. ())
调试输出 (“图像为空!”)
到循环尾 ()  ' 跳过当前图像,继续处理下一个

' 调整图像大小
视觉_尺寸变换 (小地图, 尺寸变换图, , 缩放, 缩放, #插值_双线性三次 )
' 第一张图像处理(作为基准)
判断 (拼接图像. ())
' 创建空白拼接图像
拼接图像.初始化零 (拼接尺寸, 拼接尺寸, #矩阵_三通道字节型U )
' 计算图像居中放置的位置
x (拼接尺寸 - 尺寸变换图.列数) ÷ 2
y (拼接尺寸 - 尺寸变换图.行数) ÷ 2
宽 = 尺寸变换图.列数
高 = 尺寸变换图.行数
' 检查ROI是否越界
如果真 (x < 0 y < 0 x + 宽 > 拼接图像.列数 y + 高 > 拼接图像.行数)
调试输出 (“警告:第一张图像位置越界,无法初始化拼接图像”)
拼接图像.释放 ()
应该退出 = 真
跳出循环 ()
' 创建感兴趣区域(ROI)并将图像复制到中心位置
感兴区域.初始化 (x, y, 宽, 高)
目标区域 = 拼接图像.感兴区域 (感兴区域)
掩码 = 创建中心掩码 (尺寸变换图, 中心尺寸)
尺寸变换图.复制 (目标区域, )
对齐点.横坐标 = x
对齐点.纵坐标 = y
分数 = 执行模板匹配 (缓存图, 尺寸变换图, 裁剪比例, 中心尺寸, 匹配位置, 匹配模式)
如果 (分数 < 0)
调试输出 (“错误:模板匹配失败,跳过当前图像”)
到循环尾 ()
调试输出 (“匹配分数:”, 分数, “[”, 匹配位置.横坐标, 匹配位置.纵坐标, “]”)
' 更新对齐点 计算图像放置的位置
对齐点.横坐标 = 对齐点.横坐标 + 匹配位置.横坐标
对齐点.纵坐标 = 对齐点.纵坐标 + 匹配位置.纵坐标
调试输出 (“拼接位置:[”, 对齐点.横坐标, 对齐点.纵坐标, “]”)
' 检查ROI是否越界
如果真 (对齐点.横坐标 < 0 对齐点.纵坐标 < 0 x + 尺寸变换图.列数 > 拼接图像.列数 y + 尺寸变换图.行数 > 拼接图像.行数)
调试输出 (“警告:图像位置越界,跳过当前图像”)
到循环尾 ()
' 创建感兴趣区域并将图像复制到对应位置
感兴区域.初始化 (对齐点.横坐标, 对齐点.纵坐标, 尺寸变换图.列数, 尺寸变换图.行数)
目标区域 = 拼接图像.感兴区域 (感兴区域)
掩码 = 创建中心掩码 (尺寸变换图, 中心尺寸)
尺寸变换图.复制 (目标区域, )
' 显示拼接结果
视觉_显示图像 (“拼接结果”, 拼接图像)
' 检查是否有按键事件,任意键跳出循环
如果真 (视觉_等待按键 (100) ≥ 0)
跳出循环 ()
' 缓存当前图像,用于下一次比对
缓存图 = 尺寸变换图.克隆 ()
计次循环尾 ()
' 等待用户按键决定是否继续或退出
如果真 (视觉_等待按键 (1000) = 27)
应该退出 = 真
拼接图像.释放 ()
判断循环尾 ()
子程序名返回值类型公开备 注
是否支持掩码逻辑型 判断匹配方法是否支持掩码
参数名类 型参考可空数组备 注
匹配模式整数型
返回 (匹配模式 = #匹配_平方差 匹配模式 = #匹配_平方差_标准 匹配模式 = #匹配_相关_标准 匹配模式 = #匹配_归一化相关_标准 )
子程序名返回值类型公开备 注
创建中心掩码数据矩阵类 创建中心区域蒙版(用于屏蔽图像中心区域)
参数名类 型参考可空数组备 注
数据矩阵类
中心尺寸整数型
变量名类 型静态数组备 注
蒙版数据矩阵类 
中心X整数型 
中心Y整数型 
半径整数型 
X1整数型 
Y1整数型 
X2整数型 
Y2整数型 
' 创建全白蒙版(255表示不屏蔽)
蒙版.初始化 (图.行数, 图.列数, 0, 标量 (255))
' 计算中心区域
中心X = 图.列数 ÷ 2
中心Y = 图.行数 ÷ 2
半径 = 中心尺寸 ÷ 2
' 确定中心区域的边界(确保不超出图像范围)
X1 = 视觉_取最大值 (0, 中心X - 半径)
Y1 = 视觉_取最大值 (0, 中心Y - 半径)
X2 = 视觉_取最小值 (图.列数 - 1, 中心X + 半径)
Y2 = 视觉_取最小值 (图.行数 - 1, 中心Y + 半径)
' 将中心区域设为黑色(0表示屏蔽)
如果真 (X2 > X1 Y2 > Y1)
蒙版.感兴区域 (矩形整型 (X1, X2, X2 - X1, Y2 - Y1)).设置 (标量 (0), )
返回 (蒙版)
子程序名返回值类型公开备 注
取子模板数据矩阵类 
参数名类 型参考可空数组备 注
输入图像数据矩阵类
裁剪小数型裁剪比例(0.0-1.0),表示裁剪后图像占原始图像的比例
裁剪X整数型水平方向裁剪的位置
裁剪Y整数型垂直方向裁剪的位置
变量名类 型静态数组备 注
整数型 
整数型 
复制宽整数型 
复制高整数型 
开始X整数型 
开始Y整数型 
裁剪区域矩形整型结构 
' 确保裁剪比例在有效范围内
裁剪 = 视觉_取最大值 (0, 视觉_取最小值 (1, 裁剪))
' 获取图像尺寸
宽 = 输入图像.列数
高 = 输入图像.行数
' 计算裁剪区域的大小
复制宽 = 宽 × 裁剪
复制高 = 高 × 裁剪
' 计算裁剪区域的起始坐标(居中裁剪)
开始X (宽 - 复制宽) ÷ 2
开始Y (高 - 复制高) ÷ 2
' 创建裁剪区域的矩形
裁剪区域.初始化 (开始X, 开始Y, 复制宽, 复制高)
' 输出裁剪坐标
裁剪X = 开始X
裁剪Y = 开始Y
' 裁剪图像并返回
返回 (输入图像.感兴区域 (裁剪区域))
子程序名返回值类型公开备 注
执行模板匹配双精度小数型 
参数名类 型参考可空数组备 注
源图像数据矩阵类
模板图像数据矩阵类
裁剪比例小数型用于裁剪模板图像中心区域
中心尺寸整数型屏蔽中心区域大小
最佳匹配位置坐标二维整型结构
匹配方法整数型模板匹配方法
变量名类 型静态数组备 注
裁剪X整数型 
裁剪Y整数型 
子模版数据矩阵类 
掩码数据矩阵类 
结果数据矩阵类 
最小值双精度小数型 
最大值双精度小数型 
最小值坐标坐标二维整型结构 
最大值坐标坐标二维整型结构 
分数双精度小数型 
绘制图数据矩阵类 
' 确保输入图像有效
如果真 (源图像. () 模板图像. ())
调试输出 (“错误:源图像或模板图像为空!”)
返回 (-1)
' 确保模板尺寸不大于源图像
如果真 (模板图像.行数 > 模板图像.行数 源图像.列数 > 源图像.列数)
调试输出 (“错误:模板尺寸大于源图像!”)
返回 (-1)
子模版 = 取子模板 (模板图像, 裁剪比例, 裁剪X, 裁剪Y)
' 检查是否成功提取子模板
如果真 (子模版. ())
调试输出 (“错误:未能提取有效模板!”)
返回 (-1)
' 创建掩码并检查是否支持使用掩码
如果 (是否支持掩码 (匹配方法))
掩码 = 创建中心掩码 (子模版, 中心尺寸)
如果真 (掩码. ())
调试输出 (“错误:未能创建有效掩码!”)
返回 (-1)

调试输出 (“警告:所选匹配方法不支持掩码,将不使用掩码进行匹配”)
' 执行模板匹配
判断 (是否支持掩码 (匹配方法) 掩码. ())
视觉_模板匹配 (源图像, 子模版, 结果, 匹配方法, 掩码)
视觉_模板匹配 (源图像, 子模版, 结果, 匹配方法, )
' 查找最佳匹配位置
视觉_最小最大位置 (结果, 最小值, 最大值, 最小值坐标, 最大值坐标, )
' 根据匹配方法确定最佳匹配位置
如果 (匹配方法 = #匹配_平方差 匹配方法 = #匹配_平方差_标准 )
分数 = 1 - 最小值
最佳匹配位置.横坐标 = 最小值坐标.横坐标 - 裁剪X
最佳匹配位置.纵坐标 = 最小值坐标.纵坐标 - 裁剪Y
分数 = 最大值



i支持库列表   支持库注释   
OpenCV(未知支持库)
spec特殊功能支持库

回复 支持 反对

使用道具 举报

签到天数: 1 天

发表于 5 天前 | 显示全部楼层   广东省江门市
66666666666666666666
回复 支持 反对

使用道具 举报

发表于 2025-7-15 08:30:49 | 显示全部楼层   福建省龙岩市
我要,这个好用的
回复 支持 反对

使用道具 举报

结帖率:42% (5/12)

签到天数: 4 天

发表于 2025-7-12 14:48:24 | 显示全部楼层   黑龙江省哈尔滨市
大佬还在更新 我超
回复 支持 反对

使用道具 举报

发表于 2025-6-16 05:25:12 | 显示全部楼层   四川省绵阳市
hdjhfdjtrjfjfjfj
回复 支持 反对

使用道具 举报

签到天数: 10 天

发表于 2025-6-16 00:43:25 | 显示全部楼层   四川省成都市
RE: Opencv SIFT特征点匹配 模版匹配 小地图拼接【实现论证】 [修改]
回复 支持 反对

使用道具 举报

结帖率:50% (3/6)
发表于 2025-6-15 16:39:46 | 显示全部楼层   辽宁省本溪市
RE: Opencv SIFT特征点匹配 模版匹配 小地图拼接【实现论证】 [修改]
回复 支持 反对

使用道具 举报

签到天数: 6 天

发表于 2025-6-13 21:11:21 | 显示全部楼层   四川省成都市
66666666666666
回复 支持 反对

使用道具 举报

签到天数: 2 天

发表于 2025-6-12 23:18:30 | 显示全部楼层   河南省开封市
66666666666666666666666666666
回复 支持 反对

使用道具 举报

签到天数: 2 天

发表于 2025-6-12 23:08:39 | 显示全部楼层   浙江省嘉兴市
6666666
回复 支持 反对

使用道具 举报

结帖率:0% (0/1)
发表于 2025-6-11 21:53:51 | 显示全部楼层   四川省攀枝花市
666666666666666666666666666666666666666666
回复 支持 反对

使用道具 举报

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

本版积分规则 致发广告者

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

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

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