Initial commit
BIN
calib_video.avi
Normal file
217
camera_calibration.py
Normal file
@ -0,0 +1,217 @@
|
||||
|
||||
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()
|
||||
BIN
frames/frame_0001.png
Normal file
|
After Width: | Height: | Size: 569 KiB |
BIN
frames/frame_0002.png
Normal file
|
After Width: | Height: | Size: 549 KiB |
BIN
frames/frame_0003.png
Normal file
|
After Width: | Height: | Size: 562 KiB |
BIN
frames/frame_0004.png
Normal file
|
After Width: | Height: | Size: 551 KiB |
BIN
frames/frame_0005.png
Normal file
|
After Width: | Height: | Size: 535 KiB |
BIN
frames/frame_0006.png
Normal file
|
After Width: | Height: | Size: 550 KiB |
BIN
frames/frame_0007.png
Normal file
|
After Width: | Height: | Size: 559 KiB |
BIN
frames/frame_0008.png
Normal file
|
After Width: | Height: | Size: 569 KiB |
BIN
frames/frame_0009.png
Normal file
|
After Width: | Height: | Size: 553 KiB |
BIN
frames/frame_0010.png
Normal file
|
After Width: | Height: | Size: 545 KiB |
BIN
frames/frame_0011.png
Normal file
|
After Width: | Height: | Size: 525 KiB |
BIN
frames/frame_0012.png
Normal file
|
After Width: | Height: | Size: 529 KiB |
BIN
frames/frame_0013.png
Normal file
|
After Width: | Height: | Size: 511 KiB |
BIN
frames/frame_0014.png
Normal file
|
After Width: | Height: | Size: 484 KiB |
BIN
frames/frame_0015.png
Normal file
|
After Width: | Height: | Size: 423 KiB |
BIN
frames/frame_0016.png
Normal file
|
After Width: | Height: | Size: 455 KiB |
BIN
frames/frame_0017.png
Normal file
|
After Width: | Height: | Size: 476 KiB |
BIN
frames/frame_0018.png
Normal file
|
After Width: | Height: | Size: 506 KiB |
BIN
frames/frame_0019.png
Normal file
|
After Width: | Height: | Size: 513 KiB |
BIN
frames/frame_0020.png
Normal file
|
After Width: | Height: | Size: 513 KiB |
BIN
frames/frame_0021.png
Normal file
|
After Width: | Height: | Size: 520 KiB |
BIN
frames/frame_0022.png
Normal file
|
After Width: | Height: | Size: 510 KiB |
BIN
frames/frame_0023.png
Normal file
|
After Width: | Height: | Size: 518 KiB |
BIN
frames/frame_0024.png
Normal file
|
After Width: | Height: | Size: 516 KiB |
BIN
frames/frame_0025.png
Normal file
|
After Width: | Height: | Size: 508 KiB |
BIN
frames/frame_0026.png
Normal file
|
After Width: | Height: | Size: 506 KiB |
BIN
frames/frame_0027.png
Normal file
|
After Width: | Height: | Size: 508 KiB |
BIN
frames/frame_0028.png
Normal file
|
After Width: | Height: | Size: 520 KiB |
BIN
frames/frame_0029.png
Normal file
|
After Width: | Height: | Size: 509 KiB |
BIN
frames/frame_0030.png
Normal file
|
After Width: | Height: | Size: 508 KiB |
BIN
frames/frame_0031.png
Normal file
|
After Width: | Height: | Size: 494 KiB |
BIN
frames/frame_0032.png
Normal file
|
After Width: | Height: | Size: 489 KiB |
BIN
frames/frame_0033.png
Normal file
|
After Width: | Height: | Size: 496 KiB |
BIN
frames/frame_0034.png
Normal file
|
After Width: | Height: | Size: 449 KiB |
BIN
frames/frame_0035.png
Normal file
|
After Width: | Height: | Size: 442 KiB |
BIN
frames/frame_0036.png
Normal file
|
After Width: | Height: | Size: 450 KiB |
BIN
frames/frame_0037.png
Normal file
|
After Width: | Height: | Size: 466 KiB |
BIN
frames/frame_0038.png
Normal file
|
After Width: | Height: | Size: 475 KiB |
BIN
frames/frame_0039.png
Normal file
|
After Width: | Height: | Size: 467 KiB |
BIN
frames/frame_0040.png
Normal file
|
After Width: | Height: | Size: 484 KiB |
BIN
frames/frame_0041.png
Normal file
|
After Width: | Height: | Size: 469 KiB |
BIN
frames/frame_0042.png
Normal file
|
After Width: | Height: | Size: 441 KiB |
BIN
frames/frame_0043.png
Normal file
|
After Width: | Height: | Size: 440 KiB |
BIN
frames/frame_0044.png
Normal file
|
After Width: | Height: | Size: 462 KiB |
BIN
frames/frame_0045.png
Normal file
|
After Width: | Height: | Size: 475 KiB |
BIN
frames/frame_0046.png
Normal file
|
After Width: | Height: | Size: 489 KiB |
BIN
frames/frame_0047.png
Normal file
|
After Width: | Height: | Size: 490 KiB |
BIN
frames/frame_0048.png
Normal file
|
After Width: | Height: | Size: 475 KiB |
BIN
frames/frame_0049.png
Normal file
|
After Width: | Height: | Size: 475 KiB |
BIN
frames/frame_0050.png
Normal file
|
After Width: | Height: | Size: 473 KiB |
BIN
frames/frame_0051.png
Normal file
|
After Width: | Height: | Size: 474 KiB |
BIN
frames/frame_0052.png
Normal file
|
After Width: | Height: | Size: 470 KiB |
BIN
frames/frame_0053.png
Normal file
|
After Width: | Height: | Size: 473 KiB |
BIN
frames/frame_0054.png
Normal file
|
After Width: | Height: | Size: 470 KiB |
BIN
frames/frame_0055.png
Normal file
|
After Width: | Height: | Size: 460 KiB |
BIN
frames/frame_0056.png
Normal file
|
After Width: | Height: | Size: 468 KiB |
BIN
frames/frame_0057.png
Normal file
|
After Width: | Height: | Size: 467 KiB |
BIN
frames/frame_0058.png
Normal file
|
After Width: | Height: | Size: 498 KiB |
BIN
frames/frame_0059.png
Normal file
|
After Width: | Height: | Size: 503 KiB |
BIN
frames/frame_0060.png
Normal file
|
After Width: | Height: | Size: 506 KiB |
BIN
frames/frame_0061.png
Normal file
|
After Width: | Height: | Size: 515 KiB |
BIN
frames/frame_0062.png
Normal file
|
After Width: | Height: | Size: 532 KiB |
BIN
frames/frame_0063.png
Normal file
|
After Width: | Height: | Size: 527 KiB |
BIN
frames/frame_0064.png
Normal file
|
After Width: | Height: | Size: 516 KiB |
BIN
frames/frame_0065.png
Normal file
|
After Width: | Height: | Size: 510 KiB |
BIN
frames/frame_0066.png
Normal file
|
After Width: | Height: | Size: 513 KiB |
BIN
frames/frame_0067.png
Normal file
|
After Width: | Height: | Size: 518 KiB |
BIN
frames/frame_0068.png
Normal file
|
After Width: | Height: | Size: 506 KiB |
BIN
frames/frame_0069.png
Normal file
|
After Width: | Height: | Size: 510 KiB |
BIN
frames/frame_0070.png
Normal file
|
After Width: | Height: | Size: 505 KiB |
BIN
frames/frame_0071.png
Normal file
|
After Width: | Height: | Size: 508 KiB |
BIN
frames/frame_0072.png
Normal file
|
After Width: | Height: | Size: 506 KiB |
BIN
frames/frame_0073.png
Normal file
|
After Width: | Height: | Size: 516 KiB |
BIN
frames/frame_0074.png
Normal file
|
After Width: | Height: | Size: 486 KiB |
BIN
frames/frame_0075.png
Normal file
|
After Width: | Height: | Size: 488 KiB |
BIN
frames/frame_0076.png
Normal file
|
After Width: | Height: | Size: 492 KiB |
BIN
frames/frame_0077.png
Normal file
|
After Width: | Height: | Size: 463 KiB |
BIN
frames/frame_0078.png
Normal file
|
After Width: | Height: | Size: 494 KiB |
BIN
frames/frame_0079.png
Normal file
|
After Width: | Height: | Size: 480 KiB |
BIN
frames/frame_0080.png
Normal file
|
After Width: | Height: | Size: 485 KiB |
BIN
frames/frame_0081.png
Normal file
|
After Width: | Height: | Size: 493 KiB |
BIN
frames/frame_0082.png
Normal file
|
After Width: | Height: | Size: 487 KiB |
BIN
frames/frame_0083.png
Normal file
|
After Width: | Height: | Size: 473 KiB |
BIN
frames/frame_0084.png
Normal file
|
After Width: | Height: | Size: 492 KiB |
BIN
frames/frame_0085.png
Normal file
|
After Width: | Height: | Size: 489 KiB |
BIN
frames/frame_0086.png
Normal file
|
After Width: | Height: | Size: 495 KiB |
BIN
frames/frame_0087.png
Normal file
|
After Width: | Height: | Size: 479 KiB |
BIN
frames/frame_0088.png
Normal file
|
After Width: | Height: | Size: 474 KiB |
BIN
frames/frame_0089.png
Normal file
|
After Width: | Height: | Size: 468 KiB |
BIN
frames/frame_0090.png
Normal file
|
After Width: | Height: | Size: 464 KiB |
BIN
frames/frame_0091.png
Normal file
|
After Width: | Height: | Size: 462 KiB |
BIN
frames/frame_0092.png
Normal file
|
After Width: | Height: | Size: 473 KiB |
BIN
frames/frame_0093.png
Normal file
|
After Width: | Height: | Size: 458 KiB |
BIN
frames/frame_0094.png
Normal file
|
After Width: | Height: | Size: 457 KiB |
BIN
frames/frame_0095.png
Normal file
|
After Width: | Height: | Size: 426 KiB |
BIN
frames/frame_0096.png
Normal file
|
After Width: | Height: | Size: 412 KiB |
BIN
frames/frame_0097.png
Normal file
|
After Width: | Height: | Size: 393 KiB |
BIN
frames/frame_0098.png
Normal file
|
After Width: | Height: | Size: 375 KiB |