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.
Files changed (29) hide show
  1. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/CMakeLists.txt +2 -1
  2. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/PKG-INFO +2 -2
  3. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/_version.py +3 -3
  4. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/nnf.cpp +224 -119
  5. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/nnf.h +43 -17
  6. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/CLAUDE.md +0 -0
  7. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/LICENSE +0 -0
  8. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/README.md +0 -0
  9. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/SECURITY.md +0 -0
  10. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/examples/images/chelsea.png +0 -0
  11. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/examples/images/forest_pruned.bmp +0 -0
  12. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/__init__.py +0 -0
  13. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/_lib.py +0 -0
  14. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/inpaint.cpp +0 -0
  15. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/inpaint.h +0 -0
  16. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/masked_image.cpp +0 -0
  17. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/masked_image.h +0 -0
  18. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/pyinterface.cpp +0 -0
  19. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/csrc/pyinterface.h +0 -0
  20. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/patch_match.py +0 -0
  21. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/patchmatch/py.typed +0 -0
  22. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/pyproject.toml +0 -0
  23. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/scripts/build_opencv.py +0 -0
  24. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/scripts/evaluate_inpainting.py +0 -0
  25. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/scripts/render_readme_example.py +0 -0
  26. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/tests/conftest.py +0 -0
  27. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/tests/test_e2e.py +0 -0
  28. {pypatchmatch-2.1.0 → pypatchmatch-2.1.1}/tests/test_patch_match.py +0 -0
  29. {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.0
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
- ![Birdhouses removed from a photo by patchmatch.inpaint](https://raw.githubusercontent.com/mauwii/PyPatchMatch/v2.1.0/examples/images/readme_example.jpg)
46
+ ![Birdhouses removed from a photo by patchmatch.inpaint](https://raw.githubusercontent.com/mauwii/PyPatchMatch/v2.1.1/examples/images/readme_example.jpg)
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.0'
22
- __version_tuple__ = version_tuple = (2, 1, 0)
21
+ __version__ = version = '2.1.1'
22
+ __version_tuple__ = version_tuple = (2, 1, 1)
23
23
 
24
- __commit_id__ = commit_id = 'g2f558d40a'
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
- int distance = PatchDistanceMetric::kDistanceScale;
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
- distance = _distance(y, x, y_target, x_target);
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, distance);
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
- this_value[0] =
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
- this_value[1] =
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
- this_value[2] = _distance(i, j, this_value[0], this_value[1]);
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 && this_value[2] > 0 &&
110
- m_target.contains_mask(this_value[0], this_value[1], m_distance_metric->patch_size()))
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
- int yp = at(y, x - direction, 0);
152
- int xp = at(y, x - direction, 1) + direction;
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
- _random_search(y, x);
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 auto [ys, xs] = source_center;
201
- const auto [yt, xt] = target_center;
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
- auto ptr = mutable_ptr(y, x);
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
- auto ptr = mutable_ptr(y, x);
102
- ptr[2] = _distance(y, x, ptr[0], ptr[1]);
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
- int _distance(int source_y, int source_x, int target_y, int target_x) const
113
+ // Private: a link written from outside would not update its sum.
114
+ int *mutable_ptr(int y, int x)
112
115
  {
113
- return (*m_distance_metric)(m_source, source_y, source_x, m_target, target_y, target_x);
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 int distance = m_distance_metric->distance_below(m_source, y, x, m_target, y_target, x_target, current);
119
- if (distance < current)
120
- _set(y, x, y_target, x_target, distance);
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, int distance)
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