Merge pull request #1483 from alicevision/dev/masking

New ImageMasking and MeshMasking nodes
This commit is contained in:
Fabien Castan 2021-07-23 00:09:42 +02:00 committed by GitHub
commit 66e046c613
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 442 additions and 23 deletions

View file

@ -10,7 +10,7 @@ class Attribute(BaseObject):
"""
"""
def __init__(self, name, label, description, value, advanced, uid, group, enabled):
def __init__(self, name, label, description, value, advanced, semantic, uid, group, enabled):
super(Attribute, self).__init__()
self._name = name
self._label = label
@ -20,6 +20,7 @@ class Attribute(BaseObject):
self._group = group
self._advanced = advanced
self._enabled = enabled
self._semantic = semantic
name = Property(str, lambda self: self._name, constant=True)
label = Property(str, lambda self: self._label, constant=True)
@ -29,6 +30,7 @@ class Attribute(BaseObject):
group = Property(str, lambda self: self._group, constant=True)
advanced = Property(bool, lambda self: self._advanced, constant=True)
enabled = Property(Variant, lambda self: self._enabled, constant=True)
semantic = Property(str, lambda self: self._semantic, constant=True)
type = Property(str, lambda self: self.__class__.__name__, constant=True)
def validateValue(self, value):
@ -55,13 +57,13 @@ class Attribute(BaseObject):
class ListAttribute(Attribute):
""" A list of Attributes """
def __init__(self, elementDesc, name, label, description, group='allParams', advanced=False, enabled=True, joinChar=' '):
def __init__(self, elementDesc, name, label, description, group='allParams', advanced=False, semantic='', enabled=True, joinChar=' '):
"""
:param elementDesc: the Attribute description of elements to store in that list
"""
self._elementDesc = elementDesc
self._joinChar = joinChar
super(ListAttribute, self).__init__(name=name, label=label, description=description, value=[], uid=(), group=group, advanced=advanced, enabled=enabled)
super(ListAttribute, self).__init__(name=name, label=label, description=description, value=[], uid=(), group=group, advanced=advanced, semantic=semantic, enabled=enabled)
elementDesc = Property(Attribute, lambda self: self._elementDesc, constant=True)
uid = Property(Variant, lambda self: self.elementDesc.uid, constant=True)
@ -92,13 +94,13 @@ class ListAttribute(Attribute):
class GroupAttribute(Attribute):
""" A macro Attribute composed of several Attributes """
def __init__(self, groupDesc, name, label, description, group='allParams', advanced=False, enabled=True, joinChar=' '):
def __init__(self, groupDesc, name, label, description, group='allParams', advanced=False, semantic='', enabled=True, joinChar=' '):
"""
:param groupDesc: the description of the Attributes composing this group
"""
self._groupDesc = groupDesc
self._joinChar = joinChar
super(GroupAttribute, self).__init__(name=name, label=label, description=description, value={}, uid=(), group=group, advanced=advanced, enabled=enabled)
super(GroupAttribute, self).__init__(name=name, label=label, description=description, value={}, uid=(), group=group, advanced=advanced, semantic=semantic, enabled=enabled)
groupDesc = Property(Variant, lambda self: self._groupDesc, constant=True)
@ -166,15 +168,15 @@ class GroupAttribute(Attribute):
class Param(Attribute):
"""
"""
def __init__(self, name, label, description, value, uid, group, advanced, enabled):
super(Param, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, enabled=enabled)
def __init__(self, name, label, description, value, uid, group, advanced, semantic, enabled):
super(Param, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled)
class File(Attribute):
"""
"""
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, enabled=True):
super(File, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, enabled=enabled)
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, semantic='', enabled=True):
super(File, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled)
def validateValue(self, value):
if not isinstance(value, pyCompatibility.basestring):
@ -185,8 +187,8 @@ class File(Attribute):
class BoolParam(Param):
"""
"""
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, enabled=True):
super(BoolParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, enabled=enabled)
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, semantic='', enabled=True):
super(BoolParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled)
def validateValue(self, value):
try:
@ -198,9 +200,9 @@ class BoolParam(Param):
class IntParam(Param):
"""
"""
def __init__(self, name, label, description, value, range, uid, group='allParams', advanced=False, enabled=True):
def __init__(self, name, label, description, value, range, uid, group='allParams', advanced=False, semantic='', enabled=True):
self._range = range
super(IntParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, enabled=enabled)
super(IntParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled)
def validateValue(self, value):
# handle unsigned int values that are translated to int by shiboken and may overflow
@ -217,9 +219,9 @@ class IntParam(Param):
class FloatParam(Param):
"""
"""
def __init__(self, name, label, description, value, range, uid, group='allParams', advanced=False, enabled=True):
def __init__(self, name, label, description, value, range, uid, group='allParams', advanced=False, semantic='', enabled=True):
self._range = range
super(FloatParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, enabled=enabled)
super(FloatParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled)
def validateValue(self, value):
try:
@ -233,13 +235,13 @@ class FloatParam(Param):
class ChoiceParam(Param):
"""
"""
def __init__(self, name, label, description, value, values, exclusive, uid, group='allParams', joinChar=' ', advanced=False, enabled=True):
def __init__(self, name, label, description, value, values, exclusive, uid, group='allParams', joinChar=' ', advanced=False, semantic='', enabled=True):
assert values
self._values = values
self._exclusive = exclusive
self._joinChar = joinChar
self._valueType = type(self._values[0]) # cast to value type
super(ChoiceParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, enabled=enabled)
super(ChoiceParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled)
def conformValue(self, val):
""" Conform 'val' to the correct type and check for its validity """
@ -264,8 +266,8 @@ class ChoiceParam(Param):
class StringParam(Param):
"""
"""
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, enabled=True):
super(StringParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, enabled=enabled)
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, semantic='', enabled=True):
super(StringParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled)
def validateValue(self, value):
if not isinstance(value, pyCompatibility.basestring):

