相机标定与图像校准

相机标定流程

  1. 运行环境
    该相机标定流程分为python版本与C++版本(需要与opencv进行环境配置);

  2. 标定对象
    标定对象为存在畸变的普通免驱相机或其他有畸变相机;

  3. 标定物品
    该标定方法为张正友相机标定法,需要使用“棋盘”标定板(该标定板棋盘样式为12*9,该格式与程序参数有关);
    相机标定与图像校准_第1张图片

  4. 流程简述
    首先需要使用有畸变的相机拍摄完整的棋盘照片,可用照片数量建议15-25张;拍摄时建议棋盘出现在相机的不同位置,覆盖各个方位和一定的距离段。然后使用已拍摄的照片计算出相机的内外参数,要注意相机的内参、外参、参数格式等。最后使用获得参数进行图像全局矫正或者图像稀疏点的矫正。

  5. python版本
    为了使用方便,有照片截取程序与参数求解流程,以及矫正函数使用流程。
    5.1照片截取程序

#照片截取程序
import numpy as np
import cv2 
import matplotlib.pyplot as plt
cap = cv2.VideoCapture(0)
#cap.set(cv2.CAP_PROP_FRAME_WIDTH,300)
#cap.set(cv2.CAP_PROP_FRAME_HEIGHT,200)
if not cap.isOpened():
    raise ValueError("Video is not openning")
    
cv2.namedWindow('chess',cv2.WINDOW_NORMAL)
count = 0
while(True):
    ret, frame = cap.read()
    cv2.imshow('chess',frame)
    if cv2.waitKey(1) & 0xFF == ord('s'):#当键盘键入"S"会进行一次拍照
        cv2.imwrite('/home/pi/Desktop/biaoding'+str(count)+'.jpg',frame)#home/pi/Desktop/biaoding 为照片写入路径
        count = count + 1
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()

5.2参数求解程序

#参数求解程序
import numpy as np
import cv2 as cv
import glob
# termination criteria
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 20, 0.001)
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((11*8,3), np.float32)
objp[:,:2] = np.mgrid[0:11,0:8].T.reshape(-1,2)
# Arrays to store object points and image points from all the images.
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
images = glob.glob('/home/pi/Desktop/biaoding/*.jpg')

for fname in images:
    
    img = cv.imread(fname)
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    # Find the chess board corners
    ret, corners = cv.findChessboardCorners(gray, (11,8), None)
    print('0')
    # If found, add object points, image points (after refining them)
    if ret == True:
        print('1')#该输出为方便了解是否在照片中找到棋盘各点,表示该照片是否有效
        objpoints.append(objp)
        corners2 = cv.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria)
        imgpoints.append(corners)
        # Draw and display the corners
        cv.drawChessboardCorners(img, (11,8), corners2, ret)
        cv.imshow('img', img)
        cv.waitKey(500)
cv.destroyAllWindows()
print('参数正在求解')
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
print(mtx)
print(dist)
np.savetxt('/home/pi/Desktop/biaoding/',mtx)#该路径为参数写入路径
np.savetxt('/home/pi/Desktop/biaoding/',dist)

5.3图像矫正
使用 cv2.undistort() 这是最简单的方法。只需使用这个函数和上边得到
的 ROI 对结果进行裁剪。

#undistort方式矫正
img = cv2.imread('left12.jpg')
h, w = img.shape[:2]
newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))
# undistort
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
# crop the image
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('calibresult.png',dst)

使用 remapping 我们要找到从畸变图像到非畸变图像的映射方程,再使用重映射方程

# undistort
mapx,mapy = cv2.initUndistortRectifyMap(mtx,dist,None,newcameramtx,(w,h),5)
dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
# crop the image
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('calibresult.png',dst)
  1. C++程序版本
    6.1 C++程序集图像采集、参数输出、图像矫正于一体
#include
#include
#include 
#include 
#include 
#include 
#include
#include
using namespace std;

