3dcv-homework3/camera_calibration.py
github-classroom[bot] 6eab1d902c
Initial commit
2023-11-07 07:43:14 +00:00

218 lines
8.7 KiB
Python

import numpy as np
import cv2 as cv
import glob, sys, argparse
class Calibrator:
def __init__(self, args):
self.args = args
self.inner_w = args.w
self.inner_h = args.h
# termination criteria
self.criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
self.objp = np.zeros((self.inner_w * self.inner_h, 3), np.float32)
self.objp[:,:2] = np.mgrid[0:self.inner_h, 0:self.inner_w].T.reshape(-1,2)
# Arrays to store object points and image points from all the images.
self.objpoints = [] # 3d point in real world space
self.imgpoints = [] # 2d points in image plane.
self.imgs = []
def run(self):
self.video_reader = cv.VideoCapture(args.input)
self.load_images()
assert len(self.imgs) >= 4, print('=> Error: need a least 4 images to calibrate')
ret, self.K, self.dist, self.rvec, self.tvecs = self.calibrate()
print('=> Overall RMS re-projection error: {}'.format(ret))
self.save_result()
if args.show:
self.show_result(self.imgs, self.imgpoints, self.K, self.dist, self.rvec, self.tvecs)
def load_images(self):
#for fname in images:
print('=> press <q> to add image')
print('=> press <space> to exit adding and start calibration')
while True:
#img = cv.imread(fname)
ret, img = self.video_reader.read()
if not ret: break
#img = img[:, ::-1]
cv.imshow('calibration video', img)
key = cv.waitKey(10) #& 0xFF
if key == ord(' '):
self.imgs.append(img)
print('save images: {}'.format(len(self.imgs)))
if key == ord('q'):
break
cv.destroyAllWindows()
def calibrate(self):
print('=> start corner detection and calibration')
for img in self.imgs:
# Find the chess board corners
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, corners = cv.findChessboardCorners(gray, (self.inner_h, self.inner_w), None)
# If found, add object points, image points (after refining them)
if ret == True:
self.objpoints.append(self.objp)
corners2 = cv.cornerSubPix(gray,corners, (11,11), (-1,-1), self.criteria)
self.imgpoints.append(corners2)
# Draw and display the corners
img_ = cv.UMat(img)
cv.drawChessboardCorners(img_, (self.inner_h, self.inner_w), corners2, ret)
cv.imshow('img', img_)
cv.waitKey(45)
cv.destroyAllWindows()
assert len(self.objpoints) > 0, print('=> Error: no corner detected')
assert len(self.objpoints) >= 4, print('=> Error: need a least 4 images to calibrate')
print('=> corners found in {} images'.format(len(self.objpoints)))
#ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
return cv.calibrateCamera(self.objpoints, self.imgpoints, gray.shape[::-1], None, None)
def save_result(self):
print('Camera Intrinsic')
print(self.K)
print('Distortion Coefficients')
print(self.dist)
np.save(self.args.output, {'K': self.K, 'dist': self.dist})
def show_result(self, imgs, imgpoints, mtx, dist, rvecs, tvecs):
try:
import open3d as o3d
except Exception as e:
print(e)
return
coord = np.eye(4)
coord[1, 1] = -1
coord[2, 2] = -1
vis = o3d.visualization.Visualizer()
vis.create_window()
for i in range(self.inner_h+1):
for j in range(self.inner_w+1):
color = [0, 0, 0] if (i + j) % 2 == 0 else [0.9, 0.9, 0.9]
mesh = o3d.geometry.TriangleMesh.create_box(width=1.0, height=1.0, depth=0.2)
mesh.translate((i-1, - j, -0.2))
mesh.paint_uniform_color(color)
vis.add_geometry(mesh)
def expand_batch(_m, _batch_size):
b_m = np.repeat(np.expand_dims(_m, 0), _batch_size, axis=0)
return b_m
def create_camera(_img, _r_mat, _t_vec, _K):
_h, _w = _img.shape[:2]
verts = np.zeros((5, 3)).astype(np.float32)
verts[1:3, 0] = _w
verts[2:4, 1] = _h
verts[:, 2] = 1.
verts[4, 0] = _w / 2
verts[4, 1] = _h / 2
verts = (expand_batch(np.linalg.inv(_K), 5) @ np.expand_dims(verts, -1))
verts[4, -1] = 0.
#verts = expand_batch(coord[:3, :3], 5) @ verts
verts = (expand_batch(_r_mat, 5) @ verts) + _t_vec
lines = [[0, 1], [1, 2], [2, 3], [3, 0], [0, 4], [1, 4], [2, 4], [3, 4]]
colors = [[1, 0, 0]] * len(lines)
cam = o3d.geometry.LineSet()
cam.points = o3d.utility.Vector3dVector(verts.squeeze(-1))
cam.lines = o3d.utility.Vector2iVector(lines)
cam.colors = o3d.utility.Vector3dVector(colors)
extrinsic = np.concatenate([_r_mat, _t_vec], axis=-1)
extrinsic = np.concatenate([extrinsic, np.zeros([1, 4], np.float32)], axis=0)
extrinsic[-1, -1] = 1.
img_rgbd = o3d.geometry.RGBDImage.create_from_color_and_depth(
o3d.geometry.Image(_img[..., ::-1].astype(np.uint8)),
o3d.geometry.Image(np.ones((_h, _w), np.float32)),
1.0, 2.0, False
)
img_ = o3d.geometry.PointCloud.create_from_rgbd_image(
img_rgbd,
o3d.camera.PinholeCameraIntrinsic(_w, _h, _K[0, 0], _K[1, 1], _K[0, 2], _K[1, 2])
)
#img_.transform(coord)
img_.transform(extrinsic.astype(np.float64))
return img_, cam
num_points = self.inner_w * self.inner_h
batch_K_inv = expand_batch(np.linalg.inv(mtx), num_points)
for i, (img, rvec, tvec, imgpoint) in enumerate(zip(imgs, rvecs, tvecs, imgpoints)):
r_mat = cv.Rodrigues(rvec)[0]
Rt = np.concatenate([r_mat, tvec], -1)
Rt = np.concatenate([Rt, np.zeros((1, 4))], 0)
Rt[-1, -1] = 1.
Rt = Rt @ coord
r_mat = Rt[:3, :3]
tvec = Rt[:3, -1]
tvec = np.expand_dims(tvec, -1)
imgpoint_ = np.concatenate([imgpoint, np.ones([imgpoint.shape[0], 1, 1], np.float32)], -1)
campoint = batch_K_inv @ imgpoint_.transpose(0, 2, 1)
worldpoint = expand_batch(r_mat.transpose(), num_points) @ campoint
_Rt = r_mat.transpose() @ tvec
s = worldpoint[:, -1] / _Rt[-1]
s = np.expand_dims(np.repeat(s, 3, axis=1), -1)
corners = (worldpoint / s - _Rt).squeeze(-1)
line = o3d.geometry.LineSet()
line.points = o3d.utility.Vector3dVector(corners)
line.lines = o3d.utility.Vector2iVector([[i, i+1] for i in range(num_points - 1)])
LINE_COLORS = [[1, 0, 0], [1, 0.5, 0], [0.75, 0.75, 0], [0, 1, 0], [0, 0.75, 0.75], [0, 0, 1], [1, 0, 1]]
line_colors = []
for i in range(self.inner_w):
for j in range(self.inner_h):
k = i % len(LINE_COLORS)
line_colors.append(LINE_COLORS[k])
line.colors = o3d.utility.Vector3dVector(line_colors[:-1])
#line.transform(coord)
vis.add_geometry(line)
img_, cam = create_camera(img, r_mat, tvec, mtx)
vis.add_geometry(cam)
vis.add_geometry(img_)
vis.run()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('input',
help='input video for calibration')
parser.add_argument('--output',
default='camera_parameters.npy',
help='npy file of camera parameters')
parser.add_argument('--w',
type=int,
default=6,
help='the width of inner corners of chessboard')
parser.add_argument('--h',
type=int,
default=8,
help='the height of inner corners of chessboard')
parser.add_argument('--show',
action='store_true',
help='to show the 3D visualization of calibration (require install open3D and only work on Linux)')
args = parser.parse_args()
calibrator = Calibrator(args)
calibrator.run()