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