00001 /*!@file Image/PyramidOps.C Free functions operating on pyramid data structures */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the // 00005 // University of Southern California (USC) and the iLab at USC. // 00006 // See http://iLab.usc.edu for information about this project. // 00007 // //////////////////////////////////////////////////////////////////// // 00008 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00009 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00010 // in Visual Environments, and Applications'' by Christof Koch and // 00011 // Laurent Itti, California Institute of Technology, 2001 (patent // 00012 // pending; application number 09/912,225 filed July 23, 2001; see // 00013 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00014 // //////////////////////////////////////////////////////////////////// // 00015 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00016 // // 00017 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00018 // redistribute it and/or modify it under the terms of the GNU General // 00019 // Public License as published by the Free Software Foundation; either // 00020 // version 2 of the License, or (at your option) any later version. // 00021 // // 00022 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00023 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00024 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00025 // PURPOSE. See the GNU General Public License for more details. // 00026 // // 00027 // You should have received a copy of the GNU General Public License // 00028 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00029 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00030 // Boston, MA 02111-1307 USA. // 00031 // //////////////////////////////////////////////////////////////////// // 00032 // 00033 // Primary maintainer for this file: Laurent Itti <itti@usc.edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Image/PyramidOps.C $ 00035 // $Id: PyramidOps.C 14474 2011-02-04 01:21:21Z dberg $ 00036 // 00037 00038 #include "Image/PyramidOps.H" 00039 00040 #include "Image/Image.H" 00041 #include "Image/ImageSet.H" 00042 #include "Image/ImageSetOps.H" 00043 #include "Image/FilterOps.H" 00044 #include "Image/LowPassLpt.H" 00045 #include "Image/Kernels.H" 00046 #include "Image/MathOps.H" 00047 #include "Image/ShapeOps.H" 00048 #include "Image/Transforms.H" // for chamfer34() 00049 #include "Image/Pixels.H" 00050 #include "Util/Assert.H" 00051 #include "rutz/trace.h" 00052 00053 // ############################################################ 00054 // ##### Dyadic pyramid pixel operations: 00055 // ############################################################ 00056 00057 // ###################################################################### 00058 template <class T> 00059 T getPyrPixel(const ImageSet<T>& pyr, 00060 const Point2D<int>& p, const uint lev) 00061 { 00062 ASSERT(lev < pyr.size()); 00063 ASSERT(pyr[0].coordsOk(p)); 00064 ASSERT(isDyadic(pyr)); 00065 00066 float fac = float(1 << lev); 00067 float ii = float(p.i) / fac, jj = float(p.j) / fac; 00068 int ww = pyr[lev].getWidth() - 1, hh = pyr[lev].getHeight() - 1; 00069 if (ii > ww) ii = ww; if (jj > hh) jj = hh; 00070 00071 return pyr[lev].getValInterp(ii, jj); 00072 } 00073 00074 // ###################################################################### 00075 template <class T> 00076 T getPyrPixelNI(const ImageSet<T>& pyr, 00077 const Point2D<int>& p, const uint lev) 00078 { 00079 ASSERT(lev < pyr.size()); 00080 ASSERT(pyr[0].coordsOk(p)); 00081 ASSERT(isDyadic(pyr)); 00082 00083 int ii = p.i >> lev, jj = p.j >> lev; 00084 int ww = pyr[lev].getWidth() - 1, hh = pyr[lev].getHeight() - 1; 00085 if (ii > ww) ii = ww; if (jj > hh) jj = hh; 00086 00087 return pyr[lev].getVal(ii, jj); 00088 } 00089 00090 // ###################################################################### 00091 template <class T> 00092 T getPyrPixel(const ImageSet<T>& pyr, 00093 const float x, const float y, const float z) 00094 { 00095 ASSERT(isDyadic(pyr)); 00096 00097 const uint nlev = pyr.size(); 00098 00099 // let's do a getValInterp on the two adjacent levels, then a linear 00100 // interpolation between these two values: 00101 const uint lev1 = int(floor(z)); ASSERT(lev1 < nlev); 00102 00103 // we will interpolate between lev and lev+1. Let's compute the 00104 // scaled down (x, y) coordinates for lev: 00105 const float fac1 = float(1 << lev1); 00106 float ii1 = x / fac1, jj1 = y / fac1; 00107 const float ww1 = float(pyr[lev1].getWidth() - 1); 00108 const float hh1 = float(pyr[lev1].getHeight() - 1); 00109 if (ii1 > ww1) ii1 = ww1; 00110 if (jj1 > hh1) jj1 = hh1; 00111 00112 const T val1 = pyr[lev1].getValInterp(ii1, jj1); 00113 00114 // if we are at the top level, then we cannot interpolate in the 00115 // scale dimension, so we just use that single level: 00116 if (lev1 == nlev - 1) return val1; 00117 00118 // otherwise, repeat for level lev+1: 00119 const uint lev2 = lev1 + 1; 00120 const float fac2 = float(2 << lev2); 00121 float ii2 = x / fac2, jj2 = y / fac2; 00122 float ww2 = float(pyr[lev2].getWidth() - 1); 00123 float hh2 = float(pyr[lev2].getHeight() - 1); 00124 if (ii2 > ww2) ii2 = ww2; 00125 if (jj2 > hh2) jj2 = hh2; 00126 00127 const T val2 = pyr[lev2].getValInterp(ii2, jj2); 00128 00129 // now a last linear interpolation between these two values: 00130 const float fz = z - float(lev1); // fractional z value 00131 00132 return T(val1 + (val2 - val1) * fz); // no need to clamp 00133 } 00134 00135 // ###################################################################### 00136 // ##### Pyramid builder functions: 00137 // ###################################################################### 00138 00139 // ###################################################################### 00140 template <class T> 00141 ImageSet<T> buildPyrGaussian(const Image<T>& image, 00142 int firstlevel, int depth, int filterSize) 00143 { 00144 GVX_TRACE(__PRETTY_FUNCTION__); 00145 ASSERT(image.initialized()); 00146 00147 ImageSet<T> result(depth); 00148 00149 if (0 >= firstlevel) 00150 result[0] = image; 00151 00152 Image<T> a = image; 00153 for (int lev = 1; lev < depth; ++lev) 00154 { 00155 if (filterSize == 5) 00156 { 00157 a = lowPass5yDecY(lowPass5xDecX(a,2),2); 00158 } 00159 else 00160 { 00161 a = decX(lowPassX(filterSize, a)); 00162 a = decY(lowPassY(filterSize, a)); 00163 } 00164 00165 // NOTE: when lev < firstlevel, we leave result[lev] empty even 00166 // though it isn't any extra work to fill it in; that way, other 00167 // functions that see the result ImageSet might be able to gain 00168 // additional speed-ups by seeing that some of the images are 00169 // empty and avoid processing them further 00170 if (lev >= firstlevel) 00171 result[lev] = a; 00172 } 00173 00174 return result; 00175 } 00176 00177 // ###################################################################### 00178 template <class T> 00179 ImageSet<T> buildRadialPyrGaussian(const Image<T>& image, int firstlevel, int depth) 00180 { 00181 GVX_TRACE(__PRETTY_FUNCTION__); 00182 ASSERT(image.initialized()); 00183 00184 ImageSet<T> result(depth); 00185 00186 if (0 >= firstlevel) 00187 result[0] = image; 00188 00189 Image<T> a = image; 00190 for (int lev = 1; lev < depth; ++lev) 00191 { 00192 a = decXY(lowPassLpt5(a, CROSS_HEMI)); 00193 00194 // NOTE: when lev < firstlevel, we leave result[lev] empty even 00195 // though it isn't any extra work to fill it in; that way, other 00196 // functions that see the result ImageSet might be able to gain 00197 // additional speed-ups by seeing that some of the images are 00198 // empty and avoid processing them further 00199 if (lev >= firstlevel) 00200 result[lev] = a; 00201 } 00202 00203 return result; 00204 } 00205 00206 // ###################################################################### 00207 template <class T> 00208 ImageSet<T> buildPyrConvolve(const Image<T>& image, 00209 int firstlevel, int depth, 00210 const Image<float>& filter, 00211 ConvolutionBoundaryStrategy boundary) 00212 { 00213 GVX_TRACE(__PRETTY_FUNCTION__); 00214 ASSERT(image.initialized()); 00215 00216 ImageSet<T> result(depth); 00217 00218 Image<T> base; 00219 for (int lev = 0; lev < depth; ++lev) 00220 { 00221 if (lev == 0) base = image; else base = decXY(lowPass5(base)); 00222 00223 if (lev >= firstlevel) 00224 result[lev] = convolve(base, filter, boundary); 00225 } 00226 00227 return result; 00228 } 00229 00230 // ###################################################################### 00231 template <class T> 00232 ImageSet<T> buildPyrLaplacian(const Image<T>& image, 00233 int firstlevel, int depth, int filterSize) 00234 { 00235 GVX_TRACE(__PRETTY_FUNCTION__); 00236 ASSERT(image.initialized()); 00237 00238 ImageSet<T> result(depth); 00239 00240 Image<T> lpf = lowPass(filterSize, image); 00241 00242 if (0 >= firstlevel) 00243 result[0] = image - lpf; 00244 00245 for (int lev = 1; lev < depth; ++lev) 00246 { 00247 const Image<T> dec = decXY(lpf); 00248 lpf = lowPass(filterSize, dec); 00249 00250 if (lev >= firstlevel) 00251 result[lev] = dec - lpf; 00252 } 00253 00254 return result; 00255 } 00256 00257 // ###################################################################### 00258 template <class T> 00259 ImageSet<T> buildPyrOrientedFromLaplacian(const ImageSet<T>& laplacian, 00260 int filterSize, 00261 float theta, float intens, 00262 const bool usetab) 00263 { 00264 int attenuation_width = -1; 00265 float spatial_freq = -1.0; 00266 00267 switch (filterSize) 00268 { 00269 case 5: attenuation_width = 3; spatial_freq = M_PI_2; break; 00270 case 9: attenuation_width = 5; spatial_freq = 2.6; break; 00271 default: 00272 LFATAL("Filter size %d is not supported", filterSize); 00273 } 00274 00275 // compute oriented filter from laplacian: 00276 ImageSet<T> result(laplacian.size()); 00277 00278 for (size_t lev = 0; lev < result.size(); ++lev) 00279 { 00280 if (laplacian[lev].initialized()) 00281 { 00282 result[lev] = orientedFilter(laplacian[lev], spatial_freq, 00283 theta, intens, usetab); 00284 // attenuate borders that are overestimated due to filter trunctation: 00285 inplaceAttenuateBorders(result[lev], attenuation_width); 00286 } 00287 } 00288 00289 return result; 00290 } 00291 00292 // ###################################################################### 00293 template <class T> 00294 ImageSet<T> buildPyrOriented(const Image<T>& image, 00295 int firstlevel, int depth, int filterSize, 00296 float theta, float intens, 00297 const bool usetab) 00298 { 00299 GVX_TRACE(__PRETTY_FUNCTION__); 00300 ASSERT(image.initialized()); 00301 00302 // let's promote the intermediary results to float 00303 typedef typename promote_trait<T, float>::TP TF; 00304 00305 const ImageSet<TF> laplacian = 00306 buildPyrLaplacian(Image<TF>(image), firstlevel, depth, filterSize); 00307 00308 const ImageSet<TF> orifilt = 00309 buildPyrOrientedFromLaplacian(laplacian, filterSize, theta, 00310 intens, usetab); 00311 00312 ImageSet<T> result(orifilt.size()); 00313 for (size_t i = 0; i < result.size(); ++i) 00314 result[i] = Image<T>(orifilt[i]); 00315 00316 return result; 00317 } 00318 00319 // ###################################################################### 00320 template <class T> 00321 ImageSet<T> buildPyrLocalAvg(const Image<T>& image, int depth) 00322 { 00323 GVX_TRACE(__PRETTY_FUNCTION__); 00324 ASSERT(image.initialized()); 00325 00326 ASSERT(depth >= 1); 00327 00328 ImageSet<T> result(depth); 00329 00330 // only deepest level of pyr is filled with local avg: 00331 const int scale = int(pow(2.0, depth - 1)); 00332 result[depth - 1] = quickLocalAvg(image, scale); 00333 00334 return result; 00335 } 00336 00337 // ###################################################################### 00338 template <class T> 00339 ImageSet<T> buildPyrLocalAvg2x2(const Image<T>& image, int depth) 00340 { 00341 GVX_TRACE(__PRETTY_FUNCTION__); 00342 ASSERT(depth >= 0); 00343 00344 if (depth == 0) 00345 return ImageSet<T>(); 00346 00347 ImageSet<T> result(depth); 00348 result[0] = image; 00349 00350 for (int i = 1; i < depth; ++i) 00351 result[i] = quickLocalAvg2x2(result[i-1]); 00352 00353 return result; 00354 } 00355 00356 // ###################################################################### 00357 template <class T> 00358 ImageSet<T> buildPyrLocalMax(const Image<T>& image, int depth) 00359 { 00360 GVX_TRACE(__PRETTY_FUNCTION__); 00361 ASSERT(image.initialized()); 00362 00363 ASSERT(depth >= 1); 00364 00365 ImageSet<T> result(depth); 00366 00367 // only deepest level of pyr is filled with local max: 00368 const int scale = int(pow(2.0, depth - 1)); 00369 result[depth - 1] = quickLocalMax(image, scale); 00370 00371 return result; 00372 } 00373 00374 // ###################################################################### 00375 template <class T> 00376 ImageSet<T> buildPyrGeneric(const Image<T>& image, 00377 int firstlevel, int depth, 00378 const PyramidType typ, const float gabor_theta, 00379 const float intens) 00380 { 00381 GVX_TRACE(__PRETTY_FUNCTION__); 00382 switch(typ) 00383 { 00384 case Gaussian3: return buildPyrGaussian(image, firstlevel, depth, 3); 00385 case Gaussian5: return buildPyrGaussian(image, firstlevel, depth, 5); 00386 case Gaussian9: return buildPyrGaussian(image, firstlevel, depth, 9); 00387 00388 case Laplacian5: return buildPyrLaplacian(image, firstlevel, depth, 5); 00389 case Laplacian9: return buildPyrLaplacian(image, firstlevel, depth, 9); 00390 00391 case Oriented5: return buildPyrOriented(image, firstlevel, depth, 5, gabor_theta, intens); 00392 case Oriented9: return buildPyrOriented(image, firstlevel, depth, 9, gabor_theta, intens); 00393 00394 case QuickLocalAvg: return buildPyrLocalAvg(image, depth); 00395 case QuickLocalMax: return buildPyrLocalMax(image, depth); 00396 00397 default: 00398 LFATAL("Attempt to create Pyramid of unknown type %d", typ); 00399 } 00400 00401 ASSERT(false); 00402 return ImageSet<T>(); // "can't happen", but placate compiler 00403 } 00404 00405 // ###################################################################### 00406 ImageSet<float> buildPyrGabor(const ImageSet<float>& gaussianPyr, 00407 float angle, float filter_period, 00408 float elongation, int size, int flags) 00409 { 00410 GVX_TRACE(__PRETTY_FUNCTION__); 00411 const double major_stddev = filter_period / 3.0; 00412 const double minor_stddev = major_stddev * elongation; 00413 00414 // We have to add 90 to the angle here when constructing the gabor 00415 // filter. That's because the angle used to build the gabor filter 00416 // actually specifies the direction along which the grating 00417 // varies. This direction is orthogonal to the the direction of the 00418 // contours that the grating will detect. 00419 const double theta = angle + 90.0f; 00420 00421 // In concept, we want to filter with four phases of the filter: odd 00422 // on+off, and even on+off (that would be xox, oxo, xo, and ox). But 00423 // since the on version just produces the negation of the off version, 00424 // we can get the summed output of both by just taking the absolute 00425 // value of the outputs of one of them. So we only need to convolve 00426 // with the filter in two phases, then take the absolute value (i.e., 00427 // we're doing |xox| and |xo|). 00428 00429 Image<float> g0 = gaborFilter3(major_stddev, minor_stddev, 00430 filter_period, 0.0f, theta, size); 00431 Image<float> g90 = gaborFilter3(major_stddev, minor_stddev, 00432 filter_period, 90.0f, theta, size); 00433 00434 LDEBUG("angle = %.2f, period = %.2f pix, size = %dx%d pix", 00435 angle, filter_period, g0.getWidth(), g0.getHeight()); 00436 00437 Image<float> f0, f90; 00438 ImageSet<float> result(gaussianPyr.size()); 00439 00440 for (uint i = 0; i < gaussianPyr.size(); ++i) 00441 { 00442 // if the i'th level in our input is empty, then leave it empty 00443 // in the output as well: 00444 if (!gaussianPyr[i].initialized()) 00445 continue; 00446 00447 if (flags & DO_ENERGY_NORM) 00448 { 00449 Image<float> temp = energyNorm(gaussianPyr[i]); 00450 f0 = optConvolve(temp, g0); 00451 f90 = optConvolve(temp, g90); 00452 } 00453 else 00454 { 00455 f0 = optConvolve(gaussianPyr[i], g0); 00456 f90 = optConvolve(gaussianPyr[i], g90); 00457 } 00458 00459 if (!(flags & NO_ABS)) 00460 { 00461 f0 = abs(f0); 00462 f90 = abs(f90); 00463 } 00464 00465 // this really doesn't do much - take it out for now 00466 // DW 04/25/03 00467 //if (flags & DO_CLAMPED_DIFF) 00468 //{ 00469 // result[i] = 00470 // clampedDiff(f0, gaussianPyr[i]) + clampedDiff(f90, gaussianPyr[i]); 00471 //} 00472 //else 00473 //{ 00474 result[i] = f0 + f90; 00475 //} 00476 } 00477 00478 return result; 00479 } 00480 00481 // ###################################################################### 00482 ImageSet<float> buildPyrGabor(const Image<float>& img, 00483 int firstlevel, int depth, float angle, 00484 float filter_period, float elongation, 00485 int size, int flags) 00486 { 00487 GVX_TRACE(__PRETTY_FUNCTION__); 00488 ImageSet<float> pyr; 00489 00490 //LINFO("flags: %d", flags); 00491 if (flags & DO_LAPLACIAN) 00492 { 00493 pyr = buildPyrLaplacian(img, firstlevel, depth, 9); 00494 } 00495 else 00496 { 00497 pyr = buildPyrGaussian(img, firstlevel, depth, 9); 00498 } 00499 00500 return buildPyrGabor(pyr, angle, filter_period, elongation, size, flags); 00501 } 00502 00503 // ###################################################################### 00504 // ##### Processing Functions: 00505 // ###################################################################### 00506 00507 // ###################################################################### 00508 template <class T> 00509 Image<T> centerSurround(const ImageSet<T>& pyr, 00510 const int lev1, const int lev2, 00511 const bool absol, 00512 const ImageSet<float>* clipPyr) 00513 { 00514 GVX_TRACE(__PRETTY_FUNCTION__); 00515 ASSERT(lev1 >= 0 && lev2 >= 0); 00516 ASSERT(uint(lev1) < pyr.size() && uint(lev2) < pyr.size()); 00517 00518 const int largeLev = std::min(lev1, lev2); 00519 const int smallLev = std::max(lev1, lev2); 00520 00521 if (clipPyr != 0 && clipPyr->isNonEmpty()) 00522 { 00523 ASSERT((*clipPyr)[largeLev].getDims() == pyr[largeLev].getDims()); 00524 ASSERT((*clipPyr)[smallLev].getDims() == pyr[smallLev].getDims()); 00525 00526 return 00527 ::centerSurround(Image<T>(pyr[largeLev] * (*clipPyr)[largeLev]), 00528 Image<T>(pyr[smallLev] * (*clipPyr)[smallLev]), 00529 absol); 00530 } 00531 else 00532 return ::centerSurround(pyr[largeLev], pyr[smallLev], absol); 00533 } 00534 00535 // ###################################################################### 00536 template <class T> 00537 void centerSurround(const ImageSet<T>& pyr, 00538 const int lev1, const int lev2, 00539 Image<T>& pos, Image<T>& neg, 00540 const ImageSet<float>* clipPyr) 00541 { 00542 GVX_TRACE(__PRETTY_FUNCTION__); 00543 ASSERT(lev1 >= 0 && lev2 >= 0); 00544 ASSERT(uint(lev1) < pyr.size() && uint(lev2) < pyr.size()); 00545 00546 const int largeLev = std::min(lev1, lev2); 00547 const int smallLev = std::max(lev1, lev2); 00548 00549 if (clipPyr != 0 && clipPyr->isNonEmpty()) 00550 { 00551 ASSERT((*clipPyr)[largeLev].getDims() == pyr[largeLev].getDims()); 00552 ASSERT((*clipPyr)[smallLev].getDims() == pyr[smallLev].getDims()); 00553 00554 ::centerSurround(Image<T>(pyr[largeLev] * (*clipPyr)[largeLev]), 00555 Image<T>(pyr[smallLev] * (*clipPyr)[smallLev]), 00556 pos, neg); 00557 } 00558 else 00559 ::centerSurround(pyr[largeLev], pyr[smallLev], pos, neg); 00560 } 00561 00562 // ###################################################################### 00563 template <class T> 00564 Image<T> centerSurroundSingleOpponent(const ImageSet<T>& cpyr, 00565 const ImageSet<T>& spyr, 00566 const int lev1, const int lev2, 00567 const bool absol, 00568 const ImageSet<float>* clipPyr) 00569 { 00570 GVX_TRACE(__PRETTY_FUNCTION__); 00571 ASSERT(lev1 >= 0 && lev2 >= 0); 00572 ASSERT(uint(lev1) < cpyr.size() && uint(lev2) < cpyr.size()); 00573 ASSERT(uint(lev1) < spyr.size() && uint(lev2) < spyr.size()); 00574 00575 const int largeLev = std::min(lev1, lev2); 00576 const int smallLev = std::max(lev1, lev2); 00577 00578 if (clipPyr != 0 && clipPyr->isNonEmpty()) 00579 { 00580 ASSERT((*clipPyr)[largeLev].getDims() == cpyr[largeLev].getDims()); 00581 ASSERT((*clipPyr)[smallLev].getDims() == cpyr[smallLev].getDims()); 00582 ASSERT((*clipPyr)[largeLev].getDims() == spyr[largeLev].getDims()); 00583 ASSERT((*clipPyr)[smallLev].getDims() == spyr[smallLev].getDims()); 00584 00585 return 00586 ::centerSurround(Image<T>(cpyr[largeLev] * (*clipPyr)[largeLev]), 00587 Image<T>(spyr[smallLev] * (*clipPyr)[smallLev]), 00588 absol); 00589 } 00590 else 00591 return ::centerSurround(cpyr[largeLev], spyr[smallLev], absol); 00592 } 00593 00594 // ###################################################################### 00595 template <class T> 00596 void centerSurroundSingleOpponent(const ImageSet<T>& cpyr, 00597 const ImageSet<T>& spyr, 00598 const int lev1, const int lev2, 00599 Image<T>& pos, Image<T>& neg, 00600 const ImageSet<float>* clipPyr) 00601 { 00602 GVX_TRACE(__PRETTY_FUNCTION__); 00603 ASSERT(lev1 >= 0 && lev2 >= 0); 00604 ASSERT(uint(lev1) < cpyr.size() && uint(lev2) < cpyr.size()); 00605 ASSERT(uint(lev1) < spyr.size() && uint(lev2) < spyr.size()); 00606 00607 const int largeLev = std::min(lev1, lev2); 00608 const int smallLev = std::max(lev1, lev2); 00609 00610 if (clipPyr != 0 && clipPyr->isNonEmpty()) 00611 { 00612 ASSERT((*clipPyr)[largeLev].getDims() == cpyr[largeLev].getDims()); 00613 ASSERT((*clipPyr)[smallLev].getDims() == cpyr[smallLev].getDims()); 00614 ASSERT((*clipPyr)[largeLev].getDims() == spyr[largeLev].getDims()); 00615 ASSERT((*clipPyr)[smallLev].getDims() == spyr[smallLev].getDims()); 00616 00617 ::centerSurround(Image<T>(cpyr[largeLev] * (*clipPyr)[largeLev]), 00618 Image<T>(spyr[smallLev] * (*clipPyr)[smallLev]), 00619 pos, neg); 00620 } 00621 else 00622 ::centerSurround(cpyr[largeLev], spyr[smallLev], pos, neg); 00623 } 00624 00625 // ###################################################################### 00626 template <class T> 00627 Image<T> centerSurroundDiff(const ImageSet<T>& pyr1, 00628 const ImageSet<T>& pyr2, 00629 const int lev1, const int lev2, 00630 const bool absol, 00631 const ImageSet<float>* clipPyr) 00632 { 00633 GVX_TRACE(__PRETTY_FUNCTION__); 00634 ASSERT(lev1 >= 0 && lev2 >= 0); 00635 ASSERT(uint(lev1) < pyr1.size() && uint(lev2) < pyr1.size()); 00636 00637 const int largeLev = std::min(lev1, lev2); 00638 const int smallLev = std::max(lev1, lev2); 00639 00640 ASSERT(pyr1[largeLev].getDims() == pyr2[largeLev].getDims()); 00641 ASSERT(pyr1[smallLev].getDims() == pyr2[smallLev].getDims()); 00642 00643 // compute differences between the two pyramids: 00644 const Image<T> limg = pyr1[largeLev] - pyr2[largeLev]; 00645 const Image<T> simg = pyr1[smallLev] - pyr2[smallLev]; 00646 00647 if (clipPyr != 0 && clipPyr->isNonEmpty()) 00648 { 00649 ASSERT((*clipPyr)[largeLev].getDims() == limg.getDims()); 00650 ASSERT((*clipPyr)[smallLev].getDims() == simg.getDims()); 00651 00652 return ::centerSurround(Image<T>(limg * (*clipPyr)[largeLev]), 00653 Image<T>(simg * (*clipPyr)[smallLev]), 00654 absol); 00655 } 00656 else 00657 return ::centerSurround(limg, simg, absol); 00658 } 00659 00660 // ###################################################################### 00661 template <class T> 00662 void centerSurroundDiff(const ImageSet<T>& pyr1, 00663 const ImageSet<T>& pyr2, 00664 const int lev1, const int lev2, 00665 Image<T>& pos, Image<T>& neg, 00666 const ImageSet<float>* clipPyr) 00667 { 00668 GVX_TRACE(__PRETTY_FUNCTION__); 00669 ASSERT(lev1 >= 0 && lev2 >= 0); 00670 ASSERT(uint(lev1) < pyr1.size() && uint(lev2) < pyr1.size()); 00671 00672 const int largeLev = std::min(lev1, lev2); 00673 const int smallLev = std::max(lev1, lev2); 00674 00675 ASSERT(pyr1[largeLev].getDims() == pyr2[largeLev].getDims()); 00676 ASSERT(pyr1[smallLev].getDims() == pyr2[smallLev].getDims()); 00677 00678 // compute differences between the two pyramids: 00679 const Image<T> limg = pyr1[largeLev] - pyr2[largeLev]; 00680 const Image<T> simg = pyr1[smallLev] - pyr2[smallLev]; 00681 00682 if (clipPyr != 0 && clipPyr->isNonEmpty()) 00683 { 00684 ASSERT((*clipPyr)[largeLev].getDims() == limg.getDims()); 00685 ASSERT((*clipPyr)[smallLev].getDims() == simg.getDims()); 00686 00687 ::centerSurround(Image<T>(limg * (*clipPyr)[largeLev]), 00688 Image<T>(simg * (*clipPyr)[smallLev]), 00689 pos, neg); 00690 } 00691 else 00692 ::centerSurround(limg, simg, pos, neg); 00693 } 00694 00695 // ###################################################################### 00696 template <class T> 00697 Image<T> centerSurroundDiffSingleOpponent(const ImageSet<T>& cpyr1, 00698 const ImageSet<T>& cpyr2, 00699 const ImageSet<T>& spyr1, 00700 const ImageSet<T>& spyr2, 00701 const int lev1, const int lev2, 00702 const bool absol, 00703 const ImageSet<float>* clipPyr) 00704 { 00705 GVX_TRACE(__PRETTY_FUNCTION__); 00706 ASSERT(lev1 >= 0 && lev2 >= 0); 00707 ASSERT(uint(lev1) < cpyr1.size() && uint(lev2) < cpyr1.size()); 00708 00709 const int largeLev = std::min(lev1, lev2); 00710 const int smallLev = std::max(lev1, lev2); 00711 00712 ASSERT(cpyr1[largeLev].getDims() == cpyr2[largeLev].getDims()); 00713 ASSERT(cpyr1[smallLev].getDims() == cpyr2[smallLev].getDims()); 00714 ASSERT(spyr1[largeLev].getDims() == spyr2[largeLev].getDims()); 00715 ASSERT(spyr1[smallLev].getDims() == spyr2[smallLev].getDims()); 00716 00717 // compute differences between the two pyramids: 00718 const Image<T> limg = cpyr1[largeLev] - cpyr2[largeLev]; 00719 const Image<T> simg = spyr1[smallLev] - spyr2[smallLev]; 00720 00721 if (clipPyr != 0 && clipPyr->isNonEmpty()) 00722 { 00723 ASSERT((*clipPyr)[largeLev].getDims() == limg.getDims()); 00724 ASSERT((*clipPyr)[smallLev].getDims() == simg.getDims()); 00725 00726 return ::centerSurround(Image<T>(limg * (*clipPyr)[largeLev]), 00727 Image<T>(simg * (*clipPyr)[smallLev]), 00728 absol); 00729 } 00730 else 00731 return ::centerSurround(limg, simg, absol); 00732 } 00733 00734 // ###################################################################### 00735 template <class T> 00736 void centerSurroundDiffSingleOpponent(const ImageSet<T>& cpyr1, 00737 const ImageSet<T>& cpyr2, 00738 const ImageSet<T>& spyr1, 00739 const ImageSet<T>& spyr2, 00740 const int lev1, const int lev2, 00741 Image<T>& pos, Image<T>& neg, 00742 const ImageSet<float>* clipPyr) 00743 { 00744 GVX_TRACE(__PRETTY_FUNCTION__); 00745 ASSERT(lev1 >= 0 && lev2 >= 0); 00746 ASSERT(uint(lev1) < cpyr1.size() && uint(lev2) < cpyr1.size()); 00747 00748 const int largeLev = std::min(lev1, lev2); 00749 const int smallLev = std::max(lev1, lev2); 00750 00751 ASSERT(cpyr1[largeLev].getDims() == cpyr2[largeLev].getDims()); 00752 ASSERT(cpyr1[smallLev].getDims() == cpyr2[smallLev].getDims()); 00753 ASSERT(spyr1[largeLev].getDims() == spyr2[largeLev].getDims()); 00754 ASSERT(spyr1[smallLev].getDims() == spyr2[smallLev].getDims()); 00755 00756 // compute differences between the two pyramids: 00757 const Image<T> limg = cpyr1[largeLev] - cpyr2[largeLev]; 00758 const Image<T> simg = spyr1[smallLev] - spyr2[smallLev]; 00759 00760 if (clipPyr != 0 && clipPyr->isNonEmpty()) 00761 { 00762 ASSERT((*clipPyr)[largeLev].getDims() == limg.getDims()); 00763 ASSERT((*clipPyr)[smallLev].getDims() == simg.getDims()); 00764 00765 ::centerSurround(Image<T>(limg * (*clipPyr)[largeLev]), 00766 Image<T>(simg * (*clipPyr)[smallLev]), 00767 pos, neg); 00768 } 00769 else 00770 ::centerSurround(limg, simg, pos, neg); 00771 } 00772 00773 // ###################################################################### 00774 template <class T> 00775 Image<T> weightedBlur(const Image<byte>& modulator, const ImageSet<T>& pyr) 00776 { 00777 // make sure params are correct: 00778 ASSERT(pyr.size() > 0); 00779 ASSERT(modulator.getDims() == pyr[0].getDims()); 00780 00781 // the modulator is 0 inside the objects and up to 255 00782 // outside. We are now going to use the pyramid 00783 // to spatially modulate image resolution by picking a 00784 // pyramid level (with interpolation) according to the 00785 // modulator value. The code here is similar in spirit to 00786 // that in retina.C 00787 const int w = modulator.getWidth(), h = modulator.getHeight(); 00788 Image<T> image(modulator.getDims(), NO_INIT); 00789 Image<byte>::const_iterator mod = modulator.begin(); 00790 typename Image<T>::iterator ima = image.beginw(); 00791 const int maxdepth = pyr.size() - 1; 00792 const float rstep = (float)(256 / maxdepth); 00793 00794 for (int j = 0; j < h; j ++) 00795 for (int i = 0; i < w; i ++) 00796 { 00797 // determine resolution from which this pixel will be taken: 00798 const float d = (*mod++) / rstep; 00799 00800 // get the pixel value from depth d, d+1: 00801 const int di = (int)(floor(d)); 00802 const float dd = d - (float)di; 00803 00804 const Point2D<int> loc(i, j); 00805 const T pix0 = getPyrPixel(pyr, loc, di); 00806 const T pix1 = getPyrPixel(pyr, loc, std::min(di+1, maxdepth)); 00807 // This is fast but may cause clamping problems: 00808 //*ima++ = pix0 + (pix1 - pix0) * dd; 00809 // slower but more robust: 00810 *ima++ = clamped_convert<T>(pix0 * (1.0F - dd) + pix1 * dd); 00811 } 00812 return image; 00813 } 00814 00815 // ###################################################################### 00816 template <class T> 00817 Image<T> foveate(const Image<byte>& mask, const ImageSet<T>& pyr) 00818 { 00819 // make sure params are correct: 00820 ASSERT(mask.getDims() == pyr[0].getDims()); 00821 00822 // normalize the mask: 00823 Image<float> remask = mask; 00824 inplaceNormalize(remask, 0.0f, 255.0f); 00825 inplaceLowThresh(remask, 254.0f, 0.0f); 00826 00827 // let's now tranform the mask into a distance map: 00828 const int w = remask.getWidth(), h = remask.getHeight(); 00829 int maxdist = std::max(w, h) * 3; 00830 float scalefac = float(maxdist) / 255.0f; 00831 remask = chamfer34(remask, float(maxdist)) / scalefac; 00832 Image<byte> modulator = remask; // will clamp as necessary 00833 00834 // if modulator does not contain any point at zero (inside 00835 // object), that means that the mask was empty, which is 00836 // the case at the beginning of a simulation. Set it to some 00837 // intermediary value to provide a uniform medium blur: 00838 byte mi, ma; getMinMax(modulator, mi, ma); 00839 if (mi > 0) modulator /= 3; 00840 00841 return weightedBlur(modulator, pyr); 00842 } 00843 00844 // Include the explicit instantiations 00845 #include "inst/Image/PyramidOps.I" 00846 00847 template ImageSet<double> buildPyrLocalAvg2x2(const Image<double>&, int); 00848 00849 // ###################################################################### 00850 /* So things look consistent in everyone's emacs... */ 00851 /* Local Variables: */ 00852 /* indent-tabs-mode: nil */ 00853 /* End: */