Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Pinard Clement
drone-depth-validation-set
Commits
ac6f363f
Commit
ac6f363f
authored
Nov 24, 2020
by
Clément Pinard
Browse files
add new images ground truth generation (needs doc now)
parent
5c977416
Changes
13
Hide whitespace changes
Inline
Side-by-side
anafi_metadata.py
View file @
ac6f363f
...
...
@@ -37,14 +37,13 @@ def preprocess_metadata(metadata, proj):
metadata
[
"indoor"
]
=
False
if
0
in
metadata
[
"location_valid"
].
unique
():
location_validity
=
metadata
[
"location_valid"
].
diff
()
invalidity_start
=
location_validity
.
index
[
location_validity
==
-
1
].
tolist
()
validity_start
=
location_validity
.
index
[
location_validity
==
1
].
tolist
()
if
metadata
[
"location_valid"
].
iloc
[
0
]:
if
not
metadata
[
"location_valid"
].
iloc
[
0
]:
end
=
validity_start
.
pop
(
0
)
positions
[:
end
]
=
extrapolate_position
(
speed
[:
end
],
timestamps
[:
end
],
None
,
positions
[
end
])
if
metadata
[
"location_valid"
].
iloc
[
-
1
]:
if
not
metadata
[
"location_valid"
].
iloc
[
-
1
]:
start
=
invalidity_start
.
pop
(
-
1
)
-
1
positions
[
start
:]
=
extrapolate_position
(
speed
[
start
:],
timestamps
[
start
:],
positions
[
start
],
None
)
...
...
cli_utils.py
View file @
ac6f363f
from
argparse
import
ArgumentParser
,
ArgumentDefaultsHelpFormatter
from
path
import
Path
import
numpy
as
np
def
set_argparser
():
parser
=
ArgumentParser
(
description
=
'Main pipeline, from LIDAR pictures and videos to GT depth enabled videos'
,
formatter_class
=
ArgumentDefaultsHelpFormatter
)
def
add_main_options
(
parser
):
main_parser
=
parser
.
add_argument_group
(
"Main options"
)
main_parser
.
add_argument
(
'--input_folder'
,
metavar
=
'PATH'
,
default
=
Path
(
"."
),
type
=
Path
,
help
=
"Folder with LAS point cloud, videos, and images"
)
...
...
@@ -21,7 +19,9 @@ def set_argparser():
main_parser
.
add_argument
(
'--begin_step'
,
metavar
=
"N"
,
type
=
int
,
default
=
None
)
main_parser
.
add_argument
(
'--show_steps'
,
action
=
"store_true"
)
main_parser
.
add_argument
(
'--add_new_videos'
,
action
=
"store_true"
,
help
=
"If selected, will skit first 6 steps to directly register videos without mapping"
)
help
=
"If selected, will skip first 6 steps to directly register videos without mapping"
)
main_parser
.
add_argument
(
'--generate_groundtruth_for_individual_images'
,
action
=
"store_true"
,
help
=
"If selected, will generate Ground truth for individual images as well as videos"
)
main_parser
.
add_argument
(
'--save_space'
,
action
=
"store_true"
)
main_parser
.
add_argument
(
'-v'
,
'--verbose'
,
action
=
"count"
,
default
=
0
)
main_parser
.
add_argument
(
'--resume_work'
,
action
=
"store_true"
,
...
...
@@ -36,7 +36,9 @@ def set_argparser():
main_parser
.
add_argument
(
'--raw_ext'
,
nargs
=
'+'
,
default
=
[
".ARW"
,
".NEF"
,
".DNG"
],
help
=
'Raw Image extensions to scrape from input folder'
)
pcp_parser
=
parser
.
add_argument_group
(
"PointCLoud preparation"
)
def
add_pcp_options
(
parser
):
pcp_parser
=
parser
.
add_argument_group
(
"PointCloud preparation"
)
pcp_parser
.
add_argument
(
"--pointcloud_resolution"
,
default
=
None
,
type
=
float
,
help
=
'If set, will subsample the Lidar point clouds at the chosen resolution'
)
pcp_parser
.
add_argument
(
"--SOR"
,
default
=
[
10
,
6
],
nargs
=
2
,
type
=
float
,
...
...
@@ -44,6 +46,8 @@ def set_argparser():
pcp_parser
.
add_argument
(
'--registration_method'
,
choices
=
[
"simple"
,
"eth3d"
,
"interactive"
],
default
=
"simple"
,
help
=
'Method used for point cloud registration. See README, Manual step by step : step 11'
)
def
add_ve_options
(
parser
):
ve_parser
=
parser
.
add_argument_group
(
"Video extractor"
)
ve_parser
.
add_argument
(
'--total_frames'
,
default
=
500
,
type
=
int
)
ve_parser
.
add_argument
(
'--orientation_weight'
,
default
=
1
,
type
=
float
,
...
...
@@ -66,7 +70,12 @@ def set_argparser():
ve_parser
.
add_argument
(
'--generic_model'
,
default
=
'OPENCV'
,
help
=
'COLMAP model for generic videos. Same zoom level assumed throughout the whole video. '
'See https://colmap.github.io/cameras.html'
)
ve_parser
.
add_argument
(
'--full_metadata'
,
default
=
None
,
help
=
'where to save all concatenated metadata in a file that will be used to add new videos afterward. '
'If not set, will save at the root of workspace'
)
def
add_exec_options
(
parser
):
exec_parser
=
parser
.
add_argument_group
(
"Executable files"
)
exec_parser
.
add_argument
(
'--log'
,
default
=
None
,
type
=
Path
)
exec_parser
.
add_argument
(
'--nw'
,
default
=
"native-wrapper.sh"
,
type
=
Path
,
...
...
@@ -78,6 +87,8 @@ def set_argparser():
exec_parser
.
add_argument
(
"--ffmpeg"
,
default
=
"ffmpeg"
,
type
=
Path
)
exec_parser
.
add_argument
(
"--pcl_util"
,
default
=
"pcl_util/build"
,
type
=
Path
)
def
add_pm_options
(
parser
):
pm_parser
=
parser
.
add_argument_group
(
"Photogrammetry"
)
pm_parser
.
add_argument
(
'--max_num_matches'
,
default
=
32768
,
type
=
int
,
help
=
"max number of matches, lower it if you get GPU memory error"
)
pm_parser
.
add_argument
(
'--vocab_tree'
,
type
=
Path
,
default
=
"vocab_tree_flickr100K_words256K.bin"
)
...
...
@@ -94,6 +105,8 @@ def set_argparser():
pm_parser
.
add_argument
(
'--stereo_min_depth'
,
type
=
float
,
default
=
0.1
,
help
=
"Min depth for PatchMatch Stereo"
)
pm_parser
.
add_argument
(
'--stereo_max_depth'
,
type
=
float
,
default
=
100
,
help
=
"Max depth for PatchMatch Stereo"
)
def
add_om_options
(
parser
):
om_parser
=
parser
.
add_argument_group
(
"Occlusion Mesh"
)
om_parser
.
add_argument
(
'--normals_method'
,
default
=
"radius"
,
choices
=
[
"radius"
,
"neighbours"
],
help
=
'Method used for normal computation between radius and nearest neighbours'
)
...
...
@@ -112,11 +125,69 @@ def set_argparser():
help
=
'Splat size is defined by mean istance from its neighbours. You can define a max splat size for '
'isolated points which otherwise would make a very large useless splat. '
'If not set, will be `2.5*splat_threshold`.'
)
def
add_gt_options
(
parser
):
gt_parser
=
parser
.
add_argument_group
(
"Ground Truth Creator"
)
gt_parser
.
add_argument
(
'--max_occlusion_depth'
,
default
=
250
,
type
=
float
,
help
=
'max depth for occlusion. Everything further will not be considered at infinity'
)
gt_parser
.
add_argument
(
'--eth3d_splat_radius'
,
default
=
0.01
,
type
=
float
,
help
=
'see splat radius for ETH3D'
)
def
set_full_argparser
():
parser
=
ArgumentParser
(
description
=
'Main pipeline, from LIDAR pictures and videos to GT depth enabled videos'
,
formatter_class
=
ArgumentDefaultsHelpFormatter
)
add_main_options
(
parser
)
add_pcp_options
(
parser
)
add_ve_options
(
parser
)
add_exec_options
(
parser
)
add_pm_options
(
parser
)
add_om_options
(
parser
)
add_gt_options
(
parser
)
return
parser
def
set_full_argparser_no_lidar
():
parser
=
ArgumentParser
(
description
=
'Main pipeline, from pictures and videos to GT depth enabled videos, '
'using only COLMAP (No lidar)'
,
formatter_class
=
ArgumentDefaultsHelpFormatter
)
add_main_options
(
parser
)
add_ve_options
(
parser
)
add_exec_options
(
parser
)
add_pm_options
(
parser
)
add_om_options
(
parser
)
add_gt_options
(
parser
)
parser
.
add_argument
(
"--SOR"
,
default
=
[
10
,
6
],
nargs
=
2
,
type
=
float
,
help
=
"Satistical Outlier Removal parameters : Number of nearest neighbours, "
"max relative distance to standard deviation. "
"This will be used for filtering dense reconstruction"
)
return
parser
def
set_new_images_arparser
():
parser
=
ArgumentParser
(
description
=
'Additional pipeline, to add new pictures to an already existing workspace, '
'The main pipeline must be finished before launching this script, '
' at least until reconstruction alignment'
,
formatter_class
=
ArgumentDefaultsHelpFormatter
)
add_main_options
(
parser
)
add_exec_options
(
parser
)
add_pm_options
(
parser
)
add_om_options
(
parser
)
add_gt_options
(
parser
)
parser
.
add_argument
(
"--map_new_images"
,
action
=
"store_true"
,
help
=
"if selected, will replace the 'omage_registrator' step with a full mapping step"
)
parser
.
add_argument
(
"--bundle_adjustor_steps"
,
default
=
100
,
type
=
int
,
help
=
"number of iteration for bundle adjustor after image registration"
)
parser
.
add_argument
(
"--rebuild_occlusion_mesh"
,
action
=
"store_true"
,
help
=
"If selected, will rebuild a new dense point cloud and deauney mesh. "
"Useful when new images see new parts of the model"
)
parser
.
add_argument
(
'--generic_model'
,
default
=
'OPENCV'
,
help
=
'COLMAP model for generic videos. Same zoom level assumed throughout the whole video. '
'See https://colmap.github.io/cameras.html'
)
return
parser
...
...
@@ -159,3 +230,21 @@ def print_workflow():
print
(
"
\t
Per video:"
)
for
i
,
s
in
enumerate
(
per_vid_steps_2
):
print
(
"
\t
{}) {}"
.
format
(
i
+
1
,
s
))
def
get_matrix
(
path
):
if
path
.
isfile
():
'''Note : We use the inverse matrix here, because in general, it's easier to register the reconstructed model into the lidar one,
as the reconstructed will have less points, but in the end we need the matrix to apply to the lidar point to be aligned
with the camera positions (ie the inverse)'''
return
np
.
linalg
.
inv
(
np
.
fromfile
(
path
,
sep
=
" "
).
reshape
(
4
,
4
))
else
:
print
(
"Error, no registration matrix can be found"
)
print
(
"Ensure that your registration matrix was saved under the name {}"
.
format
(
path
))
decision
=
None
while
decision
not
in
[
"y"
,
"n"
,
""
]:
decision
=
input
(
"retry ? [Y/n]"
)
if
decision
.
lower
()
in
[
"y"
,
""
]:
return
get_matrix
(
path
)
elif
decision
.
lower
()
==
"n"
:
return
np
.
eye
(
4
)
convert_dataset.py
View file @
ac6f363f
...
...
@@ -11,11 +11,11 @@ from tqdm import tqdm
from
wrappers
import
FFMpeg
import
gzip
from
pebble
import
ProcessPool
import
pandas
as
pd
import
yaml
def
save_intrinsics
(
cameras
,
images
,
output_dir
,
downscale
=
1
):
def
construct_intrinsics
(
cam
):
def
save_intrinsics
(
cameras
,
images
,
output_dir
,
output_width
=
None
,
downscale
=
None
):
def
construct_intrinsics
(
cam
,
downscale
):
# assert('PINHOLE' in cam.model)
if
'SIMPLE'
in
cam
.
model
:
fx
,
cx
,
cy
,
*
_
=
cam
.
params
...
...
@@ -27,16 +27,32 @@ def save_intrinsics(cameras, images, output_dir, downscale=1):
[
0
,
fy
/
downscale
,
cy
/
downscale
],
[
0
,
0
,
1
]])
def
save_cam
(
cam
,
intrinsics_path
,
yaml_path
):
if
downscale
is
None
:
current_downscale
=
output_width
/
cam
.
width
else
:
current_downscale
=
downscale
intrinsics
=
construct_intrinsics
(
cam
,
current_downscale
)
np
.
savetxt
(
intrinsics_path
,
intrinsics
)
with
open
(
yaml_path
,
'w'
)
as
f
:
camera_dict
=
{
"model"
:
cam
.
model
,
"params"
:
cam
.
params
,
"width"
:
cam
.
width
/
current_downscale
,
"height"
:
cam
.
height
/
current_downscale
}
yaml
.
dump
(
camera_dict
,
f
,
default_flow_style
=
False
)
if
len
(
cameras
)
==
1
:
print
(
"bonjour"
)
cam
=
cameras
[
list
(
cameras
.
keys
())[
0
]]
intrinsics
=
construct_intrinsics
(
cam
)
np
.
savetxt
(
output_dir
/
'intrinsics.txt'
,
intrinsics
)
save_cam
(
cam
,
output_dir
/
"intrinsics.txt"
,
output_dir
/
"camera.yaml"
)
else
:
print
(
"au revoir"
)
for
_
,
img
in
images
.
items
():
cam
=
cameras
[
img
.
camera_id
]
intrinsics
=
construct_intrinsics
(
cam
)
intrinsics_name
=
output_dir
/
Path
(
img
.
name
).
stem
+
"_intrinsics.txt"
np
.
savetxt
(
intrinsics_name
,
intrinsics
)
save_cam
(
cam
,
output_dir
/
Path
(
img
.
name
).
stem
+
"_intrinsics.txt"
,
output_dir
/
Path
(
img
.
name
).
stem
+
"_camera.yaml"
)
def
to_transform_matrix
(
q
,
t
):
...
...
@@ -179,27 +195,37 @@ parser.add_argument('--verbose', '-v', action='count', default=0)
def
convert_dataset
(
final_model
,
depth_dir
,
images_root_folder
,
occ_dir
,
dataset_output_dir
,
video_output_dir
,
metadata_path
,
interpolated_frames
,
ffmpeg
,
threads
=
8
,
downscale
=
None
,
compressed
=
True
,
dataset_output_dir
,
video_output_dir
,
ffmpeg
,
interpolated_frames
=
[],
metadata
=
None
,
images_list
=
None
,
threads
=
8
,
downscale
=
None
,
compressed
=
True
,
width
=
None
,
visualization
=
False
,
video
=
False
,
verbose
=
0
,
**
env
):
dataset_output_dir
.
makedirs_p
()
video_output_dir
.
makedirs_p
()
if
video
:
visualization
=
True
cameras
,
images
,
_
=
rm
.
read_model
(
final_model
,
'.txt'
)
metadata
=
pd
.
read_csv
(
metadata_path
).
set_index
(
"db_id"
,
drop
=
False
).
sort_values
(
"time"
)
framerate
=
metadata
[
"framerate"
].
values
[
0
]
if
downscale
is
None
:
assert
(
width
is
not
None
)
# image_df = pd.DataFrame.from_dict(images, orient="index").set_index("id")
if
metadata
is
not
None
:
metadata
=
metadata
.
set_index
(
"db_id"
,
drop
=
False
).
sort_values
(
"time"
)
framerate
=
metadata
[
"framerate"
].
values
[
0
]
# image_df = image_df.reindex(metadata.index)
images_list
=
metadata
[
"image_path"
]
else
:
assert
images_list
is
not
None
framerate
=
None
video
=
False
input_width
=
metadata
[
"width"
].
values
[
0
]
downscale
=
width
/
input_width
# Discard images and cameras that are not represented by the image list
images
=
{
k
:
i
for
k
,
i
in
images
.
items
()
if
i
.
name
in
images_list
}
cameras_ids
=
set
([
i
.
camera_id
for
i
in
images
.
values
()])
cameras
=
{
k
:
cameras
[
k
]
for
k
in
cameras_ids
}
save_intrinsics
(
cameras
,
images
,
dataset_output_dir
,
downscale
)
if
downscale
is
None
:
assert
width
is
not
None
save_intrinsics
(
cameras
,
images
,
dataset_output_dir
,
width
,
downscale
)
save_positions
(
images
,
dataset_output_dir
)
image_df
=
pd
.
DataFrame
.
from_dict
(
images
,
orient
=
"index"
).
set_index
(
"id"
)
image_df
=
image_df
.
reindex
(
metadata
.
index
)
depth_maps
=
[]
occ_maps
=
[]
interpolated
=
[]
...
...
@@ -207,7 +233,7 @@ def convert_dataset(final_model, depth_dir, images_root_folder, occ_dir,
cameras
=
[]
not_registered
=
0
for
i
in
metadata
[
"image_path"
]
:
for
i
in
images_list
:
img_path
=
images_root_folder
/
i
imgs
.
append
(
img_path
)
...
...
@@ -236,8 +262,8 @@ def convert_dataset(final_model, depth_dir, images_root_folder, occ_dir,
depth_maps
.
append
(
None
)
occ_maps
.
append
(
None
)
interpolated
.
append
(
False
)
print
(
'{}/{} Frames not registered ({:.2f}%)'
.
format
(
not_registered
,
len
(
metadata
),
100
*
not_registered
/
len
(
metadata
)))
print
(
'{}/{} Frames interpolated ({:.2f}%)'
.
format
(
sum
(
interpolated
),
len
(
metadata
),
100
*
sum
(
interpolated
)
/
len
(
metadata
)))
print
(
'{}/{} Frames not registered ({:.2f}%)'
.
format
(
not_registered
,
len
(
images_list
),
100
*
not_registered
/
len
(
images_list
)))
print
(
'{}/{} Frames interpolated ({:.2f}%)'
.
format
(
sum
(
interpolated
),
len
(
images_list
),
100
*
sum
(
interpolated
)
/
len
(
images_list
)))
if
threads
==
1
:
for
i
,
d
,
o
,
n
in
tqdm
(
zip
(
imgs
,
depth_maps
,
occ_maps
,
interpolated
),
total
=
len
(
imgs
)):
process_one_frame
(
i
,
d
,
o
,
dataset_output_dir
,
video_output_dir
,
downscale
,
n
,
visualization
,
viz_width
=
1920
)
...
...
extract_
video
_from_model.py
→
extract_
pictures
_from_model.py
View file @
ac6f363f
...
...
@@ -11,21 +11,39 @@ parser.add_argument('--input_model', metavar='DIR', type=Path, required=True,
parser
.
add_argument
(
'--output_model'
,
metavar
=
'DIR'
,
type
=
Path
,
required
=
True
,
help
=
'Output folder where the modified COLMAP model will be saved'
)
parser
.
add_argument
(
'--output_format'
,
choices
=
[
'.txt'
,
'.bin'
],
default
=
'.txt'
)
parser
.
add_argument
(
'--metadata_path'
,
metavar
=
"CSV"
,
type
=
Path
,
required
=
True
,
help
=
'Path to metadata CSV file of the desired video. '
'Usually in /pictures/Videos/<size>/<video_name>/metadata.csv'
)
group
=
parser
.
add_mutually_exclusive_group
()
group
.
add_argument
(
'--metadata_path'
,
metavar
=
"CSV"
,
type
=
Path
,
default
=
None
,
help
=
'Path to metadata CSV file of the desired video. '
'Usually in /pictures/Videos/<size>/<video_name>/metadata.csv'
)
group
.
add_argument
(
'--picture_list_path'
,
type
=
Path
,
default
=
None
,
help
=
'Path to list of picture to extract from model. '
'Picture paths must be relatvie to colmap root'
)
def
extract_
video
(
input
,
output
,
video_metadata_path
,
output_format
=
'.bin'
):
def
extract_
pictures
(
input
,
output
,
picture_list
,
output_format
=
'.bin'
):
cameras
=
rm
.
read_cameras_binary
(
input
/
"cameras.bin"
)
images
=
rm
.
read_images_binary
(
input
/
"images.bin"
)
images_per_name
=
{}
video_metadata
=
pd
.
read_csv
(
video_metadata_path
)
image_names
=
video_metadata
[
"image_path"
].
values
camera_ids
=
[]
def
add_image
(
image
):
images_per_name
[
image
.
name
]
=
image
.
_replace
(
xys
=
[],
point3D_ids
=
[])
cam_id
=
image
.
camera_id
if
cam_id
not
in
camera_ids
:
camera_ids
.
append
(
cam_id
)
for
id
,
image
in
images
.
items
():
if
image
.
name
in
image_names
:
images_per_name
[
image
.
name
]
=
image
.
_replace
(
xys
=
[],
point3D_ids
=
[])
camera_ids
=
video_metadata
[
"camera_id"
].
unique
()
if
image
.
name
in
picture_list
:
add_image
(
image
)
if
len
(
images_per_name
)
==
1
:
# Add also first picture so that we have multiple pictures.
# Otherwise, GourndTruth Creator will error
for
id
,
image
in
images
.
items
():
if
image
.
name
not
in
picture_list
:
add_image
(
image
)
break
output_cameras
=
{
cid
:
cameras
[
cid
]
for
cid
in
camera_ids
if
cid
in
cameras
.
keys
()}
rm
.
write_model
(
output_cameras
,
images_per_name
,
{},
output
,
output_format
)
...
...
@@ -35,7 +53,12 @@ def extract_video(input, output, video_metadata_path, output_format='.bin'):
def
main
():
args
=
parser
.
parse_args
()
extract_video
(
args
.
input_model
,
args
.
output_model
,
args
.
metadata_path
,
args
.
output_format
)
if
args
.
metadata_path
is
not
None
:
picture_list
=
pd
.
read_csv
(
args
.
metadata_path
)[
"image_path"
].
values
elif
args
.
picture_list_path
is
not
None
:
with
open
(
args
.
picture_list_path
,
'r'
)
as
f
:
picture_list
=
[
line
[:
-
1
]
for
line
in
f
.
readlines
()]
extract_pictures
(
args
.
input_model
,
args
.
output_model
,
picture_list
,
args
.
output_format
)
return
...
...
generate_sky_masks.py
View file @
ac6f363f
...
...
@@ -58,13 +58,13 @@ def extract_sky_mask(network, image_paths, mask_folder):
imageio
.
imwrite
(
mask_folder
/
(
path
.
basename
()
+
'.png'
),
(
f
*
255
).
astype
(
np
.
uint8
))
def
process_folder
(
folder_to_process
,
image_path
,
mask_path
,
pic_ext
,
verbose
=
False
,
batchsize
=
8
,
**
env
):
def
process_folder
(
folder_to_process
,
colmap_img_root
,
mask_path
,
pic_ext
,
verbose
=
False
,
batchsize
=
8
,
**
env
):
network
=
prepare_network
()
folders
=
[
folder_to_process
]
+
list
(
folder_to_process
.
walkdirs
())
for
folder
in
folders
:
mask_folder
=
mask_path
/
image_path
.
relpathto
(
folder
)
mask_folder
=
mask_path
/
colmap_img_root
.
relpathto
(
folder
)
mask_folder
.
makedirs_p
()
images
=
sum
((
folder
.
files
(
'*{}'
.
format
(
ext
))
for
ext
in
pic_ext
),
[])
if
images
:
...
...
las2ply.py
View file @
ac6f363f
...
...
@@ -23,7 +23,6 @@ def load_and_convert(input_file, output_folder, verbose=False):
file_type
=
input_file
.
ext
[
1
:].
upper
()
if
file_type
==
"LAS"
:
offset
=
np
.
array
(
laspy
.
file
.
File
(
input_file
,
mode
=
"r"
).
header
.
offset
)
print
(
offset
)
else
:
offset
=
np
.
zeros
(
3
)
cloud
=
PyntCloud
.
from_file
(
input_file
)
...
...
main_pipeline.py
View file @
ac6f363f
import
las2ply
import
numpy
as
np
from
wrappers
import
Colmap
,
FFMpeg
,
PDraw
,
ETH3D
,
PCLUtil
from
cli_utils
import
set_argparser
,
print_step
,
print_workflow
from
video_localization
import
localize_video
,
generate_GT
from
cli_utils
import
set_
full_
argparser
,
print_step
,
print_workflow
,
get_matrix
from
video_localization
import
localize_video
,
generate_GT
,
generate_GT_individual_pictures
import
meshlab_xml_writer
as
mxw
import
prepare_images
as
pi
import
prepare_workspace
as
pw
...
...
@@ -37,20 +37,21 @@ def prepare_point_clouds(pointclouds, lidar_path, verbose, eth3d, pcl_util, SOR,
def
main
():
args
=
set_argparser
().
parse_args
()
args
=
set_
full_
argparser
().
parse_args
()
env
=
vars
(
args
)
if
args
.
show_steps
:
print_workflow
()
return
if
args
.
add_new_videos
:
args
.
skip_step
+=
[
1
,
2
,
4
,
5
,
6
]
env
[
"resume_work"
]
=
True
args
.
skip_step
=
[
1
,
2
,
4
,
5
,
8
]
if
args
.
begin_step
is
not
None
:
args
.
skip_step
+=
list
(
range
(
args
.
begin_step
))
pw
.
check_input_folder
(
args
.
input_folder
)
args
.
workspace
=
args
.
workspace
.
abspath
()
pw
.
prepare_workspace
(
args
.
workspace
,
env
)
colmap
=
Colmap
(
db
=
env
[
"thorough_db"
],
image_path
=
env
[
"
image_path
"
],
image_path
=
env
[
"
colmap_img_root
"
],
mask_path
=
env
[
"mask_path"
],
dense_workspace
=
env
[
"dense_workspace"
],
binary
=
args
.
colmap
,
...
...
@@ -90,21 +91,24 @@ def main():
i
+=
1
if
i
not
in
args
.
skip_step
:
print_step
(
i
,
"Pictures preparation"
)
env
[
"
existing
_pictures"
]
=
pi
.
extract_pictures_to_workspace
(
**
env
)
env
[
"
individual
_pictures"
]
=
pi
.
extract_pictures_to_workspace
(
**
env
)
else
:
env
[
"existing_pictures"
]
=
sum
((
list
(
env
[
"image_path"
].
walkfiles
(
'*{}'
.
format
(
ext
)))
for
ext
in
env
[
"pic_ext"
]),
[])
full_paths
=
sum
((
list
(
env
[
"individual_pictures_path"
].
walkfiles
(
'*{}'
.
format
(
ext
)))
for
ext
in
env
[
"pic_ext"
]),
[])
env
[
"individual_pictures"
]
=
[
path
.
relpath
(
env
[
"colmap_img_root"
])
for
path
in
full_paths
]
i
+=
1
# Get already existing_videos
env
[
"videos_frames_folders"
]
=
{}
by_name
=
{
v
.
stem
:
v
for
v
in
env
[
"videos_list"
]}
for
folder
in
env
[
"video_path"
].
walkdirs
():
video_name
=
folder
.
basename
()
if
video_name
in
by_name
.
keys
():
env
[
"videos_frames_folders"
][
by_name
[
video_name
]]
=
folder
if
i
not
in
args
.
skip_step
:
print_step
(
i
,
"Extracting Videos and selecting optimal frames for a thorough scan"
)
env
[
"videos_frames_folders"
]
=
pi
.
extract_videos_to_workspace
(
fps
=
args
.
lowfps
,
**
env
)
else
:
env
[
"videos_frames_folders"
]
=
{}
by_name
=
{
v
.
stem
:
v
for
v
in
env
[
"videos_list"
]}
for
folder
in
env
[
"video_path"
].
walkdirs
():
video_name
=
folder
.
basename
()
if
video_name
in
by_name
.
keys
():
env
[
"videos_frames_folders"
][
by_name
[
video_name
]]
=
folder
new_video_frame_folders
=
pi
.
extract_videos_to_workspace
(
fps
=
args
.
lowfps
,
**
env
)
# Concatenate both already treated videos and newly detected videos
env
[
"videos_frames_folders"
]
=
{
**
env
[
"videos_frames_folders"
],
**
new_video_frame_folders
}
env
[
"videos_workspaces"
]
=
{}
for
v
,
frames_folder
in
env
[
"videos_frames_folders"
].
items
():
env
[
"videos_workspaces"
][
v
]
=
pw
.
prepare_video_workspace
(
v
,
frames_folder
,
**
env
)
...
...
@@ -127,6 +131,7 @@ def main():
if
i
not
in
args
.
skip_step
:
print_step
(
i
,
"Alignment of photogrammetric reconstruction with GPS"
)
env
[
"georef_recon"
].
makedirs_p
()
env
[
"georef_full_recon"
].
makedirs_p
()
colmap
.
align_model
(
output
=
env
[
"georef_recon"
],
input
=
thorough_model
,
ref_images
=
env
[
"georef_frames_list"
])
...
...
@@ -136,6 +141,9 @@ def main():
thorough_model
.
merge_tree
(
env
[
"georef_recon"
])
env
[
"georef_recon"
].
merge_tree
(
env
[
"georef_full_recon"
])
if
args
.
inspect_dataset
:
print
(
"FIRST DATASET INSPECTION"
)
print
(
"Inspection of localisalization of frames used in thorough mapping "
"w.r.t Sparse reconstruction"
)
colmap
.
export_model
(
output
=
env
[
"georef_recon"
]
/
"georef_sparse.ply"
,
input
=
env
[
"georef_recon"
])
georef_mlp
=
env
[
"georef_recon"
]
/
"georef_recon.mlp"
...
...
@@ -145,7 +153,7 @@ def main():
output_type
=
"TXT"
)
eth3d
.
inspect_dataset
(
scan_meshlab
=
georef_mlp
,
colmap_model
=
env
[
"georef_recon"
],
image_path
=
env
[
"
image_path
"
])
image_path
=
env
[
"
colmap_img_root
"
])
i
+=
1
if
i
not
in
args
.
skip_step
:
...
...
@@ -163,30 +171,13 @@ def main():
i
+=
1
if
i
not
in
args
.
skip_step
:
print_step
(
i
,
"Full reconstruction point cloud densificitation"
)
env
[
"georef_full_recon"
].
makedirs_p
()
colmap
.
undistort
(
input
=
env
[
"georef_full_recon"
])
colmap
.
dense_stereo
(
min_depth
=
env
[
"stereo_min_depth"
],
max_depth
=
env
[
"stereo_max_depth"
])
colmap
.
stereo_fusion
(
output
=
env
[
"georefrecon_ply"
])
def
get_matrix
(
path
):
if
path
.
isfile
():
'''Note : We use the inverse matrix here, because in general, it's easier to register the reconstructed model into the lidar one,
as the reconstructed will have less points, but in the end we need the matrix to apply to the lidar point to be aligned
with the camera positions (ie the inverse)'''
return
np
.
linalg
.
inv
(
np
.
fromfile
(
path
,
sep
=
" "
).
reshape
(
4
,
4
))
else
:
print
(
"Error, no registration matrix can be found"
)
print
(
"Ensure that your registration matrix was saved under the name {}"
.
format
(
path
))
decision
=
None
while
decision
not
in
[
"y"
,
"n"
,
""
]:
decision
=
input
(
"retry ? [Y/n]"
)
if
decision
.
lower
()
in
[
"y"
,
""
]:
return
get_matrix
(
path
)
elif
decision
.
lower
()
==
"n"
:
return
np
.
eye
(
4
)
i
+=
1
if
i
not
in
args
.
skip_step
:
print_step
(
i
,
"
Registration
of photogrammetric reconstruction with respect to Lidar Point Cloud"
)
print_step
(
i
,
"
Alignment
of photogrammetric reconstruction with respect to Lidar Point Cloud"
)
if
args
.
registration_method
==
"eth3d"
:
# Note : ETH3D doesn't register with scale, this might not be suitable for very large areas
mxw
.
add_meshes_to_project
(
env
[
"lidar_mlp"
],
env
[
"aligned_mlp"
],
[
env
[
"georefrecon_ply"
]],
start_index
=
0
)
...
...
@@ -248,22 +239,27 @@ def main():
if
args
.
inspect_dataset
:
# First inspection : Check registration of the Lidar pointcloud wrt to COLMAP model but without the occlusion mesh
# Second inspection : Check the occlusion mesh and the splats
colmap
.
export_model
(
output
=
env
[
"georef_full_recon"
]
/
"georef_sparse.ply"
,
input
=
env
[
"georef_full_recon"
])
georef_mlp
=
env
[
"georef_recon"
]
/
"georef_recon.mlp"
mxw
.
create_project
(
georef_mlp
,
[
env
[
"georefrecon_ply"
]])
colmap
.
export_model
(
output
=
env
[
"georef_full_recon"
],
input
=
env
[
"georef_full_recon"
],
output_type
=
"TXT"
)
print
(
"SECOND DATASET INSPECTION"
)
print
(
"Inspection of localisalization of frames used in thorough mapping "
"w.r.t Dense reconstruction"
)
eth3d
.
inspect_dataset
(
scan_meshlab
=
georef_mlp
,
colmap_model
=
env
[
"georef_full_recon"
],
image_path
=
env
[
"image_path"
])
image_path
=
env
[
"colmap_img_root"
])
print
(
"Inspection of localisalization of frames used in thorough mapping "
"w.r.t Aligned Lidar Point Cloud"
)
eth3d
.
inspect_dataset
(
scan_meshlab
=
env
[
"aligned_mlp"
],
colmap_model
=
env
[
"georef_full_recon"
],
image_path
=
env
[
"image_path"
])
image_path
=
env
[
"colmap_img_root"
])
print
(
"Inspection of localisalization of frames used in thorough mapping "
"w.r.t Aligned Lidar Point Cloud and Occlusion Meshes"
)
eth3d
.
inspect_dataset
(
scan_meshlab
=
env
[
"aligned_mlp"
],
colmap_model
=
env
[
"georef_full_recon"
],
image_path
=
env
[
"
image_path
"
],
image_path
=
env
[
"
colmap_img_root
"
],
occlusions
=
env
[
"occlusion_ply"
],
splats
=
env
[
"splats_ply"
])
...
...
@@ -279,6 +275,9 @@ def main():
num_videos
=
len
(
env
[
"videos_to_localize"
]),
metadata
=
video_env
[
"metadata"
],
**
video_env
[
"output_env"
],
**
env
)
if
env
[
"generate_groundtruth_for_individual_images"
]:
generate_GT_individual_pictures
(
input_colmap_model
=
env
[
"georef_full_recon"
],
step_index
=
i
,
**
env
)
if
__name__
==
'__main__'
:
...
...
main_pipeline_no_lidar.py
View file @
ac6f363f
import
numpy
as
np
from
wrappers
import
Colmap
,
FFMpeg
,
PDraw
,
ETH3D
,
PCLUtil
from
cli_utils
import
set_argparser
,
print_step
,
print_workflow
from
cli_utils
import
set_
full_
argparser
,
print_step
,
print_workflow
from
video_localization
import
localize_video
,
generate_GT
import
meshlab_xml_writer
as
mxw
import
prepare_images
as
pi
...
...
@@ -8,7 +8,7 @@ import prepare_workspace as pw
def
main
():
args
=
set_argparser
().
parse_args
()
args
=
set_
full_
argparser
().
parse_args
()
env
=
vars
(
args
)
if
args
.
show_steps
:
print_workflow
()
...
...
@@ -21,7 +21,7 @@ def main():
args
.
workspace
=
args
.
workspace
.
abspath
()
pw
.
prepare_workspace
(
args
.
workspace
,
env
,
with_lidar
=
False
)
colmap
=
Colmap
(
db
=
env
[
"thorough_db"
],
image_path
=
env
[
"
image_path
"
],
image_path
=
env
[
"
colmap_img_root
"
],
mask_path
=
env
[
"mask_path"
],
dense_workspace
=
env
[
"dense_workspace"
],
binary
=
args
.
colmap
,
...
...
@@ -49,7 +49,7 @@ def main():
print_step
(
i
,
"Pictures preparation"
)
env
[
"existing_pictures"
]
=
pi
.
extract_pictures_to_workspace
(
**
env
)
else
:
env
[
"existing_pictures"
]
=
sum
((
list
(
env
[
"
image_path
"
].
walkfiles
(
'*{}'
.
format
(
ext
)))
for
ext
in
env
[
"pic_ext"
]),
[])
env
[
"existing_pictures"
]
=
sum
((
list
(
env
[
"
colmap_img_root
"
].
walkfiles
(
'*{}'
.
format
(
ext
)))
for
ext
in
env
[
"pic_ext"
]),
[])
i
+=
1
if
i
not
in
args
.
skip_step
:
...
...
@@ -103,7 +103,7 @@ def main():
output_type
=
"TXT"
)
eth3d
.
inspect_dataset
(
scan_meshlab
=
georef_mlp
,
colmap_model
=
env
[
"georef_recon"
],
image_path
=
env
[
"
image_path
"
])