本文参考:计算机视觉life
概念
单应性(homography)是指两个平面之间的一种保直线性的对应关系。如果一个平面上的点集经过某种变换后,在另一个平面上形成的新点集仍然保持原来的线性特性(如共线的点仍然共线),那么这种变换就称为单应性变换。在数学上,单应性变换可以用一个3x3的矩阵来表示,这个矩阵就是所谓的单应矩阵。
回顾相机成像
世界坐标系到相机坐标系的变换为:
我们简化表达式为, 其中M是3 x 3的矩阵:
( u v 1 ) = M ( x y z 1 ) \begin{pmatrix} u\\ v\\ 1 \end{pmatrix} = M\begin{pmatrix} x\\ y\\ z\\ 1 \end{pmatrix} uv1 =M xyz1
对于两个不同的相机,像素坐标和空间点坐标可以写成如下的表示
我们把上面两个式子合并一下就得到了下面这个式子,其中的H就是单应矩阵啦!H矩阵的两边是两张图像对应的匹配点对。也就是说单应矩阵H把三维空间中同一平面的点在两个相机的成像图片坐标进行了映射。
有时1会被平面方程替代,是因为
1.约束条件:在推导单应矩阵的过程中,通常会涉及到多个未知数。为了求解单应矩阵 H,我们需要建立足够的约束条件。平面方程在这里被用来增加额外的约束条件,以便降低问题的自由度。
2. 降维: 由于单应矩阵 H 是一个3x3的矩阵,理论上包含9个参数。但由于它是齐次坐标下的矩阵,所以可以任意缩放,这意味着有一个自由度是冗余的。因此,单应矩阵实际上有8个自由度。为了求解这8个自由度,我们需要至少4对匹配点来建立8个线性方程。
3. 平面方程: 在某些推导过程中,可能会引入平面方程作为约束条件。例如,假设我们有一个平面nTx+d=0,其中 n是平面的法向量,x 是平面上的点,d 是常数。在某些情况下,可以将这个平面方程中的 d 替换为1,从而简化计算或引入额外的约束。
平面方程:在讨论单应矩阵时提到的平面方程,通常指的是图像中的某个平面在三维空间中的方程。具体来说,当我们在处理单应矩阵时,我们关注的是两个图像之间对应点的关系,这些点通常位于同一个平面上。这个平面可以是场景中的任何一个平面,例如墙面、地面或者任何具有平面结构的物体表面。平面方程nTx+d=0中的x = (X,Y, Z)指的是三维世界坐标系中的点,世界坐标系是一个固定的参考坐标系,用于描述三维空间中的物体位置和方向。在这个坐标系中,每个点的位置可以用三个坐标(X,Y, Z)来表示。如下图:
下面给出代码:
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
struct userdata {
Mat im;
vector<Point2f> points;
};
void mouseHandler(int event, int x, int y, int flags, void* data_ptr) {
if (event == EVENT_LBUTTONDOWN) {
userdata* data = (userdata*)data_ptr;
circle(data->im, Point(x, y), 3, Scalar(0, 255, 255), 5, LINE_AA);
imshow("Image", data->im);
if (data->points.size() < 4) {
data->points.push_back(Point2f(x, y));
}
}
}
int main(int argc, char** argv) {
// Read in the logo and the image with the billboard
Mat im_logo = imread("cy.png");
Mat im_ad = imread("tm.jpg");
if (im_logo.empty() || im_ad.empty()) {
cout << "Could not open or find the images!" << endl;
return -1;
}
// Define the four corners of the logo in the logo image (source points)
Size logo_size = im_logo.size();
vector<Point2f> pts_logo = {
Point2f(0, 0),
Point2f(logo_size.width - 1, 0),
Point2f(logo_size.width - 1, logo_size.height - 1),
Point2f(0, logo_size.height - 1)
};
// Destination image
Mat im_temp = im_ad.clone();
userdata data;
data.im = im_temp;
// Show the image and set mouse callback
imshow("Image", im_temp);
cout << "Click on four corners of the billboard and then press ENTER" << endl;
setMouseCallback("Image", mouseHandler, &data);
waitKey(0);
// Check if we have exactly four points
if (data.points.size() != 4) {
cout << "You need to click exactly four points!" << endl;
return -1;
}
// Define the four corners of the billboard in the destination image (destination points)
vector<Point2f> pts_ad = data.points;
// Compute the perspective transform matrix
Mat H = findHomography(pts_logo, pts_ad);
// Warp the logo image to fit the billboard area in the destination image
Mat im_warped;
warpPerspective(im_logo, im_warped, H, im_ad.size());
// Create a mask for the warped logo
Mat mask = Mat::zeros(im_ad.size(), CV_8UC1);
vector<Point> mask_points(pts_ad.begin(), pts_ad.end());
fillConvexPoly(mask, mask_points, Scalar(255));
// Blend the warped logo with the destination image
Mat im_ad_masked;
im_ad.copyTo(im_ad_masked, ~mask); // Copy the background
im_warped.copyTo(im_ad_masked, mask); // Overlay the warped logo
// Display the final result
imshow("Result", im_ad_masked);
waitKey(0);
return 0;
}
最后出图是一位绝顶大帅哥(不是秃头):