View file

@ -38,6 +38,13 @@ It is robust to motion-blur, depth-of-field, occlusion. Be careful to have enoug
value='',
uid=[0],
),
desc.File(
name='masksFolder',
label='Masks Folder',
description='Use masks to filter features. Filename should be the same or the image uid.',
value='',
uid=[0],
),
desc.ChoiceParam(
name='describerTypes',
label='Describer Types',

View file

@ -0,0 +1,147 @@
__version__ = "3.0"
from meshroom.core import desc
class ImageMasking(desc.CommandLineNode):
commandLine = 'aliceVision_imageMasking {allParams}'
size = desc.DynamicNodeSize('input')
parallelization = desc.Parallelization(blockSize=40)
commandLineRange = '--rangeStart {rangeStart} --rangeSize {rangeBlockSize}'
inputs = [
desc.File(
name='input',
label='Input',
description='''SfMData file.''',
value='',
uid=[0],
),
desc.ChoiceParam(
name='algorithm',
label='Algorithm',
description='',
value='HSV',
values=['HSV', 'AutoGrayscaleThreshold'],
exclusive=True,
uid=[0],
),
desc.GroupAttribute(
name="hsv",
label="HSV Parameters",
description="""Values to select:
- Green: default values
- White: Tolerance = 1, minSaturation = 0, maxSaturation = 0.1, minValue = 0.8, maxValue = 1
- Black: Tolerance = 1, minSaturation = 0, maxSaturation = 0.1, minValue = 0, maxValue = 0.2
""",
group=None,
enabled=lambda node: node.algorithm.value == 'HSV',
groupDesc=[
desc.FloatParam(
name='hsvHue',
label='Hue',
description='Hue value to isolate in [0,1] range. 0 = red, 0.33 = green, 0.66 = blue, 1 = red.',
semantic='color/hue',
value=0.33,
range=(0, 1, 0.01),
uid=[0]
),
desc.FloatParam(
name='hsvHueRange',
label='Tolerance',
description='Tolerance around the hue value to isolate.',
value=0.1,
range=(0, 1, 0.01),
uid=[0]
),
desc.FloatParam(
name='hsvMinSaturation',
label='Min Saturation',
description='Hue is meaningless if saturation is low. Do not mask pixels below this threshold.',
value=0.3,
range=(0, 1, 0.01),
uid=[0]
),
desc.FloatParam(
name='hsvMaxSaturation',
label='Max Saturation',
description='Do not mask pixels above this threshold. It might be useful to mask white/black pixels.',
value=1,
range=(0, 1, 0.01),
uid=[0]
),
desc.FloatParam(
name='hsvMinValue',
label='Min Value',
description='Hue is meaningless if value is low. Do not mask pixels below this threshold.',
value=0.3,
range=(0, 1, 0.01),
uid=[0]
),
desc.FloatParam(
name='hsvMaxValue',
label='Max Value',
description='Do not mask pixels above this threshold. It might be useful to mask white/black pixels.',
value=1,
range=(0, 1, 0.01),
uid=[0]
),
]),
desc.BoolParam(
name='invert',
label='Invert',
description='''If ticked, the selected area is ignored.
If not, only the selected area is considered.''',
value=True,
uid=[0]
),
desc.IntParam(
name='growRadius',
label='Grow Radius',
description='Grow the selected area. It might be used to fill the holes: then use shrinkRadius to restore the initial coutours.',
value=0,
range=(0, 50, 1),
uid=[0]
),
desc.IntParam(
name='shrinkRadius',
label='Shrink Radius',
description='Shrink the selected area.',
value=0,
range=(0, 50, 1),
uid=[0]
),
desc.File(
name='depthMapFolder',
label='Depth Mask Folder',
description='''Depth Mask Folder''',
value='',
uid=[0],
),
desc.StringParam(
name='depthMapExp',
label='Depth Mask Expression',
description='''Depth Mask Expression, like "{inputFolder}/{stem}-depth.{ext}".''',
value='',
uid=[0],
),
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='output',
label='Output',
description='''Output folder.''',
value=desc.Node.internalFolder,
uid=[],
),
]

