目录
一、关于相机标定和我的学习体会
1.相机标定
我在做这个东西的时候犯了十分愚蠢的错误,我在Camera Calibration Pattern Generator – calib.io这个网站上生成了一个棋盘图,在pdf格式下拍了照然后直接复制粘贴了[OpenCV实战]38 基于OpenCV的相机标定_落痕的寒假的博客-优快云博客_opencv 相机标定上面的代码改了路径,运行之后疯狂报错,然后我想一定是程序的问题,我就从上向下放输出结果排查一圈下来什么问题都没有,后来我从网上找了一个用于标注的图片试了一下,结果直接就能跑通了,只能说明搞这个还是需要一块精度高的标定板的,为了学得这个道理我浪费了整整四个小时有余。
当然可能那个网站上的棋盘是可以用只是我的使用方法和拍摄环境不对,但是我在调整了灯光和背景之后依然没有标定成功,我很好奇这个东西到底该怎么用但是无所谓了,我直接用网上的图来标定吧。
效果图:
代码(来源于 [OpenCV实战]38 基于OpenCV的相机标定_落痕的寒假的博客-优快云博客_opencv 相机标定)
#include <opencv2/opencv.hpp>
#include <stdio.h>
#include <iostream>
using namespace std;
using namespace cv;
// Defining the dimensions of checkerboard
// 定义棋盘格的尺寸
int CHECKERBOARD[2]{ 6,9 };
int main()
{
// Creating vector to store vectors of 3D points for each checkerboard image
// 创建矢量以存储每个棋盘图像的三维点矢量
std::vector<std::vector<cv::Point3f> > objpoints;
// Creating vector to store vectors of 2D points for each checkerboard image
// 创建矢量以存储每个棋盘图像的二维点矢量
std::vector<std::vector<cv::Point2f> > imgpoints;
// Defining the world coordinates for 3D points
// 为三维点定义世界坐标系
std::vector<cv::Point3f> objp;
for (int i{ 0 }; i < CHECKERBOARD[1]; i++)
{
for (int j{ 0 }; j < CHECKERBOARD[0]; j++)
{
objp.push_back(cv::Point3f(j, i, 0));
}
}
// Extracting path of individual image stored in a given directory
// 提取存储在给定目录中的单个图像的路径
std::vector<cv::String> images;
// Path of the folder containing checkerboard images
// 包含棋盘图像的文件夹的路径
std::string path = "C:\\Users\\DELL\\Desktop\\imagelist\\*.jpg";
// 使用glob函数读取所有图像的路径
cv::glob(path, images);
cout << images.size() << endl;
cv::Mat frame, gray;
// vector to store the pixel coordinates of detected checker board corners
// 存储检测到的棋盘转角像素坐标的矢量
std::vector<cv::Point2f> corner_pts;
bool success;
// Looping over all the images in the directory
// 循环读取图像
for (int i{ 0 }; i < images.size(); i++)
{
frame = cv::imread(images[i]);
cout << "the current image is " << i << "th" << endl;
cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
// Finding checker board corners
// 寻找角点
// If desired number of corners are found in the image then success = true
// 如果在图像中找到所需数量的角,则success = true
// opencv4以下版本,flag参数为CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE
success = cv::findChessboardCorners(gray, cv::Size(CHECKERBOARD[0], CHECKERBOARD[1]), corner_pts, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FAST_CHECK | CALIB_CB_NORMALIZE_IMAGE);
if (!success)
{
cout << "失败了" << endl;
}
/*
* If desired number of corner are detected,
* we refine the pixel coordinates and display
* them on the images of checker board
*/
// 如果检测到所需数量的角点,我们将细化像素坐标并将其显示在棋盘图像上
if (success)
{
// 如果是OpenCV4以下版本,第一个参数为CV_TERMCRIT_EPS | CV_TERMCRIT_ITER
cv::TermCriteria criteria(TermCriteria::EPS | TermCriteria::Type::MAX_ITER, 30, 0.001);
// refining pixel coordinates for given 2d points.
// 为给定的二维点细化像素坐标
cv::cornerSubPix(gray, corner_pts, cv::Size(11, 11), cv::Size(-1, -1), criteria);
// Displaying the detected corner points on the checker board
// 在棋盘上显示检测到的角点
cv::drawChessboardCorners(frame, cv::Size(CHECKERBOARD[0], CHECKERBOARD[1]), corner_pts, success);
objpoints.push_back(objp);
imgpoints.push_back(corner_pts);
}
cv::imshow("Image", frame);
cv::waitKey(0);
}
cv::destroyAllWindows();
cv::Mat cameraMatrix, distCoeffs, R, T;
/*
* Performing camera calibration by
* passing the value of known 3D points (objpoints)
* and corresponding pixel coordinates of the
* detected corners (imgpoints)
*/
// 通过传递已知3D点(objpoints)的值和检测到的角点(imgpoints)的相应像素坐标来执行相机校准
cv::calibrateCamera(objpoints, imgpoints, cv::Size(gray.rows, gray.cols), cameraMatrix, distCoeffs, R, T);
// 内参矩阵
std::cout << "cameraMatrix : " << cameraMatrix << std::endl;
// 透镜畸变系数
std::cout << "distCoeffs : " << distCoeffs << std::endl;
// rvecs
std::cout << "Rotation vector : " << R << std::endl;
// tvecs
std::cout << "Translation vector : " << T << std::endl;
return 0;
}
2.关于相机中几个函数的理解
我个人在第一遍拜读这段代码的时候是完全看不懂的,可能是因为我才刚刚接触计算机视觉,还是一个小白,我在仔细看了几遍又加上一些修修改改之后才大概理解了整个流程,这段代码的核心只有两个函数findChessBoardCorners和calibrateCamera,第一个函数用于找到图像中棋盘的角点,第二个函数是用来求解内参和外参的。第一个函数中有一个Size的参数,这个并不是棋盘的尺寸,而是棋盘中我去掉外圈后棋盘中角点的个数,比如说我有一个(4,4)的棋盘,我要传的参数实际上是(3,3),大概的原因可能是最外圈的点直接和背景接触,所以没有办法进行精确的识别。
二、关于cv::undistort函数除畸变
1.几个主要参数
void undistort( InputArray src, //输入原图
OutputArray dst,//输出矫正后的图像
InputArray cameraMatrix,//内参矩阵
InputArray distCoeffs,//畸变系数
InputArray newCameraMatrix=noArray() );
这个函数需要一个原图,一个存放的图,两个Mat分别放相机的内参和畸变参数,具体实现我是把上个程序得到的输出结果复制粘贴对矩阵进行赋值。
2.具体实现
我用的是别人的图,我测试了一下之后发现原图和处理后的图差别并不大,甚至可以说是一模一样。
效果图:
代码:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
cv::Mat src, dst;
cv::VideoCapture capture(0);
//内参矩阵
Mat cameraMatrix = Mat::eye(3, 3, CV_64F);
cameraMatrix.at<double>(0, 0) = 461.3117283390973;//fx
cameraMatrix.at<double>(0, 1) = 0;
cameraMatrix.at<double>(0, 2) = 254.2004547186644;//cx
cameraMatrix.at<double>(1, 1) = 446.1662593638758;//fy
cameraMatrix.at<double>(1, 2) = 217.6920652976458;//cy
//畸变参数
Mat distCoeffs = Mat::zeros(5, 1, CV_64F);
distCoeffs.at<double>(0, 0) = 0.1443317732031333;//k1
distCoeffs.at<double>(1, 0) = -0.03174165306686211;//k2
distCoeffs.at<double>(2, 0) = -0.004786554097723182;//p1
distCoeffs.at<double>(3, 0) = -0.01697806545282722;//p2
distCoeffs.at<double>(4, 0) = -0.08464757406284915;
while (true)
{
capture >> src;
namedWindow("原图", WINDOW_AUTOSIZE);
namedWindow("处理后",WINDOW_AUTOSIZE);
undistort(src, dst, cameraMatrix, distCoeffs);
imshow("原图", src);
imshow("处理后", dst);
waitKey(1);
}
}
但这并不代表这个函数没有效果,我尝试对参数进行修改,然后得到了一些具有神秘特性的光学图像,放出来给各位赏玩:
这是一个有凸面镜效果的:
这是一个然人感觉到诡异的:
等我有了一个精度高的标定板我再来试一试我的手机如何,今天大概就到这里。