Skip to content

Commit b67755f

Browse files
Merge pull request #2003 from alicevision/dev/maskEroding
New node to erode masks' valid regions
2 parents ba76197 + ee306a9 commit b67755f

File tree

3 files changed

+251
-0
lines changed

3 files changed

+251
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
__version__ = "1.0"
2+
3+
from meshroom.core import desc
4+
from meshroom.core.utils import DESCRIBER_TYPES, VERBOSE_LEVEL
5+
6+
import os.path
7+
8+
9+
class MaskEroding(desc.AVCommandLineNode):
10+
commandLine = "aliceVision_maskEroding {allParams}"
11+
12+
size = desc.DynamicNodeSize("input")
13+
parallelization = desc.Parallelization(blockSize=50)
14+
commandLineRange = "--rangeIteration {rangeIteration} --rangeBlocksCount {rangeBlocksCount}"
15+
16+
category = "Utils"
17+
documentation = """ Assumes the inputs are binary masks.
18+
Erode the valid part of the mask such that a new pixel is valid if and only if the input region is fully valid. """
19+
20+
inputs = [
21+
desc.File(
22+
name="input",
23+
label="Input Directory",
24+
description="A directory with a set of mask.",
25+
value="",
26+
),
27+
desc.IntParam(
28+
name="radius",
29+
label="Radius of erosion",
30+
description="Radius of the erosion filter",
31+
value=5,
32+
range=None,
33+
),
34+
desc.ChoiceParam(
35+
name="verboseLevel",
36+
label="Verbose Level",
37+
description="Verbosity level (fatal, error, warning, info, debug, trace).",
38+
values=VERBOSE_LEVEL,
39+
value="info",
40+
)
41+
]
42+
43+
outputs = [
44+
desc.File(
45+
name="output",
46+
label="Output",
47+
description="Path to the output directory.",
48+
value="{nodeCacheFolder}",
49+
),
50+
desc.File(
51+
name="masks",
52+
label="Masks",
53+
description="Processed masks.",
54+
semantic="imageList",
55+
value= "{nodeCacheFolder}/*.exr",
56+
group="",
57+
),
58+
]

src/software/utils/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ if (ALICEVISION_BUILD_SFM)
6262
${Boost_LIBRARIES}
6363
)
6464