View file

@ -0,0 +1,98 @@
__version__ = "1.0"
from meshroom.core import desc
class MeshMasking(desc.CommandLineNode):
commandLine = 'aliceVision_meshMasking {allParams}'
category = 'Mesh Post-Processing'
documentation = '''
Decimate triangles based on image masks.
'''
inputs = [
desc.File(
name='input',
label='Dense SfMData',
description='SfMData file.',
value='',
uid=[0],
),
desc.File(
name='inputMesh',
label='Input Mesh',
description='''Input Mesh (OBJ file format).''',
value='',
uid=[0],
),
desc.ListAttribute(
elementDesc=desc.File(
name="masksFolder",
label="Masks Folder",
description="",
value="",
uid=[0],
),
name="masksFolders",
label="Masks Folders",
description='Use masks from specific folder(s). Filename should be the same or the image uid.',
),
desc.IntParam(
name='threshold',
label='Threshold',
description='The minimum number of visibility to keep a vertex.',
value=1,
range=(1, 100, 1),
uid=[0]
),
desc.BoolParam(
name='smoothBoundary',
label='Smooth Boundary',
description='Modify the triangles at the boundary to fit the masks.',
value=False,
uid=[0]
),
desc.BoolParam(
name='invert',
label='Invert',
description='''If ticked, the selected area is ignored.
If not, only the selected area is considered.''',
value=False,
uid=[0]
),
desc.BoolParam(
name='undistortMasks',
label='Undistort Masks',
description='''Undistort the masks with the same parameters as the matching image.
Tick it if the masks are drawn on the original images.''',
value=False,
uid=[0]
),
desc.BoolParam(
name='usePointsVisibilities',
label='Use points visibilities',
description='''Use the points visibilities from the meshing to filter triangles.
Example: when they are occluded, back-face, etc.''',
value=False,
uid=[0]
),
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='outputMesh',
label='Output Mesh',
description='''Output mesh (OBJ file format).''',
value=desc.Node.internalFolder + 'mesh.obj',
uid=[],
),
]

View file

