相机标定及图片描点测距

一、相机标定

	1.棋盘格做相机标定
		自制棋盘格如图所示:

在这里插入图片描述
如图是一个(7,6)的黑白棋盘格,在这里运用cv::findChessboardCorners方法获取点坐标,如图:
在这里插入图片描述
具体代码如下:

	std::vector< cv::Point3f > worldPoints;
	for (int j = 0; j < colCount; ++j)
	{
		for (int k = 0; k < rowCount; ++k)
		{
			worldPoints.push_back(cv::Point3f(k*1.0, j*1.0, 0.0f));
		}
	}
	std::vector< cv::Point2f > corners;
	std::vector< std::vector< cv::Point2f > > corners2;
	std::vector< std::vector< cv::Point3f > > worldPoints2;
	for (int i = 0; i < imageCount; ++i)
	{
	// 输入图片,图片内角点数(不算棋盘格最外层的角点),输出角点,求解方式
		bool_t found = cv::findChessboardCorners(images[i], cv::Size(rowCount, colCount), corners, cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE);
	// 将找到的角点放入容器中
		corners2.push_back(corners);
	//世界坐标系的二维vector 放入三维vector
		worldPoints2.push_back(worldPoints);
	}
		注意这里的rowCount和colCount参数是棋盘格角点的横纵方向的个数,一定要与图片一致,如果想要画出角点,
	就用cv::drawChessboardCorners(image, Size(7, 6), corners, found);的方法画出图像中的角点。
	得到角点后,接下来就是矫正了,接下来使用OpenCV的cv::calibrateCamera方法。
	cv::calibrateCamera(worldPoints2, corners2, images[0].size(), cameraMatrix_, distCoeffs_, rvecs, tvecs, cv::CALIB_FIX_PRINCIPAL_POINT);
	首先来看一下参数介绍
	cameraMatrix为内参数矩阵。输入一个cv::Mat cameraMatrix即可。
	distCoeffs为畸变矩阵。输入一个cv::Mat distCoeffs即可。
	rvecs为旋转向量;应该输入一个cv::Mat的vector
	vector<cv::Mat> rvecs因为每个vector<Point3f>会得到一个rvecs。
	tvecs为位移向量;和rvecs一样,也应该为vector<cv::Mat> tvecs
	得到矫正参数之后,可以通过cv::undistort方法得到矫正后的图像。
	cv::undistort(image, realImage, cameraMatrix_, distCoeffs_);
	另外,还可以利用cv::initUndistortRectifyMap的方法和remap的方法得到矫正后的图像,但是initUndistortRectifyMap返回的mapx和mapy具体有什么用我也没搞明白。
		如果要获取图像中每个点标定后的修正坐标,还需要使用cv::undistortPoints方法,输入的是以左上角为原点,向右为x轴正方向,
	向下为y轴正方向的点,得到的点是相对于图像正中心的,如果得到的是一个小于零的数,那说明是得到了偏移量,
	需要对应乘上图像的行列数,如果要直接得出像素坐标,则需要在最后加上cameraMatrix_参数。
	cv::undistortPoints(obj_p, res_p, cameraMatrix_, distCoeffs_, cv::noArray(), cameraMatrix_);

二、描点测距

