摄像机标定
利用摄像机所拍摄到的图像来还原空间中的物体。在这里,不妨假设摄像机所拍摄到的图像与三维空间中的物体之间存在以下一种简单的线性关系:[像]=M[物],这里,矩阵M可以看成是摄像机成像的几何模
型。 M中的参数就是摄像机参数。通常,这些参数是要通过实验与计算来得到的。这个求解参数的过程
就称为摄像机标定。
摄像机标定
camera calibration
用 途 帮助还原空间物体 常用方法 张正友标定方法 板子尺寸 300X300mm
目录
1 简要介绍
2 机器视觉标定板说明
3 张正友平面标定方法
算法原理
算法描述
优缺点
简要介绍
在图像测量过程以及机器视觉应用中,常常会涉及到这样一个概念,那就是利用摄像机所拍摄到的图像
来还原空间中的物体。在这里,不妨假设摄像机所拍摄到的图像与三维空间中的物体之间存在以下一种
简单的线性关系:
[像]=M[物]
机器视觉标定板说明编辑
MV-SB型
特征圆成阵列分布,间距30mm、板子尺寸:300X300mm。
4个大圆为标志圆,大圆环标志确定方向。
用特征圆的圆心坐标进行标定。
特征圆的圆心坐标提取方法:获得4个标志圆坐标,利用仿射变换将特征圆的坐标调正,然后对其进行
排序,确定相应特征圆图像坐标。
采用铝合金材料。
张正友平面标定方法
算法原理
在这里假定模板平面在世界坐标系Z=0的平面上。
基本原理:
其中,K为摄像机的内参数矩阵,[X Y 1]T为模板平面上点的齐次坐标,[u v 1]T为模板平面上点投影到
图象平面上对应点的齐次坐标,[r1 r2 r3]和t 分别是摄像机坐标系相对于世界坐标系的旋转矩阵和平
移向量。
根据旋转矩阵的性质,即r1Tr2=0和||r1||=||r2||=1,每幅图象可以获得以下两个对内参数矩阵的基本
约束
由于摄像机有4个未知内参数,所以当所摄取得的图像数目大于等于3时,就可以线性唯一求解出K。
算法描述
1、打印一张模板并贴在一个平面上;
2、从不同角度拍摄若干张模板图像;
3、检测出图像中的特征点;
4、求出摄像机的内参数和外参数;
5、求出畸变系数;
6、优化求精。
优缺点
张正友的平面标定方法是介于传统标定方法和自标定方法之间的一种方法。它既避免了传统方法设备要
求高,操作繁琐等缺点,又较自标定方法精度高,符合办公、家庭使用的桌面视觉系统(DVS)的标定要求
。此方法是需要确定模板上点阵的物理坐标以及图像和模板之间的点的匹配,这给不熟悉计算机视觉的
使用者带来了不便。
========
摄像机标定(Camera calibration)笔记
一 作用建立3D到2D的映射关系,一旦标定后,对于一个摄像机内部参数K(光心焦距变形参数等,简化的情况是
只有f错切=0,变比=1,光心位置简单假设为图像中心),参数已知,那么根据2D投影,就可以估计出R t
;
空间3D点所在的线就确定了,根据多视图(多视图可以是运动图像)可以重建3D。
如果场景已知,则可以把场景中的虚拟物体投影到2D图像平面(DLT,只要知道M即可)。或者根据世界
坐标与摄像机坐标的相对关系,R,T,直接在Wc位置渲染3D图形,这是AR的应用。
因为是离线的,所以可以用手工定位点的方法。
二 方法
1 Direct linear transformation (DLT) method
2 A classical approach is "Roger Y. Tsai Algorithm".It is a 2-stage algorithm, calculating
the pose (3D Orientation, and x-axis and y-axis translation) in first stage. In second
stage it computes the focal length, distortion coefficients and the z-axis translation.
3 Zhengyou Zhang's "a flexible new technique for camera calibration" based on a planar
chess board. It is based on constrains on homography.
4个空间:世界,摄像机(镜头所在位置),图像(原点到镜头中心位置距离f),图像仿射空间(最终成
像空间),这些空间原点在不同应用场合可以假设重合。
具体说来,前两个空间是R t关系,6个变量搞定;第二到第三空间用相似三角形投影到平面上,如果没
有变形,光心在图像中心上,到这里为止其实就够了,K就一个参数。
K是摄像机坐标系到图像平面的转换函数,叫内部参数(intrinsic parameter)。标定就是求上三角矩阵K
的5个参数的过程。
用齐次(Homogeneous)坐标表示这个关系: (实际二维点是[u,v]'=[U/W , V/W]')
[U] [a b -u0][-fxc/zc] [-fa -fb -u0][xc/zc] [xc/zc]
ub=[V] =[0 c -v0][-fyc/zc ]= [0 -fc -v0][yc/zc]= K * [yc/zc]
[W] [0 0 1][ 1 ] [0 0 1] [ 1 ] [ 1 ]
世界坐标系到图像坐标系的转换有平移矢量t和旋转R矩阵组成,R,t是外部参数(extrinsic parameters)
,表示摄像机姿态,包含6个自由度,R t 各3个。Xc=[xc yc zc]'= R(Xw-t) , ' 表示转置。
把两个合起来
zc*ub=zc*[U V W]'= K*R(Xw-t)
把三维点Xw表示成齐次坐标,Xwb=[Xw,1]',则
[U]
ub=[V] = [K*R |-K*R*t][Xw 1]' =M*[Xw 1]' = M* Xwb
[W] ([K*R |-K*R*t]也可写成K[R t']串联的形式)
3×4矩阵M称为投影矩阵,这样三维点P到二维平面的投影就由上式的线性变换表达了。摄像机标定就是
求K,K是与R,t一起耦合在M中的。
常用的方法是已知场景3D点和图像二维点,求M。如Tsai grid方法。
根据至少6个对应点,建立方程组:
alpha*[u v 1]' = M * [x y z 1]'
写成2n×12矩阵 l*mij=0的形式(叫Direct Linear Transformation (DLT)):
[P1 | 0 | -u*P1] [m11]
[0 |P1 | -v*P1]* [m12] = 0.
[ ... ] ...
[ ... ] [m34] (矢量Pi表示第i个三维点[Xi,Yi,Zi,1]')
方程解是l'*l的最小eigenvalue对应的eigenvector,|m|2=1,很多书上提到用SVD分解,l=UDV',V就是
特征矢量矩阵。
其实,l'*l=VDU'UDV'=VDDV',V'=inv(V),所以l'*l*V=VD^2,这就是特征矢量的求法。很明显,l的
singular vector是l'*l的特征矢量的平方根。
(PCA,K-L变换 和SVD之间的关系。边肇琪《模式识别》提到人脸识别降维的例子时,讲到如果样本数小
于特征维数,要用SVD提取特征向量。在这里协方差矩阵为l'*l,是12×12的矩阵,l矩阵的短边为9,而
且计算M(Homography矩阵)时,点对要经过Normalize,完全可以参照人脸的例子。SVD总是和最小二乘,
伪逆Ax=b-》A'*Ax=A'b-》x=inv(A'*A)*A'*b,联系在一起。)
M=[K*R |-K*R*t]=[A|b],A = K*R,用QR分解(或SVD)将A分解为上三角矩阵K和单位正交阵R.
实际上M只有11个参数(Rt6个,K5个)。如果把摄像头畸变也考虑进去,mij变成16个值,则需要更多的
对应点。
如果3D点在一个平面上,l is singular,那么退化成9个参数。
已知场景3D点pattern拍一张照片,点分布在2到3个pattern平面上。
以上参考 milan sonka的Image processing一书。
实际上现在大家都是用张正友的方法,将网格点构成的pattern预先打印在一张纸上,对它拍摄不同方向
照片(至少两张)。
网上有matlab calibration包,OpenCV也有对应的实现,可以同时求摄像机的内部参数和外部参数(姿态
估计)。
具体用到梯度下降方法,尽管R有3*3=9个数,但只有3个自由度,用rodrigues formula转为3维矢量,计
算Jacobi更方便。
具体参考:
1 Rodrigues' Formula
http://www.cs.berkeley.edu/~ug/slide/pipeline/assignments/as5/rotation.html
2 OpenCV函数说明
http://opencv.willowgarage.com/documentation/camera_calibration_and_3d_reconstruction.html
3 Camera Calibration Toolbox for Matlab
http://www.vision.caltech.edu/bouguetj/calib_doc/
--------------------------------------------------
如果只有平面4个点,如何计算构造DLT,求Homography矩阵[mij]?
1 左边8×9矩阵A,m为9个数。rank 8, i.e. they have a one-dimensional null-space.
The solution can be determined from the null-space of A.
svd分解 A=UDV',D为singular valuies,mij的解为V中最小的singular value对应列。
2 退化成一个平面到另一个平面的透视变换
u = (L1*X + L2*Y + L3)/(L7*X + L8*Y + 1) ;
v = (L4*X + L5*X + L6)/(L7*X + L8*Y + 1) ;
--------------------------------------------------
姿态估计:
基本思想是用3D点align到2D点上,使误差最小。已知3D场景点,估计摄像机姿态,或者3D场景点关于摄
像机坐标系的相对位置。
比如,marker可以构成世界坐标系,4个点坐标已知。它们相对于摄像机空间的坐标(Xa,Ya,Za)求出,
xy轴可以求出,用右手法则z轴可以求出。
这样世界坐标系的3根轴已知,就可以渲染3D场景了。(AR应用)
具体参见Hybrid camera pose estimation combining square fiducials localisation technique and
orthogonal iteration algorithm
或 OI算法 都是类似梯度下降的迭代算法,可以利用前一帧的参数作为初值,实时估计R和T。
--------------------------------------------------
重建3D场景:
对于多个未经标定的摄像机,也可以根据在每个图像中的2D点位置重建3D点
x1=P1X
x2=P2X
。。。。
Pi可以由已知场景点实时更新,xi可以根据运动估计更新,这样可以计算X。
具体方法参见 Multiple View Geometry in Computer Vision 一书。
本文引用地址:http://blog.sciencenet.cn/home.php?mod=space&uid=465130&do=blog&id=365366
========
摄像头标定
目录1 标定原理介绍
2 标定程序1(opencv自带的示例程序)
2.1 简介
2.2 使用说明
2.3 调用命令行和参数介绍
2.4 list_of_views.txt
2.5 输入为摄像机或者avi文件时
2.6 代码
3 标定程序2
3.1 代码
标定原理介绍
摄像机小孔模型 Cv照相机定标和三维重建#针孔相机模型和变形
标定程序1(opencv自带的示例程序)
简介
读者可以直接使用Opencv自带的摄像机标定示例程序,该程序位于 “\OpenCV\samples\c目录下的
calibration.cpp”,程序的输入支持直接从USB摄像机读取图片标定,或者读取avi文件或者已经存放于
电脑上图片进行标定。
使用说明
编译运行程序,如果未设置任何命令行参数,则程序会有提示,告诉你应该在你编译出来的程序添加必
要的命令行,比如你的程序是calibration.exe(以windows操作系统为例)。则你可以添加如下命令行(
以下加粗的字体所示):
calibration -w 6 -h 8 -s 2 -n 10 -o camera.yml -op -oe [<list_of_views.txt>]
调用命令行和参数介绍
Usage: calibration
-w <board_width> # 图片某一维方向上的交点个数
-h <board_height> # 图片另一维上的交点个数
[-n <number_of_frames>] # 标定用的图片帧数
# (if not specified, it will be set to the number
# of board views actually available)
[-d <delay>] # a minimum delay in ms between subsequent attempts to capture
a next view
# (used only for video capturing)
[-s <square_size>] # square size in some user-defined units (1 by default)
[-o <out_camera_params>] # the output filename for intrinsic [and extrinsic] parameters
[-op] # write detected feature points
[-oe] # write extrinsic parameters
[-zt] # assume zero tangential distortion
[-a <aspect_ratio>] # fix aspect ratio (fx/fy)
[-p] # fix the principal point at the center
[-v] # flip the captured images around the horizontal axis
[input_data] # 输入数据,是下面三种之中的一种:
# - 指定的包含图片列表的txt文件
# - name of video file with a video of the board
# if input_data not specified, a live view from the camera is
used
标定图片示例
标定图片示例
上图中,横向和纵向分别为9个交点和6个交点,对应上面的命令行的命令参数应该为: -w 9 -h 6。
经多次使用发现,不指定 -p参数时计算的结果误差较大,主要表现在对u0,v0的估计误差较大,因此建
议使用时加上-p参数
list_of_views.txt
该txt文件表示的是你在电脑上面需要用以标定的图片列表。
view000.png
view001.png
#view002.png
view003.png
view010.png
one_extra_view.jpg
上面的例子中,前面加“井号”的图片被忽略。
在windows的命令行中,有一种简便的办法来产生此txt文件。在CMD窗口中输入如下命令(假设当前目录
里面的所有jpg文件都用作标定,并且生成的文件为a.txt)。
dir *.jpg /B >> a.txt
输入为摄像机或者avi文件时
"When the live video from camera is used as input, the following hot-keys may be
used:\n"
" <ESC>, 'q' - quit the program\n"
" 'g' - start capturing images\n"
" 'u' - switch undistortion on/off\n";
代码
请直接复制 calibration.cpp 中的相关代码。
标定程序2
OPENCV没有提供完整的示例,自己整理了一下,贴出来记录。
首先自制一张标定图片,用A4纸打印出来,设定距离,再设定标定棋盘的格子数目,如8×6,以下是我
做的图片8×8
然后利用cvFindChessboardCorners找到棋盘在摄像头中的2D位置,这里cvFindChessboardCorners不太
稳定,有时不能工作,也许需要图像增强处理。
计算实际的距离,应该是3D的距离。我设定为21.6毫米,既在A4纸上为两厘米。
再用cvCalibrateCamera2计算内参,
最后用cvUndistort2纠正图像的变形。
结果如下:
具体的函数使用,请参考Cv照相机定标和三维重建#照相机定标
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// OpenCV
#include <cxcore.h>
#include <cv.h>
#include <highgui.h>
#include <cvaux.h>
void InitCorners3D(CvMat *Corners3D, CvSize ChessBoardSize, int Nimages, float SquareSize);
void makeChessBoard();
int myFindChessboardCorners( const void* image, CvSize pattern_size,
CvPoint2D32f* corners, int* corner_count=NULL,
int flags=CV_CALIB_CB_ADAPTIVE_THRESH );
inline int drawCorssMark(IplImage *dst,CvPoint pt)
/*************************************************
Function: main_loop
Description: 绘制一个十字标记
Calls:
Called By:
Input: RGB image, pt
Output:
Return:
Others: 需要检查坐标是否越界 to do list
*************************************************/
{
const int cross_len = 4;
CvPoint pt1,pt2,pt3,pt4;
pt1.x = pt.x;
pt1.y = pt.y - cross_len;
pt2.x = pt.x;
pt2.y = pt.y + cross_len;
pt3.x = pt.x - cross_len;
pt3.y = pt.y;
pt4.x = pt.x + cross_len;
pt4.y = pt.y;
cvLine(dst,pt1,pt2,CV_RGB(0,255,0),2,CV_AA, 0 );
cvLine(dst,pt3,pt4,CV_RGB(0,255,0),2,CV_AA, 0 );
return 0;
}
/* declarations for OpenCV */
IplImage *current_frame_rgb,grid;
IplImage *current_frame_gray;
IplImage *chessBoard_Img;
int Thresholdness = 120;
int image_width = 320;
int image_height = 240;
bool verbose = false;
const int ChessBoardSize_w = 7;
const int ChessBoardSize_h = 7;
// Calibration stuff
bool calibration_done = false;
const CvSize ChessBoardSize = cvSize(ChessBoardSize_w,ChessBoardSize_h);
//float SquareWidth = 21.6f; //实际距离 毫米单位 在A4纸上为两厘米
float SquareWidth = 17; //投影实际距离 毫米单位 200
const int NPoints = ChessBoardSize_w*ChessBoardSize_h;
const int NImages = 20; //Number of images to collect
CvPoint2D32f corners[NPoints*NImages];
int corner_count[NImages] = {0};
int captured_frames = 0;
CvMat *intrinsics;
CvMat *distortion_coeff;
CvMat *rotation_vectors;
CvMat *translation_vectors;
CvMat *object_points;
CvMat *point_counts;
CvMat *image_points;
int find_corners_result =0 ;
void on_mouse( int event, int x, int y, int flags, void* param )
{
if( event == CV_EVENT_LBUTTONDOWN )
{
//calibration_done = true;
}
}
int main(int argc, char *argv[])
{
CvFont font;
cvInitFont( &font, CV_FONT_VECTOR0,5, 5, 0, 7, 8);
intrinsics = cvCreateMat(3,3,CV_32FC1);
distortion_coeff = cvCreateMat(1,4,CV_32FC1);
rotation_vectors = cvCreateMat(NImages,3,CV_32FC1);
translation_vectors = cvCreateMat(NImages,3,CV_32FC1);
point_counts = cvCreateMat(NImages,1,CV_32SC1);
object_points = cvCreateMat(NImages*NPoints,3,CV_32FC1);
image_points = cvCreateMat(NImages*NPoints,2,CV_32FC1);
// Function to fill in the real-world points of the checkerboard
InitCorners3D(object_points, ChessBoardSize, NImages, SquareWidth);
CvCapture* capture = 0;
if( argc == 1 || (argc == 2 && strlen(argv[1]) == 1 && isdigit(argv[1][0])))
capture = cvCaptureFromCAM( argc == 2 ? argv[1][0] - '0' : 0 );
else if( argc == 2 )
capture = cvCaptureFromAVI( argv[1] );
if( !capture )
{
fprintf(stderr,"Could not initialize capturing...\n");
return -1;
}
// Initialize all of the IplImage structures
current_frame_rgb = cvCreateImage(cvSize(image_width, image_height), IPL_DEPTH_8U, 3);
IplImage *current_frame_rgb2 = cvCreateImage(cvSize(image_width, image_height),
IPL_DEPTH_8U, 3);
current_frame_gray = cvCreateImage(cvSize(image_width, image_height), IPL_DEPTH_8U, 1);
chessBoard_Img = cvCreateImage(cvSize(image_width, image_height), IPL_DEPTH_8U, 3);
current_frame_rgb2->origin = chessBoard_Img->origin = current_frame_gray->origin =
current_frame_rgb->origin = 1;
makeChessBoard();
cvNamedWindow( "result", 0);
cvNamedWindow( "Window 0", 0);
cvNamedWindow( "grid", 0);
cvMoveWindow( "grid", 100,100);
cvSetMouseCallback( "Window 0", on_mouse, 0 );
cvCreateTrackbar("Thresholdness","Window 0",&Thresholdness, 255,0);
while (!calibration_done)
{
while (captured_frames < NImages)
{
current_frame_rgb = cvQueryFrame( capture );
//current_frame_rgb = cvLoadImage( "c:\\BoardStereoL3.jpg" );
//cvCopy(chessBoard_Img,current_frame_rgb);
if( !current_frame_rgb )
break;
cvCopy(current_frame_rgb,current_frame_rgb2);
cvCvtColor(current_frame_rgb, current_frame_gray, CV_BGR2GRAY);
//cvThreshold
(current_frame_gray,current_frame_gray,Thresholdness,255,CV_THRESH_BINARY);
//cvThreshold
(current_frame_gray,current_frame_gray,150,255,CV_THRESH_BINARY_INV);
/*
int pos = 1;
IplConvKernel* element = 0;
const int element_shape = CV_SHAPE_ELLIPSE;
element = cvCreateStructuringElementEx( pos*2+1, pos*2+1, pos, pos, element_shape,
0 );
cvDilate(current_frame_gray,current_frame_gray,element,1);
cvErode(current_frame_gray,current_frame_gray,element,1);
cvReleaseStructuringElement(&element);
*/
find_corners_result = cvFindChessboardCorners(current_frame_gray,
ChessBoardSize,
&corners[captured_frames*NPoints],
&corner_count[captured_frames],
0);
cvDrawChessboardCorners(current_frame_rgb2, ChessBoardSize, &corners
[captured_frames*NPoints], NPoints, find_corners_result);
cvShowImage("Window 0",current_frame_rgb2);
cvShowImage("grid",chessBoard_Img);
if(find_corners_result==1)
{
cvWaitKey(2000);
cvSaveImage("c:\\hardyinCV.jpg",current_frame_rgb2);
captured_frames++;
}
//cvShowImage("result",current_frame_gray);
intrinsics->data.fl[0] = 256.8093262; //fx
intrinsics->data.fl[2] = 160.2826538; //cx
intrinsics->data.fl[4] = 254.7511139; //fy
intrinsics->data.fl[5] = 127.6264572; //cy
intrinsics->data.fl[1] = 0;
intrinsics->data.fl[3] = 0;
intrinsics->data.fl[6] = 0;
intrinsics->data.fl[7] = 0;
intrinsics->data.fl[8] = 1;
distortion_coeff->data.fl[0] = -0.193740; //k1
distortion_coeff->data.fl[1] = -0.378588; //k2
distortion_coeff->data.fl[2] = 0.028980; //p1
distortion_coeff->data.fl[3] = 0.008136; //p2
cvWaitKey(40);
find_corners_result = 0;
}
//if (find_corners_result !=0)
{
printf("\n");
cvSetData( image_points, corners, sizeof(CvPoint2D32f));
cvSetData( point_counts, &corner_count, sizeof(int));
cvCalibrateCamera2( object_points,
image_points,
point_counts,
cvSize(image_width,image_height),
intrinsics,
distortion_coeff,
rotation_vectors,
translation_vectors,
0);
// [fx 0 cx; 0 fy cy; 0 0 1].
cvUndistort2
(current_frame_rgb,current_frame_rgb,intrinsics,distortion_coeff);
cvShowImage("result",current_frame_rgb);
float intr[3][3] = {0.0};
float dist[4] = {0.0};
float tranv[3] = {0.0};
float rotv[3] = {0.0};
for ( int i = 0; i < 3; i++)
{
for ( int j = 0; j < 3; j++)
{
intr[i][j] = ((float*)(intrinsics->data.ptr + intrinsics-
>step*i))[j];
}
dist[i] = ((float*)(distortion_coeff->data.ptr))[i];
tranv[i] = ((float*)(translation_vectors->data.ptr))[i];
rotv[i] = ((float*)(rotation_vectors->data.ptr))[i];
}
dist[3] = ((float*)(distortion_coeff->data.ptr))[3];
printf("-----------------------------------------\n");
printf("INTRINSIC MATRIX: \n");
printf("[ %6.4f %6.4f %6.4f ] \n", intr[0][0], intr[0][1], intr[0][2]);
printf("[ %6.4f %6.4f %6.4f ] \n", intr[1][0], intr[1][1], intr[1][2]);
printf("[ %6.4f %6.4f %6.4f ] \n", intr[2][0], intr[2][1], intr[2][2]);
printf("-----------------------------------------\n");
printf("DISTORTION VECTOR: \n");
printf("[ %6.4f %6.4f %6.4f %6.4f ] \n", dist[0], dist[1], dist[2], dist
[3]);
printf("-----------------------------------------\n");
printf("ROTATION VECTOR: \n");
printf("[ %6.4f %6.4f %6.4f ] \n", rotv[0], rotv[1], rotv[2]);
printf("TRANSLATION VECTOR: \n");
printf("[ %6.4f %6.4f %6.4f ] \n", tranv[0], tranv[1], tranv[2]);
printf("-----------------------------------------\n");
cvWaitKey(0);
calibration_done = true;
}
}
exit(0);
cvDestroyAllWindows();
}
void InitCorners3D(CvMat *Corners3D, CvSize ChessBoardSize, int NImages, float SquareSize)
{
int CurrentImage = 0;
int CurrentRow = 0;
int CurrentColumn = 0;
int NPoints = ChessBoardSize.height*ChessBoardSize.width;
float * temppoints = new float[NImages*NPoints*3];
// for now, assuming we're row-scanning
for (CurrentImage = 0 ; CurrentImage < NImages ; CurrentImage++)
{
for (CurrentRow = 0; CurrentRow < ChessBoardSize.height; CurrentRow++)
{
for (CurrentColumn = 0; CurrentColumn < ChessBoardSize.width; CurrentColumn++)
{
temppoints[(CurrentImage*NPoints*3)+(CurrentRow*ChessBoardSize.width +
CurrentColumn)*3]=(float)CurrentRow*SquareSize;
temppoints[(CurrentImage*NPoints*3)+(CurrentRow*ChessBoardSize.width +
CurrentColumn)*3+1]=(float)CurrentColumn*SquareSize;
temppoints[(CurrentImage*NPoints*3)+(CurrentRow*ChessBoardSize.width +
CurrentColumn)*3+2]=0.f;
}
}
}
(*Corners3D) = cvMat(NImages*NPoints,3,CV_32FC1, temppoints);
}
int myFindChessboardCorners( const void* image, CvSize pattern_size,
CvPoint2D32f* corners, int* corner_count,
int flags )
{
IplImage* eig = cvCreateImage( cvGetSize(image), 32, 1 );
IplImage* temp = cvCreateImage( cvGetSize(image), 32, 1 );
double quality = 0.01;
double min_distance = 5;
int win_size =10;
int count = pattern_size.width * pattern_size.height;
cvGoodFeaturesToTrack( image, eig, temp, corners, &count,
quality, min_distance, 0, 3, 0, 0.04 );
cvFindCornerSubPix( image, corners, count,
cvSize(win_size,win_size), cvSize(-1,-1),
cvTermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS,20,0.03));
cvReleaseImage( &eig );
cvReleaseImage( &temp );
return 1;
}
void makeChessBoard()
{
CvScalar e;
e.val[0] =255;
e.val[1] =255;
e.val[2] =255;
cvSet(chessBoard_Img,e,0);
for(int i = 0;i<ChessBoardSize.width+1;i++)
for(int j = 0;j<ChessBoardSize.height+1;j++)
{
int w =(image_width)/2/(ChessBoardSize.width);
int h = w; //(image_height)/2/(ChessBoardSize.height);
int ii = i+1;
int iii = ii+1;
int jj =j+1;
int jjj =jj+1;
int s_x = image_width/6;
if((i+j)%2==1)
cvRectangle( chessBoard_Img, cvPoint(w*i+s_x,h*j+s_x),cvPoint(w*ii-
1+s_x,h*jj-1+s_x), CV_RGB(0,0,0),CV_FILLED, 8, 0 );
}
}
========
OpenCV单目摄像机标定程序
我自己写了一个摄像机标定程序,核心算法参照learning opencv,但是那个程序要从命令行预先输入参
数,且标定图片要预先准备好,我觉得不太好,我就自己写了一个,跟大家分享下。
若有纰漏,希望大家指正!
#include <string>
#include <iostream>
#include <cv.h>
#include <highgui.h>
#pragma comment(lib, "ml.lib")
#pragma comment(lib, "cv.lib")
#pragma comment(lib, "cvaux.lib")
#pragma comment(lib, "cvcam.lib")
#pragma comment(lib, "cxcore.lib")
#pragma comment(lib, "cxts.lib")
#pragma comment(lib, "highgui.lib")
#pragma comment(lib, "cvhaartraining.lib")
using namespace std;
int main()
{
int cube_length=7;
CvCapture* capture;
capture=cvCreateCameraCapture(0);
if(capture==0)
{
printf("无法捕获摄像头设备!\n\n");
return 0;
}
else
{
printf("捕获摄像头设备成功!!\n\n");
}
IplImage* frame = NULL;
cvNamedWindow("摄像机帧截取窗口",1);
printf("按“C”键截取当前帧并保存为标定图片...\n按“Q”键退出截取帧过程...\n\n");
int number_image=1;
char *str1;
str1=".jpg";
char filename[20]="";
while(true)
{
frame=cvQueryFrame(capture);
if(!frame)
break;
cvShowImage("摄像机帧截取窗口",frame);
if(cvWaitKey(10)=='c')
{
sprintf_s (filename,"%d.jpg",number_image);
cvSaveImage(filename,frame);
cout<<"成功获取当前帧,并以文件名"<<filename<<"保存...\n\n";
printf("按“C”键截取当前帧并保存为标定图片...\n按“Q”键退出截取帧
过程...\n\n");
number_image++;
}
else if(cvWaitKey(10)=='q')
{
printf("截取图像帧过程完成...\n\n");
cout<<"共成功截取"<<--number_image<<"帧图像!!\n\n";
break;
}
}
cvReleaseImage(&frame);
cvDestroyWindow("摄像机帧截取窗口");
IplImage * show;
cvNamedWindow("RePlay",1);
int a=1;
int number_image_copy = number_image;
CvSize board_size=cvSize(7,7);
int board_width=board_size.width;
int board_height=board_size.height;
int total_per_image=board_width*board_height;
CvPoint2D32f * image_points_buf = new CvPoint2D32f[total_per_image];
CvMat * image_points=cvCreateMat(number_image*total_per_image,2,CV_32FC1);
CvMat * object_points=cvCreateMat(number_image*total_per_image,3,CV_32FC1);
CvMat * point_counts=cvCreateMat(number_image,1,CV_32SC1);
CvMat * intrinsic_matrix=cvCreateMat(3,3,CV_32FC1);
CvMat * distortion_coeffs=cvCreateMat(5,1,CV_32FC1);
int count;
int found;
int step;
int successes=0;
while(a<=number_image_copy)
{
sprintf_s (filename,"%d.jpg",a);
show=cvLoadImage(filename,-1);
found=cvFindChessboardCorners(show,board_size,image_points_buf,&count,
CV_CALIB_CB_ADAPTIVE_THRESH|CV_CALIB_CB_FILTER_QUADS);
if(found==0)
{
cout<<"第"<<a<<"帧图片无法找到棋盘格所有角点!\n\n";
cvNamedWindow("RePlay",1);
cvShowImage("RePlay",show);
cvWaitKey(0);
}
else
{
cout<<"第"<<a<<"帧图像成功获得"<<count<<"个角点...\n";
cvNamedWindow("RePlay",1);
IplImage * gray_image= cvCreateImage(cvGetSize(show),8,1);
cvCvtColor(show,gray_image,CV_BGR2GRAY);
cout<<"获取源图像灰度图过程完成...\n";
cvFindCornerSubPix(gray_image,image_points_buf,count,cvSize
(11,11),cvSize(-1,-1),
cvTermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER,30,0.1));
cout<<"灰度图亚像素化过程完成...\n";
cvDrawChessboardCorners
(show,board_size,image_points_buf,count,found);
cout<<"在源图像上绘制角点过程完成...\n\n";
cvShowImage("RePlay",show);
cvWaitKey(0);
}
if(total_per_image==count)
{
step=successes*total_per_image;
for(int i=step,j=0;j<total_per_image;++i,++j)
{
CV_MAT_ELEM(*image_points,float,i,0)=image_points_buf[j].x;
CV_MAT_ELEM(*image_points,float,i,1)=image_points_buf[j].y;
CV_MAT_ELEM(*object_points,float,i,0)=(float)
(j/cube_length);
CV_MAT_ELEM(*object_points,float,i,1)=(float)(j
%cube_length);
CV_MAT_ELEM(*object_points,float,i,2)=0.0f;
}
CV_MAT_ELEM(*point_counts,int,successes,0)=total_per_image;
successes++;
}
a++;
}
cvReleaseImage(&show);
cvDestroyWindow("RePlay");
cout<<"*********************************************\n";
cout<<number_image<<"帧图片中,标定成功的图片为"<<successes<<"帧...\n";
cout<<number_image<<"帧图片中,标定失败的图片为"<<number_image-successes<<"帧...\n
\n";
cout<<"*********************************************\n\n";
cout<<"按任意键开始计算摄像机内参数...\n\n";
CvCapture* capture1;
capture1=cvCreateCameraCapture(0);
IplImage * show_colie;
show_colie=cvQueryFrame(capture1);
CvMat * object_points2=cvCreateMat(successes*total_per_image,3,CV_32FC1);
CvMat * image_points2=cvCreateMat(successes*total_per_image,2,CV_32FC1);
CvMat * point_counts2=cvCreateMat(successes,1,CV_32SC1);
for(int i=0;i<successes*total_per_image;++i)
{
CV_MAT_ELEM(*image_points2,float,i,0)=CV_MAT_ELEM(*image_points,float,i,0);
CV_MAT_ELEM(*image_points2,float,i,1)=CV_MAT_ELEM(*image_points,float,i,1);
CV_MAT_ELEM(*object_points2,float,i,0)=CV_MAT_ELEM
(*object_points,float,i,0);
CV_MAT_ELEM(*object_points2,float,i,1)=CV_MAT_ELEM
(*object_points,float,i,1);
CV_MAT_ELEM(*object_points2,float,i,2)=CV_MAT_ELEM
(*object_points,float,i,2);
}
for(int i=0;i<successes;++i)
{
CV_MAT_ELEM(*point_counts2,int,i,0)=CV_MAT_ELEM(*point_counts,int,i,0);
}
cvReleaseMat(&object_points);
cvReleaseMat(&image_points);
cvReleaseMat(&point_counts);
CV_MAT_ELEM(*intrinsic_matrix,float,0,0)=1.0f;
CV_MAT_ELEM(*intrinsic_matrix,float,1,1)=1.0f;
cvCalibrateCamera2(object_points2,image_points2,point_counts2,cvGetSize
(show_colie),
intrinsic_matrix,distortion_coeffs,NULL,NULL,0);
cout<<"摄像机内参数矩阵为:\n";
cout<<CV_MAT_ELEM(*intrinsic_matrix,float,0,0)<<" "<<CV_MAT_ELEM
(*intrinsic_matrix,float,0,1)
<<" "<<CV_MAT_ELEM(*intrinsic_matrix,float,0,2)
<<"\n\n";
cout<<CV_MAT_ELEM(*intrinsic_matrix,float,1,0)<<" "<<CV_MAT_ELEM
(*intrinsic_matrix,float,1,1)
<<" "<<CV_MAT_ELEM(*intrinsic_matrix,float,1,2)
<<"\n\n";
cout<<CV_MAT_ELEM(*intrinsic_matrix,float,2,0)<<" "<<CV_MAT_ELEM
(*intrinsic_matrix,float,2,1)
<<" "<<CV_MAT_ELEM(*intrinsic_matrix,float,2,2)
<<"\n\n";
cout<<"畸变系数矩阵为:\n";
cout<<CV_MAT_ELEM(*distortion_coeffs,float,0,0)<<" "<<CV_MAT_ELEM
(*distortion_coeffs,float,1,0)
<<" "<<CV_MAT_ELEM(*distortion_coeffs,float,2,0)
<<" "<<CV_MAT_ELEM(*distortion_coeffs,float,3,0)
<<" "<<CV_MAT_ELEM(*distortion_coeffs,float,4,0)
<<"\n\n";
cvSave("Intrinsics.xml",intrinsic_matrix);
cvSave("Distortion.xml",distortion_coeffs);
cout<<"摄像机矩阵、畸变系数向量已经分别存储在名为Intrinsics.xml、Distortion.xml文档
中\n\n";
CvMat * intrinsic=(CvMat *)cvLoad("Intrinsics.xml");
CvMat * distortion=(CvMat *)cvLoad("Distortion.xml");
IplImage * mapx=cvCreateImage(cvGetSize(show_colie),IPL_DEPTH_32F,1);
IplImage * mapy=cvCreateImage(cvGetSize(show_colie),IPL_DEPTH_32F,1);
cvInitUndistortMap(intrinsic,distortion,mapx,mapy);
cvNamedWindow("原始图像",1);
cvNamedWindow("非畸变图像",1);
cout<<"按‘E’键退出显示...\n\n";
while(show_colie)
{
IplImage * clone=cvCloneImage(show_colie);
cvShowImage("原始图像",show_colie);
cvRemap(clone,show_colie,mapx,mapy);
cvReleaseImage(&clone);
cvShowImage("非畸变图像",show_colie);
if(cvWaitKey(10)=='e')
{
break;
}
show_colie=cvQueryFrame(capture1);
}
return 0;
}
========
使用OpenCV进行摄像机标定
目录(?)
Cv照相机定标和三维重建
目录
针孔相机模型和变形
照相机定标
ProjectPoints2
FindHomography
CalibrateCamera2
FindExtrinsicCameraParams2
Rodrigues2
Undistort2
InitUndistortMap
FindChessboardCorners
DrawChessBoardCorners
姿态估计
CreatePOSITObject
POSIT
ReleasePOSITObject
CalcImageHomography
对极几何双视几何
FindFundamentalMat
ComputeCorrespondEpilines
ConvertPointsHomogenious
Cv照相机定标和三维重建
目录
1 针孔相机模型和变形
2 照相机定标
2.1 ProjectPoints2
2.2 FindHomography
2.3 CalibrateCamera2
2.4 FindExtrinsicCameraParams2
2.5 Rodrigues2
2.6 Undistort2
2.7 InitUndistortMap
2.8 FindChessboardCorners
2.9 DrawChessBoardCorners
3 姿态估计
3.1 CreatePOSITObject
3.2 POSIT
3.3 ReleasePOSITObject
3.4 CalcImageHomography
4 对极几何(双视几何)
4.1 FindFundamentalMat
4.2 ComputeCorrespondEpilines
4.3 ConvertPointsHomogenious
针孔相机模型和变形
这一节里的函数都使用针孔摄像机模型,这就是说,一幅视图是通过透视变换将三维空间中的点投影到
图像平面。投影公式如下:
s \cdot m' = A\cdot[R|t] \cdot M'或者
s\cdot \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \begin{bmatrix} fx & 0 & cx \\ 0 & fy &
cy \\ 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} r_{11} & r_{12} & r_{13} & t_{1} \\ r_
{21} & r_{22} & r_{23} & t_{2} \\ r_{31} & r_{32} & r_{33} & t_{3} \end{bmatrix} \cdot
\begin{bmatrix} X \\ Y \\ Z \\ 1 \end{bmatrix}
这里(X, Y, Z)是一个点的世界坐标,(u, v)是点投影在图像平面的坐标,以像素为单位。A被称作摄像
机矩阵,或者内参数矩阵。(cx, cy)是基准点(通常在图像的中心),fx, fy是以像素为单位的焦距。
所以如果因为某些因素对来自于摄像机的一幅图像升采样或者降采样,所有这些参数(fx, fy, cx和cy)
都将被缩放(乘或者除)同样的尺度。内参数矩阵不依赖场景的视图,一旦计算出,可以被重复使用(
只要焦距固定)。旋转-平移矩阵[R|t]被称作外参数矩阵,它用来描述相机相对于一个固定场景的运动
,或者相反,物体围绕相机的的刚性运动。也就是[R|t]将点(X, Y, Z)的坐标变换到某个坐标系,这个
坐标系相对于摄像机来说是固定不变的。上面的变换等价与下面的形式(z≠0):
\begin{bmatrix}x \\ y \\z \end{bmatrix} = R \cdot \begin{bmatrix}X \\ Y \\Z \end{bmatrix} +
t
x' = x / z
y' = y / z
u=fx \cdot x' + cx
v=fy \cdot y' + cy
真正的镜头通常有一些形变,主要的变形为径向形变,也会有轻微的切向形变。所以上面的模型可以扩
展为:
\begin{bmatrix}x \\ y \\z \end{bmatrix} = R \cdot \begin{bmatrix}X \\ Y \\Z \end{bmatrix} +
t
x' = x / z
y' = y / z
x'' = x' \cdot (1 + k_1 \cdot r^2 + k_2 \cdot r^4) + 2 \cdot p_1 \cdot x'\cdot y' + p_2
\cdot (r^2+2x'^2)
y'' = y' \cdot (1 + k_1 \cdot r^2 + k_2 \cdot r^4) + p_1 \cdot (r^2+2 \cdot y'^2) + 2 \cdot
p_2 \cdot x'\cdot y'
这里 r2 = x'2 + y'2
u = fx \cdot x'' + cx
v = fy \cdot y'' + cy
k1和k2是径向形变系数,p1和p1是切向形变系数。OpenCV中没有考虑高阶系数。形变系数跟拍摄的场景
无关,因此它们是内参数,而且与拍摄图像的分辨率无关。
后面的函数使用上面提到的模型来做如下事情:
给定内参数和外参数,投影三维点到图像平面。
给定内参数、几个三维点坐标和其对应的图像坐标,来计算外参数。
根据已知的定标模式,从几个角度(每个角度都有几个对应好的3D-2D点对)的照片来计算相机的外参数
和内参数。
照相机定标
ProjectPoints2
投影三维点到图像平面
void cvProjectPoints2( const CvMat* object_points, const CvMat* rotation_vector,
const CvMat* translation_vector, const CvMat* intrinsic_matrix,
const CvMat* distortion_coeffs, CvMat* image_points,
CvMat* dpdrot=NULL, CvMat* dpdt=NULL, CvMat* dpdf=NULL,
CvMat* dpdc=NULL, CvMat* dpddist=NULL );
object_points
物体点的坐标,为3xN或者Nx3的矩阵,这儿N是视图中的所有所有点的数目。
rotation_vector
旋转向量,1x3或者3x1。
translation_vector
平移向量,1x3或者3x1。
intrinsic_matrix
摄像机内参数矩阵A:\begin{bmatrix}fx & 0 & cx\\ 0 & fy & cy\\ 0&0&1\end{bmatrix}
distortion_coeffs
形变参数向量,4x1或者1x4,为[k1,k2,p1,p2]。如果是NULL,所有形变系数都设为0。
image_points
输出数组,存储图像点坐标。大小为2xN或者Nx2,这儿N是视图中的所有点的数目。
dpdrot
可选参数,关于旋转向量部分的图像上点的导数,Nx3矩阵。
dpdt
可选参数,关于平移向量部分的图像上点的导数,Nx3矩阵。
dpdf
可选参数,关于fx和fy的图像上点的导数,Nx2矩阵。
dpdc
可选参数,关于cx和cy的图像上点的导数,Nx2矩阵。
dpddist
可选参数,关于形变系数的图像上点的导数,Nx4矩阵。
函数cvProjectPoints2通过给定的内参数和外参数计算三维点投影到二维图像平面上的坐标。另外,这
个函数可以计算关于投影参数的图像点偏导数的雅可比矩阵。雅可比矩阵可以用在cvCalibrateCamera2
和cvFindExtrinsicCameraParams2函数的全局优化中。这个函数也可以用来计算内参数和外参数的反投
影误差。 注意,将内参数和(或)外参数设置为特定值,这个函数可以用来计算外变换(或内变换)。
[编辑]
FindHomography
计算两个平面之间的透视变换
void cvFindHomography( const CvMat* src_points,
const CvMat* dst_points,
CvMat* homography );
src_points
原始平面的点坐标,大小为2xN,Nx2,3xN或者 Nx3矩阵(后两个表示齐次坐标),这儿N表示点的数目
。
dst_points
目标平面的点坐标大小为2xN,Nx2,3xN或者 Nx3矩阵(后两个表示齐次坐标)。
homography
输出的3x3的homography矩阵。
函数cvFindHomography计算源平面和目标平面之间的透视变换H=\begin{bmatrix}h_{ij}\end{bmatrix}
_{i,j}.
s_i \begin{bmatrix}x'_i \\ y'_i \\ 1\end{bmatrix} \approx H \begin{bmatrix}x_i \\ y_i \\
1\end{bmatrix}
使得反投影错误最小:
\sum_i((x'_i-\frac{h_{11}x_i + h_{12}y_i + h_{13}}{h_{31}x_i + h_{32}y_i + h_{33}})^2+
(y'_i-\frac{h_{21}x_i + h_{22}y_i + h_{23}}{h_{31}x_i + h_{32}y_i + h_{33}})^2)
这个函数可以用来计算初始的内参数和外参数矩阵。由于Homography矩阵的尺度可变,所以它被规一化
使得h33 = 1
CalibrateCamera2
利用定标来计算摄像机的内参数和外参数
void cvCalibrateCamera2( const CvMat* object_points, const CvMat* image_points,
const CvMat* point_counts, CvSize image_size,
CvMat* intrinsic_matrix, CvMat* distortion_coeffs,
CvMat* rotation_vectors=NULL,
CvMat* translation_vectors=NULL,
int flags=0 );
object_points
定标点的世界坐标,为3xN或者Nx3的矩阵,这里N是所有视图中点的总数。
image_points
定标点的图像坐标,为2xN或者Nx2的矩阵,这里N是所有视图中点的总数。
point_counts
向量,指定不同视图里点的数目,1xM或者Mx1向量,M是视图数目。
image_size
图像大小,只用在初始化内参数时。
intrinsic_matrix
输出内参矩阵(A) \begin{bmatrix}fx & 0 & cx\\ 0 & fy & cy \\ 0&0&1\end{bmatrix},如果指定
CV_CALIB_USE_INTRINSIC_GUESS和(或)CV_CALIB_FIX_ASPECT_RATION,fx、 fy、 cx和cy部分或者全
部必须被初始化。
distortion_coeffs
输出大小为4x1或者1x4的向量,里面为形变参数[k1, k2, p1, p2]。
rotation_vectors
输出大小为3xM或者Mx3的矩阵,里面为旋转向量(旋转矩阵的紧凑表示方式,具体参考函数
cvRodrigues2)
translation_vectors
输出大小为3xM或Mx3的矩阵,里面为平移向量。
flags
不同的标志,可以是0,或者下面值的组合:
CV_CALIB_USE_INTRINSIC_GUESS - 内参数矩阵包含fx,fy,cx和cy的初始值。否则,(cx, cy)被初始化
到图像中心(这儿用到图像大小),焦距用最小平方差方式计算得到。注意,如果内部参数已知,没有
必要使用这个函数,使用cvFindExtrinsicCameraParams2则可。
CV_CALIB_FIX_PRINCIPAL_POINT - 主点在全局优化过程中不变,一直在中心位置或者在其他指定的位置
(当CV_CALIB_USE_INTRINSIC_GUESS设置的时候)。
CV_CALIB_FIX_ASPECT_RATIO - 优化过程中认为fx和fy中只有一个独立变量,保持比例fx/fy不变,
fx/fy的值跟内参数矩阵初始化时的值一样。在这种情况下, (fx, fy)的实际初始值或者从输入内存矩
阵中读取(当CV_CALIB_USE_INTRINSIC_GUESS被指定时),或者采用估计值(后者情况中fx和fy可能被
设置为任意值,只有比值被使用)。
CV_CALIB_ZERO_TANGENT_DIST – 切向形变参数(p1, p2)被设置为0,其值在优化过程中保持为0。
函数cvCalibrateCamera2从每个视图中估计相机的内参数和外参数。3维物体上的点和它们对应的在每个
视图的2维投影必须被指定。这些可以通过使用一个已知几何形状且具有容易检测的特征点的物体来实现
。这样的一个物体被称作定标设备或者定标模式,OpenCV有内建的把棋盘当作定标设备方法(参考
cvFindChessboardCorners)。目前,传入初始化的内参数(当CV_CALIB_USE_INTRINSIC_GUESS不被设置
时)只支持平面定标设备(物体点的Z坐标必须为全0或者全1)。不过3维定标设备依然可以用在提供初
始内参数矩阵情况。在内参数和外参数矩阵的初始值都计算出之后,它们会被优化用来减小反投影误差
(图像上的实际坐标跟cvProjectPoints2计算出的图像坐标的差的平方和)。
FindExtrinsicCameraParams2
计算指定视图的摄像机外参数
void cvFindExtrinsicCameraParams2( const CvMat* object_points,
const CvMat* image_points,
const CvMat* intrinsic_matrix,
const CvMat* distortion_coeffs,
CvMat* rotation_vector,
CvMat* translation_vector );
object_points
定标点的坐标,为3xN或者Nx3的矩阵,这里N是视图中的个数。
image_points
定标点在图像内的坐标,为2xN或者Nx2的矩阵,这里N是视图中的个数。
intrinsic_matrix
内参矩阵(A) \begin{bmatrix}fx & 0 & cx\\ 0 & fy & cy \\ 0&0&1\end{bmatrix}。
distortion_coeffs
大小为4x1或者1x4的向量,里面为形变参数[k1,k2,p1,p2]。如果是NULL,所有的形变系数都为0。
rotation_vector
输出大小为3x1或者1x3的矩阵,里面为旋转向量(旋转矩阵的紧凑表示方式,具体参考函数
cvRodrigues2)。
translation_vector
大小为3x1或1x3的矩阵,里面为平移向量。
函数cvFindExtrinsicCameraParams2使用已知的内参数和某个视图的外参数来估计相机的外参数。3维物
体上的点坐标和相应的2维投影必须被指定。这个函数也可以用来最小化反投影误差。
Rodrigues2
进行旋转矩阵和旋转向量间的转换
int cvRodrigues2( const CvMat* src, CvMat* dst, CvMat* jacobian=0 );
src
输入的旋转向量(3x1或者1x3)或者旋转矩阵(3x3)。
dst
输出的旋转矩阵(3x3)或者旋转向量(3x1或者1x3)
jacobian
可选的输出雅可比矩阵(3x9或者9x3),关于输入部分的输出数组的偏导数。
函数转换旋转向量到旋转矩阵,或者相反。旋转向量是旋转矩阵的紧凑表示形式。旋转向量的方向是旋
转轴,向量的长度是围绕旋转轴的旋转角。旋转矩阵R,与其对应的旋转向量r,通过下面公式转换:
\theta \leftarrow norm(r)
r \leftarrow r/\theta
R = \cos(\theta)I + (1-\cos(\theta))rr^T + \sin(\theta) \begin{bmatrix}0&-r_z&r_y\\
r_z&0&-r_x\\ -r_y&r_x&0\end{bmatrix}
反变换也可以很容易的通过如下公式实现:
\sin(\theta) \begin{bmatrix}0&-r_z&r_y\\ r_z&0&-r_x\\ -r_y&r_x&0\end{bmatrix} = \frac{R-
R^T}{2}
旋转向量是只有3个自由度的旋转矩阵一个方便的表示,这种表示方式被用在函数
cvFindExtrinsicCameraParams2和cvCalibrateCamera2内部的全局最优化中。
Undistort2
校正图像因相机镜头引起的变形
void cvUndistort2( const CvArr* src, CvArr* dst,
const CvMat* intrinsic_matrix,
const CvMat* distortion_coeffs );
src
原始图像(已经变形的图像)。只能变换32fC1的图像。
dst
结果图像(已经校正的图像)。
intrinsic_matrix
相机内参数矩阵,格式为 \begin{bmatrix}fx & 0 & cx\\ 0 & fy & cy\\ 0&0&1\end{bmatrix}。
distortion_coeffs
四个变形系数组成的向量,大小为4x1或者1x4,格式为[k1,k2,p1,p2]。
函数cvUndistort2对图像进行变换来抵消径向和切向镜头变形。相机参数和变形参数可以通过函数
cvCalibrateCamera2取得。使用本节开始时提到的公式,对每个输出图像像素计算其在输入图像中的位
置,然后输出图像的像素值通过双线性插值来计算。如果图像得分辨率跟定标时用得图像分辨率不一样
,fx、fy、cx和cy需要相应调整,因为形变并没有变化。
InitUndistortMap
计算形变和非形变图像的对应(map)
void cvInitUndistortMap( const CvMat* intrinsic_matrix,
const CvMat* distortion_coeffs,
CvArr* mapx, CvArr* mapy );
intrinsic_matrix
摄像机内参数矩阵(A) [fx 0 cx; 0 fy cy; 0 0 1].
distortion_coeffs
形变系数向量[k1, k2, p1, p2],大小为4x1或者1x4。
mapx
x坐标的对应矩阵。
mapy
y坐标的对应矩阵。
函数cvInitUndistortMap预先计算非形变对应-正确图像的每个像素在形变图像里的坐标。这个对应可
以传递给cvRemap函数(跟输入和输出图像一起)。
FindChessboardCorners
寻找棋盘图的内角点位置
int cvFindChessboardCorners( const void* image, CvSize pattern_size,
CvPoint2D32f* corners, int* corner_count=NULL,
int flags=CV_CALIB_CB_ADAPTIVE_THRESH );
image
输入的棋盘图,必须是8位的灰度或者彩色图像。
pattern_size
棋盘图中每行和每列角点的个数。
corners
检测到的角点
corner_count
输出,角点的个数。如果不是NULL,函数将检测到的角点的个数存储于此变量。
flags
各种操作标志,可以是0或者下面值的组合:
CV_CALIB_CB_ADAPTIVE_THRESH - 使用自适应阈值(通过平均图像亮度计算得到)将图像转换为黑白图
,而不是一个固定的阈值。
CV_CALIB_CB_NORMALIZE_IMAGE - 在利用固定阈值或者自适应的阈值进行二值化之前,先使用
cvNormalizeHist来均衡化图像亮度。
CV_CALIB_CB_FILTER_QUADS - 使用其他的准则(如轮廓面积,周长,方形形状)来去除在轮廓检测阶段
检测到的错误方块。
函数cvFindChessboardCorners试图确定输入图像是否是棋盘模式,并确定角点的位置。如果所有角点都
被检测到且它们都被以一定顺序排布(一行一行地,每行从左到右),函数返回非零值,否则在函数不
能发现所有角点或者记录它们地情况下,函数返回0。例如一个正常地棋盘图右8x8个方块和7x7个内角点
,内角点是黑色方块相互联通地位置。这个函数检测到地坐标只是一个大约地值,如果要精确地确定它
们的位置,可以使用函数cvFindCornerSubPix。
DrawChessBoardCorners
绘制检测到的棋盘角点
void cvDrawChessboardCorners( CvArr* image, CvSize pattern_size,
CvPoint2D32f* corners, int count,
int pattern_was_found );
image
结果图像,必须是8位彩色图像。
pattern_size
每行和每列地内角点数目。
corners
检测到地角点数组。
count
角点数目。
pattern_was_found
指示完整地棋盘被发现(≠0)还是没有发现(=0)。可以传输cvFindChessboardCorners函数的返回值。
当棋盘没有完全检测出时,函数cvDrawChessboardCorners以红色圆圈绘制检测到的棋盘角点;如果整个
棋盘都检测到,则用直线连接所有的角点。
姿态估计
CreatePOSITObject
初始化包含对象信息的结构
CvPOSITObject* cvCreatePOSITObject( CvPoint3D32f* points, int point_count );
points
指向三维对象模型的指针
point_count
对象的点数
函数 cvCreatePOSITObject 为对象结构分配内存并计算对象的逆矩阵。
预处理的对象数据存储在结构CvPOSITObject中,只能在OpenCV内部被调用,即用户不能直接读写数据结
构。用户只可以创建这个结构并将指针传递给函数。
对象是在某坐标系内的一系列点的集合,函数 cvPOSIT计算从照相机坐标系中心到目标点points[0] 之
间的向量。
一旦完成对给定对象的所有操作,必须使用函数cvReleasePOSITObject释放内存。
POSIT
执行POSIT算法
void cvPOSIT( CvPOSITObject* posit_object, CvPoint2D32f* image_points,
double focal_length,
CvTermCriteria criteria, CvMatr32f rotation_matrix,
CvVect32f translation_vector );
posit_object
指向对象结构的指针
image_points
指针,指向目标像素点在二维平面图上的投影。
focal_length
使用的摄像机的焦距
criteria
POSIT迭代算法程序终止的条件
rotation_matrix
旋转矩阵
translation_vector
平移矩阵.
函数 cvPOSIT 执行POSIT算法。图像坐标在摄像机坐标系统中给出。焦距可以通过摄像机标定得到。算
法每一次迭代都会重新计算在估计位置的透视投影。
两次投影之间的范式差值是对应点中的最大距离。如果差值过小,参数criteria.epsilon就会终止程序
。
ReleasePOSITObject
释放3D对象结构
void cvReleasePOSITObject( CvPOSITObject** posit_object );
posit_object
指向 CvPOSIT 结构指针的指针。
函数 cvReleasePOSITObject 释放函数 cvCreatePOSITObject分配的内存。
CalcImageHomography
计算长方形或椭圆形平面对象(例如胳膊)的Homography矩阵
void cvCalcImageHomography( float* line, CvPoint3D32f* center,
float* intrinsic, float* homography );
line
对象的主要轴方向,为向量(dx,dy,dz).
center
对象坐标中心 ((cx,cy,cz)).
intrinsic
摄像机内参数 (3x3 matrix).
homography
输出的Homography矩阵(3x3).
函数 cvCalcImageHomography 为从图像平面到图像平面的初始图像变化(defined by 3D oblong object
line)计算Homography矩阵。
对极几何(双视几何)
FindFundamentalMat
由两幅图像中对应点计算出基本矩阵
int cvFindFundamentalMat( const CvMat* points1,
const CvMat* points2,
CvMat* fundamental_matrix,
int method=CV_FM_RANSAC,
double param1=1.,
double param2=0.99,
CvMat* status=NULL);
points1
第一幅图像点的数组,大小为2xN/Nx2 或 3xN/Nx3 (N 点的个数),多通道的1xN或Nx1也可以。点坐标应
该是浮点数(双精度或单精度)。:
points2
第二副图像的点的数组,格式、大小与第一幅图像相同。
fundamental_matrix
输出的基本矩阵。大小是 3x3 或者 9x3 ,(7-点法最多可返回三个矩阵).
method
计算基本矩阵的方法
CV_FM_7POINT – 7-点算法,点数目= 7
CV_FM_8POINT – 8-点算法,点数目 >= 8
CV_FM_RANSAC – RANSAC 算法,点数目 >= 8
CV_FM_LMEDS - LMedS 算法,点数目 >= 8
param1
这个参数只用于方法RANSAC 或 LMedS 。它是点到对极线的最大距离,超过这个值的点将被舍弃,不用
于后面的计算。通常这个值的设定是0.5 or 1.0 。
param2
这个参数只用于方法RANSAC 或 LMedS 。 它表示矩阵正确的可信度。例如可以被设为0.99 。
status
具有N个元素的输出数组,在计算过程中没有被舍弃的点,元素被被置为1;否则置为0。这个数组只可以
在方法RANSAC and LMedS 情况下使用;在其它方法的情况下,status一律被置为1。这个参数是可选参
数。
对极几何可以用下面的等式描述:
p_2^T \cdot F \cdot p_1=0
其中 F 是基本矩阵,p1 和 p2 分别是两幅图上的对应点。
函数 FindFundamentalMat 利用上面列出的四种方法之一计算基本矩阵,并返回基本矩阵的值:没有找
到矩阵,返回0,找到一个矩阵返回1,多个矩阵返回3。 计算出的基本矩阵可以传递给函数
cvComputeCorrespondEpilines来计算指定点的对极线。
例子1:使用 RANSAC 算法估算基本矩阵。
int numPoints = 100;
CvMat* points1;
CvMat* points2;
CvMat* status;
CvMat* fundMatr;
points1 = cvCreateMat(2,numPoints,CV_32F);
points2 = cvCreateMat(2,numPoints,CV_32F);
status = cvCreateMat(1,numPoints,CV_32F);
/* 在这里装入对应点的数据... */
fundMatr = cvCreateMat(3,3,CV_32F);
int num = cvFindFundamentalMat(points1,points2,fundMatr,CV_FM_RANSAC,1.0,0.99,status);
if( num == 1 )
printf("Fundamental matrix was found\n");
else
printf("Fundamental matrix was not found\n");
例子2:7点算法(3个矩阵)的情况。
CvMat* points1;
CvMat* points2;
CvMat* fundMatr;
points1 = cvCreateMat(2,7,CV_32F);
points2 = cvCreateMat(2,7,CV_32F);
/* 在这里装入对应点的数据... */
fundMatr = cvCreateMat(9,3,CV_32F);
int num = cvFindFundamentalMat(points1,points2,fundMatr,CV_FM_7POINT,0,0,0);
printf("Found %d matrixes\n",num);
ComputeCorrespondEpilines
为一幅图像中的点计算其在另一幅图像中对应的对极线。
void cvComputeCorrespondEpilines( const CvMat* points,
int which_image,
const CvMat* fundamental_matrix,
CvMat* correspondent_lines);
points
输入点,是2xN 或者 3xN 数组 (N为点的个数)
which_image
包含点的图像指数(1 or 2)
fundamental_matrix
基本矩阵
correspondent_lines
计算对极点, 3xN数组
函数 ComputeCorrespondEpilines 根据外级线几何的基本方程计算每个输入点的对应外级线。如果点位
于第一幅图像(which_image=1),对应的对极线可以如下计算 :
l_2=F \cdot p_1
其中F是基本矩阵,p1 是第一幅图像中的点, l2 - 是与第二幅对应的对极线。如果点位于第二副图像
中 which_image=2),计算如下:
l_1=F^T \cdot p_2
其中p2 是第二幅图像中的点,l1 是对应于第一幅图像的对极线,每条对极线都可以用三个系数表示 a,
b, c:
a\cdot x + b\cdot y + c = 0
归一化后的对极线系数存储在correspondent_lines 中。
ConvertPointsHomogenious
Convert points to/from homogenious coordinates
void cvConvertPointsHomogenious( const CvMat* src, CvMat* dst );
src
The input point array, 2xN, Nx2, 3xN, Nx3, 4xN or Nx4 (where N is the number of points).
Multi-channel 1xN or Nx1 array is also acceptable.
dst
The output point array, must contain the same number of points as the input; The
dimensionality must be the same, 1 less or 1 more than the input, and also within 2..4.
The function cvConvertPointsHomogenious converts 2D or 3D points from/to homogenious
coordinates, or simply copies or transposes the array. In case if the input array
dimensionality is larger than the output, each point coordinates are divided by the last
coordinate:
(x,y[,z],w) -> (x',y'[,z'])
其中
x' = x/w
y' = y/w
z' = z/w (if output is 3D)
If the output array dimensionality is larger, an extra 1 is appended to each point.
(x,y[,z]) -> (x,y[,z],1)
Otherwise, the input array is simply copied (with optional tranposition) to the output.
Note that, because the function accepts a large variety of array layouts, it may report an
error when input/output array dimensionality is ambiguous. It is always safe to use the
function with number of points N>=5, or to use multi-channel Nx1 or 1xN arrays.
========
OPENCV版本的摄像机标定
摄像机的标定问题是机器视觉领域的入门问题,可以分为传统的摄像机定标方法和摄像机自定标方法。
定标的方法有很多中常见的有:Tsai(传统)和张正友(介于传统和自定标)等,
摄像机成像模型和四个坐标系(通用原理)。
OPENCV版本的摄像机标定(张正友)
摄像机模型采用经典的小孔模型,如图中Oc(光心),像面π表示的是视野平面,其到光心的距离为f(
镜头焦距)。
四个坐标系分别为:世界坐标系(Ow),摄像机坐标系(Oc),图像物理坐标系(O1,单位mm),图像
像素坐标系(O,位于视野平面的左上角,单位pix)。
空间某点P到其像点p的坐标转换过程主要是通过这四套坐标系的三次转换实现的,首先将世界坐标系进
行平移和转换得到摄像机坐标系,然后根据三角几何变换得到图像物理坐标系,最后根据像素和公制单
位的比率得到图像像素坐标系。(实际的应用过程是这个的逆过程,即由像素长度获知实际的长度)。
ps:通过摄像头的标定,可以得到视野平面上的mm/pix分辨率,对于视野平面以外的物体还是需要通过
坐标转换得到视野平面上。
转化的过程和公式参见:摄像机标定原理(关键是三个坐标系).ppt
OPENCV版本的摄像机标定(张正友)
2 张正友算法的原理
zhang法通过对一定标板在不同方向多次(三次以上)完整拍照,不需要知道定标板的运动方式。直接获
得相机的内参(参考文献上矩阵A)和畸变系数。该标定方法精度高于自定标法,且不需要高精度的定位
仪器。
ZHANG的算法包含两个模型:一.经典针孔模型,包含四个坐标系,二畸变模型(这个来源未知)
OPENCV版本的摄像机标定(张正友)
公式三项依次表示,径向畸变,切线畸变,薄棱镜畸变。OPENCV中函数只能给出k1,k2,p1,p2。
还存在另外一种畸变模型,见《摄像机标定算法库的设计和试验验证》一文26 page。(也不知道出处)
OPENCV版本的摄像机标定(张正友)
ps:单从公式推导的过程来看,第一组公式等号右边应该是U0。
Key:这个方程怎么求?x,y 代表理想的图像坐标(mm),是未知数(不太可能是已知数,xike说的是不
考虑畸变的投影值,这个就太简单了)。
*************************************************************************************
#include "cvut.h"
#include <iostream>
#include <fstream>
#include <string>
using namespace cvut;
using namespace std;
void main() {
ifstream fin("calibdata.txt");
ofstream fout("caliberation_result.txt");
cout<<"开始提取角点………………";
int image_count=0;
CvSize image_size;
CvSize board_size = cvSize(5,7);
CvPoint2D32f * image_points_buf = new CvPoint2D32f[board_size.width*board_size.height];
Seq<CvPoint2D32f> image_points_seq;
string filename;
while (std::getline(fin,filename))
{
cout<<"\n 将鼠标焦点移到标定图像所在窗口并输入回车进行下一幅图像的角点提取 \n";
image_count++;
int count;
Image<uchar> view(filename);
if (image_count == 1)
{
image_size.width = view.size().width;
image_size.height = view.size().height;
}
if (0 == cvFindChessboardCorners( view.cvimage, board_size,
image_points_buf, &count, CV_CALIB_CB_ADAPTIVE_THRESH ))
{
cout<<"can not find chessboard corners!\n";
exit(1);
}
else {
Image<uchar> view_gray(view.size(),8,1);
rgb2gray(view,view_gray);
cvFindCornerSubPix( view_gray.cvimage, image_points_buf, count, cvSize(11,11),
cvSize(-1,-1), cvTermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 30, 0.1 ));
image_points_seq.push_back(image_points_buf,count);
cvDrawChessboardCorners( view.cvimage, board_size, image_points_buf, count, 1);
view.show("calib");
cvWaitKey();
view.close();
}
}
delete []image_points_buf;
cout<<"角点提取完成!\n"<<endl;
cout<<"开始定标………………"<<"\n"<<endl;
CvSize square_size = cvSize(10,10);
Matrix<double> object_points(1,board_size.width*board_size.height*image_count,3);
Matrix<double> image_points(1,image_points_seq.cvseq->total,2);
Matrix<int> point_counts(1,image_count,1);
Matrix<double> intrinsic_matrix(3,3,1);
Matrix<double> distortion_coeffs(1,4,1);
Matrix<double> rotation_vectors(1,image_count,3);
Matrix<double> translation_vectors(1,image_count,3);
int i,j,t;
for (t=0;t<image_count;t++) {
for (i=0;i<board_size.height;i++) {
for (j=0;j<board_size.width;j++) {
object_points(0,t*board_size.height*board_size.width + i*board_size.width + j,0) =
i*square_size.width;
object_points(0,t*board_size.height*board_size.width + i*board_size.width + j,1) =
j*square_size.height;
object_points(0,t*board_size.height*board_size.width + i*board_size.width + j,2) = 0;
}
}
}
char str[10];
itoa(image_points_seq.cvseq->total,str,10);
cout<<str<<"\n"<<endl;
for (i=0;i<image_points_seq.cvseq->total;i++)
{
image_points(0,i,0) = image_points_seq[i].x;
image_points(0,i,1) = image_points_seq[i].y;
}
for (i=0;i<image_count;i++)
point_counts(0,i) = board_size.width*board_size.height;
cvCalibrateCamera2(object_points.cvmat,
image_points.cvmat,
point_counts.cvmat,
image_size,
intrinsic_matrix.cvmat,
distortion_coeffs.cvmat,
rotation_vectors.cvmat,
translation_vectors.cvmat,
0);
cout<<"定标完成!\n";
cout<<"标定结果显示\n";
cout<<"*************************************************\n";
cout<<"相机内参intrinsic_matrix\n";
for(int h=0;h<3;h++)
{
cout<<"X:"<<intrinsic_matrix(h,0,0)<<"\tY:"<<intrinsic_matrix(h,1,0)
<<"\tZ:"<<intrinsic_matrix(h,2,0)<<"\n";
}
cout<<"\n畸变系数:distortion_coeffs\n";
for(int ndis=0;ndis<4;ndis++)
{
cout<<distortion_coeffs(0,ndis,0)<<"\\";
}
cout<<"\n";
cout<<"\nrotation_vectors\n";
for(int rot=0;rot<7;rot++)
{
cout<<"X:"<<rotation_vectors(0,rot,0)<<"\tY:"<<rotation_vectors(0,rot,1)
<<"\tZ:"<<rotation_vectors(0,rot,2)<<"\n";
}
cout<<"\ntranslation_vectors\n";
for(i=0;i<7;i++)
{
cout<<"第"<<i+1<<"张图"<<"\tX:"<<translation_vectors(0,i,0)<<"\tY:"<<translation_vectors
(0,i,1)<<"\tZ:"<<translation_vectors(0,i,2)<<"\n";
}
cout<<"***************************************************\n";
cout<<"开始评价定标结果………………\n";
double total_err = 0.0;
double err = 0.0;
Matrix<double> image_points2(1,point_counts(0,0,0),2);
int temp_num = point_counts(0,0,0);
cout<<"\t每幅图像的定标误差:\n";
fout<<"每幅图像的定标误差:\n";
for (i=0;i<image_count;i++)
{
cvProjectPoints2(object_points.get_cols(i * point_counts(0,0,0),(i+1)*point_counts
(0,0,0)-1).cvmat,
rotation_vectors.get_col(i).cvmat,
translation_vectors.get_col(i).cvmat,
intrinsic_matrix.cvmat,
distortion_coeffs.cvmat,
image_points2.cvmat,
0,0,0,0);
err = cvNorm(image_points.get_cols(i*point_counts(0,0,0),(i+1)*point_counts(0,0,0)-
1).cvmat,
image_points2.cvmat,
CV_L1);
total_err += err/=point_counts(0,0,0);
cout<<"******************************************************************\n";
cout<<"\t\t第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<'\n';
fout<<"\t第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<'\n';
cout<<"显示image_point2\n";
for(int ih=0;ih<7;ih++)
{
cout<<"X:"<<image_points2(0,ih,0)<<"\tY:"<<image_points2(0,ih,1)<<"\n";
}
cout<<"显示object_Points\n";
for(int iw=0;iw<7;iw++)
{
cout<<"X:"<<image_points.get_cols(i*point_counts(0,0,0),(i+1)*point_counts(0,0,0)-1)
(0,iw,0)
<<"\tY:"<<image_points.get_cols(i*point_counts(0,0,0),(i+1)*point_counts(0,0,0)-1)
(0,iw,1)<<"\n";
}
}
cout<<"\t总体平均误差:"<<total_err/image_count<<"像素"<<'\n';
fout<<"总体平均误差:"<<total_err/image_count<<"像素"<<'\n'<<'\n';
cout<<"评价完成!\n";
cout<<"开始保存定标结果………………";
Matrix<double> rotation_vector(3,1);
Matrix<double> rotation_matrix(3,3);
fout<<"相机内参数矩阵:\n";
fout<<intrinsic_matrix<<'\n';
fout<<"畸变系数:\n";
fout<<distortion_coeffs<<'\n';
for (i=0;i<image_count;i++) {
fout<<"第"<<i+1<<"幅图像的旋转向量:\n";
fout<<rotation_vectors.get_col(i);
for (j=0;j<3;j++) {
rotation_vector(j,0,0) = rotation_vectors(0,i,j);
}
cvRodrigues2(rotation_vector.cvmat,rotation_matrix.cvmat);
fout<<"第"<<i+1<<"幅图像的旋转矩阵:\n";
fout<<rotation_matrix;
fout<<"第"<<i+1<<"幅图像的平移向量:\n";
fout<<translation_vectors.get_col(i)<<'\n';
}
cout<<"完成保存\n";
}
========
OpenCV: 摄像机标定原理
#include "stdafx.h"
#include "cv.h"
#include "highgui.h"
#include <string>
#include <iostream>
using namespace std;
int main()
{
int cube_length=7;
CvCapture* capture;
capture=cvCreateCameraCapture(0);
if(capture==0){
printf("无法捕获摄像头设备!\n\n");
return 0;
}else{
printf("捕获摄像头设备成功!!\n\n");
}
IplImage* frame;
cvNamedWindow("摄像机帧截取窗口",1); //cvNamedWindow()函数用于在屏幕上创建一个窗口,
将被显示的图像包含于该窗口中。函数的第一个参数指定了该窗口的窗口标题,如果要使用HighGUI库所
提供的其他函数与该窗口进行交互时,我们将通过该参数值引用这个窗口。
printf("按“C”键截取当前帧并保存为标定图片...\n按“Q”键退出截取帧过程...\n\n");
int number_image=1;
char *str1;
str1=".jpg";
char filename[20]="";
while(true)
{
frame=cvQueryFrame(capture);// 从摄像头或者文件中抓取并返回一帧
if(!frame)
break;
cvShowImage("摄像机帧截取窗口",frame); //图像显示
if(cvWaitKey(10)=='c'){
sprintf_s (filename,"%d.jpg",number_image); // int sprintf_s( char *buffer,
size_t sizeOfBuffer, const char *format [, argument] ... );
这个函数的主要作用是将若干个argument按照format格式存到buffer中
cvSaveImage(filename,frame);//保存
cout<<"成功获取当前帧,并以文件名"<<filename<<"保存...\n\n";
printf("按“C”键截取当前帧并保存为标定图片...\n按“Q”键退出截取帧过
程...\n\n");
number_image++;
}else if(cvWaitKey(10)=='q'){
printf("截取图像帧过程完成...\n\n");
cout<<"共成功截取"<<--number_image<<"帧图像!!\n\n";
break;
}
}
cvReleaseImage(&frame); //释放图像
cvDestroyWindow("摄像机帧截取窗口");
IplImage * show;
cvNamedWindow("RePlay",1);
int a=1;
int number_image_copy=number_image;
CvSize board_size=cvSize(7,7); // Cvsizes:OpenCV的基本数据类型之一。表示矩阵框大小,
以像素为精度。与CvPoint结构类似,但数据成员是integer类型的width和height。
//cvSize是
int board_width=board_size.width;
int board_height=board_size.height;
int total_per_image=board_width*board_height;
CvPoint2D32f * image_points_buf = new CvPoint2D32f[total_per_image];
CvMat * image_points=cvCreateMat(number_image*total_per_image,2,CV_32FC1);//图像坐标
系
CvMat * object_points=cvCreateMat(number_image*total_per_image,3,CV_32FC1);//世界坐
标系
CvMat * point_counts=cvCreateMat(number_image,1,CV_32SC1);//
CvMat * intrinsic_matrix=cvCreateMat(3,3,CV_32FC1);//
CvMat * distortion_coeffs=cvCreateMat(5,1,CV_32FC1);
int count;
int found;
int step;
int successes=0;
while(a<=number_image_copy){
sprintf_s (filename,"%d.jpg",a);
show=cvLoadImage(filename,-1);
found=cvFindChessboardCorners(show,board_size,image_points_buf,&count,
CV_CALIB_CB_ADAPTIVE_THRESH|CV_CALIB_CB_FILTER_QUADS);
if(found==0){
cout<<"第"<<a<<"帧图片无法找到棋盘格所有角点!\n\n";
cvNamedWindow("RePlay",1);
cvShowImage("RePlay",show);
cvWaitKey(0);
}else{
cout<<"第"<<a<<"帧图像成功获得"<<count<<"个角点...\n";
cvNamedWindow("RePlay",1);
IplImage * gray_image= cvCreateImage(cvGetSize(show),8,1); //创建头并
分配数据IplImage* cvCreateImage( CvSize size, int depth, int channels ); depth 图像元素的位
深度
cvCvtColor(show,gray_image,CV_BGR2GRAY); // cvCvtColor(...),是Opencv里
的颜色空间转换函数,可以实现rgb颜色向HSV,HSI等颜色空间的转换,也可以转换为灰度图像。
cout<<"获取源图像灰度图过程完成...\n";
cvFindCornerSubPix(gray_image,image_points_buf,count,cvSize
(11,11),cvSize(-1,-1),由于非常接近P的像素产生了很小的特征值,所以这个自相关矩阵并不总是可逆
的。为了解决这个问题,一般可以简单地剔除离P点非常近的像素。输入参数:ero_zone定义了一个禁区(
与win相似,但通常比win小),这个区域在方程组以及自相关矩阵中不被考虑。如果不需要这样一个禁区
,则zero_zone应设置为cvSize(-1,-1)0
cvTermCriteria(CV_TERMCRIT_EPS
+CV_TERMCRIT_ITER,30,0.1));
cout<<"灰度图亚像素化过程完成...\n";
cvDrawChessboardCorners(show,board_size,image_points_buf,count,found);
cout<<"在源图像上绘制角点过程完成...\n\n";
cvShowImage("RePlay",show);
cvWaitKey(0);
}
if(total_per_image==count){
step=successes*total_per_image;
for(int i=step,j=0;j<total_per_image;++i,++j){
CV_MAT_ELEM(*image_points,float,i,0)=image_points_buf[j].x; //
opencv中用来访问矩阵每个元素的宏,这个宏只对单通道矩阵有效,多通道CV_MAT_ELEM( matrix,
elemtype, row, col )参数 matrix:要访问的矩阵 elemtype:矩阵元素的类型 row:所要访
问元素的行数 col:所要访问元素的列数
CV_MAT_ELEM(*image_points,float,i,1)=image_points_buf[j].y;//
求完每个角点横纵坐标值都存在image_point_buf里
CV_MAT_ELEM(*object_points,float,i,0)=(float)(j/cube_length);
CV_MAT_ELEM(*object_points,float,i,1)=(float)(j%cube_length);
CV_MAT_ELEM(*object_points,float,i,2)=0.0f;
}
CV_MAT_ELEM(*point_counts,int,successes,0)=total_per_image;
successes++;
}
a++;
}
cvReleaseImage(&show);
cvDestroyWindow("RePlay");
cout<<"*********************************************\n";
cout<<number_image<<"帧图片中,标定成功的图片为"<<successes<<"帧...\n";
cout<<number_image<<"帧图片中,标定失败的图片为"<<number_image-successes<<"帧...\n
\n";
cout<<"*********************************************\n\n";
cout<<"按任意键开始计算摄像机内参数...\n\n";
CvCapture* capture1;
capture1=cvCreateCameraCapture(0);
IplImage * show_colie;
show_colie=cvQueryFrame(capture1);
CvMat * object_points2=cvCreateMat(successes*total_per_image,3,CV_32FC1); // OpenCV
中重要的矩阵变换函数,使用方法为cvMat* cvCreateMat ( int rows, int cols, int type ); 这里
type可以是任何预定义类型,预定义类型的结构如下:CV_<bit_depth> (S|U|F)C<number_of_channels>
。
CvMat * image_points2=cvCreateMat(successes*total_per_image,2,CV_32FC1);
CvMat * point_counts2=cvCreateMat(successes,1,CV_32SC1);
for(int i=0;i<successes*total_per_image;++i){
CV_MAT_ELEM(*image_points2,float,i,0)=CV_MAT_ELEM(*image_points,float,i,0);//
用来存储角点提取成功的图像的角点
CV_MAT_ELEM(*image_points2,float,i,1)=CV_MAT_ELEM(*image_points,float,i,1);
CV_MAT_ELEM(*object_points2,float,i,0)=CV_MAT_ELEM(*object_points,float,i,0);
CV_MAT_ELEM(*object_points2,float,i,1)=CV_MAT_ELEM(*object_points,float,i,1);
CV_MAT_ELEM(*object_points2,float,i,2)=CV_MAT_ELEM(*object_points,float,i,2);
}
for(int i=0;i<successes;++i){
CV_MAT_ELEM(*point_counts2,int,i,0)=CV_MAT_ELEM(*point_counts,int,i,0);
}
cvReleaseMat(&object_points);
cvReleaseMat(&image_points);
cvReleaseMat(&point_counts);
CV_MAT_ELEM(*intrinsic_matrix,float,0,0)=1.0f;
CV_MAT_ELEM(*intrinsic_matrix,float,1,1)=1.0f;
cvCalibrateCamera2(object_points2,image_points2,point_counts2,cvGetSize(show_colie),
intrinsic_matrix,distortion_coeffs,NULL,NULL,0);
cout<<"摄像机内参数矩阵为:\n";
cout<<CV_MAT_ELEM(*intrinsic_matrix,float,0,0)<<" "<<CV_MAT_ELEM
(*intrinsic_matrix,float,0,1)
<<" "<<CV_MAT_ELEM(*intrinsic_matrix,float,0,2)
<<"\n\n";
cout<<CV_MAT_ELEM(*intrinsic_matrix,float,1,0)<<" "<<CV_MAT_ELEM
(*intrinsic_matrix,float,1,1)
<<" "<<CV_MAT_ELEM(*intrinsic_matrix,float,1,2)
<<"\n\n";
cout<<CV_MAT_ELEM(*intrinsic_matrix,float,2,0)<<" "<<CV_MAT_ELEM
(*intrinsic_matrix,float,2,1)
<<" "<<CV_MAT_ELEM(*intrinsic_matrix,float,2,2)
<<"\n\n";
cout<<"畸变系数矩阵为:\n";
cout<<CV_MAT_ELEM(*distortion_coeffs,float,0,0)<<" "<<CV_MAT_ELEM
(*distortion_coeffs,float,1,0)
<<" "<<CV_MAT_ELEM(*distortion_coeffs,float,2,0)
<<" "<<CV_MAT_ELEM(*distortion_coeffs,float,3,0)
<<" "<<CV_MAT_ELEM(*distortion_coeffs,float,4,0)
<<"\n\n";
cvSave("Intrinsics.xml",intrinsic_matrix);
cvSave("Distortion.xml",distortion_coeffs);
cout<<"摄像机矩阵、畸变系数向量已经分别存储在名为Intrinsics.xml、Distortion.xml文档
中\n\n";
CvMat * intrinsic=(CvMat *)cvLoad("Intrinsics.xml");
CvMat * distortion=(CvMat *)cvLoad("Distortion.xml");
IplImage * mapx=cvCreateImage(cvGetSize(show_colie),IPL_DEPTH_32F,1);
IplImage * mapy=cvCreateImage(cvGetSize(show_colie),IPL_DEPTH_32F,1);
cvInitUndistortMap(intrinsic,distortion,mapx,mapy);
cvNamedWindow("原始图像",1);
cvNamedWindow("非畸变图像",1);
cout<<"按‘E’键退出显示...\n\n";
while(show_colie){
IplImage * clone=cvCloneImage(show_colie);
cvShowImage("原始图像",show_colie);
cvRemap(clone,show_colie,mapx,mapy);
cvReleaseImage(&clone);
cvShowImage("非畸变图像",show_colie);
if(cvWaitKey(10)=='e'){
break;
}
show_colie=cvQueryFrame(capture1);
}
return 0;
}
各标定步骤实现方法
1 计算标靶平面与图像平面之间的映射矩阵
计算标靶平面与图像平面之间的映射矩阵,计算映射矩阵时不考虑摄像机的成像模型,只是根据平面标
靶坐标点和对应的图像坐标点的数据,利用最小二乘方法计算得到[ [ix] ]
.2 求解摄像机参数矩阵
由计算得到的标靶平面和图像平面的映射矩阵得到与摄像机内部参数相关的基本方程关系,求解方程得
到摄像机内部参数,考虑镜头的畸变模型,将上述解方程获
得的内部参数作为初值,进行非线性优化搜索,从而计算出所有参数的准确值 [[x] ]
.3 求解左右两摄像机之间的相对位置关系
设双目视觉系统左右摄像机的外部参数分别为Rl, Tl,与Rr, Tr,,即Rl, Tl表示左摄像机与世界坐标系
的相对位置,Rr, Tr表示右摄像机与世界坐标系的相对位置 [[xi] ]。因此,对于空间任意一点,如果
在世界坐标系、左摄像机坐标系和右摄像机坐标系中的坐标分别为Xw,, Xl , Xr,则有:Xl=RlXw+Tl
;Xr=RrXw+Tr .因此,两台摄像机之间的相对几何关系可以由下式表示R=RrRl-1 ;T=Tr- RrRl-1Tl
在实际标定过程中,由标定靶对两台摄像机同时进行摄像标定,以分别获得两台摄像机的内、外参数,
从而不仅可以标定出摄像机的内部参数,还可以同时标定出双目视觉系统的结构参数 [xii] 。由单摄像
机标定过程可以知道,标定靶每变换一个位置就可以得到一组摄像机外参数:Rr,Tr,与Rl, Tl,因此,由
公式R=RrRl-1 ;T=Tr- RrRl-1Tl,可以得到一组结构参数R和T
========
关于基于OPENCV摄像机标定的一点感受
从一月到现在一直有一个问题比较烦人,就是这相机标定的问题,这个问题使用相机的人基本都会先
去做这个,很多人在这问题上做研究,有很多种,基于各种模型的,而且算法都比较成熟。我们一开始
就把问题想简单了,因为OPENCV说里面已经写好了,直接掉出来用就行了。实现确实如此,从开始就出
OPENCV,用一周的之间就写了出来代码,拿别人的相片一看,呵呵呵,还行,结果和别人的差不多,那
是就像这时到此结束了.....
真的吗?我们自己的相机回来的,拍了写照片,发现结果不是一般的不稳定,用6x7的棋盘拍些照片是
一种结果,换7x7的,就是另外一种结果而且差异很大,郁闷了。就觉得是自己拍图片的问题,我们试着
用更大的棋盘,拍跟多的图片结果还是一样不稳定。觉得自己受骗了,OPENCV真不稳定。
开始去看张正友的算法,试着自己去写,这就是这个月做得事,张正友的算法第一步使用线性模型求
出H矩阵、估算优化、计算内部参数、最大貌似优化、计算畸变参数、使用最大貌似优化参数(大概是这
样),这些就不说了,张正友对操作的过程说的很详细,相片的拍摄的角度,相片的数量,噪声的影像
、图片的平整等等,可以看出拍摄图片对精度的影像是很大的。
1、棋盘的排放
算法是基于2D模型的,如果棋盘摆放的不平整,肯定会造成很大的影像。
原文:
点击看大图
平整度的影像远远大于噪声的影像
点击看大图
张正友在做这个试验的时候使用:
点击看大图
2.图片数目的影像
点击看大图
事实上图片的数目多了会很好的
3.图片的角度:这里注意的是图片的角度是45度最好,但是太大的角度对于角点提取的精度影像比较大
,所以保持在45度以内比较好
点击看大图
4.除上面的因素以外,很所有光学一同一样,光线对图片的影像也很大,这个也要注意
还有,似乎我也不知道,因为标定的问题我还没有解决,只希望大家少走点弯路
========