Skip to content

Commit 605e0ef

Browse files
Merge pull request #2023 from alicevision/dev/exportScale
Enable decimation of output images in the exportImages/IntrinsicsTransforming pipeline
2 parents 2b0aee8 + da289dc commit 605e0ef

File tree

9 files changed

+367
-35
lines changed

9 files changed

+367
-35
lines changed

meshroom/aliceVision/ExportImages.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
__version__ = "1.1"
22

33
from meshroom.core import desc
4-
from meshroom.core.utils import VERBOSE_LEVEL
4+
from meshroom.core.utils import COLORSPACES, EXR_STORAGE_DATA_TYPE, VERBOSE_LEVEL
55

66

77
class ExportImages(desc.AVCommandLineNode):
@@ -29,6 +29,18 @@ class ExportImages(desc.AVCommandLineNode):
2929
description="This SfMData file contains the required intrinsics for the output images.",
3030
value="",
3131
),
32+
desc.ListAttribute(
33+
elementDesc=desc.File(
34+
name="masksFolder",
35+
label="Masks Folder",
36+
description="",
37+
value="",
38+
),
39+
name="masksFolders",
40+
label="Masks Folders",
41+
description="Use masks from specific folder(s). Filename should be the same or the image UID.",
42+
exposed=True
43+
),
3244
desc.ChoiceParam(
3345
name="outputFileType",
3446
label="Output File Type",
@@ -61,24 +73,25 @@ class ExportImages(desc.AVCommandLineNode):
6173
value="viewid",
6274
values=["viewid", "frameid", "keep"],
6375
),
64-
desc.ListAttribute(
65-
elementDesc=desc.File(
66-
name="masksFolder",
67-
label="Masks Folder",
68-
description="",
69-
value="",
70-
),
71-
name="masksFolders",
72-
label="Masks Folders",
73-
description="Use masks from specific folder(s). Filename should be the same or the image UID.",
74-
),
7576
desc.ChoiceParam(
7677
name="maskExtension",
7778
label="Mask Extension",
7879
description="File extension for the masks to use.",
7980
value="png",
8081
values=["exr", "jpg", "png"],
8182
),
83+
desc.ChoiceParam(
84+
name="storageDataType",
85+
label="Storage Data Type",
86+
description="Storage image data type:\n"
87+
" - float: Use full floating point (32 bits per channel).\n"
88+
" - half: Use half float (16 bits per channel).\n"
89+
" - halfFinite: Use half float, but clamp values to avoid non-finite values.\n"
90+
" - auto: Use half float if all values can fit, else use full float.",
91+
values=EXR_STORAGE_DATA_TYPE,
92+
value="halfFinite",
93+
enabled=lambda node: node.outputFileType.value == "exr"
94+
),
8295
desc.ChoiceParam(
8396
name="verboseLevel",
8497
label="Verbose Level",

meshroom/aliceVision/IntrinsicsTransforming.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ class IntrinsicsTransforming(desc.AVCommandLineNode):
4040
value=90.0,
4141
range=(1.0, 179.0, 0.1),
4242
),
43+
desc.FloatParam(
44+
name="scaleFactor",
45+
label="Scale Factor",
46+
description="Rescale the size of the images in the sfmData description",
47+
value=1.0,
48+
range=(0.0, 1.0, 0.1),
49+
enabled=lambda node: node.type.value == "pinhole"
50+
),
4351
desc.BoolParam(
4452
name="correctPrincipalPoint",
4553
label="Correct Principal Point",

src/aliceVision/camera/IntrinsicScaleOffsetDisto.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ std::shared_ptr<IntrinsicScaleOffsetDisto> IntrinsicScaleOffsetDisto::cast(std::
1515
}
1616

1717
bool IntrinsicScaleOffsetDisto::operator==(const IntrinsicBase& otherBase) const
18+
{
19+
return equalTo(otherBase, false);
20+
}
21+
22+
bool IntrinsicScaleOffsetDisto::equalTo(const IntrinsicBase& otherBase, bool ignoreDistortion) const
1823
{
1924
if (!IntrinsicScaleOffset::operator==(otherBase))
2025
{
@@ -26,6 +31,11 @@ bool IntrinsicScaleOffsetDisto::operator==(const IntrinsicBase& otherBase) const
2631
return false;
2732
}
2833

34+
if (ignoreDistortion)
35+
{
36+
return true;
37+
}
38+
2939
const IntrinsicScaleOffsetDisto& other = static_cast<const IntrinsicScaleOffsetDisto&>(otherBase);
3040

3141
if (_distortionInitializationMode != other._distortionInitializationMode)

src/aliceVision/camera/IntrinsicScaleOffsetDisto.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,21 @@ class IntrinsicScaleOffsetDisto : public IntrinsicScaleOffset
6363

6464
void assign(const IntrinsicBase& other) override { *this = dynamic_cast<const IntrinsicScaleOffsetDisto&>(other); }
6565

66+
/**
67+
* @brief compare to another intrinsic object
68+
* @param otherBase an intrinsic object to compare to.
69+
* @return true if both objects are considered equal
70+
*/
6671
bool operator==(const IntrinsicBase& otherBase) const override;
6772

73+
/**
74+
* @brief compare to another intrinsic object
75+
* @param otherBase an intrinsic object to compare to.
76+
* @param ignoreDistortion ignore difference in distortion.
77+
* @return true if both objects are considered equal
78+
*/
79+
bool equalTo(const IntrinsicBase& otherBase, bool ignoreDistortion) const;
80+
6881
void setDistortionObject(std::shared_ptr<Distortion> object) { _pDistortion = object; }
6982

7083
bool hasDistortion() const override { return _pDistortion != nullptr || _pUndistortion != nullptr; }

src/aliceVision/image/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ set(image_files_headers
1919
pixelTypes.hpp
2020
Rgb.hpp
2121
Sampler.hpp
22+
remap.hpp
2223
cache.hpp
2324
)
2425

@@ -31,6 +32,7 @@ set(image_files_sources
3132
io.cpp
3233
imageAlgo.cpp
3334
jetColorMap.cpp
35+
remap.cpp
3436
cache.cpp
3537
)
3638

src/aliceVision/image/remap.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// This file is part of the AliceVision project.
2+
// Copyright (c) 2025 AliceVision contributors.
3+
// This Source Code Form is subject to the terms of the Mozilla Public License,
4+
// v. 2.0. If a copy of the MPL was not distributed with this file,
5+
// You can obtain one at https://mozilla.org/MPL/2.0/.
6+
7+
#include <aliceVision/image/remap.hpp>
8+
9+
namespace aliceVision {
10+
namespace image {
11+
12+
13+
14+
} // namespace image
15+
} // namespace aliceVision

src/aliceVision/image/remap.hpp

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// This file is part of the AliceVision project.
2+
// Copyright (c) 2025 AliceVision contributors.
3+
// This Source Code Form is subject to the terms of the Mozilla Public License,
4+
// v. 2.0. If a copy of the MPL was not distributed with this file,
5+
// You can obtain one at https://mozilla.org/MPL/2.0/.
6+
7+
#pragma once
8+
9+
#include <aliceVision/image/Image.hpp>
10+
#include <aliceVision/image/Sampler.hpp>
11+
#include <aliceVision/system/Logger.hpp>
12+
#include <type_traits>
13+
14+
namespace aliceVision {
15+
namespace image {
16+
17+
/**
18+
* @brief Fill pixels of an image such that dest(i, j) = input(map(i, j).y, map(i, j).x)
19+
* @param[in] src source image (of size H1,W1) to remap
20+
* @param[in] map image (of size H2, W2) containing coordinates in the source image
21+
* @param[in] fillColor the default pixel value if not possible to remap
22+
* @param[out] output image to store the result (will be resized to size H2, W2)
23+
*/
24+
template <typename P>
25+
void remap(const Image<P> & source, const Image<Vec2> & map, const P & fillColor, Image<P> & output)
26+
{
27+
if (output.width() != map.width() || output.height() != map.height())
28+
{
29+
output.resize(map.width(), map.height());
30+
}
31+
32+
const image::Sampler2d<image::SamplerLinear> sampler;
33+
34+
for (int i = 0; i < map.height(); i++)
35+
{
36+
for (int j = 0; j < map.width(); j++)
37+
{
38+
const Vec2 & coord = map(i, j);
39+
40+
const double & x = coord.x();
41+
const double & y = coord.y();
42+
43+
// pick pixel if it is in the image domain
44+
if (source.contains(y, x))
45+
{
46+
output(i, j) = sampler(source, y, x);
47+
}
48+
else
49+
{
50+
output(i, j) = fillColor;
51+
}
52+
}
53+
}
54+
}
55+
56+
/**
57+
* @brief Fill pixels of an image such that dest(i, j) = input(map(i, j).y, map(i, j).x)
58+
* @param[in] src source image (of size H1,W1) to remap
59+
* @param[in] map image (of size H2, W2) containing coordinates in the source image
60+
* @param[in] fillColor the default pixel value if not possible to remap
61+
* @param[out] output image to store the result (will be resized to size H2, W2)
62+
*/
63+
template <typename P>
64+
void remapInter(const Image<P> & source, const Image<Vec2> & map, const P& fillColor, Image<P> & output)
65+
{
66+
if (output.width() != map.width() || output.height() != map.height())
67+
{
68+
output.resize(map.width(), map.height());
69+
}
70+
71+
int srcWidth = source.width();
72+
int srcHeight = source.height();
73+
74+
const image::Sampler2d<image::SamplerLinear> sampler;
75+
76+
for (int i = 0; i < map.height(); i++)
77+
{
78+
for (int j = 0; j < map.width(); j++)
79+
{
80+
const Vec2 & coord = map(i, j);
81+
82+
const double & x = coord.x();
83+
const double & y = coord.y();
84+
85+
double scaleX = 1.0;
86+
if (j < map.width() - 1 && j > 0)
87+
{
88+
scaleX = std::abs(map(i, j + 1).x() - map(i, j - 1).x()) * 0.5;
89+
}
90+
else if (j < map.width() - 1)
91+
{
92+
scaleX = std::abs(map(i, j + 1).x() - x);
93+
}
94+
else if (j > 0)
95+
{
96+
scaleX = std::abs(x - map(i, j - 1).x());
97+
}
98+
99+
double scaleY = 1.0;
100+
if (i < map.height() - 1 && i > 0)
101+
{
102+
scaleY = std::abs(map(i + 1, j).y() - map(i - 1, j).y()) * 0.5;
103+
}
104+
else if (i < map.height() - 1)
105+
{
106+
scaleY = std::abs(map(i + 1, j).y() - y);
107+
}
108+
else if (i > 0)
109+
{
110+
scaleY = std::abs(y - map(i - 1, j).y());
111+
}
112+
113+
const double max_scale = std::max(scaleX, scaleY);
114+
if (scaleX > 1.0 && scaleY > 1.0)
115+
{
116+
//We are doing downsampling here.
117+
//Use area interpolation to avoid aliasing
118+
119+
double halfWidth = scaleX * 0.5;
120+
double halfHeight = scaleY * 0.5;
121+
122+
double x1 = coord.x() - halfWidth;
123+
double x2 = coord.x() + halfWidth;
124+
double y1 = coord.y() - halfHeight;
125+
double y2 = coord.y() + halfHeight;
126+
127+
int ix1 = static_cast<int>(x1);
128+
int ix2 = static_cast<int>(std::ceil(x2));
129+
int iy1 = static_cast<int>(y1);
130+
int iy2 = static_cast<int>(std::ceil(y2));
131+
132+
//Not using sampler as the neigborhood size is dynamic
133+
134+
P sum;
135+
sum.fill(0.0);
136+
137+
double wsum = 0.0;
138+
139+
for (int by = iy1; by < iy2; by++)
140+
{
141+
if (by < 0 || by >= srcHeight)
142+
{
143+
continue;
144+
}
145+
146+
//Compute vertical occupancy
147+
// (by + 1) - y1
148+
// or y2 - by
149+
double yocc = std::max(0.0, std::min(double(by + 1), y2) - std::max(double(by), y1));
150+
151+
for (int bx = ix1; bx < ix2; bx++)
152+
{
153+
if (bx < 0 || bx >= srcWidth)
154+
{
155+
continue;
156+
}
157+
158+
//Compute horizontal occupancy
159+
// (bx + 1) - x1
160+
// or x2 - bx
161+
double xocc = std::max(0.0, std::min(double(bx + 1), x2) - std::max(double(bx), x1));
162+
163+
const P & pix = source(by, bx);
164+
double weight = yocc * xocc;
165+
166+
if constexpr (std::is_same<P, RGBAfColor>())
167+
{
168+
weight *= pix.a();
169+
}
170+
171+
sum += pix * weight;
172+
wsum += weight;
173+
}
174+
}
175+
176+
//Output pixel is the weighted mean of the
177+
//Source pixels in the area covered by the output pixel.
178+
if (wsum > 0.0)
179+
{
180+
sum /= wsum;
181+
}
182+
183+
output(i, j) = sum;
184+
}
185+
else
186+
{
187+
// Not downsampling, do bilinear interpolation
188+
189+
// pick pixel if it is in the image domain
190+
if (source.contains(y, x))
191+
{
192+
output(i, j) = sampler(source, y, x);
193+
}
194+
else
195+
{
196+
output(i, j) = fillColor;
197+
}
198+
}
199+
}
200+
}
201+
}
202+
203+
} // namespace image
204+
} // namespace aliceVision

0 commit comments

Comments
 (0)