有了上面的矫正,描点测距就比较简单了,首先在实现图像矫正的同时,要以黑白棋盘格格子的宽度的实际距离和像素距离的比值得到一个比例系数,
然后实测时用两点之间的像素距离之间乘上比例系数,就可以得到实际距离了。
首先要说一下如何计算出比例系数,因为findChessboardCorners这个方法是可以找到棋盘格的角点的,而且找到的角点是有顺序的,
所以我们可以选取靠近中心位置的一个格子作为基准,由于一般的图片行列宽不一样,所以在这里也应该将横纵方向分开考虑,
选取两条边,分别作为x方向和y方向的基准。
在这里插入图片描述
如图,以6x7的棋盘格为例,因为角点是有顺序的,那么我们只需要选取棋盘中心位置的两个棋盘格边,就可以获取比较接近的
单个棋盘格像素宽度,已知角点的个数一共是42个,在这里我选的是第17,18和25这三个点来获取两条边,因为拍到的棋盘格的
角度是不能确定的,所以还要考虑这两条边的偏转角,以竖直方向为准,角度大于45或是两个偏转角相比较大的那个作为x方向的基准,
另一个为y方向的基准,或者也可以以x方向为基准做判断。
因为findChessboardCorners找到的点都保存在corners中,所以可以在corners中获取点坐标,通过点的坐标就可以计算出两点之
间的像素距离也就是棋盘格边长的像素距离,而角度,也可以通过两点的坐标计算,棋盘格格子的实际宽度,需要用尺子测量,
作为参数手动输入,然后用实际距离/像素距离,就可以得到比例系数,因为考虑到了x,y两个方向,因此比例系数也是两个。
求取比例系数的代码如下:

	int centerP = rowCount * (colCount / 2 - 1) + (rowCount - 1) / 2;
	//realB_[2]为棋盘格像素距离,realAngle[2]为偏转角度。
	double realB_[2], realAngle[2];
	//这里的centerP就是之前提到的选取的点,这里是17,这里是计算两条边的像素距离
	realB_[0] = sqrt((corner_1[centerP].x - corner_1[centerP + 1].x) * (corner_1[centerP].x - corner_1[centerP + 1].x) + (corner_1[centerP].y - corner_1[centerP + 1].y) * (corner_1[centerP].y - corner_1[centerP + 1].y));
	realB_[1] = sqrt((corner_1[centerP + 1 + rowCount].x - corner_1[centerP + 1].x) * (corner_1[centerP + 1 + rowCount].x - corner_1[centerP + 1].x) + (corner_1[centerP + 1 + rowCount].y - corner_1[centerP + 1].y) * (corner_1[centerP + 1 + rowCount].y - corner_1[centerP + 1].y));
	//因为点的位置难以确定,所以要先判断点的位置,再计算角度
	if (corner_1[centerP].x > corner_1[centerP + 1].x) {
		realAngle[0] = fabs(atan((corner_1[centerP].y - corner_1[centerP + 1].y) / (corner_1[centerP].x - corner_1[centerP + 1].x)));
	}
	else {
		realAngle[0] = fabs(atan((corner_1[centerP + 1].y - corner_1[centerP].y) / (corner_1[centerP + 1].x - corner_1[centerP].x)));
	}
	if (corner_1[centerP + 1 + rowCount].x > corner_1[centerP + 1].x) {
		realAngle[1] = fabs(atan((corner_1[centerP + 1 + rowCount].y - corner_1[centerP + 1].y) / (corner_1[centerP + 1 + rowCount].x - corner_1[centerP + 1].x)));
	}
	else {
		realAngle[1] = fabs(atan((corner_1[centerP + 1].y - corner_1[centerP + 1 + rowCount].y) / (corner_1[centerP + 1].x - corner_1[centerP + 1 + rowCount].x)));
	}
	//通过角度区分x,y方向,算出比例系数
	if (realAngle[0] < realAngle[1]) {
		realBs_[0] = lengthOfSide / realB_[0];
		realBs_[1] = lengthOfSide / realB_[1];
	}
	else {
		realBs_[0] = lengthOfSide / realB_[1];
		realBs_[1] = lengthOfSide / realB_[0];
	}
	计算完成后,使用如下:
	points[i].x = (res_p[0].x - center[0] / 2) * realBs_[0];
	points[i].y = (center[1] / 2 - res_p[0].y) * realBs_[1];
	这里得到的是以图片中心为原点,x轴向右为正,y轴向下为正的实际坐标距离。
	下面附完整代码:
	注意:使用前要先配置好opencv的环境

完整代码

		// cameraCalibration.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <string.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <vector>
#include <assert.h>

using namespace std;
using namespace cv;

typedef bool bool_t;

