2021-04-14 15:22:51 +08:00
|
|
|
import cv2
|
|
|
|
import numpy as np
|
|
|
|
import os
|
2022-08-21 16:04:51 +08:00
|
|
|
from os.path import join
|
2021-04-14 15:22:51 +08:00
|
|
|
class FileStorage(object):
|
|
|
|
def __init__(self, filename, isWrite=False):
|
|
|
|
version = cv2.__version__
|
|
|
|
self.major_version = int(version.split('.')[0])
|
|
|
|
self.second_version = int(version.split('.')[1])
|
|
|
|
|
|
|
|
if isWrite:
|
|
|
|
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
2022-08-21 16:04:51 +08:00
|
|
|
self.fs = open(filename, 'w')
|
|
|
|
self.fs.write('%YAML:1.0\r\n')
|
|
|
|
self.fs.write('---\r\n')
|
2021-04-14 15:22:51 +08:00
|
|
|
else:
|
2022-08-21 16:04:51 +08:00
|
|
|
assert os.path.exists(filename), filename
|
2021-04-14 15:22:51 +08:00
|
|
|
self.fs = cv2.FileStorage(filename, cv2.FILE_STORAGE_READ)
|
2022-08-21 16:04:51 +08:00
|
|
|
self.isWrite = isWrite
|
2021-04-14 15:22:51 +08:00
|
|
|
|
|
|
|
def __del__(self):
|
2022-08-21 16:04:51 +08:00
|
|
|
if self.isWrite:
|
|
|
|
self.fs.close()
|
|
|
|
else:
|
|
|
|
cv2.FileStorage.release(self.fs)
|
|
|
|
|
|
|
|
def _write(self, out):
|
|
|
|
self.fs.write(out+'\r\n')
|
2021-04-14 15:22:51 +08:00
|
|
|
|
|
|
|
def write(self, key, value, dt='mat'):
|
|
|
|
if dt == 'mat':
|
2022-08-21 16:04:51 +08:00
|
|
|
self._write('{}: !!opencv-matrix'.format(key))
|
|
|
|
self._write(' rows: {}'.format(value.shape[0]))
|
|
|
|
self._write(' cols: {}'.format(value.shape[1]))
|
|
|
|
self._write(' dt: d')
|
2023-06-19 17:23:44 +08:00
|
|
|
self._write(' data: [{}]'.format(', '.join(['{:.6f}'.format(i) for i in value.reshape(-1)])))
|
2021-04-14 15:22:51 +08:00
|
|
|
elif dt == 'list':
|
2022-08-21 16:04:51 +08:00
|
|
|
self._write('{}:'.format(key))
|
|
|
|
for elem in value:
|
|
|
|
self._write(' - "{}"'.format(elem))
|
2023-06-19 17:23:44 +08:00
|
|
|
elif dt == 'int':
|
|
|
|
self._write('{}: {}'.format(key, value))
|
2021-04-14 15:22:51 +08:00
|
|
|
|
|
|
|
def read(self, key, dt='mat'):
|
|
|
|
if dt == 'mat':
|
|
|
|
output = self.fs.getNode(key).mat()
|
|
|
|
elif dt == 'list':
|
|
|
|
results = []
|
|
|
|
n = self.fs.getNode(key)
|
|
|
|
for i in range(n.size()):
|
|
|
|
val = n.at(i).string()
|
|
|
|
if val == '':
|
|
|
|
val = str(int(n.at(i).real()))
|
|
|
|
if val != 'none':
|
|
|
|
results.append(val)
|
|
|
|
output = results
|
2023-06-19 17:23:44 +08:00
|
|
|
elif dt == 'int':
|
|
|
|
output = int(self.fs.getNode(key).real())
|
2021-04-14 15:22:51 +08:00
|
|
|
else:
|
|
|
|
raise NotImplementedError
|
|
|
|
return output
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
self.__del__(self)
|
|
|
|
|
|
|
|
def read_intri(intri_name):
|
|
|
|
assert os.path.exists(intri_name), intri_name
|
|
|
|
intri = FileStorage(intri_name)
|
|
|
|
camnames = intri.read('names', dt='list')
|
|
|
|
cameras = {}
|
|
|
|
for key in camnames:
|
|
|
|
cam = {}
|
|
|
|
cam['K'] = intri.read('K_{}'.format(key))
|
|
|
|
cam['invK'] = np.linalg.inv(cam['K'])
|
|
|
|
cam['dist'] = intri.read('dist_{}'.format(key))
|
|
|
|
cameras[key] = cam
|
|
|
|
return cameras
|
|
|
|
|
|
|
|
def write_intri(intri_name, cameras):
|
2022-08-21 16:04:51 +08:00
|
|
|
if not os.path.exists(os.path.dirname(intri_name)):
|
|
|
|
os.makedirs(os.path.dirname(intri_name))
|
2021-04-14 15:22:51 +08:00
|
|
|
intri = FileStorage(intri_name, True)
|
|
|
|
results = {}
|
|
|
|
camnames = list(cameras.keys())
|
|
|
|
intri.write('names', camnames, 'list')
|
|
|
|
for key_, val in cameras.items():
|
|
|
|
key = key_.split('.')[0]
|
|
|
|
K, dist = val['K'], val['dist']
|
|
|
|
assert K.shape == (3, 3), K.shape
|
2022-08-21 16:04:51 +08:00
|
|
|
assert dist.shape == (1, 5) or dist.shape == (5, 1) or dist.shape == (1, 4) or dist.shape == (4, 1), dist.shape
|
2021-04-14 15:22:51 +08:00
|
|
|
intri.write('K_{}'.format(key), K)
|
2022-08-21 16:04:51 +08:00
|
|
|
intri.write('dist_{}'.format(key), dist.flatten()[None])
|
2021-04-14 15:22:51 +08:00
|
|
|
|
|
|
|
def write_extri(extri_name, cameras):
|
2022-08-21 16:04:51 +08:00
|
|
|
if not os.path.exists(os.path.dirname(extri_name)):
|
|
|
|
os.makedirs(os.path.dirname(extri_name))
|
2021-04-14 15:22:51 +08:00
|
|
|
extri = FileStorage(extri_name, True)
|
|
|
|
results = {}
|
|
|
|
camnames = list(cameras.keys())
|
|
|
|
extri.write('names', camnames, 'list')
|
|
|
|
for key_, val in cameras.items():
|
|
|
|
key = key_.split('.')[0]
|
|
|
|
extri.write('R_{}'.format(key), val['Rvec'])
|
|
|
|
extri.write('Rot_{}'.format(key), val['R'])
|
|
|
|
extri.write('T_{}'.format(key), val['T'])
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def read_camera(intri_name, extri_name, cam_names=[]):
|
|
|
|
assert os.path.exists(intri_name), intri_name
|
|
|
|
assert os.path.exists(extri_name), extri_name
|
|
|
|
|
|
|
|
intri = FileStorage(intri_name)
|
|
|
|
extri = FileStorage(extri_name)
|
|
|
|
cams, P = {}, {}
|
|
|
|
cam_names = intri.read('names', dt='list')
|
|
|
|
for cam in cam_names:
|
|
|
|
# 内参只读子码流的
|
|
|
|
cams[cam] = {}
|
|
|
|
cams[cam]['K'] = intri.read('K_{}'.format( cam))
|
|
|
|
cams[cam]['invK'] = np.linalg.inv(cams[cam]['K'])
|
2023-06-19 17:23:44 +08:00
|
|
|
H = intri.read('H_{}'.format(cam), dt='int')
|
|
|
|
W = intri.read('W_{}'.format(cam), dt='int')
|
|
|
|
if H is None or W is None:
|
|
|
|
print('[camera] no H or W for {}'.format(cam))
|
|
|
|
H, W = -1, -1
|
|
|
|
cams[cam]['H'] = H
|
|
|
|
cams[cam]['W'] = W
|
2021-04-14 15:22:51 +08:00
|
|
|
Rvec = extri.read('R_{}'.format(cam))
|
|
|
|
Tvec = extri.read('T_{}'.format(cam))
|
2022-08-21 16:04:51 +08:00
|
|
|
assert Rvec is not None, cam
|
2021-04-14 15:22:51 +08:00
|
|
|
R = cv2.Rodrigues(Rvec)[0]
|
|
|
|
RT = np.hstack((R, Tvec))
|
|
|
|
|
|
|
|
cams[cam]['RT'] = RT
|
|
|
|
cams[cam]['R'] = R
|
2022-08-21 16:04:51 +08:00
|
|
|
cams[cam]['Rvec'] = Rvec
|
2021-04-14 15:22:51 +08:00
|
|
|
cams[cam]['T'] = Tvec
|
2022-08-21 16:04:51 +08:00
|
|
|
cams[cam]['center'] = - Rvec.T @ Tvec
|
2021-04-14 15:22:51 +08:00
|
|
|
P[cam] = cams[cam]['K'] @ cams[cam]['RT']
|
|
|
|
cams[cam]['P'] = P[cam]
|
|
|
|
|
|
|
|
cams[cam]['dist'] = intri.read('dist_{}'.format(cam))
|
2023-06-19 17:23:44 +08:00
|
|
|
if cams[cam]['dist'] is None:
|
|
|
|
cams[cam]['dist'] = intri.read('D_{}'.format(cam))
|
|
|
|
if cams[cam]['dist'] is None:
|
|
|
|
print('[camera] no dist for {}'.format(cam))
|
2021-04-14 15:22:51 +08:00
|
|
|
cams['basenames'] = cam_names
|
|
|
|
return cams
|
|
|
|
|
2022-08-21 16:04:51 +08:00
|
|
|
def read_cameras(path, intri='intri.yml', extri='extri.yml', subs=[]):
|
|
|
|
cameras = read_camera(join(path, intri), join(path, extri))
|
|
|
|
cameras.pop('basenames')
|
|
|
|
if len(subs) > 0:
|
|
|
|
cameras = {key:cameras[key].astype(np.float32) for key in subs}
|
|
|
|
return cameras
|
|
|
|
|
2021-04-14 15:22:51 +08:00
|
|
|
def write_camera(camera, path):
|
|
|
|
from os.path import join
|
|
|
|
intri_name = join(path, 'intri.yml')
|
|
|
|
extri_name = join(path, 'extri.yml')
|
|
|
|
intri = FileStorage(intri_name, True)
|
|
|
|
extri = FileStorage(extri_name, True)
|
|
|
|
results = {}
|
|
|
|
camnames = [key_.split('.')[0] for key_ in camera.keys()]
|
|
|
|
intri.write('names', camnames, 'list')
|
|
|
|
extri.write('names', camnames, 'list')
|
|
|
|
for key_, val in camera.items():
|
|
|
|
if key_ == 'basenames':
|
|
|
|
continue
|
|
|
|
key = key_.split('.')[0]
|
|
|
|
intri.write('K_{}'.format(key), val['K'])
|
|
|
|
intri.write('dist_{}'.format(key), val['dist'])
|
2023-06-19 17:23:44 +08:00
|
|
|
if 'H' in val.keys() and 'W' in val.keys():
|
|
|
|
intri.write('H_{}'.format(key), val['H'], dt='int')
|
|
|
|
intri.write('W_{}'.format(key), val['W'], dt='int')
|
2023-07-25 13:54:46 +08:00
|
|
|
assert val['R'].shape == (3, 3), f"{val['R'].shape} must == (3, 3)"
|
2021-04-14 15:22:51 +08:00
|
|
|
if 'Rvec' not in val.keys():
|
|
|
|
val['Rvec'] = cv2.Rodrigues(val['R'])[0]
|
|
|
|
extri.write('R_{}'.format(key), val['Rvec'])
|
|
|
|
extri.write('Rot_{}'.format(key), val['R'])
|
|
|
|
extri.write('T_{}'.format(key), val['T'])
|
|
|
|
|
2021-05-27 21:12:17 +08:00
|
|
|
def camera_from_img(img):
|
|
|
|
height, width = img.shape[0], img.shape[1]
|
|
|
|
# focal = 1.2*max(height, width) # as colmap
|
|
|
|
focal = 1.2*min(height, width) # as colmap
|
|
|
|
K = np.array([focal, 0., width/2, 0., focal, height/2, 0. ,0., 1.]).reshape(3, 3)
|
|
|
|
camera = {'K':K ,'R': np.eye(3), 'T': np.zeros((3, 1)), 'dist': np.zeros((1, 5))}
|
2022-08-21 16:04:51 +08:00
|
|
|
camera['invK'] = np.linalg.inv(camera['K'])
|
|
|
|
camera['P'] = camera['K'] @ np.hstack((camera['R'], camera['T']))
|
2021-05-27 21:12:17 +08:00
|
|
|
return camera
|
|
|
|
|
2021-04-14 15:22:51 +08:00
|
|
|
class Undistort:
|
2022-08-21 16:04:51 +08:00
|
|
|
distortMap = {}
|
|
|
|
@classmethod
|
2023-06-19 17:23:44 +08:00
|
|
|
def image(cls, frame, K, dist, sub=None, interp=cv2.INTER_NEAREST):
|
2022-08-21 16:04:51 +08:00
|
|
|
if sub is None:
|
|
|
|
return cv2.undistort(frame, K, dist, None)
|
|
|
|
else:
|
|
|
|
if sub not in cls.distortMap.keys():
|
|
|
|
h, w = frame.shape[:2]
|
|
|
|
mapx, mapy = cv2.initUndistortRectifyMap(K, dist, None, K, (w,h), 5)
|
|
|
|
cls.distortMap[sub] = (mapx, mapy)
|
|
|
|
mapx, mapy = cls.distortMap[sub]
|
2023-06-19 17:23:44 +08:00
|
|
|
img = cv2.remap(frame, mapx, mapy, interp)
|
2022-08-21 16:04:51 +08:00
|
|
|
return img
|
2021-04-14 15:22:51 +08:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def points(keypoints, K, dist):
|
|
|
|
# keypoints: (N, 3)
|
|
|
|
assert len(keypoints.shape) == 2, keypoints.shape
|
|
|
|
kpts = keypoints[:, None, :2]
|
|
|
|
kpts = np.ascontiguousarray(kpts)
|
|
|
|
kpts = cv2.undistortPoints(kpts, K, dist, P=K)
|
2022-10-25 20:57:27 +08:00
|
|
|
keypoints = np.hstack([kpts[:, 0], keypoints[:, 2:]])
|
2021-04-14 15:22:51 +08:00
|
|
|
return keypoints
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def bbox(bbox, K, dist):
|
|
|
|
keypoints = np.array([[bbox[0], bbox[1], 1], [bbox[2], bbox[3], 1]])
|
|
|
|
kpts = Undistort.points(keypoints, K, dist)
|
|
|
|
bbox = np.array([kpts[0, 0], kpts[0, 1], kpts[1, 0], kpts[1, 1], bbox[4]])
|
|
|
|
return bbox
|
|
|
|
|
2023-06-19 17:23:44 +08:00
|
|
|
class Distort:
|
|
|
|
@staticmethod
|
|
|
|
def points(keypoints, K, dist):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def bbox(bbox, K, dist):
|
|
|
|
keypoints = np.array([[bbox[0], bbox[1]], [bbox[2], bbox[3]]], dtype=np.float32)
|
|
|
|
k3d = cv2.convertPointsToHomogeneous(keypoints)
|
|
|
|
k3d = (np.linalg.inv(K) @ k3d[:, 0].T).T[:, None]
|
|
|
|
k2d, _ = cv2.projectPoints(k3d, np.zeros((3,)), np.zeros((3,)), K, dist)
|
|
|
|
k2d = k2d[:, 0]
|
|
|
|
bbox = np.array([k2d[0,0], k2d[0,1], k2d[1, 0], k2d[1, 1], bbox[-1]])
|
|
|
|
return bbox
|
|
|
|
|
2022-08-21 16:04:51 +08:00
|
|
|
def unproj(kpts, invK):
|
|
|
|
homo = np.hstack([kpts[:, :2], np.ones_like(kpts[:, :1])])
|
|
|
|
homo = homo @ invK.T
|
|
|
|
return np.hstack([homo[:, :2], kpts[:, 2:]])
|
|
|
|
class UndistortFisheye:
|
|
|
|
@staticmethod
|
|
|
|
def image(frame, K, dist):
|
|
|
|
Knew = K.copy()
|
|
|
|
frame = cv2.fisheye.undistortImage(frame, K, dist, Knew=Knew)
|
|
|
|
return frame, Knew
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def points(keypoints, K, dist, Knew):
|
|
|
|
# keypoints: (N, 3)
|
|
|
|
assert len(keypoints.shape) == 2, keypoints.shape
|
|
|
|
kpts = keypoints[:, None, :2]
|
|
|
|
kpts = np.ascontiguousarray(kpts)
|
|
|
|
kpts = cv2.fisheye.undistortPoints(kpts, K, dist, P=Knew)
|
2022-10-25 20:57:27 +08:00
|
|
|
keypoints = np.hstack([kpts[:, 0], keypoints[:, 2:]])
|
2022-08-21 16:04:51 +08:00
|
|
|
return keypoints
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def bbox(bbox, K, dist, Knew):
|
|
|
|
keypoints = np.array([[bbox[0], bbox[1], 1], [bbox[2], bbox[3], 1]])
|
|
|
|
kpts = UndistortFisheye.points(keypoints, K, dist, Knew)
|
|
|
|
bbox = np.array([kpts[0, 0], kpts[0, 1], kpts[1, 0], kpts[1, 1], bbox[4]])
|
|
|
|
return bbox
|
|
|
|
|
|
|
|
|
|
|
|
def get_Pall(cameras, camnames):
|
|
|
|
Pall = np.stack([cameras[cam]['K'] @ np.hstack((cameras[cam]['R'], cameras[cam]['T'])) for cam in camnames])
|
|
|
|
return Pall
|
2021-04-14 15:22:51 +08:00
|
|
|
|
|
|
|
def get_fundamental_matrix(cameras, basenames):
|
|
|
|
skew_op = lambda x: np.array([[0, -x[2], x[1]], [x[2], 0, -x[0]], [-x[1], x[0], 0]])
|
|
|
|
fundamental_op = lambda K_0, R_0, T_0, K_1, R_1, T_1: np.linalg.inv(K_0).T @ (
|
|
|
|
R_0 @ R_1.T) @ K_1.T @ skew_op(K_1 @ R_1 @ R_0.T @ (T_0 - R_0 @ R_1.T @ T_1))
|
|
|
|
fundamental_RT_op = lambda K_0, RT_0, K_1, RT_1: fundamental_op (K_0, RT_0[:, :3], RT_0[:, 3], K_1,
|
|
|
|
RT_1[:, :3], RT_1[:, 3] )
|
|
|
|
F = np.zeros((len(basenames), len(basenames), 3, 3)) # N x N x 3 x 3 matrix
|
|
|
|
F = {(icam, jcam): np.zeros((3, 3)) for jcam in basenames for icam in basenames}
|
|
|
|
for icam in basenames:
|
|
|
|
for jcam in basenames:
|
|
|
|
F[(icam, jcam)] += fundamental_RT_op(cameras[icam]['K'], cameras[icam]['RT'], cameras[jcam]['K'], cameras[jcam]['RT'])
|
|
|
|
if F[(icam, jcam)].sum() == 0:
|
|
|
|
F[(icam, jcam)] += 1e-12 # to avoid nan
|
|
|
|
return F
|
2022-08-21 16:04:51 +08:00
|
|
|
|
|
|
|
def interp_cameras(cameras, keys, step=20, loop=True, allstep=-1, **kwargs):
|
|
|
|
from scipy.spatial.transform import Rotation as R
|
|
|
|
from scipy.spatial.transform import Slerp
|
|
|
|
if allstep != -1:
|
|
|
|
tall = np.linspace(0., 1., allstep+1)[:-1].reshape(-1, 1, 1)
|
|
|
|
elif allstep == -1 and loop:
|
|
|
|
tall = np.linspace(0., 1., 1+step*len(keys))[:-1].reshape(-1, 1, 1)
|
|
|
|
elif allstep == -1 and not loop:
|
|
|
|
tall = np.linspace(0., 1., 1+step*(len(keys)-1))[:-1].reshape(-1, 1, 1)
|
|
|
|
cameras_new = {}
|
|
|
|
for ik in range(len(keys)):
|
|
|
|
if ik == len(keys) -1 and not loop:
|
|
|
|
break
|
|
|
|
if loop:
|
|
|
|
start, end = (ik * tall.shape[0])//len(keys), int((ik+1)*tall.shape[0])//len(keys)
|
|
|
|
print(ik, start, end, tall.shape)
|
|
|
|
else:
|
|
|
|
start, end = (ik * tall.shape[0])//(len(keys)-1), int((ik+1)*tall.shape[0])//(len(keys)-1)
|
|
|
|
t = tall[start:end].copy()
|
|
|
|
t = (t-t.min())/(t.max()-t.min())
|
|
|
|
left, right = keys[ik], keys[0 if ik == len(keys)-1 else ik + 1]
|
|
|
|
camera_left = cameras[left]
|
|
|
|
camera_right = cameras[right]
|
|
|
|
# 插值相机中心: center = - R.T @ T
|
|
|
|
center_l = - camera_left['R'].T @ camera_left['T']
|
|
|
|
center_r = - camera_right['R'].T @ camera_right['T']
|
|
|
|
center_l, center_r = center_l[None], center_r[None]
|
|
|
|
if False:
|
|
|
|
centers = center_l * (1-t) + center_r * t
|
|
|
|
else:
|
|
|
|
# 球面插值
|
|
|
|
norm_l, norm_r = np.linalg.norm(center_l), np.linalg.norm(center_r)
|
|
|
|
center_l, center_r = center_l/norm_l, center_r/norm_r
|
|
|
|
costheta = (center_l*center_r).sum()
|
|
|
|
sintheta = np.sqrt(1. - costheta**2)
|
|
|
|
theta = np.arctan2(sintheta, costheta)
|
|
|
|
centers = (np.sin(theta*(1-t)) * center_l + np.sin(theta * t) * center_r)/sintheta
|
|
|
|
norm = norm_l * (1-t) + norm_r * t
|
|
|
|
centers = centers * norm
|
|
|
|
key_rots = R.from_matrix(np.stack([camera_left['R'], camera_right['R']]))
|
|
|
|
key_times = [0, 1]
|
|
|
|
slerp = Slerp(key_times, key_rots)
|
|
|
|
interp_rots = slerp(t.squeeze()).as_matrix()
|
|
|
|
# 计算相机T RX + T = 0 => T = - R @ X
|
|
|
|
T = - np.einsum('bmn,bno->bmo', interp_rots, centers)
|
|
|
|
K = camera_left['K'] * (1-t) + camera_right['K'] * t
|
|
|
|
for i in range(T.shape[0]):
|
|
|
|
cameras_new['{}-{}-{}'.format(left, right, i)] = \
|
|
|
|
{
|
|
|
|
'K': K[i],
|
|
|
|
'dist': np.zeros((1, 5)),
|
|
|
|
'R': interp_rots[i],
|
|
|
|
'T': T[i]
|
|
|
|
}
|
|
|
|
return cameras_new
|