mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-08-06 10:18:42 +02:00
Merge pull request #1403 from alicevision/dev/distortionCalibration
New lens distortion calibration node
This commit is contained in:
commit
d3cb164316
4 changed files with 85 additions and 10 deletions
|
@ -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)
|
||||||
|
|
|
@ -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": "",
|
||||||
|
|
53
meshroom/nodes/aliceVision/DistortionCalibration.py
Normal file
53
meshroom/nodes/aliceVision/DistortionCalibration.py
Normal 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=[],
|
||||||
|
)
|
||||||
|
]
|
|
@ -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):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue