PyPatchMatch 2.1.0__tar.gz → 2.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/CMakeLists.txt +2 -1
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/PKG-INFO +2 -2
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/_version.py +3 -3
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/nnf.cpp +224 -119
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/nnf.h +43 -17
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/CLAUDE.md +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/LICENSE +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/README.md +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/SECURITY.md +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/examples/images/chelsea.png +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/examples/images/forest_pruned.bmp +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/__init__.py +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/_lib.py +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/inpaint.cpp +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/inpaint.h +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/masked_image.cpp +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/masked_image.h +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/pyinterface.cpp +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/pyinterface.h +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/patch_match.py +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/py.typed +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/pyproject.toml +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/scripts/build_opencv.py +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/scripts/evaluate_inpainting.py +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/scripts/render_readme_example.py +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/tests/conftest.py +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/tests/test_e2e.py +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/tests/test_patch_match.py +0 -0
- {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/tests/test_properties.py +0 -0
|
@@ -57,8 +57,9 @@ if(PATCHMATCH_SANITIZE)
|
|
|
57
57
|
endif()
|
|
58
58
|
# GCC's -fsanitize=undefined leaves out float-cast-overflow
|
|
59
59
|
set(PATCHMATCH_SANITIZERS -fsanitize=address,undefined,float-cast-overflow)
|
|
60
|
+
# -UNDEBUG keeps the asserts, which check the incremental distances of the propagation
|
|
60
61
|
target_compile_options(patchmatch PRIVATE ${PATCHMATCH_SANITIZERS}
|
|
61
|
-
-fno-sanitize-recover=all -fno-omit-frame-pointer -g)
|
|
62
|
+
-fno-sanitize-recover=all -fno-omit-frame-pointer -g -UNDEBUG)
|
|
62
63
|
target_link_options(patchmatch PRIVATE ${PATCHMATCH_SANITIZERS})
|
|
63
64
|
endif()
|
|
64
65
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPatchMatch
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: This library implements the PatchMatch based inpainting algorithm.
|
|
5
5
|
Keywords: inpainting,patchmatch,image-processing,opencv
|
|
6
6
|
Author-Email: The InvokeAI Project <lincoln.stein@gmail.com>, Jiayuan Mao <maojiayuan@gmail.com>, Kyle Schouviller <kyle0654@hotmail.com>, Matthias Wild <mauwii@outlook.de>, Younesse ANDAM <younesse.andam@gmail.com>
|
|
@@ -43,7 +43,7 @@ and Python interfaces. This implementation is heavily based on the implementatio
|
|
|
43
43
|
Younesse ANDAM: [younesse-cv/PatchMatch](https://github.com/younesse-cv/PatchMatch),
|
|
44
44
|
with some bug fixes, and updates.
|
|
45
45
|
|
|
46
|
-

|
|
47
47
|
|
|
48
48
|
From left to right: the photo, the input of
|
|
49
49
|
[examples/py_example.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example.py)
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.1.
|
|
22
|
-
__version_tuple__ = version_tuple = (2, 1,
|
|
21
|
+
__version__ = version = '2.1.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 1, 1)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'gef1a63c74'
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#include "nnf.h"
|
|
2
2
|
|
|
3
3
|
#include <algorithm>
|
|
4
|
+
#include <cassert>
|
|
4
5
|
#include <cmath>
|
|
5
6
|
#include <cstdint>
|
|
6
7
|
#include <random>
|
|
8
|
+
#include <typeinfo>
|
|
7
9
|
|
|
8
10
|
#include "masked_image.h"
|
|
9
11
|
|
|
@@ -33,6 +35,154 @@ namespace
|
|
|
33
35
|
{
|
|
34
36
|
return static_cast<int>(random_engine()() % static_cast<unsigned int>(n));
|
|
35
37
|
}
|
|
38
|
+
|
|
39
|
+
inline int pow2(int i)
|
|
40
|
+
{
|
|
41
|
+
return i * i;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
struct Position
|
|
45
|
+
{
|
|
46
|
+
int y;
|
|
47
|
+
int x;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// A source and a target image with their sizes, which are read once per patch.
|
|
51
|
+
struct ImagePair
|
|
52
|
+
{
|
|
53
|
+
ImagePair(const MaskedImage &source_image, const MaskedImage &target_image)
|
|
54
|
+
: source(&source_image), target(&target_image), source_size(source_image.size()),
|
|
55
|
+
target_size(target_image.size())
|
|
56
|
+
{
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
bool rows_inside(int ys, int yt) const
|
|
60
|
+
{
|
|
61
|
+
return ys >= 0 && ys < source_size.height && yt >= 0 && yt < target_size.height;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const MaskedImage *source;
|
|
65
|
+
const MaskedImage *target;
|
|
66
|
+
cv::Size source_size;
|
|
67
|
+
cv::Size target_size;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// One row of a source and a target image, both inside their images. The gradients must be computed.
|
|
71
|
+
class RowPair
|
|
72
|
+
{
|
|
73
|
+
public:
|
|
74
|
+
RowPair(const ImagePair &images, int ys, int yt)
|
|
75
|
+
: m_source_width(images.source_size.width), m_target_width(images.target_size.width),
|
|
76
|
+
m_si(images.source->image().ptr<unsigned char>(ys, 0)),
|
|
77
|
+
m_ti(images.target->image().ptr<unsigned char>(yt, 0)),
|
|
78
|
+
m_sm(images.source->mask().ptr<unsigned char>(ys, 0)),
|
|
79
|
+
m_tm(images.target->mask().ptr<unsigned char>(yt, 0)),
|
|
80
|
+
m_sgm(
|
|
81
|
+
images.source->has_global_mask() ? images.source->global_mask().ptr<unsigned char>(ys, 0) : nullptr),
|
|
82
|
+
m_tgm(
|
|
83
|
+
images.target->has_global_mask() ? images.target->global_mask().ptr<unsigned char>(yt, 0) : nullptr),
|
|
84
|
+
m_sgy(images.source->grady().ptr<unsigned char>(ys, 0)),
|
|
85
|
+
m_tgy(images.target->grady().ptr<unsigned char>(yt, 0)),
|
|
86
|
+
m_sgx(images.source->gradx().ptr<unsigned char>(ys, 0)),
|
|
87
|
+
m_tgx(images.target->gradx().ptr<unsigned char>(yt, 0))
|
|
88
|
+
{
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// The SSD over the colors and gradients, kSSDScale where a pixel is masked or outside its image.
|
|
92
|
+
int cost(int xs, int xt) const
|
|
93
|
+
{
|
|
94
|
+
// The bounds first: the masks are read only inside the image.
|
|
95
|
+
if (xs < 0 || xs >= m_source_width || xt < 0 || xt >= m_target_width || m_sm[xs] || m_tm[xt] ||
|
|
96
|
+
(m_sgm && m_sgm[xs]) || (m_tgm && m_tgm[xt]))
|
|
97
|
+
return PatchSSDDistanceMetric::kSSDScale;
|
|
98
|
+
|
|
99
|
+
int ssd = 0;
|
|
100
|
+
for (int c = 0; c < 3; ++c)
|
|
101
|
+
{
|
|
102
|
+
ssd += pow2(m_si[xs * 3 + c] - m_ti[xt * 3 + c]);
|
|
103
|
+
ssd += pow2(m_sgx[xs * 3 + c] - m_tgx[xt * 3 + c]);
|
|
104
|
+
ssd += pow2(m_sgy[xs * 3 + c] - m_tgy[xt * 3 + c]);
|
|
105
|
+
}
|
|
106
|
+
return ssd;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private:
|
|
110
|
+
int m_source_width;
|
|
111
|
+
int m_target_width;
|
|
112
|
+
const unsigned char *m_si;
|
|
113
|
+
const unsigned char *m_ti;
|
|
114
|
+
const unsigned char *m_sm;
|
|
115
|
+
const unsigned char *m_tm;
|
|
116
|
+
const unsigned char *m_sgm;
|
|
117
|
+
const unsigned char *m_tgm;
|
|
118
|
+
const unsigned char *m_sgy;
|
|
119
|
+
const unsigned char *m_tgy;
|
|
120
|
+
const unsigned char *m_sgx;
|
|
121
|
+
const unsigned char *m_tgx;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// The costs of the row of the patches around s and t.
|
|
125
|
+
std::int64_t row_sum(const ImagePair &images, Position s, Position t, int patch_size)
|
|
126
|
+
{
|
|
127
|
+
if (!images.rows_inside(s.y, t.y))
|
|
128
|
+
return std::int64_t{PatchSSDDistanceMetric::kSSDScale} * (2 * patch_size + 1);
|
|
129
|
+
const RowPair row(images, s.y, t.y);
|
|
130
|
+
std::int64_t sum = 0;
|
|
131
|
+
for (int dx = -patch_size; dx <= patch_size; ++dx)
|
|
132
|
+
sum += row.cost(s.x + dx, t.x + dx);
|
|
133
|
+
return sum;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// The costs of the column of the patches around s and t.
|
|
137
|
+
std::int64_t column_sum(const ImagePair &images, Position s, Position t, int patch_size)
|
|
138
|
+
{
|
|
139
|
+
std::int64_t sum = 0;
|
|
140
|
+
for (int dy = -patch_size; dy <= patch_size; ++dy)
|
|
141
|
+
{
|
|
142
|
+
if (images.rows_inside(s.y + dy, t.y + dy))
|
|
143
|
+
sum += RowPair(images, s.y + dy, t.y + dy).cost(s.x, t.x);
|
|
144
|
+
else
|
|
145
|
+
sum += PatchSSDDistanceMetric::kSSDScale;
|
|
146
|
+
}
|
|
147
|
+
return sum;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// The costs of the patches around s and t, or -1 once their distance is certain to exceed bound.
|
|
151
|
+
std::int64_t patch_sum(
|
|
152
|
+
const MaskedImage &source, Position s, const MaskedImage &target, Position t, int patch_size, int bound)
|
|
153
|
+
{
|
|
154
|
+
// Exact integer sums: long double is emulated in software on Linux aarch64
|
|
155
|
+
std::int64_t sum = 0;
|
|
156
|
+
|
|
157
|
+
// Above this sum the result is at least bound + 1, a margin for the rounding of the doubles.
|
|
158
|
+
const double side = 2.0 * patch_size + 1;
|
|
159
|
+
const double limit =
|
|
160
|
+
(bound + 1.0) * PatchSSDDistanceMetric::kSSDScale * side * side / PatchDistanceMetric::kDistanceScale;
|
|
161
|
+
|
|
162
|
+
source.compute_image_gradients();
|
|
163
|
+
target.compute_image_gradients();
|
|
164
|
+
|
|
165
|
+
const ImagePair images(source, target);
|
|
166
|
+
for (int dy = -patch_size; dy <= patch_size; ++dy)
|
|
167
|
+
{
|
|
168
|
+
if (static_cast<double>(sum) > limit)
|
|
169
|
+
return -1;
|
|
170
|
+
sum += row_sum(images, {s.y + dy, s.x}, {t.y + dy, t.x}, patch_size);
|
|
171
|
+
}
|
|
172
|
+
return sum;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Scales a sum of costs to [0, kDistanceScale].
|
|
176
|
+
int scale_sum(std::int64_t sum, int patch_size)
|
|
177
|
+
{
|
|
178
|
+
const std::int64_t side = 2 * patch_size + 1;
|
|
179
|
+
const double scaled = static_cast<double>(sum) / PatchSSDDistanceMetric::kSSDScale;
|
|
180
|
+
const auto res =
|
|
181
|
+
static_cast<int>(PatchDistanceMetric::kDistanceScale * scaled / static_cast<double>(side * side));
|
|
182
|
+
if (res < 0 || res > PatchDistanceMetric::kDistanceScale)
|
|
183
|
+
return PatchDistanceMetric::kDistanceScale;
|
|
184
|
+
return res;
|
|
185
|
+
}
|
|
36
186
|
} // namespace
|
|
37
187
|
|
|
38
188
|
void NearestNeighborField::seed_random(unsigned int seed)
|
|
@@ -40,6 +190,30 @@ void NearestNeighborField::seed_random(unsigned int seed)
|
|
|
40
190
|
random_engine().seed(seed);
|
|
41
191
|
}
|
|
42
192
|
|
|
193
|
+
void NearestNeighborField::_allocate_sums()
|
|
194
|
+
{
|
|
195
|
+
// Only the plain SSD adds up line by line; a subclass may measure differently.
|
|
196
|
+
if (typeid(*m_distance_metric) == typeid(PatchSSDDistanceMetric))
|
|
197
|
+
m_sums.assign(static_cast<std::size_t>(m_field.rows) * m_field.cols, -1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
NearestNeighborField::Measured NearestNeighborField::_measure(int y, int x, int y_target, int x_target, int bound) const
|
|
201
|
+
{
|
|
202
|
+
if (m_sums.empty())
|
|
203
|
+
{
|
|
204
|
+
const int distance =
|
|
205
|
+
bound < PatchDistanceMetric::kDistanceScale
|
|
206
|
+
? m_distance_metric->distance_below(m_source, y, x, m_target, y_target, x_target, bound)
|
|
207
|
+
: (*m_distance_metric)(m_source, y, x, m_target, y_target, x_target);
|
|
208
|
+
return {distance, -1};
|
|
209
|
+
}
|
|
210
|
+
const int patch_size = m_distance_metric->patch_size();
|
|
211
|
+
const std::int64_t sum = patch_sum(m_source, {y, x}, m_target, {y_target, x_target}, patch_size, bound);
|
|
212
|
+
if (sum < 0)
|
|
213
|
+
return {PatchDistanceMetric::kDistanceScale, -1};
|
|
214
|
+
return {scale_sum(sum, patch_size), sum};
|
|
215
|
+
}
|
|
216
|
+
|
|
43
217
|
void NearestNeighborField::_randomize_field(bool reset)
|
|
44
218
|
{
|
|
45
219
|
auto this_size = source_size();
|
|
@@ -61,19 +235,22 @@ void NearestNeighborField::_randomize_link(int y, int x)
|
|
|
61
235
|
auto this_target_size = target_size();
|
|
62
236
|
int y_target = 0;
|
|
63
237
|
int x_target = 0;
|
|
64
|
-
|
|
238
|
+
Measured measured{PatchDistanceMetric::kDistanceScale, -1};
|
|
65
239
|
for (int t = 0; t < kMaxRetry; ++t)
|
|
66
240
|
{
|
|
67
241
|
y_target = random_int(this_target_size.height);
|
|
68
242
|
x_target = random_int(this_target_size.width);
|
|
69
243
|
if (m_target.is_globally_masked(y_target, x_target))
|
|
244
|
+
{
|
|
245
|
+
measured.sum = -1; // the distance of an earlier try, which is kDistanceScale
|
|
70
246
|
continue;
|
|
247
|
+
}
|
|
71
248
|
|
|
72
|
-
|
|
73
|
-
if (distance < PatchDistanceMetric::kDistanceScale)
|
|
249
|
+
measured = _measure(y, x, y_target, x_target, PatchDistanceMetric::kDistanceScale);
|
|
250
|
+
if (measured.distance < PatchDistanceMetric::kDistanceScale)
|
|
74
251
|
break;
|
|
75
252
|
}
|
|
76
|
-
_set(y, x, y_target, x_target,
|
|
253
|
+
_set(y, x, y_target, x_target, measured);
|
|
77
254
|
}
|
|
78
255
|
|
|
79
256
|
void NearestNeighborField::_initialize_field_from(const NearestNeighborField &other)
|
|
@@ -92,22 +269,22 @@ void NearestNeighborField::_initialize_field_from(const NearestNeighborField &ot
|
|
|
92
269
|
|
|
93
270
|
auto ilow = static_cast<int>(std::min(i / fi, static_cast<double>(other_size.height - 1)));
|
|
94
271
|
auto jlow = static_cast<int>(std::min(j / fj, static_cast<double>(other_size.width - 1)));
|
|
95
|
-
auto this_value = mutable_ptr(i, j);
|
|
96
272
|
auto other_value = other.ptr(ilow, jlow);
|
|
97
273
|
|
|
98
274
|
// Keep the offset within the coarse pixel, so that neighbors still point to
|
|
99
275
|
// neighbors. Without it, both pixels of a pair point to the same target pixel,
|
|
100
276
|
// which makes the upscaled fill blocky.
|
|
101
|
-
|
|
277
|
+
const int y_target =
|
|
102
278
|
std::clamp(static_cast<int>(other_value[0] * fi + (i - ilow * fi)), 0, target_size().height - 1);
|
|
103
|
-
|
|
279
|
+
const int x_target =
|
|
104
280
|
std::clamp(static_cast<int>(other_value[1] * fj + (j - jlow * fj)), 0, target_size().width - 1);
|
|
105
|
-
|
|
281
|
+
const Measured measured = _measure(i, j, y_target, x_target, PatchDistanceMetric::kDistanceScale);
|
|
282
|
+
_set(i, j, y_target, x_target, measured);
|
|
106
283
|
|
|
107
284
|
// A hole that is new on this level: search near the patch before a neighbor's link replaces its own.
|
|
108
285
|
const bool linked_to_itself = other_value[0] == ilow && other_value[1] == jlow;
|
|
109
|
-
if (linked_to_itself &&
|
|
110
|
-
m_target.contains_mask(
|
|
286
|
+
if (linked_to_itself && measured.distance > 0 &&
|
|
287
|
+
m_target.contains_mask(y_target, x_target, m_distance_metric->patch_size()))
|
|
111
288
|
_random_search(i, j);
|
|
112
289
|
}
|
|
113
290
|
}
|
|
@@ -139,21 +316,47 @@ void NearestNeighborField::_minimize_link(int y, int x, int direction)
|
|
|
139
316
|
|
|
140
317
|
// propagation along the y direction.
|
|
141
318
|
if (y - direction >= 0 && y - direction < this_size.height && !m_source.is_globally_masked(y - direction, x))
|
|
142
|
-
|
|
143
|
-
int yp = at(y - direction, x, 0) + direction;
|
|
144
|
-
int xp = at(y - direction, x, 1);
|
|
145
|
-
_link_if_closer(y, x, yp, xp);
|
|
146
|
-
}
|
|
319
|
+
_propagate(y, x, direction, 0);
|
|
147
320
|
|
|
148
321
|
// propagation along the x direction.
|
|
149
322
|
if (x - direction >= 0 && x - direction < this_size.width && !m_source.is_globally_masked(y, x - direction))
|
|
323
|
+
_propagate(y, x, 0, direction);
|
|
324
|
+
|
|
325
|
+
_random_search(y, x);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Tries the link of the neighbor (y - dy, x - dx), shifted by (dy, dx). With the neighbor's sum,
|
|
329
|
+
// its distance takes the line that leaves the patch and the one that enters it (paper 3.3).
|
|
330
|
+
void NearestNeighborField::_propagate(int y, int x, int dy, int dx)
|
|
331
|
+
{
|
|
332
|
+
const int y_target = at(y - dy, x - dx, 0) + dy;
|
|
333
|
+
const int x_target = at(y - dy, x - dx, 1) + dx;
|
|
334
|
+
const std::int64_t neighbor_sum = m_sums.empty() ? -1 : m_sums[_index(y - dy, x - dx)];
|
|
335
|
+
if (neighbor_sum < 0)
|
|
150
336
|
{
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
_link_if_closer(y, x, yp, xp);
|
|
337
|
+
_link_if_closer(y, x, y_target, x_target);
|
|
338
|
+
return;
|
|
154
339
|
}
|
|
340
|
+
if (y_target == at(y, x, 0) && x_target == at(y, x, 1))
|
|
341
|
+
return;
|
|
155
342
|
|
|
156
|
-
|
|
343
|
+
const int patch_size = m_distance_metric->patch_size();
|
|
344
|
+
const ImagePair images(m_source, m_target);
|
|
345
|
+
const auto line_sum = [&images, dy, patch_size](Position s, Position t) {
|
|
346
|
+
return dy != 0 ? row_sum(images, s, t, patch_size) : column_sum(images, s, t, patch_size);
|
|
347
|
+
};
|
|
348
|
+
const int leaving = patch_size + 1;
|
|
349
|
+
const std::int64_t sum =
|
|
350
|
+
neighbor_sum -
|
|
351
|
+
line_sum({y - dy * leaving, x - dx * leaving}, {y_target - dy * leaving, x_target - dx * leaving}) +
|
|
352
|
+
line_sum({y + dy * patch_size, x + dx * patch_size}, {y_target + dy * patch_size, x_target + dx * patch_size});
|
|
353
|
+
// The sanitizer build keeps the asserts, so the tests compare every sum with a full measurement.
|
|
354
|
+
assert(
|
|
355
|
+
sum ==
|
|
356
|
+
patch_sum(m_source, {y, x}, m_target, {y_target, x_target}, patch_size, PatchDistanceMetric::kDistanceScale));
|
|
357
|
+
const int distance = scale_sum(sum, patch_size);
|
|
358
|
+
if (distance < at(y, x, 2))
|
|
359
|
+
_set(y, x, y_target, x_target, {distance, sum});
|
|
157
360
|
}
|
|
158
361
|
|
|
159
362
|
// Random search around the link in windows of decreasing size, clamped to the image.
|
|
@@ -180,112 +383,14 @@ const int PatchSSDDistanceMetric::kSSDScale = 9 * 255 * 255;
|
|
|
180
383
|
|
|
181
384
|
namespace
|
|
182
385
|
{
|
|
183
|
-
|
|
184
|
-
inline int pow2(int i)
|
|
185
|
-
{
|
|
186
|
-
return i * i;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
struct Position
|
|
190
|
-
{
|
|
191
|
-
int y;
|
|
192
|
-
int x;
|
|
193
|
-
};
|
|
194
|
-
|
|
195
386
|
// Returns kDistanceScale once the result is certain to reach bound, never for bound kDistanceScale.
|
|
196
387
|
int distance_masked_images(
|
|
197
388
|
const MaskedImage &source, Position source_center, const MaskedImage &target, Position target_center,
|
|
198
389
|
int patch_size, int bound)
|
|
199
390
|
{
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// Exact integer sums: long double is emulated in software on Linux aarch64
|
|
204
|
-
std::int64_t distance = 0;
|
|
205
|
-
std::int64_t wsum = 0;
|
|
206
|
-
|
|
207
|
-
// Above this sum the result is at least bound + 1, a margin for the rounding of the doubles.
|
|
208
|
-
const double side = 2.0 * patch_size + 1;
|
|
209
|
-
const double limit =
|
|
210
|
-
(bound + 1.0) * PatchSSDDistanceMetric::kSSDScale * side * side / PatchDistanceMetric::kDistanceScale;
|
|
211
|
-
|
|
212
|
-
source.compute_image_gradients();
|
|
213
|
-
target.compute_image_gradients();
|
|
214
|
-
|
|
215
|
-
auto source_size = source.size();
|
|
216
|
-
auto target_size = target.size();
|
|
217
|
-
|
|
218
|
-
for (int dy = -patch_size; dy <= patch_size; ++dy)
|
|
219
|
-
{
|
|
220
|
-
if (static_cast<double>(distance) > limit)
|
|
221
|
-
return PatchDistanceMetric::kDistanceScale;
|
|
222
|
-
|
|
223
|
-
const int yys = ys + dy;
|
|
224
|
-
const int yyt = yt + dy;
|
|
225
|
-
|
|
226
|
-
if (yys < 0 || yys >= source_size.height || yyt < 0 || yyt >= target_size.height)
|
|
227
|
-
{
|
|
228
|
-
distance += std::int64_t{PatchSSDDistanceMetric::kSSDScale} * (2 * patch_size + 1);
|
|
229
|
-
wsum += 2 * patch_size + 1;
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const auto *p_si = source.image().ptr<unsigned char>(yys, 0);
|
|
234
|
-
const auto *p_ti = target.image().ptr<unsigned char>(yyt, 0);
|
|
235
|
-
const auto *p_sm = source.mask().ptr<unsigned char>(yys, 0);
|
|
236
|
-
const auto *p_tm = target.mask().ptr<unsigned char>(yyt, 0);
|
|
237
|
-
|
|
238
|
-
const unsigned char *p_sgm = nullptr;
|
|
239
|
-
const unsigned char *p_tgm = nullptr;
|
|
240
|
-
if (source.has_global_mask())
|
|
241
|
-
p_sgm = source.global_mask().ptr<unsigned char>(yys, 0);
|
|
242
|
-
if (target.has_global_mask())
|
|
243
|
-
p_tgm = target.global_mask().ptr<unsigned char>(yyt, 0);
|
|
244
|
-
|
|
245
|
-
const auto *p_sgy = source.grady().ptr<unsigned char>(yys, 0);
|
|
246
|
-
const auto *p_tgy = target.grady().ptr<unsigned char>(yyt, 0);
|
|
247
|
-
const auto *p_sgx = source.gradx().ptr<unsigned char>(yys, 0);
|
|
248
|
-
const auto *p_tgx = target.gradx().ptr<unsigned char>(yyt, 0);
|
|
249
|
-
|
|
250
|
-
for (int dx = -patch_size; dx <= patch_size; ++dx)
|
|
251
|
-
{
|
|
252
|
-
const int xxs = xs + dx;
|
|
253
|
-
const int xxt = xt + dx;
|
|
254
|
-
wsum += 1;
|
|
255
|
-
|
|
256
|
-
// The bounds first: the masks are read only inside the image.
|
|
257
|
-
if (xxs < 0 || xxs >= source_size.width || xxt < 0 || xxt >= target_size.width || p_sm[xxs] ||
|
|
258
|
-
p_tm[xxt] || (p_sgm && p_sgm[xxs]) || (p_tgm && p_tgm[xxt]))
|
|
259
|
-
{
|
|
260
|
-
distance += PatchSSDDistanceMetric::kSSDScale;
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
int ssd = 0;
|
|
265
|
-
for (int c = 0; c < 3; ++c)
|
|
266
|
-
{
|
|
267
|
-
int s_value = p_si[xxs * 3 + c];
|
|
268
|
-
int t_value = p_ti[xxt * 3 + c];
|
|
269
|
-
int s_gy = p_sgy[xxs * 3 + c];
|
|
270
|
-
int t_gy = p_tgy[xxt * 3 + c];
|
|
271
|
-
int s_gx = p_sgx[xxs * 3 + c];
|
|
272
|
-
int t_gx = p_tgx[xxt * 3 + c];
|
|
273
|
-
|
|
274
|
-
ssd += pow2(s_value - t_value);
|
|
275
|
-
ssd += pow2(s_gx - t_gx);
|
|
276
|
-
ssd += pow2(s_gy - t_gy);
|
|
277
|
-
}
|
|
278
|
-
distance += ssd;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const double scaled = static_cast<double>(distance) / PatchSSDDistanceMetric::kSSDScale;
|
|
283
|
-
const auto res = static_cast<int>(PatchDistanceMetric::kDistanceScale * scaled / static_cast<double>(wsum));
|
|
284
|
-
if (res < 0 || res > PatchDistanceMetric::kDistanceScale)
|
|
285
|
-
return PatchDistanceMetric::kDistanceScale;
|
|
286
|
-
return res;
|
|
391
|
+
const std::int64_t sum = patch_sum(source, source_center, target, target_center, patch_size, bound);
|
|
392
|
+
return sum < 0 ? PatchDistanceMetric::kDistanceScale : scale_sum(sum, patch_size);
|
|
287
393
|
}
|
|
288
|
-
|
|
289
394
|
} // namespace
|
|
290
395
|
|
|
291
396
|
int PatchSSDDistanceMetric::operator()(
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cstdint>
|
|
3
5
|
#include <opencv2/core.hpp>
|
|
4
6
|
#include <stdexcept>
|
|
7
|
+
#include <vector>
|
|
5
8
|
|
|
6
9
|
#include "masked_image.h"
|
|
7
10
|
|
|
@@ -39,6 +42,7 @@ public:
|
|
|
39
42
|
: m_source(source), m_target(target), m_distance_metric(metric)
|
|
40
43
|
{
|
|
41
44
|
m_field = cv::Mat(m_source.size(), CV_32SC3);
|
|
45
|
+
_allocate_sums();
|
|
42
46
|
_randomize_field(true);
|
|
43
47
|
}
|
|
44
48
|
NearestNeighborField(
|
|
@@ -47,6 +51,7 @@ public:
|
|
|
47
51
|
: m_source(source), m_target(target), m_distance_metric(metric)
|
|
48
52
|
{
|
|
49
53
|
m_field = cv::Mat(m_source.size(), CV_32SC3);
|
|
54
|
+
_allocate_sums();
|
|
50
55
|
_initialize_field_from(other);
|
|
51
56
|
}
|
|
52
57
|
|
|
@@ -69,16 +74,14 @@ public:
|
|
|
69
74
|
void set_source(const MaskedImage &source)
|
|
70
75
|
{
|
|
71
76
|
m_source = source;
|
|
77
|
+
std::fill(m_sums.begin(), m_sums.end(), -1);
|
|
72
78
|
}
|
|
73
79
|
void set_target(const MaskedImage &target)
|
|
74
80
|
{
|
|
75
81
|
m_target = target;
|
|
82
|
+
std::fill(m_sums.begin(), m_sums.end(), -1);
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
int *mutable_ptr(int y, int x)
|
|
79
|
-
{
|
|
80
|
-
return m_field.ptr<int>(y, x);
|
|
81
|
-
}
|
|
82
85
|
const int *ptr(int y, int x) const
|
|
83
86
|
{
|
|
84
87
|
return m_field.ptr<int>(y, x);
|
|
@@ -88,18 +91,17 @@ public:
|
|
|
88
91
|
{
|
|
89
92
|
return m_field.ptr<int>(y, x)[c];
|
|
90
93
|
}
|
|
94
|
+
// The distance 0 is not measured, so the sum stays unknown.
|
|
91
95
|
void set_identity(int y, int x)
|
|
92
96
|
{
|
|
93
|
-
|
|
94
|
-
ptr[0] = y;
|
|
95
|
-
ptr[1] = x;
|
|
96
|
-
ptr[2] = 0;
|
|
97
|
+
_set(y, x, y, x, {0, -1});
|
|
97
98
|
}
|
|
98
99
|
// Measures the link again after an image changed.
|
|
99
100
|
void update_distance(int y, int x)
|
|
100
101
|
{
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
_set(
|
|
103
|
+
y, x, at(y, x, 0), at(y, x, 1),
|
|
104
|
+
_measure(y, x, at(y, x, 0), at(y, x, 1), PatchDistanceMetric::kDistanceScale));
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
void minimize(int nr_pass);
|
|
@@ -108,35 +110,59 @@ public:
|
|
|
108
110
|
static void seed_random(unsigned int seed);
|
|
109
111
|
|
|
110
112
|
private:
|
|
111
|
-
|
|
113
|
+
// Private: a link written from outside would not update its sum.
|
|
114
|
+
int *mutable_ptr(int y, int x)
|
|
112
115
|
{
|
|
113
|
-
return (
|
|
116
|
+
return m_field.ptr<int>(y, x);
|
|
114
117
|
}
|
|
118
|
+
|
|
119
|
+
// A distance and the sum of the pixel costs behind it, -1 if unknown.
|
|
120
|
+
struct Measured
|
|
121
|
+
{
|
|
122
|
+
int distance;
|
|
123
|
+
std::int64_t sum;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// The distance if it is below bound, otherwise any value >= bound.
|
|
127
|
+
Measured _measure(int y, int x, int y_target, int x_target, int bound) const;
|
|
115
128
|
void _link_if_closer(int y, int x, int y_target, int x_target)
|
|
116
129
|
{
|
|
130
|
+
// In coherent regions most propagated candidates are the link itself, measured in full otherwise.
|
|
131
|
+
if (y_target == at(y, x, 0) && x_target == at(y, x, 1))
|
|
132
|
+
return;
|
|
117
133
|
const int current = at(y, x, 2);
|
|
118
|
-
const
|
|
119
|
-
if (distance < current)
|
|
120
|
-
_set(y, x, y_target, x_target,
|
|
134
|
+
const Measured measured = _measure(y, x, y_target, x_target, current);
|
|
135
|
+
if (measured.distance < current)
|
|
136
|
+
_set(y, x, y_target, x_target, measured);
|
|
121
137
|
}
|
|
122
|
-
void _set(int y, int x, int y_target, int x_target,
|
|
138
|
+
void _set(int y, int x, int y_target, int x_target, Measured measured)
|
|
123
139
|
{
|
|
124
140
|
auto ptr = mutable_ptr(y, x);
|
|
125
141
|
ptr[0] = y_target;
|
|
126
142
|
ptr[1] = x_target;
|
|
127
|
-
ptr[2] = distance;
|
|
143
|
+
ptr[2] = measured.distance;
|
|
144
|
+
if (!m_sums.empty())
|
|
145
|
+
m_sums[_index(y, x)] = measured.sum;
|
|
146
|
+
}
|
|
147
|
+
std::size_t _index(int y, int x) const
|
|
148
|
+
{
|
|
149
|
+
return static_cast<std::size_t>(y) * m_field.cols + x;
|
|
128
150
|
}
|
|
129
151
|
|
|
130
152
|
void _randomize_field(bool reset);
|
|
131
153
|
void _randomize_link(int y, int x);
|
|
132
154
|
void _initialize_field_from(const NearestNeighborField &other);
|
|
155
|
+
void _allocate_sums();
|
|
133
156
|
void _minimize_link(int y, int x, int direction);
|
|
157
|
+
void _propagate(int y, int x, int dy, int dx);
|
|
134
158
|
void _random_search(int y, int x);
|
|
135
159
|
|
|
136
160
|
MaskedImage m_source;
|
|
137
161
|
MaskedImage m_target;
|
|
138
162
|
// per pixel: the target y and x and the scaled distance to it
|
|
139
163
|
cv::Mat m_field;
|
|
164
|
+
// per pixel: the sum behind the distance, for updates in O(patch_size); empty for other metrics
|
|
165
|
+
std::vector<std::int64_t> m_sums;
|
|
140
166
|
const PatchDistanceMetric *m_distance_metric = nullptr;
|
|
141
167
|
};
|
|
142
168
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|