00001 /*!@file Image/Retinex.C Retinex color-correction algorithm */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005 // 00005 // by the 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: Rob Peters <rjpeters at usc dot edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Image/Retinex.C $ 00035 // $Id: Retinex.C 9996 2008-07-29 01:08:40Z icore $ 00036 // 00037 00038 #ifndef IMAGE_RETINEX_C_DEFINED 00039 #define IMAGE_RETINEX_C_DEFINED 00040 00041 #include "Image/Retinex.H" 00042 00043 #include "Image/Image.H" 00044 #include "Image/ImageSet.H" 00045 #include "Image/MathOps.H" 00046 #include "Image/PyramidOps.H" 00047 #include "Image/Range.H" 00048 #include "Image/ShapeOps.H" 00049 #include "Util/Assert.H" 00050 #include "Util/log.H" 00051 #include "Util/safecopy.H" 00052 00053 #include <vector> 00054 00055 // ###################################################################### 00056 template <class T> static 00057 Image<T> retinexCompareNeighbors(const Image<T>& src, 00058 const Image<T>& radiance, 00059 const Point2D<int> shift, 00060 const T maxi, 00061 const Rectangle& bbox) 00062 { 00063 ASSERT(src.getDims() == radiance.getDims()); 00064 00065 Image<T> result(src.getDims(), NO_INIT); 00066 00067 const int w = result.getWidth(); 00068 const int h = result.getHeight(); 00069 00070 const int offset = shift.i + shift.j * w; 00071 00072 const int top = bbox.top(); 00073 const int bottom = bbox.bottomO(); 00074 const int left = bbox.left(); 00075 const int right = bbox.rightO(); 00076 00077 // the number of pixels that we need to skip after each row because 00078 // they are outside the range of our output rect: 00079 const int wskip = w - (right - left); 00080 00081 typename Image<T>::const_iterator sptr = src.begin() + top * w + bbox.left(); 00082 typename Image<T>::const_iterator ritr = radiance.begin() + top * w + bbox.left(); 00083 typename Image<T>::iterator dptr = result.beginw() + top * w + bbox.left(); 00084 00085 for (int y = top; y < bottom; ++y) 00086 { 00087 for (int x = left; x < right; ++x, ++sptr, ++ritr, ++dptr) 00088 { 00089 T val; 00090 00091 if (y+shift.j < 0 || y+shift.j >= h 00092 || x+shift.i < 0 || x+shift.i >= w) 00093 { 00094 val = sptr[0]; 00095 } 00096 else 00097 { 00098 val = sptr[offset] + ritr[0] - ritr[offset]; 00099 } 00100 00101 if (val > maxi) val = maxi; 00102 00103 *dptr = T(0.5F * (val + sptr[0])); 00104 } 00105 00106 sptr += wskip; 00107 ritr += wskip; 00108 dptr += wskip; 00109 } 00110 00111 return result; 00112 } 00113 00114 // ###################################################################### 00115 size_t retinexDepth(const Dims& dims) 00116 { 00117 size_t power = 0; 00118 00119 while ((dims.w() >> power) > 1 && (dims.h() >> power) > 1) 00120 ++power; 00121 00122 return power + 1; 00123 } 00124 00125 // ###################################################################### 00126 template <class T> 00127 Image<T> intXYWithPad(const Image<T>& src, const bool padx, const bool pady) 00128 { 00129 ASSERT(src.initialized()); // works only if there is an image 00130 00131 const int w = src.getWidth(); 00132 const int h = src.getHeight(); 00133 00134 const int w2 = w * 2 + int(padx); 00135 const int h2 = h * 2 + int(pady); 00136 00137 Image<T> result(w2, h2, NO_INIT); 00138 00139 typename Image<T>::const_iterator sptr = src.begin(); 00140 typename Image<T>::iterator dptr = result.beginw(); 00141 for (int j = 0; j < h; ++j) 00142 { 00143 for (int i = 0; i < w; ++i) 00144 { 00145 *dptr++ = *sptr; // copy one point 00146 *dptr++ = *sptr; // again 00147 if (padx && i == w-1) 00148 *dptr++ = *sptr; // and one more time if padx and we're at the end of the line 00149 ++sptr; 00150 } 00151 // duplicate line we just wrote: 00152 safecopy(dptr, dptr - w2, w2); 00153 dptr += w2; 00154 if (pady && j == h-1) 00155 { 00156 // duplicate the line one more time if pady and we're at the last line 00157 safecopy(dptr, dptr - w2, w2); 00158 dptr += w2; 00159 } 00160 } 00161 00162 return result; 00163 } 00164 00165 // ###################################################################### 00166 template <class T> 00167 ImageSet<T> buildPyrRetinexLog(const Image<T>& L, 00168 const size_t depth, const int niter, 00169 const Rectangle& outrect) 00170 { 00171 ASSERT(depth >= 1); 00172 ASSERT(niter >= 1); 00173 00174 const ImageSet<T> radiancePyr = buildPyrLocalAvg2x2(L, depth); 00175 00176 if (radiancePyr[depth - 1].getSize() > 25) 00177 LFATAL("invalid toplevel image size %dx%d", 00178 radiancePyr[depth - 1].getWidth(), 00179 radiancePyr[depth - 1].getHeight()); 00180 00181 const Range<T> rng = rangeOf(radiancePyr[0]); 00182 00183 Image<T> OP(radiancePyr[depth - 1].getDims(), NO_INIT); 00184 OP.clear(rng.max()); 00185 00186 ImageSet<T> result(depth); 00187 00188 std::vector<Rectangle> bboxes(depth); 00189 bboxes[0] = L.getBounds().getOverlap 00190 (Rectangle::tlbrO(outrect.top() - niter - 1, 00191 outrect.left() - niter - 1, 00192 outrect.bottomO() + niter + 1, 00193 outrect.rightO() + niter + 1)); 00194 for (size_t i = 1; i < bboxes.size(); ++i) 00195 { 00196 bboxes[i] = radiancePyr[i].getBounds().getOverlap 00197 (Rectangle::tlbrO(bboxes[i-1].top() / 2 - niter - 1, 00198 bboxes[i-1].left() / 2 - niter - 1, 00199 bboxes[i-1].bottomO() / 2 + niter, 00200 bboxes[i-1].rightO() / 2 + niter)); 00201 } 00202 00203 for (size_t i = 0; i < bboxes.size(); ++i) 00204 { 00205 LINFO("pyr[%"ZU"] dims=%dx%d, rect=%dx%d @ (%d,%d)", 00206 i, radiancePyr[i].getWidth(), radiancePyr[i].getHeight(), 00207 bboxes[i].width(), bboxes[i].height(), 00208 bboxes[i].left(), bboxes[i].top()); 00209 } 00210 00211 for (int lev = depth - 1; lev >= 0; --lev) 00212 { 00213 const Image<T> radiance = radiancePyr[lev]; 00214 00215 ASSERT(OP.getDims() == radiance.getDims()); 00216 00217 for (int iter = 0; iter < niter; ++iter) 00218 { 00219 OP = retinexCompareNeighbors(OP, radiance, Point2D<int>( 0, -1), rng.max(), bboxes[lev]); 00220 OP = retinexCompareNeighbors(OP, radiance, Point2D<int>( 1, -1), rng.max(), bboxes[lev]); 00221 OP = retinexCompareNeighbors(OP, radiance, Point2D<int>( 1, 0), rng.max(), bboxes[lev]); 00222 OP = retinexCompareNeighbors(OP, radiance, Point2D<int>( 1, 1), rng.max(), bboxes[lev]); 00223 OP = retinexCompareNeighbors(OP, radiance, Point2D<int>( 0, 1), rng.max(), bboxes[lev]); 00224 OP = retinexCompareNeighbors(OP, radiance, Point2D<int>(-1, 1), rng.max(), bboxes[lev]); 00225 OP = retinexCompareNeighbors(OP, radiance, Point2D<int>(-1, 0), rng.max(), bboxes[lev]); 00226 OP = retinexCompareNeighbors(OP, radiance, Point2D<int>(-1, -1), rng.max(), bboxes[lev]); 00227 } 00228 00229 result[lev] = OP; 00230 00231 if (lev > 0) 00232 { 00233 const int padx = radiancePyr[lev-1].getWidth() - OP.getWidth() * 2; 00234 ASSERT(padx == 0 || padx == 1); 00235 const int pady = radiancePyr[lev-1].getHeight() - OP.getHeight() * 2; 00236 ASSERT(pady == 0 || pady == 1); 00237 00238 LINFO("padx = %d, pady = %d", padx, pady); 00239 00240 if (!padx && !pady) 00241 OP = intXY(OP, true); 00242 else 00243 OP = intXYWithPad(OP, bool(padx), bool(pady)); 00244 } 00245 else 00246 // we are going to be exiting the loop, so we don't need OP 00247 // anymore: 00248 OP = Image<T>(); 00249 } 00250 00251 return result; 00252 } 00253 00254 template ImageSet<float> buildPyrRetinexLog(const Image<float>&, size_t, int, 00255 const Rectangle&); 00256 00257 // ###################################################################### 00258 /* So things look consistent in everyone's emacs... */ 00259 /* Local Variables: */ 00260 /* mode: c++ */ 00261 /* indent-tabs-mode: nil */ 00262 /* End: */ 00263 00264 #endif // IMAGE_RETINEX_C_DEFINED