PyPatchMatch 2.0.0b1__tar.gz → 2.0.0rc1__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.0.0b1 → pypatchmatch-2.0.0rc1}/CMakeLists.txt +13 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/PKG-INFO +55 -19
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/README.md +45 -17
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/_lib.py +8 -2
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/_version.py +3 -3
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/inpaint.cpp +25 -13
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/masked_image.h +1 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/nnf.cpp +40 -30
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/nnf.h +8 -29
- pypatchmatch-2.0.0rc1/patchmatch/csrc/pyinterface.cpp +214 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/pyinterface.h +6 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/patch_match.py +56 -9
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/pyproject.toml +18 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/scripts/build_opencv.py +32 -3
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/tests/test_e2e.py +39 -11
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/tests/test_patch_match.py +110 -4
- pypatchmatch-2.0.0b1/patchmatch/csrc/pyinterface.cpp +0 -133
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/LICENSE +0 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/examples/images/forest_pruned.bmp +0 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/__init__.py +0 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/inpaint.h +0 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/csrc/masked_image.cpp +0 -0
- {pypatchmatch-2.0.0b1 → pypatchmatch-2.0.0rc1}/patchmatch/py.typed +0 -0
|
@@ -61,3 +61,16 @@ install(TARGETS patchmatch
|
|
|
61
61
|
LIBRARY DESTINATION patchmatch
|
|
62
62
|
RUNTIME DESTINATION patchmatch
|
|
63
63
|
)
|
|
64
|
+
|
|
65
|
+
# A static OpenCV becomes part of the library, so a wheel has to ship its licenses,
|
|
66
|
+
# which scripts/build_opencv.py collects in <prefix>/licenses.
|
|
67
|
+
if(DEFINED SKBUILD_METADATA_DIR AND NOT OpenCV_SHARED)
|
|
68
|
+
set(PATCHMATCH_OPENCV_LICENSES "${OpenCV_INSTALL_PATH}/licenses")
|
|
69
|
+
if(EXISTS "${PATCHMATCH_OPENCV_LICENSES}/opencv-LICENSE")
|
|
70
|
+
install(DIRECTORY "${PATCHMATCH_OPENCV_LICENSES}/"
|
|
71
|
+
DESTINATION "${SKBUILD_METADATA_DIR}/licenses/opencv")
|
|
72
|
+
else()
|
|
73
|
+
message(WARNING "No OpenCV licenses in ${PATCHMATCH_OPENCV_LICENSES}, "
|
|
74
|
+
"the wheel will not include them")
|
|
75
|
+
endif()
|
|
76
|
+
endif()
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPatchMatch
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0rc1
|
|
4
4
|
Summary: This library implements the PatchMatch based inpainting algorithm.
|
|
5
|
-
|
|
5
|
+
Keywords: inpainting,patchmatch,image-processing,opencv
|
|
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>
|
|
6
7
|
License-Expression: MIT
|
|
7
8
|
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Operating System :: MacOS
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
8
13
|
Classifier: Programming Language :: C++
|
|
9
14
|
Classifier: Programming Language :: Python :: 3
|
|
10
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -14,7 +19,10 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
14
19
|
Classifier: Programming Language :: Python :: 3.14
|
|
15
20
|
Classifier: Topic :: Multimedia :: Graphics
|
|
16
21
|
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
22
|
+
Classifier: Typing :: Typed
|
|
17
23
|
Project-URL: Source Code, https://github.com/mauwii/PyPatchMatch
|
|
24
|
+
Project-URL: Issues, https://github.com/mauwii/PyPatchMatch/issues
|
|
25
|
+
Project-URL: Release Notes, https://github.com/mauwii/PyPatchMatch/releases
|
|
18
26
|
Requires-Python: >=3.10
|
|
19
27
|
Requires-Dist: numpy
|
|
20
28
|
Requires-Dist: pillow
|
|
@@ -24,7 +32,7 @@ Description-Content-Type: text/markdown
|
|
|
24
32
|
|
|
25
33
|
[](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE)
|
|
26
34
|
[](https://pypi.org/project/PyPatchMatch/)
|
|
27
|
-
[](https://pepy.tech/
|
|
35
|
+
[](https://pepy.tech/projects/pypatchmatch)
|
|
28
36
|
[](https://github.com/astral-sh/ruff)
|
|
29
37
|
|
|
30
38
|
This library implements the PatchMatch based inpainting algorithm. It provides both C++
|
|
@@ -38,14 +46,25 @@ with some bug fixes, and updates.
|
|
|
38
46
|
pip install PyPatchMatch
|
|
39
47
|
```
|
|
40
48
|
|
|
41
|
-
Wheels for Linux,
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
Wheels for Linux (glibc, x86_64 and aarch64), macOS 11+ (arm64 and x86_64) and Windows
|
|
50
|
+
(x64) ship the compiled library with a statically linked OpenCV, so no compiler or
|
|
51
|
+
OpenCV installation is needed. On other platforms, e.g. Alpine Linux or Windows on ARM,
|
|
52
|
+
pip builds from the source distribution, which requires a C++17 compiler, CMake and the
|
|
44
53
|
OpenCV development files (e.g. `apt install libopencv-dev` or `brew install opencv`).
|
|
45
54
|
|
|
55
|
+
If that build fails, PyPatchMatch is installed without the native library instead of
|
|
56
|
+
blocking the installation: `patchmatch.patchmatch_available` is `False` and the
|
|
57
|
+
inpainting functions raise `RuntimeError`. After installing the missing tools, reinstall
|
|
58
|
+
it without the cached build:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
pip install --force-reinstall --no-deps --no-cache-dir PyPatchMatch
|
|
62
|
+
```
|
|
63
|
+
|
|
46
64
|
## Usage
|
|
47
65
|
|
|
48
|
-
Python (see
|
|
66
|
+
Python (see
|
|
67
|
+
[examples/py_example.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example.py)):
|
|
49
68
|
|
|
50
69
|
```python
|
|
51
70
|
import patchmatch
|
|
@@ -56,12 +75,27 @@ result = patchmatch.inpaint(image, mask, patch_size=3)
|
|
|
56
75
|
```
|
|
57
76
|
|
|
58
77
|
The mask must have the same height and width as the image. If `mask` is omitted, all
|
|
59
|
-
pure white pixels are treated as holes.
|
|
78
|
+
pure white pixels are treated as holes. Only the holes are filled; all other pixels keep
|
|
79
|
+
their values. The optional keyword argument `global_mask`, in the same format, marks
|
|
80
|
+
pixels that are neither filled nor used as a source; they keep their values as well (see
|
|
81
|
+
[examples/py_example_global_mask.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example_global_mask.py)).
|
|
60
82
|
`patchmatch.patchmatch_available` tells whether the native library could be loaded.
|
|
61
83
|
The previous import path `from patchmatch import patch_match` keeps working.
|
|
62
84
|
|
|
63
|
-
|
|
64
|
-
|
|
85
|
+
`patch_size` is the radius of the compared patches, which span `2 * patch_size + 1`
|
|
86
|
+
pixels in each direction. Larger patches follow larger structures but are much slower;
|
|
87
|
+
the default of 15 compares 31x31 patches, the examples use 3.
|
|
88
|
+
|
|
89
|
+
`patchmatch.set_random_seed(seed)` sets the seed of the randomized search and
|
|
90
|
+
`patchmatch.set_verbose(True)` prints the progress of the native code to stderr.
|
|
91
|
+
`patchmatch.inpaint_regularity(image, mask, ijmap)` additionally guides the search with
|
|
92
|
+
a regularity map, an HxWx3 float32 array with the regularity coordinates of each pixel
|
|
93
|
+
in its first two channels. Its `guide_weight` must be at least 0.
|
|
94
|
+
|
|
95
|
+
C++ (see
|
|
96
|
+
[examples/cpp_example.cpp](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example.cpp),
|
|
97
|
+
build and run it with
|
|
98
|
+
[examples/cpp_example_run.sh](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example_run.sh)):
|
|
65
99
|
|
|
66
100
|
```cpp
|
|
67
101
|
#include "inpaint.h"
|
|
@@ -90,13 +124,21 @@ uv run pytest -m "not e2e" # only the fast unit tests
|
|
|
90
124
|
```
|
|
91
125
|
|
|
92
126
|
Releases are published to PyPI by creating a GitHub release; the version is taken from
|
|
93
|
-
its tag (e.g. `
|
|
127
|
+
its tag (e.g. `v2.0.0`).
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
PyPatchMatch is released under the
|
|
132
|
+
[MIT License](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE). The wheels
|
|
133
|
+
contain a statically linked build of OpenCV core (Apache-2.0) and its bundled
|
|
134
|
+
third-party code; their licenses are included in the `licenses/opencv` directory of the
|
|
135
|
+
wheel's `.dist-info`.
|
|
94
136
|
|
|
95
137
|
## README and COPYRIGHT by Younesse ANDAM
|
|
96
138
|
|
|
97
139
|
@Author: Younesse ANDAM
|
|
98
140
|
|
|
99
|
-
@Contact: younesse.andam@gmail.com
|
|
141
|
+
@Contact: <younesse.andam@gmail.com>
|
|
100
142
|
|
|
101
143
|
Description:
|
|
102
144
|
|
|
@@ -107,12 +149,6 @@ C.Barnes, E.Shechtman, A.Finkelstein and Dan B.Goldman ACM Transactions on Graph
|
|
|
107
149
|
(Proc. SIGGRAPH), vol.28, aug-2009
|
|
108
150
|
|
|
109
151
|
For more information please refer to
|
|
110
|
-
|
|
152
|
+
<https://gfx.cs.princeton.edu/pubs/Barnes_2009_PAR/>
|
|
111
153
|
|
|
112
154
|
Copyright (c) 2010-2011
|
|
113
|
-
|
|
114
|
-
## Requirements
|
|
115
|
-
|
|
116
|
-
To run the project you need to install Opencv library and link it to your project.
|
|
117
|
-
Opencv can be download it here
|
|
118
|
-
http://opencv.org/downloads.html
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE)
|
|
4
4
|
[](https://pypi.org/project/PyPatchMatch/)
|
|
5
|
-
[](https://pepy.tech/
|
|
5
|
+
[](https://pepy.tech/projects/pypatchmatch)
|
|
6
6
|
[](https://github.com/astral-sh/ruff)
|
|
7
7
|
|
|
8
8
|
This library implements the PatchMatch based inpainting algorithm. It provides both C++
|
|
@@ -16,14 +16,25 @@ with some bug fixes, and updates.
|
|
|
16
16
|
pip install PyPatchMatch
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Wheels for Linux,
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
Wheels for Linux (glibc, x86_64 and aarch64), macOS 11+ (arm64 and x86_64) and Windows
|
|
20
|
+
(x64) ship the compiled library with a statically linked OpenCV, so no compiler or
|
|
21
|
+
OpenCV installation is needed. On other platforms, e.g. Alpine Linux or Windows on ARM,
|
|
22
|
+
pip builds from the source distribution, which requires a C++17 compiler, CMake and the
|
|
22
23
|
OpenCV development files (e.g. `apt install libopencv-dev` or `brew install opencv`).
|
|
23
24
|
|
|
25
|
+
If that build fails, PyPatchMatch is installed without the native library instead of
|
|
26
|
+
blocking the installation: `patchmatch.patchmatch_available` is `False` and the
|
|
27
|
+
inpainting functions raise `RuntimeError`. After installing the missing tools, reinstall
|
|
28
|
+
it without the cached build:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pip install --force-reinstall --no-deps --no-cache-dir PyPatchMatch
|
|
32
|
+
```
|
|
33
|
+
|
|
24
34
|
## Usage
|
|
25
35
|
|
|
26
|
-
Python (see
|
|
36
|
+
Python (see
|
|
37
|
+
[examples/py_example.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example.py)):
|
|
27
38
|
|
|
28
39
|
```python
|
|
29
40
|
import patchmatch
|
|
@@ -34,12 +45,27 @@ result = patchmatch.inpaint(image, mask, patch_size=3)
|
|
|
34
45
|
```
|
|
35
46
|
|
|
36
47
|
The mask must have the same height and width as the image. If `mask` is omitted, all
|
|
37
|
-
pure white pixels are treated as holes.
|
|
48
|
+
pure white pixels are treated as holes. Only the holes are filled; all other pixels keep
|
|
49
|
+
their values. The optional keyword argument `global_mask`, in the same format, marks
|
|
50
|
+
pixels that are neither filled nor used as a source; they keep their values as well (see
|
|
51
|
+
[examples/py_example_global_mask.py](https://github.com/mauwii/PyPatchMatch/blob/main/examples/py_example_global_mask.py)).
|
|
38
52
|
`patchmatch.patchmatch_available` tells whether the native library could be loaded.
|
|
39
53
|
The previous import path `from patchmatch import patch_match` keeps working.
|
|
40
54
|
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
`patch_size` is the radius of the compared patches, which span `2 * patch_size + 1`
|
|
56
|
+
pixels in each direction. Larger patches follow larger structures but are much slower;
|
|
57
|
+
the default of 15 compares 31x31 patches, the examples use 3.
|
|
58
|
+
|
|
59
|
+
`patchmatch.set_random_seed(seed)` sets the seed of the randomized search and
|
|
60
|
+
`patchmatch.set_verbose(True)` prints the progress of the native code to stderr.
|
|
61
|
+
`patchmatch.inpaint_regularity(image, mask, ijmap)` additionally guides the search with
|
|
62
|
+
a regularity map, an HxWx3 float32 array with the regularity coordinates of each pixel
|
|
63
|
+
in its first two channels. Its `guide_weight` must be at least 0.
|
|
64
|
+
|
|
65
|
+
C++ (see
|
|
66
|
+
[examples/cpp_example.cpp](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example.cpp),
|
|
67
|
+
build and run it with
|
|
68
|
+
[examples/cpp_example_run.sh](https://github.com/mauwii/PyPatchMatch/blob/main/examples/cpp_example_run.sh)):
|
|
43
69
|
|
|
44
70
|
```cpp
|
|
45
71
|
#include "inpaint.h"
|
|
@@ -68,13 +94,21 @@ uv run pytest -m "not e2e" # only the fast unit tests
|
|
|
68
94
|
```
|
|
69
95
|
|
|
70
96
|
Releases are published to PyPI by creating a GitHub release; the version is taken from
|
|
71
|
-
its tag (e.g. `
|
|
97
|
+
its tag (e.g. `v2.0.0`).
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
PyPatchMatch is released under the
|
|
102
|
+
[MIT License](https://github.com/mauwii/PyPatchMatch/blob/main/LICENSE). The wheels
|
|
103
|
+
contain a statically linked build of OpenCV core (Apache-2.0) and its bundled
|
|
104
|
+
third-party code; their licenses are included in the `licenses/opencv` directory of the
|
|
105
|
+
wheel's `.dist-info`.
|
|
72
106
|
|
|
73
107
|
## README and COPYRIGHT by Younesse ANDAM
|
|
74
108
|
|
|
75
109
|
@Author: Younesse ANDAM
|
|
76
110
|
|
|
77
|
-
@Contact: younesse.andam@gmail.com
|
|
111
|
+
@Contact: <younesse.andam@gmail.com>
|
|
78
112
|
|
|
79
113
|
Description:
|
|
80
114
|
|
|
@@ -85,12 +119,6 @@ C.Barnes, E.Shechtman, A.Finkelstein and Dan B.Goldman ACM Transactions on Graph
|
|
|
85
119
|
(Proc. SIGGRAPH), vol.28, aug-2009
|
|
86
120
|
|
|
87
121
|
For more information please refer to
|
|
88
|
-
|
|
122
|
+
<https://gfx.cs.princeton.edu/pubs/Barnes_2009_PAR/>
|
|
89
123
|
|
|
90
124
|
Copyright (c) 2010-2011
|
|
91
|
-
|
|
92
|
-
## Requirements
|
|
93
|
-
|
|
94
|
-
To run the project you need to install Opencv library and link it to your project.
|
|
95
|
-
Opencv can be download it here
|
|
96
|
-
http://opencv.org/downloads.html
|
|
@@ -80,9 +80,13 @@ def find_library() -> Path:
|
|
|
80
80
|
candidate = Path(directory) / LIBRARY_NAME
|
|
81
81
|
if candidate.is_file():
|
|
82
82
|
return candidate
|
|
83
|
+
# Without a wheel for the platform, a failed build from the sdist installs the
|
|
84
|
+
# package without the library, see the overrides in pyproject.toml.
|
|
83
85
|
raise OSError(
|
|
84
|
-
f"{LIBRARY_NAME} not found in {list(package.__path__)}
|
|
85
|
-
"
|
|
86
|
+
f"{LIBRARY_NAME} not found in {list(package.__path__)}. Without a wheel for "
|
|
87
|
+
"this platform, PyPatchMatch is built from source, which needs a C++ "
|
|
88
|
+
"compiler, CMake and OpenCV. Install them and reinstall it without the "
|
|
89
|
+
"cache: pip install --force-reinstall --no-deps --no-cache-dir pypatchmatch"
|
|
86
90
|
)
|
|
87
91
|
|
|
88
92
|
|
|
@@ -95,6 +99,8 @@ def load_library() -> ctypes.CDLL:
|
|
|
95
99
|
lib.PM_set_verbose.restype = None
|
|
96
100
|
lib.PM_free_pymat.argtypes = [CMatT]
|
|
97
101
|
lib.PM_free_pymat.restype = None
|
|
102
|
+
lib.PM_last_error.argtypes = []
|
|
103
|
+
lib.PM_last_error.restype = ctypes.c_char_p
|
|
98
104
|
|
|
99
105
|
lib.PM_inpaint.argtypes = [CMatT, CMatT, ctypes.c_int]
|
|
100
106
|
lib.PM_inpaint2.argtypes = [CMatT, CMatT, CMatT, ctypes.c_int]
|
|
@@ -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.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (2, 0, 0, '
|
|
21
|
+
__version__ = version = '2.0.0rc1'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 0, 0, 'rc1')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g75098309c'
|
|
@@ -9,13 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
namespace
|
|
11
11
|
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
void init_kDistance2Similarity()
|
|
12
|
+
std::vector<double> make_distance2similarity()
|
|
15
13
|
{
|
|
16
14
|
double base[11] = {1.0, 0.99, 0.96, 0.83, 0.38, 0.11, 0.02, 0.005, 0.0006, 0.0001, 0};
|
|
17
15
|
int length = (PatchDistanceMetric::kDistanceScale + 1);
|
|
18
|
-
|
|
16
|
+
std::vector<double> table(length);
|
|
19
17
|
for (int i = 0; i < length; ++i)
|
|
20
18
|
{
|
|
21
19
|
double t = (double)i / length;
|
|
@@ -23,8 +21,17 @@ namespace
|
|
|
23
21
|
int k = j + 1;
|
|
24
22
|
double vj = (j < 11) ? base[j] : 0;
|
|
25
23
|
double vk = (k < 11) ? base[k] : 0;
|
|
26
|
-
|
|
24
|
+
table[i] = vj + (100 * t - j) * (vk - vj);
|
|
27
25
|
}
|
|
26
|
+
return table;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Built on first use. The initialization of a local static is thread-safe, which
|
|
30
|
+
// matters because the Python bindings release the GIL during inpainting.
|
|
31
|
+
const std::vector<double> &distance2similarity()
|
|
32
|
+
{
|
|
33
|
+
static const std::vector<double> table = make_distance2similarity();
|
|
34
|
+
return table;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
inline void _weighted_copy(const MaskedImage &source, int ys, int xs, cv::Mat &target, int yt, int xt, double weight)
|
|
@@ -37,7 +44,6 @@ namespace
|
|
|
37
44
|
auto source_ptr = source.get_image(ys, xs);
|
|
38
45
|
auto target_ptr = target.ptr<double>(yt, xt);
|
|
39
46
|
|
|
40
|
-
#pragma unroll
|
|
41
47
|
for (int c = 0; c < 3; ++c)
|
|
42
48
|
target_ptr[c] += static_cast<double>(source_ptr[c]) * weight;
|
|
43
49
|
target_ptr[3] += weight;
|
|
@@ -69,16 +75,11 @@ void Inpainting::_initialize_pyramid()
|
|
|
69
75
|
source = source.downsample();
|
|
70
76
|
m_pyramid.push_back(source);
|
|
71
77
|
}
|
|
72
|
-
|
|
73
|
-
if (kDistance2Similarity.size() == 0)
|
|
74
|
-
{
|
|
75
|
-
init_kDistance2Similarity();
|
|
76
|
-
}
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
cv::Mat Inpainting::run(bool verbose, bool verbose_visualize, unsigned int random_seed)
|
|
80
81
|
{
|
|
81
|
-
|
|
82
|
+
NearestNeighborField::seed_random(random_seed);
|
|
82
83
|
const int nr_levels = m_pyramid.size();
|
|
83
84
|
|
|
84
85
|
MaskedImage source, target;
|
|
@@ -124,7 +125,17 @@ cv::Mat Inpainting::run(bool verbose, bool verbose_visualize, unsigned int rando
|
|
|
124
125
|
target = _expectation_maximization(source, target, level, verbose);
|
|
125
126
|
}
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
// Only the holes are filled. The votes of overlapping patches also change the known
|
|
129
|
+
// pixels near the holes, and the pyramid leaves globally masked pixels black, which
|
|
130
|
+
// are neither filled nor used as a source. Keep the input values of both.
|
|
131
|
+
cv::Mat result = target.image();
|
|
132
|
+
if (result.empty()) // OpenCV rejects empty operands in matrix expressions
|
|
133
|
+
return result;
|
|
134
|
+
cv::Mat keep = m_initial.mask() == 0;
|
|
135
|
+
if (!m_initial.global_mask().empty())
|
|
136
|
+
keep |= m_initial.global_mask() != 0;
|
|
137
|
+
m_initial.image().copyTo(result, keep);
|
|
138
|
+
return result;
|
|
128
139
|
}
|
|
129
140
|
|
|
130
141
|
// EM-Like algorithm (see "PatchMatch" - page 6).
|
|
@@ -211,6 +222,7 @@ void Inpainting::_expectation_step(
|
|
|
211
222
|
auto source_size = nnf.source_size();
|
|
212
223
|
auto target_size = nnf.target_size();
|
|
213
224
|
const int patch_size = m_distance_metric->patch_size();
|
|
225
|
+
const auto &kDistance2Similarity = distance2similarity();
|
|
214
226
|
|
|
215
227
|
for (int i = 0; i < source_size.height; ++i)
|
|
216
228
|
{
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include <algorithm>
|
|
2
2
|
#include <iostream>
|
|
3
3
|
#include <cmath>
|
|
4
|
+
#include <random>
|
|
4
5
|
|
|
5
6
|
#include "masked_image.h"
|
|
6
7
|
#include "nnf.h"
|
|
@@ -17,9 +18,35 @@ T clamp(T value, T min_value, T max_value)
|
|
|
17
18
|
return std::min(std::max(value, min_value), max_value);
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
namespace
|
|
22
|
+
{
|
|
23
|
+
// One generator per thread, so concurrent inpaintings with the same seed give the
|
|
24
|
+
// same results. It only drives the randomized search, which has to be reproducible
|
|
25
|
+
// from a seed, so a standard generator is the right choice. SonarCloud's PRNG rule
|
|
26
|
+
// (cpp:S2245) flags every declaration of its type, hence the NOSONAR markers.
|
|
27
|
+
std::mt19937 &random_engine() // NOSONAR
|
|
28
|
+
{
|
|
29
|
+
thread_local std::mt19937 engine; // NOSONAR
|
|
30
|
+
return engine;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The modulo instead of std::uniform_int_distribution keeps the sequence identical
|
|
34
|
+
// on all platforms.
|
|
35
|
+
inline int random_int(int n)
|
|
36
|
+
{
|
|
37
|
+
return static_cast<int>(random_engine()() % static_cast<unsigned int>(n));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
void NearestNeighborField::seed_random(unsigned int seed)
|
|
42
|
+
{
|
|
43
|
+
random_engine().seed(seed);
|
|
44
|
+
}
|
|
45
|
+
|
|
20
46
|
void NearestNeighborField::_randomize_field(int max_retry, bool reset)
|
|
21
47
|
{
|
|
22
48
|
auto this_size = source_size();
|
|
49
|
+
auto this_target_size = target_size();
|
|
23
50
|
for (int i = 0; i < this_size.height; ++i)
|
|
24
51
|
{
|
|
25
52
|
for (int j = 0; j < this_size.width; ++j)
|
|
@@ -37,8 +64,8 @@ void NearestNeighborField::_randomize_field(int max_retry, bool reset)
|
|
|
37
64
|
int i_target = 0, j_target = 0;
|
|
38
65
|
for (int t = 0; t < max_retry; ++t)
|
|
39
66
|
{
|
|
40
|
-
i_target =
|
|
41
|
-
j_target =
|
|
67
|
+
i_target = random_int(this_target_size.height);
|
|
68
|
+
j_target = random_int(this_target_size.width);
|
|
42
69
|
if (m_target.is_globally_masked(i_target, j_target))
|
|
43
70
|
continue;
|
|
44
71
|
|
|
@@ -138,8 +165,8 @@ void NearestNeighborField::_minimize_link(int y, int x, int direction)
|
|
|
138
165
|
int random_scale = (std::min(this_target_size.height, this_target_size.width) - 1) / 2;
|
|
139
166
|
while (random_scale > 0)
|
|
140
167
|
{
|
|
141
|
-
int yp = this_ptr[0] + (
|
|
142
|
-
int xp = this_ptr[1] + (
|
|
168
|
+
int yp = this_ptr[0] + (random_int(2 * random_scale + 1) - random_scale);
|
|
169
|
+
int xp = this_ptr[1] + (random_int(2 * random_scale + 1) - random_scale);
|
|
143
170
|
yp = clamp(yp, 0, target_size().height - 1);
|
|
144
171
|
xp = clamp(xp, 0, target_size().width - 1);
|
|
145
172
|
|
|
@@ -201,10 +228,9 @@ namespace
|
|
|
201
228
|
const unsigned char *p_sgm = nullptr;
|
|
202
229
|
const unsigned char *p_tgm = nullptr;
|
|
203
230
|
if (!source.global_mask().empty())
|
|
204
|
-
{
|
|
205
231
|
p_sgm = source.global_mask().ptr<unsigned char>(yys, 0);
|
|
232
|
+
if (!target.global_mask().empty())
|
|
206
233
|
p_tgm = target.global_mask().ptr<unsigned char>(yyt, 0);
|
|
207
|
-
}
|
|
208
234
|
|
|
209
235
|
const auto *p_sgy = source.grady().ptr<unsigned char>(yys, 0);
|
|
210
236
|
const auto *p_tgy = target.grady().ptr<unsigned char>(yyt, 0);
|
|
@@ -216,7 +242,7 @@ namespace
|
|
|
216
242
|
int xxs = xs + dx, xxt = xt + dx;
|
|
217
243
|
wsum += 1;
|
|
218
244
|
|
|
219
|
-
if (xxs <= 0 || xxs >= source_size.width - 1 || xxt <= 0 || xxt >=
|
|
245
|
+
if (xxs <= 0 || xxs >= source_size.width - 1 || xxt <= 0 || xxt >= target_size.width - 1)
|
|
220
246
|
{
|
|
221
247
|
distance += PatchSSDDistanceMetric::kSSDScale;
|
|
222
248
|
continue;
|
|
@@ -261,27 +287,6 @@ int PatchSSDDistanceMetric::operator()(const MaskedImage &source, int source_y,
|
|
|
261
287
|
return distance_masked_images(source, source_y, source_x, target, target_y, target_x, m_patch_size);
|
|
262
288
|
}
|
|
263
289
|
|
|
264
|
-
int DebugPatchSSDDistanceMetric::operator()(const MaskedImage &source, int source_y, int source_x, const MaskedImage &target, int target_y, int target_x) const
|
|
265
|
-
{
|
|
266
|
-
fprintf(stderr, "DebugPatchSSDDistanceMetric: %d %d %d %d\n", source.size().width, source.size().height, m_width, m_height);
|
|
267
|
-
return distance_masked_images(source, source_y, source_x, target, target_y, target_x, m_patch_size);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
int RegularityGuidedPatchDistanceMetricV1::operator()(const MaskedImage &source, int source_y, int source_x, const MaskedImage &target, int target_y, int target_x) const
|
|
271
|
-
{
|
|
272
|
-
double dx = remainder(double(source_x - target_x) / source.size().width, m_dx1);
|
|
273
|
-
double dy = remainder(double(source_y - target_y) / source.size().height, m_dy2);
|
|
274
|
-
|
|
275
|
-
double score1 = sqrt(dx * dx + dy * dy) / m_scale;
|
|
276
|
-
if (score1 < 0 || score1 > 1)
|
|
277
|
-
score1 = 1;
|
|
278
|
-
score1 *= PatchDistanceMetric::kDistanceScale;
|
|
279
|
-
|
|
280
|
-
double score2 = distance_masked_images(source, source_y, source_x, target, target_y, target_x, m_patch_size);
|
|
281
|
-
double score = score1 * m_weight + score2 / (1 + m_weight);
|
|
282
|
-
return static_cast<int>(score / (1 + m_weight));
|
|
283
|
-
}
|
|
284
|
-
|
|
285
290
|
int RegularityGuidedPatchDistanceMetricV2::operator()(const MaskedImage &source, int source_y, int source_x, const MaskedImage &target, int target_y, int target_x) const
|
|
286
291
|
{
|
|
287
292
|
if (target_y < 0 || target_y >= target.size().height || target_x < 0 || target_x >= target.size().width)
|
|
@@ -312,6 +317,11 @@ int RegularityGuidedPatchDistanceMetricV2::operator()(const MaskedImage &source,
|
|
|
312
317
|
}
|
|
313
318
|
|
|
314
319
|
double score2 = distance_masked_images(source, source_y, source_x, target, target_y, target_x, m_patch_size);
|
|
315
|
-
double score = score1 * m_weight + score2;
|
|
316
|
-
|
|
320
|
+
double score = (score1 * m_weight + score2) / (1 + m_weight);
|
|
321
|
+
// The distance indexes the similarity table in Inpainting, so keep it in range.
|
|
322
|
+
if (!(score > 0))
|
|
323
|
+
return 0;
|
|
324
|
+
if (score >= PatchDistanceMetric::kDistanceScale)
|
|
325
|
+
return PatchDistanceMetric::kDistanceScale;
|
|
326
|
+
return static_cast<int>(score);
|
|
317
327
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
+
#include <stdexcept>
|
|
3
4
|
#include <opencv2/core.hpp>
|
|
4
5
|
#include "masked_image.h"
|
|
5
6
|
|
|
@@ -87,6 +88,9 @@ public:
|
|
|
87
88
|
|
|
88
89
|
void minimize(int nr_pass);
|
|
89
90
|
|
|
91
|
+
// Seeds the random search of the calling thread.
|
|
92
|
+
static void seed_random(unsigned int seed);
|
|
93
|
+
|
|
90
94
|
private:
|
|
91
95
|
inline int _distance(int source_y, int source_x, int target_y, int target_x)
|
|
92
96
|
{
|
|
@@ -111,44 +115,19 @@ public:
|
|
|
111
115
|
static const int kSSDScale;
|
|
112
116
|
};
|
|
113
117
|
|
|
114
|
-
class DebugPatchSSDDistanceMetric : public PatchDistanceMetric
|
|
115
|
-
{
|
|
116
|
-
public:
|
|
117
|
-
DebugPatchSSDDistanceMetric(int patch_size, int width, int height) : PatchDistanceMetric(patch_size), m_width(width), m_height(height) {}
|
|
118
|
-
virtual int operator()(const MaskedImage &source, int source_y, int source_x, const MaskedImage &target, int target_y, int target_x) const;
|
|
119
|
-
|
|
120
|
-
protected:
|
|
121
|
-
int m_width, m_height;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
class RegularityGuidedPatchDistanceMetricV1 : public PatchDistanceMetric
|
|
125
|
-
{
|
|
126
|
-
public:
|
|
127
|
-
RegularityGuidedPatchDistanceMetricV1(int patch_size, double dx1, double dy1, double dx2, double dy2, double weight)
|
|
128
|
-
: PatchDistanceMetric(patch_size), m_dx1(dx1), m_dy1(dy1), m_dx2(dx2), m_dy2(dy2), m_weight(weight)
|
|
129
|
-
{
|
|
130
|
-
|
|
131
|
-
assert(m_dy1 == 0);
|
|
132
|
-
assert(m_dx2 == 0);
|
|
133
|
-
m_scale = sqrt(m_dx1 * m_dx1 + m_dy2 * m_dy2) / 4;
|
|
134
|
-
}
|
|
135
|
-
virtual int operator()(const MaskedImage &source, int source_y, int source_x, const MaskedImage &target, int target_y, int target_x) const;
|
|
136
|
-
|
|
137
|
-
protected:
|
|
138
|
-
double m_dx1, m_dy1, m_dx2, m_dy2;
|
|
139
|
-
double m_scale, m_weight;
|
|
140
|
-
};
|
|
141
|
-
|
|
142
118
|
class RegularityGuidedPatchDistanceMetricV2 : public PatchDistanceMetric
|
|
143
119
|
{
|
|
144
120
|
public:
|
|
145
121
|
RegularityGuidedPatchDistanceMetricV2(int patch_size, cv::Mat ijmap, double weight)
|
|
146
122
|
: PatchDistanceMetric(patch_size), m_ijmap(ijmap), m_weight(weight)
|
|
147
123
|
{
|
|
124
|
+
// The distance is divided by 1 + weight, so a negative weight flips its sign.
|
|
125
|
+
if (!(weight >= 0))
|
|
126
|
+
throw std::invalid_argument("the guide weight must be >= 0");
|
|
148
127
|
}
|
|
149
128
|
virtual int operator()(const MaskedImage &source, int source_y, int source_x, const MaskedImage &target, int target_y, int target_x) const;
|
|
150
129
|
|
|
151
130
|
protected:
|
|
152
131
|
cv::Mat m_ijmap;
|
|
153
|
-
double
|
|
132
|
+
double m_weight;
|
|
154
133
|
};
|