本篇主要是使用python opencv标定相机内参和畸变参数的记录,主要参考opencv官方文档中的示例。
本篇不会涉及标定原理。
一张棋盘格图(最好把棋盘格图粘贴到一块平板上,保证棋盘上的角点都在同一平面)。
这里提供一个简单的棋盘格生成程序,在A4纸打印分辨率为96ppi时,棋盘每个格子的宽度为cube_cm:
def generate_chessboard(cube_cm=2., pattern_size=(8, 6), scale=37.79527559055118):
"""
generate chessboard image with given cube length, which adapts to A4 paper print
:param cube_cm: float, single cube length in cm
:param pattern_size: (x, y), the number of points in x, y axes in the chessboard
:param scale: float, scale pixel/cm in A4 paper
"""
# convert cm to pixel
cube_pixel = cube_cm * scale
width = round(pattern_size[0] * cube_cm * scale)
height = round(pattern_size[1] * cube_cm * scale)
# generate canvas
image = np.zeros([width, height, 3], dtype=np.uint8)
image.fill(255)
color = (255, 255, 255)
fill_color = 0
# drawing the chessboard
for j in range(0, height + 1):
y = round(j * cube_pixel)
for i in range(0, width + 1):
x0 = round(i * cube_pixel)
y0 = y
rect_start = (x0, y0)
x1 = round(x0 + cube_pixel)
y1 = round(y0 + cube_pixel)
rect_end = (x1, y1)
cv2.rectangle(image, rect_start, rect_end, color, 1, 0)
image[y0:y1, x0:x1] = fill_color
if width % 2:
if i != width:
fill_color = (0 if (fill_color == 255) else 255)
else:
if i != width + 1:
fill_color = (0 if (fill_color == 255) else 255)
# add border around the chess
chessboard = cv2.copyMakeBorder(image, 30, 30, 30, 30, borderType=cv2.BORDER_CONSTANT, value=(255, 255, 255))
# visualize
win_name = "chessboard"
cv2.imshow(win_name, chessboard)
cv2.waitKey(0)
把需要标定的相机以不同姿态角度向棋盘格拍照,然后放到一个文件夹中,通过下面的程序获得相机内参和畸变参数。主要就是cv2.findChessboardCorners()
找到棋盘格角点的2d坐标,然后cv2.calibrateCamera()
根据角点的2d坐标与3d空间中的位置顺序计算相机内参和畸变参数:
def calib_camera(calib_dir, pattern_size=(8, 6), draw_points=False):
"""
calibrate camera
:param calib_dir: str
:param pattern_size: (x, y), the number of points in x, y axes in the chessboard
:param draw_points: bool, whether to draw the chessboard points
"""
# store 3d object points and 2d image points from all the images
object_points = []
image_points = []
# 3d object point coordinate
xl = np.linspace(0, pattern_size[0], pattern_size[0], endpoint=False)
yl = np.linspace(0, pattern_size[1], pattern_size[1], endpoint=False)
xv, yv = np.meshgrid(xl, yl)
object_point = np.insert(np.stack([xv, yv], axis=-1), 2, 0, axis=-1).astype(np.float32).reshape([-1, 3])
# set termination criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# load image
img_dir = calib_dir
assert os.path.isdir(img_dir), 'Path {} is not a dir'.format(img_dir)
imagenames = os.listdir(img_dir)
for imagename in imagenames:
if not os.path.splitext(imagename)[-1] in ['.jpg', '.png', '.bmp', '.tiff', '.jpeg']:
continue
img_path = os.path.join(img_dir, imagename)
img = cv2.imread(img_path)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# find chessboard points
ret, corners = cv2.findChessboardCorners(img_gray, patternSize=pattern_size)
if ret:
# add the corresponding 3d points to the summary list
object_points.append(object_point)
# if chessboard points are found, refine them to SubPix level (pixel location in float)
corners_refined = cv2.cornerSubPix(img_gray, corners, (11, 11), (-1, -1), criteria)
# add the 2d chessboard points to the summary list
image_points.append(corners.reshape([-1, 2]))
# visualize the points
if draw_points:
cv2.drawChessboardCorners(img, pattern_size, corners_refined, ret)
if img.shape[0] * img.shape[1] > 1e6:
scale = round((1. / (img.shape[0] * img.shape[1] // 1e6)) ** 0.5, 3)
img_draw = cv2.resize(img, (0, 0), fx=scale, fy=scale)
else:
img_draw = img
cv2.imshow('img', img_draw)
cv2.waitKey(0)
assert len(image_points) > 0, 'Cannot find any chessboard points, maybe incorrect pattern_size has been set'
# calibrate the camera, note that ret is the rmse of reprojection error, ret=1 means 1 pixel error
reproj_err, k_cam, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera(object_points,
image_points,
img_gray.shape[::-1],
None,
None,
criteria=criteria)
return k_cam, dist_coeffs
if name == '__main__':
calib_camera('my_dir/')
把上面求出来的相机内参和畸变参数带入cv2.undistort()
函数中去畸变:
dst = cv2.undistort(img, k_cam, dist_coeffs)