Skip to content

Commit 845820a

Browse files
committed
improved script to keypoint distribution evaluation
1 parent d29e021 commit 845820a

1 file changed

Lines changed: 94 additions & 131 deletions

File tree

scripts/keypoint_density_evaluation.py

Lines changed: 94 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -2,146 +2,58 @@
22
import argparse
33
import pycolmap
44
import random
5+
import rasterio
56
import numpy as np
7+
from typing import List, Tuple
8+
9+
from tqdm import tqdm
610
from pathlib import Path
7-
import rasterio
811
from rasterio.features import rasterize
912
from rasterio.transform import from_origin
13+
from scipy.ndimage import zoom
1014

11-
def pair_id_to_image_ids(pair_id):
12-
image_id2 = pair_id % 2147483647
13-
image_id1 = (pair_id - image_id2) / 2147483647
14-
return image_id1, image_id2
15-
16-
def ExportMatches(
17-
database_path: Path,
18-
min_num_matches: int,
19-
output_path: Path,
20-
width: int,
21-
height: int,
22-
) -> None:
23-
24-
if not output_path.exists():
25-
print("Output folder does not exist")
26-
quit()
27-
28-
connection = sqlite3.connect(database_path)
29-
cursor = connection.cursor()
30-
31-
images = {}
32-
pairs = []
33-
34-
cursor.execute("SELECT image_id, name FROM images")
35-
for row in cursor:
36-
image_id = row[0]
37-
name = row[1]
38-
images[image_id] = name
39-
40-
cursor.execute("SELECT pair_id, rows FROM two_view_geometries")
41-
for row in cursor:
42-
pair_id = row[0]
43-
n_matches = row[1]
44-
id_img1, id_img2 = pair_id_to_image_ids(pair_id)
45-
id_img1, id_img2 = int(id_img1), int(id_img2)
46-
img1 = images[id_img1]
47-
img2 = images[id_img2]
48-
49-
if n_matches >= min_num_matches:
50-
pairs.append((img1, img2))
51-
52-
with open(output_path / "pairs.txt", "w") as f:
53-
n_pairs = len(pairs)
54-
n_brute = 0
55-
N = len(images.keys())
56-
for x in range(1, N):
57-
n_brute += N-x
58-
print(f"n_pairs/n_brute: {n_pairs} / {n_brute}")
59-
60-
for pair in pairs:
61-
f.write(f"{pair[0]} {pair[1]}\n")
62-
63-
connection.close()
64-
65-
def EvaluateKeypointDensity(
66-
database_path: Path,
67-
min_num_matches: Path,
68-
output_path: Path,
15+
def ProjectionsOfTriangulatedTiePoints(
6916
model_path: Path,
7017
) -> None:
7118

19+
image_projections = {}
7220
reconstruction = pycolmap.Reconstruction(model_path)
73-
74-
point3Ds = []
75-
projections_3d_tiepoints = {}
76-
for point3D_id in reconstruction.points3D:
77-
point3Ds.append(point3D_id)
78-
79-
#random_point3D_ids = random.sample(point3Ds, 200)
80-
81-
#for point3D_id in random_point3D_ids:
82-
for point3D_id in point3Ds:
83-
point3D = reconstruction.points3D[point3D_id]
84-
#print(f"3D Point {point3D_id} coordinates: {point3D.xyz}")
21+
22+
for image in tqdm(reconstruction.images.values(), desc="Find projections that have a corresponding 3D tie points"):
23+
camera_id = image.camera_id
24+
camera = reconstruction.cameras[camera_id]
25+
width = camera.width
26+
height = camera.height
8527

8628
projections = []
87-
for image in reconstruction.images.values():
88-
# For each image, check if it observes the 3D point
89-
for feature_idx, point2D in enumerate(image.points2D):
90-
if point2D.has_point3D and point2D.point3D_id == point3D_id:
91-
# Add the projection information: image ID and 2D point coordinates
92-
projections.append((image.image_id, point2D.xy))
93-
#print(f"Image {image.image_id}: Projected 2D point (x, y) = {point2D.xy}")
94-
95-
projections_3d_tiepoints[point3D_id] = {
96-
'3D_tiepoint': point3D.xyz,
97-
'projections': projections,
98-
}
99-
100-
return projections_3d_tiepoints
101-
102-
if __name__ == "__main__":
103-
parser = argparse.ArgumentParser(
104-
description="Export pairs in a txt file from a COLMAP database."
105-
)
106-
parser.add_argument("-d", "--database", type=Path, required=True, help="Path to COLMAP database.")
107-
parser.add_argument("-m", "--min_n_matches", type=int, required=True, help="Min number of matches that a pair should have after geometric verification.")
108-
parser.add_argument("-o", "--output", type=Path, required=True, help="Path to output folder.")
109-
parser.add_argument("-l", "--model", type=Path, required=True, help="Path to model folder.")
110-
args = parser.parse_args()
111-
112-
#ExportMatches(
113-
# database_path=args.database,
114-
# min_num_matches=args.min_n_matches,
115-
# output_path=args.output,
116-
# width=args.width,
117-
# height=args.height,
118-
#)
119-
120-
projections_3d_tiepoints = EvaluateKeypointDensity(
121-
database_path=args.database,
122-
min_num_matches=args.min_n_matches,
123-
output_path=args.output,
124-
model_path=args.model,
125-
)
126-
print(projections_3d_tiepoints)
127-
128-
129-
x_values = np.array([])
130-
y_values = np.array([])
131-
for point in projections_3d_tiepoints:
132-
projections = projections_3d_tiepoints[point]['projections']
133-
x_values = np.concatenate((x_values, np.array([prj[1][0] for prj in projections])))
134-
y_values = np.concatenate((y_values, np.array([prj[1][1] for prj in projections])))
135-
136-
137-
xmin, ymin, xmax, ymax = 0, 0, 1067, 800 # Rectangle bounds (local system)
138-
resolution = 30 # Grid cell size in the same units as your coordinates
139-
140-
# Generate example points
141-
x_points = np.random.uniform(xmin, xmax, 1000) # Example x-coordinates
142-
y_points = np.random.uniform(ymin, ymax, 1000) # Example y-coordinates
143-
x_points = x_values
144-
y_points = y_values
29+
for feature_idx, point2D in enumerate(image.points2D):
30+
#if point2D.has_point3D:
31+
if point2D.point3D_id < 1000000:
32+
#print(point2D.point3D_id);quit()
33+
#print(point2D.has_point3D.__getattribute__)
34+
#print(type(point2D.has_point3D))
35+
#quit()
36+
x, y = point2D.xy
37+
x, y = x, (height-y)
38+
projections.append((image.image_id, (x,y), width, height))
39+
image_projections[image] = projections
40+
41+
return image_projections
42+
43+
def ComupteMetrics(
44+
output_dir: Path,
45+
image_name: str,
46+
projections: List[Tuple],
47+
scale_max_value: int = 10,
48+
resolution: int = 30,
49+
upsample_factor: int = 10,
50+
) -> None:
51+
firs_prj = projections[0]
52+
width, height = firs_prj[2], firs_prj[3]
53+
x_points = np.array([prj[1][0] for prj in projections])
54+
y_points = np.array([prj[1][1] for prj in projections])
55+
xmin, ymin, xmax, ymax = 0, 0, width, height # Rectangle bounds (local system)
56+
resolution = resolution # Grid cell size in the same units as your coordinates
14557

14658
# Create a grid for point density
14759
cols = int((xmax - xmin) / resolution)
@@ -156,15 +68,28 @@ def EvaluateKeypointDensity(
15668
density_grid[row, col] += 1
15769

15870
# Normalize density values for better visualization (optional)
159-
density_grid_normalized = density_grid / density_grid.max() # Scale to [0, 1]
71+
density_grid_normalized = ((density_grid / scale_max_value) != 0).astype(int)
16072
density_grid_normalized = (density_grid_normalized * 255).astype(np.uint8) # Scale to [0, 255]
16173

16274
# Define raster transformation for local reference
16375
transform = from_origin(xmin, ymax, resolution, resolution)
16476

77+
zero_cells = np.count_nonzero(density_grid == 0)
78+
print(f"Image: {image_name}")
79+
print(f"Number of cells with zero density: {zero_cells}")
80+
print(f"Total number of cells: {rows * cols}")
81+
covered_area = ((rows * cols)-zero_cells) / (rows * cols) * 100
82+
print(f"Percentage of cells with non zero density: {covered_area}%")
83+
84+
density_grid_normalized = zoom(
85+
density_grid_normalized,
86+
zoom=upsample_factor,
87+
order=1 # Cubic interpolation (you can use order=1 for linear interpolation)
88+
)
89+
16590
# Save the density map as a JPEG
16691
with rasterio.open(
167-
'/home/threedom/Desktop/buttare/density.jpg',
92+
f'{output_dir}/density_{image_name}.jpg',
16893
"w",
16994
driver="JPEG", # Use JPEG driver
17095
height=density_grid_normalized.shape[0],
@@ -174,7 +99,45 @@ def EvaluateKeypointDensity(
17499
transform=transform
175100
) as dst:
176101
dst.write(density_grid_normalized, 1)
102+
103+
return image_name, covered_area
104+
105+
if __name__ == "__main__":
106+
107+
parser = argparse.ArgumentParser(
108+
description="Export pairs in a txt file from a COLMAP database."
109+
)
110+
parser.add_argument("-d", "--database", type=Path, required=True, help="Path to COLMAP database.")
111+
parser.add_argument("-m", "--min_n_matches", type=int, required=True, help="Min number of matches that a pair should have after geometric verification.")
112+
parser.add_argument("-o", "--output", type=Path, required=True, help="Path to output folder.")
113+
parser.add_argument("-l", "--model", type=Path, required=True, help="Path to model folder.")
114+
args = parser.parse_args()
177115

116+
image_projections = ProjectionsOfTriangulatedTiePoints(model_path=args.model)
117+
118+
results = {}
119+
120+
for image in tqdm(image_projections, desc="Compute metrics"):
121+
#print(f"Image: {image.name} - Number of projections: {len(image_projections[image])}")
122+
image_name, covered_area = ComupteMetrics(
123+
output_dir=Path(args.output),
124+
image_name=image.name,
125+
projections=image_projections[image],
126+
scale_max_value=1,
127+
resolution=15,
128+
upsample_factor=10,
129+
)
130+
results[image_name] = covered_area
131+
132+
radiometric = []
133+
rgb = []
134+
for key, value in results.items():
135+
if "radiometric" in key:
136+
radiometric.append(value)
137+
else:
138+
rgb.append(value)
139+
print(f"Radiometric: {sum(radiometric)/len(radiometric)}")
140+
print(f"RGB: {sum(rgb)/len(rgb)}")
178141

179142

180143

0 commit comments

Comments
 (0)