class CorrectImage
{
protected:
	cv::Mat cameraMatrix_;
	cv::Mat distCoeffs_;
	double realBs_[2];
	double center[2] = { 1920, 1080 };
	double mmPerPixels_[2];
	double centerOfPixels_[2];
public:
	CorrectImage()
	{
		realBs_[0] = realBs_[1] = 0;
	}
	bool_t Init(const cv::Mat* images, int imageCount, int rowCount, int colCount, double lengthOfSide)
	{
		rowCount--;
		colCount--;
		std::vector< cv::Point3f > worldPoints;
		for (int j = 0; j < colCount; ++j)
		{
			for (int k = 0; k < rowCount; ++k)
			{
				worldPoints.push_back(cv::Point3f(k*1.0, j*1.0, 0.0f));
			}
		}
		std::vector< cv::Mat > rvecs, tvecs;
		std::vector< cv::Point2f > corners;
		std::vector< std::vector< cv::Point2f > > corners2;
		std::vector< std::vector< cv::Point3f > > worldPoints2;
		for (int i = 0; i < imageCount; ++i)
		{
			bool_t found = cv::findChessboardCorners(images[i], cv::Size(rowCount, colCount), corners, cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE);
			corners2.push_back(corners);
			worldPoints2.push_back(worldPoints);
		}
		cv::calibrateCamera(worldPoints2, corners2, images[0].size(), cameraMatrix_, distCoeffs_, rvecs, tvecs, cv::CALIB_FIX_PRINCIPAL_POINT);
		cv::Mat realImage;
		std::vector< cv::Point2f > corner_1;
		cv::undistort(images[0], realImage, cameraMatrix_, distCoeffs_);
		cv::imshow("img1", realImage);
		bool found = cv::findChessboardCorners(realImage, cv::Size(rowCount, colCount), corner_1, cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE);
		int centerP = rowCount * (colCount / 2 - 1) + (rowCount - 1) / 2;
		cout << centerP << endl;
		double realB_[2], realAngle[2];
		realB_[0] = sqrt((corner_1[centerP].x - corner_1[centerP + 1].x) * (corner_1[centerP].x - corner_1[centerP + 1].x) + (corner_1[centerP].y - corner_1[centerP + 1].y) * (corner_1[centerP].y - corner_1[centerP + 1].y));
		realB_[1] = sqrt((corner_1[centerP + 1 + rowCount].x - corner_1[centerP + 1].x) * (corner_1[centerP + 1 + rowCount].x - corner_1[centerP + 1].x) + (corner_1[centerP + 1 + rowCount].y - corner_1[centerP + 1].y) * (corner_1[centerP + 1 + rowCount].y - corner_1[centerP + 1].y));
		if (corner_1[centerP].x > corner_1[centerP + 1].x) {
			realAngle[0] = fabs(atan((corner_1[centerP].y - corner_1[centerP + 1].y) / (corner_1[centerP].x - corner_1[centerP + 1].x)));
		}
		else {
			realAngle[0] = fabs(atan((corner_1[centerP + 1].y - corner_1[centerP].y) / (corner_1[centerP + 1].x - corner_1[centerP].x)));
		}
		if (corner_1[centerP + 1 + rowCount].x > corner_1[centerP + 1].x) {
			realAngle[1] = fabs(atan((corner_1[centerP + 1 + rowCount].y - corner_1[centerP + 1].y) / (corner_1[centerP + 1 + rowCount].x - corner_1[centerP + 1].x)));
		}
		else {
			realAngle[1] = fabs(atan((corner_1[centerP + 1].y - corner_1[centerP + 1 + rowCount].y) / (corner_1[centerP + 1].x - corner_1[centerP + 1 + rowCount].x)));
		}
		if (realAngle[0] < realAngle[1]) {
			realBs_[0] = lengthOfSide / realB_[0];
			realBs_[1] = lengthOfSide / realB_[1];
		}
		else {
			realBs_[0] = lengthOfSide / realB_[1];
			realBs_[1] = lengthOfSide / realB_[0];
		}
		cout << realBs_[0] << endl;
		cout << realBs_[1] << endl;
		
		return true;
	}
	void TranslateCoord(cv::Point2f* points, int pointCount)
	{
		// 这是为了防止没有调用 Init 进行初始化。
		cout << "222" << endl;
		assert(realBs_[0] > 0);
		for (int i = 0; i < pointCount; ++i)
		{
			cv::Point2f point1 = cv::Point2f(points[i].x, points[i].y);
			std::vector< cv::Point2f > obj_p;
			std::vector< cv::Point2f > res_p;
			obj_p.push_back(point1);
			cv::undistortPoints(obj_p, res_p, cameraMatrix_, distCoeffs_, cv::noArray(), cameraMatrix_);
			//cout << res_p[0].x << endl;
			//cout << res_p[0].y << endl;

			points[i].x = (res_p[0].x - center[0] / 2) * realBs_[0];
			points[i].y = (center[1] / 2 - res_p[0].y) * realBs_[1];
			/*points[i].x = (res_p[0].x - center[0] / 2);
			points[i].y = (center[1] / 2 - res_p[0].y);*/
			cout << points[i].x << "," << points[i].y << endl;

		}
	}
	
};

	int main()
	{
		Mat images[3];

		images[0] = cv::imread("E:\\CPP\\rectImage\\1\\a.bmp");
		images[1] = cv::imread("E:\\CPP\\rectImage\\1\\b.bmp");
		images[2] = cv::imread("E:\\CPP\\rectImage\\1\\c.bmp");

		cv::Point2f pts[2] = { };
		pts[0] = Point2f(213.927, 216.998);
		pts[1] = Point2f(331.96, 214.274);

		CorrectImage ci;
		ci.Init(images, 3, 8, 7, 21);
		ci.TranslateCoord(pts, 2);
		Mat img1 = cv::imread("E:\\CPP\\rectImage\\1\\5.bmp");
		cv::waitKey(0);
	}
  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值