Merge pull request #1403 from alicevision/dev/distortionCalibration

New lens distortion calibration node
This commit is contained in:
Fabien Castan 2021-06-01 23:03:43 +02:00 committed by GitHub
commit d3cb164316
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 85 additions and 10 deletions

View file

@ -12,7 +12,7 @@ imageExtensions = (
# cineon: # cineon:
'.cin', '.cin',
# dds # dds
'dds' '.dds',
# dpx: # dpx:
'.dpx', '.dpx',
# gif: # gif:
@ -490,6 +490,12 @@ def cameraTrackingPipeline(graph, sourceSfm=None):
sfmNodes, _ = sfmAugmentation(graph, sourceSfm) sfmNodes, _ = sfmAugmentation(graph, sourceSfm)
cameraInitT, featureExtractionT, imageMatchingT, featureMatchingT, structureFromMotionT = sfmNodes cameraInitT, featureExtractionT, imageMatchingT, featureMatchingT, structureFromMotionT = sfmNodes
distortionCalibrationT = graph.addNewNode('DistortionCalibration',
input=cameraInitT.output)
graph.removeEdge(featureMatchingT.input)
graph.addEdge(distortionCalibrationT.outSfMData, featureMatchingT.input)
imageMatchingT.attribute("nbMatches").value = 5 # voctree nb matches imageMatchingT.attribute("nbMatches").value = 5 # voctree nb matches
imageMatchingT.attribute("nbNeighbors").value = 10 imageMatchingT.attribute("nbNeighbors").value = 10
@ -500,6 +506,8 @@ def cameraTrackingPipeline(graph, sourceSfm=None):
structureFromMotionT.attribute("minAngleForLandmark").value = 0.5 structureFromMotionT.attribute("minAngleForLandmark").value = 0.5
exportAnimatedCameraT = graph.addNewNode('ExportAnimatedCamera', input=structureFromMotionT.output) exportAnimatedCameraT = graph.addNewNode('ExportAnimatedCamera', input=structureFromMotionT.output)
if sourceSfm:
graph.addEdge(sourceSfm.output, exportAnimatedCameraT.sfmDataFilter)
# store current pipeline version in graph header # store current pipeline version in graph header
graph.header.update({'pipelineVersion': __version__}) graph.header.update({'pipelineVersion': __version__})
@ -509,6 +517,7 @@ def cameraTrackingPipeline(graph, sourceSfm=None):
featureExtractionT, featureExtractionT,
imageMatchingT, imageMatchingT,
featureMatchingT, featureMatchingT,
distortionCalibrationT,
structureFromMotionT, structureFromMotionT,
exportAnimatedCameraT, exportAnimatedCameraT,
] ]
@ -537,7 +546,7 @@ def photogrammetryAndCameraTracking(inputImages=list(), inputViewpoints=list(),
with GraphModification(graph): with GraphModification(graph):
cameraInit, featureExtraction, imageMatching, featureMatching, structureFromMotion = sfmPipeline(graph) cameraInit, featureExtraction, imageMatching, featureMatching, structureFromMotion = sfmPipeline(graph)
cameraInitT, featureExtractionT, imageMatchingMultiT, featureMatchingT, structureFromMotionT, exportAnimatedCameraT = cameraTrackingPipeline(graph, structureFromMotion) cameraInitT, featureExtractionT, imageMatchingMultiT, featureMatchingT, distortionCalibrationT, structureFromMotionT, exportAnimatedCameraT = cameraTrackingPipeline(graph, structureFromMotion)
cameraInit.viewpoints.extend([{'path': image} for image in inputImages]) cameraInit.viewpoints.extend([{'path': image} for image in inputImages])
cameraInit.viewpoints.extend(inputViewpoints) cameraInit.viewpoints.extend(inputViewpoints)

View file

@ -34,7 +34,10 @@ Intrinsic = [
"So this value is used to limit the range of possible values in the optimization. \n" "So this value is used to limit the range of possible values in the optimization. \n"
"If you put -1, this value will not be used and the focal length will not be bounded.", "If you put -1, this value will not be used and the focal length will not be bounded.",
value=-1.0, uid=[0], range=None), value=-1.0, uid=[0], range=None),
desc.FloatParam(name="pxFocalLength", label="Focal Length", description="Known/Calibrated Focal Length (in pixels)", value=-1.0, uid=[0], range=None), desc.GroupAttribute(name="pxFocalLength", label="Focal Length", description="Known/Calibrated Focal Length (in pixels)", groupDesc=[
desc.FloatParam(name="x", label="x", description="", value=-1, uid=[], range=(0, 10000, 1)),
desc.FloatParam(name="y", label="y", description="", value=-1, uid=[], range=(0, 10000, 1)),
]),
desc.ChoiceParam(name="type", label="Camera Type", desc.ChoiceParam(name="type", label="Camera Type",
description="Mathematical Model used to represent a camera:\n" description="Mathematical Model used to represent a camera:\n"
" * pinhole: Simplest projective camera model without optical distortion (focal and optical center).\n" " * pinhole: Simplest projective camera model without optical distortion (focal and optical center).\n"
@ -42,8 +45,11 @@ Intrinsic = [
" * radial3: Pinhole camera with 3 radial distortion parameters\n" " * radial3: Pinhole camera with 3 radial distortion parameters\n"
" * brown: Pinhole camera with 3 radial and 2 tangential distortion parameters\n" " * brown: Pinhole camera with 3 radial and 2 tangential distortion parameters\n"
" * fisheye4: Pinhole camera with 4 distortion parameters suited for fisheye optics (like 120deg FoV)\n" " * fisheye4: Pinhole camera with 4 distortion parameters suited for fisheye optics (like 120deg FoV)\n"
" * equidistant_r3: Non-projective camera model suited for full-fisheye optics (like 180deg FoV)\n", " * equidistant_r3: Non-projective camera model suited for full-fisheye optics (like 180deg FoV)\n"
value="", values=['', 'pinhole', 'radial1', 'radial3', 'brown', 'fisheye4', 'equidistant_r3'], exclusive=True, uid=[0]), " * 3deanamorphic4: Pinhole camera with a 4 anamorphic distortion coefficients.\n"
" * 3declassicld: Pinhole camera with a 10 anamorphic distortion coefficients\n"
" * 3deradial4: Pinhole camera with 3DE radial4 model\n",
value="", values=['', 'pinhole', 'radial1', 'radial3', 'brown', 'fisheye4', 'equidistant_r3', '3deanamorphic4', '3declassicld', '3deradial4'], exclusive=True, uid=[0]),
desc.IntParam(name="width", label="Width", description="Image Width", value=0, uid=[], range=(0, 10000, 1)), desc.IntParam(name="width", label="Width", description="Image Width", value=0, uid=[], range=(0, 10000, 1)),
desc.IntParam(name="height", label="Height", description="Image Height", value=0, uid=[], range=(0, 10000, 1)), desc.IntParam(name="height", label="Height", description="Image Height", value=0, uid=[], range=(0, 10000, 1)),
desc.FloatParam(name="sensorWidth", label="Sensor Width", description="Sensor Width (mm)", value=36, uid=[], range=(0, 1000, 1)), desc.FloatParam(name="sensorWidth", label="Sensor Width", description="Sensor Width (mm)", value=36, uid=[], range=(0, 1000, 1)),
@ -100,6 +106,12 @@ def readSfMData(sfmFile):
intrinsic['principalPoint'] = {} intrinsic['principalPoint'] = {}
intrinsic['principalPoint']['x'] = pp[0] intrinsic['principalPoint']['x'] = pp[0]
intrinsic['principalPoint']['y'] = pp[1] intrinsic['principalPoint']['y'] = pp[1]
f = intrinsic['pxFocalLength']
intrinsic['pxFocalLength'] = {}
intrinsic['pxFocalLength']['x'] = f[0]
intrinsic['pxFocalLength']['y'] = f[1]
# convert empty string distortionParams (i.e: Pinhole model) to empty list # convert empty string distortionParams (i.e: Pinhole model) to empty list
if intrinsic['distortionParams'] == '': if intrinsic['distortionParams'] == '':
intrinsic['distortionParams'] = list() intrinsic['distortionParams'] = list()
@ -182,8 +194,8 @@ The metadata needed are:
name='allowedCameraModels', name='allowedCameraModels',
label='Allowed Camera Models', label='Allowed Camera Models',
description='the Camera Models that can be attributed.', description='the Camera Models that can be attributed.',
value=['pinhole', 'radial1', 'radial3', 'brown', 'fisheye4', 'fisheye1'], value=['pinhole', 'radial1', 'radial3', 'brown', 'fisheye4', 'fisheye1', '3deanamorphic4', '3deradial4', '3declassicld'],
values=['pinhole', 'radial1', 'radial3', 'brown', 'fisheye4', 'fisheye1'], values=['pinhole', 'radial1', 'radial3', 'brown', 'fisheye4', 'fisheye1', '3deanamorphic4', '3deradial4', '3declassicld'],
exclusive=False, exclusive=False,
uid=[], uid=[],
joinChar=',', joinChar=',',
@ -298,6 +310,7 @@ The metadata needed are:
intrinsics = node.intrinsics.getPrimitiveValue(exportDefault=True) intrinsics = node.intrinsics.getPrimitiveValue(exportDefault=True)
for intrinsic in intrinsics: for intrinsic in intrinsics:
intrinsic['principalPoint'] = [intrinsic['principalPoint']['x'], intrinsic['principalPoint']['y']] intrinsic['principalPoint'] = [intrinsic['principalPoint']['x'], intrinsic['principalPoint']['y']]
intrinsic['pxFocalLength'] = [intrinsic['pxFocalLength']['x'], intrinsic['pxFocalLength']['y']]
views = node.viewpoints.getPrimitiveValue(exportDefault=False) views = node.viewpoints.getPrimitiveValue(exportDefault=False)
# convert the metadata string into a map # convert the metadata string into a map
@ -306,7 +319,7 @@ The metadata needed are:
view['metadata'] = json.loads(view['metadata']) view['metadata'] = json.loads(view['metadata'])
sfmData = { sfmData = {
"version": [1, 0, 0], "version": [1, 2, 0],
"views": views + newViews, "views": views + newViews,
"intrinsics": intrinsics, "intrinsics": intrinsics,
"featureFolder": "", "featureFolder": "",

View file

@ -0,0 +1,53 @@
__version__ = '2.0'
from meshroom.core import desc
class DistortionCalibration(desc.CommandLineNode):
commandLine = 'aliceVision_distortionCalibration {allParams}'
size = desc.DynamicNodeSize('input')
documentation = '''
Calibration of a camera/lens couple distortion using a full screen checkerboard
'''
inputs = [
desc.File(
name='input',
label='SfmData',
description='SfmData File',
value='',
uid=[0],
),
desc.ListAttribute(
elementDesc=desc.File(
name='lensGridImage',
label='Lens Grid Image',
description='',
value='',
uid=[0],
),
name='lensGrid',
label='Lens Grid Images',
description='Lens grid images to estimate the optical distortions.',
),
desc.ChoiceParam(
name='verboseLevel',
label='Verbose Level',
description='Verbosity level (fatal, error, warning, info, debug, trace).',
value='info',
values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'],
exclusive=True,
uid=[],
),
]
outputs = [
desc.File(
name='outSfMData',
label='Output SfmData File',
description='Path to the output sfmData file',
value=desc.Node.internalFolder + 'sfmData.sfm',
uid=[],
)
]

View file

@ -360,8 +360,8 @@ class ViewpointWrapper(QObject):
""" Get camera vertical field of view in degrees. """ """ Get camera vertical field of view in degrees. """
if not self.solvedIntrinsics: if not self.solvedIntrinsics:
return None return None
pxFocalLength = float(self.solvedIntrinsics["pxFocalLength"]) pxFocalLength = self.solvedIntrinsics["pxFocalLength"]
return 2.0 * math.atan(self.orientedImageSize.height() / (2.0 * pxFocalLength)) * 180 / math.pi return 2.0 * math.atan(self.orientedImageSize.height() / (2.0 * float(pxFocalLength[0]))) * 180 / math.pi
@Property(type=QUrl, notify=denseSceneParamsChanged) @Property(type=QUrl, notify=denseSceneParamsChanged)
def undistortedImageSource(self): def undistortedImageSource(self):