void help(char* argv[])
{
     
}
int main(int argc, char* argv[])
{
        //argc is argument count,argv is argument vector(传入main函数的参数序列或指针
	//argv[0]一定是程序名称,并且包含程序所在的完整路径
	int n_boards = 0; // initial n_board,which will determined by input
	float image_sf = 1.0f;//f确定格式
	float delay = 1.f;
	int board_w = 0;
	int board_h = 0;
	if (argc < 4 || argc > 6) {
     
		cout << "\nERROR: Wrong number of input parameters";
		help(argv);
		return -1;
	}
	board_w = atoi(argv[1]);
	board_h = atoi(argv[2]);
	n_boards = atoi(argv[3]);
	if (argc > 4) delay = atof(argv[4]);
	if (argc > 5) image_sf = atof(argv[5]);
	int board_n = board_w * board_h;
	cv::Size board_sz = cv::Size(board_w, board_h);
	cv::VideoCapture capture(1);
	if (!capture.isOpened()) {
     
		cout << "\nCouldn't open the camera\n";
		help(argv);
		return -1;
	}
	// 分配内存
	//
	vector< vector<cv::Point2f> > image_points;
	vector< vector<cv::Point3f> > object_points;
	// Capture corner views: loop until we've got n_boards successful
	// captures (all corners on the board are found).

	double last_captured_timestamp = 0;
	cv::Size image_size;
	while (image_points.size() < (size_t)n_boards) {
     
		cv::Mat image0, image;
		capture >> image0;
		image_size = image0.size();
		cv::resize(image0, image, cv::Size(), image_sf, image_sf, cv::INTER_LINEAR);
		// Find the board

		vector<cv::Point2f> corners;
		bool found = cv::findChessboardCorners(image, board_sz, corners);
		// Draw it

		drawChessboardCorners(image, board_sz, corners, found);
		// If we got a good board, add it to our data

		double timestamp = (double)clock() / CLOCKS_PER_SEC;
		if (found && timestamp - last_captured_timestamp > 1) {
     
			last_captured_timestamp = timestamp;
			image ^= cv::Scalar::all(255);
			cv::Mat mcorners(corners); // do not copy the data
			mcorners *= (1. / image_sf); // scale the corner coordinates
			image_points.push_back(corners);
			object_points.push_back(vector<cv::Point3f>());
			vector<cv::Point3f>& opts = object_points.back();
			opts.resize(board_n);
			for (int j = 0; j < board_n; j++) {
     
				opts[j] = cv::Point3f((float)(j / board_w), (float)(j % board_w), 0.f);
			}
			cout << "Collected our " << (int)image_points.size() <<
				" of " << n_boards << " needed chessboard images\n" << endl;
		}
		cv::imshow("Calibration", image); //show in color if we did collect the image
		if ((cv::waitKey(30) & 255) == 27)
			return -1;
	}
	// end collectiom
	cv::destroyWindow("Calibration");
	cout << "\n\n*** CALIBRATING THE CAMERA...\n" << endl;
	// start calibrat the camera
	cv::Mat intrinsic_matrix, distortion_coeffs;
	double err = cv::calibrateCamera(
		object_points,
		image_points,
		image_size,
		intrinsic_matrix,
		distortion_coeffs,
		cv::noArray(),
		cv::noArray(),
		cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_FIX_PRINCIPAL_POINT
	);
	// save the intrinsics and distortions
	cout << " *** DONE!\n\nReprojection error is " << err <<
		"\nStoring Intrinsics.xml and Distortions.xml files\n\n";
	cv::FileStorage fs("intrinsics.xml", cv::FileStorage::WRITE);
	fs << "image_width" << image_size.width << "image_height" << image_size.height
		<< "camera_matrix" << intrinsic_matrix << "distortion_coefficients"
		<< distortion_coeffs;
	fs.release();
	//loading the date back
	fs.open("intrinsics.xml", cv::FileStorage::READ);
	cout << "\nimage width: " << (int)fs["image_width"];
	cout << "\nimage height: " << (int)fs["image_height"];
	cv::Mat intrinsic_matrix_loaded, distortion_coeffs_loaded;
	fs["camera_matrix"] >> intrinsic_matrix_loaded;
	fs["distortion_coefficients"] >> distortion_coeffs_loaded;
	cout << "\nintrinsic matrix:" << intrinsic_matrix_loaded;
	cout << "\ndistortion coefficients: " << distortion_coeffs_loaded << endl;


	//build the undistort map which we will use for all subsequent frames
	cv::Mat map1, map2;
	cv::initUndistortRectifyMap(
		intrinsic_matrix_loaded,
		distortion_coeffs_loaded,
		cv::Mat(),
		intrinsic_matrix_loaded,
		image_size,
		CV_16SC2,
		map1,
		map2
	);

	//just run the camera to the screen,now showing the raw and the undistorted imamge
	for (;;) {
     
		cv::Mat image, image0;
		capture >> image0;
		if (image0.empty()) break;
		cv::remap(
			image0,
			image,
			map1,
			map2,
			cv::INTER_LINEAR,
			cv::BORDER_CONSTANT,
			cv::Scalar()

		);
		cv::imshow("undiatorted", image);
		if ((cv::waitKey(30) & 255) == 27) break;
	}
	return 0;
}

在程序运行之前需要对argc进行命令参数传值,传递参数视个人具体情况而定

6.2该文件为参数生成文件,包含所需参数
相机标定与图像校准_第2张图片

  1. 图像稀疏点矫正 注意事项
    使用cv::undistortPoints()进行稀疏矫正,不会矫正整个图像,而是从图像中收集一组点,使用该函数来进行矫正可以获得其正确位置,也可以减少计算量
    在这里插入图片描述
    相机标定与图像校准_第3张图片
    对于单目相机的图像点矫正,使用函数需要输入六个参数
cv::undistortPoints(inputDistortedPoints, outputUndistortedPoints, cameraMatrix, distCoeffs)

使用的输入会使得矫正后的数据过小,影响使用,正确使用方法为

cv::undistortPoints(inputDistortedPoints, outputUndistortedPoints, cameraMatrix, distCoeffs, cv::noArray(), cameraMatrix);

重复输入cameraMatrix该矩阵可以解决问题

此外还应该注意对于图像点矫正的流程

如果需要对图像点进行运算,应当在图像矫正后进行;在图像矫正之前不应该进行改变图像位置点的运算,这样会导致图像点矫正后出现混乱,要切记

你可能感兴趣的:(robomaster竞赛)