@ -34,6 +34,18 @@ This node export undistorted images so the depth map and texturing can be comput
label="Images Folders",
description='Use images from specific folder(s). Filename should be the same or the image uid.',
),
desc.ListAttribute(
elementDesc=desc.File(
name="masksFolder",
label="Masks Folder",
description="",
value="",
uid=[0],
),
name="masksFolders",
label="Masks Folders",
description='Use masks from specific folder(s). Filename should be the same or the image uid.',
),
desc.ChoiceParam(
name='outputFileType',
label='Output File Type',

View file

@ -0,0 +1,67 @@
__version__ = "3.0"
from meshroom.core import desc
class SfMDistances(desc.CommandLineNode):
commandLine = 'aliceVision_utils_sfmDistances {allParams}'
size = desc.DynamicNodeSize('input')
inputs = [
desc.File(
name='input',
label='Input',
description='''SfMData file.''',
value='',
uid=[0],
),
desc.ChoiceParam(
name='objectType',
label='Type',
description='',
value='landmarks',
values=['landmarks', 'cameras'],
exclusive=True,
uid=[0],
),
desc.ChoiceParam(
name='landmarksDescriberTypes',
label='Describer Types',
description='Describer types used to describe an image (only used when using "landmarks").',
value=['cctag3'],
values=['sift', 'sift_float', 'sift_upright', 'akaze', 'akaze_liop', 'akaze_mldb', 'cctag3', 'cctag4', 'sift_ocv', 'akaze_ocv'],
exclusive=False,
uid=[0],
joinChar=',',
),
desc.StringParam(
name='A',
label='A IDs',
description='It will display the distances between A and B elements.\n'
'This value should be an ID or a list of IDs of landmarks IDs or cameras (UID or filename without extension).\n'
'It will list all elements if empty.',
value='',
uid=[0],
),
desc.StringParam(
name='B',
label='B IDs',
description='It will display the distances between A and B elements.\n'
'This value should be an ID or a list of IDs of landmarks IDs or cameras (UID or filename without extension).\n'
'It will list all elements if empty.',
value='',
uid=[0],
),
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 = [
]

View file

@ -53,7 +53,7 @@ The transformation can be based on:
label='Transformation',
description="Required only for 'transformation' and 'from_single_camera' methods:\n"
" * transformation: Align [X,Y,Z] to +Y-axis, rotate around Y by R deg, scale by S; syntax: X,Y,Z;R;S\n"
" * from_single_camera: Camera UID or image filename",
" * from_single_camera: Camera UID or simplified regular expression to match image filepath (like '*camera2*.jpg')",
value='',
uid=[0],
enabled=lambda node: node.method.value == "transformation" or node.method.value == "from_single_camera",

View file

@ -270,15 +270,36 @@ It iterates like that, adding cameras and triangulating new 2D features into 3D
uid=[0],
advanced=True,
),
desc.IntParam(
name='rigMinNbCamerasForCalibration',
label='Min Nb Cameras For Rig Calibration',
description='Minimal number of cameras to start the calibration of the rig',
value=20,
range=(1, 50, 1),
uid=[0],
advanced=True,
),
desc.BoolParam(
name='lockAllIntrinsics',
label='Force Lock of All Intrinsic Camera Parameters.',
label='Force Lock of All Intrinsic Camera Parameters',
description='Force to keep constant all the intrinsics parameters of the cameras (focal length, \n'
'principal point, distortion if any) during the reconstruction.\n'
'This may be helpful if the input cameras are already fully calibrated.',
value=False,
uid=[0],
),
desc.IntParam(
name='minNbCamerasToRefinePrincipalPoint',
label='Min Nb Cameras To Refine Principal Point',
description='Minimal number of cameras to refine the principal point of the cameras (one of the intrinsic parameters of the camera). '
'If we do not have enough cameras, the principal point in consider is considered in the center of the image. '
'If minNbCamerasToRefinePrincipalPoint<=0, the principal point is never refined. '
'If minNbCamerasToRefinePrincipalPoint==1, the principal point is always refined.',
value=3,
range=(0, 20, 1),
uid=[0],
advanced=True,
),
desc.BoolParam(
name='filterTrackForks',
label='Filter Track Forks',

View file

@ -145,7 +145,10 @@ RowLayout {
{
case "ChoiceParam": return attribute.desc.exclusive ? comboBox_component : multiChoice_component
case "IntParam": return slider_component
case "FloatParam": return slider_component
case "FloatParam":
if(attribute.desc.semantic === 'color/hue')
return color_hue_component
return slider_component
case "BoolParam": return checkbox_component
case "ListAttribute": return listAttribute_component
case "GroupAttribute": return groupAttribute_component
@ -379,5 +382,66 @@ RowLayout {
}
}
}
Component {
id: color_hue_component
RowLayout {
TextField {
implicitWidth: 100
enabled: root.editable
// cast value to string to avoid intrusive scientific notations on numbers
property string displayValue: String(slider.pressed ? slider.formattedValue : attribute.value)
text: displayValue
selectByMouse: true
validator: DoubleValidator {
locale: 'C' // use '.' decimal separator disregarding the system locale
}
onEditingFinished: setTextFieldAttribute(text)
onAccepted: setTextFieldAttribute(text)
Component.onDestruction: {
if(activeFocus)
setTextFieldAttribute(text)
}
}
Rectangle {
height: slider.height
width: height
color: Qt.hsla(slider.pressed ? slider.formattedValue : attribute.value, 1, 0.5, 1)
}
Slider {
Layout.fillWidth: true
id: slider
readonly property int stepDecimalCount: 2
readonly property real formattedValue: value.toFixed(stepDecimalCount)
enabled: root.editable
value: attribute.value
from: 0
to: 1
stepSize: 0.01
snapMode: Slider.SnapAlways
onPressedChanged: {
if(!pressed)
_reconstruction.setAttribute(attribute, formattedValue)
}
background: ShaderEffect {
width: control.availableWidth
height: control.availableHeight
blending: false
fragmentShader: "
varying mediump vec2 qt_TexCoord0;
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
gl_FragColor = vec4(hsv2rgb(vec3(qt_TexCoord0.x, 1.0, 1.0)), 1.0);
}"
}
}
}
}
}
}

View file

@ -49,6 +49,7 @@ import Utils 1.0
case ".abc": if(Viewer3DSettings.supportAlembic) component = abcLoaderEntityComponent; break;
case ".exr": if(Viewer3DSettings.supportDepthMap) component = exrLoaderComponent; break;
case ".obj":
case ".stl":
default: component = sceneLoaderEntityComponent; break;
}

View file

@ -13,7 +13,7 @@ Item {
// supported 3D files extensions
readonly property var supportedExtensions: {
var exts = ['.obj'];
var exts = ['.obj', '.stl'];
if(supportAlembic)
exts.push('.abc');
if(supportDepthMap)