应是天仙狂醉
乱把浮云揉碎
前言
为应付作业,只得先调包使用,之后有时间再进行详细解析和自己实现
特征匹配
特征提取
利用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;
}
相机标定和视角转换
相机标定
相机是利用小孔成像原理工作的,因此基于此原理,我们可以很容易的得到被摄物体和图像之间的基本投影方程。
因此在相机几何结构已知的情况下,这个关系决定三维坐标点在成像平面上投影的位置。如果坐标系位于焦点的位置上,那么(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\),则:
其中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 + 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);
原图:
鸟瞰图:
拉近一点: