blob: 39ec70ee82e060d8ca8bee77ec00afe6a378fc25 [file] [log] [blame]
Yash Chainani5458dea2022-06-29 21:05:02 -07001//=============================================================================
2//
3// nldiffusion_functions.cpp
4// Author: Pablo F. Alcantarilla
5// Institution: University d'Auvergne
6// Address: Clermont Ferrand, France
7// Date: 27/12/2011
8// Email: pablofdezalc@gmail.com
9//
10// KAZE Features Copyright 2012, Pablo F. Alcantarilla
11// All Rights Reserved
12// See LICENSE for the license information
13//=============================================================================
14
15/**
16 * @file nldiffusion_functions.cpp
17 * @brief Functions for non-linear diffusion applications:
18 * 2D Gaussian Derivatives
19 * Perona and Malik conductivity equations
20 * Perona and Malik evolution
21 * @date Dec 27, 2011
22 * @author Pablo F. Alcantarilla
23 */
24
25#include "nldiffusion_functions.h"
26
27#include <cstdint>
28#include <cstring>
29#include <iostream>
30#include <opencv2/core.hpp>
31#include <opencv2/imgproc.hpp>
32
33// Namespaces
34
35/* ************************************************************************* */
36
37namespace cv {
38using namespace std;
39
40/* ************************************************************************* */
41/**
42 * @brief This function smoothes an image with a Gaussian kernel
43 * @param src Input image
44 * @param dst Output image
45 * @param ksize_x Kernel size in X-direction (horizontal)
46 * @param ksize_y Kernel size in Y-direction (vertical)
47 * @param sigma Kernel standard deviation
48 */
49void gaussian_2D_convolutionV2(const cv::Mat& src, cv::Mat& dst, int ksize_x,
50 int ksize_y, float sigma) {
51 int ksize_x_ = 0, ksize_y_ = 0;
52
53 // Compute an appropriate kernel size according to the specified sigma
54 if (sigma > ksize_x || sigma > ksize_y || ksize_x == 0 || ksize_y == 0) {
55 ksize_x_ = (int)ceil(2.0f * (1.0f + (sigma - 0.8f) / (0.3f)));
56 ksize_y_ = ksize_x_;
57 }
58
59 // The kernel size must be and odd number
60 if ((ksize_x_ % 2) == 0) {
61 ksize_x_ += 1;
62 }
63
64 if ((ksize_y_ % 2) == 0) {
65 ksize_y_ += 1;
66 }
67
68 // Perform the Gaussian Smoothing with border replication
69 GaussianBlur(src, dst, Size(ksize_x_, ksize_y_), sigma, sigma,
70 BORDER_REPLICATE);
71}
72
73/* ************************************************************************* */
74/**
75 * @brief This function computes image derivatives with Scharr kernel
76 * @param src Input image
77 * @param dst Output image
78 * @param xorder Derivative order in X-direction (horizontal)
79 * @param yorder Derivative order in Y-direction (vertical)
80 * @note Scharr operator approximates better rotation invariance than
81 * other stencils such as Sobel. See Weickert and Scharr,
82 * A Scheme for Coherence-Enhancing Diffusion Filtering with Optimized Rotation
83 * Invariance, Journal of Visual Communication and Image Representation 2002
84 */
85void image_derivatives_scharrV2(const cv::Mat& src, cv::Mat& dst, int xorder,
86 int yorder) {
87 Scharr(src, dst, CV_32F, xorder, yorder, 1.0, 0, BORDER_DEFAULT);
88}
89
90/* ************************************************************************* */
91/**
92 * @brief This function computes the Perona and Malik conductivity coefficient
93 * g1 g1 = exp(-|dL|^2/k^2)
94 * @param Lx First order image derivative in X-direction (horizontal)
95 * @param Ly First order image derivative in Y-direction (vertical)
96 * @param dst Output image
97 * @param k Contrast factor parameter
98 */
99void pm_g1V2(const cv::Mat& Lx, const cv::Mat& Ly, cv::Mat& dst, float k) {
100 // Compute: dst = exp((Lx.mul(Lx) + Ly.mul(Ly)) / (-k * k))
101
102 const float neg_inv_k2 = -1.0f / (k * k);
103
104 const int total = Lx.rows * Lx.cols;
105 const float* lx = Lx.ptr<float>(0);
106 const float* ly = Ly.ptr<float>(0);
107 float* d = dst.ptr<float>(0);
108
109 for (int i = 0; i < total; i++)
110 d[i] = neg_inv_k2 * (lx[i] * lx[i] + ly[i] * ly[i]);
111
112 exp(dst, dst);
113}
114
115/* ************************************************************************* */
116/**
117 * @brief This function computes the Perona and Malik conductivity coefficient
118 * g2 g2 = 1 / (1 + dL^2 / k^2)
119 * @param Lx First order image derivative in X-direction (horizontal)
120 * @param Ly First order image derivative in Y-direction (vertical)
121 * @param dst Output image
122 * @param k Contrast factor parameter
123 */
124void pm_g2V2(const cv::Mat& Lx, const cv::Mat& Ly, cv::Mat& dst, float k) {
125 // Compute: dst = 1.0f / (1.0f + ((Lx.mul(Lx) + Ly.mul(Ly)) / (k * k)) );
126
127 const float inv_k2 = 1.0f / (k * k);
128
129 const int total = Lx.rows * Lx.cols;
130 const float* lx = Lx.ptr<float>(0);
131 const float* ly = Ly.ptr<float>(0);
132 float* d = dst.ptr<float>(0);
133
134 for (int i = 0; i < total; i++)
135 d[i] = 1.0f / (1.0f + ((lx[i] * lx[i] + ly[i] * ly[i]) * inv_k2));
136}
137
138/* ************************************************************************* */
139/**
140 * @brief This function computes Weickert conductivity coefficient gw
141 * @param Lx First order image derivative in X-direction (horizontal)
142 * @param Ly First order image derivative in Y-direction (vertical)
143 * @param dst Output image
144 * @param k Contrast factor parameter
145 * @note For more information check the following paper: J. Weickert
146 * Applications of nonlinear diffusion in image processing and computer vision,
147 * Proceedings of Algorithmy 2000
148 */
149void weickert_diffusivityV2(const cv::Mat& Lx, const cv::Mat& Ly, cv::Mat& dst,
150 float k) {
151 // Compute: dst = 1.0f - exp(-3.315f / ((Lx.mul(Lx) + Ly.mul(Ly)) / (k *
152 // k))^4)
153
154 const float inv_k2 = 1.0f / (k * k);
155
156 const int total = Lx.rows * Lx.cols;
157 const float* lx = Lx.ptr<float>(0);
158 const float* ly = Ly.ptr<float>(0);
159 float* d = dst.ptr<float>(0);
160
161 for (int i = 0; i < total; i++) {
162 float dL = inv_k2 * (lx[i] * lx[i] + ly[i] * ly[i]);
163 d[i] = -3.315f / (dL * dL * dL * dL);
164 }
165
166 exp(dst, dst);
167
168 for (int i = 0; i < total; i++) d[i] = 1.0f - d[i];
169}
170
171/* ************************************************************************* */
172/**
173 * @brief This function computes Charbonnier conductivity coefficient gc
174 * gc = 1 / sqrt(1 + dL^2 / k^2)
175 * @param Lx First order image derivative in X-direction (horizontal)
176 * @param Ly First order image derivative in Y-direction (vertical)
177 * @param dst Output image
178 * @param k Contrast factor parameter
179 * @note For more information check the following paper: J. Weickert
180 * Applications of nonlinear diffusion in image processing and computer vision,
181 * Proceedings of Algorithmy 2000
182 */
183void charbonnier_diffusivityV2(const cv::Mat& Lx, const cv::Mat& Ly,
184 cv::Mat& dst, float k) {
185 // Compute: dst = 1.0f / sqrt(1.0f + (Lx.mul(Lx) + Ly.mul(Ly)) / (k * k))
186
187 const float inv_k2 = 1.0f / (k * k);
188
189 const int total = Lx.rows * Lx.cols;
190 const float* lx = Lx.ptr<float>(0);
191 const float* ly = Ly.ptr<float>(0);
192 float* d = dst.ptr<float>(0);
193
194 for (int i = 0; i < total; i++)
195 d[i] = 1.0f / sqrtf(1.0f + inv_k2 * (lx[i] * lx[i] + ly[i] * ly[i]));
196}
197
198/* ************************************************************************* */
199/**
200 * @brief This function computes a good empirical value for the k contrast
201 * factor given two gradient images, the percentile (0-1), the temporal storage
202 * to hold gradient norms and the histogram bins
203 * @param Lx Horizontal gradient of the input image
204 * @param Ly Vertical gradient of the input image
205 * @param perc Percentile of the image gradient histogram (0-1)
206 * @param modgs Temporal vector to hold the gradient norms
207 * @param histogram Temporal vector to hold the gradient histogram
208 * @return k contrast factor
209 */
210float compute_k_percentileV2(const cv::Mat& Lx, const cv::Mat& Ly, float perc,
211 cv::Mat& modgs, cv::Mat& histogram) {
212 const int total = modgs.cols;
213 const int nbins = histogram.cols;
214
215 CV_DbgAssert(total == (Lx.rows - 2) * (Lx.cols - 2));
216 CV_DbgAssert(nbins > 2);
217
218 float* modg = modgs.ptr<float>(0);
219 int32_t* hist = histogram.ptr<int32_t>(0);
220
221 for (int i = 1; i < Lx.rows - 1; i++) {
222 const float* lx = Lx.ptr<float>(i) + 1;
223 const float* ly = Ly.ptr<float>(i) + 1;
224 const int cols = Lx.cols - 2;
225
226 for (int j = 0; j < cols; j++)
227 *modg++ = sqrtf(lx[j] * lx[j] + ly[j] * ly[j]);
228 }
229 modg = modgs.ptr<float>(0);
230
231 // Get the maximum
232 float hmax = 0.0f;
233 for (int i = 0; i < total; i++)
234 if (hmax < modg[i]) hmax = modg[i];
235
236 if (hmax == 0.0f) return 0.03f; // e.g. a blank image
237
238 // Compute the bin numbers: the value range [0, hmax] -> [0, nbins-1]
239 for (int i = 0; i < total; i++) modg[i] *= (nbins - 1) / hmax;
240
241 // Count up
242 std::memset(hist, 0, sizeof(int32_t) * nbins);
243 for (int i = 0; i < total; i++) hist[(int)modg[i]]++;
244
245 // Now find the perc of the histogram percentile
246 const int nthreshold =
247 (int)((total - hist[0]) * perc); // Exclude hist[0] as background
248 int nelements = 0;
249 for (int k = 1; k < nbins; k++) {
250 if (nelements >= nthreshold) return (float)hmax * k / nbins;
251
252 nelements = nelements + hist[k];
253 }
254
255 return 0.03f;
256}
257
258/* ************************************************************************* */
259/**
260 * @brief Compute Scharr derivative kernels for sizes different than 3
261 * @param _kx Horizontal kernel ues
262 * @param _ky Vertical kernel values
263 * @param dx Derivative order in X-direction (horizontal)
264 * @param dy Derivative order in Y-direction (vertical)
265 * @param scale_ Scale factor or derivative size
266 */
267void compute_scharr_derivative_kernelsV2(cv::OutputArray _kx,
268 cv::OutputArray _ky, int dx, int dy,
269 int scale) {
270 int ksize = 3 + 2 * (scale - 1);
271
272 // The standard Scharr kernel
273 if (scale == 1) {
274 getDerivKernels(_kx, _ky, dx, dy, FILTER_SCHARR, true, CV_32F);
275 return;
276 }
277
278 _kx.create(ksize, 1, CV_32F, -1, true);
279 _ky.create(ksize, 1, CV_32F, -1, true);
280 Mat kx = _kx.getMat();
281 Mat ky = _ky.getMat();
282
283 float w = 10.0f / 3.0f;
284 float norm = 1.0f / (2.0f * (w + 2.0f));
285
286 std::vector<float> kerI(ksize, 0.0f);
287
288 if (dx == 0) {
289 kerI[0] = norm, kerI[ksize / 2] = w * norm, kerI[ksize - 1] = norm;
290 } else if (dx == 1) {
291 kerI[0] = -1, kerI[ksize / 2] = 0, kerI[ksize - 1] = 1;
292 }
293 Mat(kx.rows, kx.cols, CV_32F, &kerI[0]).copyTo(kx);
294
295 kerI.assign(ksize, 0.0f);
296
297 if (dy == 0) {
298 kerI[0] = norm, kerI[ksize / 2] = w * norm, kerI[ksize - 1] = norm;
299 } else if (dy == 1) {
300 kerI[0] = -1, kerI[ksize / 2] = 0, kerI[ksize - 1] = 1;
301 }
302 Mat(ky.rows, ky.cols, CV_32F, &kerI[0]).copyTo(ky);
303}
304
305inline void nld_step_scalar_one_lane(const cv::Mat& Lt, const cv::Mat& Lf,
306 cv::Mat& Lstep, int idx, int skip) {
307 /* The labeling scheme for this five star stencil:
308 [ a ]
309 [ -1 c +1 ]
310 [ b ]
311 */
312
313 const int cols = Lt.cols - 2;
314 int row = idx;
315
316 const float *lt_a, *lt_c, *lt_b;
317 const float *lf_a, *lf_c, *lf_b;
318 float* dst;
319
320 // Process the top row
321 if (row == 0) {
322 lt_c = Lt.ptr<float>(0) + 1; /* Skip the left-most column by +1 */
323 lf_c = Lf.ptr<float>(0) + 1;
324 lt_b = Lt.ptr<float>(1) + 1;
325 lf_b = Lf.ptr<float>(1) + 1;
326 dst = Lstep.ptr<float>(0) + 1;
327
328 for (int j = 0; j < cols; j++) {
329 dst[j] = (lf_c[j] + lf_c[j + 1]) * (lt_c[j + 1] - lt_c[j]) +
330 (lf_c[j] + lf_c[j - 1]) * (lt_c[j - 1] - lt_c[j]) +
331 (lf_c[j] + lf_b[j]) * (lt_b[j] - lt_c[j]);
332 }
333 row += skip;
334 }
335
336 // Process the middle rows
337 for (; row < Lt.rows - 1; row += skip) {
338 lt_a = Lt.ptr<float>(row - 1);
339 lf_a = Lf.ptr<float>(row - 1);
340 lt_c = Lt.ptr<float>(row);
341 lf_c = Lf.ptr<float>(row);
342 lt_b = Lt.ptr<float>(row + 1);
343 lf_b = Lf.ptr<float>(row + 1);
344 dst = Lstep.ptr<float>(row);
345
346 // The left-most column
347 dst[0] = (lf_c[0] + lf_c[1]) * (lt_c[1] - lt_c[0]) +
348 (lf_c[0] + lf_b[0]) * (lt_b[0] - lt_c[0]) +
349 (lf_c[0] + lf_a[0]) * (lt_a[0] - lt_c[0]);
350
351 lt_a++;
352 lt_c++;
353 lt_b++;
354 lf_a++;
355 lf_c++;
356 lf_b++;
357 dst++;
358
359 // The middle columns
360 for (int j = 0; j < cols; j++) {
361 dst[j] = (lf_c[j] + lf_c[j + 1]) * (lt_c[j + 1] - lt_c[j]) +
362 (lf_c[j] + lf_c[j - 1]) * (lt_c[j - 1] - lt_c[j]) +
363 (lf_c[j] + lf_b[j]) * (lt_b[j] - lt_c[j]) +
364 (lf_c[j] + lf_a[j]) * (lt_a[j] - lt_c[j]);
365 }
366
367 // The right-most column
368 dst[cols] = (lf_c[cols] + lf_c[cols - 1]) * (lt_c[cols - 1] - lt_c[cols]) +
369 (lf_c[cols] + lf_b[cols]) * (lt_b[cols] - lt_c[cols]) +
370 (lf_c[cols] + lf_a[cols]) * (lt_a[cols] - lt_c[cols]);
371 }
372
373 // Process the bottom row
374 if (row == Lt.rows - 1) {
375 lt_a = Lt.ptr<float>(row - 1) + 1; /* Skip the left-most column by +1 */
376 lf_a = Lf.ptr<float>(row - 1) + 1;
377 lt_c = Lt.ptr<float>(row) + 1;
378 lf_c = Lf.ptr<float>(row) + 1;
379 dst = Lstep.ptr<float>(row) + 1;
380
381 for (int j = 0; j < cols; j++) {
382 dst[j] = (lf_c[j] + lf_c[j + 1]) * (lt_c[j + 1] - lt_c[j]) +
383 (lf_c[j] + lf_c[j - 1]) * (lt_c[j - 1] - lt_c[j]) +
384 (lf_c[j] + lf_a[j]) * (lt_a[j] - lt_c[j]);
385 }
386 }
387}
388
389/* ************************************************************************* */
390/**
391 * @brief This function computes a scalar non-linear diffusion step
392 * @param Ld Base image in the evolution
393 * @param c Conductivity image
394 * @param Lstep Output image that gives the difference between the current
395 * Ld and the next Ld being evolved
396 * @note Forward Euler Scheme 3x3 stencil
397 * The function c is a scalar value that depends on the gradient norm
398 * dL_by_ds = d(c dL_by_dx)_by_dx + d(c dL_by_dy)_by_dy
399 */
400void nld_step_scalarV2(const cv::Mat& Ld, const cv::Mat& c, cv::Mat& Lstep) {
401 nld_step_scalar_one_lane(Ld, c, Lstep, 0, 1);
402}
403
404/* ************************************************************************* */
405/**
406 * @brief This function downsamples the input image using OpenCV resize
407 * @param src Input image to be downsampled
408 * @param dst Output image with half of the resolution of the input image
409 */
410void halfsample_imageV2(const cv::Mat& src, cv::Mat& dst) {
411 // Make sure the destination image is of the right size
412 CV_Assert(src.cols / 2 == dst.cols);
413 CV_Assert(src.rows / 2 == dst.rows);
414 resize(src, dst, dst.size(), 0, 0, cv::INTER_AREA);
415}
416
417/* ************************************************************************* */
418/**
419 * @brief This function checks if a given pixel is a maximum in a local
420 * neighbourhood
421 * @param img Input image where we will perform the maximum search
422 * @param dsize Half size of the neighbourhood
423 * @param value Response value at (x,y) position
424 * @param row Image row coordinate
425 * @param col Image column coordinate
426 * @param same_img Flag to indicate if the image value at (x,y) is in the input
427 * image
428 * @return 1->is maximum, 0->otherwise
429 */
430bool check_maximum_neighbourhoodV2(const cv::Mat& img, int dsize, float value,
431 int row, int col, bool same_img) {
432 bool response = true;
433
434 for (int i = row - dsize; i <= row + dsize; i++) {
435 for (int j = col - dsize; j <= col + dsize; j++) {
436 if (i >= 0 && i < img.rows && j >= 0 && j < img.cols) {
437 if (same_img == true) {
438 if (i != row || j != col) {
439 if ((*(img.ptr<float>(i) + j)) > value) {
440 response = false;
441 return response;
442 }
443 }
444 } else {
445 if ((*(img.ptr<float>(i) + j)) > value) {
446 response = false;
447 return response;
448 }
449 }
450 }
451 }
452 }
453
454 return response;
455}
456
457} // namespace cv