物体尺寸检测项目--相机标定(像素坐标系/实际程序关键理解)

相机标定的三个坐标系变化看得比较吃力(线性代数都还回去了)。

历时3个礼拜,断断续续今天终于算是成功跑出程序,做个小结。

一、坐标系的理解

还是有必要理解一下这个坐标系的关系;一共四个:分别是世界坐标系、像素坐标系、图像物理坐标系、相机坐标系;

首先要清楚三个坐标系分别是什么:看下图:

uv是像素坐标系,xy是图像物理坐标系:二者的关系有两个:xy的原点记为(U0、V0);像素大小转换关系:后面的表达式

xwywzw是世界坐标系,xcyc是相机坐标系(可以认为就是相机的透镜,反映了相机相对于世界坐标系的位置和角度)

ok! 为什么要知道这些坐标系呢?因为给一张图片,我们可以知道一张图片上的标记点在世界坐标系和像素坐标系下的坐标!然后他们两个本身存在一个矩阵转换关系,可以求得矩阵,这个矩阵就是相机的内参,进而可以进行畸变矫正。

这样我们用来测量长度的照片测出来的尺寸才会比较准确!

那这个矩阵长什么样子?(治好了多年的颈椎病)

 二、标定开始

1、先打印一张棋盘

2、各种角度拍7张照片

3、量好小方块的实际尺寸mm--然后量整个棋盘的大小

代码如下:我什么时候可以完整写出这么牛逼的代码;

#include <opencv2/opencv.hpp>

#include <highgui.h> 
#include <cxcore.h> 

#include <stdio.h>
using namespace cv;
using namespace std;

int main(int argc, char* argv[]){
	int cube_length = 13;//实际小方块大小
	int cam_Dx =182; //整个棋盘横轴方向长度
	int cam_Dy = 182; //整个棋盘纵轴方向长度
	int number_image = 7;//照片数量
	int a = 1;
	int number_image_copy = 7;
	CvSize board_size = cvSize(13, 13);
	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(4, 1, CV_32FC1); //畸变系数向量

	char picName[7][10] = { "1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg", "6.jpg", "7.jpg" };
	IplImage * show;

	int count;
	int found;
	int step;
	int successes = 0;

	
	while (a <= number_image_copy){
		show = cvLoadImage(picName[a - 1], -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";

			IplImage * gray_image = cvCreateImage(cvGetSize(show), 8, 1);

			cvCvtColor(show, gray_image, CV_BGR2GRAY);

			cout << "获取源图像灰度图过程完成...\n";

			//要先灰度化才可以,使用FindCornerSubPix
			//这一步是为了更加精确确定像素坐标,像素坐标放在了image_points_buf,十分关键
			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";
		}
	
		if (total_per_image == count){
			step = successes*total_per_image;
			for (int i = step, j = 0; j<total_per_image; ++i, ++j){
				//写入矩阵,image_points是像素坐标
				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;
				//写入矩阵,object_points是世界系坐标--你要明白cam—dx实际上等于cube——length*横坐标方向有多少个方块。
				CV_MAT_ELEM(*object_points, float, i, 0) = (float)((j / cube_length) * cam_Dx);
				CV_MAT_ELEM(*object_points, float, i, 1) = (float)((j%cube_length) * cam_Dy);
				CV_MAT_ELEM(*object_points, float, i, 2) = 0.0f;
			}
			CV_MAT_ELEM(*point_counts, int, successes, 0) = total_per_image;
			successes++;
		}
		a++;
	}
	cout << "*********************************************\n";
	cout << number_image << "帧图片中,标定成功的图片为" << successes << "帧...\n";
	cout << number_image << "帧图片中,标定失败的图片为" << number_image - successes << "帧...\n\n";
	cout << "*********************************************\n\n";



	IplImage * show_colie;
	show_colie = show;


	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);

	//输出文本
	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中
	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);

	//自己重新拍一张照片命名为8.jpg
	show_colie = cvLoadImage("8.jpg");
	IplImage * clone = cvCloneImage(show_colie);
	cvShowImage("原始图像", show_colie);

	//这个就是用来校准的函数
	cvRemap(clone, show_colie, mapx, mapy);
	cvReleaseImage(&clone);
	cvShowImage("非畸变图像", show_colie);

	cvWaitKey(0);

	return 0;
}

 ok!在程序后面这样我们得到了MAPX,MAPY两个矩阵,这就是用来校准相片的参数,以后直接调用就ok了;

PS:发现自己C++基础很薄弱啊!在考虑要认真学一遍。C和C++还是不太一样的。

下一步我要开始,实际展开项目了!12号开始放9天假,要好好学习。完成项目!!

  • 10
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
像素坐标转换为相机坐标系需要知道相机内参和外参。相机内参包括相机的焦距、主点等参数,相机外参包括相机的旋转和平移矩阵。 假设像素坐标的原点为图像的左上角,x轴向右延伸,y轴向下延伸,相机坐标系的原点为相机的光心位置,x轴指向相机的右侧,y轴指向相机的上方,z轴指向相机的前方。 以下是像素坐标到相机坐标系的转换公式: 1. 将像素坐标的点转换为归一化平面坐标系的点 $$\begin{bmatrix}u \\ v \\ 1\end{bmatrix} = \begin{bmatrix} 1/f_x & 0 & -c_x/f_x \\ 0 & 1/f_y & -c_y/f_y \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}x \\ y \\ 1\end{bmatrix}$$ 其中,$u$和$v$是归一化平面坐标系下的坐标,$x$和$y$是像素坐标下的坐标,$f_x$和$f_y$是相机的焦距,$c_x$和$c_y$是主点的坐标。 2. 将归一化平面坐标系的点转换为相机坐标系的点 $$\begin{bmatrix}X_c \\ Y_c \\ Z_c \\ 1\end{bmatrix} = \begin{bmatrix} R_{11} & R_{12} & R_{13} & T_x \\ R_{21} & R_{22} & R_{23} & T_y \\ R_{31} & R_{32} & R_{33} & T_z \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}u \\ v \\ 1 \\ d\end{bmatrix}$$ 其中,$X_c$、$Y_c$、$Z_c$是相机坐标系下的坐标,$R$是相机的旋转矩阵,$T$是相机的平移矩阵,$d$是归一化平面坐标系的深度值。 需要注意的是,以上公式中的旋转矩阵和平移矩阵需要通过相机标定获得,一般使用棋盘格标定法或者三维物体标定法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值