落地基于特征的图像拼接
针对一张图不同角度的两张图片进行拼接,首先思路进行特征点提取之后进行暴力匹配,将两幅图的特征点进行对比。
#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;
}
}
}
}