diff --git a/apps/python-calibration-generator/board.py b/apps/python-calibration-generator/board.py deleted file mode 100644 index da9dec83c4..0000000000 --- a/apps/python-calibration-generator/board.py +++ /dev/null @@ -1,59 +0,0 @@ -# This file is part of OpenCV project. -# It is subject to the license terms in the LICENSE file found in the top-level directory -# of this distribution and at http://opencv.org/license.html. - -import numpy as np - -class Board: - def __init__(self, w, h, square_len, euler_limit, t_limit, t_origin=None): - assert w >= 0 and h >= 0 and square_len >= 0 - assert len(euler_limit) == len(t_limit) == 3 - self.w = w - self.h = h - self.square_len = square_len - - self.t_limit = t_limit - self.euler_limit = np.array(euler_limit, dtype=np.float32) - colors = [[1,0,0], [0,1,0], [0,0,0], [0,0,1]] - self.colors_board = np.zeros((w*h, 3)) - self.t_origin = np.array(t_origin, dtype=np.float32)[:,None] if t_origin is not None else None - for i in range(h): - for j in range(w): - if j <= w // 2 and i <= h // 2: color = colors[0] - elif j <= w // 2 and i > h // 2: color = colors[1] - elif j > w // 2 and i <= h // 2: color = colors[2] - else: color = colors[3] - self.colors_board[i*w+j] = color - for i in range(3): - assert len(euler_limit[i]) == len(t_limit[i]) == 2 - self.euler_limit[i] *= (np.pi / 180) - - def isProjectionValid(self, pts_proj): - """ - projection is valid, if x coordinate of left top corner point is smaller than x of bottom right point, ie do not allow 90 deg rotation of 2D board - also, if x coordinate of left bottom corner is smaller than x coordinate on top right corner, ie do not allow flip - pts_proj : 2 x N - """ - assert pts_proj.ndim == 2 and pts_proj.shape[0] == 2 - # pdb.set_trace() - return pts_proj[0,0] < pts_proj[0,-1] and pts_proj[0,(self.h-1)*self.w] < pts_proj[0,self.w-1] - -class CircleBoard(Board): - def __init__(self, w, h, square_len, euler_limit, t_limit, t_origin=None): - super().__init__(w, h, square_len, euler_limit, t_limit, t_origin) - self.pattern = [] - for row in range(h): - for col in range(w): - if row % 2 == 1: - self.pattern.append([(col+.5)*square_len, square_len*(row//2+.5), 0]) - else: - self.pattern.append([col*square_len, (row//2)*square_len, 0]) - self.pattern = np.array(self.pattern, dtype=np.float32).T - -class CheckerBoard(Board): - def __init__(self, w, h, square_len, euler_limit, t_limit, t_origin=None): - super().__init__(w, h, square_len, euler_limit, t_limit, t_origin) - self.pattern = np.zeros((w * h, 3), np.float32) - # https://stackoverflow.com/questions/37310210/camera-calibration-with-opencv-how-to-adjust-chessboard-square-size - self.pattern[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2) * square_len # only for (x,y,z=0) - self.pattern = self.pattern.T diff --git a/apps/python-calibration-generator/calibration_generator.py b/apps/python-calibration-generator/calibration_generator.py deleted file mode 100644 index 81b465ef36..0000000000 --- a/apps/python-calibration-generator/calibration_generator.py +++ /dev/null @@ -1,362 +0,0 @@ -# This file is part of OpenCV project. -# It is subject to the license terms in the LICENSE file found in the top-level directory -# of this distribution and at http://opencv.org/license.html. - -# The script generates synthetic data for multi-camera calibration assessment -# Input: cameras configuration. See config_cv_test.yaml -# Output: generated object points (3d), image points (2d) for calibration and -# board poses ground truth (R, t) for check - -import argparse -import numpy as np -import math -import yaml -from drawer import animation2D, animation3D -from utils import RandGen, insideImage, eul2rot, saveKDRT, areAllInsideImage, insideImageMask, projectCamera, export2JSON, writeMatrix -from pathlib import Path -from board import CheckerBoard -import os -import json - -class Camera: - def __init__(self, idx, img_width, img_height, fx_limit, euler_limit, t_limit, is_fisheye, fy_deviation=None, skew=None, - distortion_limit=None, noise_scale_img_diag=None): - """ - @skew : is either None or in radians - @fy_deviation : is either None (that is fx=fy) or value such that fy = [fx*(1-fy_deviation/100), fx*(1+fy_deviation/100)] - @distortion_limit : is either None or array of size (num_tangential_dist+num_radial_dist) x 2 - @euler_limit : is 3 x 2 limit of euler angles in degrees - @t_limit : is 3 x 2 limit of translation in meters - """ - assert len(fx_limit) == 2 and img_width >= 0 and img_width >= 0 - if is_fisheye and distortion_limit is not None: assert len(distortion_limit) == 4 # distortion for fisheye has only 4 parameters - self.idx = idx - self.img_width, self.img_height = img_width, img_height - self.fx_min = fx_limit[0] - self.fx_max = fx_limit[1] - self.fy_deviation = fy_deviation - self.img_diag = math.sqrt(img_height ** 2 + img_width ** 2) - self.is_fisheye = is_fisheye - self.fx, self.fy = None, None - self.px, self.py = None, None - self.K, self.R, self.t, self.P = None, None, None, None - self.skew = skew - self.distortion = None - self.distortion_lim = distortion_limit - self.euler_limit = np.array(euler_limit, dtype=np.float32) - self.t_limit = t_limit - self.noise_scale_img_diag = noise_scale_img_diag - if idx != 0: - assert len(euler_limit) == len(t_limit) == 3 - for i in range(3): - assert len(euler_limit[i]) == len(t_limit[i]) == 2 - self.euler_limit[i] *= (np.pi / 180) - -def generateAll(cameras, board, num_frames, rand_gen, MAX_RAND_ITERS=10000, save_proj_animation=None, save_3d_animation=None): - EPS = 1e-10 - """ - output: - points_2d: NUM_FRAMES x NUM_CAMERAS x 2 x NUM_PTS - """ - - for i in range(len(cameras)): - cameras[i].t = np.zeros((3, 1)) - if cameras[i].idx == 0: - cameras[i].R = np.identity(3) - else: - angles = [0, 0, 0] - for k in range(3): - if abs(cameras[i].t_limit[k][0] - cameras[i].t_limit[k][1]) < EPS: - cameras[i].t[k] = cameras[i].t_limit[k][0] - else: - cameras[i].t[k] = rand_gen.randRange(cameras[i].t_limit[k][0], cameras[i].t_limit[k][1]) - - if abs(cameras[i].euler_limit[k][0] - cameras[i].euler_limit[k][1]) < EPS: - angles[k] = cameras[i].euler_limit[k][0] - else: - angles[k] = rand_gen.randRange(cameras[i].euler_limit[k][0], cameras[i].euler_limit[k][1]) - - cameras[i].R = eul2rot(angles) - - if abs(cameras[i].fx_min - cameras[i].fx_max) < EPS: - cameras[i].fx = cameras[i].fx_min - else: - cameras[i].fx = rand_gen.randRange(cameras[i].fx_min, cameras[i].fx_max) - if cameras[i].fy_deviation is None: - cameras[i].fy = cameras[i].fx - else: - cameras[i].fy = rand_gen.randRange((1 - cameras[i].fy_deviation) * cameras[i].fx, - (1 + cameras[i].fy_deviation) * cameras[i].fx) - - cameras[i].px = int(cameras[i].img_width / 2.0) + 1 - cameras[i].py = int(cameras[i].img_height / 2.0) + 1 - cameras[i].K = np.array([[cameras[i].fx, 0, cameras[i].px], [0, cameras[i].fy, cameras[i].py], [0, 0, 1]], dtype=float) - if cameras[i].skew is not None: cameras[i].K[0, 1] = np.tan(cameras[i].skew) * cameras[i].K[0, 0] - cameras[i].P = cameras[i].K @ np.concatenate((cameras[i].R, cameras[i].t), 1) - - if cameras[i].distortion_lim is not None: - cameras[i].distortion = np.zeros((1, len(cameras[i].distortion_lim))) # opencv using 5 values distortion as default - for k, lim in enumerate(cameras[i].distortion_lim): - cameras[i].distortion[0,k] = rand_gen.randRange(lim[0], lim[1]) - else: - cameras[i].distortion = np.zeros((1, 5)) # opencv is using 5 values distortion as default - - origin = None - box = np.array([[0, board.square_len * (board.w - 1), 0, board.square_len * (board.w - 1)], - [0, 0, board.square_len * (board.h - 1), board.square_len * (board.h - 1)], - [0, 0, 0, 0]]) - - if board.t_origin is None: - try: - import torch, pytorch3d, pytorch3d.transforms - has_pytorch = True - except: - has_pytorch = False - - if has_pytorch: - rot_angles = torch.zeros(3, requires_grad=True) - origin = torch.ones((3,1), requires_grad=True) - optimizer = torch.optim.Adam([rot_angles, origin], lr=5e-3) - Ps = torch.tensor(np.stack([cam.K @ np.concatenate((cam.R, cam.t), 1) for cam in cameras]), dtype=torch.float32) - rot_conv = 'XYZ' - board_pattern = torch.tensor(box, dtype=Ps.dtype) - corners = torch.tensor([[[0, 0], [0, cam.img_height], [cam.img_width, 0], [cam.img_width, cam.img_height]] for cam in cameras], dtype=Ps.dtype).transpose(-1,-2) - loss_fnc = torch.nn.HuberLoss() - lr_scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', min_lr=1e-4, factor=0.8, patience=10) - prev_loss = 1e10 - torch.autograd.set_detect_anomaly(True) - MAX_DEPTH = 4 - for it in range(500): - pts_board = pytorch3d.transforms.euler_angles_to_matrix(rot_angles, rot_conv) @ board_pattern + origin - pts_proj = Ps[:,:3,:3] @ pts_board[None,:] + Ps[:,:,[-1]] - pts_proj = pts_proj[:, :2] / (pts_proj[:, [2]]+1e-15) - - loss = num_wrong = 0 - for i, proj in enumerate(pts_proj): - if not areAllInsideImage(pts_proj[i], cameras[i].img_width, cameras[i].img_height): - loss += loss_fnc(corners[i], pts_proj[i]) - num_wrong += 1 - if num_wrong > 0: - loss /= num_wrong - loss.backward() - optimizer.step() - lr_scheduler.step(loss) - if origin[2] < 0: - with torch.no_grad(): origin[2] = 2.0 - if it % 5 == 0: - print('iter', it, 'loss %.2E' % loss) - if abs(prev_loss - loss) < 1e-10: - break - prev_loss = loss.item() - else: - print('all points inside') - break - print(origin) - points_board = (torch.tensor(board.pattern, dtype=Ps.dtype) + origin).detach().numpy() - else: - max_sum_diag = 0.0 - total_tested = 0 - for z in np.arange(0.25, 50, .5): - if origin is not None: break # will not update - min_x1, max_x1 = -z * cameras[0].px / cameras[0].fx, (cameras[0].img_width * z - z * cameras[0].px) / cameras[0].fx - min_y1, max_y1 = -z * cameras[0].py / cameras[0].fy, (cameras[0].img_height * z - z * cameras[0].py) / cameras[0].fy - min_x2, max_x2 = -z * cameras[0].px / cameras[0].fx - box[0, 1], (cameras[0].img_width * z - z * cameras[0].px) / cameras[0].fx - box[0, 1] - min_y2, max_y2 = -z * cameras[0].py / cameras[0].fy - box[1, 2], (cameras[0].img_height * z - z * cameras[0].py) / cameras[0].fy - box[1, 2] - min_x = max(min_x1, min_x2) - min_y = max(min_y1, min_y2) - max_x = min(max_x1, max_x2) - max_y = min(max_y1, max_y2) - if max_x < min_x or max_y < min_y: continue - for x in np.linspace(min_x, max_x, 40): - for y in np.linspace(min_y, max_y, 40): - total_tested += 1 - pts = box + np.array([[x], [y], [z]]) - sum_diag = 0.0 - all_visible = True - for i in range(len(cameras)): - pts_proj = projectCamera(cameras[i], pts) - visible_pts = insideImage(pts_proj, cameras[i].img_width, cameras[i].img_height) - if visible_pts != pts_proj.shape[1]: - # print(i,')',x, y, z, 'not visible, total', visible_pts, '/', pts_proj.shape[1]) - all_visible = False - break - sum_diag += np.linalg.norm(pts_proj[:, 0] - pts_proj[:, -1]) - if not all_visible: continue - if max_sum_diag < sum_diag: - max_sum_diag = sum_diag - origin = np.array([[x], [y], [z]]) - points_board = board.pattern + origin - else: - points_board = board.pattern + board.t_origin - - points_2d, points_3d = [], [] - valid_frames_per_camera = np.zeros(len(cameras)) - MIN_FRAMES_PER_CAM = int(num_frames * 0.1) - R_used = [] - t_used = [] - for frame in range(MAX_RAND_ITERS): - R_board = eul2rot([ rand_gen.randRange(board.euler_limit[0][0], board.euler_limit[0][1]), - rand_gen.randRange(board.euler_limit[1][0], board.euler_limit[1][1]), - rand_gen.randRange(board.euler_limit[2][0], board.euler_limit[2][1])]) - t_board = np.array([[rand_gen.randRange(board.t_limit[0][0], board.t_limit[0][1])], - [rand_gen.randRange(board.t_limit[1][0], board.t_limit[1][1])], - [rand_gen.randRange(board.t_limit[2][0], board.t_limit[2][1])]]) - - points_board_mean = points_board.mean(-1)[:,None] - pts_board = R_board @ (points_board - points_board_mean) + points_board_mean + t_board - cam_points_2d = [projectCamera(cam, pts_board) for cam in cameras] - - """ - # plot normals - board_normal = 10*np.cross(pts_board[:,board.w] - pts_board[:,0], pts_board[:,board.w-1] - pts_board[:,0]) - ax = plotCamerasAndBoardFig(pts_board, cameras, pts_color=board.colors_board) - pts = np.stack((pts_board[:,0], pts_board[:,0]+board_normal)) - ax.plot(pts[:,0], pts[:,1], pts[:,2], 'r-') - for ii, cam in enumerate(cameras): - pts = np.stack((cam.t.flatten(), cam.t.flatten()+cam.R[2])) - ax.plot(pts[:,0], pts[:,1], pts[:,2], 'g-') - print(ii, np.arccos(board_normal.dot(cam.R[2]) / np.linalg.norm(board_normal))*180/np.pi, np.arccos((-board_normal).dot(cam.R[2]) / np.linalg.norm(board_normal))*180/np.pi) - plotAllProjectionsFig(np.stack(cam_points_2d), cameras, pts_color=board.colors_board) - plt.show() - """ - - for cam_idx in range(len(cameras)): - # Check whether the board is in front of the the image - pt_3d = cameras[cam_idx].R @ pts_board + cameras[cam_idx].t - if not board.isProjectionValid(cam_points_2d[cam_idx]) or np.min(pt_3d[2]) < 1e-3: - cam_points_2d[cam_idx] = -np.ones_like(cam_points_2d[cam_idx]) - elif cameras[cam_idx].noise_scale_img_diag is not None: - cam_points_2d[cam_idx] += np.random.normal(0, cameras[cam_idx].img_diag * cameras[cam_idx].noise_scale_img_diag, cam_points_2d[cam_idx].shape) - - ### test - pts_inside_camera = np.zeros(len(cameras), dtype=bool) - for ii, pts_2d in enumerate(cam_points_2d): - mask = insideImageMask(pts_2d, cameras[ii].img_width, cameras[ii].img_height) - # cam_points_2d[ii] = cam_points_2d[ii][:,mask] - pts_inside_camera[ii] = mask.all() - # print(pts_inside, end=' ') - # print('from max inside', pts_board.shape[1]) - ### - - if pts_inside_camera.sum() >= 2: - valid_frames_per_camera += np.array(pts_inside_camera, int) - print(valid_frames_per_camera) - points_2d.append(np.stack(cam_points_2d)) - points_3d.append(pts_board) - - R_used.append(R_board) - t_used.append(R_board @ (board.t_origin - points_board_mean) + points_board_mean + t_board) - - if len(points_2d) >= num_frames and (valid_frames_per_camera >= MIN_FRAMES_PER_CAM).all(): - print('tried samples', frame) - break - - VIDEOS_FPS = 5 - VIDEOS_DPI = 250 - MAX_FRAMES = 100 - if save_proj_animation is not None: animation2D(board, cameras, points_2d, save_proj_animation, VIDEOS_FPS, VIDEOS_DPI, MAX_FRAMES) - if save_3d_animation is not None: animation3D(board, cameras, points_3d, save_3d_animation, VIDEOS_FPS, VIDEOS_DPI, MAX_FRAMES) - - print('number of found frames', len(points_2d)) - return np.stack(points_2d), np.stack(points_3d), np.stack(R_used), np.stack(t_used) - -def createConfigFile(fname, params): - file = open(fname, 'w') - - def writeDict(dict_write, tab): - for key, value in dict_write.items(): - if isinstance(value, dict): - file.write(tab+key+' :\n') - writeDict(value, tab+' ') - else: - file.write(tab+key+' : '+str(value)+'\n') - file.write('\n') - writeDict(params, '') - file.close() - -def generateRoomConfiguration(): - params = {'NAME' : '"room_corners"', 'NUM_SAMPLES': 1, 'SEED': 0, 'MAX_FRAMES' : 50, 'MAX_RANDOM_ITERS' : 100000, 'NUM_CAMERAS': 4, - 'BOARD': {'WIDTH':9, 'HEIGHT':7, 'SQUARE_LEN':0.08, 'T_LIMIT': [[-0.2,0.2], [-0.2,0.2], [-0.1,0.1]], 'EULER_LIMIT': [[-45, 45], [-180, 180], [-45, 45]], 'T_ORIGIN': [-0.3,0,1.5]}} - params['CAMERA1'] = {'FX': [1200, 1200], 'FY_DEVIATION': 'null', 'IMG_WIDTH': 1500, 'IMG_HEIGHT': 1080, 'EULER_LIMIT': 'null', 'T_LIMIT': 'null', 'NOISE_SCALE': 3.0e-4, 'FISHEYE': False, 'DIST': [[5.2e-1,5.2e-1], [0,0], [0,0], [0,0], [0,0]]} - params['CAMERA2'] = {'FX': [1000, 1000], 'FY_DEVIATION': 'null', 'IMG_WIDTH': 1300, 'IMG_HEIGHT': 1000, 'EULER_LIMIT': [[0,0], [90,90], [0,0]], 'T_LIMIT': [[-2.0,-2.0], [0.0, 0.0], [1.5, 1.5]], 'NOISE_SCALE': 3.5e-4, 'FISHEYE': False, 'DIST': [[3.2e-1,3.2e-1], [0,0], [0,0], [0,0], [0,0]]} - params['CAMERA3'] = {'FX': [1000, 1000], 'FY_DEVIATION': 'null', 'IMG_WIDTH': 1300, 'IMG_HEIGHT': 1000, 'EULER_LIMIT': [[0,0], [-90,-90], [0,0]], 'T_LIMIT': [[2.0,2.0], [0.0, 0.0], [1.5, 1.5]], 'NOISE_SCALE': 4.0e-4, 'FISHEYE': False, 'DIST': [[6.2e-1,6.2e-1], [0,0], [0,0], [0,0], [0,0]]} - params['CAMERA4'] = {'FX': [1000, 1000], 'FY_DEVIATION': 'null', 'IMG_WIDTH': 1300, 'IMG_HEIGHT': 1000, 'EULER_LIMIT': [[0,0], [180,180], [0,0]], 'T_LIMIT': [[0.0,0.0], [0.0, 0.0], [3.0, 3.0]], 'NOISE_SCALE': 3.2e-4, 'FISHEYE': False, 'DIST': [[4.2e-1,4.2e-1], [0,0], [0,0], [0,0], [0,0]]} - createConfigFile('python/configs/config_room_corners.yaml', params) - -def generateCircularCameras(): - rand_gen = RandGen(0) - params = {'NAME' : '"circular"', 'NUM_SAMPLES': 1, 'SEED': 0, 'MAX_FRAMES' : 70, 'MAX_RANDOM_ITERS' : 100000, 'NUM_CAMERAS': 9, - 'BOARD': {'WIDTH': 9, 'HEIGHT': 7, 'SQUARE_LEN':0.08, 'T_LIMIT': [[-0.2,0.2], [-0.2,0.2], [-0.1,0.1]], 'EULER_LIMIT': [[-45, 45], [-180, 180], [-45, 45]], 'T_ORIGIN': [-0.3,0,2.2]}} - - dist = 1.1 - xs = np.arange(dist, dist*(params['NUM_CAMERAS']//4)+1e-3, dist) - xs = np.concatenate((xs, xs[::-1])) - xs = np.concatenate((xs, -xs)) - dist_z = 0.90 - zs = np.arange(dist_z, dist_z*(params['NUM_CAMERAS']//2)+1e-3, dist_z) - zs = np.concatenate((zs, zs[::-1])) - yaw = np.linspace(0, -360, params['NUM_CAMERAS']+1)[1:-1] - for i in range(9): - fx = rand_gen.randRange(900, 1300) - d0 = rand_gen.randRange(4e-1, 7e-1) - euler_limit = 'null' - t_limit = 'null' - if i > 0: - euler_limit = [[0,0], [yaw[i-1], yaw[i-1]], [0,0]] - t_limit = [[xs[i-1], xs[i-1]], [0,0], [zs[i-1], zs[i-1]]] - params['CAMERA'+str((i+1))] = {'FX': [fx, fx], 'FY_DEVIATION': 'null', 'IMG_WIDTH': int(rand_gen.randRange(1200, 1600)), 'IMG_HEIGHT': int(rand_gen.randRange(800, 1200)), - 'EULER_LIMIT': euler_limit, 'T_LIMIT': t_limit, 'NOISE_SCALE': rand_gen.randRange(2e-4, 5e-4), 'FISHEYE': False, 'DIST': [[d0,d0], [0,0], [0,0], [0,0], [0,0]]} - - createConfigFile('python/configs/config_circular.yaml', params) - -def getCamerasFromCfg(cfg): - cameras = [] - for i in range(cfg['NUM_CAMERAS']): - cameras.append(Camera(i, cfg['CAMERA' + str(i+1)]['IMG_WIDTH'], cfg['CAMERA' + str(i+1)]['IMG_HEIGHT'], - cfg['CAMERA' + str(i+1)]['FX'], cfg['CAMERA' + str(i+1)]['EULER_LIMIT'], cfg['CAMERA' + str(i+1)]['T_LIMIT'], - cfg['CAMERA' + str(i+1)]['FISHEYE'], cfg['CAMERA' + str(i+1)]['FY_DEVIATION'], - noise_scale_img_diag=cfg['CAMERA' + str(i+1)]['NOISE_SCALE'], distortion_limit=cfg['CAMERA' + str(i+1)]['DIST'])) - return cameras - -def main(cfg_name, save_folder): - cfg = yaml.safe_load(open(cfg_name, 'r')) - print(cfg) - np.random.seed(cfg['SEED']) - for trial in range(cfg['NUM_SAMPLES']): - Path(save_folder).mkdir(exist_ok=True, parents=True) - - checkerboard = CheckerBoard(cfg['BOARD']['WIDTH'], cfg['BOARD']['HEIGHT'], cfg['BOARD']['SQUARE_LEN'], cfg['BOARD']['EULER_LIMIT'], cfg['BOARD']['T_LIMIT'], cfg['BOARD']['T_ORIGIN']) - cameras = getCamerasFromCfg(cfg) - points_2d, points_3d, R_used, t_used = generateAll(cameras, checkerboard, cfg['MAX_FRAMES'], RandGen(cfg['SEED']), cfg['MAX_RANDOM_ITERS'], save_folder+'plots_projections.mp4', save_folder+'board_cameras.mp4') - - for i in range(len(cameras)): - print('Camera', i) - print('K', cameras[i].K) - print('R', cameras[i].R) - print('t', cameras[i].t.flatten()) - print('distortion', cameras[i].distortion.flatten()) - print('-----------------------------') - - imgs_width_height = [[cam.img_width, cam.img_height] for cam in cameras] - is_fisheye = [cam.is_fisheye for cam in cameras] - export2JSON(checkerboard.pattern, points_2d, imgs_width_height, is_fisheye, save_folder+'opencv_sample_'+cfg['NAME']+'.json') - saveKDRT(cameras, save_folder+'gt.txt') - - file = open(save_folder + "gt.txt", "a") - for i in range(R_used.shape[0]): - writeMatrix(file, 'R_%d' % i, R_used[i]) - writeMatrix(file, 'T_%d' % i, t_used[i]) - - poses = dict() - for idx in range(len(R_used)): - poses['frame_%d' % idx] = {'R': R_used[idx].tolist(), 'T': t_used[idx].tolist()} - - with open(os.path.join(save_folder, "gt_poses.json"), 'wt') as gt: - gt.write(json.dumps(poses, indent=4)) - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument('--cfg', type=str, required=True, help='path to config file, e.g., config_cv_test.yaml') - parser.add_argument('--output_folder', type=str, default='', help='output folder') - params, _ = parser.parse_known_args() - main(params.cfg, params.output_folder) diff --git a/apps/python-calibration-generator/config_cv_test.yaml b/apps/python-calibration-generator/config_cv_test.yaml deleted file mode 100644 index e426beed9d..0000000000 --- a/apps/python-calibration-generator/config_cv_test.yaml +++ /dev/null @@ -1,57 +0,0 @@ -NAME : "cv_test" -NUM_SAMPLES : 1 -SEED : 0 -MAX_FRAMES : 50 -MAX_RANDOM_ITERS : 100000 -NUM_CAMERAS : 4 -BOARD : - WIDTH : 9 - HEIGHT : 7 - SQUARE_LEN : 0.08 - T_LIMIT : [[-0.2, 0.2], [-0.2, 0.2], [-0.1, 0.1]] - EULER_LIMIT : [[-45, 45], [-180, 180], [-45, 45]] - T_ORIGIN : [-0.3, 0, 1.5] - -CAMERA1 : - FX : [1200, 1200] - FY_DEVIATION : null - IMG_WIDTH : 1500 - IMG_HEIGHT : 1080 - EULER_LIMIT : null - T_LIMIT : null - NOISE_SCALE : 0.0003 - FISHEYE : False - DIST : [[0.52, 0.52], [0, 0], [0, 0], [0, 0], [0, 0]] - -CAMERA2 : - FX : [1000, 1000] - FY_DEVIATION : null - IMG_WIDTH : 1300 - IMG_HEIGHT : 1000 - EULER_LIMIT : [[0, 0], [90, 90], [0, 0]] - T_LIMIT : [[-2.0, -2.0], [0.0, 0.0], [1.5, 1.5]] - NOISE_SCALE : 0.00035 - FISHEYE : False - DIST : [[0.32, 0.32], [0, 0], [0, 0], [0, 0], [0, 0]] - -CAMERA3 : - FX : [1000, 1000] - FY_DEVIATION : null - IMG_WIDTH : 1300 - IMG_HEIGHT : 1000 - EULER_LIMIT : [[0, 0], [-90, -90], [0, 0]] - T_LIMIT : [[2.0, 2.0], [0.0, 0.0], [1.5, 1.5]] - NOISE_SCALE : 0.0004 - FISHEYE : False - DIST : [[0.62, 0.62], [0, 0], [0, 0], [0, 0], [0, 0]] - -CAMERA4 : - FX : [1000, 1000] - FY_DEVIATION : null - IMG_WIDTH : 1300 - IMG_HEIGHT : 1000 - EULER_LIMIT : [[0, 0], [180, 180], [0, 0]] - T_LIMIT : [[0.0, 0.0], [0.0, 0.0], [3.0, 3.0]] - NOISE_SCALE : 0.00032 - FISHEYE : False - DIST : [[0.42, 0.42], [0, 0], [0, 0], [0, 0], [0, 0]] diff --git a/apps/python-calibration-generator/drawer.py b/apps/python-calibration-generator/drawer.py deleted file mode 100644 index 9a3d340cae..0000000000 --- a/apps/python-calibration-generator/drawer.py +++ /dev/null @@ -1,138 +0,0 @@ -# This file is part of OpenCV project. -# It is subject to the license terms in the LICENSE file found in the top-level directory -# of this distribution and at http://opencv.org/license.html. - -import numpy as np -import matplotlib.animation as manimation -import matplotlib.pyplot as plt -import cv2 as cv - -def plotImg(img): - new_img_size = 1200. * 800. - if img.shape[0] * img.shape[1] > new_img_size: - new_img = cv.resize(img, (int(np.sqrt(img.shape[1] * new_img_size / img.shape[0])), int(np.sqrt(img.shape[0] * new_img_size / img.shape[1])))) - else: new_img = img - fig = plt.figure(figsize=(14,8)) - fig.tight_layout() - fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=None, hspace=None) - plt.imshow(new_img) - plt.show() - -def getDimBox(points_, num_dim=3): - points = np.array(points_) if type(points_) is list else points_ - assert points.ndim == 3 - return np.array([[np.median([pts[k].min() for pts in points]), np.median([pts[k].max() for pts in points])] for k in - range(num_dim)]) - -def plotPoints(ax, points_list, num_dim, dim_box=None, title='', marker='o', s=7, legend=None, save_fname='', azim=0, - elev=0, fontsize=15, are_pts_colorful=False): - colors = ['red', 'green', 'blue', 'black', 'magenta', 'brown'] - if dim_box is None: dim_box = getDimBox(points_list, num_dim=num_dim) - if isinstance(are_pts_colorful, bool): - colors_pts = np.random.rand(len(points_list[0][0]), 3) if are_pts_colorful else None - else: - colors_pts = are_pts_colorful - for ii, points in enumerate(points_list): - color_ = colors_pts if colors_pts is not None else colors[ii] - if num_dim == 2: - ax.scatter(points[0], points[1], color=color_, marker=marker, s=s) - else: - ax.scatter(points[0], points[1], points[2], color=color_, marker=marker, s=s) - - ax.set_xlim(dim_box[0]) - ax.set_ylim(dim_box[1]) - ax.set_xlabel('x', fontsize=fontsize) - ax.set_ylabel('y', fontsize=fontsize) - if num_dim == 3: - ax.set_zlabel('z', fontsize=fontsize) - ax.set_zlim(dim_box[2]) - ax.view_init(azim=azim, elev=elev) - ax.set_box_aspect((dim_box[0, 1] - dim_box[0, 0], dim_box[1, 1] - dim_box[1, 0], dim_box[2, 1] - dim_box[2, 0])) - else: - ax.set_aspect('equal', 'box') - - if legend is not None: ax.legend(legend) - if title != '': ax.set_title(title, fontsize=30) - if save_fname != '': plt.savefig(save_fname, bbox_inches='tight', pad_inches=0) - -def plotAllProjections(axs, cam_points_2d, cameras, sqr, pts_color=False): - for i in range(len(cameras)): - axs[i // sqr, i % sqr].clear() - plotPoints(axs[i // sqr, i % sqr], [cam_points_2d[i]], 2, dim_box=[[0, cameras[i].img_width], [0, cameras[i].img_height]], title='camera '+str(i), are_pts_colorful=pts_color) - # plotPoints(axs[i // sqr, i % sqr], [cam_points_2d[i]], 2, title='projected points, camera '+str(i), are_pts_colorful=pts_color) - axs[i // sqr, i % sqr].invert_yaxis() - -def plotCamerasAndBoard(ax, pts_board, cam_box, cameras, colors, dim_box, pts_color=False): - ax_lines = [None for ii in range(len(cameras))] - ax.clear() - ax.set_title('Cameras and board position', fontsize=40) - plotPoints(ax, [pts_board], 3, s=10, are_pts_colorful=pts_color) - all_pts = [pts_board] - for ii, cam in enumerate(cameras): - cam_box_i = cam_box.copy() - cam_box_i[:,0] *= cam.img_width / max(cam.img_height, cam.img_width) - cam_box_i[:,1] *= cam.img_height / max(cam.img_height, cam.img_width) - cam_box_Rt = (cam.R @ cam_box_i.T + cam.t).T - all_pts.append(np.concatenate((cam_box_Rt, cam.t.T)).T) - - ax_lines[ii] = ax.plot([cam.t[0,0], cam_box_Rt[0,0]], [cam.t[1,0], cam_box_Rt[0,1]], [cam.t[2,0], cam_box_Rt[0,2]], '-', color=colors[ii])[0] - ax.plot([cam.t[0,0], cam_box_Rt[1,0]], [cam.t[1,0], cam_box_Rt[1,1]], [cam.t[2,0], cam_box_Rt[1,2]], '-', color=colors[ii]) - ax.plot([cam.t[0,0], cam_box_Rt[2,0]], [cam.t[1,0], cam_box_Rt[2,1]], [cam.t[2,0], cam_box_Rt[2,2]], '-', color=colors[ii]) - ax.plot([cam.t[0,0], cam_box_Rt[3,0]], [cam.t[1,0], cam_box_Rt[3,1]], [cam.t[2,0], cam_box_Rt[3,2]], '-', color=colors[ii]) - - ax.plot([cam_box_Rt[0,0], cam_box_Rt[1,0]], [cam_box_Rt[0,1], cam_box_Rt[1,1]], [cam_box_Rt[0,2], cam_box_Rt[1,2]], '-', color=colors[ii]) - ax.plot([cam_box_Rt[1,0], cam_box_Rt[2,0]], [cam_box_Rt[1,1], cam_box_Rt[2,1]], [cam_box_Rt[1,2], cam_box_Rt[2,2]], '-', color=colors[ii]) - ax.plot([cam_box_Rt[2,0], cam_box_Rt[3,0]], [cam_box_Rt[2,1], cam_box_Rt[3,1]], [cam_box_Rt[2,2], cam_box_Rt[3,2]], '-', color=colors[ii]) - ax.plot([cam_box_Rt[3,0], cam_box_Rt[0,0]], [cam_box_Rt[3,1], cam_box_Rt[0,1]], [cam_box_Rt[3,2], cam_box_Rt[0,2]], '-', color=colors[ii]) - ax.legend(ax_lines, [str(ii) for ii in range(len(cameras))], fontsize=20) - if dim_box is None: dim_box = getDimBox([np.concatenate((all_pts),1)]) - ax.set_xlim(dim_box[0]) - ax.set_ylim(dim_box[1]) - ax.set_zlim(dim_box[2]) - ax.set_box_aspect((dim_box[0, 1] - dim_box[0, 0], dim_box[1, 1] - dim_box[1, 0], dim_box[2, 1] - dim_box[2, 0])) - ax.view_init(azim=-89, elev=-15) - return ax - -def plotAllProjectionsFig(cam_points_2d, cameras, pts_color=False): - sqr = int(np.ceil(np.sqrt(len(cameras)))) - fig, axs = plt.subplots(sqr, sqr, figsize=(15,10)) - plotAllProjections(axs, cam_points_2d, cameras, sqr, pts_color) - -def getCameraBox(): - cam_box = np.array([[1, 1, 0], [1, -1, 0], [-1, -1, 0], [-1, 1, 0]],dtype=np.float32) - cam_box[:,2] = 3.0 - cam_box *= 0.15 - return cam_box - -def plotCamerasAndBoardFig(pts_board, cameras, pts_color=False): - fig = plt.figure(figsize=(13.0, 15.0)) - ax = fig.add_subplot(111, projection='3d') - return plotCamerasAndBoard(ax, pts_board, getCameraBox(), cameras, np.random.rand(len(cameras),3), None, pts_color) - -def animation2D(board, cameras, points_2d, save_proj_animation, VIDEOS_FPS, VIDEOS_DPI, MAX_FRAMES): - writer = manimation.writers['ffmpeg'](fps=VIDEOS_FPS) - sqr = int(np.ceil(np.sqrt(len(cameras)))) - fig, axs = plt.subplots(sqr, sqr, figsize=(15,10)) - with writer.saving(fig, save_proj_animation, dpi=VIDEOS_DPI): - for k, cam_points_2d in enumerate(points_2d): - if k >= MAX_FRAMES: break - plotAllProjections(axs, cam_points_2d, cameras, sqr, pts_color=board.colors_board) - writer.grab_frame() - -def animation3D(board, cameras, points_3d, save_3d_animation, VIDEOS_FPS, VIDEOS_DPI, MAX_FRAMES): - writer = manimation.writers['ffmpeg'](fps=VIDEOS_FPS) - fig = plt.figure(figsize=(13.0, 15.0)) - ax = fig.add_subplot(111, projection='3d') - dim_box = None - cam_box = getCameraBox() - colors = np.random.rand(10,3) - all_pts = [] - cam_pts = np.concatenate([cam.R @ cam_box.T + cam.t for cam in cameras], 1) - for k in range(min(50, len(points_3d))): - all_pts.append(np.concatenate((cam_pts, points_3d[k]),1)) - dim_box = getDimBox(all_pts) - with writer.saving(fig, save_3d_animation, dpi=VIDEOS_DPI): - for i, pts_board in enumerate(points_3d): - if i >= MAX_FRAMES: break - plotCamerasAndBoard(ax, pts_board, cam_box, cameras, colors, dim_box, pts_color=board.colors_board) - writer.grab_frame() diff --git a/apps/python-calibration-generator/utils.py b/apps/python-calibration-generator/utils.py deleted file mode 100644 index 80a74c2bfd..0000000000 --- a/apps/python-calibration-generator/utils.py +++ /dev/null @@ -1,75 +0,0 @@ -# This file is part of OpenCV project. -# It is subject to the license terms in the LICENSE file found in the top-level directory -# of this distribution and at http://opencv.org/license.html. - -import numpy as np -import math -import cv2 as cv -import json - -class RandGen: - def __init__(self, seed = 0): - self.rand_gen = np.random.RandomState(seed) - - def randRange(self, min_v, max_v): - return self.rand_gen.rand(1).item() * (max_v - min_v) + min_v - -def project(K, R, t, dist, pts_3d, is_fisheye): - if is_fisheye: - pts_2d = cv.fisheye.projectPoints(pts_3d.T[None,:], cv.Rodrigues(R)[0], t, K, dist.flatten())[0].reshape(-1,2).T - else: - pts_2d = cv.projectPoints(pts_3d, R, t, K, dist)[0].reshape(-1,2).T - return pts_2d - -def projectCamera(camera, pts_3d): - return project(camera.K, camera.R, camera.t, camera.distortion, pts_3d, camera.is_fisheye) - -def eul2rot(theta): # [x y z] - # https://learnopencv.com/rotation-matrix-to-euler-angles/ - R_x = np.array([[1, 0, 0 ], - [0, math.cos(theta[0]), -math.sin(theta[0]) ], - [0, math.sin(theta[0]), math.cos(theta[0]) ]]) - R_y = np.array([[math.cos(theta[1]), 0, math.sin(theta[1]) ], - [0, 1, 0 ], - [-math.sin(theta[1]), 0, math.cos(theta[1]) ]]) - R_z = np.array([[math.cos(theta[2]), -math.sin(theta[2]), 0], - [math.sin(theta[2]), math.cos(theta[2]), 0], - [0, 0, 1]]) - return np.dot(R_z, np.dot(R_y, R_x)) - -def insideImageMask(pts, w, h): - return np.logical_and(np.logical_and(pts[0] < w, pts[1] < h), np.logical_and(pts[0] > 0, pts[1] > 0)) - -def insideImage(pts, w, h): - return insideImageMask(pts, w, h).sum() - -def areAllInsideImage(pts, w, h): - return insideImageMask(pts, w, h).all() - -def writeMatrix(file, label, M): - file.write("%s:\n" % label) - for i in range(M.shape[0]): - for j in range(M.shape[1]): - file.write(str(M[i,j]) + ('\n' if j == M.shape[1]-1 else ' ')) - -def saveKDRT(cameras, fname): - file = open(fname, 'w') - for idx, cam in enumerate(cameras): - file.write("camera_%d:\n" % idx) - writeMatrix(file, "K", cam.K) - writeMatrix(file, "distortion", cam.distortion) - writeMatrix(file, "R", cam.R) - writeMatrix(file, "T", cam.t) - -def export2JSON(pattern_points, image_points, image_sizes, is_fisheye, json_file): - image_points = image_points.transpose(1,0,3,2) - image_points_list = [[] for i in range(len(image_sizes))] - for c in range(len(image_points)): - for f in range(len(image_points[c])): - if insideImage(image_points[c][f].T, image_sizes[c][0], image_sizes[c][1]) >= 4: - mask = np.logical_not(insideImageMask(image_points[c][f].T, image_sizes[c][0], image_sizes[c][1])) - image_points[c][f][mask] = -1. - image_points_list[c].append(image_points[c][f].tolist()) - else: - image_points_list[c].append([]) - json.dump({'object_points': pattern_points.tolist(), 'image_points': image_points_list, 'image_sizes': image_sizes, 'is_fisheye': is_fisheye}, open(json_file, 'w'))