opencv-FindHomography接口-C语言实现
什么是单应性矩阵?
单应性矩阵(Homography)是一个 3x3 的变换矩阵,用于描述两个平面之间的投影变换。在计算机视觉中,它通常用于将一张图像中的点映射到另一张图像中的对应点,尤其是在两张图像拍摄的是同一个平面但视角不同的情况下。
找到单应性矩阵的步骤
-
找到对应点:
-
需要至少 4 对对应点,这些点分别来自两张图像,并且在现实世界中位于同一个平面上。
-
设第一张图像中的点为 (𝑥1,𝑦1),(𝑥2,𝑦2),…,(𝑥𝑛,𝑦𝑛)(x1,y1),(x2,y2),…,(xn,yn),第二张图像中的对应点为 (𝑥1′,𝑦1′),(𝑥2′,𝑦2′),…,(𝑥𝑛′,𝑦𝑛′)(x1′,y1′),(x2′,y2′),…,(xn′,yn′)。
-
-
建立单应性方程:
-
单应性矩阵 𝐻H 是一个 3x3 的矩阵:
𝐻=[ℎ11ℎ12ℎ13ℎ21ℎ22ℎ23ℎ31ℎ32ℎ33]H=h11h21h31h12h22h32h13h23h33 -
对于每一对点,满足以下关系:
[𝑥′𝑦′1]∼𝐻[𝑥𝑦1]x′y′1∼Hxy1其中 ∼∼ 表示“相等,允许一个比例因子”。
-
-
转换为线性方程组:
-
将上述关系展开,得到:
𝑥′=ℎ11𝑥+ℎ12𝑦+ℎ13ℎ31𝑥+ℎ32𝑦+ℎ33x′=h31x+h32y+h33h11x+h12y+h13𝑦′=ℎ21𝑥+ℎ22𝑦+ℎ23ℎ31𝑥+ℎ32𝑦+ℎ33y′=h31x+h32y+h33h21x+h22y+h23 -
将其重写为线性方程组的形式:
{𝑥′(ℎ31𝑥+ℎ32𝑦+ℎ33)=ℎ11𝑥+ℎ12𝑦+ℎ13𝑦′(ℎ31𝑥+ℎ32𝑦+ℎ33)=ℎ21𝑥+ℎ22𝑦+ℎ23{x′(h31x+h32y+h33)=h11x+h12y+h13y′(h31x+h32y+h33)=h21x+h22y+h23 -
最终可以写成矩阵形式 𝐴ℎ=0Ah=0,其中 𝐴A 是一个 2𝑛×92n×9 的矩阵,ℎh 是单应性矩阵 𝐻H 的 9 个元素组成的向量。
-
-
求解方程组:
-
使用 奇异值分解(SVD) 或其他方法求解 ℎh。
-
对 𝐻H 进行归一化,通常令 ℎ33=1h33=1。
-
-
优化单应性矩阵:
-
可以使用 RANSAC(随机采样一致性)算法来去除异常点,提高鲁棒性。
-
下面是pytho的简单实现:
import cv2
import numpy as np
# 定义图像1和图像2中的对应点
pts1 = np.array([[50, 50], [200, 50], [50, 200], [200, 200]], dtype=np.float32)
pts2 = np.array([[70, 60], [220, 70], [60, 210], [210, 220]], dtype=np.float32)
# 计算单应性矩阵
H, _ = cv2.findHomography(pts1, pts2)
print("单应性矩阵 H:")
print(H)
单应性矩阵的应用
-
图像拼接:将多张图像拼接成全景图。
-
增强现实(AR):将虚拟物体叠加到现实世界的平面上。
-
透视校正:校正图像中的透视变形(例如,将倾斜拍摄的文档图像校正为正面视角)。
下面我们来根据算法逻辑实现FindHomography并通过实现RANSAC算法来求解H矩阵:
下面是main.cpp
#include <opencv2/opencv.hpp>
#include <iostream>
#include <fstream>
#include "get_H.h"
using namespace cv;
int main() {
// 遍历图像对imgId<-->modID
for (int i = 0; i < 163; ++i) {
std::string imagepath1 = image_path + std::to_string(modid[i].src) + ".bmp"; // src
std::string imagepath2 = image_path + std::to_string(modid[i].dst) + ".bmp"; // dst
cv::Mat srcImage = cv::imread(imagepath1, cv::IMREAD_GRAYSCALE);
cv::Mat dstImage = cv::imread(imagepath2, cv::IMREAD_GRAYSCALE);
if (srcImage.empty() || dstImage.empty()) {
std::cerr << "Could not open or find the images!" << std::endl;
return -1;
}
// 使用 SIFT 检测特征点
cv::Ptr<cv::SIFT> sift = cv::SIFT::create();
// 检测关键点并计算描述符
std::vector<KeyPoint> keypoints1, keypoints2;
Mat descriptors1, descriptors2;
sift->detectAndCompute(srcImage, noArray(), keypoints1, descriptors1);
sift->detectAndCompute(dstImage, noArray(), keypoints2, descriptors2);
// 使用 BFMatcher 进行特征匹配
BFMatcher matcher(NORM_L2);
std::vector<DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);
// 对匹配结果排序
std::sort(matches.begin(), matches.end(), [](const DMatch &a, const DMatch &b) {
return a.distance < b.distance;
});
// 过滤匹配点,选取前 50 个最佳匹配
const int numGoodMatches = 50;
std::vector<DMatch> goodMatches(matches.begin(), matches.begin() + std::min(numGoodMatches, (int)matches.size()));
// 提取匹配点
std::vector<Point2f> srcPoints, dstPoints;
for (const auto& match : goodMatches) {
srcPoints.push_back(keypoints1[match.queryIdx].pt);
dstPoints.push_back(keypoints2[match.trainIdx].pt);
}
GuassHonophgy *Guaa = new GuassHonophgy();
int numpoints = srcPoints.size();
point_32F SRC_P[numpoints];
point_32F DST_P[numpoints];
for (int i = 0; i < numpoints; ++i) {
SRC_P[i].x = srcPoints[i].x;
SRC_P[i].y = srcPoints[i].y;
DST_P[i].x = dstPoints[i].x;
DST_P[i].y = dstPoints[i].y;
}
float H[9] = {0.f};
bool result = Guaa->MyFindHomography(SRC_P,DST_P,H,numpoints,mycv::RANSAC);
}
return 0;
}
结果与opencv比对:
结果基本一致。。。。