Added camera module, tests

Former-commit-id: 48298e646c
pull/1161/head
Piero Toffanin 2019-06-25 12:22:27 -04:00
rodzic e6e62aae36
commit 3b4bcb09e4
5 zmienionych plików z 181 dodań i 30 usunięć

72
opendm/camera.py 100644
Wyświetl plik

@ -0,0 +1,72 @@
import os, json
from opendm import log
def get_cameras_from_opensfm(reconstruction_file):
"""
Extract the cameras from OpenSfM's reconstruction.json
"""
if os.path.exists(reconstruction_file):
with open(reconstruction_file, 'r') as fin:
reconstructions = json.loads(fin.read())
result = {}
for recon in reconstructions:
if 'cameras' in recon:
for camera_id in recon['cameras']:
# Strip "v2" from OpenSfM camera IDs
new_camera_id = camera_id
if new_camera_id.startswith("v2 "):
new_camera_id = new_camera_id[3:]
result[new_camera_id] = recon['cameras'][camera_id]
# Remove "_prior" keys
keys = list(result[new_camera_id].keys())
for k in keys:
if k.endswith('_prior'):
result[new_camera_id].pop(k)
return result
else:
raise RuntimeError("%s does not exist." % reconstruction_file)
def get_opensfm_camera_models(cameras):
"""
Convert cameras to a format OpenSfM can understand
(opposite of get_cameras_from_opensfm)
"""
if isinstance(cameras, dict):
result = {}
for camera_id in cameras:
# Quick check on IDs
if len(camera_id.split(" ")) < 6:
raise RuntimeError("Invalid cameraID: %s" % camera_id)
# Add "v2" to camera ID
if not camera_id.startswith("v2 "):
osfm_camera_id = "v2 " + camera_id
else:
osfm_camera_id = camera_id
# Add "_prior" keys
camera = cameras[camera_id]
prior_fields = ["focal","focal_x","focal_y","c_x","c_y","k1","k2","p1","p2","k3"]
valid_fields = ["id","width","height","projection_type"] + prior_fields + [f + "_prior" for f in prior_fields]
keys = list(camera.keys())
for param in keys:
param_prior = param + "_prior"
if param in prior_fields and not param_prior in camera:
camera[param_prior] = camera[param]
# Remove invalid keys
keys = list(camera.keys())
for k in keys:
if not k in valid_fields:
camera.pop(k)
log.ODM_WARNING("Invalid camera key ignored: %s" % k)
result[osfm_camera_id] = camera
return result
else:
raise RuntimeError("Invalid cameras format: %s. Expected dict." % str(cameras))

Wyświetl plik

@ -1,4 +1,5 @@
import argparse import argparse
import json
from opendm import context from opendm import context
from opendm import io from opendm import io
from opendm import log from opendm import log
@ -23,6 +24,25 @@ def alphanumeric_string(string):
raise argparse.ArgumentTypeError(msg) raise argparse.ArgumentTypeError(msg)
return string return string
def path_or_json_string(string):
if string == "":
return {}
if string.startswith("[") or string.startswith("{"):
try:
return json.loads(string)
except:
raise argparse.ArgumentTypeError("{0} is not a valid JSON string.".format(string))
elif io.file_exists(string):
try:
with open(string, 'r') as f:
return json.loads(f.read())
except:
raise argparse.ArgumentTypeError("{0} is not a valid JSON file.".format(string))
else:
raise argparse.ArgumentTypeError("{0} is not a valid JSON file or string.".format(string))
# Django URL validation regex # Django URL validation regex
def url_string(string): def url_string(string):
import re import re
@ -137,6 +157,16 @@ def config():
default=False, default=False,
help='Turn off camera parameter optimization during bundler') help='Turn off camera parameter optimization during bundler')
parser.add_argument('--cameras',
default='',
metavar='<string>',
type=path_or_json_string,
help='Use the camera parameters computed from '
'another dataset instead of calculating them. '
'Can be specified either as path to a cameras.json file or as a '
'JSON string representing the contents of a '
'cameras.json file. Default: %(default)s')
parser.add_argument('--max-concurrency', parser.add_argument('--max-concurrency',
metavar='<positive integer>', metavar='<positive integer>',
default=context.num_cores, default=context.num_cores,
@ -556,6 +586,8 @@ def config():
'%(default)s')) '%(default)s'))
args = parser.parse_args() args = parser.parse_args()
print(args.cameras)
exit(1)
# check that the project path setting has been set properly # check that the project path setting has been set properly
if not args.project_path: if not args.project_path:

Wyświetl plik

