00001 /*! @file SIFT/app-SIFT-panorama.C Build a panorama from several overlapping images */ 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: Laurent Itti <itti@usc.edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/SIFT/app-SIFT-panorama.C $ 00035 // $Id: app-SIFT-panorama.C 14118 2010-10-09 07:36:25Z itti $ 00036 // 00037 00038 #include "Raster/Raster.H" 00039 #include "SIFT/Keypoint.H" 00040 #include "SIFT/VisualObject.H" 00041 #include "SIFT/VisualObjectMatch.H" 00042 #include "Image/ImageSet.H" 00043 #include "Util/Timer.H" 00044 #include <iostream> 00045 00046 //! Stitch images into a panorama. 00047 /*! The images given on the command-line should overlap by some amount 00048 and should be taken in sequence; e.g., the first one may be the 00049 leftmost, the second one shifted slightly rightwards compared to the 00050 first, the third slightly shifted rightwards compared to the second, 00051 etc. Indeed this program is very simple and will just stitch the 00052 first image to the second, then the second to the third, etc, 00053 without enforcing any further consistency. */ 00054 int main(const int argc, const char **argv) 00055 { 00056 MYLOGVERB = LOG_DEBUG; 00057 00058 // check command-line args: 00059 if (argc < 4) 00060 LFATAL("USAGE: app-SIFT-panorama <result.png> " 00061 "<image1.png> ... <imageN.png>"); 00062 00063 // loop over the images and get all the SIFTaffines: 00064 ImageSet< PixRGB<byte> > images; 00065 00066 const char *nam1 = argv[2]; 00067 Image< PixRGB<byte> > im1 = Raster::ReadRGB(nam1); 00068 images.push_back(im1); 00069 rutz::shared_ptr<VisualObject> vo1(new VisualObject(nam1, "", im1)); 00070 std::vector<SIFTaffine> affines; 00071 SIFTaffine comboaff; // default constructor is identity 00072 affines.push_back(comboaff); 00073 int minx = 0, miny = 0, maxx = im1.getWidth()-1, maxy = im1.getHeight()-1; 00074 00075 for (int i = 3; i < argc; i ++) 00076 { 00077 const char *nam2 = argv[i]; 00078 Image< PixRGB<byte> > im2 = Raster::ReadRGB(nam2); 00079 images.push_back(im2); 00080 rutz::shared_ptr<VisualObject> vo2(new VisualObject(nam2, "", im2)); 00081 00082 // compute the matching keypoints: 00083 Timer tim(1000000); 00084 VisualObjectMatch match(vo1, vo2, VOMA_SIMPLE); 00085 uint64 t = tim.get(); 00086 00087 LINFO("Found %u matches between %s and %s in %.3fms", 00088 match.size(), nam1, nam2, float(t) * 0.001F); 00089 00090 // let's prune the matches: 00091 uint np = match.prune(); 00092 LINFO("Pruned %u outlier matches.", np); 00093 00094 // show our final affine transform: 00095 SIFTaffine aff = match.getSIFTaffine(); 00096 std::cerr<<aff; 00097 00098 // compose with the previous affines and store: 00099 comboaff = comboaff.compose(aff); 00100 affines.push_back(comboaff); 00101 00102 // update panorama boundaries, using the inverse combo aff to 00103 // find the locations of the four corners of our image in the 00104 // panorama: 00105 if (comboaff.isInversible() == false) LFATAL("Oooops, singular affine!"); 00106 SIFTaffine iaff = comboaff.inverse(); 00107 const float ww = float(im2.getWidth() - 1); 00108 const float hh = float(im2.getHeight() - 1); 00109 float xx, yy; int x, y; 00110 00111 iaff.transform(0.0F, 0.0F, xx, yy); x = int(xx+0.5F); y = int(yy+0.5F); 00112 if (x < minx) minx = x; if (x > maxx) maxx = x; 00113 if (y < miny) miny = y; if (y > maxy) maxy = y; 00114 00115 iaff.transform(ww, 0.0F, xx, yy); x = int(xx+0.5F); y = int(yy+0.5F); 00116 if (x < minx) minx = x; if (x > maxx) maxx = x; 00117 if (y < miny) miny = y; if (y > maxy) maxy = y; 00118 00119 iaff.transform(0.0F, hh, xx, yy); x = int(xx+0.5F); y = int(yy+0.5F); 00120 if (x < minx) minx = x; if (x > maxx) maxx = x; 00121 if (y < miny) miny = y; if (y > maxy) maxy = y; 00122 00123 iaff.transform(ww, hh, xx, yy); x = int(xx+0.5F); y = int(yy+0.5F); 00124 if (x < minx) minx = x; if (x > maxx) maxx = x; 00125 if (y < miny) miny = y; if (y > maxy) maxy = y; 00126 00127 // get ready for next pair: 00128 im1 = im2; vo1 = vo2; nam1 = nam2; 00129 } 00130 00131 // all right, allocate the panorama: 00132 LINFO("x = [%d .. %d], y = [%d .. %d]", minx, maxx, miny, maxy); 00133 const int w = maxx - minx + 1, h = maxy - miny + 1; 00134 LINFO("Allocating %dx%d panorama...", w, h); 00135 if (w < 2 || h < 2) LFATAL("Oooops, panorama too small!"); 00136 Image< PixRGB<byte> > pano(w, h, ZEROS); 00137 Image< PixRGB<byte> >::iterator p = pano.beginw(); 00138 00139 // let's stitch the images into the panorama. This code is similar 00140 // to that in VisualObjectMatch::getTransfTestImage() but modified 00141 // for a large panorama and many images: 00142 for (int j = 0; j < h; j ++) 00143 for (int i = 0; i < w; i ++) 00144 { 00145 // compute the value that should go into the current panorama 00146 // pixel based on all the images and affines; this is very 00147 // wasteful and may be optimized later: 00148 PixRGB<int> val(0); uint n = 0U; 00149 00150 for (uint k = 0; k < images.size(); k ++) 00151 { 00152 // get transformed coordinates for image k: 00153 float u, v; 00154 affines[k].transform(float(i + minx), float(j + miny), u, v); 00155 00156 // if we are within bounds of image k, accumulate the pix value: 00157 if (images[k].coordsOk(u, v)) 00158 { 00159 val += PixRGB<int>(images[k].getValInterp(u, v)); 00160 ++ n; 00161 } 00162 } 00163 00164 if (n > 0) *p = PixRGB<byte>(val / n); 00165 00166 ++ p; 00167 } 00168 00169 // save final panorama: 00170 Raster::WriteRGB(pano, std::string(argv[1])); 00171 LINFO("Done."); 00172 00173 return 0; 00174 }