Main Page | Namespace List | Class Hierarchy | Class List | Directories | File List | Class Members | File Members

redEye.cpp

Go to the documentation of this file.
00001 //==============================================
00002 //  copyright            : (C) 2003-2005 by Will Stokes
00003 //==============================================
00004 //  This program is free software; you can redistribute it
00005 //  and/or modify it under the terms of the GNU General
00006 //  Public License as published by the Free Software
00007 //  Foundation; either version 2 of the License, or
00008 //  (at your option) any later version.
00009 //==============================================
00010 
00011 //Systemwide includes
00012 #include <qimage.h>
00013 #include <qstring.h>
00014 #include <qapplication.h>
00015 
00016 //Projectwide includes
00017 #include "redEye.h"
00018 #include "redEye_internal.h"
00019 #include "../../gui/statusWidget.h"
00020 
00021 //==============================================
00022 QImage* removeRedeyeRegions( QString filename, 
00023                              QPoint topLeftExtreme, QPoint bottomRightExtreme,
00024                              StatusWidget* statusWidget )
00025 {
00026   //store handle to status widget
00027   status = statusWidget;
00028   
00029   //load original image
00030   rawImage = QImage( filename );
00031   
00032   //sanity check: unable to load image
00033   if(rawImage.isNull()) { return NULL; }
00034 
00035   //sanity check: make sure topLeftExtreme and bottomRightExtreme are within image boundary
00036   topLeftExtreme.setX( QMAX( topLeftExtreme.x(), 0 ) );
00037   topLeftExtreme.setY( QMAX( topLeftExtreme.y(), 0 ) );
00038   bottomRightExtreme.setX( QMIN( bottomRightExtreme.x(), rawImage.width()-1 ) );
00039   bottomRightExtreme.setY( QMIN( bottomRightExtreme.y(), rawImage.height()-1 ) );
00040 
00041   //setup progress bar
00042   QString statusMessage = qApp->translate( "removeRedeyeRegions", "Removing Red-Eye:" );
00043   status->showProgressBar( statusMessage, 100 );
00044   qApp->processEvents();  
00045   
00046   //update progress bar for every 1% of completion
00047   updateIncrement = (int) ( 0.01 * 
00048                             ( bottomRightExtreme.x() - topLeftExtreme.x() + 1 ) *
00049                             ( bottomRightExtreme.y() - topLeftExtreme.y() + 1 ) );
00050   newProgress = 0;   
00051 
00052   //find region of interest: constrain search box to boundary that actually contains red enough pixels
00053   findRegionOfInterest(topLeftExtreme, bottomRightExtreme);
00054 
00055   //if no pixels were found then immediately return a NULL pointer signaling no change
00056   if(topLeft.x() == -1) 
00057   { 
00058     //hide progress bar
00059     status->setStatus( "" );
00060     qApp->processEvents();
00061 
00062     return NULL; 
00063   }
00064 
00065   //load an editing image
00066   //two images mus be loaded becuase pixel values are replaced
00067   //using a compbination of niehgbors and their own in order
00068   //to avoid sharp lines at the edge of the saturated region
00069   editedImage = new QImage( filename );
00070   
00071   //sanity check: unable to allocated edited image
00072   if( editedImage == NULL) 
00073   { 
00074     //hide progress bar
00075     status->setStatus( "" );
00076     qApp->processEvents();
00077 
00078     return NULL; 
00079   }
00080 
00081   findBlobs();
00082   sortBlobsByDecreasingSize();
00083   findBestTwoBlobs();
00084 
00085   //if we found two good blobs then desaturate those only
00086   if(id1 != -1)
00087   {
00088     desaturateBlobs();
00089   }
00090   //else desaturate all pixels above thresh within selection area
00091   else
00092   {
00093     desaturateEntireImage(topLeftExtreme, bottomRightExtreme);
00094   }
00095 
00096   //remove status bar
00097   status->setStatus( "" );
00098   qApp->processEvents();
00099 
00100   //return pointer to edited image
00101   return editedImage;      
00102 }
00103 //==============================================
00104 
00105 // 51 = 20% of red channel, a good heuristic for false positives
00106 //at border of face on a dark background.
00107 #define MIN_RED_VAL 40
00108 
00109 //==============================================
00110 void findRegionOfInterest(QPoint topLeftExtreme, QPoint bottomRightExtreme)
00111 {
00112   topLeft = QPoint(-1,-1);
00113   bottomRight = QPoint(-1,-1);
00114   
00115   int x, y;
00116   QRgb* rgb;
00117   uchar* scanLine;
00118   for( y=topLeftExtreme.y(); y<=bottomRightExtreme.y(); y++)
00119   {
00120     scanLine = rawImage.scanLine(y);
00121     for( x=topLeftExtreme.x(); x<=bottomRightExtreme.x(); x++)
00122     {
00123       rgb = ((QRgb*)scanLine+x);
00124       
00125       bool threshMet = qRed(*rgb) > 2*qGreen(*rgb) &&
00126         qRed(*rgb) > MIN_RED_VAL;
00127       if(threshMet)
00128       {
00129         //first pixel
00130         if(topLeft.x() == -1) 
00131         {
00132           topLeft = QPoint(x,y);
00133           bottomRight = QPoint(x,y);
00134         }
00135         
00136         if(x < topLeft.x() ) topLeft.setX( x );
00137         if(y < topLeft.y() ) topLeft.setY( y );
00138         if(x > bottomRight.x() ) bottomRight.setX( x );
00139         if(y > bottomRight.y() ) bottomRight.setY( y );
00140       }
00141       
00142       //update status bar if significant progress has been made since last update
00143       newProgress++;
00144       if(newProgress >= updateIncrement)
00145       {
00146         newProgress = 0;
00147         status->incrementProgress();
00148         qApp->processEvents();  
00149       }
00150       
00151     }
00152   }  
00153 }
00154 //==============================================
00155 void pushPixel(int x, int y, int id)
00156 {
00157   //if pixel off image or below thresh ignore push attempt
00158   if(  x < 0  || 
00159        y <  0 ||
00160        x >= regionWidth ||
00161        y >= regionHeight ||
00162        regionOfInterest[ x + y*regionWidth ] != 1 )
00163     return;
00164   
00165   //passes! set id and actually put pixel onto stack
00166   regionOfInterest[ x + y*regionWidth] = id;  
00167   spreadablePixels.push( QPoint( x, y ) );
00168   
00169   //increase blob pixel count and update topLeft and bottomRight
00170   blobPixelCount++;
00171   blobTopLeft.setX( QMIN( x, blobTopLeft.x() ) );
00172   blobTopLeft.setY( QMIN( y, blobTopLeft.y() ) );
00173   blobBottomRight.setX( QMAX( x, blobBottomRight.x() ) );
00174   blobBottomRight.setY( QMAX( y, blobBottomRight.y() ) );
00175 }
00176 //==============================================
00177 void findBlobs()
00178 {
00179   //create small matrix for region of interest
00180   regionWidth = bottomRight.x() - topLeft.x() + 1;
00181   regionHeight = bottomRight.y() - topLeft.y() + 1;  
00182   regionOfInterest = new int[ regionWidth * regionHeight ];
00183   
00184   //set all pixels that meet thresh to 1, all others to 0
00185   int x, y;
00186   int x2, y2;
00187   QRgb* rgb;
00188   uchar* scanLine;
00189   for( y=topLeft.y(); y<=bottomRight.y(); y++)
00190   {
00191     y2 = y - topLeft.y();
00192     
00193     scanLine = rawImage.scanLine(y);
00194     for( x=topLeft.x(); x<=bottomRight.x(); x++)
00195     {
00196     
00197       x2 = x - topLeft.x();
00198       
00199       rgb = ((QRgb*)scanLine+x);
00200       
00201       bool threshMet = qRed(*rgb) > 2*qGreen(*rgb) &&
00202                        qRed(*rgb) > MIN_RED_VAL;
00203       
00204       if(threshMet)
00205         regionOfInterest[ x2 + y2*regionWidth ] = 1;
00206       else
00207         regionOfInterest[ x2 + y2*regionWidth ] = 0;
00208     }
00209   } 
00210   
00211   //walk over region of interest and propogate blobs
00212   int nextValidID = 2;
00213   for(x = 0; x<regionWidth; x++)
00214   {
00215     for(y = 0; y<regionHeight; y++)
00216     {
00217       //if any blobs can be propogated handle them first
00218       while( !spreadablePixels.empty() )
00219       {
00220         QPoint point = spreadablePixels.pop();
00221         int id = regionOfInterest[ point.x() + point.y()*regionWidth ];
00222         
00223         pushPixel( point.x()-1, point.y()-1, id );
00224         pushPixel( point.x(),   point.y()-1, id );
00225         pushPixel( point.x()+1, point.y()-1, id );
00226         pushPixel( point.x()-1, point.y(), id );
00227         pushPixel( point.x()+1, point.y(), id );
00228         pushPixel( point.x()-1, point.y()+1, id );
00229         pushPixel( point.x(),   point.y()+1, id );
00230         pushPixel( point.x()+1, point.y()+1, id );
00231       }
00232       
00233       //if this pixel has met thresh and has not yet been assigned a unique ID,
00234       //assign it the next unique id and push all valid neighbors
00235       if( regionOfInterest[ x + y*regionWidth ] == 1 )
00236       {
00237         //print last blob stats
00238         if( nextValidID > 2)
00239         {
00240           blobIDs.push( (nextValidID - 1) );
00241           blobSizes.push( blobPixelCount );
00242           blobAspectRatios.push( ((double)(blobBottomRight.x() - blobTopLeft.x()+1)) / (blobBottomRight.y() - blobTopLeft.y()+1) );
00243         }
00244         
00245         regionOfInterest[x + y*regionWidth] = nextValidID;
00246         pushPixel( x-1, y-1, nextValidID );
00247         pushPixel( x,   y-1, nextValidID );
00248         pushPixel( x+1, y-1, nextValidID );
00249         pushPixel( x-1, y, nextValidID );
00250         pushPixel( x+1, y, nextValidID );
00251         pushPixel( x-1, y+1, nextValidID );
00252         pushPixel( x,   y+1, nextValidID );
00253         pushPixel( x+1, y+1, nextValidID );
00254         nextValidID++;        
00255         
00256         blobPixelCount = 1;
00257         blobTopLeft = QPoint( x, y );
00258         blobBottomRight = QPoint( x, y );
00259       }
00260     } //y
00261   } //x
00262   
00263   //insert last blob stats
00264   if( nextValidID > 2)
00265   {
00266     blobIDs.push( (nextValidID - 1) );
00267     blobSizes.push( blobPixelCount );
00268     blobAspectRatios.push( ((double)(blobBottomRight.x() - blobTopLeft.x()+1)) / (blobBottomRight.y() - blobTopLeft.y()+1) );
00269   }
00270 }
00271 //==============================================
00272 void sortBlobsByDecreasingSize()
00273 {
00274   blobCount = blobIDs.count();
00275   ids = new int[blobCount];
00276   sizes = new int[blobCount];
00277   ratios = new double[blobCount];
00278   
00279   int i,j;
00280   for(i=0; i<blobCount; i++)
00281   {
00282     ids[i] = blobIDs.pop();
00283     sizes[i] = blobSizes.pop();
00284     ratios[i] = blobAspectRatios.pop();
00285   }
00286   
00287   //quick and dirty bubble sort
00288   for(j = blobCount-1; j>0; j--)
00289   {
00290     for(i=0; i<j; i++)
00291     {
00292       if( sizes[i+1] > sizes[i] )
00293       {
00294         int t = sizes[i+1];
00295         sizes[i+1] = sizes[i];
00296         sizes[i] = t;
00297         
00298         t = ids[i+1];
00299         ids[i+1] = ids[i];
00300         ids[i] = t;
00301         
00302         double tR = ratios[i+1];
00303         ratios[i+1] = ratios[i];
00304         ratios[i] = tR;        
00305       }
00306     }
00307   }
00308 }
00309 //==============================================
00310 void findBestTwoBlobs()
00311 {
00312   id1 = -1;
00313   id2 = -1;
00314   int i;
00315   
00316   //special case: 2 blobs found, both larger than 1 pixel
00317   if(blobCount == 2 &&
00318      sizes[0] > 1 &&
00319      sizes[1] > 1)
00320   {
00321     id1 = ids[0];
00322     id2 = ids[1];
00323   }
00324   else
00325   {
00326     for(i=0; i<blobCount-2; i++)
00327     {
00328       //once we hit blobs that are only one pixel large stop because they are probably just noise
00329       if( sizes[i+1] <= 1 ) break;
00330       
00331       double as1 = ratios[i];
00332       double as2 = ratios[i+1];
00333 
00334       if(as1 < 1) as1 = 1.0/as1;
00335       if(as2 < 1) as2 = 1.0/as2;
00336       
00337       if( //both blobs must be semi-circular, prefer those that are wider
00338           ratios[i] > 0.75 &&   ratios[i] < 2 &&
00339           ratios[i+1] > 0.75 && ratios[i+1] < 2 &&
00340           //both blobs must be similar in shape
00341           QMAX(as2,as1)/QMIN(as2,as1) < 2 &&
00342           //both blobs must be similar in size
00343           ((double)QMAX( sizes[i], sizes[i+1] )) / QMIN( sizes[i], sizes[i+1] ) < 1.5 &&
00344           //both blobs must be above a certain thresh size, this prevents selecting blobs that are very very tiny
00345           //if only tiny blobs are around we'll end up desaturating entire region
00346           QMAX( sizes[i], sizes[i+1] ) > 20 )
00347       {
00348         id1 = ids[i];
00349         id2 = ids[i+1];
00350         break;
00351       }    
00352     }
00353   }
00354   
00355   //Comment this sectionin to see what blobs were found and selected
00356 /* cout << "-----\n";
00357   for(i=0; i<blobCount-1; i++)
00358   {
00359     if( ids[i] == id1 || ids[i] == id2 )
00360       cout << "--->";
00361     cout << "ID: " << ids[i] << "count: " << sizes[i] << " w:h: " << ratios[i] << "\n";      
00362   }*/
00363 }
00364 //==============================================
00365 bool IDedPixel( int x, int y)
00366 {
00367   if( x < topLeft.x() || y < topLeft.y() ||
00368       x > bottomRight.x() || y > bottomRight.y() )
00369     return false;
00370   
00371   int regionIndex = x - topLeft.x() + (y-topLeft.y())*regionWidth;
00372   return ( regionOfInterest[regionIndex] == id1 ||
00373            regionOfInterest[regionIndex] == id2 );
00374 }
00375 //==============================================
00376 double desaturateAlpha(int x, int y)
00377 {
00378   int n = 0;
00379   if( IDedPixel(x  ,y  ) ) n++;
00380   
00381   if(n == 1)
00382     return 1.0;
00383   
00384   if( IDedPixel(x-1,y-1) ) n++;
00385   if( IDedPixel(x  ,y-1) ) n++;
00386   if( IDedPixel(x+1,y-1) ) n++;
00387   if( IDedPixel(x-1,y  ) ) n++;
00388   if( IDedPixel(x+1,y  ) ) n++;
00389   if( IDedPixel(x-1,y+1) ) n++;
00390   if( IDedPixel(x  ,y+1) ) n++;
00391   if( IDedPixel(x+1,y+1) ) n++;
00392   
00393   if( IDedPixel(x-2,y-2) ) n++;
00394   if( IDedPixel(x-1,y-2) ) n++;
00395   if( IDedPixel(x  ,y-2) ) n++;
00396   if( IDedPixel(x+1,y-2) ) n++;
00397   if( IDedPixel(x+2,y-2) ) n++;
00398   
00399   if( IDedPixel(x-2,y-1) ) n++;
00400   if( IDedPixel(x+2,y-1) ) n++;
00401   if( IDedPixel(x-2,y  ) ) n++;
00402   if( IDedPixel(x+2,y  ) ) n++;
00403   if( IDedPixel(x-2,y+1) ) n++;
00404   if( IDedPixel(x+2,y+1) ) n++;
00405   
00406   if( IDedPixel(x-2,y+2) ) n++;
00407   if( IDedPixel(x-1,y+2) ) n++;
00408   if( IDedPixel(x  ,y+2) ) n++;
00409   if( IDedPixel(x+1,y+2) ) n++;
00410   if( IDedPixel(x+2,y+2) ) n++;
00411   
00412   
00413   return ((double)n) / 25;
00414 }
00415 //==============================================
00416 void desaturateBlobs()
00417 {
00418   //desaturate bad pixels
00419   int x, y;
00420   double r;
00421   QRgb* rgb;
00422   uchar* scanLine;
00423   for( y = QMAX( topLeft.y()-1, 0); 
00424        y<= QMIN( bottomRight.y()+1, editedImage->height()-1 ); 
00425        y++)
00426   {
00427     scanLine = editedImage->scanLine(y);
00428     for( x =  QMAX( topLeft.x()-1, 0); 
00429          x <= QMIN( bottomRight.x()+1, editedImage->width()-1 ); 
00430          x++)
00431     {      
00432       double alpha = desaturateAlpha( x, y );
00433       if( alpha > 0)
00434       {
00435         rgb = ((QRgb*)scanLine+x);
00436         
00437         r = alpha*(0.05*qRed(*rgb) + 0.6*qGreen(*rgb) + 0.3*qBlue(*rgb)) +
00438           (1-alpha)*qRed(*rgb);
00439         *rgb = qRgb( (int)r,
00440                      qGreen(*rgb),
00441                      qBlue(*rgb) );
00442       } //alpha > 0
00443     } //x
00444   } //y  
00445 }
00446 //==============================================
00447 void desaturateEntireImage(QPoint topLeftExtreme, QPoint bottomRightExtreme)
00448 {
00449   //desaturate bad pixels
00450   int x, y;
00451   QRgb* rgb;
00452   uchar* scanLine;
00453   for( y=topLeftExtreme.y(); y<=bottomRightExtreme.y(); y++)
00454   {
00455     scanLine = editedImage->scanLine(y);
00456     for( x=topLeftExtreme.x(); x<=bottomRightExtreme.x(); x++)
00457     {
00458       rgb = ((QRgb*)scanLine+x);
00459       if( qRed(*rgb) > 2*qGreen(*rgb) )
00460       {
00461         *rgb = qRgb( (int) (0.05*qRed(*rgb) + 0.6*qGreen(*rgb) + 0.3*qBlue(*rgb)),
00462                      qGreen(*rgb),
00463                      qBlue(*rgb) );
00464       } // > thresh
00465     } //x
00466   } //y
00467 }
00468 //==============================================
00469 
00470 
00471 
00472 
00473 

Generated on Sat Apr 2 05:44:04 2005 for AlbumShaper by  doxygen 1.3.9.1