diff --git a/meshroom/multiview.py b/meshroom/multiview.py index 8cc0230579..7380b0ea59 100644 --- a/meshroom/multiview.py +++ b/meshroom/multiview.py @@ -12,7 +12,7 @@ # cineon: '.cin', # dds - 'dds' + '.dds', # dpx: '.dpx', # gif: @@ -490,6 +490,12 @@ def cameraTrackingPipeline(graph, sourceSfm=None): sfmNodes, _ = sfmAugmentation(graph, sourceSfm) 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("nbNeighbors").value = 10 @@ -500,6 +506,8 @@ def cameraTrackingPipeline(graph, sourceSfm=None): structureFromMotionT.attribute("minAngleForLandmark").value = 0.5 exportAnimatedCameraT = graph.addNewNode('ExportAnimatedCamera', input=structureFromMotionT.output) + if sourceSfm: + graph.addEdge(sourceSfm.output, exportAnimatedCameraT.sfmDataFilter) # store current pipeline version in graph header graph.header.update({'pipelineVersion': __version__}) @@ -509,6 +517,7 @@ def cameraTrackingPipeline(graph, sourceSfm=None): featureExtractionT, imageMatchingT, featureMatchingT, + distortionCalibrationT, structureFromMotionT, exportAnimatedCameraT, ] @@ -537,7 +546,7 @@ def photogrammetryAndCameraTracking(inputImages=list(), inputViewpoints=list(), with GraphModification(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(inputViewpoints) diff --git a/meshroom/nodes/aliceVision/CameraInit.py b/meshroom/nodes/aliceVision/CameraInit.py index a0cf800ed7..f37e808097 100644 --- a/meshroom/nodes/aliceVision/CameraInit.py +++ b/meshroom/nodes/aliceVision/CameraInit.py @@ -34,7 +34,10 @@ "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.", 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", description="Mathematical Model used to represent a camera:\n" " * pinhole: Simplest projective camera model without optical distortion (focal and optical center).\n" @@ -42,8 +45,11 @@ " * radial3: Pinhole camera with 3 radial 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" - " * 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]), + " * equidistant_r3: Non-projective camera model suited for full-fisheye optics (like 180deg FoV)\n" + " * 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="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)), @@ -100,6 +106,12 @@ def readSfMData(sfmFile): intrinsic['principalPoint'] = {} intrinsic['principalPoint']['x'] = pp[0] 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 if intrinsic['distortionParams'] == '': intrinsic['distortionParams'] = list() @@ -182,8 +194,8 @@ class CameraInit(desc.CommandLineNode): name='allowedCameraModels', label='Allowed Camera Models', description='the Camera Models that can be attributed.', - value=['pinhole', 'radial1', 'radial3', 'brown', 'fisheye4', 'fisheye1'], - values=['pinhole', 'radial1', 'radial3', 'brown', 'fisheye4', 'fisheye1'], + value=['pinhole', 'radial1', 'radial3', 'brown', 'fisheye4', 'fisheye1', '3deanamorphic4', '3deradial4', '3declassicld'], + values=['pinhole', 'radial1', 'radial3', 'brown', 'fisheye4', 'fisheye1', '3deanamorphic4', '3deradial4', '3declassicld'], exclusive=False, uid=[], joinChar=',', @@ -298,6 +310,7 @@ def createViewpointsFile(self, node, additionalViews=()): intrinsics = node.intrinsics.getPrimitiveValue(exportDefault=True) for intrinsic in intrinsics: intrinsic['principalPoint'] = [intrinsic['principalPoint']['x'], intrinsic['principalPoint']['y']] + intrinsic['pxFocalLength'] = [intrinsic['pxFocalLength']['x'], intrinsic['pxFocalLength']['y']] views = node.viewpoints.getPrimitiveValue(exportDefault=False) # convert the metadata string into a map @@ -306,7 +319,7 @@ def createViewpointsFile(self, node, additionalViews=()): view['metadata'] = json.loads(view['metadata']) sfmData = { - "version": [1, 0, 0], + "version": [1, 2, 0], "views": views + newViews, "intrinsics": intrinsics, "featureFolder": "", diff --git a/meshroom/nodes/aliceVision/DistortionCalibration.py b/meshroom/nodes/aliceVision/DistortionCalibration.py new file mode 100644 index 0000000000..87fa9606f0 --- /dev/null +++ b/meshroom/nodes/aliceVision/DistortionCalibration.py @@ -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=[], + ) + ] diff --git a/meshroom/ui/reconstruction.py b/meshroom/ui/reconstruction.py index 187d136c26..96c9205f9b 100755 --- a/meshroom/ui/reconstruction.py +++ b/meshroom/ui/reconstruction.py @@ -360,8 +360,8 @@ def fieldOfView(self): """ Get camera vertical field of view in degrees. """ if not self.solvedIntrinsics: return None - pxFocalLength = float(self.solvedIntrinsics["pxFocalLength"]) - return 2.0 * math.atan(self.orientedImageSize.height() / (2.0 * pxFocalLength)) * 180 / math.pi + pxFocalLength = self.solvedIntrinsics["pxFocalLength"] + return 2.0 * math.atan(self.orientedImageSize.height() / (2.0 * float(pxFocalLength[0]))) * 180 / math.pi @Property(type=QUrl, notify=denseSceneParamsChanged) def undistortedImageSource(self):