目录
1.4大坐标系
首先,相机的成像过程涉及到四个坐标系:世界坐标系、相机坐标系、图像坐标系、像素坐标系。接下来依次介绍:
- 世界坐标系(单位:m)
客观三维世界的绝对坐标系。因为相机安放在三维空间中,我们需要世界坐标系这个基准坐标系来描述相机的位置,并且用它来描述安放在此三维环境中的其它任何物体的位置,表示为:
- 相机坐标系(单位:m)
以相机的光心为坐标原点,X 轴和Y 轴分别平行于图像坐标系的 X 轴和Y 轴,相机的光轴为Z 轴,表示为:
- 图像坐标系(单位:mm)
以CCD 图像平面的中心为坐标原点,X轴和Y 轴分别平行于图像平面的两条垂直边。图像坐标系是用物理单位(例如毫米)表示像素在图像中的位置。表示为:
- 像素坐标系(单位:像素(pixel))
以图像的左上角点为坐标原点,水平方向向右为x轴(u轴),数值方向向下为y轴(v轴)。表示为:
2.相机成像原理
如何成像:
- 在物体和胶片之间,增加一块带有小孔的屏障,这就构成了小孔成像,用数学模型表示就叫做:针孔相机模型,而屏障上的小孔称之为光圈,并且胶片上获得倒立的图像。
- object到barrier的距离为Z,也就是深度,film到barrier的距离为f,也就是焦距。
- 针孔相机模型: f为焦距,X为三维坐标中点,x为相机坐标系中的点
3.光圈(控制进光量)
-
光圈越小,进的光线减少,画面越暗,背景虚化越小
-
光圈越大,进的光线增加,画面越亮,背景虚化越大
4.现实生活中的相机系统
这也说明了,尽管从物理原理来说,小孔成像的像应该是倒像,但是由于相机的自身设计,像一般变为正的了。
5.坐标系的关系
-
图像坐标系和相机坐标的关系
首先,我们可以通过两个正的相似三角形可以得到图像坐标系和相机坐标系的关系:
用齐次坐标系和矩阵表示上述关系:
-
像素坐标系和图像坐标系的关系
接着,对于像素坐标系和图像坐标系的关系,它们之间相差了一个缩放和一个原点的平移。我们设像素坐标在 u 轴上缩放了 1/dx倍,在 v 上缩放了 1/dy倍,那么我们可以表示为:
用矩阵的形式表示:
- 两种关系合并:
-
相机坐标系到世界坐标系的关系
相机坐标系到世界坐标系存在一个位姿的变换,[R|T],写成矩阵的形式如下:
组合在一起:
其中, 相机的内参K 为:
R和T分别为世界坐标系和相机坐标系之间的旋转矩阵和平移矩阵,两者组成了相机的外参矩阵,
相机的外参矩阵和内参矩阵构成了相机的投影矩阵P= K[R |t]
6.相机畸变
1.径向畸变(Radial Distortion)
简单来说,由透镜形状(相机镜头径向曲率的不规则变化)引起的畸变称为径向畸变,是导致相机成像变形的主要因素。径向畸变主要分为桶形畸变和枕型畸变。在针孔模型中,一条直线投影到像素平面上还是一条直线。 但在实际中,相机的透镜往往使得真实环境中的一条直线在图片中变成了曲线。越靠近图像的边缘现象越明显。 由于透镜往往是中心对称的,这使得不规则畸变通常径向对称。(成像中心处的径向畸变最小,距离中心越远,产生的变形越大,畸变也越明显 )
- 正向畸变(枕型畸变):从图像中心开始,径向曲率逐渐增加。
- 负向畸变(桶形畸变):边缘的径向曲率小于中心的径向曲率。(鱼眼相机)
实际摄像机的透镜总是在成像仪的边缘产生显著的畸变,这种现象来源于“筒形”或“鱼眼”的影响。如下图,光线在原理透镜中心的地方比靠近中心的地方更加弯曲。对于常用的普通透镜来说,这种现象更加严重。筒形畸变在便宜的网络摄像机中非常厉害,但在高端摄像机中不明显,因为这些透镜系统做了很多消除径向畸变的工作。
径向畸变模型: r 为像平面坐标系中点(x, y)与图像中心(x0, y0)的像素距离。
- 当k1为0.01,0.02,0.03,0.04,-0.01,-0.02,-0.03,-0.04时,1+k1*r^2的函数曲线
- 当k1一定时,随着r的增加,畸变影响越大
- 随着k1的增加或者减小,畸变影响变小
- 1+r^2,1+r^4,1+r^6,1+r^8的函数曲线 (次方越高,函数曲线越接近)
- k2为0.01,0.02,0.03,-0.01,-0.02,-0.03时,1+0.01*x^2+k2*x^4的曲线
- 高次的影响
2.切向畸变(Tangential Distortion)
切向畸变是由于相机镜头在制造安装过程中并非完全平行于成像平面造成的。不同于径向畸变在图像中心径向方向上发生偏移变形,切向畸变主要表现为图像点相对理想成像点产生切向偏移。
切向畸变模型可以描述为:p 1和p 2 —镜头的切向畸变系数。
最终需要得到的5个畸变参数:
畸变参数的一般顺序是k1,k2,p1,p2,k3。之所以把k3放在最后其实也很容易理解,因为前面说了一般k1,k2用来处理径向畸变足矣,k3相对而言用的比较少。 在获得了畸变参数以后,也就找到了真实观测的带畸变的像素与无畸变的像素间的关系,重采样即可实现影像校正。
- 非畸变图像中的像素点(u,v)--->非畸变的图像坐标系(x,y)
- 畸变后的图像坐标系(x',y'),由畸变模型(x,y)-->(x',y')
- 根据相机模型,得到畸变图像上的像素点坐标(u',v')
7.图像插值
- 最近邻插值(INTER_NEAREST)
将目标图像中的点对应到原图像中后,找到最相邻的整数坐标点的像素值,作为该点的像素值输出。由最邻近插值法得到的图像放大后有很严重的马赛克,会出现明显的块状效应,缩小后的图像有很严重的失真。
- 双线性插值(先两次x轴方向上的线性插值,再y方向上一次线性插值)
8.标定
理论:
相机标定的基本原理:确定场景中一系列点的三维坐标并拍摄这个场景,然后观测这些点在图像上投影的位置。有了足够多的三维点和图像上对应的二维点,就可以根据投影方程推断出准确的相机参数。
- 实质:已知x、X,求K、R、t
DLT求解相机标定原理:
- 一组2D-3D对应点提供关于P的两个线性方程
- n组2D-3D对应点提供关于P的2n个线性方程
- 当n≥6时,P可以通过DLT线性解出最小二乘解(P的自由度为11),然后从P中分解相机内外参数K、R、t,这样我们就得到了其内参和外参。
但是,对于DLT标定方法来说,存在一定的问题,因为我们都知道相机得到的图像不是理想的,还存在着畸变,然而该方法没有考虑相机的畸变。
张正友标定法:
张正友标定法主要利用平面标定板进行实现,虽然精度上不及使用三维标定物,但是这种方法更加的实用(打印一张黑白棋盘格、一块足够平的木板)
实现过程:
OpenCV推荐使用国际象棋棋盘的图案生成用于校准的三维场景点的集合。这个图案在每个方块的角点位置创建场景点,并且由于图案是平面的,因此我们可以假设棋盘位于Z=0且X 和Y 的坐标轴与网格对齐的位置。这样,校准时就只需从不同的视角拍摄棋盘图案。下面是一个6 × 9的图案:
- 寻找棋盘格中的角点
bool found = cv::findChessboardCorners(image,boardSize, imageCorners);
- 对寻找到的角点进行亚像素提纯
cornerSubPix(gray,corners,cv::Size(5, 5), cv::Size(-1, -1),TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 30, 0.1));
- 可以打印并显示检测到的角点
drawChessboardCorners(img,board_size,corners,found);
- 保存2D角点和3D点
image_points.push_back(corners); object_points.push_back(objectCorners);
- 标定
calibrateCamera(object_points,image_points,img.size(),instrisincMatrix,distortionCoeff,rvecs,tvecs);
矫正
得到标定参数以后,我们需要使用畸变参数对图像进行矫正,OpenCV中对畸变图像进行畸变校正主要用的函数有:
- undistort()函数
- initUndistortRectifyMap()结合remap()函数
其实undistort()就是initUndistortRectifyMap()和remap()的简单组合,效果是一样的。
- 计算重投影误差
projectPoints(Mat(objectPoints[i]), rvecs[i], tvecs[i], cameraMatrix,distCoeffs, imagePoints2);
注意:根据经验,10~20个棋盘图像就足够了,但是必须在不同的深度,从不同的视角拍摄。这个函数的两个重要输出对象是相机矩阵和畸变参数。
实验代码:
完整代码链接:里面包含了实现的代码和采集的29张棋盘格照片
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <string>
#include <vector>
using namespace std;
using namespace cv;
void computeReprojectionErrors(const vector< vector< Point3f > >& objectPoints,
const vector< vector< Point2f > >& imagePoints,
const vector< Mat >& rvecs, const vector< Mat >& tvecs,
const Mat& cameraMatrix , const Mat& distCoeffs) {
vector< Point2f > imagePoints2;
for (int i = 0; i < (int)objectPoints.size(); ++i) {
double err;
projectPoints(Mat(objectPoints[i]), rvecs[i], tvecs[i], cameraMatrix,
distCoeffs, imagePoints2);
err =(double) norm(Mat(imagePoints[i]), Mat(imagePoints2), CV_L2)/objectPoints[i].size();
cout<<"第"<<i+1<<"图片的误差为: "<<err<<endl;
}
}
vector< vector< Point3f > > object_points;
vector< vector< Point2f > > image_points;
int main() {
// 水平和垂直方向内部角点的数量
int board_width=6;
int board_height=9;
//图片的数量
int num_imgs=29;
string base_path="../calib_imgs/1";
Size board_size=Size(board_width,board_height);
vector<Point2f> corners;
std::vector<cv::Point3f> objectCorners;
// 处理所有视角
Mat img,gray;
for (int k = 1; k <= num_imgs; k++)
{
char filename[100];
sprintf(filename,"%s/left%d.jpg",base_path.c_str(),k);
img=imread(filename,CV_LOAD_IMAGE_COLOR);
cvtColor(img,gray,CV_BGR2GRAY);
bool found=false;
found=findChessboardCorners(img,board_size,corners,CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FILTER_QUADS);
if(found)
{
cornerSubPix(gray,corners,cv::Size(5, 5), cv::Size(-1, -1),TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 30, 0.1));
drawChessboardCorners(img,board_size,corners,found);
}
// 场景中的三维点:
// 在棋盘坐标系中,初始化棋盘中的角点
// 角点的三维坐标(X,Y,Z)= (j,i,0)
// 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y
for (int i=0; i<board_size.height; i++) {
for (int j=0; j<board_size.width; j++) {
objectCorners.push_back(cv::Point3f(j, i, 0.0f));
// cout<<"("<<j<<","<<i<<")"<<endl;
}
}
if (found) {
cout << k << ". Found corners!" << endl;
image_points.push_back(corners);
object_points.push_back(objectCorners);
objectCorners.clear();
}
// imshow("1",img);
// waitKey(0);
}
cv::Mat instrisincMatrix=cv::Mat::eye(3,3,CV_64F);
cv::Mat distortionCoeff=cv::Mat::zeros(4,1,CV_64F);
vector<Mat> rvecs, tvecs;
int flag = 0;
flag |= CV_CALIB_FIX_K4;
flag |= CV_CALIB_FIX_K5;
calibrateCamera(object_points,image_points,img.size(),instrisincMatrix,distortionCoeff,rvecs,tvecs);
cout<<"instrisincMatrix: "<<endl<<instrisincMatrix<<endl;
cout<<"distortionCoeff: "<<endl<<distortionCoeff<<endl;
computeReprojectionErrors(object_points, image_points, rvecs, tvecs, instrisincMatrix, distortionCoeff);
//通过畸变校正效果查看摄像机标定效果
cv::Mat R = cv::Mat::eye(3, 3, CV_32FC1);
cv::Mat mapx, mapy, undistortImg;
cv::initUndistortRectifyMap(instrisincMatrix, distortionCoeff, R, instrisincMatrix, img.size(), CV_32FC1, mapx, mapy);
cv::remap(img, undistortImg, mapx, mapy, CV_INTER_LINEAR);
cv::imshow("undistortImg", undistortImg);
cv::waitKey(0);
return 0;
}
函数讲解:
-
FindChessboardCorners()
bool findChessboardCorners( InputArray image,
Size patternSize,
OutputArray corners,
int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE );
- 功能:用来寻找棋盘图的内角点位置。
- 参数:
- Image:输入的棋盘图,必须是8位的灰度或者彩色图像。
- patternSize:棋盘图中每行和每列角点的个数(内点)。
- imageCorners:存储检测到的内部角点的像素坐标
- Flags:各种操作标志,可以是0或者下面值的组合:
CV_CALIB_CB_ADAPTIVE_THRESH -使用自适应阈值(通过平均图像亮度计算得到)将图像转换为黑白图,而不是一个固定的阈值。
CV_CALIB_CB_NORMALIZE_IMAGE -在利用固定阈值或者自适应的阈值进行二值化之前,先使用cvNormalizeHist来均衡化图像亮度。
CV_CALIB_CB_FILTER_QUADS -使用其他的准则(如轮廓面积,周长,方形形状)来去除在轮廓检测阶段检测到的错误方块。
-
drawChessboardCorners()
cv::drawChessboardCorners(image,boardSize, imageCorners,found); // 找到的角点
- 功能:画出棋盘图像上的角点,用线条依次连接起来
- 参数:
- Image:输入的棋盘图,必须是8位的灰度或者彩色图像。
- boardSize:棋盘图中每行和每列角点的个数(内点)。
- imageCorners:存储检测到的内部角点的像素坐标
- found:是否成功找到(bool)
-
cornerSubPix()
void cornerSubPix(
InputArray image,
InputOutputArray corners,
Size winSize,
Size zeroZone,
TermCriteria criteria);
- 功能:亚像素角点检测
- 参数:
- cv::InputArray image:输入图像
- cv::InputOutputArray corners: 角点(既作为输入,也作为输出)
- cv::Size winSize:区域大小为 NXN; N=(winSize*2+1)
- cv::Size zeroZone:类似于winSize,但是总具有较小的范围,Size(-1,-1)表示忽略
- cv::TermCriteria criteria : 停止优化的标准
TermCriteria:
cv::TermCriteria::TermCriteria ( int type, int maxCount, double epsilon)
- type:迭代终止的条件类型(cv::TermCriteria::MAX_ITER (迭代次数达到了最大次数)、cv::TermCriteria::EPS(角点位置变化的最小值),可以选其一,或两者均选)
- maxCount:最大迭代的次数
- epsilon:收敛的阈值
-
calibrateCamera()
double calibrateCamera( InputArrayOfArrays objectPoints,
InputArrayOfArrays imagePoints,
Size imageSize,
InputOutputArray cameraMatrix,
InputOutputArray distCoeffs,
OutputArrayOfArrays rvecs,
OutputArrayOfArrays tvecs,
int flags = 0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 30, DBL_EPSILON) );
- 功能:相机标定
- 参数:
- objectPoints: 三维点
- imagePoints,: 图像点
- imageSize:图像尺寸
- cameraMatrix:输出相机矩阵
- distCoeffs: 输出畸变矩阵
- rvecs, tvecs: Rs、Ts
- flag:设置选项
-
initUndistortRectifyMap()
void initUndistortRectifyMap( InputArray cameraMatrix,
InputArray distCoeffs,
InputArray R,
InputArray newCameraMatrix,
Size size,
int m1type,
OutputArray map1,
OutputArray map2 )
- 功能:
这个函数用于计算无畸变和修正转换关系,并以映射的形式表示结果以进行重新映射。无畸变的图像看起来就像原始的图像,就像这个图像是用内参为newCameraMatrix的且无畸变的相机采集得到的。
在单目相机例子中,newCameraMatrix一般和cameraMatrix相等,或者可以用cv::getOptimalNewCameraMatrix来计算,获得一个更好的有尺度的控制结果。
在双目相机例子中,newCameraMatrix一般是用cv::stereoRectify计算而来的,设置为P1或P2。 此外,根据R,新的相机在坐标空间中的取向是不同的。例如,它帮助配准双目相机的两个相机方向,从而使得两个图像的极线是水平的,且y坐标相同(在双目相机的两个相机谁水平放置的情况下)。 该函数实际上为反向映射算法构建映射,供反向映射使用。也就是,对于在已经修正畸变的图像中的每个像素(u,v),该函数计算原来图像(从相机中获得的原始图像)中对应的坐标系
- 参数:
-
remap()
void remap(InputArray src,
OutputArray dst,
InputArray map1,
InputArray map2,
int interpolation,
int borderMode=BORDER_CONSTANT,
const Scalar& borderValue=Scalar())
- 功能:重映射,把一副图像中某位置的像素放到另一幅图像的指定位置的过程。
例如:g(x,y)为目标图像函数,f(x,y)为源图像,h(x,y)为映射函数
如果要是实现按x的翻转,则h(x,y)=(l.cols-x,y);
- 参数:
src:输入图像,即原图像,需要单通道8位或者浮点类型的图像
dst :输出图像,即目标图像,需和原图形一样的尺寸和类型
map1:它有两种可能表示的对象:(1)表示点(x,y)的第一个映射;(2)表示CV_16SC2,CV_32FC1等
map2:它有两种可能表示的对象:(1)若map1表示点(x,y)时,这个参数不代表任何值;(2)表示 CV_16UC1,CV_32FC1类型的Y值
interpolation:插值方式,有四中插值方式:(1)INTER_NEAREST——最近邻插值
(2)INTER_LINEAR——双线性插值(默认)(3)INTER_CUBIC——双三样条插值(默认)
(4)INTER_LANCZOS4——lanczos插值(默认)
borderMode:边界模式,默认BORDER_CONSTANT
borderValue:边界颜色,默认Scalar()黑色
-
undistort()
void undistort(InputArray src,
OutputArray dst,
InputArray cameraMatrix,
InputArray distCoeffs,
InputArray newCameraMatrix=noArray() )
- 功能:矫正畸变
- 参数:
- src –(输入)失真图像。
- dst –(输出)校正的图像,其大小和类型与相同
src
。- cameraMatrix –相机内参矩阵
。
- distCoeffs –
相机的畸变矩阵 。如果向量为空,则假定零畸变系数。
- newCameraMatrix –新的内参矩阵。默认情况下,它与
cameraMatrix
相同。可以使用getOptimalNewCameraMatrix()计算newCameraMatrix。
-
projectPoints()
void projectPoints( InputArray objectPoints,
InputArray rvec, InputArray tvec,
InputArray cameraMatrix, InputArray distCoeffs,
OutputArray imagePoints,
OutputArray jacobian = noArray(),
double aspectRatio = 0 );
- 功能:通过给定的内参数和外参数计算三维点投影到二维图像平面上的坐标
- 参数:
- objectPoints:输入三维点
- rvec,tvec:相机的位姿R,T
- cameraMatrix:内参矩阵
- distCoeffs:畸变矩阵
- imagePoints:输出像素坐标系坐标
- jacobian:
参考:
https://www.cnblogs.com/zyly/p/9373991.html
双目视觉三维重建_tiemaxiaosu的博客-CSDN博客
相机畸变与标定:相机畸变与标定
综述|相机标定方法:综述 | 相机标定方法 - 腾讯云开发者社区-腾讯云
最详细、最完整的相机标定讲解:最详细、最完整的相机标定讲解_卍卐没想到的博客-CSDN博客
OpenCV相机标定和姿态更新:https://www.cnblogs.com/mikewolf2002/p/5746667.html
SLAM 中常用的相机模型&畸变模型总结:SLAM 中常用的相机模型&畸变模型总结_啊啦啦工业的博客-CSDN博客
Opencv畸变矫正原理与损失有效像素原理分析:https://www.cnblogs.com/riddick/p/6711263.html
双目视觉自标定技术的研究:双目视觉自标定技术的研究 - 百度文库
Zhengyou Zhang: A Flexible New Technique for Camera Calibration. IEEE T-PAMI), 2000.
[图像]畸变校正详解:[图像]畸变校正详解_图像畸变校正_祥知道的博客-CSDN博客
超实用!图像去畸变矫正及双线性内插法:超实用!图像去畸变矫正及双线性内插法_3D视觉工坊的博客-CSDN博客