void OptimizePhotometricPanel::runOptimizer(const UIntSet & imgs) { DEBUG_TRACE(""); int mode = m_mode_cb->GetSelection(); // check if vignetting and response are linked, display a warning if they are not // The variables to check: const HuginBase::ImageVariableGroup::ImageVariableEnum vars[] = { HuginBase::ImageVariableGroup::IVE_EMoRParams, HuginBase::ImageVariableGroup::IVE_ResponseType, HuginBase::ImageVariableGroup::IVE_VigCorrMode, HuginBase::ImageVariableGroup::IVE_RadialVigCorrCoeff, HuginBase::ImageVariableGroup::IVE_RadialVigCorrCenterShift }; // keep a list of commands needed to fix it: std::vector<PT::PanoCommand *> commands; HuginBase::ConstImageVariableGroup & lenses = variable_groups->getLenses(); for (size_t i = 0; i < lenses.getNumberOfParts(); i++) { std::set<HuginBase::ImageVariableGroup::ImageVariableEnum> links_needed; links_needed.clear(); for (int v = 0; v < 5; v++) { if (!lenses.getVarLinkedInPart(vars[v], i)) { links_needed.insert(vars[v]); } }; if (!links_needed.empty()) { commands.push_back(new PT::LinkLensVarsCmd(*m_pano, i, links_needed)); } } // if the list of commands is empty, all is good and we don't need a warning. if (!commands.empty()) { int ok = wxMessageBox(_("The same vignetting and response parameters should\nbe applied for all images of a lens.\nCurrently each image can have different parameters.\nLink parameters?"), _("Link parameters"), wxYES_NO | wxICON_INFORMATION); if (ok == wxYES) { // perform all the commands we stocked up earilier. for (std::vector<PT::PanoCommand *>::iterator it = commands.begin(); it != commands.end(); it++) { GlobalCmdHist::getInstance().addCommand(*it); } } else { // free all the commands, the user doesn't want them used. for (std::vector<PT::PanoCommand *>::iterator it = commands.begin(); it != commands.end(); it++) { delete *it; } } } Panorama optPano = m_pano->getSubset(imgs); PanoramaOptions opts = optPano.getOptions(); OptimizeVector optvars; if(mode==OPT_CUSTOM) { optvars = getOptimizeVector(); if (optPano.getNrOfImages() != m_pano->getNrOfImages()) { OptimizeVector o = optvars; optvars.clear(); for (UIntSet::const_iterator it = imgs.begin(); it != imgs.end(); ++it) { optvars.push_back(o[*it]); } } unsigned int countVar=0; for(unsigned int i=0;i<optvars.size();i++) { countVar+=optvars[i].size(); }; if(countVar==0) { wxMessageBox(_("You selected no parameters to optimize.\nTherefore optimization will be canceled."), _("Exposure optimization"), wxOK | wxICON_INFORMATION); return; }; }; std::vector<vigra_ext::PointPairRGB> m_points; // extract points only if not done previously long nPoints = 200; wxConfigBase::Get()->Read(wxT("/OptimizePhotometric/nRandomPointsPerImage"), & nPoints); // get parameters for estimation. nPoints = wxGetNumberFromUser(_("The vignetting and exposure correction is determined by analysing color values in the overlapping areas.\nTo speed up the computation, only a random subset of points is used."), _("Number of points per image"), _("Photometric optimization"), nPoints, 0, 32000, this); if (nPoints < 0) { return; } wxConfigBase::Get()->Write(wxT("/OptimizePhotometric/nRandomPointsPerImage"),nPoints); ProgressReporterDialog progress(5.0, _("Photometric alignment"), _("Loading images")); progress.Show(); nPoints = nPoints * optPano.getNrOfImages(); // get the small images std::vector<vigra::FRGBImage *> srcImgs; for (size_t i=0; i < optPano.getNrOfImages(); i++) { ImageCache::EntryPtr e = ImageCache::getInstance().getSmallImage(optPano.getImage(i).getFilename()); vigra::FRGBImage * img = new FRGBImage; if (!e) { wxMessageBox(_("Error: could not load all images"), _("Error")); return; } if (e->image8 && e->image8->width() > 0) { reduceToNextLevel(*(e->image8), *img); transformImage(vigra::srcImageRange(*img), vigra::destImage(*img), vigra::functor::Arg1()/vigra::functor::Param(255.0)); } else if (e->image16 && e->image16->width() > 0) { reduceToNextLevel(*(e->image16), *img); transformImage(vigra::srcImageRange(*img), vigra::destImage(*img), vigra::functor::Arg1()/vigra::functor::Param(65535.0)); } else { reduceToNextLevel(*(e->imageFloat), *img); } srcImgs.push_back(img); } bool randomPoints = true; extractPoints(optPano, srcImgs, nPoints, randomPoints, progress, m_points); if (m_points.size() == 0) { wxMessageBox(_("Error: no overlapping points found, Photometric optimization aborted"), _("Error")); return; } double error = 0; try { //wxBusyCursor busyc; if (mode != OPT_CUSTOM) { // run automatic optimisation // ensure that we have a valid anchor. PanoramaOptions opts = optPano.getOptions(); if (opts.colorReferenceImage >= optPano.getNrOfImages()) { opts.colorReferenceImage = 0; optPano.setOptions(opts); } smartOptimizePhotometric(optPano, PhotometricOptimizeMode(mode), m_points, progress, error); } else { // optimize selected parameters optimizePhotometric(optPano, optvars, m_points, progress, error); } } catch (std::exception & error) { wxMessageBox(_("Internal error during photometric optimization:\n") + wxString(error.what(), wxConvLocal), _("Internal error")); } progress.Close(); // display information about the estimation process: int ret = wxMessageBox(wxString::Format(_("Photometric optimization results:\nAverage difference (RMSE) between overlapping pixels: %.2f gray values (0..255)\n\nApply results?"), error*255), _("Photometric optimization finished"), wxYES_NO | wxICON_INFORMATION,this); if (ret == wxYES) { DEBUG_DEBUG("Applying vignetting corr"); // TODO: merge into a single update command const VariableMapVector & vars = optPano.getVariables(); GlobalCmdHist::getInstance().addCommand( new PT::UpdateImagesVariablesCmd(*m_pano, imgs, vars) ); //now update panorama exposure value PanoramaOptions opts = m_pano->getOptions(); opts.outputExposureValue = calcMeanExposure(*m_pano); GlobalCmdHist::getInstance().addCommand( new PT::SetPanoOptionsCmd(*m_pano, opts) ); } }
int main(int argc, char *argv[]) { // parse arguments const char * optstring = "alho:npqsv:m"; int c; string output; bool doPairwise = false; bool doAutoOpt = false; bool doNormalOpt = false; bool doLevel = false; bool chooseProj = false; bool quiet = false; bool doPhotometric = false; double hfov = 0.0; while ((c = getopt (argc, argv, optstring)) != -1) { switch (c) { case 'o': output = optarg; break; case 'h': usage(argv[0]); return 0; case 'p': doPairwise = true; break; case 'a': doAutoOpt = true; break; case 'n': doNormalOpt = true; break; case 'l': doLevel = true; break; case 's': chooseProj = true; break; case 'q': quiet = true; break; case 'v': hfov = atof(optarg); break; case 'm': doPhotometric = true; break; default: abort (); } } if (argc - optind != 1) { usage(argv[0]); return 1; } const char * scriptFile = argv[optind]; Panorama pano; if (scriptFile[0] == '-') { DocumentData::ReadWriteError err = pano.readData(std::cin); if (err != DocumentData::SUCCESSFUL) { cerr << "error while reading script file from stdin." << endl; cerr << "DocumentData::ReadWriteError code: " << err << endl; return 1; } } else { ifstream prjfile(scriptFile); if (!prjfile.good()) { cerr << "could not open script : " << scriptFile << endl; return 1; } pano.setFilePrefix(hugin_utils::getPathPrefix(scriptFile)); DocumentData::ReadWriteError err = pano.readData(prjfile); if (err != DocumentData::SUCCESSFUL) { cerr << "error while parsing panos tool script: " << scriptFile << endl; cerr << "DocumentData::ReadWriteError code: " << err << endl; return 1; } } if (pano.getNrOfImages() == 0) { cerr << "Panorama should consist of at least one image" << endl; return 1; } // for bad HFOV (from autopano-SIFT) for (unsigned i=0; i < pano.getNrOfImages(); i++) { SrcPanoImage img = pano.getSrcImage(i); if (img.getProjection() == SrcPanoImage::RECTILINEAR && img.getHFOV() >= 180) { // something is wrong here, try to read from exif data double focalLength = 0; double cropFactor = 0; cerr << "HFOV of image " << img.getFilename() << " invalid, trying to read EXIF tags" << endl; bool ok = img.readEXIF(focalLength, cropFactor, true, false); if (! ok) { if (hfov) { img.setHFOV(hfov); } else { cerr << "EXIF reading failed, please specify HFOV with -v" << endl; return 1; } } pano.setSrcImage(i, img); } } if(pano.getNrOfCtrlPoints()==0 && (doPairwise || doAutoOpt || doNormalOpt)) { cerr << "Panorama have to have control points to optimise positions" << endl; return 1; }; if (doPairwise && ! doAutoOpt) { // do pairwise optimisation set<string> optvars; optvars.insert("r"); optvars.insert("p"); optvars.insert("y"); AutoOptimise::autoOptimise(pano); // do global optimisation if (!quiet) std::cerr << "*** Pairwise position optimisation" << endl; PTools::optimize(pano); } else if (doAutoOpt) { if (!quiet) std::cerr << "*** Adaptive geometric optimisation" << endl; SmartOptimise::smartOptimize(pano); } else if (doNormalOpt) { if (!quiet) std::cerr << "*** Optimising parameters specified in PTO file" << endl; PTools::optimize(pano); } else { if (!quiet) std::cerr << "*** Geometric parameters not optimized" << endl; } if (doLevel) { bool hasVerticalLines=false; CPVector allCP=pano.getCtrlPoints(); if(allCP.size()>0 && (doPairwise || doAutoOpt || doNormalOpt)) { for(size_t i=0;i<allCP.size() && !hasVerticalLines;i++) { hasVerticalLines=(allCP[i].mode==ControlPoint::X); }; }; // straighten only if there are no vertical control points if(hasVerticalLines) { cout << "Skipping automatic leveling because of existing vertical control points." << endl; } else { StraightenPanorama(pano).run(); CenterHorizontally(pano).run(); }; } if (chooseProj) { PanoramaOptions opts = pano.getOptions(); double hfov, vfov; CalculateFitPanorama fitPano = CalculateFitPanorama(pano); fitPano.run(); opts.setHFOV(fitPano.getResultHorizontalFOV()); opts.setHeight(roundi(fitPano.getResultHeight())); vfov = opts.getVFOV(); hfov = opts.getHFOV(); // avoid perspective projection if field of view > 100 deg double mf = 100; if (vfov < mf) { // cylindrical or rectilinear if (hfov < mf) { opts.setProjection(PanoramaOptions::RECTILINEAR); } else { opts.setProjection(PanoramaOptions::CYLINDRICAL); } } // downscale pano a little double sizeFactor = 0.7; pano.setOptions(opts); double w = CalculateOptimalScale::calcOptimalScale(pano); opts.setWidth(roundi(opts.getWidth()*w*sizeFactor), true); pano.setOptions(opts); } if(doPhotometric) { // photometric estimation PanoramaOptions opts = pano.getOptions(); int nPoints = 200; int pyrLevel=3; bool randomPoints = true; nPoints = nPoints * pano.getNrOfImages(); std::vector<vigra_ext::PointPairRGB> points; ProgressDisplay *progressDisplay; if(!quiet) progressDisplay=new StreamProgressDisplay(std::cout); else progressDisplay=new DummyProgressDisplay(); try { loadImgsAndExtractPoints(pano, nPoints, pyrLevel, randomPoints, *progressDisplay, points, !quiet); } catch (std::exception & e) { cerr << "caught exception: " << e.what() << endl; return 1; }; if(!quiet) cout << "\rSelected " << points.size() << " points" << endl; if (points.size() == 0) { cerr << "Error: no overlapping points found, exiting" << endl; return 1; } progressDisplay->startSubtask("Photometric Optimization", 0.0); // first, ensure that vignetting and response coefficients are linked const HuginBase::ImageVariableGroup::ImageVariableEnum vars[] = { HuginBase::ImageVariableGroup::IVE_EMoRParams, HuginBase::ImageVariableGroup::IVE_ResponseType, HuginBase::ImageVariableGroup::IVE_VigCorrMode, HuginBase::ImageVariableGroup::IVE_RadialVigCorrCoeff, HuginBase::ImageVariableGroup::IVE_RadialVigCorrCenterShift }; HuginBase::StandardImageVariableGroups variable_groups(pano); HuginBase::ImageVariableGroup & lenses = variable_groups.getLenses(); for (size_t i = 0; i < lenses.getNumberOfParts(); i++) { std::set<HuginBase::ImageVariableGroup::ImageVariableEnum> links_needed; links_needed.clear(); for (int v = 0; v < 5; v++) { if (!lenses.getVarLinkedInPart(vars[v], i)) { links_needed.insert(vars[v]); } }; if (!links_needed.empty()) { std::set<HuginBase::ImageVariableGroup::ImageVariableEnum>::iterator it; for (it = links_needed.begin(); it != links_needed.end(); it++) { lenses.linkVariablePart(*it, i); } } } HuginBase::SmartPhotometricOptimizer::PhotometricOptimizeMode optmode = HuginBase::SmartPhotometricOptimizer::OPT_PHOTOMETRIC_LDR; if (opts.outputMode == PanoramaOptions::OUTPUT_HDR) { optmode = HuginBase::SmartPhotometricOptimizer::OPT_PHOTOMETRIC_HDR; } SmartPhotometricOptimizer photoOpt(pano, progressDisplay, pano.getOptimizeVector(), points, optmode); photoOpt.run(); // calculate the mean exposure. opts.outputExposureValue = CalculateMeanExposure::calcMeanExposure(pano); pano.setOptions(opts); progressDisplay->finishSubtask(); delete progressDisplay; }; // write result OptimizeVector optvec = pano.getOptimizeVector(); UIntSet imgs; fill_set(imgs,0, pano.getNrOfImages()-1); if (output != "") { ofstream of(output.c_str()); pano.printPanoramaScript(of, optvec, pano.getOptions(), imgs, false, hugin_utils::getPathPrefix(scriptFile)); } else { pano.printPanoramaScript(cout, optvec, pano.getOptions(), imgs, false, hugin_utils::getPathPrefix(scriptFile)); } return 0; }
int main(int argc, char* argv[]) { // parse arguments const char* optstring = "o:p:f:c:s:lh"; static struct option longOptions[] = { {"output", required_argument, NULL, 'o' }, {"projection", required_argument, NULL, 'p' }, {"fov", required_argument, NULL, 'f' }, {"crop", required_argument, NULL, 'c' }, {"stacklength", required_argument, NULL, 's' }, {"linkstacks", no_argument, NULL, 'l' }, {"distortion", no_argument, NULL, 300 }, {"vignetting", no_argument, NULL, 301 }, {"help", no_argument, NULL, 'h' }, 0 }; int c; int optionIndex = 0; string output; int projection=-1; float fov=-1; int stackLength=1; bool linkStacks=false; vigra::Rect2D cropRect(0,0,0,0); bool loadDistortion=false; bool loadVignetting=false; while ((c = getopt_long (argc, argv, optstring, longOptions,&optionIndex)) != -1) { switch (c) { case 'o': output = optarg; break; case 'h': usage(argv[0]); return 0; case 'p': { projection=atoi(optarg); if((projection==0) && (strcmp(optarg,"0")!=0)) { cerr << "Could not parse image number."; return 1; }; if(projection<0) { cerr << "Invalid projection number." << endl; return 1; }; }; break; case 'f': fov=atof(optarg); if(fov<1 || fov>360) { cerr << "Invalid field of view"; return 1; }; break; case 'c': { int left, right, top, bottom; int n=sscanf(optarg, "%d,%d,%d,%d", &left, &right, &top, &bottom); if (n==4) { if(right>left && bottom>top) { cropRect.setUpperLeft(vigra::Point2D(left,top)); cropRect.setLowerRight(vigra::Point2D(right,bottom)); } else { cerr << "Invalid crop area" << endl; return 1; }; } else { cerr << "Could not parse crop values" << endl; return 1; }; }; break; case 's': stackLength=atoi(optarg); if(stackLength<1) { cerr << "Could not parse stack length." << endl; return 1; }; break; case 'l': linkStacks=true; break; case 300: loadDistortion=true; break; case 301: loadVignetting=true; break; case ':': cerr <<"Option " << longOptions[optionIndex].name << " requires a number" << endl; return 1; break; case '?': break; default: abort (); } } if (argc - optind < 1) { usage(argv[0]); return 1; }; cout << "Generating pto file..." << endl; cout.flush(); std::vector<string> filelist; while(optind<argc) { string input; #ifdef _WINDOWS //do globbing input=GetAbsoluteFilename(argv[optind]); char drive[_MAX_DRIVE]; char dir[_MAX_DIR]; char fname[_MAX_FNAME]; char ext[_MAX_EXT]; char newFile[_MAX_PATH]; _splitpath(input.c_str(), drive, dir, NULL, NULL); struct _finddata_t finddata; intptr_t findhandle = _findfirst(input.c_str(), &finddata); if (findhandle != -1) { do { //ignore folder, can be happen when using *.* if((finddata.attrib & _A_SUBDIR)==0) { _splitpath(finddata.name, NULL, NULL, fname, ext); _makepath(newFile, drive, dir, fname, ext); //check if valid image file if(vigra::isImage(newFile)) { filelist.push_back(std::string(newFile)); }; }; } while (_findnext(findhandle, &finddata) == 0); _findclose(findhandle); } #else input=argv[optind]; if(hugin_utils::FileExists(input)) { if(vigra::isImage(input.c_str())) { filelist.push_back(GetAbsoluteFilename(input)); }; }; #endif optind++; }; if(filelist.size()==0) { cerr << "No valid image files given." << endl; return 1; }; //sort filenames sort(filelist.begin(),filelist.end(),doj::alphanum_less()); if(projection<0) { InitLensDB(); }; Panorama pano; for(size_t i=0; i<filelist.size();i++) { SrcPanoImage srcImage; cout << "Reading " << filelist[i] << "..." << endl; srcImage.setFilename(filelist[i]); try { vigra::ImageImportInfo info(filelist[i].c_str()); if(info.width()==0 || info.height()==0) { cerr << "ERROR: Could not decode image " << filelist[i] << endl << "Skipping this image." << endl << endl; continue; } srcImage.setSize(info.size()); std::string pixelType=info.getPixelType(); if((pixelType=="UINT8") || (pixelType=="UINT16") || (pixelType=="INT16")) { srcImage.setResponseType(HuginBase::SrcPanoImage::RESPONSE_EMOR); } else { srcImage.setResponseType(HuginBase::SrcPanoImage::RESPONSE_LINEAR); }; } catch(std::exception & e) { cerr << "ERROR: caught exception: " << e.what() << endl; cerr << "Could not read image information for file " << filelist[i] << endl; cerr << "Skipping this image." << endl << endl; continue; }; srcImage.readEXIF(); srcImage.applyEXIFValues(); if(projection>=0) { srcImage.setProjection((HuginBase::BaseSrcPanoImage::Projection)projection); } else { srcImage.readProjectionFromDB(); }; if(fov>0) { srcImage.setHFOV(fov); if(srcImage.getCropFactor()==0) { srcImage.setCropFactor(1.0); }; } else { //set plausible default value if they could not read from exif if(srcImage.getExifFocalLength()==0 || srcImage.getCropFactor()==0) { cout << "\tNo value for field of view found in EXIF data. " << endl << "\tAssuming a HFOV of 50 degrees. " << endl; srcImage.setHFOV(50); srcImage.setCropFactor(1.0); }; }; if(cropRect.width()>0 && cropRect.height()>0) { if(srcImage.isCircularCrop()) { srcImage.setCropMode(SrcPanoImage::CROP_CIRCLE); } else { srcImage.setCropMode(SrcPanoImage::CROP_RECTANGLE); }; srcImage.setAutoCenterCrop(false); srcImage.setCropRect(cropRect); }; if(loadDistortion) { if(srcImage.readDistortionFromDB()) { cout << "\tRead distortion data from lensfun database." << endl; } else { cout << "\tNo valid distortion data found in lensfun database." << endl; }; }; if(loadVignetting) { if(srcImage.readVignettingFromDB()) { cout << "\tRead vignetting data from lensfun database." << endl; } else { cout << "\tNo valid vignetting data found in lensfun database." << endl; }; }; pano.addImage(srcImage); }; if(pano.getNrOfImages()==0) { cerr << "Adding images to project files failed." << endl; HuginBase::LensDB::LensDB::Clean(); return 1; }; //link lenses if(pano.getNrOfImages()>1) { double redBalanceAnchor=pano.getImage(pano.getOptions().colorReferenceImage).getExifRedBalance(); double blueBalanceAnchor=pano.getImage(pano.getOptions().colorReferenceImage).getExifBlueBalance(); if(fabs(redBalanceAnchor)<1e-2) { redBalanceAnchor=1; }; if(fabs(blueBalanceAnchor)<1e-2) { blueBalanceAnchor=1; }; StandardImageVariableGroups variable_groups(pano); ImageVariableGroup& lenses = variable_groups.getLenses(); for(size_t i=1;i<pano.getNrOfImages();i++) { int image=-1; const SrcPanoImage & srcImg=pano.getImage(i); for(size_t j=0;j<i;j++) { const SrcPanoImage & compareImg=pano.getImage(j); if(srcImg.getHFOV()==compareImg.getHFOV() && srcImg.getProjection()==compareImg.getProjection() && srcImg.getExifModel()==compareImg.getExifModel() && srcImg.getExifMake()==compareImg.getExifMake() && srcImg.getSize()==compareImg.getSize()) { image=j; break; }; }; if(image!=-1) { SrcPanoImage img=pano.getSrcImage(i); double ev=img.getExposureValue(); lenses.switchParts(i,lenses.getPartNumber(image)); lenses.unlinkVariableImage(HuginBase::ImageVariableGroup::IVE_ExposureValue, i); img.setExposureValue(ev); lenses.unlinkVariableImage(HuginBase::ImageVariableGroup::IVE_WhiteBalanceRed, i); lenses.unlinkVariableImage(HuginBase::ImageVariableGroup::IVE_WhiteBalanceBlue, i); img.setWhiteBalanceRed(img.getExifRedBalance()/redBalanceAnchor); img.setWhiteBalanceBlue(img.getExifBlueBalance()/blueBalanceAnchor); pano.setSrcImage(i, img); }; }; cout << endl << "Assigned " << lenses.getNumberOfParts() << " lenses." << endl; if(lenses.getNumberOfParts()>1 && stackLength>1) { cout << "Project contains more than one lens, but you requested to assign" << endl << "stacks. This is not supported. Therefore stacks will not be" << endl << "assigned." << endl << endl; stackLength=1; }; }; //link stacks if(pano.getNrOfImages()>1 && stackLength>1) { stackLength=std::min<int>(stackLength,pano.getNrOfImages()); int stackCount=pano.getNrOfImages() / stackLength; if(pano.getNrOfImages() % stackLength > 0) { stackCount++; }; if(stackCount<pano.getNrOfImages()) { for(size_t stackNr=0;stackNr<stackCount;stackNr++) { size_t firstImgStack=stackNr*stackLength; for(size_t i=0;i<stackLength;i++) { if(firstImgStack+i<pano.getNrOfImages()) { pano.linkImageVariableStack(firstImgStack,firstImgStack+i); if(linkStacks) { pano.linkImageVariableYaw(firstImgStack,firstImgStack+i); pano.linkImageVariablePitch(firstImgStack,firstImgStack+i); pano.linkImageVariableRoll(firstImgStack,firstImgStack+i); }; }; }; }; cout << "Assigned " << stackCount << " stacks." << endl; }; }; //set output exposure value PanoramaOptions opt = pano.getOptions(); opt.outputExposureValue = CalculateMeanExposure::calcMeanExposure(pano); pano.setOptions(opt); // set optimizer switches pano.setOptimizerSwitch(HuginBase::OPT_PAIR); pano.setPhotometricOptimizerSwitch(HuginBase::OPT_EXPOSURE | HuginBase::OPT_VIGNETTING | HuginBase::OPT_RESPONSE); //output if(output=="") { output=hugin_utils::stripExtension(pano.getImage(0).getFilename()); if(pano.getNrOfImages()>1) { output.append("-"); output.append(hugin_utils::stripExtension(hugin_utils::stripPath(pano.getImage(pano.getNrOfImages()-1).getFilename()))); }; output=output.append(".pto"); }; output=GetAbsoluteFilename(output); //write output UIntSet imgs; fill_set(imgs,0, pano.getNrOfImages()-1); ofstream of(output.c_str()); pano.printPanoramaScript(of, pano.getOptimizeVector(), pano.getOptions(), imgs, false, hugin_utils::getPathPrefix(output)); cout << endl << "Written output to " << output << endl; HuginBase::LensDB::LensDB::Clean(); return 0; }