|
| 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 | + |
0 commit comments