计算机视觉(三)

特征匹配和相机标定

Posted by Gavin on October 12, 2019

应是天仙狂醉

乱把浮云揉碎

前言

为应付作业,只得先调包使用,之后有时间再进行详细解析和自己实现


特征匹配

特征提取

利用SIFT算法提取特征

void ChooseFeatures(cv::Mat &img)
{
    std::vector<cv::KeyPoint> keypoints;
    cv::Ptr<cv::xfeatures2d::SiftFeatureDetector> ptrFAST = cv::xfeatures2d::SiftFeatureDetector::create(40);
    //cv::Ptr<cv::FastFeatureDetector> ptrFAST = cv::FastFeatureDetector::create(40);
    ptrFAST->detect(img,keypoints);
    cv::drawKeypoints(img, keypoints, img,cv::Scalar(0,0,255),cv::DrawMatchesFlags::DRAW_OVER_OUTIMG);
    cv::imwrite("../Pics/MyFeatures.jpg", img);
}

特征匹配

bool comparePics(cv::Mat &image1, cv::Mat &image2)
{
    cv::Mat img1(image1);
    cv::Mat img2(image2);
    cv::cvtColor(img1, img1, cv::COLOR_BGR2GRAY);
    cv::cvtColor(img2, img2, cv::COLOR_BGR2GRAY);

    std::vector<cv::KeyPoint> keypoints1, keypoints2;
    cv::Ptr<cv::xfeatures2d::SiftFeatureDetector> ptrFAST =cv::xfeatures2d::SiftFeatureDetector::create(40);
    ptrFAST->detect(img1,keypoints1);
    ptrFAST->detect(img2,keypoints2);
    cv::Mat desp1,desp2;
    ptrFAST->compute(img1,keypoints1, desp1);
    ptrFAST->compute(img2, keypoints2, desp2);

    cv::BFMatcher matcher(cv::NORM_L2);
    std::vector<cv::DMatch> matches;
    matcher.match(desp1,desp2,matches);

    sort(matches.begin(), matches.end());
    std::vector<cv::KeyPoint> goodPoints1,goodPoints2;

    std::vector<cv::DMatch> goodMatches;
    for (int i = 0; i < 10; ++i)
    {
        cv::DMatch dmatch;
        dmatch.queryIdx = dmatch.trainIdx = i;
        goodMatches.push_back(dmatch);

        goodPoints1.push_back(keypoints1[matches[i].queryIdx]);
        goodPoints2.push_back(keypoints2[matches[i].trainIdx]);
    }

    cv::Mat res;
    cv::drawMatches(image1,goodPoints1,image2,goodPoints2,goodMatches,res);
    cv::imwrite("../Pics/Mymatches.jpg", res);
    return matches.size() > keypoints1.size()*0.98;
    //return true;
}


相机标定和视角转换

相机标定

相机是利用小孔成像原理工作的,因此基于此原理,我们可以很容易的得到被摄物体和图像之间的基本投影方程。

\[h_i = f{h_0 \over d_0}\]

因此在相机几何结构已知的情况下,这个关系决定三维坐标点在成像平面上投影的位置。如果坐标系位于焦点的位置上,那么(X,Y,Z)的投影(x,y)=(fX/Z,fY/Z),引入齐次坐标即是:

\[s\left[ \begin{matrix} x \\\\ y \\\\ 1 \end{matrix} \right] = \left[ \begin{matrix} f & 0 & 0 & 0 \\\\ 0 & f & 0 & 0\\\\ 0 & 0 & 1 & 0 \end{matrix} \right] \left[ \begin{matrix} X \\\\ Y \\\\ Z \\\\ 1 \end{matrix} \right]\]

如果坐标系没有与焦点对其,则需要引入旋转量r和偏移量t,引入它们之后,就可以把三维点表示为一个以相机为中心的坐标系:

\[s\left[ \begin{matrix} x \\\\ y \\\\ 1 \end{matrix} \right] = \left[ \begin{matrix} f & 0 & 0 \\\\ 0 & f & 0 \\\\ 0 & 0 & 1 \end{matrix} \right] \left[ \begin{matrix} r_1 & r_2 & r_3 & t_1 \\\\ r_4 & r_5 & r_6 & t_2 \\\\ r_7 & r_8 & r_9 & t_3 \end{matrix} \right] \left[ \begin{matrix} X \\\\ Y \\\\ Z \\\\ 1 \end{matrix} \right]\]

第一个矩阵包含了相机的焦距参数,第二个矩阵则是外参


这是数字图像的成像过程,我们知道物体在相机成像平面上的坐标是(fX/Z,fY/Z),要将这个坐标转换成以像素为单位的图像坐标x,y分别除以像素的宽度(\(p_x\))、高度(\(p_y\)),记\(f_x = f / p_x,f_y = f / p_y\),则:

\[x = {f_xX \over Z} + u_0\] \[y = {f_yY \over Z} + v_0\]

