1 //============================================================================= 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 (at) 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 "../precomp.hpp" 26 #include "nldiffusion_functions.h" 27 #include <iostream> 28 29 // Namespaces 30 31 /* ************************************************************************* */ 32 33 namespace cv 34 { 35 using namespace std; 36 37 /* ************************************************************************* */ 38 /** 39 * @brief This function smoothes an image with a Gaussian kernel 40 * @param src Input image 41 * @param dst Output image 42 * @param ksize_x Kernel size in X-direction (horizontal) 43 * @param ksize_y Kernel size in Y-direction (vertical) 44 * @param sigma Kernel standard deviation 45 */ 46 void gaussian_2D_convolution(const cv::Mat& src, cv::Mat& dst, int ksize_x, int ksize_y, float sigma) { 47 48 int ksize_x_ = 0, ksize_y_ = 0; 49 50 // Compute an appropriate kernel size according to the specified sigma 51 if (sigma > ksize_x || sigma > ksize_y || ksize_x == 0 || ksize_y == 0) { 52 ksize_x_ = (int)ceil(2.0f*(1.0f + (sigma - 0.8f) / (0.3f))); 53 ksize_y_ = ksize_x_; 54 } 55 56 // The kernel size must be and odd number 57 if ((ksize_x_ % 2) == 0) { 58 ksize_x_ += 1; 59 } 60 61 if ((ksize_y_ % 2) == 0) { 62 ksize_y_ += 1; 63 } 64 65 // Perform the Gaussian Smoothing with border replication 66 GaussianBlur(src, dst, Size(ksize_x_, ksize_y_), sigma, sigma, BORDER_REPLICATE); 67 } 68 69 /* ************************************************************************* */ 70 /** 71 * @brief This function computes image derivatives with Scharr kernel 72 * @param src Input image 73 * @param dst Output image 74 * @param xorder Derivative order in X-direction (horizontal) 75 * @param yorder Derivative order in Y-direction (vertical) 76 * @note Scharr operator approximates better rotation invariance than 77 * other stencils such as Sobel. See Weickert and Scharr, 78 * A Scheme for Coherence-Enhancing Diffusion Filtering with Optimized Rotation Invariance, 79 * Journal of Visual Communication and Image Representation 2002 80 */ 81 void image_derivatives_scharr(const cv::Mat& src, cv::Mat& dst, int xorder, int yorder) { 82 Scharr(src, dst, CV_32F, xorder, yorder, 1.0, 0, BORDER_DEFAULT); 83 } 84 85 /* ************************************************************************* */ 86 /** 87 * @brief This function computes the Perona and Malik conductivity coefficient g1 88 * g1 = exp(-|dL|^2/k^2) 89 * @param Lx First order image derivative in X-direction (horizontal) 90 * @param Ly First order image derivative in Y-direction (vertical) 91 * @param dst Output image 92 * @param k Contrast factor parameter 93 */ 94 void pm_g1(const cv::Mat& Lx, const cv::Mat& Ly, cv::Mat& dst, float k) { 95 96 Size sz = Lx.size(); 97 float inv_k = 1.0f / (k*k); 98 for (int y = 0; y < sz.height; y++) { 99 100 const float* Lx_row = Lx.ptr<float>(y); 101 const float* Ly_row = Ly.ptr<float>(y); 102 float* dst_row = dst.ptr<float>(y); 103 104 for (int x = 0; x < sz.width; x++) { 105 dst_row[x] = (-inv_k*(Lx_row[x]*Lx_row[x] + Ly_row[x]*Ly_row[x])); 106 } 107 } 108 109 exp(dst, dst); 110 } 111 112 /* ************************************************************************* */ 113 /** 114 * @brief This function computes the Perona and Malik conductivity coefficient g2 115 * g2 = 1 / (1 + dL^2 / k^2) 116 * @param Lx First order image derivative in X-direction (horizontal) 117 * @param Ly First order image derivative in Y-direction (vertical) 118 * @param dst Output image 119 * @param k Contrast factor parameter 120 */ 121 void pm_g2(const cv::Mat &Lx, const cv::Mat& Ly, cv::Mat& dst, float k) { 122 123 Size sz = Lx.size(); 124 dst.create(sz, Lx.type()); 125 float k2inv = 1.0f / (k * k); 126 127 for(int y = 0; y < sz.height; y++) { 128 const float *Lx_row = Lx.ptr<float>(y); 129 const float *Ly_row = Ly.ptr<float>(y); 130 float* dst_row = dst.ptr<float>(y); 131 for(int x = 0; x < sz.width; x++) { 132 dst_row[x] = 1.0f / (1.0f + ((Lx_row[x] * Lx_row[x] + Ly_row[x] * Ly_row[x]) * k2inv)); 133 } 134 } 135 } 136 /* ************************************************************************* */ 137 /** 138 * @brief This function computes Weickert conductivity coefficient gw 139 * @param Lx First order image derivative in X-direction (horizontal) 140 * @param Ly First order image derivative in Y-direction (vertical) 141 * @param dst Output image 142 * @param k Contrast factor parameter 143 * @note For more information check the following paper: J. Weickert 144 * Applications of nonlinear diffusion in image processing and computer vision, 145 * Proceedings of Algorithmy 2000 146 */ 147 void weickert_diffusivity(const cv::Mat& Lx, const cv::Mat& Ly, cv::Mat& dst, float k) { 148 149 Size sz = Lx.size(); 150 float inv_k = 1.0f / (k*k); 151 for (int y = 0; y < sz.height; y++) { 152 153 const float* Lx_row = Lx.ptr<float>(y); 154 const float* Ly_row = Ly.ptr<float>(y); 155 float* dst_row = dst.ptr<float>(y); 156 157 for (int x = 0; x < sz.width; x++) { 158 float dL = inv_k*(Lx_row[x]*Lx_row[x] + Ly_row[x]*Ly_row[x]); 159 dst_row[x] = -3.315f/(dL*dL*dL*dL); 160 } 161 } 162 163 exp(dst, dst); 164 dst = 1.0 - dst; 165 } 166 167 168 /* ************************************************************************* */ 169 /** 170 * @brief This function computes Charbonnier conductivity coefficient gc 171 * gc = 1 / sqrt(1 + dL^2 / k^2) 172 * @param Lx First order image derivative in X-direction (horizontal) 173 * @param Ly First order image derivative in Y-direction (vertical) 174 * @param dst Output image 175 * @param k Contrast factor parameter 176 * @note For more information check the following paper: J. Weickert 177 * Applications of nonlinear diffusion in image processing and computer vision, 178 * Proceedings of Algorithmy 2000 179 */ 180 void charbonnier_diffusivity(const cv::Mat& Lx, const cv::Mat& Ly, cv::Mat& dst, float k) { 181 182 Size sz = Lx.size(); 183 float inv_k = 1.0f / (k*k); 184 for (int y = 0; y < sz.height; y++) { 185 186 const float* Lx_row = Lx.ptr<float>(y); 187 const float* Ly_row = Ly.ptr<float>(y); 188 float* dst_row = dst.ptr<float>(y); 189 190 for (int x = 0; x < sz.width; x++) { 191 float den = sqrt(1.0f+inv_k*(Lx_row[x]*Lx_row[x] + Ly_row[x]*Ly_row[x])); 192 dst_row[x] = 1.0f / den; 193 } 194 } 195 } 196 197 198 /* ************************************************************************* */ 199 /** 200 * @brief This function computes a good empirical value for the k contrast factor 201 * given an input image, the percentile (0-1), the gradient scale and the number of 202 * bins in the histogram 203 * @param img Input image 204 * @param perc Percentile of the image gradient histogram (0-1) 205 * @param gscale Scale for computing the image gradient histogram 206 * @param nbins Number of histogram bins 207 * @param ksize_x Kernel size in X-direction (horizontal) for the Gaussian smoothing kernel 208 * @param ksize_y Kernel size in Y-direction (vertical) for the Gaussian smoothing kernel 209 * @return k contrast factor 210 */ 211 float compute_k_percentile(const cv::Mat& img, float perc, float gscale, int nbins, int ksize_x, int ksize_y) { 212 213 int nbin = 0, nelements = 0, nthreshold = 0, k = 0; 214 float kperc = 0.0, modg = 0.0; 215 float npoints = 0.0; 216 float hmax = 0.0; 217 218 // Create the array for the histogram 219 std::vector<int> hist(nbins, 0); 220 221 // Create the matrices 222 Mat gaussian = Mat::zeros(img.rows, img.cols, CV_32F); 223 Mat Lx = Mat::zeros(img.rows, img.cols, CV_32F); 224 Mat Ly = Mat::zeros(img.rows, img.cols, CV_32F); 225 226 // Perform the Gaussian convolution 227 gaussian_2D_convolution(img, gaussian, ksize_x, ksize_y, gscale); 228 229 // Compute the Gaussian derivatives Lx and Ly 230 Scharr(gaussian, Lx, CV_32F, 1, 0, 1, 0, cv::BORDER_DEFAULT); 231 Scharr(gaussian, Ly, CV_32F, 0, 1, 1, 0, cv::BORDER_DEFAULT); 232 233 // Skip the borders for computing the histogram 234 for (int i = 1; i < gaussian.rows - 1; i++) { 235 const float *lx = Lx.ptr<float>(i); 236 const float *ly = Ly.ptr<float>(i); 237 for (int j = 1; j < gaussian.cols - 1; j++) { 238 modg = lx[j]*lx[j] + ly[j]*ly[j]; 239 240 // Get the maximum 241 if (modg > hmax) { 242 hmax = modg; 243 } 244 } 245 } 246 hmax = sqrt(hmax); 247 // Skip the borders for computing the histogram 248 for (int i = 1; i < gaussian.rows - 1; i++) { 249 const float *lx = Lx.ptr<float>(i); 250 const float *ly = Ly.ptr<float>(i); 251 for (int j = 1; j < gaussian.cols - 1; j++) { 252 modg = lx[j]*lx[j] + ly[j]*ly[j]; 253 254 // Find the correspondent bin 255 if (modg != 0.0) { 256 nbin = (int)floor(nbins*(sqrt(modg) / hmax)); 257 258 if (nbin == nbins) { 259 nbin--; 260 } 261 262 hist[nbin]++; 263 npoints++; 264 } 265 } 266 } 267 268 // Now find the perc of the histogram percentile 269 nthreshold = (int)(npoints*perc); 270 271 for (k = 0; nelements < nthreshold && k < nbins; k++) { 272 nelements = nelements + hist[k]; 273 } 274 275 if (nelements < nthreshold) { 276 kperc = 0.03f; 277 } 278 else { 279 kperc = hmax*((float)(k) / (float)nbins); 280 } 281 282 return kperc; 283 } 284 285 /* ************************************************************************* */ 286 /** 287 * @brief This function computes Scharr image derivatives 288 * @param src Input image 289 * @param dst Output image 290 * @param xorder Derivative order in X-direction (horizontal) 291 * @param yorder Derivative order in Y-direction (vertical) 292 * @param scale Scale factor for the derivative size 293 */ 294 void compute_scharr_derivatives(const cv::Mat& src, cv::Mat& dst, int xorder, int yorder, int scale) { 295 Mat kx, ky; 296 compute_derivative_kernels(kx, ky, xorder, yorder, scale); 297 sepFilter2D(src, dst, CV_32F, kx, ky); 298 } 299 300 /* ************************************************************************* */ 301 /** 302 * @brief Compute derivative kernels for sizes different than 3 303 * @param _kx Horizontal kernel ues 304 * @param _ky Vertical kernel values 305 * @param dx Derivative order in X-direction (horizontal) 306 * @param dy Derivative order in Y-direction (vertical) 307 * @param scale_ Scale factor or derivative size 308 */ 309 void compute_derivative_kernels(cv::OutputArray _kx, cv::OutputArray _ky, int dx, int dy, int scale) { 310 311 int ksize = 3 + 2 * (scale - 1); 312 313 // The standard Scharr kernel 314 if (scale == 1) { 315 getDerivKernels(_kx, _ky, dx, dy, 0, true, CV_32F); 316 return; 317 } 318 319 _kx.create(ksize, 1, CV_32F, -1, true); 320 _ky.create(ksize, 1, CV_32F, -1, true); 321 Mat kx = _kx.getMat(); 322 Mat ky = _ky.getMat(); 323 324 float w = 10.0f / 3.0f; 325 float norm = 1.0f / (2.0f*scale*(w + 2.0f)); 326 327 for (int k = 0; k < 2; k++) { 328 Mat* kernel = k == 0 ? &kx : &ky; 329 int order = k == 0 ? dx : dy; 330 std::vector<float> kerI(ksize, 0.0f); 331 332 if (order == 0) { 333 kerI[0] = norm, kerI[ksize / 2] = w*norm, kerI[ksize - 1] = norm; 334 } 335 else if (order == 1) { 336 kerI[0] = -1, kerI[ksize / 2] = 0, kerI[ksize - 1] = 1; 337 } 338 339 Mat temp(kernel->rows, kernel->cols, CV_32F, &kerI[0]); 340 temp.copyTo(*kernel); 341 } 342 } 343 344 class Nld_Step_Scalar_Invoker : public cv::ParallelLoopBody 345 { 346 public: 347 Nld_Step_Scalar_Invoker(cv::Mat& Ld, const cv::Mat& c, cv::Mat& Lstep, float _stepsize) 348 : _Ld(&Ld) 349 , _c(&c) 350 , _Lstep(&Lstep) 351 , stepsize(_stepsize) 352 { 353 } 354 355 virtual ~Nld_Step_Scalar_Invoker() 356 { 357 358 } 359 360 void operator()(const cv::Range& range) const 361 { 362 cv::Mat& Ld = *_Ld; 363 const cv::Mat& c = *_c; 364 cv::Mat& Lstep = *_Lstep; 365 366 for (int i = range.start; i < range.end; i++) 367 { 368 const float *c_prev = c.ptr<float>(i - 1); 369 const float *c_curr = c.ptr<float>(i); 370 const float *c_next = c.ptr<float>(i + 1); 371 const float *ld_prev = Ld.ptr<float>(i - 1); 372 const float *ld_curr = Ld.ptr<float>(i); 373 const float *ld_next = Ld.ptr<float>(i + 1); 374 375 float *dst = Lstep.ptr<float>(i); 376 377 for (int j = 1; j < Lstep.cols - 1; j++) 378 { 379 float xpos = (c_curr[j] + c_curr[j+1])*(ld_curr[j+1] - ld_curr[j]); 380 float xneg = (c_curr[j-1] + c_curr[j]) *(ld_curr[j] - ld_curr[j-1]); 381 float ypos = (c_curr[j] + c_next[j]) *(ld_next[j] - ld_curr[j]); 382 float yneg = (c_prev[j] + c_curr[j]) *(ld_curr[j] - ld_prev[j]); 383 dst[j] = 0.5f*stepsize*(xpos - xneg + ypos - yneg); 384 } 385 } 386 } 387 private: 388 cv::Mat * _Ld; 389 const cv::Mat * _c; 390 cv::Mat * _Lstep; 391 float stepsize; 392 }; 393 394 /* ************************************************************************* */ 395 /** 396 * @brief This function performs a scalar non-linear diffusion step 397 * @param Ld2 Output image in the evolution 398 * @param c Conductivity image 399 * @param Lstep Previous image in the evolution 400 * @param stepsize The step size in time units 401 * @note Forward Euler Scheme 3x3 stencil 402 * The function c is a scalar value that depends on the gradient norm 403 * dL_by_ds = d(c dL_by_dx)_by_dx + d(c dL_by_dy)_by_dy 404 */ 405 void nld_step_scalar(cv::Mat& Ld, const cv::Mat& c, cv::Mat& Lstep, float stepsize) { 406 407 cv::parallel_for_(cv::Range(1, Lstep.rows - 1), Nld_Step_Scalar_Invoker(Ld, c, Lstep, stepsize), (double)Ld.total()/(1 << 16)); 408 409 float xneg, xpos, yneg, ypos; 410 float* dst = Lstep.ptr<float>(0); 411 const float* cprv = NULL; 412 const float* ccur = c.ptr<float>(0); 413 const float* cnxt = c.ptr<float>(1); 414 const float* ldprv = NULL; 415 const float* ldcur = Ld.ptr<float>(0); 416 const float* ldnxt = Ld.ptr<float>(1); 417 for (int j = 1; j < Lstep.cols - 1; j++) { 418 xpos = (ccur[j] + ccur[j+1]) * (ldcur[j+1] - ldcur[j]); 419 xneg = (ccur[j-1] + ccur[j]) * (ldcur[j] - ldcur[j-1]); 420 ypos = (ccur[j] + cnxt[j]) * (ldnxt[j] - ldcur[j]); 421 dst[j] = 0.5f*stepsize*(xpos - xneg + ypos); 422 } 423 424 dst = Lstep.ptr<float>(Lstep.rows - 1); 425 ccur = c.ptr<float>(Lstep.rows - 1); 426 cprv = c.ptr<float>(Lstep.rows - 2); 427 ldcur = Ld.ptr<float>(Lstep.rows - 1); 428 ldprv = Ld.ptr<float>(Lstep.rows - 2); 429 430 for (int j = 1; j < Lstep.cols - 1; j++) { 431 xpos = (ccur[j] + ccur[j+1]) * (ldcur[j+1] - ldcur[j]); 432 xneg = (ccur[j-1] + ccur[j]) * (ldcur[j] - ldcur[j-1]); 433 yneg = (cprv[j] + ccur[j]) * (ldcur[j] - ldprv[j]); 434 dst[j] = 0.5f*stepsize*(xpos - xneg - yneg); 435 } 436 437 ccur = c.ptr<float>(1); 438 ldcur = Ld.ptr<float>(1); 439 cprv = c.ptr<float>(0); 440 ldprv = Ld.ptr<float>(0); 441 442 int r0 = Lstep.cols - 1; 443 int r1 = Lstep.cols - 2; 444 445 for (int i = 1; i < Lstep.rows - 1; i++) { 446 cnxt = c.ptr<float>(i + 1); 447 ldnxt = Ld.ptr<float>(i + 1); 448 dst = Lstep.ptr<float>(i); 449 450 xpos = (ccur[0] + ccur[1]) * (ldcur[1] - ldcur[0]); 451 ypos = (ccur[0] + cnxt[0]) * (ldnxt[0] - ldcur[0]); 452 yneg = (cprv[0] + ccur[0]) * (ldcur[0] - ldprv[0]); 453 dst[0] = 0.5f*stepsize*(xpos + ypos - yneg); 454 455 xneg = (ccur[r1] + ccur[r0]) * (ldcur[r0] - ldcur[r1]); 456 ypos = (ccur[r0] + cnxt[r0]) * (ldnxt[r0] - ldcur[r0]); 457 yneg = (cprv[r0] + ccur[r0]) * (ldcur[r0] - ldprv[r0]); 458 dst[r0] = 0.5f*stepsize*(-xneg + ypos - yneg); 459 460 cprv = ccur; 461 ccur = cnxt; 462 ldprv = ldcur; 463 ldcur = ldnxt; 464 } 465 Ld += Lstep; 466 } 467 468 /* ************************************************************************* */ 469 /** 470 * @brief This function downsamples the input image using OpenCV resize 471 * @param img Input image to be downsampled 472 * @param dst Output image with half of the resolution of the input image 473 */ 474 void halfsample_image(const cv::Mat& src, cv::Mat& dst) { 475 476 // Make sure the destination image is of the right size 477 CV_Assert(src.cols / 2 == dst.cols); 478 CV_Assert(src.rows / 2 == dst.rows); 479 resize(src, dst, dst.size(), 0, 0, cv::INTER_AREA); 480 } 481 482 /* ************************************************************************* */ 483 /** 484 * @brief This function checks if a given pixel is a maximum in a local neighbourhood 485 * @param img Input image where we will perform the maximum search 486 * @param dsize Half size of the neighbourhood 487 * @param value Response value at (x,y) position 488 * @param row Image row coordinate 489 * @param col Image column coordinate 490 * @param same_img Flag to indicate if the image value at (x,y) is in the input image 491 * @return 1->is maximum, 0->otherwise 492 */ 493 bool check_maximum_neighbourhood(const cv::Mat& img, int dsize, float value, int row, int col, bool same_img) { 494 495 bool response = true; 496 497 for (int i = row - dsize; i <= row + dsize; i++) { 498 for (int j = col - dsize; j <= col + dsize; j++) { 499 if (i >= 0 && i < img.rows && j >= 0 && j < img.cols) { 500 if (same_img == true) { 501 if (i != row || j != col) { 502 if ((*(img.ptr<float>(i)+j)) > value) { 503 response = false; 504 return response; 505 } 506 } 507 } 508 else { 509 if ((*(img.ptr<float>(i)+j)) > value) { 510 response = false; 511 return response; 512 } 513 } 514 } 515 } 516 } 517 518 return response; 519 } 520 521 } 522