前段时间进行了摄像头标定,我使用过matlab直接标定,效果感觉还可以。那么这次纯粹是使用Opencv来进行一次标定。
准备标定板
标定板是必不可少的,这里我说明一下,建议使用长宽不等的标定板,这样可以避免出现错误的姿态误差。比如我下面准备的图片。
我是直接打印出来,其实并不建议直接打印,因为我打印出来后发现整张纸有点潮,晾干后不太平整,所以并不太建议直接打印。
标定程序
先说一下步骤:
- 定位出图片中所有的角点
- 利用所有的角点去进行单目标定
- 利用所有的角点去进行双目标定
定位角点
当然,在找点前需要把图片导进来,导进来是一个 numpy
的矩阵,形状为(h, w, 3)
。
接下来我就直接定义了个函数,用来专门定位。
这个函数简单介绍一下,有两个参数:
image
是图片的矩阵
size
是标定板的角点的尺寸,上面的那张标定图就是(11, 8)
返回值就只有两种可能,如果能找到就直接返回点,找不到就返回None
。
程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| def cornerFind(image, size): """ 角点查找 :param image: 图片 :param size: 角点个数(横向个数,纵向个数) :return: """ criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) success, corners = cv2.findChessboardCorners(gray, size, None) if success: output = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) return output else: return None
|
单张图片定位角点的程序有了,那么就直接使用。
接下来就是把每次的结果直接放进列表里:
- 定义
obj_points
列表,专门放标定板的坐标
- 定义
left_img_points
列表,专门放左相机拍摄的角点
- 定义
right_img_points
列表,专门放右相机拍摄的角点
那么,完整的程序就是如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| board = (11, 8)
size = 19.2
bw, bh = board
objp = np.zeros((bw * bh, 3), np.float32) objp[:, :2] = np.mgrid[0:bw, 0:bh].T.reshape(-1, 2) objp *= size
obj_points = [] left_img_points = [] right_img_points = []
image_size = None
for left_path, right_path in zip(left_image_list, right_image_list):
left_image = cv2.imread(left_path) right_image = cv2.imread(right_path)
image_size = left_image.shape[1::-1]
left_corners = cornerFind(left_image, board) right_corners = cornerFind(right_image, board)
if (left_corners is not None) and (right_corners is not None):
obj_points.append(objp)
left_img_points.append(left_corners) right_img_points.append(right_corners)
cv2.drawChessboardCorners(left_image, board, left_corners, True) cv2.drawChessboardCorners(right_image, board, right_corners, True) cv2.circle(left_image, left_corners[0, 0].astype(int), 10, (255, 0, 0)) cv2.circle(right_image, right_corners[0, 0].astype(int), 10, (255, 0, 0))
|
这里我来说一下size
这个变量,Opencv的量纲是毫米,size
就是单个方框的尺寸,我打印出来之后测量了一下就是 19.2 mm,所以objp *= size
的作用就是,将标定板做成实际坐标。
看一下效果吧:
单目标定
单目就直接使用cv2.calibrateCamera()
就可以直接标定。
会返回出五个结果,分别是:精度、内参矩阵、畸变参数、旋转矩阵、平移向量。
1 2 3 4 5 6
| _, l_mtx, l_dist, _, _ = cv2.calibrateCamera( obj_points, left_img_points, image_size, None, None)
_, r_mtx, r_dist, _, _ = cv2.calibrateCamera( obj_points, right_img_points, image_size, None, None)
|
在接下来的使用中只会使用到内参矩阵和畸变参数,所以,不会直接使用其他的返回值。
双目标定
双目就直接使用cv2.stereoCalibrate()
函数。
返回值是:精度、左相机内参矩阵、左相机畸变参数、右相机内参矩阵、右相机畸变参数、旋转矩阵、平移向量、本征矩阵、基本矩阵。
1 2 3 4 5 6 7
| ret, l_mtx, l_dist, r_mtx, r_dist, R, T, E, F = cv2.stereoCalibrate( obj_points, left_img_points, right_img_points, l_mtx, l_dist, r_mtx, r_dist, image_size)
|
说明一下,旋转矩阵和平移向量是第二个相机(右相机)相对于第一个相机而言(左相机)。
参数保存
随便使用一个方法把这些参数保存下来,这样就可以下此就直接使用了。
可以保存成json格式,方便自己私自打开查看。
使用
以上就是标定的过程,具体的使用还是如下所示。
去除畸变和图像校正
标定完了之后,就可以直接使用了,主要是分为两步:
- 去除畸变:目的是消除图片的切向畸变和径向畸变
- 图像校正:将对应位置进行调平
这里我就直接使用cv2.stereoRectify()
函数和cv2.initUndistortRectifyMap()
函数,我再这里直接封装成了一个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class RectifyDistort(object): def __init__(self, size: tuple, datas: dict):
R1, R2, P1, P2, Q, validPixROI1, validPixROI2 = cv2.stereoRectify( datas["leftCameraMatrix"], datas["leftDistCoeffs"], datas["rightCameraMatrix"], datas["rightDistCoeffs"], size, datas["R"], datas["T"])
self.Q = Q
self.left_map = cv2.initUndistortRectifyMap( datas["leftCameraMatrix"], datas["leftDistCoeffs"], R1, P1, size, cv2.INTER_NEAREST)
self.right_map = cv2.initUndistortRectifyMap( datas["rightCameraMatrix"], datas["rightDistCoeffs"], R2, P2, size, cv2.INTER_NEAREST)
def __call__(self, image: np.ndarray, camera: str): """ 校正图 :param image: :param camera: 摄像头区分 :return: """
if camera == "left": map1, map2 = self.left_map elif camera == "right": map1, map2 = self.right_map else: raise ValueError
new_image = cv2.remap(image, map1, map2, cv2.INTER_LINEAR)
return new_image
|
直接看图片效果。
未处理图片:
处理图片:
Q矩阵的使用
在上面的程序中,还会得到一个Q矩阵,这是一个关键的矩阵,可以利用此矩阵直接将世界坐标计算出来。
公式为:
直接使用Q矩阵乘上一个向量就可以直接得出,世界坐标。
这里说明两点:
- x、y、d的单位是像素
- X/W,Y/W,Z/W,才是最终的世界坐标,单位是毫米
- d是视差值,左像素和右像素的之差