Hugintrunk  0.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
hugin_hdrmerge.cpp
Go to the documentation of this file.
1 // -*- c-basic-offset: 4 -*-
2 
27 #include <hugin_config.h>
28 
29 #include <fstream>
30 #include <sstream>
31 #include <cmath>
32 #include <algorithm>
33 
34 #include <memory>
35 
36 #include <vigra/error.hxx>
37 #include <vigra/functorexpression.hxx>
38 #include <vigra/transformimage.hxx>
39 
40 #include <hugin_utils/utils.h>
41 
42 #include <vigra_ext/impexalpha.hxx>
43 #include <vigra_ext/HDRUtils.h>
45 
46 #include <getopt.h>
47 
48 #include "../deghosting/deghosting.h"
49 #include "../deghosting/khan.h"
50 
51 // define the types of images we will use.
52 // use float for RGB
53 typedef vigra::FRGBImage ImageType;
54 
55 // smart pointers to the images.
56 typedef std::shared_ptr<ImageType> ImagePtr;
57 
58 static int g_verbose = 0;
59 
60 const uint16_t OTHER_GRAY = 1;
61 
62 // load all images and apply a weighted average merge, with
63 // special cases for completely over or underexposed pixels.
64 bool mergeWeightedAverage(std::vector<std::string> inputFiles, vigra::FRGBImage& output, vigra::BImage& alpha, vigra::Rect2D& outputROI)
65 {
66  // load all images into memory
67  std::vector<ImagePtr> images;
68  std::vector<deghosting::BImagePtr> weightImages;
69  std::vector<vigra::ImageImportInfo> imageInfo;
70 
71  // read information of all images and check ROI
72  for (size_t i = 0; i < inputFiles.size(); i++)
73  {
74  vigra::ImageImportInfo info(inputFiles[i].c_str());
75  imageInfo.push_back(info);
76  if (i == 0)
77  {
78  outputROI = vigra::Rect2D(vigra::Point2D(info.getPosition()), info.size());
79  }
80  else
81  {
82  outputROI |= vigra::Rect2D(vigra::Point2D(info.getPosition()), info.size());
83  };
84  };
85 
86  for (size_t i=0; i < imageInfo.size(); i++)
87  {
88  ImagePtr img = ImagePtr(new ImageType());
89  deghosting::BImagePtr weight = deghosting::BImagePtr(new vigra::BImage());
90 
91  if (g_verbose > 0)
92  {
93  std::cout << "Loading image: " << inputFiles[i] << std::endl;
94  }
95  //calculate offset
96  vigra::Point2D offset = vigra::Point2D(imageInfo[i].getPosition());
97  offset -= outputROI.upperLeft();
98  // now load images with offset
99  img->resize(outputROI.size());
100  weight->resize(img->size().width(), img->size().height(), 0);
101  if (imageInfo[i].numBands() == 4)
102  {
103  importImageAlpha(imageInfo[i], destImage(*img, offset), destImage(*weight, offset));
104  }
105  else
106  {
107  importImage(imageInfo[i], destImage(*img, offset));
108  vigra::initImage(vigra::destImageRange(*weight, vigra::Rect2D(offset, imageInfo[i].size())), 255);
109  }
110  images.push_back(img);
111  weightImages.push_back(weight);
112  };
113 
114  output.resize(outputROI.size());
115  alpha.resize(output.width(), output.height(), 0);
116  if (g_verbose > 0)
117  {
118  std::cout << "Calculating weighted average " << std::endl;
119  }
120  // apply weighted average functor with
121  // heuristic to deal with pixels that are overexposed in all images
123 
124  // loop over all pixels in the image (very low level access)
125  for (int y=0; y < output.height(); y++)
126  {
127  for (int x=0; x < output.width(); x++)
128  {
129  waverage.reset();
130  // loop over all exposures
131  bool hasValues = false;
132  for (unsigned imgNr=0; imgNr < images.size(); imgNr++)
133  {
134  // add pixel to weighted average
135  const vigra::UInt8 weight = (*weightImages[imgNr])(x, y);
136  waverage( (*images[imgNr])(x,y), weight);
137  hasValues |= (weight > 0);
138  }
139  // get result
140  if(hasValues)
141  {
142  output(x,y) = waverage();
143  alpha(x, y) = 255;
144  };
145  }
146  }
147  return true;
148 }
149 
151 bool weightedAverageOfImageFiles(const std::vector<std::string>& inputFiles,
152  const std::vector<deghosting::FImagePtr>& weights,
153  vigra::FRGBImage& output,
154  vigra::BImage& alpha,
155  const vigra::Rect2D outputROI)
156 {
157  if(g_verbose > 0)
158  {
159  std::cout << "Merging input images" << std::endl;
160  }
161  int width = (weights[0])->width();
162  int height = (weights[0])->height();
163 
164  assert(inputFiles.size() == weights.size());
165 
166  vigra::BasicImage<vigra::NumericTraits<vigra::FRGBImage::PixelType>::Promote> weightedImg(width, height);
167  vigra::BasicImage<vigra::NumericTraits<vigra::FImage::PixelType>::Promote> weightAdded(width, height);
168  for(unsigned i = 0; i < inputFiles.size(); i++)
169  {
170  vigra::ImageImportInfo inputInfo(inputFiles[i].c_str());
171  vigra::BasicImage<vigra::NumericTraits<vigra::FRGBImage::PixelType>::Promote> tmpImg(outputROI.size());
172 
173  //load image
174  vigra::Point2D offset = vigra::Point2D(inputInfo.getPosition());
175  offset -= outputROI.upperLeft();
176  if (inputInfo.numBands() == 4)
177  {
178  vigra::BImage tmpMask(tmpImg.size());
179  vigra::importImageAlpha(inputInfo, vigra::destImage(tmpImg, offset), vigra::destImage(tmpMask, offset));
180  }
181  else
182  {
183  vigra::importImage(inputInfo, vigra::destImage(tmpImg, offset));
184  }
185 
186  //combine with weight
187  vigra::combineThreeImages(srcImageRange(tmpImg), srcImage(*(weights[i])), srcImage(weightedImg), destImage(weightedImg), vigra::functor::Arg1() *vigra::functor::Arg2() + vigra::functor::Arg3());
188  vigra::combineTwoImages(srcImageRange(weightAdded), srcImage(*(weights[i])), destImage(weightAdded), vigra::functor::Arg1() + vigra::functor::Arg2());
189  }
190  output.resize(width, height);
191  alpha.resize(width, height, 0);
192  vigra::combineTwoImages(srcImageRange(weightedImg), srcImage(weightAdded), destImage(output), vigra::functor::Arg1() / vigra::functor::Arg2());
194  vigra::Threshold<vigra::FImage::PixelType, vigra::BImage::PixelType>(1e-7f, FLT_MAX, 0, 255));
195  return true;
196 }
197 
198 static void usage(const char* name)
199 {
200  std::cout << name << ": merge overlapping images" << std::endl
201  << std::endl
202  << "hugin_hdrmerge version " << hugin_utils::GetHuginVersion() << std::endl
203  << std::endl
204  << "Usage: " << name << " [options] -o output.exr <input-files>" << std::endl
205  << "Valid options are:" << std::endl
206  << " -o|--output prefix output file" << std::endl
207  << " -m mode merge mode, can be one of: avg (default), avg_slow, khan, if avg, no" << std::endl
208  << " -i and -s options apply" << std::endl
209  << " -i iter number of iterations to execute (default is 4). Khan only" << std::endl
210  << " -s sigma standard deviation of Gaussian weighting" << std::endl
211  << " function (sigma > 0); default: 30. Khan only" << std::endl
212  << " -a set advanced settings. Possible options are:" << std::endl
213  << " f use gray images for computation. It's about two times faster" << std::endl
214  << " but it usually returns worse results." << std::endl
215  << " g use gamma 2.2 correction instead of logarithm" << std::endl
216  << " m do not scale image, NOTE: slows down process" << std::endl
217  << " -c Only consider pixels that are defined in all images (avg mode only)" << std::endl
218  << " -v|--verbose Verbose, print progress messages, repeat for" << std::endl
219  << " even more verbose output" << std::endl
220  << " -h|help Display help (this text)" << std::endl
221  << std::endl;
222 }
223 
224 
225 int main(int argc, char* argv[])
226 {
227 
228  // parse arguments
229  const char* optstring = "chvo:m:i:s:a:el";
230  static struct option longOptions[] =
231  {
232  { "output", required_argument, NULL, 'o' },
233  { "verbose", no_argument, NULL, 'v' },
234  { "help", no_argument, NULL, 'h' },
235  0
236  };
237  int c;
238 
239  g_verbose = 0;
240  std::string outputFile = "merged.exr";
241  std::string mode = "avg";
242  bool onlyCompleteOverlap = false;
243  int iterations = 4;
244  double sigma = 30;
245  uint16_t flags = 0;
246  uint16_t otherFlags = 0;
247 
248  while ((c = getopt_long(argc, argv, optstring, longOptions, nullptr)) != -1)
249  {
250  switch (c)
251  {
252  case 'm':
253  mode = optarg;
254  break;
255  case 'i':
256  iterations = atoi(optarg);
257  break;
258  case 's':
259  sigma = atof(optarg);
260  break;
261  case 'a':
262  for(char* c = optarg; *c; c++)
263  {
264  switch(*c)
265  {
266  case 'f':
267  otherFlags += OTHER_GRAY;
268  break;
269  case 'g':
270  flags += deghosting::ADV_GAMMA;
271  break;
272  case 'm':
273  flags -= deghosting::ADV_MULTIRES;
274  break;
275  default:
276  std::cerr << hugin_utils::stripPath(argv[0]) << ": unknown option" << std::endl;
277  exit(1);
278  }
279  }
280  case 'c':
281  onlyCompleteOverlap = true;
282  break;
283  case 'o':
284  outputFile = optarg;
285  break;
286  case 'v':
287  g_verbose++;
288  break;
289  case 'h':
290  usage(hugin_utils::stripPath(argv[0]).c_str());
291  return 0;
292  case ':':
293  case '?':
294  // missing argument or invalid switch
295  return 1;
296  break;
297  default:
298  // this should not happen
299  abort();
300  }
301  }//end while
302 
303  unsigned nFiles = argc - optind;
304  if (nFiles == 0)
305  {
306  std::cerr << hugin_utils::stripPath(argv[0]) << ": at least one input image needed" << std::endl;
307  return 1;
308  }
309  else if (nFiles == 1)
310  {
311  std::cout << std::endl << "Only one input image given. Copying input image to output image." << std::endl;
312  // simply copy image file
313  std::ifstream infile(argv[optind], std::ios_base::binary);
314  std::ofstream outfile(outputFile.c_str(), std::ios_base::binary);
315  outfile << infile.rdbuf();
316  return 0;
317  }
318 
319  // load all images
320  std::vector<std::string> inputFiles;
321  for (size_t i=optind; i < (size_t)argc; i++)
322  {
323  inputFiles.push_back(argv[i]);
324  }
325 
326  // output image
327  ImageType output;
328  try
329  {
330  if (mode == "avg_slow")
331  {
332  // use a weighted average, with special consideration of pixels
333  // that are completely over or underexposed in all exposures.
334  if (g_verbose > 0)
335  {
336  std::cout << "Running simple weighted avg algorithm" << std::endl;
337  }
338  vigra::BImage alpha;
339  vigra::Rect2D outputROI;
340  mergeWeightedAverage(inputFiles, output, alpha, outputROI);
341  // save output file
342  if (g_verbose > 0)
343  {
344  std::cout << "Writing " << outputFile << std::endl;
345  }
346  vigra::ImageExportInfo exinfo(outputFile.c_str());
347  exinfo.setPixelType("FLOAT");
348  exinfo.setPosition(outputROI.upperLeft());
349  exinfo.setCanvasSize(vigra::Size2D(outputROI.lowerRight().x, outputROI.lowerRight().y));
350  vigra::exportImageAlpha(srcImageRange(output), srcImage(alpha), exinfo);
351  }
352  else if (mode == "avg")
353  {
354  // apply weighted average functor with
355  // heuristic to deal with pixels that are overexposed in all images
357  // calc weighted average without loading the whole images into memory
358  reduceFilesToHDR(inputFiles, outputFile, onlyCompleteOverlap, waverage);
359  }
360  else if (mode == "khan")
361  {
362  if (g_verbose > 0)
363  {
364  std::cout << "Running Khan deghosting algorithm" << std::endl;
365  }
366  vigra::Rect2D outputROI;
367  std::vector<deghosting::FImagePtr> weights;
368 
369  if (otherFlags & OTHER_GRAY)
370  {
371  deghosting::Khan<float> deghoster(inputFiles, flags, 0, iterations, sigma, g_verbose);
372  weights = deghoster.createWeightMasks();
373  outputROI = deghoster.getOutputROI();
374  }
375  else
376  {
377  deghosting::Khan<vigra::RGBValue<float>> deghoster(inputFiles, flags, 0, iterations, sigma, g_verbose);
378  weights = deghoster.createWeightMasks();
379  outputROI = deghoster.getOutputROI();
380  };
381  vigra::BImage alpha;
382  weightedAverageOfImageFiles(inputFiles, weights, output, alpha, outputROI);
383  if (g_verbose > 0)
384  {
385  std::cout << "Writing " << outputFile << std::endl;
386  }
387  vigra::ImageExportInfo exinfo(outputFile.c_str());
388  exinfo.setPixelType("FLOAT");
389  exinfo.setPosition(outputROI.upperLeft());
390  exinfo.setCanvasSize(vigra::Size2D(outputROI.lowerRight().x, outputROI.lowerRight().y));
392  }
393  else
394  {
395  std::cerr << "Unknown merge mode, see help for a list of possible modes" << std::endl;
396  return 1;
397  }
398  }
399  catch (std::exception& e)
400  {
401  std::cerr << "caught exception: " << e.what() << std::endl;
402  abort();
403  }
404 
405  return 0;
406 }
407 
408 
const uint16_t ADV_MULTIRES
Definition: deghosting.h:53
std::shared_ptr< vigra::BImage > BImagePtr
Definition: deghosting.h:45
void transformImage(vigra::triple< SrcImageIterator, SrcImageIterator, SrcAccessor > src, vigra::triple< DestImageIterator, DestImageIterator, DestAccessor > dest, std::pair< AlphaImageIterator, AlphaAccessor > alpha, vigra::Diff2D destUL, TRANSFORM &transform, PixelTransform &pixelTransform, bool warparound, Interpolator interpol, AppBase::ProgressDisplay *progress, bool singleThreaded=false)
Transform an image into the panorama.
int g_verbose
bool mergeWeightedAverage(std::vector< std::string > inputFiles, vigra::FRGBImage &output, vigra::BImage &alpha, vigra::Rect2D &outputROI)
bool weightedAverageOfImageFiles(const std::vector< std::string > &inputFiles, const std::vector< deghosting::FImagePtr > &weights, vigra::FRGBImage &output, vigra::BImage &alpha, const vigra::Rect2D outputROI)
compute output image when given source images
void exportImageAlpha(ImageIterator image_upper_left, ImageIterator image_lower_right, ImageAccessor image_accessor, AlphaIterator alpha_upper_left, AlphaAccessor alpha_accessor, const ImageExportInfo &export_info)
Write the image and its alpha channel to a file.
vigra::pair< typename ROIImage< Image, Mask >::image_const_traverser, typename ROIImage< Image, Mask >::ImageConstAccessor > srcImage(const ROIImage< Image, Mask > &img)
Definition: ROIImage.h:300
const uint16_t OTHER_GRAY
static double sigma
virtual std::vector< FImagePtr > createWeightMasks() override
create weight masks create weight masks for masking out ghosting regions
Definition: khan.h:301
void combineThreeImages(SrcImageIterator1 src1_upperleft, SrcImageIterator1 src1_lowerright, SrcAccessor1 src1_acc, SrcImageIterator2 src2_upperleft, SrcAccessor2 src2_acc, SrcImageIterator3 src3_upperleft, SrcAccessor3 src3_acc, DestImageIterator dest_upperleft, DestAccessor dest_acc, const Functor &func)
Definition: openmp_vigra.h:288
void combineTwoImages(SrcImageIterator1 src1_upperleft, SrcImageIterator1 src1_lowerright, SrcAccessor1 src1_acc, SrcImageIterator2 src2_upperleft, SrcAccessor2 src2_acc, DestImageIterator dest_upperleft, DestAccessor dest_acc, const Functor &func)
Definition: openmp_vigra.h:249
void importImageAlpha(const ImageImportInfo &import_info, ImageIterator image_iterator, ImageAccessor image_accessor, AlphaIterator alpha_iterator, AlphaAccessor alpha_accessor, VigraTrueType)
Definition: impexalpha.hxx:240
vigra::FRGBImage ImageType
static int iterations
std::shared_ptr< ImageType > ImagePtr
vigra::pair< typename ROIImage< Image, Alpha >::image_traverser, typename ROIImage< Image, Alpha >::ImageAccessor > destImage(ROIImage< Image, Alpha > &img)
Definition: ROIImage.h:324
vigra::triple< typename ROIImage< Image, Mask >::image_const_traverser, typename ROIImage< Image, Mask >::image_const_traverser, typename ROIImage< Image, Mask >::ImageConstAccessor > srcImageRange(const ROIImage< Image, Mask > &img)
helper function for ROIImages
Definition: ROIImage.h:287
void importImageAlpha(const ImageImportInfo &import_info, ImageIterator image_iterator, ImageAccessor image_accessor, AlphaIterator alpha_iterator, AlphaAccessor alpha_accessor)
Read the image specified by the given vigra::ImageImportInfo object including its alpha channel...
Definition: impexalpha.hxx:479
const uint16_t ADV_GAMMA
Definition: deghosting.h:51
static uint16_t otherFlags
static void usage()
Definition: Main.cpp:32
std::string GetHuginVersion()
return a string with version numbers
Definition: utils.cpp:907
vigra::triple< typename ROIImage< Image, Alpha >::image_traverser, typename ROIImage< Image, Alpha >::image_traverser, typename ROIImage< Image, Alpha >::ImageAccessor > destImageRange(ROIImage< Image, Alpha > &img)
Definition: ROIImage.h:312
static void info(const char *fmt,...)
Definition: svm.cpp:95
static uint16_t flags
vigra::Rect2D getOutputROI() const
Definition: deghosting.cpp:63
std::string stripPath(const std::string &filename)
remove the path of a filename (mainly useful for gui display of filenames)
Definition: utils.cpp:160
void reduceFilesToHDR(std::vector< std::string > input, std::string output, bool onlyCompleteOverlap, Functor &reduce)
Definition: ReduceOpenEXR.h:60
int main(int argc, char *argv[])
Definition: Main.cpp:167