@ -8,6 +8,7 @@ from opendm import io
from opendm import log from opendm import log
from opendm import system from opendm import system
from opendm import context from opendm import context
from opendm import camera
from opensfm.large import metadataset from opensfm.large import metadataset
from opensfm.large import tools from opensfm.large import tools
@ -85,13 +86,14 @@ class OSFMContext:
log.ODM_DEBUG("Copied image_groups.txt to OpenSfM directory") log.ODM_DEBUG("Copied image_groups.txt to OpenSfM directory")
io.copy(image_groups_file, os.path.join(self.opensfm_project_path, "image_groups.txt")) io.copy(image_groups_file, os.path.join(self.opensfm_project_path, "image_groups.txt"))
# check for cameras.json # check for cameras
# TODO: use config.cameras if args.cameras:
camera_models_file = os.path.join(args.project_path, "cameras.json") try:
has_camera_calibration = io.file_exists(camera_models_file) with open(os.path.join(self.opensfm_project_path, "camera_models_overrides.json"), 'r') as f:
if has_camera_calibration: f.write(json.dumps(camera.get_opensfm_camera_models(args.cameras)))
log.ODM_DEBUG("Copied cameras.json to OpenSfM directory (camera_models_overrides.json)") log.ODM_DEBUG("Wrote camera_models_overrides.json to OpenSfM directory (camera_models_overrides.json)")
io.copy(camera_models_file, os.path.join(self.opensfm_project_path, "camera_models_overrides.json")) except Exception as e:
log.ODM_WARNING("Cannot set camera_models_overrides.json: %s" % str(e))
# create config file for OpenSfM # create config file for OpenSfM
config = [ config = [
@ -196,30 +198,11 @@ class OSFMContext:
def extract_cameras(self, output, rerun=False): def extract_cameras(self, output, rerun=False):
reconstruction_file = self.path("reconstruction.json") reconstruction_file = self.path("reconstruction.json")
if not os.path.exists(output) or rerun: if not os.path.exists(output) or rerun:
if os.path.exists(reconstruction_file): try:
result = {}
with open(reconstruction_file, 'r') as fin:
json_f = json.loads(fin.read())
for recon in json_f:
if 'cameras' in recon:
for camera_id in recon['cameras']:
# Strip "v2" from OpenSfM camera IDs
new_camera_id = camera_id
if new_camera_id.startswith("v2 "):
new_camera_id = new_camera_id[3:]
result[new_camera_id] = recon['cameras'][camera_id]
# Remove "_prior" keys
keys = list(result[new_camera_id].keys())
for k in keys:
if k.endswith('_prior'):
result[new_camera_id].pop(k)
with open(output, 'w') as fout: with open(output, 'w') as fout:
fout.write(json.dumps(result)) fout.write(json.dumps(camera.get_cameras_from_opensfm(reconstruction_file)))
else: except Exception as e:
log.ODM_WARNING("Cannot export cameras to %s. reconstruction.json does not exist." % output) log.ODM_WARNING("Cannot export cameras to %s. %s." % (output, str(e)))
else: else:
log.ODM_INFO("Already extracted cameras") log.ODM_INFO("Already extracted cameras")

Wyświetl plik

@ -0,0 +1,23 @@
[
{
"reference_lla": {
"latitude": 46.84265666666667,
"altitude": 0,
"longitude": -91.99400802469134
},
"cameras": {
"v2 dji fc300s 4000 2250 perspective 0.5555": {
"focal_prior": 0.5555555555555556,
"width": 4000,
"k1": 0.0052219633739416784,
"k2": 0.015202301516716735,
"k1_prior": 0.0,
"k2_prior": 0.0,
"projection_type": "perspective",
"focal": 0.5614307793062748,
"height": 2250
}
},
"shots": {}
}
]

Wyświetl plik

@ -0,0 +1,41 @@
import time
import unittest
import os
import shutil
from opendm import camera
class TestCamera(unittest.TestCase):
def setUp(self):
if os.path.exists("tests/assets/output"):
shutil.rmtree("tests/assets/output")
os.makedirs("tests/assets/output")
def test_camera(self):
c = camera.get_cameras_from_opensfm("tests/assets/reconstruction.json")
self.assertEqual(len(c.keys()), 1)
camera_id = c.keys()[0]
self.assertTrue('v2 ' not in camera_id)
self.assertRaises(RuntimeError, camera.get_cameras_from_opensfm, 'tests/assets/nonexistant.json')
self.assertRaises(ValueError, camera.get_cameras_from_opensfm, 'tests/assets/gcp_extras.txt')
self.assertFalse('k1_prior' in c[camera_id])
# Add bogus field
c[camera_id]['test'] = 0
osfm_c = camera.get_opensfm_camera_models(c)
self.assertEqual(len(osfm_c.keys()), 1)
c1 = osfm_c[osfm_c.keys()[0]]
self.assertTrue('k1_prior' in c1)
self.assertTrue('k2_prior' in c1)
self.assertFalse('test' in c1)
self.assertEqual(c1['k1'], c1['k1_prior'])
self.assertEqual(c1['k2'], c1['k2_prior'])
self.assertEqual(c1['focal'], c1['focal_prior'])
self.assertTrue('width_prior' not in c1)
if __name__ == '__main__':
unittest.main()