Hugin trunk 0.1
Loading...
Searching...
No Matches
align_image_stack.cpp
Go to the documentation of this file.
1// -*- c-basic-offset: 4 -*-
2
27#include <hugin_config.h>
28#include <fstream>
29#include <sstream>
30#include <iostream>
31
32#include <vigra/error.hxx>
33#include <vigra/cornerdetection.hxx>
34#include <vigra/localminmax.hxx>
35#include <hugin_utils/utils.h>
36
37#include <vigra_ext/Pyramid.h>
41
42#include <panodata/Panorama.h>
46#include <nona/Stitcher.h>
48#include <lensdb/LensDB.h>
49
50#include <getopt.h>
51
53#include <tiff.h>
54
55#ifdef __APPLE__
56#include <hugin_config.h>
57#include <mach-o/dyld.h> /* _NSGetExecutablePath */
58#include <limits.h> /* PATH_MAX */
59#include <libgen.h> /* dirname */
60#endif
61
62int g_verbose = 0;
63
64static void usage(const char* name)
65{
66 std::cout << name << ": align overlapping images for HDR creation" << std::endl
67 << "align_image_stack version " << hugin_utils::GetHuginVersion() << std::endl
68 << std::endl
69 << "Usage: " << name << " [options] input files" << std::endl
70 << "Valid options are:" << std::endl
71 << " Modes of operation:" << std::endl
72 << " -p file Output .pto file (useful for debugging, or further refinement)" << std::endl
73 << " -a prefix align images, output as prefix_xxxx.tif" << std::endl
74 << " -o output merge images to HDR, generate output.hdr" << std::endl
75 << " Modifiers" << std::endl
76 << " -v Verbose, print progress messages. Repeat for higher verbosity" << std::endl
77 << " -e Assume input images are full frame fish eye (default: rectilinear)" << std::endl
78 << " -t num Remove all control points with an error higher than num pixels" << std::endl
79 << " (default: 3)" << std::endl
80 << " --corr=num Correlation threshold for identifing control points" << std::endl
81 << " (default: 0.9)" << std::endl
82 << " -f HFOV approximate horizontal field of view of input images," << std::endl
83 << " use if EXIF info not complete" << std::endl
84 << " -m Optimize field of view for all images, except for first." << std::endl
85 << " Useful for aligning focus stacks with slightly" << std::endl
86 << " different magnification." << std::endl
87 << " -d Optimize radial distortion for all images, except for first." << std::endl
88 << " -i Optimize image center shift for all images, except for first." << std::endl
89 << " -x Optimize X coordinate of the camera position." << std::endl
90 << " -y Optimize Y coordinate of the camera position." << std::endl
91 << " -z Optimize Z coordinate of the camera position." << std::endl
92 << " Useful for aligning more distorted images." << std::endl
93 << " -S Assume stereo images - allow horizontal shift of control points." << std::endl
94 << " -A Align stereo window - assumes -S." << std::endl
95 << " -P Align stereo window with pop-out effect - assumes -S." << std::endl
96 << " -C Auto crop the image to the area covered by all images." << std::endl
97 << " -c num number of control points (per grid) to create" << std::endl
98 << " between adjacent images (default: 8)" << std::endl
99 << " -l Assume linear input files" << std::endl
100 << " -s scale Scale down image by 2^scale (default: 1 [2x downsampling])" << std::endl
101 << " -g gsize Break image into a rectangular grid (gsize x gsize) and attempt" << std::endl
102 << " to find num control points in each section" << std::endl
103 << " (default: 5 [5x5 grid] )" << std::endl
104 << " --distortion Try to load distortion information from lens database" << std::endl
105 << " --use-given-order Use the image order as given from command line" << std::endl
106 << " (By default images will be sorted by exposure values.)" << std::endl
107 << " --align-to-first Align all images to the first one. By default" << std::endl
108 << " align_image_stack matches all image pairs" << std::endl
109 << " consecutive." << std::endl
110 << " This implies also the --use-given-order option" << std::endl
111 << " --dont-remap-ref Don't output the remapped reference image" << std::endl
112 << " --gpu Use GPU for remapping" << std::endl
113 << " -h Display help (this text)" << std::endl
114 << std::endl;
115}
116
117typedef std::multimap<double, vigra::Diff2D> MapPoints;
119
120namespace detail
121{
122 template <class ImageType>
124 const ImageType& rightImg, const vigra::Diff2D searchPos, const int searchWidth, vigra::VigraTrueType)
125 {
126 return vigra_ext::PointFineTune(leftImg, leftImg.accessor(),
128 rightImg, rightImg.accessor(),
130 };
131
132 template <class ImageType>
134 const ImageType& rightImg, const vigra::Diff2D searchPos, const int searchWidth, vigra::VigraFalseType)
135 {
137 vigra::RGBToGrayAccessor<typename ImageType::value_type>(),
139 rightImg, vigra::RGBToGrayAccessor<typename ImageType::value_type>(),
141 };
142}
143template <class ImageType>
145 int img1, const ImageType& leftImg, const ImageType& leftImgOrig,
146 int img2, const ImageType& rightImg, const ImageType& rightImgOrig,
147 const MapPoints& points, unsigned nPoints, int pyrLevel, int templWidth, int sWidth, double scaleFactor, double corrThresh, bool stereo)
148{
149 typedef typename ImageType::value_type ImageValueType;
150 typedef typename vigra::NumericTraits<ImageValueType>::isScalar is_scalar;
151
152 unsigned nGood = 0;
153 // loop over all points, starting with the highest corner score
154 for (MapPoints::const_reverse_iterator it = points.rbegin(); it != points.rend(); ++it)
155 {
156 if (nGood >= nPoints)
157 {
158 // we have enough points, stop
159 break;
160 }
161
163 rightImg, it->second, sWidth, is_scalar());
164 if (g_verbose > 2)
165 {
166 std::ostringstream buf;
167 buf << "I :" << (*it).second.x* scaleFactor << "," << (*it).second.y* scaleFactor << " -> "
168 << res.maxpos.x* scaleFactor << "," << res.maxpos.y* scaleFactor << ": corr coeff: " << res.maxi
169 << " curv:" << res.curv.x << " " << res.curv.y << std::endl;
170 std::cout << buf.str();
171 }
172 if (res.maxi < corrThresh)
173 {
174 DEBUG_DEBUG("low correlation: " << res.maxi << " curv: " << res.curv);
175 continue;
176 }
177
178 if (pyrLevel > 0)
179 {
180 res = detail::FineTunePoint(leftImgOrig, vigra::Diff2D((*it).second.x * scaleFactor, (*it).second.y * scaleFactor),
181 templWidth, rightImgOrig, vigra::Diff2D(res.maxpos.x * scaleFactor, res.maxpos.y * scaleFactor),
182 scaleFactor, is_scalar());
183
184 if (g_verbose > 2)
185 {
186 std::ostringstream buf;
187 buf << "II>" << (*it).second.x* scaleFactor << "," << (*it).second.y* scaleFactor << " -> "
188 << res.maxpos.x << "," << res.maxpos.y << ": corr coeff: " << res.maxi
189 << " curv:" << res.curv.x << " " << res.curv.y << std::endl;
190 std::cout << buf.str();
191 }
192 if (res.maxi < corrThresh)
193 {
194 DEBUG_DEBUG("low correlation in pass 2: " << res.maxi << " curv: " << res.curv);
195 continue;
196 }
197 }
198
199 nGood++;
200 // add control point
201 HuginBase::ControlPoint p(img1, (*it).second.x * scaleFactor,
202 (*it).second.y * scaleFactor,
203 img2, res.maxpos.x,
204 res.maxpos.y,
206 {
208 pano.addCtrlPoint(p);
209 };
210 }
211 if (g_verbose > 0)
212 {
213 std::ostringstream buf;
214 buf << "Number of good matches: " << nGood << ", bad matches: " << points.size() - nGood << std::endl;
215 std::cout << buf.str();
216 }
217};
218
219namespace detail
220{
221 template <class ImageType>
222 void FindInterestPointsPartial(const ImageType& image, const vigra::Rect2D& rect, double scale,
223 unsigned nPoints, std::multimap<double, vigra::Diff2D> &points, vigra::VigraTrueType)
224 {
225 vigra_ext::findInterestPointsPartial(vigra::srcImageRange(image), rect, scale, nPoints, points);
226 };
227
228 template <class ImageType>
229 void FindInterestPointsPartial(const ImageType& image, const vigra::Rect2D& rect, double scale,
230 unsigned nPoints, std::multimap<double, vigra::Diff2D> &points, vigra::VigraFalseType)
231 {
232 typedef typename ImageType::value_type ImageValueType;
233 vigra_ext::findInterestPointsPartial(vigra::srcImageRange(image, vigra::RGBToGrayAccessor<ImageValueType>()), rect, scale, nPoints, points);
234 };
235};
236
237template <class ImageType>
238void createCtrlPoints(HuginBase::Panorama& pano, int img1, const ImageType& leftImg, const ImageType& leftImgOrig, int img2, const ImageType& rightImg, const ImageType& rightImgOrig, int pyrLevel, double scale, unsigned nPoints, unsigned grid, double corrThresh = 0.9, bool stereo = false)
239{
240 typedef typename ImageType::value_type ImageValueType;
241 typedef typename vigra::NumericTraits<ImageValueType>::isScalar is_scalar;
242
244 // find interesting corners using harris corner detector
245 if (stereo)
246 {
247 // add one vertical control point to keep the images aligned vertically
248 HuginBase::ControlPoint p(img1, 0, 0, img2, 0, 0, HuginBase::ControlPoint::X);
249 pano.addCtrlPoint(p);
250 }
251
252 if (g_verbose > 0)
253 {
254 std::cout << "Trying to find " << nPoints << " corners... " << std::endl;
255 }
256
257 vigra::Size2D size(leftImg.width(), leftImg.height());
258 std::vector<vigra::Rect2D> rects;
259 for (unsigned party = 0; party < grid; party++)
260 {
261 for (unsigned partx = 0; partx < grid; partx++)
262 {
263 // run corner detector only in current sub-region (saves a lot of memory for big images)
264 vigra::Rect2D rect(partx*size.x / grid, party*size.y / grid,
265 (partx + 1)*size.x / grid, (party + 1)*size.y / grid);
266 rect &= vigra::Rect2D(size);
267 if (rect.width()>0 && rect.height()>0)
268 {
269 rects.push_back(rect);
270 };
271 };
272 };
273
274 const double scaleFactor = 1 << pyrLevel;
275 const long templWidth = 20;
276 const long sWidth = 100;
277
278 #pragma omp parallel for schedule(dynamic)
279 for (int i = 0; i < rects.size(); ++i)
280 {
282 vigra::Rect2D rect(rects[i]);
284 FineTuneInterestPoints(pano, img1, leftImg, leftImgOrig, img2, rightImg, rightImgOrig, points, nPoints, pyrLevel, templWidth, sWidth, scaleFactor, corrThresh, stereo);
285 };
286
287 if (stereo)
288 {
289 // add some additional control points around image edges
290 // this is useful for better results - images are more distorted around edges
291 // and also for stereoscopic window adjustment - it must be alligned according to
292 // the nearest object which crosses the edge and these control points helps to find it.
294 MapPoints down;
295 MapPoints left;
296 MapPoints right;
297 int xstep = leftImg.size().x / (nPoints + 1);
298 int ystep = leftImg.size().y / (nPoints + 1);
299 for (int k = 6; k >= 0; --k)
300 {
301 for (int j = 0; j < 2; ++j)
302 {
303 for (unsigned int i = 0; i < nPoints; ++i)
304 {
305 up.insert(std::make_pair(0, vigra::Diff2D(j * xstep / 2 + i * xstep, 1 + k * 10)));
306 down.insert(std::make_pair(0, vigra::Diff2D(j * xstep / 2 + i * xstep, leftImg.size().y - 2 - k * 10)));
307 left.insert(std::make_pair(0, vigra::Diff2D(1 + k * 10, j * ystep / 2 + i * ystep)));
308 right.insert(std::make_pair(0, vigra::Diff2D(leftImg.size().x - 2 - k * 10, j * ystep / 2 + i * ystep)));
309 };
310 };
311 };
312 // execute parallel
313 #pragma omp parallel sections
314 {
315 #pragma omp section
316 {
317 FineTuneInterestPoints(pano, img1, leftImg, leftImgOrig, img2, rightImg, rightImgOrig, up, nPoints, pyrLevel, templWidth, sWidth, corrThresh, scaleFactor, stereo);
318 }
319 #pragma omp section
320 {
321 FineTuneInterestPoints(pano, img1, leftImg, leftImgOrig, img2, rightImg, rightImgOrig, down, nPoints, pyrLevel, templWidth, sWidth, corrThresh, scaleFactor, stereo);
322 }
323 #pragma omp section
324 {
325 FineTuneInterestPoints(pano, img1, leftImg, leftImgOrig, img2, rightImg, rightImgOrig, left, nPoints, pyrLevel, templWidth, sWidth, corrThresh, scaleFactor, stereo);
326 }
327 #pragma omp section
328 {
329 FineTuneInterestPoints(pano, img1, leftImg, leftImgOrig, img2, rightImg, rightImgOrig, right, nPoints, pyrLevel, templWidth, sWidth, corrThresh, scaleFactor, stereo);
330 }
331 }
332 }
333};
334
335void alignStereoWindow(HuginBase::Panorama& pano, bool pop_out)
336{
338 std::vector<HuginBase::PTools::Transform*> transTable(pano.getNrOfImages());
339
340 std::vector<int> max_i(pano.getNrOfImages() - 1, -1); // index of a point with biggest shift
341 std::vector<int> max_i_b(pano.getNrOfImages() - 1, -1); // the same as above, only border points considered
342 std::vector<double> max_dif(pano.getNrOfImages() - 1, -1000000000); // value of the shift
343 std::vector<double> max_dif_b(pano.getNrOfImages() - 1, -1000000000); // the same as above, only border points considered
344
345 for (int i=0; i < pano.getNrOfImages(); i++)
346 {
348 transTable[i]->createInvTransform(pano.getImage(i), pano.getOptions());
349 }
350
351 double rbs = 0.1; // relative border size
352
353 for (int i=0; i < (int)cps.size(); i++)
354 {
355 if (cps[i].mode == HuginBase::ControlPoint::X)
356 {
357 if (max_i[cps[i].image1Nr] < 0) // first control point for given pair
358 {
359 max_i[cps[i].image1Nr] = i; // use it as a fallback in case on other points exist
360 }
361 continue;
362 }
363
364 vigra::Size2D size1 = pano.getImage(cps[i].image1Nr).getSize();
365 vigra::Size2D size2 = pano.getImage(cps[i].image2Nr).getSize();
366
367 vigra::Rect2D rect1(size1);
368 vigra::Rect2D rect2(size2);
369
370 rect1.addBorder(-size1.width() * rbs, -size1.height() * rbs);
371 rect2.addBorder(-size2.width() * rbs, -size2.height() * rbs);
372
373
374 double xt1, yt1, xt2, yt2;
375 if(!transTable[cps[i].image1Nr]->transformImgCoord(xt1, yt1, cps[i].x1, cps[i].y1))
376 {
377 continue;
378 }
379 if(!transTable[cps[i].image2Nr]->transformImgCoord(xt2, yt2, cps[i].x2, cps[i].y2))
380 {
381 continue;
382 }
383
384 double dif = xt2 - xt1;
385 if (dif > max_dif[cps[i].image1Nr])
386 {
387 max_dif[cps[i].image1Nr] = dif;
388 max_i[cps[i].image1Nr] = i;
389 }
390
391 if (!(rect1.contains(vigra::Point2D(cps[i].x1, cps[i].y1)) &&
392 rect2.contains(vigra::Point2D(cps[i].x2, cps[i].y2))))
393 {
394 // the same for border points only
395 if (dif > max_dif_b[cps[i].image1Nr])
396 {
397 max_dif_b[cps[i].image1Nr] = dif;
398 max_i_b[cps[i].image1Nr] = i;
399 }
400 }
401 }
402
403 for (int i=0; i < pano.getNrOfImages(); i++)
404 {
405 delete transTable[i];
406 }
407
408 for (int i=0; i < (int)max_i.size(); i++)
409 {
410 if (pop_out && (max_i_b[i] >= 0)) // check points at border
411 {
413 }
414 else if (max_i[i] >= 0) // no points at border - use any point
415 {
417 }
418 else
419 {
420 //no points at all - should not happen
421 }
422
423 }
424
426 for (int i=0; i < (int)cps.size(); i++)
427 {
428 if (cps[i].mode != HuginBase::ControlPoint::X) // remove the vertical control lines, X_Y points replaces them
429 {
430 newCPs.push_back(cps[i]);
431 }
432 }
433
434 pano.setCtrlPoints(newCPs);
435}
436
438{
441 cropPano.run();
442
443 vigra::Rect2D roi=cropPano.getResultOptimalROI();
444 //set the ROI - fail if the right/bottom is zero, meaning all zero
445 if(!roi.isEmpty())
446 {
448 opt.setROI(roi);
449 pano.setOptions(opt);
450 std::cout << "Set crop size to " << roi.left() << "," << roi.top() << "," << roi.right() << "," << roi.bottom() << std::endl;
451 }
452 else
453 {
454 std::cout << "Could not find best crop rectangle for image" << std::endl;
455 };
456}
457
459{
461 {
463 corrThresh = 0.9;
464 nPoints = 8;
465 grid = 5;
466 hfov = 0;
467 pyrLevel = 1;
468 linear = false; // Assume linear input files if true
469 optHFOV = false;
470 optDistortion = false;
471 optCenter = false;
472 optX = false;
473 optY = false;
474 optZ = false;
475 stereo = false;
476 stereo_window = false;
477 pop_out = false;
478 crop = false;
479 fisheye = false;
480 gpu = false;
481 loadDistortion = false;
482 sortImagesByEv = true;
483 alignToFirst = false;
484 dontRemapRef = false;
485 }
486
490 int grid; // Partition images into grid x grid subregions, each with npoints
491 double hfov;
492 bool linear;
496 bool optX;
497 bool optY;
498 bool optZ;
500 bool stereo;
503 bool crop;
504 bool gpu;
510 std::string alignedPrefix;
511 std::string ptoFile;
512 std::string hdrFile;
513 std::string basename;
514};
515
516// dummy panotools progress functions
517static int ptProgress(int command, char* argument)
518{
519 return 1;
520}
521static int ptinfoDlg(int command, char* argument)
522{
523 return 1;
524}
525
527{
528public:
529 explicit SortImageVectorEV(const HuginBase::Panorama* pano) : m_pano(pano) {};
530 bool operator()(const unsigned int i, const unsigned int j)
531 {
532 return m_pano->getImage(i).getExposureValue() > m_pano->getImage(j).getExposureValue();
533 };
534private:
536};
537
538template <class PixelType>
539int main2(std::vector<std::string> files, Parameters param)
540{
541 typedef vigra::BasicImage<PixelType> ImageType;
542 try
543 {
546
547 // add the first image.to the panorama object
549 srcImg.setFilename(files[0]);
550
551 if (param.fisheye)
552 {
554 }
556 srcImg.applyEXIFValues();
557 // for maximum and minimum exposure value of all images
558 double maxEv = 0;
559 double minEv = 0;
560 if (param.sortImagesByEv)
561 {
562 if (fabs(srcImg.getExposureValue()) < 1E-6)
563 {
564 // no exposure values found in file, don't sort images
565 param.sortImagesByEv = false;
566 }
567 else
568 {
569 maxEv = srcImg.getExposureValue();
570 minEv = srcImg.getExposureValue();
571 };
572 };
573 // disable autorotate
574 srcImg.setRoll(0);
575 if (srcImg.getSize().x == 0 || srcImg.getSize().y == 0)
576 {
577 std::cerr << "Could not decode image: " << files[0] << "Unsupported image file format" << std::endl;
578 return 1;
579 }
580
581 if(param.loadDistortion)
582 {
583 if(srcImg.readDistortionFromDB())
584 {
585 std::cout << "\tRead distortion data from lens database." << std::endl;
586 }
587 else
588 {
589 std::cout << "\tNo valid distortion data found in lens database." << std::endl;
590 }
591 }
592
593 // use hfov specified by user.
594 if (param.hfov > 0)
595 {
596 srcImg.setHFOV(param.hfov);
597 }
598 else if (srcImg.getCropFactor() == 0)
599 {
600 // could not read HFOV, assuming default: 50
601 srcImg.setHFOV(50);
602 }
603
604 if (param.linear)
605 {
607 if (g_verbose>0)
608 {
609 std::cout << "Using linear response" << std::endl;
610 }
611 }
612
613 pano.addImage(srcImg);
614
615 // setup output to be exactly similar to input image
617
618 if (param.fisheye)
619 {
621 }
622 else
623 {
625 }
626 opts.setHFOV(srcImg.getHFOV(), false);
627 opts.setWidth(srcImg.getSize().x, false);
628 opts.setHeight(srcImg.getSize().y);
629 // output to tiff format
631 opts.tiff_saveROI = false;
632 // m estimator, to be more robust against points on moving objects
633 opts.huberSigma = 2;
634 // save also exposure value of first image
635 opts.outputExposureValue = srcImg.getExposureValue();
636 pano.setOptions(opts);
637
638 // variables that should be optimized
639 // optimize nothing in the first image
640 HuginBase::OptimizeVector optvars(1);
641 std::vector<unsigned int> images;
642 images.push_back(0);
643
645
646 // loop to add images.
647 for (int i = 1; i < (int) files.size(); i++)
648 {
649 // add next image.
650 srcImg.setFilename(files[i]);
651 // reset size to force new detection of image size
652 srcImg.setSize(vigra::Size2D());
653 srcImg.readEXIF();
654 srcImg.applyEXIFValues();
655 if (srcImg.getSize().x == 0 || srcImg.getSize().y == 0)
656 {
657 std::cerr << "Could not decode image: " << files[i] << "Unsupported image file format" << std::endl;
658 return 1;
659 }
660 if (pano.getImage(0).getSize() != srcImg.getSize())
661 {
662 std::cerr << "Images have different sizes." << std::endl
663 << files[0] << " has " << pano.getImage(0).getSize() << " pixel, while " << std::endl
664 << files[i] << " has " << srcImg.getSize() << " pixel." << std::endl
665 << "This is not supported. Align_image_stack works only with images of the same size." << std::endl;
666 return 1;
667 };
668 if (param.sortImagesByEv)
669 {
670 const double exposureValue = srcImg.getExposureValue();
671 if (exposureValue > maxEv)
672 {
674 };
675 if (exposureValue < minEv)
676 {
678 };
679 };
680
681 if(param.loadDistortion)
682 {
683 if(srcImg.readDistortionFromDB())
684 {
685 std::cout << "\tRead distortion data from lens database." << std::endl;
686 }
687 else
688 {
689 std::cout << "\tNo valid distortion data found in lens database." << std::endl;
690 }
691 }
692
693 if (param.hfov > 0)
694 {
695 srcImg.setHFOV(param.hfov);
696 }
697 else if (srcImg.getCropFactor() == 0)
698 {
699 // could not read HFOV, assuming default: 50
700 srcImg.setHFOV(50);
701 }
702
703 int imgNr = pano.addImage(srcImg);
704 variable_groups.update();
705 // each image shares the same lens.
706 variable_groups.getLenses().switchParts(imgNr, 0);
707 // unlink HFOV?
708 if (param.optHFOV)
709 {
710 pano.unlinkImageVariableHFOV(0);
711 }
712 if (param.optDistortion)
713 {
714 pano.unlinkImageVariableRadialDistortion(0);
715 }
716 if (param.optCenter)
717 {
718 pano.unlinkImageVariableRadialDistortionCenterShift(0);
719 }
720 // All images are in the same stack: Link the stack variable.
721 pano.linkImageVariableStack(0, imgNr);
722 images.push_back(i);
723 // optimize yaw, roll, pitch
724 std::set<std::string> vars;
725 vars.insert("y");
726 vars.insert("p");
727 vars.insert("r");
728 if (param.optHFOV)
729 {
730 vars.insert("v");
731 }
732 if (param.optDistortion)
733 {
734 vars.insert("a");
735 vars.insert("b");
736 vars.insert("c");
737 }
738 if (param.optCenter)
739 {
740 vars.insert("d");
741 vars.insert("e");
742 }
743 if (param.optX)
744 {
745 vars.insert("TrX");
746 }
747 if (param.optY)
748 {
749 vars.insert("TrY");
750 }
751 if (param.optZ)
752 {
753 vars.insert("TrZ");
754 }
755 optvars.push_back(vars);
756
757 };
758
759 // sort now all images by exposure value
760 if (param.sortImagesByEv)
761 {
762 // if exposure value spread is too small disable sorting
763 // this can happen e.g. for focus stacks
764 if (maxEv - minEv > 0.05)
765 {
766 std::sort(images.begin(), images.end(), SortImageVectorEV(&pano));
767 }
768 else
769 {
770 param.sortImagesByEv = false;
771 std::cout << "WARNING: Spread of exposure values is very small." << std::endl
772 << " Disabling sorting images by exposure value." << std::endl << std::endl;
773 };
774 };
775
776 // load first image
777 vigra::ImageImportInfo firstImgInfo(pano.getSrcImage(images[0]).getFilename().c_str());
778
779 // original size
781 // rescale image
782 ImageType* leftImg = new ImageType();
783 {
784 if(firstImgInfo.numExtraBands() == 1)
785 {
786 vigra::BImage alpha(firstImgInfo.size());
787 vigra::importImageAlpha(firstImgInfo, destImage(*leftImgOrig), destImage(alpha));
788 }
789 else if (firstImgInfo.numExtraBands() == 0)
790 {
791 vigra::importImage(firstImgInfo, destImage(*leftImgOrig));
792 }
793 else
794 {
795 vigra_fail("Images with multiple extra (alpha) channels not supported");
796 }
798 }
799
800
801 ImageType* rightImg = new ImageType(leftImg->size());
803 // loop to add control points between them.
804 for (int i = 1; i < (int) images.size(); i++)
805 {
806 if (g_verbose > 0)
807 {
808 if (param.alignToFirst)
809 {
810 std::cout << "Creating control points between " << pano.getSrcImage(images[0]).getFilename().c_str() << " and " <<
811 pano.getSrcImage(images[i]).getFilename().c_str() << std::endl;
812 }
813 else
814 {
815 std::cout << "Creating control points between " << pano.getSrcImage(images[i - 1]).getFilename().c_str() << " and " <<
816 pano.getSrcImage(images[i]).getFilename().c_str() << std::endl;
817 };
818 }
819
820 // load the actual image data of the next image
821 vigra::ImageImportInfo nextImgInfo(pano.getSrcImage(images[i]).getFilename().c_str());
822 assert(nextImgInfo.size() == firstImgInfo.size());
823 {
824 if (nextImgInfo.numExtraBands() == 1)
825 {
826 vigra::BImage alpha(nextImgInfo.size());
827 vigra::importImageAlpha(nextImgInfo, destImage(*rightImgOrig), destImage(alpha));
828 }
829 else if (nextImgInfo.numExtraBands() == 0)
830 {
831 vigra::importImage(nextImgInfo, destImage(*rightImgOrig));
832 }
833 else
834 {
835 vigra_fail("Images with multiple extra (alpha) channels not supported");
836 }
838 }
839
840 // add control points.
841 // work on smaller images
842 // TODO: or use a fast interest point operator.
843 if (param.alignToFirst)
844 {
845 createCtrlPoints(pano, 0, *leftImg, *leftImgOrig, images[i], *rightImg, *rightImgOrig, param.pyrLevel, 2, param.nPoints, param.grid, param.corrThresh, param.stereo);
846 vigra::initImage(vigra::destImageRange(*rightImg), vigra::NumericTraits<PixelType>::zero());
847 vigra::initImage(vigra::destImageRange(*rightImgOrig), vigra::NumericTraits<PixelType>::zero());
848 }
849 else
850 {
851 createCtrlPoints(pano, images[i - 1], *leftImg, *leftImgOrig, images[i], *rightImg, *rightImgOrig, param.pyrLevel, 2, param.nPoints, param.grid, param.corrThresh, param.stereo);
852
853 // swap images;
854 delete leftImg;
855 delete leftImgOrig;
858 rightImg = new ImageType(leftImg->size());
859 rightImgOrig = new ImageType(leftImgOrig->size());
860 };
861 }
862 delete leftImg;
863 delete rightImg;
864 delete leftImgOrig;
865 delete rightImgOrig;
866
867 // optimize everything.
868 pano.setOptimizeVector(optvars);
869 // disable optimizer progress messages if -v not given
870 if (g_verbose == 0)
871 {
874 };
876
877 // need to do some basic outlier pruning.
878 // remove all points with error higher than a specified threshold
879 if (param.cpErrorThreshold > 0)
880 {
883 for (int i=0; i < (int)cps.size(); i++)
884 {
885 if (cps[i].error < param.cpErrorThreshold ||
886 cps[i].mode == HuginBase::ControlPoint::X) // preserve the vertical control point for stereo alignment
887 {
888 newCPs.push_back(cps[i]);
889 }
890 }
891 if (g_verbose > 0)
892 {
893 std::cout << "Ctrl points before pruning: " << cps.size() << ", after: " << newCPs.size() << std::endl;
894 }
895 pano.setCtrlPoints(newCPs);
896 if (param.stereo_window)
897 {
898 alignStereoWindow(pano, param.pop_out);
899 }
900 if (param.optHFOV)
901 {
902 // check that reference image contains cp, otherwise optimizing hfov
903 // goes haywire
904 if (pano.getCtrlPointsForImage(0).empty())
905 {
906 std::cerr << std::endl << "After control points pruning reference images has no control" << std::endl
907 << "points Optimizing field of view in this case results in undefined" << std::endl
908 << "behaviour. Increase error distance (-t parameter), tweak cp" << std::endl
909 << "detection parameters or don't optimize HFOV." << std::endl
910 << std::endl
911 << "Exiting..." << std::endl;
912 return 1;
913 };
914 };
915 // reoptimize
916 // check that we have enough control points left
917 if (pano.getNrOfCtrlPoints() < pano.getOptimizeVector().size())
918 {
919 std::cerr << std::endl << "After control points pruning there are only " << pano.getNrOfCtrlPoints() << " control points" << std::endl
920 << "left, but you selected " << pano.getOptimizeVector().size() << " parameters to optimize." << std::endl
921 << "This will give strange results." << std::endl
922 << "Increase error distance (-t parameter), tweak cp" << std::endl
923 << "detection parameters or select less parameters to optimize." << std::endl
924 << std::endl
925 << "Exiting..." << std::endl;
926 return 1;
927 }
929 }
930
931 if (param.crop)
932 {
933 autoCrop(pano);
934 }
935 // deactivate ref image if requested
936 if (param.dontRemapRef)
937 {
938 pano.activateImage(0, false);
939 };
940
942 // some check for valid output
944 if (optimizeError || outputImages.size() != imgs.size())
945 {
946 if (!param.ptoFile.empty())
947 {
948 pano.WritePTOFile(param.ptoFile);
949 }
950 std::cerr << "An error occurred during optimization." << std::endl;
951 if (param.ptoFile.empty())
952 {
953 std::cerr << "Try adding \"-p debug.pto\" and checking output." << std::endl;
954 }
955 else
956 {
957 std::cerr << "Check output file " << param.ptoFile << " for details." << std::endl;
958 };
959 std::cerr << "Exiting..." << std::endl;
960 return 1;
961 }
962
963 if (param.hdrFile.size())
964 {
965 // TODO: photometric alignment (HDR, fixed white balance)
966 //utils::StreamProgressReporter progress(2.0);
967 //loadImgsAndExtractPoints(pano, nPoints, pyrLevel, randomPoints, progress, points);
968 //smartOptimizePhotometric
969
970 // switch to HDR output mode
973 opts.outputPixelType = "FLOAT";
975 pano.setOptions(opts);
976
977 // remap all images
979 if(g_verbose > 0)
980 {
982 }
983 else
984 {
986 };
988 progress, param.hdrFile, imgs);
989 std::cout << "Written HDR output to " << param.hdrFile << std::endl;
990 delete progress;
991 }
992 if (param.alignedPrefix.size())
993 {
994 // disable all exposure compensation stuff.
998 opts.outputPixelType = "";
999 opts.remapUsingGPU = param.gpu;
1000 pano.setOptions(opts);
1001 // remap all images
1003 if(g_verbose > 0)
1004 {
1006 }
1007 else
1008 {
1010 };
1011 // pass option to ignore exposure to stitcher
1013 HuginBase::Nona::SetAdvancedOption(advOpts, "ignoreExposure", true);
1015 progress, param.alignedPrefix, imgs, advOpts);
1016 delete progress;
1017 std::cout << "Written aligned images to files with prefix \"" << param.alignedPrefix << "\"" << std::endl;
1018 }
1019
1020 // At this point we have panorama options set according to the output
1021 if (!param.ptoFile.empty())
1022 {
1023 if (pano.WritePTOFile(param.ptoFile))
1024 {
1025 std::cout << "Written output to " << param.ptoFile << std::endl;
1026 };
1027 }
1028
1029
1030 }
1031 catch (std::exception& e)
1032 {
1033 std::cerr << "ERROR: caught exception: " << e.what() << std::endl;
1034 return 1;
1035 }
1036 return 0;
1037}
1038
1039int main(int argc, char* argv[])
1040{
1041 // parse arguments
1042 const char* optstring = "a:ef:g:hlmdiSAPCp:vo:s:t:c:xyz";
1043 int c;
1044
1045 g_verbose = 0;
1046
1047 Parameters param;
1048 enum
1049 {
1050 CORRTHRESH=1000,
1051 THREADS,
1052 GPU,
1053 LENSDB,
1057 };
1058
1059 static struct option longOptions[] =
1060 {
1061 {"corr", required_argument, NULL, CORRTHRESH },
1062 {"verbose", no_argument, NULL, 'v'},
1063 {"threads", required_argument, NULL, THREADS },
1064 {"gpu", no_argument, NULL, GPU },
1065 {"distortion", no_argument, NULL, LENSDB },
1066 {"use-given-order", no_argument, NULL, USEGIVENORDER },
1067 {"align-to-first", no_argument, NULL, ALIGNTOFIRST},
1068 {"dont-remap-ref", no_argument, NULL, DONTREMAPREF},
1069 {"help", no_argument, NULL, 'h' },
1070 0
1071 };
1072 while ((c = getopt_long(argc, argv, optstring, longOptions, nullptr)) != -1)
1073 {
1074 switch (c)
1075 {
1076 case 'a':
1077 param.alignedPrefix = optarg;
1078 break;
1079 case 'c':
1080 param.nPoints = atoi(optarg);
1081 if (param.nPoints < 1)
1082 {
1083 std::cerr << hugin_utils::stripPath(argv[0]) << ": Invalid parameter: Number of points/grid (-c) must be at least 1" << std::endl;
1084 return 1;
1085 }
1086 break;
1087 case 'e':
1088 param.fisheye = true;
1089 break;
1090 case 'f':
1091 param.hfov = atof(optarg);
1092 if (param.hfov <= 0)
1093 {
1094 std::cerr << hugin_utils::stripPath(argv[0]) << ": Invalid parameter: HFOV (-f) must be greater than 0" << std::endl;
1095 return 1;
1096 }
1097 break;
1098 case 'g':
1099 param.grid = atoi(optarg);
1100 if (param.grid < 1 || param.grid>50)
1101 {
1102 std::cerr << hugin_utils::stripPath(argv[0]) << ": Invalid parameter: number of grid cells (-g) should be between 1 and 50" << std::endl;
1103 return 1;
1104 }
1105 break;
1106 case 'l':
1107 param.linear = true;
1108 break;
1109 case 'm':
1110 param.optHFOV = true;
1111 break;
1112 case 'd':
1113 param.optDistortion = true;
1114 break;
1115 case 'i':
1116 param.optCenter = true;
1117 break;
1118 case 'x':
1119 param.optX = true;
1120 break;
1121 case 'y':
1122 param.optY = true;
1123 break;
1124 case 'z':
1125 param.optZ = true;
1126 break;
1127 case 'S':
1128 param.stereo = true;
1129 break;
1130 case 'A':
1131 param.stereo = true;
1132 param.stereo_window = true;
1133 break;
1134 case 'P':
1135 param.stereo = true;
1136 param.stereo_window = true;
1137 param.pop_out = true;
1138 break;
1139 case 'C':
1140 param.crop = true;
1141 break;
1142 case 't':
1143 param.cpErrorThreshold = atof(optarg);
1144 if (param.cpErrorThreshold <= 0)
1145 {
1146 std::cerr << hugin_utils::stripPath(argv[0]) << ": Invalid parameter: control point error threshold (-t) must be greater than 0" << std::endl;
1147 return 1;
1148 }
1149 break;
1150 case 'p':
1151 param.ptoFile = optarg;
1152 break;
1153 case 'o':
1154 param.hdrFile = optarg;
1155 break;
1156 case 'v':
1157 g_verbose++;
1158 break;
1159 case 'h':
1161 return 0;
1162 case 's':
1163 param.pyrLevel = atoi(optarg);
1164 if (param.pyrLevel < 0 || param.pyrLevel >8)
1165 {
1166 std::cerr << hugin_utils::stripPath(argv[0]) << ": Invalid parameter: scaling (-s) should be between 0 and 8" << std::endl;
1167 return 1;
1168 }
1169 break;
1170 case CORRTHRESH:
1171 param.corrThresh = atof(optarg);
1172 if (param.corrThresh <= 0 || param.corrThresh > 1.0)
1173 {
1174 std::cerr << hugin_utils::stripPath(argv[0]) << ": Invalid parameter: correlation should be between 0 and 1" << endl;
1175 return 1;
1176 };
1177 break;
1178 case THREADS:
1179 std::cout << "WARNING: Switch --threads is deprecated. Set environment variable OMP_NUM_THREADS instead" << std::endl;
1180 break;
1181 case GPU:
1182#if defined __APPLE__ && defined __aarch64__
1183 // disable GPU remapping on ARM Macs, GPU code is not working on these systems
1184 param.gpu = false;
1185 std::cout << "WARNING: GPU remapping is not supported on ARM Macs. Switching back to CPU remapping." << std::endl;
1186#else
1187 param.gpu = true;
1188#endif
1189 break;
1190 case LENSDB:
1191 param.loadDistortion = true;
1192 break;
1193 case USEGIVENORDER:
1194 param.sortImagesByEv = false;
1195 break;
1196 case ALIGNTOFIRST:
1197 param.alignToFirst = true;
1198 param.sortImagesByEv = false;
1199 break;
1200 case DONTREMAPREF:
1201 param.dontRemapRef = true;
1202 break;
1203 case ':':
1204 case '?':
1205 // missing argument or invalid switch
1206 return 1;
1207 break;
1208 default:
1209 // this should not happen
1210 abort();
1211 }
1212 }
1213
1214 // use always given image order for stereo options
1215 if (param.stereo)
1216 {
1217 param.sortImagesByEv = false;
1218 };
1219 unsigned nFiles = argc - optind;
1220 if (nFiles < 2)
1221 {
1222 std::cerr << hugin_utils::stripPath(argv[0]) << ": At least two files need to be specified" << std::endl;
1223 return 1;
1224 }
1225
1226 if (param.hdrFile.empty() && param.ptoFile.empty() && param.alignedPrefix.empty())
1227 {
1228 std::cerr << hugin_utils::stripPath(argv[0]) << ": Please specify at least one of the -p, -o or -a options." << std::endl;
1229 return 1;
1230 }
1231
1232 // extract file names
1233 std::vector<std::string> files;
1234 for (size_t i=0; i < nFiles; i++)
1235 {
1236 const std::string filename(argv[optind + i]);
1237 // reject raw files
1239 {
1240 std::cerr << "Ignoring raw file " << filename << std::endl;
1241 continue;
1242 };
1243 // check that it is a valid image file
1244 if (!vigra::isImage(filename.c_str()))
1245 {
1246 std::cerr << "Could not read file " << filename << std::endl;
1247 continue;
1248 };
1249 files.push_back(filename);
1250 };
1251 if (files.empty())
1252 {
1253 std::cerr << "ERROR: No valid files given. Nothing to do." << std::endl;
1254 return 1;
1255 };
1256
1257 std::string pixelType;
1258
1259 bool grayscale = false;
1260 int returnValue = 1;
1261 try
1262 {
1263 vigra::ImageImportInfo firstImgInfo(files[0].c_str());
1264 pixelType = firstImgInfo.getPixelType();
1265 if (firstImgInfo.numExtraBands()>1)
1266 {
1267 std::cerr << "ERROR: images with several alpha channels are not supported." << std::endl;
1268 return 1;
1269 };
1270 grayscale = firstImgInfo.isGrayscale();
1271 }
1272 catch (std::exception& e)
1273 {
1274 std::cerr << "ERROR: caught exception: " << e.what() << std::endl;
1275 return 1;
1276 }
1277
1278 if(param.gpu)
1279 {
1280 param.gpu=hugin_utils::initGPU(&argc, argv);
1281 };
1282 if (grayscale)
1283 {
1284 if (pixelType == "UINT8")
1285 {
1286 returnValue=main2<vigra::UInt8>(files, param);
1287 }
1288 else if (pixelType == "INT16")
1289 {
1290 returnValue=main2<vigra::Int16>(files, param);
1291 }
1292 else if (pixelType == "UINT16")
1293 {
1294 returnValue = main2<vigra::UInt16>(files, param);
1295 }
1296 else if (pixelType == "FLOAT")
1297 {
1298 returnValue=main2<float>(files, param);
1299 }
1300 else
1301 {
1302 std::cerr << " ERROR: unsupported pixel type: " << pixelType << std::endl;
1303 }
1304 }
1305 else
1306 {
1307 if (pixelType == "UINT8")
1308 {
1310 }
1311 else if (pixelType == "INT16")
1312 {
1314 }
1315 else if (pixelType == "UINT16")
1316 {
1318 }
1319 else if (pixelType == "FLOAT")
1320 {
1321 returnValue = main2<vigra::RGBValue<float> >(files, param);
1322 }
1323 else
1324 {
1325 std::cerr << " ERROR: unsupported pixel type: " << pixelType << std::endl;
1326 }
1327 };
1328
1329 if(param.gpu)
1330 {
1332 };
1334 return returnValue;
1335}
image1Nr
image2Nr
std::multimap< double, vigra::Diff2D > MapPoints
compute interest points
class to access Hugins camera and lens database
static void usage()
Definition Main.cpp:32
!! from PTOptimise.h 1951
wraps around PTOptimizer
Somewhere to specify what variables belong to what.
Contains various routines used for stitching panoramas.
static int ptProgress(int command, char *argument)
int main(int argc, char *argv[])
void FineTuneInterestPoints(HuginBase::Panorama &pano, int img1, const ImageType &leftImg, const ImageType &leftImgOrig, int img2, const ImageType &rightImg, const ImageType &rightImgOrig, const MapPoints &points, unsigned nPoints, int pyrLevel, int templWidth, int sWidth, double scaleFactor, double corrThresh, bool stereo)
static int ptinfoDlg(int command, char *argument)
int main2(std::vector< std::string > files, Parameters param)
std::multimap< double, vigra::Diff2D > MapPoints
static hugin_omp::Lock lock
void autoCrop(HuginBase::Panorama &pano)
void createCtrlPoints(HuginBase::Panorama &pano, int img1, const ImageType &leftImg, const ImageType &leftImgOrig, int img2, const ImageType &rightImg, const ImageType &rightImgOrig, int pyrLevel, double scale, unsigned nPoints, unsigned grid, double corrThresh=0.9, bool stereo=false)
void alignStereoWindow(HuginBase::Panorama &pano, bool pop_out)
int g_verbose
Dummy progress display, without output.
a progress display to print progress reports to a stream
@ RESPONSE_LINEAR
linear response
represents a control point
@ X
evaluate x, points are on a vertical line
@ Y
evaluate y, points are on a horizontal line
static void Clean()
cleanup the static LensDB instance, must be called at the end of the program
Definition LensDB.cpp:2010
Holds transformations for Image -> Pano and the other way.
Panorama image options.
void setHFOV(double h, bool keepView=true)
set the horizontal field of view.
void setProjection(ProjectionFormat f)
set the Projection format and adjust the hfov/vfov if nessecary
void setHeight(unsigned int h)
set panorama height
void setWidth(unsigned int w, bool keepView=true)
set panorama width keep the HFOV, if keepView=true
void setROI(const vigra::Rect2D &val)
Model for a panorama.
Definition Panorama.h:153
const SrcPanoImage & getImage(std::size_t nr) const
get a panorama image, counting starts with 0
Definition Panorama.h:211
const PanoramaOptions & getOptions() const
returns the options for this panorama
Definition Panorama.h:481
void setOptimizeVector(const OptimizeVector &optvec)
set optimize setting
Definition Panorama.cpp:297
std::vector< unsigned int > getCtrlPointsForImage(unsigned int imgNr) const
return all control points for a given image.
Definition Panorama.cpp:83
void setCtrlPoints(const CPVector &points)
set all control points (Ippei: Is this supposed to be 'add' method?)
Definition Panorama.cpp:449
SrcPanoImage getSrcImage(unsigned imgNr) const
get a description of a source image
unsigned int addCtrlPoint(const ControlPoint &point)
add a new control point.
Definition Panorama.cpp:381
std::size_t getNrOfCtrlPoints() const
number of control points
Definition Panorama.h:306
unsigned int addImage(const SrcPanoImage &img)
the the number for a specific image
Definition Panorama.cpp:319
void setOptions(const PanoramaOptions &opt)
set new output settings This is not used directly for optimizing/stiching, but it can be feed into ru...
void activateImage(unsigned int imgNr, bool active=true)
mark an image as active or inactive.
const CPVector & getCtrlPoints() const
get all control point of this Panorama
Definition Panorama.h:319
const OptimizeVector & getOptimizeVector() const
return the optimize settings stored inside panorama
Definition Panorama.h:454
bool WritePTOFile(const std::string &filename, const std::string &prefix="")
write data to given pto file
UIntSet getActiveImages() const
get active images
std::size_t getNrOfImages() const
number of images.
Definition Panorama.h:205
All variables of a source image.
bool readEXIF()
try to fill out information about the image, by examining the exif data
class AlphaIterator class AlphaAccessor inline void importImageAlpha(const ImageImportInfo &import_info, ImageIterator image_iterator, ImageAccessor image_accessor, AlphaIterator alpha_iterator, AlphaAccessor alpha_accessor)
vigra::FRGBImage ImageType
#define DEBUG_DEBUG(msg)
Definition utils.h:68
std::map< std::string, std::string > AdvancedOptions
void stitchPanorama(const PanoramaData &pano, const PanoramaOptions &opt, AppBase::ProgressDisplay *progress, const std::string &basename, const UIntSet &usedImgs, const AdvancedOptions &advOptions)
The main stitching function.
Definition Stitcher.cpp:40
void SetAdvancedOption(AdvancedOptions &opts, const std::string &name, const bool value)
store the option with name in AdvancedOptions
unsigned int optimize(PanoramaData &pano, const char *userScript)
optimize the images imgs, for variables optvec, using vars as start.
std::vector< ControlPoint > CPVector
std::vector< std::set< std::string > > OptimizeVector
UIntSet getImagesinROI(const PanoramaData &pano, const UIntSet activeImages)
returns set of images which are visible in output ROI
std::set< unsigned int > UIntSet
void FindInterestPointsPartial(const ImageType &image, const vigra::Rect2D &rect, double scale, unsigned nPoints, std::multimap< double, vigra::Diff2D > &points, vigra::VigraTrueType)
vigra_ext::CorrelationResult FineTunePoint(const ImageType &leftImg, const vigra::Diff2D templPos, const int templSize, const ImageType &rightImg, const vigra::Diff2D searchPos, const int searchWidth, vigra::VigraTrueType)
bool initGPU(int *argcp, char **argv)
Try to initalise GLUT and GLEW, and create an OpenGL context for GPU stitching.
Definition utils.cpp:845
std::string getExtension(const std::string &basename2)
Get extension of a filename.
Definition utils.cpp:99
bool wrapupGPU()
cleanup GPU settings
Definition utils.cpp:901
bool IsRawExtension(const std::string testExt)
return true if extension belongs to a raw file
Definition utils.cpp:970
std::string GetHuginVersion()
return a string with version numbers
Definition utils.cpp:907
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 findInterestPointsPartial(vigra::triple< ImageIter, ImageIter, ImageAcc > img, const vigra::Rect2D &rect, double scale, unsigned nPoints, std::multimap< double, vigra::Diff2D > &points)
void reduceNTimes(ImageIn &in, Image &out, int n)
Definition Pyramid.h:39
CorrelationResult PointFineTune(const IMAGET &templImg, ACCESSORT access_t, vigra::Diff2D templPos, int templSize, const IMAGES &searchImg, ACCESSORS access_s, vigra::Diff2D searchPos, int sWidth)
fine tune a point with normalized cross correlation
helper for OpenMP
std::string alignedPrefix
std::string ptoFile
std::string hdrFile
std::string basename
bool operator()(const unsigned int i, const unsigned int j)
SortImageVectorEV(const HuginBase::Panorama *pano)
const HuginBase::Panorama * m_pano
Maximum of correlation, position and value.
Definition Correlation.h:57
hugin_utils::FDiff2D curv
Definition Correlation.h:68
hugin_utils::FDiff2D maxpos
Definition Correlation.h:64
std::vector< deghosting::BImagePtr > threshold(const std::vector< deghosting::FImagePtr > &inputImages, const double threshold, const uint16_t flags)
Threshold function used for creating alpha masks for images.
Definition threshold.h:41