// -*- c-basic-offset: 4 -*- /** @file nona/Stitcher.h * * Contains various routines used for stitching panoramas. * * @author Pablo d'Angelo * * $Id$ * * This is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #ifndef _NONA_STITCHER_H #define _NONA_STITCHER_H // To avoid windows.h namespace pollution later on #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // calculate distance image for multi file output #define STITCHER_CALC_DIST_IMG 0 // somehow these are still #undef DIFFERENCE #undef min #undef max #undef MIN #undef MAX namespace HuginBase { namespace Nona { /** determine blending order (starting with image 0), and continue to * stitch the image with the biggest overlap area with the real image.. * do everything on a low res version of the masks */ void estimateBlendingOrder(const PanoramaData & pano, UIntSet images, std::vector & blendOrder); #if 0 /** * remap a single image to a multiple/multilayer tiff files. * * If tiff already contains tiff files, append to the file */ template bool remapToTiff(const PanoramaData & pano, const PanoramaOptions &opts, unsigned imgNr, TIFF * tiff AppBase::MultiProgressDisplay & progress) { bool saveRoi = opts.tiff_saveROI; const PanoImage & img = pano.getImage(imgNr); vigra::Diff2D srcSize; srcSize.x = img.getWidth(); srcSize.y = img.getHeight(); // create transforms // SpaceTransform t; // SpaceTransform invT; PTools::Transform transf; PTools::Transform invTransf; transf.createTransform(pano, imgNr, opts, srcSize); invTransf.createInvTransform(pano, imgNr, opts, srcSize); // calculate ROI for this image. ImageOptions imgOpts = img.getOptions(); vigra::Rect2D imageRect; vigra::BImage miniAlpha; double alphaScale; bool circCrop = pano.getLens(pano.getImage(imgNr).getLensNr()).getProjection() == Lens::CIRCULAR_FISHEYE; estimateImageRect(Size2D(opts.width, opts.getHeight()), srcSize, imgOpts.docrop, imgOpts.cropRect, circCrop, transf, imageRect, miniAlpha, alphaScale); /** the scanlines */ ImageType img(; AlphaType alpha; // write TIFF header if (saveRoi) { vigra_ext::createTiffDirectory(tiff, img.getFilename(), m_basename, opts.tiffCompression, imgNr+1, nImg, imageRect.upperLeft()); } else { vigra_ext::createTiffDirectory(tiff, img.getFilename(), m_basename, opts.tiffCompression, imgNr+1, nImg, vigra::Diff2D(0,0)); } #endif /** implements a stitching algorithm */ template class Stitcher { public: /** create a stitcher for the given panorama */ Stitcher(const PanoramaData & pano, AppBase::MultiProgressDisplay & prog) : m_pano(pano), m_progress(prog) { } virtual ~Stitcher() {}; /** Stitch some images into a panorama file. * * The filename can be specified with and without extension */ virtual void stitch(const PanoramaOptions & opts, UIntSet & images, const std::string & file, SingleImageRemapper & remapper) { m_images=images; calcOutputROIS(opts, images); }; virtual UIntSet getUsedImages() { UIntSet ret; assert(m_rois.size() == m_images.size()); std::vector::iterator itr = m_rois.begin(); for(UIntSet::iterator it = m_images.begin(); it != m_images.end(); ++it) { if (! itr->isEmpty()) { ret.insert(*it); } } return ret; } // template //void stitch(const PanoramaOptions & opts, UIntSet & images, vigra::triple output); protected: // calculate ROI's if not already known virtual void calcOutputROIS(const PanoramaOptions & opts, const UIntSet & images) { m_rois = HuginBase::ComputeImageROI::computeROIS(m_pano, opts, images); } const PanoramaData & m_pano; AppBase::MultiProgressDisplay & m_progress; UIntSet m_images; std::vector m_rois; }; /** remap a set of images, and store the individual remapped files. */ template class MultiImageRemapper : public Stitcher { public: typedef Stitcher Base; MultiImageRemapper(const PanoramaData & pano, AppBase::MultiProgressDisplay & progress) : Stitcher(pano, progress) { } virtual ~MultiImageRemapper() { } virtual void stitch(const PanoramaOptions & opts, UIntSet & images, const std::string & basename, SingleImageRemapper & remapper) { Base::stitch(opts, images, basename, remapper); DEBUG_ASSERT(opts.outputFormat == PanoramaOptions::TIFF_multilayer || opts.outputFormat == PanoramaOptions::TIFF_m || opts.outputFormat == PanoramaOptions::HDR_m || opts.outputFormat == PanoramaOptions::EXR_m); m_basename = basename; // setup the output. prepareOutputFile(opts); unsigned int nImg = images.size(); Base::m_progress.pushTask(AppBase::ProgressTask("Remapping", "", 1.0/(nImg))); // remap each image and save int i=0; for (UIntSet::const_iterator it = images.begin(); it != images.end(); ++it) { // get a remapped image. RemappedPanoImage * remapped = remapper.getRemapped(Base::m_pano, opts, *it, Base::m_rois[i], Base::m_progress); try { saveRemapped(*remapped, *it, Base::m_pano.getNrOfImages(), opts); } catch (vigra::PreconditionViolation & e) { // this can be thrown, if an image // is completely out of the pano std::cerr << e.what(); } // free remapped image remapper.release(remapped); i++; } Base::m_progress.popTask(); finalizeOutputFile(opts); } /** prepare the output file (setup file structures etc.) */ virtual void prepareOutputFile(const PanoramaOptions & opts) { } /** save a remapped image, or layer */ virtual void saveRemapped(RemappedPanoImage & remapped, unsigned int imgNr, unsigned int nImg, const PanoramaOptions & opts) { ImageType * final_img = 0; AlphaType * alpha_img = 0; ImageType complete; vigra::BImage alpha; if ( remapped.boundingBox().isEmpty() ) // do not save empty files... // TODO: need to tell other parts (enblend etc.) about it too! return; if (opts.outputMode == PanoramaOptions::OUTPUT_HDR) { // export alpha channel as gray channel (original pixel weights) std::ostringstream greyname; greyname << m_basename << std::setfill('0') << std::setw(4) << imgNr << "_gray.pgm"; vigra::ImageExportInfo exinfo1(greyname.str().c_str()); if (! opts.tiff_saveROI) { alpha.resize(opts.getROI().size()); vigra::Rect2D newOutRect = remapped.boundingBox(); newOutRect.moveBy(-opts.getROI().upperLeft()); vigra::copyImage(vigra_ext::applyRect(remapped.boundingBox(), vigra_ext::srcMaskRange(remapped)), vigra_ext::applyRect(newOutRect, destImage(alpha))); vigra::exportImage(srcImageRange(alpha), exinfo1); } else { exinfo1.setPosition(remapped.boundingBox().upperLeft()); exinfo1.setCanvasSize(vigra::Size2D(opts.getWidth(), opts.getHeight())); vigra::exportImage(srcImageRange(remapped.m_mask), exinfo1); } // calculate real alpha for saving with the image Base::m_progress.setMessage("Calculating mask"); remapped.calcAlpha(); } if (! opts.tiff_saveROI) { // FIXME: this is stupid. Should not require space for full image... // but this would need a lower level interface in vigra impex complete.resize(opts.getROI().size()); alpha.resize(opts.getROI().size()); vigra::Rect2D newOutRect = remapped.boundingBox(); newOutRect.moveBy(-opts.getROI().upperLeft()); vigra::copyImage(vigra_ext::applyRect(remapped.boundingBox(), vigra_ext::srcImageRange(remapped)), vigra_ext::applyRect(newOutRect, destImage(complete))); vigra::copyImage(vigra_ext::applyRect(remapped.boundingBox(), vigra_ext::srcMaskRange(remapped)), vigra_ext::applyRect(newOutRect, destImage(alpha))); final_img = & complete; alpha_img = & alpha; } else { final_img = &remapped.m_image; alpha_img = &remapped.m_mask; } /* std::string cext = hugin_utils::getExtension(m_basename); std::transform(cext.begin(),cext.end(), cext.begin(), (int(*)(int))std::tolower); // remove extension only if it specifies the same file type, otherwise // its probably part of the filename. if (cext == ext) { m_basename = hugin_utils::stripExtension(m_basename); } */ std::string ext = opts.getOutputExtension(); std::ostringstream filename; filename << m_basename << std::setfill('0') << std::setw(4) << imgNr << "." + ext; Base::m_progress.setMessage(std::string("saving ") + hugin_utils::stripPath(filename.str())); vigra::ImageExportInfo exinfo(filename.str().c_str()); exinfo.setXResolution(150); exinfo.setYResolution(150); exinfo.setICCProfile(remapped.m_ICCProfile); if (opts.tiff_saveROI) { exinfo.setPosition(remapped.boundingBox().upperLeft()); exinfo.setCanvasSize(vigra::Size2D(opts.getWidth(), opts.getHeight())); } else { exinfo.setPosition(opts.getROI().upperLeft()); exinfo.setCanvasSize(vigra::Size2D(opts.getWidth(), opts.getHeight())); } if (opts.outputPixelType.size() > 0) { exinfo.setPixelType(opts.outputPixelType.c_str()); } if (ext == "tif") { exinfo.setCompression(opts.tiffCompression.c_str()); } vigra::exportImageAlpha(srcImageRange(*final_img), srcImage(*alpha_img), exinfo); if (opts.saveCoordImgs) { vigra::UInt16Image xImg; vigra::UInt16Image yImg; Base::m_progress.setMessage("creating coordinate images"); remapped.calcSrcCoordImgs(xImg, yImg); vigra::UInt16Image dist; if (! opts.tiff_saveROI) { dist.resize(opts.getWidth(), opts.getHeight()); vigra::copyImage(srcImageRange(xImg), vigra_ext::applyRect(remapped.boundingBox(), destImage(dist))); std::ostringstream filename2; filename2 << m_basename << std::setfill('0') << std::setw(4) << imgNr << "_x.tif"; vigra::ImageExportInfo exinfo(filename2.str().c_str()); exinfo.setXResolution(150); exinfo.setYResolution(150); exinfo.setCompression(opts.tiffCompression.c_str()); vigra::exportImage(srcImageRange(dist), exinfo); vigra::copyImage(srcImageRange(yImg), vigra_ext::applyRect(remapped.boundingBox(), destImage(dist))); std::ostringstream filename3; filename3 << m_basename << std::setfill('0') << std::setw(4) << imgNr << "_y.tif"; vigra::ImageExportInfo exinfo2(filename3.str().c_str()); exinfo2.setXResolution(150); exinfo2.setYResolution(150); exinfo2.setCompression(opts.tiffCompression.c_str()); vigra::exportImage(srcImageRange(dist), exinfo2); } else { std::ostringstream filename2; filename2 << m_basename << std::setfill('0') << std::setw(4) << imgNr << "_x.tif"; vigra::ImageExportInfo exinfo(filename2.str().c_str()); exinfo.setXResolution(150); exinfo.setYResolution(150); exinfo.setCompression(opts.tiffCompression.c_str()); vigra::exportImage(srcImageRange(xImg), exinfo); std::ostringstream filename3; filename3 << m_basename << std::setfill('0') << std::setw(4) << imgNr << "_y.tif"; vigra::ImageExportInfo exinfo2(filename3.str().c_str()); exinfo2.setXResolution(150); exinfo2.setYResolution(150); exinfo2.setCompression(opts.tiffCompression.c_str()); vigra::exportImage(srcImageRange(yImg), exinfo2); } } } virtual void finalizeOutputFile(const PanoramaOptions & opts) { } protected: std::string m_basename; }; /** stitch multilayer */ template class TiffMultiLayerRemapper : public MultiImageRemapper { public: typedef MultiImageRemapper Base; TiffMultiLayerRemapper(const PanoramaData & pano, AppBase::MultiProgressDisplay & progress) : MultiImageRemapper (pano, progress) { } virtual ~TiffMultiLayerRemapper() { } virtual void prepareOutputFile(const PanoramaOptions & opts) { std::string filename = Base::m_basename + ".tif"; DEBUG_DEBUG("Layering image into a multi image tif file " << filename); m_tiff = TIFFOpen(filename.c_str(), "w"); DEBUG_ASSERT(m_tiff && "could not open tiff output file"); } /** save the remapped image in a partial tiff layer */ virtual void saveRemapped(RemappedPanoImage & remapped, unsigned int imgNr, unsigned int nImg, const PanoramaOptions & opts) { if (remapped.boundingBox().isEmpty()) return; DEBUG_DEBUG("Saving TIFF ROI"); vigra_ext::createTiffDirectory(m_tiff, Base::m_pano.getImage(imgNr).getFilename(), Base::m_basename, opts.tiffCompression, imgNr+1, nImg, remapped.boundingBox().upperLeft(), opts.getROI().size(), remapped.m_ICCProfile); vigra_ext::createAlphaTiffImage(vigra::srcImageRange(remapped.m_image), vigra::maskImage(remapped.m_mask), m_tiff); TIFFFlush(m_tiff); } /** close the tiff file */ virtual void finalizeOutputFile(const PanoramaOptions & opts) { TIFFClose(m_tiff); } protected: vigra::TiffImage * m_tiff; }; typedef std::vector > AlphaVector; /** functor to calculate the union of two images. also calculates the * size of the union */ struct CalcMaskUnion { CalcMaskUnion() : count(0) { } template PIXEL operator()(PIXEL const & img1, PIXEL const & img2) { if (img1 > vigra::NumericTraits::zero() && img2 > vigra::NumericTraits::zero()) { count++; return img1; } else { return vigra::NumericTraits::zero(); } } int count; }; template class WeightedStitcher : public Stitcher { public: typedef Stitcher Base; WeightedStitcher(const PanoramaData & pano, AppBase::MultiProgressDisplay & progress) : Stitcher(pano, progress) { } virtual ~WeightedStitcher() {}; template void stitch(const PanoramaOptions & opts, UIntSet & imgSet, vigra::triple pano, std::pair alpha, SingleImageRemapper & remapper) { std::vector images; // calculate stitching order estimateBlendingOrder(Base::m_pano, imgSet, images); unsigned int nImg = images.size(); Base::m_progress.pushTask(AppBase::ProgressTask("Stitching", "", 1.0/(nImg))); // empty ROI vigra::Rect2D panoROI; int i=0; // remap each image and blend into main pano image for (UIntVector::const_iterator it = images.begin(); it != images.end(); ++it) { // get a remapped image. DEBUG_DEBUG("remapping image: " << *it); RemappedPanoImage * remapped = remapper.getRemapped(Base::m_pano, opts, *it, Base::m_rois[i], Base::m_progress); if(iccProfile.size()==0) { iccProfile=remapped->m_ICCProfile; }; Base::m_progress.setMessage("blending"); // add image to pano and panoalpha, adjusts panoROI as well. try { vigra_ext::blend(*remapped, pano, alpha, panoROI, Base::m_progress); // update bounding box of the panorama panoROI = panoROI | remapped->boundingBox(); } catch (vigra::PreconditionViolation & e) { DEBUG_ERROR("exception during stitching" << e.what()); // this can be thrown, if an image // is completely out of the pano } // free remapped image remapper.release(remapped); i++; } } void stitch(const PanoramaOptions & opts, UIntSet & imgSet, const std::string & filename, SingleImageRemapper & remapper) { Base::stitch(opts, imgSet, filename, remapper); std::string basename = filename; // create panorama canvas ImageType pano(opts.getWidth(), opts.getHeight()); AlphaType panoMask(opts.getWidth(), opts.getHeight()); stitch(opts, imgSet, vigra::destImageRange(pano), vigra::destImage(panoMask), remapper); std::string ext = opts.getOutputExtension(); std::string cext = hugin_utils::getExtension(basename); std::transform(cext.begin(),cext.end(), cext.begin(), (int(*)(int))std::tolower); // remove extension only if it specifies the same file type, otherwise // its probably part of the filename. if (cext == ext) { basename = hugin_utils::stripExtension(basename); } std::string outputfile = basename + "." + ext; // save the remapped image Base::m_progress.setMessage("saving result: " + hugin_utils::stripPath(outputfile)); DEBUG_DEBUG("Saving panorama: " << outputfile); vigra::ImageExportInfo exinfo(outputfile.c_str()); exinfo.setXResolution(150); exinfo.setYResolution(150); exinfo.setICCProfile(iccProfile); if (opts.outputPixelType.size() > 0) { exinfo.setPixelType(opts.outputPixelType.c_str()); } // set compression quality for jpeg images. if (opts.outputFormat == PanoramaOptions::JPEG) { char jpgCompr[4]; snprintf(jpgCompr,4,"%d", opts.quality); exinfo.setCompression(jpgCompr); vigra::exportImage(srcImageRange(pano), exinfo); } else if (opts.outputFormat == PanoramaOptions::TIFF) { exinfo.setCompression(opts.tiffCompression.c_str()); vigra::exportImageAlpha(srcImageRange(pano), srcImage(panoMask), exinfo); } else if (opts.outputFormat == PanoramaOptions::HDR) { exinfo.setPixelType("FLOAT"); vigra::exportImage(srcImageRange(pano), exinfo); } else { vigra::exportImageAlpha(srcImageRange(pano), srcImage(panoMask), exinfo); } /* #ifdef DEBUG vigra::exportImage(srcImageRange(panoMask), vigra::ImageExportInfo("pano_alpha.tif")); #endif */ Base::m_progress.popTask(); } protected: vigra::ImageImportInfo::ICCProfile iccProfile; }; /** Difference reduce functor */ template struct ReduceToDifferenceFunctor { typedef VALUETYPE argument_type; typedef VALUETYPE result_type; typedef typename vigra::NumericTraits Traits; typedef typename Traits::RealPromote float_type; ReduceToDifferenceFunctor() { reset(); } void reset() { sum = vigra::NumericTraits::zero(); values.clear(); } template void operator() (T const &v, M const &a) { if (a) { values.push_back(v); sum = sum + v; } } /** return the result */ float_type operator()() const { typedef typename std::vector::const_iterator Iter; if (values.size() > 1) { float_type mean = sum / values.size(); float_type error = vigra::NumericTraits::zero(); for (Iter it= values.begin(); it != values.end(); ++it) { error += vigra::abs(*it-mean); } return error; } else { return sum; } } std::vector values; float_type sum; }; /** create a panorama using the reduce operation on all overlapping * pixels. WARNING: this version is very memory hungry for large * panoramas (> 3 images ;-) */ template class ReduceStitcher : public Stitcher { typedef Stitcher Base; public: ReduceStitcher(const PanoramaData & pano, AppBase::MultiProgressDisplay & progress) : Stitcher(pano, progress) { } virtual ~ReduceStitcher() { } template void stitch(const PanoramaOptions & opts, UIntSet & imgSet, const std::string & filename, SingleImageRemapper & remapper, FUNCTOR & reduce) { Base::stitch(opts, imgSet, filename, remapper); std::string basename = filename; // create panorama canvas ImageType pano(opts.getWidth(), opts.getHeight()); AlphaType panoMask(opts.getWidth(), opts.getHeight()); stitch(opts, imgSet, vigra::destImageRange(pano), vigra::destImage(panoMask), remapper, reduce); std::string ext = opts.getOutputExtension(); std::string cext = hugin_utils::getExtension(basename); std::transform(cext.begin(),cext.end(), cext.begin(), (int(*)(int))std::tolower); // remove extension only if it specifies the same file type, otherwise // its probably part of the filename. if (cext == ext) { basename = hugin_utils::stripExtension(basename); } std::string outputfile = basename + "." + ext; // Base::m_progress.setMessage("saving result: " + hugin_utils::stripPath(outputfile)); DEBUG_DEBUG("Saving panorama: " << outputfile); vigra::ImageExportInfo exinfo(outputfile.c_str()); if (opts.outputPixelType.size() > 0) { exinfo.setPixelType(opts.outputPixelType.c_str()); } exinfo.setXResolution(150); exinfo.setYResolution(150); exinfo.setICCProfile(iccProfile); // set compression quality for jpeg images. if (opts.outputFormat == PanoramaOptions::JPEG) { char jpgCompr[4]; snprintf(jpgCompr,4,"%d", opts.quality); exinfo.setCompression(jpgCompr); vigra::exportImage(srcImageRange(pano), exinfo); } else if (opts.outputFormat == PanoramaOptions::TIFF) { exinfo.setCompression(opts.tiffCompression.c_str()); vigra::exportImageAlpha(srcImageRange(pano), srcImage(panoMask), exinfo); } else if (opts.outputFormat == PanoramaOptions::HDR) { vigra::exportImage(srcImageRange(pano), exinfo); } else { vigra::exportImageAlpha(srcImageRange(pano), srcImage(panoMask), exinfo); } /* #ifdef DEBUG vigra::exportImage(srcImageRange(panoMask), vigra::ImageExportInfo("pano_alpha.tif")); #endif */ } template void stitch(const PanoramaOptions & opts, UIntSet & imgSet, vigra::triple pano, std::pair alpha, SingleImageRemapper & remapper, FUNCTOR & reduce) { typedef typename vigra::NumericTraits Traits; typedef typename Traits::RealPromote RealImgType; typedef typename ImageType::value_type ImgType; typedef typename AlphaAccessor::value_type MaskType; Base::stitch(opts, imgSet, "dummy", remapper); // remap all images.. typedef std::vector *> RemappedVector; unsigned int nImg = imgSet.size(); Base::m_progress.pushTask(AppBase::ProgressTask("Stitching", "", 1.0/(nImg))); // empty ROI // vigra::Rect2D panoROI; // remap all images.. RemappedVector remapped(nImg); int i=0; // remap each image and blend into main pano image for (UIntSet::const_iterator it = imgSet.begin(); it != imgSet.end(); ++it) { // get a copy of the remapped image. // not very good, keeps all images in memory, // but should be enought for the preview. remapped[i] = remapper.getRemapped(Base::m_pano, opts, *it, Base::m_rois[i], Base::m_progress); if(iccProfile.size()==0) { iccProfile=remapped[i]->m_ICCProfile; }; i++; } vigra::Diff2D size = pano.second - pano.first; ImgIter output = pano.first; // iterate over the whole image... // calculate something on the pixels that belong together.. for (int y=0; y < size.y; y++) { for (int x=0; x < size.x; x++) { reduce.reset(); MaskType maskRes=0; for (unsigned int i=0; i< nImg; i++) { MaskType a = remapped[i]->getMask(x,y); if (a) { maskRes = vigra_ext::LUTTraits::max(); reduce(remapped[i]->operator()(x,y), a); } } pano.third.set(Traits::fromRealPromote(reduce()), output, vigra::Diff2D(x,y)); alpha.second.set(maskRes, alpha.first, vigra::Diff2D(x,y)); } } Base::m_progress.popTask(); for (typename RemappedVector::iterator it=remapped.begin(); it != remapped.end(); ++it) { remapper.release(*it); } } protected: vigra::ImageImportInfo::ICCProfile iccProfile; }; /** A stitcher without seaming, just copies the images over each other */ template class SimpleStitcher : public Stitcher { typedef Stitcher Base; public: SimpleStitcher(const PanoramaData & pano, AppBase::MultiProgressDisplay & progress) : Stitcher(pano, progress) { } virtual ~SimpleStitcher() { } template void stitch(const PanoramaOptions & opts, UIntSet & imgSet, vigra::triple pano, std::pair alpha, SingleImageRemapper & remapper, BlendFunctor & blend) { Base::m_images=imgSet; Base::calcOutputROIS(opts, imgSet); unsigned int nImg = imgSet.size(); Base::m_progress.pushTask(AppBase::ProgressTask("Stitching", "", 1.0/(nImg))); // empty ROI vigra::Rect2D panoROI; unsigned i=0; // remap each image and blend into main pano image for (UIntSet::reverse_iterator it = imgSet.rbegin(); it != imgSet.rend(); ++it) { // get a remapped image. RemappedPanoImage * remapped = remapper.getRemapped(Base::m_pano, opts, *it, Base::m_rois[i], Base::m_progress); if (iccProfile.size() == 0) { // try to extract icc profile. iccProfile = remapped->m_ICCProfile; } Base::m_progress.setMessage("blending"); // add image to pano and panoalpha, adjusts panoROI as well. try { blend(*remapped, pano, alpha, panoROI); // update bounding box of the panorama panoROI = panoROI | remapped->boundingBox(); } catch (vigra::PreconditionViolation & e) { // this can be thrown, if an image // is completely out of the pano std::cerr << e.what(); } // free remapped image remapper.release(remapped); i++; } Base::m_progress.popTask(); } template void stitch(const PanoramaOptions & opts, UIntSet & imgSet, const std::string & filename, SingleImageRemapper & remapper, BlendFunctor & blend) { std::string basename = filename; // create panorama canvas ImageType pano(opts.getWidth(), opts.getHeight()); AlphaType panoMask(opts.getWidth(), opts.getHeight()); stitch(opts, imgSet, vigra::destImageRange(pano), vigra::destImage(panoMask), remapper, blend); std::string ext = opts.getOutputExtension(); std::string cext = hugin_utils::getExtension(basename); std::transform(cext.begin(),cext.end(), cext.begin(), (int(*)(int))std::tolower); // remove extension only if it specifies the same file type, otherwise // its probably part of the filename. if (cext == ext) { basename = hugin_utils::stripExtension(basename); } std::string outputfile = basename + "." + ext; Base::m_progress.setMessage("saving result: " + hugin_utils::stripPath(outputfile)); DEBUG_DEBUG("Saving panorama: " << outputfile); vigra::ImageExportInfo exinfo(outputfile.c_str()); exinfo.setXResolution(150); exinfo.setYResolution(150); exinfo.setICCProfile(iccProfile); // set compression quality for jpeg images. if (opts.outputFormat == PanoramaOptions::JPEG) { char jpgCompr[4]; snprintf(jpgCompr,4,"%d", opts.quality); exinfo.setCompression(jpgCompr); vigra::exportImage(srcImageRange(pano), exinfo); } else if (opts.outputFormat == PanoramaOptions::TIFF) { exinfo.setCompression("DEFLATE"); vigra::exportImageAlpha(srcImageRange(pano), srcImage(panoMask), exinfo); } else { vigra::exportImageAlpha(srcImageRange(pano), srcImage(panoMask), exinfo); } /* #ifdef DEBUG vigra::exportImage(srcImageRange(panoMask), vigra::ImageExportInfo("pano_alpha.tif")); #endif */ Base::m_progress.popTask(); } protected: vigra::ImageExportInfo::ICCProfile iccProfile; }; /** blend images, by simply stacking them, without soft blending or * boundary calculation */ struct StackingBlender { public: /** blend \p img into \p pano, using \p alpha mask and \p panoROI * * updates \p pano, \p alpha and \p panoROI */ template void operator()(RemappedPanoImage & img, vigra::triple pano, std::pair alpha, const vigra::Rect2D & panoROI) { using namespace vigra_ext; DEBUG_DEBUG("pano roi: " << panoROI << " img roi: " << img.boundingBox()); typedef typename AlphaIter::value_type AlphaValue; // DEBUG_DEBUG("no overlap, copying upper area. imgroi " << img.roi()); // DEBUG_DEBUG("pano roi: " << panoROI.upperLeft() << " -> " << panoROI.lowerRight()); DEBUG_DEBUG("size of panorama: " << pano.second - pano.first); // check if bounding box of image is outside of panorama... vigra::Rect2D fullPano(vigra::Size2D(pano.second-pano.first)); // blend only the intersection (which is inside the pano..) vigra::Rect2D overlap = fullPano & img.boundingBox(); vigra::copyImageIf(applyRect(overlap, vigra_ext::srcImageRange(img)), applyRect(overlap, vigra_ext::srcMask(img)), applyRect(overlap, std::make_pair(pano.first, pano.third))); // copy mask vigra::copyImageIf(applyRect(overlap, srcMaskRange(img)), applyRect(overlap, srcMask(img)), applyRect(overlap, alpha)); } }; /** seam blender. */ struct SeamBlender { public: /** blend \p img into \p pano, using \p alpha mask and \p panoROI * * updates \p pano, \p alpha and \p panoROI */ template void operator()(RemappedPanoImage & img, vigra::triple pano, std::pair alpha, const vigra::Rect2D & panoROI) { DEBUG_DEBUG("pano roi: " << panoROI << " img roi: " << img.boundingBox()); AppBase::MultiProgressDisplay dummy; vigra_ext::blend(img, pano, alpha, panoROI, dummy); } }; /** blend by difference */ struct DifferenceBlender { public: /** blend \p img into \p pano, using \p alpha mask and \p panoROI * * updates \p pano, \p alpha and \p panoROI */ template void operator()(RemappedPanoImage & img, vigra::triple pano, std::pair alpha, const vigra::Rect2D & panoROI) { DEBUG_DEBUG("pano roi: " << panoROI << " img roi: " << img.boundingBox()); typedef typename AlphaIter::value_type AlphaValue; // check if bounding box of image is outside of panorama... vigra::Rect2D fullPano(vigra::Size2D(pano.second-pano.first)); // blend only the intersection (which is inside the pano..) vigra::Rect2D overlap = fullPano & img.boundingBox(); vigra::combineTwoImagesIf(applyRect(overlap, vigra_ext::srcImageRange(img)), applyRect(overlap, std::make_pair(pano.first, pano.third)), applyRect(overlap, vigra_ext::srcMask(img)), applyRect(overlap, std::make_pair(pano.first, pano.third)), abs(vigra::functor::Arg1()-vigra::functor::Arg2())); // copy mask vigra::copyImageIf(applyRect(overlap, srcMaskRange(img)), applyRect(overlap, srcMask(img)), applyRect(overlap, alpha)); } }; /* template class MultiResStitcher : public WeightedSticher { public: MultiResStitcher(const PanoramaData & pano, AppBase::MultiProgressDisplay & progress) : WeightedStitcher(pano, progress) { } template virtual void blendOverlap(vigra::triple image, std::pair imageMask, std::pair pano, std::pair panoMask) { vigra::Diff2D size = image.second - image.first; // create new blending masks vigra::BasicImage blendPanoMask(size); vigra::BasicImage blendImageMask(size); // calculate the stitching masks. vigra_ext::nearestFeatureTransform(srcIterRange(panoMask.first, panoMask.first + size), imageMask, destImage(blendPanoMask), destImage(blendImageMask), m_progress); // calculate the a number of // copy the image into the panorama vigra::copyImageIf(image, maskImage(blendImageMask), pano); // copy mask vigra::copyImageIf(srcImageRange(blendImageMask), maskImage(blendImageMask), panoMask); } private: }; */ #if 0 /** small convinience function to remap and stitch a HDR image * Warning, this requires tons of memory, since everything is * hold on disk. */ template void stitchToHDR(Remapper & remappers, OutputImage & panoImg, Mask & mask, AppBase::MultiProgressDisplay & progress) { std::vector *> remapped; // get all remapped images for (UIntSet::const_iterator it = displayedImages.begin(); it != displayedImages.end(); ++it) { remapped.push_back(remapper.getRemapped(pano, opts, *it, progress)); } ReduceToHDRFunctor > hdrmerge; reduceROIImages(remapped, destImageRange(panoImg), destImage(alpha), hdrmerge); #endif template static void stitchPanoIntern(const PanoramaData & pano, const PanoramaOptions & opts, AppBase::MultiProgressDisplay & progress, const std::string & basename, UIntSet imgs) { using namespace vigra_ext; // typedef // vigra::NumericTraits DestTraits; FileRemapper m; // determine stitching output switch (opts.outputFormat) { case PanoramaOptions::JPEG: case PanoramaOptions::PNG: case PanoramaOptions::TIFF: case PanoramaOptions::HDR: case PanoramaOptions::EXR: { if (opts.outputMode == PanoramaOptions::OUTPUT_HDR) { ReduceToHDRFunctor hdrmerge; ReduceStitcher stitcher(pano, progress); stitcher.stitch(opts, imgs, basename, m, hdrmerge); } else { WeightedStitcher stitcher(pano, progress); stitcher.stitch(opts, imgs, basename, m); } break; } case PanoramaOptions::TIFF_m: case PanoramaOptions::HDR_m: case PanoramaOptions::EXR_m: { MultiImageRemapper stitcher(pano, progress); stitcher.stitch(opts, imgs, basename, m); break; } case PanoramaOptions::TIFF_multilayer: { TiffMultiLayerRemapper stitcher(pano, progress); stitcher.stitch(opts, imgs, basename, m); break; } case PanoramaOptions::TIFF_mask: case PanoramaOptions::TIFF_multilayer_mask: DEBUG_ERROR("multi mask stitching not implemented!"); break; default: DEBUG_ERROR("output format " << opts.getFormatName(opts.outputFormat) << "not supported"); break; } } /** stitch a panorama * * @todo vignetting correction * @todo do not keep complete output image in memory * */ IMPEX void stitchPanorama(const PanoramaData & pano, const PanoramaOptions & opts, AppBase::MultiProgressDisplay & progress, const std::string & basename, const UIntSet & usedImgs); // the instantiations of the stitching functions have been divided into two .cpp // files, because g++ will use too much memory otherwise (> 1.5 GB) void stitchPanoGray_8_16(const PanoramaData & pano, const PanoramaOptions & opts, AppBase::MultiProgressDisplay & progress, const std::string & basename, const UIntSet & usedImgs, const char * pixelType); void stitchPanoGray_32_float(const PanoramaData & pano, const PanoramaOptions & opts, AppBase::MultiProgressDisplay & progress, const std::string & basename, const UIntSet & usedImgs, const char * pixelType); void stitchPanoRGB_8_16(const PanoramaData & pano, const PanoramaOptions & opts, AppBase::MultiProgressDisplay & progress, const std::string & basename, const UIntSet & usedImgs, const char * pixelType); void stitchPanoRGB_32_float(const PanoramaData & pano, const PanoramaOptions & opts, AppBase::MultiProgressDisplay & progress, const std::string & basename, const UIntSet & usedImgs, const char * pixelType); } // namespace } // namespace #endif // _H