其中u,v是主点,加上它是要把图像左上角的点作为原点,因此成像过程用矩阵表示则为:

\[s\left[ \begin{matrix} x \\\\ y \\\\ 1 \end{matrix} \right] = \left[ \begin{matrix} f_x & 0 & u_0 \\\\ 0 & f_y & v_0 \\\\ 0 & 0 & 1 \end{matrix} \right] \left[ \begin{matrix} r_1 & r_2 & r_3 & t_1 \\\\ r_4 & r_5 & r_6 & t_2 \\\\ r_7 & r_8 & r_9 & t_3 \end{matrix} \right] \left[ \begin{matrix} X \\\\ Y \\\\ Z \\\\ 1 \end{matrix} \right]\]

一切似乎都很合理,但是镜头是有畸变的,越靠近镜头边缘的地方越容易失真,是因为镜头表面并不平滑,如下图所示,所以会造成成像失真

为此,引入畸变模型,其中径向畸变为:

\[x_{corrected} = x(1 + k_1x^2 + k_2x^4 + k_3x^6)\] \[y_{corrected} = y(1 + k_1x^2 + k_2x^4 + k_3x^6)\]

切向畸变:

\[x_{corrected} = x + 2p_1y + p_2(r^2 + 2x^2)\] \[y_{corrected} = y + p_1(r^2 + 2y^2) + p_2x\]

此时相机的全部内参就出现了,畸变参数(\(k_1,k_2,p_1,p_2,k_3\)),固有参数(\(f_x,f_y,u_0,v_0\)),外参则是(\(\theta,\varphi,\psi,t_x,t_y,t_z\)),确定这些参数的过程叫相机标定,本次作业采用张友正标定法确定参数,此法采用一张棋盘格进行标定。棋盘格的角点清晰,且棋盘本身是一个平面,不同角度拍摄的照片是另一个平面,因此每一张图片都是棋盘平面到相机成像平面的一个投影,根据成像规则,我们可以计算投影的单应矩阵,从而求得参数,推导过程比较繁琐,就不推了(不会)。

棋盘:

标定照片:

构造一个标定类:

class CameraCalibrator
{
    std::vector<std::vector<cv::Point3f>> objectPoints;
    std::vector<std::vector<cv::Point2f>> imagePoints;

    cv::Mat cameraMatrix;
    cv::Mat distCoeffs;

    std::vector<cv::Mat> rvecs,tvecs;

    int picCount = 0;

    void addPoints(const std::vector<cv::Point3f> &objectCorners,const std::vector<cv::Point2f> &imageCorners) {
        objectPoints.push_back(objectCorners);
        imagePoints.push_back(imageCorners);
    }

public:
    int addChessboardPoints(const std::vector<std::string> &filelist, cv::Size &boardSize) {
        std::vector<cv::Point3f> objectCorners;
        std::vector<cv::Point2f> imageCorners;

        for (int i = 0; i < boardSize.height; ++i) {
            for (int j = 0; j < boardSize.width; ++j) {
                objectCorners.emplace_back(cv::Point3f(i,j,0.0f));
            }
        }

        cv::Mat img,image;
        int success = 0;

        for (std::string s : filelist) {
            image = cv::imread(s,0);
            //cv::cvtColor(img,image,cv::COLOR_BGR2GRAY);

            bool found = cv::findChessboardCorners(image,boardSize,imageCorners);

            if (found) {
                cv::cornerSubPix(image, imageCorners, cv::Size(5, 5), cv::Size(-1, -1),
                                 cv::TermCriteria(cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS, 30, 0.1));
                if (imageCorners.size() == boardSize.area()) {
                    addPoints(objectCorners, imageCorners);
                    success++;
                    picCount++;
                }
            }
        }
        return success;
    };

    double calibrate(cv::Size &imageSize) {
        return cv::calibrateCamera(objectPoints,imagePoints,imageSize,cameraMatrix,distCoeffs,rvecs,tvecs);
    }

    void remap(const cv::Mat &image) {
        cv::Mat undistordered;
        //cv::initUndistortRectifyMap(cameraMatrix, distCoeffs, cv::Mat(), cv::Mat(), image.size(), CV_32FC1, map1, map2);
        //cv::remap(image,undistordered,map1,map2,cv::INTER_LINEAR);
        cv::undistort(image,undistordered,cameraMatrix,distCoeffs);
        cv::imwrite("../Pics/Remap.jpg", undistordered);
    }

