From cef98cba9f47d0eb773177a37c3d2971df9c5df7 Mon Sep 17 00:00:00 2001 From: lcmrl Date: Thu, 21 Nov 2024 10:55:02 +0100 Subject: [PATCH] extended scripts folder --- scripts/copy_images_no_exif.py | 27 +++++++ scripts/keypoint_density_evaluation.py | 77 ++++++++++++++++++ scripts/lidar_optimization.py | 105 +++++++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 scripts/copy_images_no_exif.py create mode 100644 scripts/keypoint_density_evaluation.py create mode 100644 scripts/lidar_optimization.py diff --git a/scripts/copy_images_no_exif.py b/scripts/copy_images_no_exif.py new file mode 100644 index 0000000..550d7dd --- /dev/null +++ b/scripts/copy_images_no_exif.py @@ -0,0 +1,27 @@ +import argparse +import os +import cv2 + +def copy_images_no_exif(input_folder, output_folder): + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + for filename in os.listdir(input_folder): + if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')): + img_path = os.path.join(input_folder, filename) + img = cv2.imread(img_path) + if img is not None: + output_path = os.path.join(output_folder, filename) + cv2.imwrite(output_path, img) + print(f"Copied {filename} to {output_folder}") + +def main(): + parser = argparse.ArgumentParser(description="Copy images without EXIF data") + parser.add_argument('input_folder', type=str, help="Folder with input images") + parser.add_argument('output_folder', type=str, help="Folder to save output images") + args = parser.parse_args() + + copy_images_no_exif(args.input_folder, args.output_folder) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/keypoint_density_evaluation.py b/scripts/keypoint_density_evaluation.py new file mode 100644 index 0000000..6a25fae --- /dev/null +++ b/scripts/keypoint_density_evaluation.py @@ -0,0 +1,77 @@ +import sqlite3 +import argparse +from pathlib import Path + +def pair_id_to_image_ids(pair_id): + image_id2 = pair_id % 2147483647 + image_id1 = (pair_id - image_id2) / 2147483647 + return image_id1, image_id2 + +def ExportMatches( + database_path: Path, + min_num_matches: int, + output_path: Path, + width: int, + height: int, +) -> None: + + if not output_path.exists(): + print("Output folder does not exist") + quit() + + connection = sqlite3.connect(database_path) + cursor = connection.cursor() + + images = {} + pairs = [] + + cursor.execute("SELECT image_id, name FROM images") + for row in cursor: + image_id = row[0] + name = row[1] + images[image_id] = name + + cursor.execute("SELECT pair_id, rows FROM two_view_geometries") + for row in cursor: + pair_id = row[0] + n_matches = row[1] + id_img1, id_img2 = pair_id_to_image_ids(pair_id) + id_img1, id_img2 = int(id_img1), int(id_img2) + img1 = images[id_img1] + img2 = images[id_img2] + + if n_matches >= min_num_matches: + pairs.append((img1, img2)) + + with open(output_path / "pairs.txt", "w") as f: + n_pairs = len(pairs) + n_brute = 0 + N = len(images.keys()) + for x in range(1, N): + n_brute += N-x + print(f"n_pairs/n_brute: {n_pairs} / {n_brute}") + + for pair in pairs: + f.write(f"{pair[0]} {pair[1]}\n") + + connection.close() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Export pairs in a txt file from a COLMAP database." + ) + parser.add_argument("-d", "--database", type=Path, required=True, help="Path to COLMAP database.") + parser.add_argument("-m", "--min_n_matches", type=int, required=True, help="Min number of matches that a pair should have after geometric verification.") + parser.add_argument("-w", "--width", type=int, required=True, help="Image width.") + parser.add_argument("-e", "--height", type=int, required=True, help="Image height.") + parser.add_argument("-o", "--output", type=Path, required=True, help="Path to output folder.") + args = parser.parse_args() + + ExportMatches( + database_path=args.database, + min_num_matches=args.min_n_matches, + output_path=args.output, + width=args.width, + height=args.height, + ) \ No newline at end of file diff --git a/scripts/lidar_optimization.py b/scripts/lidar_optimization.py new file mode 100644 index 0000000..40c8639 --- /dev/null +++ b/scripts/lidar_optimization.py @@ -0,0 +1,105 @@ +import tqdm +import random +import argparse +import pycolmap +import open3d as o3d +import numpy as np +from scipy.spatial import cKDTree + + +def main(): + parser = argparse.ArgumentParser(description='Process point cloud and colmap model folder') + parser.add_argument('point_cloud', type=str, help='Path to the point cloud file') + parser.add_argument('colmap_folder', type=str, help='Path to the folder containing the colmap model') + parser.add_argument('output_folder', type=str, help='Path to the output folder') + parser.add_argument('camera_pos', type=str, help='Path to camera positions') + + args = parser.parse_args() + point_cloud_path = args.point_cloud + colmap_folder_path = args.colmap_folder + output_folder = args.output_folder + camera_pos_path = args.camera_pos + + constraints = {} + reconstruction = pycolmap.Reconstruction(colmap_folder_path) + point_cloud = o3d.io.read_point_cloud(point_cloud_path) + points_ply = np.asarray(point_cloud.points) + kdtree = cKDTree(points_ply) + + #camera_pos = {} + #with open(camera_pos_path, 'r') as f: + # lines = f.readlines() + # for line in lines: + # line = line.strip() + # image, x, y, z, _ = line.split('\t', 4) + # for img_id, img in reconstruction.images.items(): + # if img.name == image: + # camera_pos[img_id] = np.array([float(x), float(y), float(z)]) + # break +# + #scale, rotation, translation = pycolmap.align_reconstruction(reconstruction, camera_pos);quit() + + point3Ds = [] + for point3D_id in reconstruction.points3D: + point3Ds.append(point3D_id) + + random_point3D_ids = random.sample(point3Ds, 200) + + for point3D_id in random_point3D_ids: + point3D = reconstruction.points3D[point3D_id] + #print(f"3D Point {point3D_id} coordinates: {point3D.xyz}") + + projections = [] + for image in reconstruction.images.values(): + # For each image, check if it observes the 3D point + for feature_idx, point2D in enumerate(image.points2D): + if point2D.has_point3D and point2D.point3D_id == point3D_id: + # Add the projection information: image ID and 2D point coordinates + projections.append((image.image_id, point2D.xy)) + #print(f"Image {image.image_id}: Projected 2D point (x, y) = {point2D.xy}") + + constraints[point3D_id] = { + '3D_tiepoint': point3D.xyz, + 'projections': projections, + 'lidar_point': None, + } + + # Export to txt file + with open(f'{output_folder}/constraints.txt', 'w') as f: + for point3D_id in random_point3D_ids: + for image_id, point2D in constraints[point3D_id]['projections']: + image = reconstruction.images[image_id] + f.write(f"{point3D_id},{image.name[:-4]},{point2D[0]},{point2D[1]}\n") + + + query_points = np.empty((0, 3)) + ids = [] + for point3D_id in random_point3D_ids: + x = constraints[point3D_id]['3D_tiepoint'][0] + y = constraints[point3D_id]['3D_tiepoint'][1] + z = constraints[point3D_id]['3D_tiepoint'][2] + query_points = np.vstack((query_points, np.array([float(x), float(y), float(z)]))) + ids.append(point3D_id) + + distances, indices = kdtree.query(query_points) + + for i, (point3D_id, index) in enumerate(zip(ids, indices)): + nearest_point = points_ply[index] + constraints[point3D_id]['lidar_point'] = nearest_point + + with open(f'{output_folder}/points.txt', 'w') as f: + for point3D_id in random_point3D_ids: + x_photogram = float(constraints[point3D_id]['3D_tiepoint'][0]) + y_photogram = float(constraints[point3D_id]['3D_tiepoint'][1]) + z_photogram = float(constraints[point3D_id]['3D_tiepoint'][2]) + x_lidar = float(constraints[point3D_id]['lidar_point'][0]) + y_lidar = float(constraints[point3D_id]['lidar_point'][1]) + z_lidar = float(constraints[point3D_id]['lidar_point'][2]) + norm = ((x_photogram-x_lidar)**2+(y_photogram-y_lidar)**2+(z_photogram-z_lidar)**2)**0.5 + print(norm) + if norm < 0.20: + f.write(f"{point3D_id},{x_lidar},{y_lidar},{z_lidar}\n") + + +if __name__ == '__main__': + main() \ No newline at end of file