Skip to content

Commit ed83dff

Browse files
committed
init commit
0 parents  commit ed83dff

19 files changed

Lines changed: 1461 additions & 0 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build
2+
__pycache__

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
cmake_minimum_required(VERSION 3.10)
2+
3+
file(GLOB SOURCES *.h *.cpp )
4+
get_filename_component(full_path_test_cpp ${CMAKE_CURRENT_SOURCE_DIR}/test.cpp ABSOLUTE)
5+
list(REMOVE_ITEM SOURCES "${full_path_test_cpp}")
6+
find_package(OpenCV REQUIRED)
7+
include_directories(${OpenCV_INCLUDE_DIRS})
8+
add_library(patchmatch_inpaint SHARED ${SOURCES})
9+
target_link_libraries(patchmatch_inpaint ${OpenCV_LIBRARIES})

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PatchMatch based inpainting algorithm modified from https://github.com/vacancy/PyPatchMatch

__init__.py

Whitespace-only changes.

cpp_out.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#include <iostream>
2+
#include <opencv2/imgcodecs.hpp>
3+
#include <opencv2/highgui.hpp>
4+
5+
#include "masked_image.h"
6+
#include "nnf.h"
7+
#include "inpaint.h"
8+
9+
int main() {
10+
auto source = cv::imread("./images/forest_pruned.bmp", cv::IMREAD_COLOR);
11+
12+
auto mask = cv::Mat(source.size(), CV_8UC1);
13+
mask = cv::Scalar::all(0);
14+
for (int i = 0; i < source.size().height; ++i) {
15+
for (int j = 0; j < source.size().width; ++j) {
16+
auto source_ptr = source.ptr<unsigned char>(i, j);
17+
if (source_ptr[0] == 255 && source_ptr[1] == 255 && source_ptr[2] == 255) {
18+
mask.at<unsigned char>(i, j) = 1;
19+
}
20+
}
21+
}
22+
23+
auto metric = PatchSSDDistanceMetric(3);
24+
auto result = Inpainting(source, mask, &metric).run(false, false);
25+
cv::imwrite("./images/forest_recovered.bmp", result);
26+
cv::imshow("Result", result);
27+
cv::waitKey();
28+
29+
return 0;
30+
}

dllmain.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// dllmain.cpp : 定义 DLL 应用程序的入口点。
2+
#include "pch.h"
3+
4+
BOOL APIENTRY DllMain( HMODULE hModule,
5+
DWORD ul_reason_for_call,
6+
LPVOID lpReserved
7+
)
8+
{
9+
switch (ul_reason_for_call)
10+
{
11+
case DLL_PROCESS_ATTACH:
12+
case DLL_THREAD_ATTACH:
13+
case DLL_THREAD_DETACH:
14+
case DLL_PROCESS_DETACH:
15+
break;
16+
}
17+
return TRUE;
18+
}
19+

framework.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#pragma once
2+
3+
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
4+
// Windows 头文件
5+
#include <windows.h>