65+
alicevision_add_software(aliceVision_maskEroding
66+
SOURCE main_maskEroding.cpp
67+
FOLDER ${FOLDER_SOFTWARE_UTILS}
68+
LINKS aliceVision_system
69+
aliceVision_cmdline
70+
aliceVision_image
71+
${Boost_LIBRARIES}
72+
)
73+
6574
# Track builder
6675
alicevision_add_software(aliceVision_tracksSimulating
6776
SOURCE main_tracksSimulating.cpp
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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/cmdline/cmdline.hpp>
8+
#include <aliceVision/system/main.hpp>
9+
#include <aliceVision/image/io.hpp>
10+
#include <aliceVision/system/Parallelization.hpp>
11+
12+
#include <boost/program_options.hpp>
13+
14+
#include <string>
15+
#include <sstream>
16+
#include <random>
17+
#include <filesystem>
18+
19+
// These constants define the current software version.
20+
// They must be updated when the command line is changed.
21+
#define ALICEVISION_SOFTWARE_VERSION_MAJOR 1
22+
#define ALICEVISION_SOFTWARE_VERSION_MINOR 0
23+
24+
using namespace aliceVision;
25+
26+
namespace po = boost::program_options;
27+
28+
/**
29+
@brief Each pixel (x,y) in integralCount is the count of non-zero pixel
30+
in the input mask's sub rectangle (0->x; 0->y).
31+
@param integralCount the output integral image
32+
@param mask the input mask
33+
*/
34+
void computeIntegralMask(image::Image<unsigned long> & integralCount, const image::Image<unsigned char> & mask)
35+
{
36+
integralCount.resize(mask.width(), mask.height());
37+
38+
//Bootstrap first row
39+
int countFirstRow = 0;
40+
for (int j = 0; j < mask.width(); j++)
41+
{
42+
if (mask(0, j))
43+
{
44+
countFirstRow++;
45+
integralCount(0, j) = countFirstRow;
46+
}
47+
}
48+
49+
//For all next rows
50+
for (int i = 1; i < mask.height(); i++)
51+
{
52+
int countRow = 0;
53+
for (int j = 0; j < mask.width(); j++)
54+
{
55+
if (mask(i, j))
56+
{
57+
countRow++;
58+
}
59+
60+
integralCount(i, j) = integralCount(i - 1, j) + countRow;
61+
}
62+
}
63+
}
64+
65+
int aliceVision_main(int argc, char** argv)
66+
{
67+
// command-line parameters
68+
std::string directoryName;
69+
std::string outDirectory;
70+
int rangeIteration = 0;
71+
int rangeBlocksCount = 1;
72+
int radius = 5;
73+
74+
// clang-format off
75+
po::options_description requiredParams("Required parameters");
76+
requiredParams.add_options()
77+
("input,i", po::value<std::string>(&directoryName)->required(),
78+
"Path to directories to process.")
79+
("output,o", po::value<std::string>(&outDirectory)->required(),
80+
"Output directory.");
81+
82+
po::options_description optionalParams("Optional parameters");
83+
optionalParams.add_options()
84+
("radius", po::value<int>(&radius)->default_value(radius), "")
85+
("rangeIteration", po::value<int>(&rangeIteration)->default_value(rangeIteration), "Chunk id.")
86+
("rangeBlocksCount", po::value<int>(&rangeBlocksCount)->default_value(rangeBlocksCount), "Chunk count.");
87+
// clang-format on
88+
89+
CmdLine cmdline("AliceVision maskEroding");
90+
cmdline.add(requiredParams);
91+
cmdline.add(optionalParams);
92+
if (!cmdline.execute(argc, argv))
93+
{
94+
return EXIT_FAILURE;
95+
}
96+
97+
// set maxThreads
98+
HardwareContext hwc = cmdline.getHardwareContext();
99+
omp_set_num_threads(hwc.getMaxThreads());
100+
101+
std::vector<std::filesystem::path> paths;
102+
for (auto &p : std::filesystem::recursive_directory_iterator(directoryName))
103+
{
104+
const std::filesystem::path path = p.path();
105+
if (path.extension() != ".exr")
106+
{
107+
continue;
108+
}
109+
110+
paths.push_back(path);
111+
}
112+
113+
int rangeStart, rangeEnd;
114+
if (!rangeComputation(rangeStart, rangeEnd, rangeIteration, rangeBlocksCount, paths.size()))
115+
{
116+
ALICEVISION_LOG_ERROR("Problem computing chunks.");
117+
return EXIT_FAILURE;
118+
}
119+
120+
#pragma omp parallel for schedule(dynamic)
121+
for (int pid = rangeStart; pid < rangeEnd; pid++)
122+
{
123+
const auto & path = paths[pid];
124+
125+
ALICEVISION_LOG_INFO("Processing " << path.string());
126+
127+
std::filesystem::path outputDirectoryPath(outDirectory);
128+
std::filesystem::path outputPath = outputDirectoryPath / path.filename();
129+
130+
131+
image::Image<unsigned char> img;
132+
aliceVision::image::readImage(path.string(), img, image::EImageColorSpace::NO_CONVERSION);
133+
134+
image::Image<unsigned long> integral;
135+
computeIntegralMask(integral, img);
136+
137+
image::Image<unsigned char> out(img.width(), img.height());
138+
for (int i = 0; i < img.height(); i++)
139+
{
140+
int mini = std::max(i - radius, 0);
141+
int maxi = std::min(i + radius, img.height() - 1);
142+
int si = maxi - mini + 1;
143+
144+
for (int j = 0; j < img.width(); j++)
145+
{
146+
int minj = std::max(j - radius, 0);
147+
int maxj = std::min(j + radius, img.width() - 1);
148+
int sj = maxj - minj + 1;
149+
150+
//A B
151+
//C D
152+
//"CD" = D - V)
153+
long A = 0;
154+
long B = 0;
155+
long C = 0;
156+
long D = integral(maxi, maxj);
157+
158+
if (minj > 0)
159+
{
160+
C = integral(maxi, minj - 1);
161+
}
162+
if (mini > 0)
163+
{
164+
B = integral(mini - 1, maxj);
165+
166+
if (minj > 0)
167+
{
168+
A = integral(mini - 1, minj - 1);
169+
}
170+
}
171+
172+
//Are all the pixels valid ?
173+
long sub = D - C - B + A;
174+
out(i, j) = (sub == (si * sj))?255:0;
175+
}
176+
}
177+
178+
aliceVision::image::ImageWriteOptions wopt;
179+
aliceVision::image::writeImage(outputPath.string(), out, wopt);
180+
}
181+
182+
return EXIT_SUCCESS;
183+
}
184+

0 commit comments

Comments
 (0)