Opencv相机标定(一)
摘 要:本文主要讲解如何利用计算机视觉开源库Opencv解决单目摄像头的标定和三维姿态的求解。
相机的标定
操作系统: Ubuntu16.04 LTS
OpenCV版本: 3.4.0
摄像头: 640×480像素 单目摄像头
一、Opencv自带例程标定
1.找到例程:
OpenCV里面提供了标定的例程,可以直接用其对摄像头进行标定。进入opencv目录并找到samples/cpp/tutorial_code/calib3d/camera_calibration目录,把它拷贝到一个合适的位置。(因为可能需要修改一些代码,因此不建议直接在原目录下使用。)
2.修改文件配置参数
进入camera_calibration,找到in_VID5.xml 这个文件,修改里面参数
<!--
Number of inner corners per a item row and column. (square, circle)
--><BoardSize_Width> 8</BoardSize_Width> # 这是行的角点数
<BoardSize_Height>5</BoardSize_Height> #这是列的角点数
把两个参数改成自己棋盘的角点数。
<Square_Size>24</Square_Size> #这是单个正方形的边长
<Input>"1"</Input> #笔记本如果外接一个摄像头这个写入1,因为笔记本自带摄像头为0.
要注意的是,棋盘的行、列角点数是内棋盘的,比如上图的行角点数为8,列角点数为5.
3.编译
在camera_calibration目录下新建CMakeLists.txt,写入:
project(Camera_Calibration)
set(CMAKE_CXX_STANDARD 11)
find_package(OpenCV 3.0 QUIET)
if(NOT OpenCV_FOUND)
find_package(OpenCV 2.4.3 QUIET)
if(NOT OpenCV_FOUND)
message(FATAL_ERROR "OpenCV > 2.4.3 not found.")
endif()
endif()
include_directories(${OpenCV_INCLUDE_DIR})
add_executable(Camera_Calibration camera_calibration.cpp)
target_link_libraries(Camera_Calibration ${OpenCV_LIBS})
新建build文件,并cd进入,在终端输入cmake… ,编译完成后再输入make。
4.运行
./Camera_Calibration ../in_VID5.xml #在build目录下输入
按下g就可以进行标定了,注意标定的时候要将标定板上下左右旋转进行多方位标定。最后在out_camera_data.xml就会输出相机的参数。
<?xml version="1.0"?>
<opencv_storage>
<calibration_time>"2018年12月06日 星期四 15时18分08秒"</calibration_time>
<nr_of_frames>25</nr_of_frames>
<image_width>640</image_width>
<image_height>480</image_height>
<board_width>8</board_width>
<board_height>5</board_height>
<square_size>24.</square_size>
<fix_aspect_ratio>1.</fix_aspect_ratio>
<!-- flags: +fix_aspectRatio +fix_principal_point +zero_tangent_dist +fix_k4 +fix_k5 -->
<flags>6158</flags>
<fisheye_model>0</fisheye_model>
<camera_matrix type_id="opencv-matrix">
<rows>3</rows>
<cols>3</cols>
<dt>d</dt>
<data> #这是相机3*3内参数矩阵
4.3471380360423700e+02 0. 320. 0. 4.3471380360423700e+02 240. 0. 0.
1.</data></camera_matrix>
<distortion_coefficients type_id="opencv-matrix">
<rows>5</rows>
<cols>1</cols>
<dt>d</dt>
<data> # 这是1*5 畸变矩阵
1.3147254180236298e-01 -2.4946059176605345e-01 0. 0.
6.0632361910940555e-02</data></distortion_coefficients>
<avg_reprojection_error>4.1607785428623084e-01</avg_reprojection_error>
这样相机的内参数矩阵和畸变矩阵就标定完成了。
二、自主标定
# -*- coding:utf-8 -*-
import cv2
import numpy as np
import glob
# 设置寻找亚像素角点的参数,采用的停止准则是最大循环次数30和最大误差容限0.001
criteria = (cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS, 30, 0.001)
# 获取标定板角点的位置
objp = np.zeros((5*8,3), np.float32)
objp[:,:2] = np.mgrid[0:8,0:5].T.reshape(-1,2) # 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y
obj_points = [] # 存储3D点
img_points = [] # 存储2D点
images = glob.glob('image/0.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
size = gray.shape[::-1]
ret, corners = cv2.findChessboardCorners(gray, (8,5), None)
if ret:
obj_points.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (6,6), (-1,-1), criteria) # 在原角点的基础上寻找亚像素角点
if corners2.all:
img_points.append(corners2)
else:
img_points.append(corners)
cv2.drawChessboardCorners(img, (8,5), corners, ret) # 记住,OpenCV的绘制函数一般无返回值
cv2.imshow('img', img)
cv2.waitKey(50)
print len(img_points)
cv2.destroyAllWindows()
# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points,size, None, None)
print "ret:",ret
print "mtx:\n",mtx # 内参数矩阵
print "dist:\n",dist # 畸变系数 distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
print "rvecs:\n",rvecs # 旋转向量 # 外参数
print "tvecs:\n",tvecs # 平移向量 # 外参数
print("-----------------------------------------------------")
# 畸变校正
img = cv2.imread(images[0])
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))
print newcameramtx
print("------------------使用undistort函数-------------------")
dst = cv2.undistort(img,mtx,dist,None,newcameramtx)
x,y,w,h = roi
dst1 = dst[y:y+h,x:x+w]
cv2.imwrite('calibresult11.jpg', dst1)
print "方法一:dst的大小为:", dst1.shape
# undistort方法二
'''print("-------------------使用重映射的方式-----------------------")
mapx,mapy = cv2.initUndistortRectifyMap(mtx,dist,None,newcameramtx,(w,h),5) # 获取映射方程
#dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR) # 重映射
dst = cv2.remap(img,mapx,mapy,cv2.INTER_CUBIC) # 重映射后,图像变小了
x,y,w,h = roi
dst2 = dst[y:y+h,x:x+w]
cv2.imwrite('calibresult11_2.jpg', dst2)
print "方法二:dst的大小为:", dst2.shape # 图像比方法一的小'''
print("-------------------计算反向投影误差-----------------------")
tot_error = 0
for i in xrange(len(obj_points)):
img_points2, _ = cv2.projectPoints(obj_points[i],rvecs[i],tvecs[i],mtx,dist)
error = cv2.norm(img_points[i],img_points2, cv2.NORM_L2)/len(img_points2)
tot_error += error
mean_error = tot_error/len(obj_points)
print "total error: ", tot_error
print "mean error: ", mean_error
直接上代码,这是参考(https://blog.csdn.net/FireMicrocosm/article/details/48594897)。
由上面代码我们拍的棋盘照片可以直接得到内参数矩阵,畸变矩阵,旋转向量,平移向量。
三、外参数的标定
前面我们已经将相机的内参数已经标定完成了,后面我们就要对其的外参数进行标定。首先我们要知道相机的外参数有旋转矩阵与平移矩阵,它们是描述相机坐标系与世界坐标系的相对位置,也就是相机坐标系经过平移和旋转最后能与世界坐标系重合。
def return_tvec(target_points):
camera_matrix = np.array([[434.713803604237,0,320],[0,434.713803604237,240],[0, 0,1]],np.float32)
dist_matrix = np.array([[0.13147254180236298,-0.24946059176605345,0,0, 0.060632361910940555]],np.float32)
# 获取标定板角点的位置
objp = np.zeros(((2*2),3), np.float32)
objp[:,:2] = np.mgrid[0:2,0:2].T.reshape(-1,2) # 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y
# 标定
#ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points,size, None, None)
retval, rvec, tvec= cv2.solvePnP(objp,target_points,camera_matrix,dist_matrix)
matrix ,jaco= cv2.Rodrigues(rvec,jacobian = 1)
return matrix ,tvec
def isRotationMatrix(R) :
Rt = np.transpose(R)
shouldBeIdentity = np.dot(Rt, R)
I = np.identity(3, dtype = R.dtype)
n = np.linalg.norm(I - shouldBeIdentity)
return n < 1e-6
#计算旋转矩阵转换成欧拉角的过程
def rotationMatrixToEulerAngles(R) :
assert(isRotationMatrix(R))
sy = math.sqrt(R[0,0] * R[0,0] + R[1,0] * R[1,0])
singular = sy < 1e-6
if not singular :
x = math.atan2(R[2,1] , R[2,2])
y = math.atan2(-R[2,0], sy)
z = math.atan2(R[1,0], R[0,0])
else :
x = math.atan2(-R[1,2], R[1,1])
y = math.atan2(-R[2,0], sy)
z = 0
m = 57.2958
return np.array([x*m, y*m, z*m])
if __name__ == '__main__':
IMAGE_DIR = 'tennis_/'
img_dir = os.listdir(IMAGE_DIR)
for f in img_dir:
immg_dir = IMAGE_DIR+f
target,cenX,cenY ,boolen= detect(immg_dir)
print cenX,cenY
if boolen:
print target
matrix ,tvec = return_tvec(target)
print isRotationMatrix(matrix)
print rotationMatrixToEulerAngles(matrix)
a,b,c = rotationMatrixToEulerAngles(matrix)
print '俯仰角%f\n滚转角%f\n偏航角%f\n' %(a,b,c)
else :
pass
我用程序自动标定了这个网球,并将其左上角的点作为我的世界坐标系的原点,Z轴垂直与这个X-Y平面坐标系。
俯仰角2.433056
滚转角-0.291323
偏航角-0.021888 这就是我标定出来相机相对于这个网球建立的世界坐标系的旋转角度。
Tips:这篇文章就是讲述了相机标定的具体实现方法。下篇文章会整理下相机标定的原理。