inpaint.cpp

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
#include <algorithm>
2+
#include <iostream>
3+
#include <opencv2/imgcodecs.hpp>
4+
#include <opencv2/imgproc.hpp>
5+
#include <opencv2/highgui.hpp>
6+
7+
#include "inpaint.h"
8+
9+
namespace {
10+
static std::vector<double> kDistance2Similarity;
11+
12+
void init_kDistance2Similarity() {
13+
double base[11] = { 1.0, 0.99, 0.96, 0.83, 0.38, 0.11, 0.02, 0.005, 0.0006, 0.0001, 0 };
14+
int length = (PatchDistanceMetric::kDistanceScale + 1);
15+
kDistance2Similarity.resize(length);
16+
for (int i = 0; i < length; ++i) {
17+
double t = (double)i / length;
18+
int j = (int)(100 * t);
19+
int k = j + 1;
20+
double vj = (j < 11) ? base[j] : 0;
21+
double vk = (k < 11) ? base[k] : 0;
22+
kDistance2Similarity[i] = vj + (100 * t - j) * (vk - vj);
23+
}
24+
}
25+
26+
27+
inline void _weighted_copy(const MaskedImage& source, int ys, int xs, cv::Mat& target, int yt, int xt, double weight) {
28+
if (source.is_masked(ys, xs)) return;
29+
if (source.is_globally_masked(ys, xs)) return;
30+
31+
auto source_ptr = source.get_image(ys, xs);
32+
auto target_ptr = target.ptr<double>(yt, xt);
33+
34+
#pragma loop( ivdep )
35+
for (int c = 0; c < 3; ++c)
36+
target_ptr[c] += static_cast<double>(source_ptr[c]) * weight;
37+
target_ptr[3] += weight;
38+
}
39+
}
40+
41+
42+
/* This algorithme uses a version proposed by Xavier Philippeau.*/
43+
44+
Inpainting::Inpainting(cv::Mat image, cv::Mat mask, const PatchDistanceMetric* metric)
45+
: m_initial(image, mask), m_distance_metric(metric), m_pyramid(), m_source2target(), m_target2source() {
46+
_initialize_pyramid();
47+
}
48+
49+
Inpainting::Inpainting(cv::Mat image, cv::Mat mask, cv::Mat global_mask, const PatchDistanceMetric* metric)
50+
: m_initial(image, mask, global_mask), m_distance_metric(metric), m_pyramid(), m_source2target(), m_target2source() {
51+
_initialize_pyramid();
52+
}
53+
54+
void Inpainting::_initialize_pyramid() {
55+
auto source = m_initial;
56+
m_pyramid.push_back(source);
57+
while (source.size().height > m_distance_metric->patch_size() && source.size().width > m_distance_metric->patch_size()) {
58+
source = source.downsample();
59+
m_pyramid.push_back(source);
60+
}
61+
62+
if (kDistance2Similarity.size() == 0) {
63+
init_kDistance2Similarity();
64+
}
65+
}
66+
67+
cv::Mat Inpainting::run(bool verbose, bool verbose_visualize, unsigned int random_seed) {
68+
srand(random_seed);
69+
const int nr_levels = m_pyramid.size();
70+
71+
MaskedImage source, target;
72+
for (int level = nr_levels - 1; level >= 0; --level) {
73+
if (verbose) std::cerr << "Inpainting level: " << level << std::endl;
74+
75+
source = m_pyramid[level];
76+
if (verbose_visualize) {
77+
auto visualize_size = m_initial.size();
78+
cv::Mat source_visualize(visualize_size, m_initial.image().type());
79+
cv::resize(source.image(), source_visualize, visualize_size);
80+
cv::imshow("Source--0", source_visualize);
81+
cv::waitKey(0);
82+
}
83+
84+
if (level == nr_levels - 1) {
85+
target = source.clone();
86+
target.clear_mask();
87+
m_source2target = NearestNeighborField(source, target, m_distance_metric);
88+
m_target2source = NearestNeighborField(target, source, m_distance_metric);
89+
}
90+
else {
91+
m_source2target = NearestNeighborField(source, target, m_distance_metric, m_source2target);
92+
m_target2source = NearestNeighborField(target, source, m_distance_metric, m_target2source);
93+
}
94+
95+
if (verbose) std::cerr << "Initialization done." << std::endl;
96+
97+
if (verbose_visualize) {
98+
auto visualize_size = m_initial.size();
99+
cv::Mat source_visualize(visualize_size, m_initial.image().type());
100+
cv::resize(source.image(), source_visualize, visualize_size);
101+
cv::imshow("Source", source_visualize);
102+
cv::Mat target_visualize(visualize_size, m_initial.image().type());
103+
cv::resize(target.image(), target_visualize, visualize_size);
104+
cv::imshow("Target", target_visualize);
105+
cv::waitKey(0);
106+
}
107+
108+
target = _expectation_maximization(source, target, level, nr_levels, verbose);
109+
}
110+
111+
return target.image();
112+
}
113+
114+
// EM-Like algorithm (see "PatchMatch" - page 6).
115+
// Returns a double sized target image (unless level = 0).
116+
MaskedImage Inpainting::_expectation_maximization(MaskedImage source, MaskedImage target, int level, int nr_level, bool verbose) {
117+
const int nr_iters_em = 1 + 2 * level;
118+
const int nr_iters_nnf = static_cast<int>(std::min(7, 1 + level));
119+
const int patch_size = m_distance_metric->patch_size();
120+
121+
MaskedImage new_source, new_target;
122+
123+
unsigned long distance_before_1 = 0, distance_before_2 = 0;
124+
125+
for (int iter_em = 0; iter_em < nr_iters_em; ++iter_em) {
126+
if (iter_em != 0) {
127+
m_source2target.set_target(new_target);
128+
m_target2source.set_source(new_target);
129+
target = new_target;
130+
}
131+
132+
if (verbose) std::cerr << "EM Iteration: " << iter_em << std::endl;
133+
134+
auto size = source.size();
135+
for (int i = 0; i < size.height; ++i) {
136+
for (int j = 0; j < size.width; ++j) {
137+
if (!source.contains_mask(i, j, patch_size)) {
138+
m_source2target.set_identity(i, j);
139+
m_target2source.set_identity(i, j);
140+
}
141+
}
142+
}
143+
if (verbose) std::cerr << " NNF minimization started." << std::endl;
144+
bool can_skip = true;
145+
//if (nr_level - level < 3)
146+
// can_skip = false;
147+
bool break_loop = false;
148+
unsigned long distance_1 = m_source2target.minimize(nr_iters_nnf, true, can_skip);
149+
unsigned long distance_2 = m_target2source.minimize(nr_iters_nnf, false, can_skip);
150+
if (verbose) std::cerr << " NNF minimization finished." << std::endl;
151+
152+
153+
if (distance_1 == 0 && distance_2 == 0 && level != 0 && iter_em != nr_iters_em - 1) {
154+
break_loop = true;
155+
//iter_em = nr_iters_em - 2;
156+
//if (iter_em < 0)
157+
// iter_em = 0;
158+
}
159+
if (distance_1 > 0 && distance_2 > 0 && level != 0 && iter_em != nr_iters_em - 1) {
160+
double inc1 = double(distance_before_1) / distance_1;
161+
double inc2 = double(distance_before_2) / distance_2;
162+
//std::cerr << inc1 << " " << inc2 << std::endl;
163+
if (distance_before_1 != 0 && distance_before_1 != 0 && inc1 < 1.0001 && inc2 < 1.0001) {
164+
break_loop = true;
165+
//iter_em = nr_iters_em - 2;
166+
//if (iter_em < 0)
167+
// iter_em = 0;
168+
}
169+
}
170+
171+
// Instead of upsizing the final target, we build the last target from the next level source image.
172+
// Thus, the final target is less blurry (see "Space-Time Video Completion" - page 5).
173+
bool upscaled = false;
174+
if ((level >= 1 && iter_em == nr_iters_em - 1) || break_loop) {
175+
new_source = m_pyramid[level - 1];
176+
new_target = target.upsample(new_source.size().width, new_source.size().height, m_pyramid[level - 1].global_mask());
177+
upscaled = true;
178+
}
179+
else {
180+
new_source = m_pyramid[level];
181+
new_target = target.clone();
182+
}
183+
184+
auto vote = cv::Mat(new_target.size(), CV_64FC4);
185+
vote.setTo(cv::Scalar::all(0));
186+
187+
// Votes for best patch from NNF Source->Target (completeness) and Target->Source (coherence).
188+
_expectation_step(m_source2target, 1, vote, new_source, upscaled);
189+
if (verbose) std::cerr << " Expectation source to target finished." << std::endl;
190+
_expectation_step(m_target2source, 0, vote, new_source, upscaled);
191+
if (verbose) std::cerr << " Expectation target to source finished." << std::endl;
192+
193+
// Compile votes and update pixel values.
194+
_maximization_step(new_target, vote);
195+
if (verbose) std::cerr << " Minimization step finished." << std::endl;
196+
197+
198+
distance_before_1 = distance_1;
199+
distance_before_2 = distance_2;
200+
if (break_loop)
201+
//std::cerr << "break loop" << std::endl;
202+
break;
203+
}
204+
205+
return new_target;
206+
}
207+
208+
// Expectation step: vote for best estimations of each pixel.
209+
void Inpainting::_expectation_step(
210+
const NearestNeighborField& nnf, bool source2target,
211+
cv::Mat& vote, const MaskedImage& source, bool upscaled
212+
) {
213+
auto source_size = nnf.source_size();
214+
auto target_size = nnf.target_size();
215+
const int patch_size = m_distance_metric->patch_size();
216+
217+
for (int i = 0; i < source_size.height; ++i) {
218+
for (int j = 0; j < source_size.width; ++j) {
219+
if (nnf.source().is_globally_masked(i, j)) continue;
220+
if (source2target) {
221+
if (!nnf.source().is_masked(i, j)) continue;
222+
}
223+
else {
224+
if (nnf.source().is_masked(i, j)) continue;
225+
}
226+
227+
int yp = nnf.at(i, j, 0), xp = nnf.at(i, j, 1), dp = nnf.at(i, j, 2);
228+
double w = kDistance2Similarity[dp];
229+
230+
for (int di = -patch_size; di <= patch_size; ++di) {
231+
for (int dj = -patch_size; dj <= patch_size; ++dj) {
232+
int ys = i + di, xs = j + dj, yt = yp + di, xt = xp + dj;
233+
if (!(ys >= 0 && ys < source_size.height && xs >= 0 && xs < source_size.width)) continue;
234+
if (nnf.source().is_globally_masked(ys, xs)) continue;
235+
if (!(yt >= 0 && yt < target_size.height && xt >= 0 && xt < target_size.width)) continue;
236+
if (nnf.target().is_globally_masked(yt, xt)) continue;
237+
238+
if (!source2target) {
239+
std::swap(ys, yt);
240+
std::swap(xs, xt);
241+
}
242+
243+
if (upscaled) {
244+
for (int uy = 0; uy < 2; ++uy) {
245+
for (int ux = 0; ux < 2; ++ux) {
246+
_weighted_copy(source, 2 * ys + uy, 2 * xs + ux, vote, 2 * yt + uy, 2 * xt + ux, w);
247+
}
248+
}
249+
}
250+
else {
251+
_weighted_copy(source, ys, xs, vote, yt, xt, w);
252+
}
253+
}
254+
}
255+
}
256+
}
257+
}
258+
259+
// Maximization Step: maximum likelihood of target pixel.
260+
void Inpainting::_maximization_step(MaskedImage& target, const cv::Mat& vote) {
261+
auto target_size = target.size();
262+
for (int i = 0; i < target_size.height; ++i) {
263+
for (int j = 0; j < target_size.width; ++j) {
264+
const double* source_ptr = vote.ptr<double>(i, j);
265+
unsigned char* target_ptr = target.get_mutable_image(i, j);
266+
267+
if (target.is_globally_masked(i, j)) {
268+
continue;
269+
}
270+
271+
if (source_ptr[3] > 0) {
272+
unsigned char r = cv::saturate_cast<unsigned char>(source_ptr[0] / source_ptr[3]);
273+
unsigned char g = cv::saturate_cast<unsigned char>(source_ptr[1] / source_ptr[3]);
274+
unsigned char b = cv::saturate_cast<unsigned char>(source_ptr[2] / source_ptr[3]);
275+
target_ptr[0] = r, target_ptr[1] = g, target_ptr[2] = b;
276+
}
277+
else {
278+
target.set_mask(i, j, 0);
279+
}
280+
}
281+
}
282+
}
283+

