本文参考:计算机视觉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;
}
 
最后出图是一位绝顶大帅哥(不是秃头):
 




















