Thank you for your reply. So it means that a low RMSE value does not always mean a good calibration? For instance when I use a small amount of images, the RMSE could be low while the calibration is not “good”?
The resolution of the cameras is 3088x2064, could the low number of chessboard corner points be a reason for a bad calibration?
With the pictures and code below, I find the following location of the second camera [X2, Y2, Z2] = [389.0419239 159.77362514 110.93641357]. So how could this location be explained?
The pictures used can be found here:
Camera 1 calibration (left)
Camera 2 calibration (right)
Synced camera 1
Synced camera 2
Below you can find my code:
import cv2
import numpy as np
import glob
rows = 6 #number of checkerboard rows.
columns = 9 #number of checkerboard columns.
world_scaling = 24.5 #change this to the real world square size. Or not.
_show = True
def calibrate_camera(images_folder):
images_names = sorted(glob.glob(images_folder))
images = []
for imname in images_names:
im = cv2.imread(imname, 1)
images.append(im)
#criteria used by checkerboard pattern detector.
#Change this if the code can't find the checkerboard
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
#coordinates of squares in the checkerboard world space
objp = np.zeros((rows*columns,3), np.float32)
objp[:,:2] = np.mgrid[0:rows,0:columns].T.reshape(-1,2)
objp = world_scaling* objp
#frame dimensions. Frames should be the same size.
width = images[0].shape[1]
height = images[0].shape[0]
#Pixel coordinates of checkerboards
imgpoints = [] # 2d points in image plane.
#coordinates of the checkerboard in checkerboard world space.
objpoints = [] # 3d point in real world space
for frame in images:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#find the checkerboard
ret, corners = cv2.findChessboardCorners(gray, (rows, columns), None)
if ret == True:
#Convolution size used to improve corner detection. Don't make this too large.
conv_size = (11, 11)
#opencv2 can attempt to improve the checkerboard coordinates
corners = cv2.cornerSubPix(gray, corners, conv_size, (-1, -1), criteria)
if _show:
# draw chessboard corners on frame
cv2.drawChessboardCorners(frame, (rows,columns), corners, ret)
# resize frame to fit to screen
res_frame = cv2.resize(frame, (1080,720))
cv2.imshow('img', res_frame)
k = cv2.waitKey(100)
# append corner locations to imgpoints
objpoints.append(objp)
imgpoints.append(corners)
# perform camera calibration
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, (width, height), None, None)
print(f'rmse single camera ({images_folder}): {ret}')
return mtx, dist, rvecs, tvecs
def stereo_calibrate(mtx1, dist1, mtx2, dist2, frames_1, frames_2):
#read the synched frames
c1_images_names = glob.glob(frames_1)
c2_images_names = glob.glob(frames_2)
c1_images = []
c2_images = []
for im1, im2 in zip(c1_images_names, c2_images_names):
_im = cv2.imread(im1, 1)
c1_images.append(_im)
_im = cv2.imread(im2, 1)
c2_images.append(_im)
# criteria for stereo calibration
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.0001)
#coordinates of squares in the checkerboard world space
objp = np.zeros((rows*columns,3), np.float32)
objp[:,:2] = np.mgrid[0:rows,0:columns].T.reshape(-1,2)
objp = world_scaling* objp
#frame dimensions. Frames should be the same size.
width = c1_images[0].shape[1]
height = c1_images[0].shape[0]
#Pixel coordinates of checkerboards
imgpoints_left = [] # 2d points in image plane.
imgpoints_right = []
#coordinates of the checkerboard in checkerboard world space.
objpoints = [] # 3d point in real world space
count = 0
for frame1, frame2 in zip(c1_images, c2_images):
gray1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
c_ret1, corners1 = cv2.findChessboardCorners(gray1, (rows, columns), None)
c_ret2, corners2 = cv2.findChessboardCorners(gray2, (rows, columns), None)
if c_ret1 == True and c_ret2 == True:
corners1 = cv2.cornerSubPix(gray1, corners1, (11, 11), (-1, -1), criteria)
corners2 = cv2.cornerSubPix(gray2, corners2, (11, 11), (-1, -1), criteria)
if count == 0:
corner_point = [corners1[0], corners2[1]]
if _show:
cv2.drawChessboardCorners(frame1, (rows, columns), corners1, c_ret1)
res_frame = cv2.resize(frame1, (1080,720))
cv2.imshow('img', res_frame)
cv2.drawChessboardCorners(frame2, (rows, columns), corners2, c_ret2)
res_frame = cv2.resize(frame2, (1080,720))
cv2.imshow('img2', res_frame)
k = cv2.waitKey(100)
objpoints.append(objp)
imgpoints_left.append(corners1)
imgpoints_right.append(corners2)
count += 1
stereocalibration_flags = cv2.CALIB_FIX_INTRINSIC
# stereo calibrate system
ret, CM1, dist1, CM2, dist2, R, T, E, F = cv2.stereoCalibrate(objpoints, imgpoints_left, imgpoints_right, mtx1, dist1,
mtx2, dist2, (width, height), criteria = criteria, flags = stereocalibration_flags)
print(f"rmse stereo: {ret}")
return R, T, corner_point
if __name__ == "__main__":
mtx1, dist1, rvecs1, tvecs1 = calibrate_camera(images_folder = 'images/stereoLeft/*')
mtx2, dist2, rvecs2, tvecs2 = calibrate_camera(images_folder = 'images/stereoRight/*')
R, T, corner_point = stereo_calibrate(mtx1, dist1, mtx2, dist2, 'images/synced/stereoLeft/*', 'images/synced/stereoRight/*')
transformation_matrix = np.empty((4,4))
transformation_matrix[:3, :3] = R
transformation_matrix[:3, 3] = T.T[0]
transformation_matrix[3, :] = [0, 0, 0, 1]
location_cam2 = np.dot(transformation_matrix, [[0], [0], [0], [1]])
print("location camera 2 [x,y,z]: ", location_cam2[:3].T)