inpaint.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#pragma once
2+
3+
#include <vector>
4+
5+
#include "masked_image.h"
6+
#include "nnf.h"
7+
8+
class Inpainting {
9+
public:
10+
Inpainting(cv::Mat image, cv::Mat mask, const PatchDistanceMetric* metric);
11+
Inpainting(cv::Mat image, cv::Mat mask, cv::Mat global_mask, const PatchDistanceMetric* metric);
12+
cv::Mat run(bool verbose = false, bool verbose_visualize = false, unsigned int random_seed = 1212);
13+
14+
private:
15+
void _initialize_pyramid(void);
16+
MaskedImage _expectation_maximization(MaskedImage source, MaskedImage target, int level, int nr_level, bool verbose);
17+
void _expectation_step(const NearestNeighborField& nnf, bool source2target, cv::Mat& vote, const MaskedImage& source, bool upscaled);
18+
void _maximization_step(MaskedImage& target, const cv::Mat& vote);
19+
20+
MaskedImage m_initial;
21+
std::vector<MaskedImage> m_pyramid;
22+
23+
NearestNeighborField m_source2target;
24+
NearestNeighborField m_target2source;
25+
const PatchDistanceMetric* m_distance_metric;
26+
};
27+
28+

0 commit comments

Comments
 (0)