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 json
from opendm import context
from opendm import io
from opendm import log
@ -23,6 +24,25 @@ def alphanumeric_string(string):
raise argparse.ArgumentTypeError(msg)
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
def url_string(string):
import re
@ -137,6 +157,16 @@ def config():
default=False,
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',
metavar='<positive integer>',
default=context.num_cores,
@ -556,6 +586,8 @@ def config():
'%(default)s'))
args = parser.parse_args()
print(args.cameras)
exit(1)
# check that the project path setting has been set properly
if not args.project_path:

Wyświetl plik

@ -8,6 +8,7 @@ from opendm import io
from opendm import log
from opendm import system
from opendm import context
from opendm import camera
from opensfm.large import metadataset
from opensfm.large import tools
@ -85,13 +86,14 @@ class OSFMContext:
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"))
# check for cameras.json
# TODO: use config.cameras
camera_models_file = os.path.join(args.project_path, "cameras.json")
has_camera_calibration = io.file_exists(camera_models_file)
if has_camera_calibration:
log.ODM_DEBUG("Copied cameras.json to OpenSfM directory (camera_models_overrides.json)")
io.copy(camera_models_file, os.path.join(self.opensfm_project_path, "camera_models_overrides.json"))
# check for cameras
if args.cameras:
try:
with open(os.path.join(self.opensfm_project_path, "camera_models_overrides.json"), 'r') as f:
f.write(json.dumps(camera.get_opensfm_camera_models(args.cameras)))
log.ODM_DEBUG("Wrote camera_models_overrides.json to OpenSfM directory (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
config = [
@ -196,30 +198,11 @@ class OSFMContext:
def extract_cameras(self, output, rerun=False):
reconstruction_file = self.path("reconstruction.json")
if not os.path.exists(output) or rerun:
if os.path.exists(reconstruction_file):
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)
try:
with open(output, 'w') as fout:
fout.write(json.dumps(result))
else:
log.ODM_WARNING("Cannot export cameras to %s. reconstruction.json does not exist." % output)
fout.write(json.dumps(camera.get_cameras_from_opensfm(reconstruction_file)))
except Exception as e:
log.ODM_WARNING("Cannot export cameras to %s. %s." % (output, str(e)))
else:
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()