    void display() {
        std::cout << "Inner parameters: " << std::endl;
        for (int i = 0; i < cameraMatrix.rows; i++) {
            for (int j = 0; j < cameraMatrix.cols; j++) {
                std::cout << cameraMatrix.at<double>(i,j) << " ";
            }
            std::cout << std::endl;
        }

        std::cout << "DistCoeffs: " << std::endl;
        for (int i = 0; i < distCoeffs.rows; i++) {
            for (int j = 0; j < distCoeffs.cols; j++) {
                std::cout << distCoeffs.at<double>(i,j) << " ";
            }
            std::cout << std::endl;
        }

        /*std::cout << "Rvecs: " << std::endl;
        for (int k = 0; k < picCount; k++) {
            std::cout << k+1 << std::endl;
            for (int i = 0; i < rvecs[k].rows; i++) {
                uchar *data = rvecs[k].ptr(i);
                for (int j = 0; j < rvecs[k].cols; j++) {
                    std::cout << static_cast<double>(*data++) << " ";
                }
                std::cout << std::endl;
            }
        }*/

        /*std::cout << "Tvecs: " << std::endl;
        for (int k = 0; k < picCount; k++) {
            std::cout << k+1 << std::endl;
            for (int i = 0; i < tvecs[k].rows; i++) {
                uchar *data = tvecs[k].ptr(i);
                for (int j = 0; j < tvecs[k].cols; j++) {
                    std::cout << static_cast<double>(*data++) << " ";
                }
                std::cout << std::endl;
            }
        }*/
    }
};

标定得到iPhone参数:

固有矩阵:
3893.68	0	1441.77
0	3820.33	1602.61
0	0	1
畸变矩阵:
0.0981073	-0.473381	0.00910247	-0.0320281	0.588129

视角转换

核心问题依然是寻找平面之间的投影矩阵,首先在原图上标出四个特征角点,然后给出目标平面上这四个点的位置,计算出单应矩阵,进行投影

	double cM[3][3] = { {3893.68,0,1441.77},{ 0,3820.33,1602.61 },{ 0,0,1 } };
    cv::Mat cameraMatrix = cv::Mat(3,3, CV_64FC1, cM);
    double dC[] = {0.0981073, -0.473381, 0.00910247, -0.0320281, 0.588129};
    cv::Mat distCoeffs = cv::Mat(1,5,CV_64FC1,dC);


    cv::Mat img = cv::imread("../Pics/IMG_2007.JPG", 0);
    cv::Mat imgUndist;
    cv::undistort(img,imgUndist,cameraMatrix,distCoeffs,cameraMatrix);
    cv::imwrite("../Pics/Remap1.jpg",imgUndist);
    //cv::cvtColor(img,img,cv::COLOR_BGR2GRAY);

    std::vector<cv::Point2f> imagePoints;
    cv::findChessboardCorners(imgUndist,cv::Size(11,8),imagePoints);
    cv::cornerSubPix(imgUndist, imagePoints,
            cv::Size(5, 5),cv::Size(-1, -1),
                     cv::TermCriteria(cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS,
                             30, 0.1));
    cv::Point2f objPts[4], imgPts[4];

    objPts[0].x = 0; objPts[0].y = 0;
    objPts[1].x = 10; objPts[1].y = 0;
    objPts[2].x = 0; objPts[2].y = 7;
    objPts[3].x = 10; objPts[3].y = 7;

    imgPts[0] = imagePoints[0];
    //std::cout << imgPts[0] << std::endl;
    imgPts[1] = imagePoints[10];
    //std::cout << imgPts[1] << std::endl;
    imgPts[2] = imagePoints[77];
    //std::cout << imgPts[2] << std::endl;
    imgPts[3] = imagePoints[87];
    //std::cout << imgPts[3] << std::endl;

    cv::Mat homograph = cv::getPerspectiveTransform(objPts,imgPts);


    //homograph.at<double>(1,0) = 0;
    //homograph.at<double>(0,1) = 1;
    //homograph.at<double>(0,2) = 0;

    double a00 = -145;
    double a01 = 56;
    double a10 = 1;
    double a11 = 38;
    double a22 = -30;

    cv::Mat res;
    for (;;) {
        /*homograph.at<double>(0,0) = a00;
        homograph.at<double>(0,1) = a01;
        homograph.at<double>(1,0) = a10;
        homograph.at<double>(1,1) = a11;*/
        homograph.at<double>(2,2) = a22;
        cv::warpPerspective(imgUndist,res,homograph,img.size(),
                            cv::WARP_INVERSE_MAP|cv::INTER_LINEAR,
                            cv::BORDER_CONSTANT,
                            cv::Scalar::all(0));
        //cv::imwrite("../Pics/BirdEye.jpg",res);
        cv::imshow("bird",res);
        int key = cv::waitKey() & 255;
        if (key == 'q') a00+=5;
        if (key == 'w') a00-=5;

        if (key == 'e') a01+=5;
        if (key == 'r') a01-=5;

        if (key == 't') a10+=5;
        if (key == 'y') a10-=5;

        if (key == 'u') a11+=5;
        if (key == 'i') a11-=5;

        if (key == 'o') a22+=5;
        if (key == 'p') a22-=5;


        if (key == 'z') break;
    }
    std::cout << homograph << std::endl;
    cv::imwrite("../Pics/birdsview.jpg",res);

原图:

鸟瞰图:

拉近一点: