Hugin trunk 0.1
Loading...
Searching...
No Matches
StitchWatershed.h
Go to the documentation of this file.
1// -*- c-basic-offset: 4 -*-
2
12/* This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public
14 * License as published by the Free Software Foundation; either
15 * version 2 of the License, or (at your option) any later version.
16 *
17 * This software is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public
23 * License along with this software. If not, see
24 * <http://www.gnu.org/licenses/>.
25 *
26 */
27
28#include <vigra/seededregiongrowing.hxx>
29#include <vigra/convolution.hxx>
31#ifdef HAVE_OPENMP
32#include <omp.h>
33#endif
34#include "openmp_vigra.h"
35
36namespace vigra_ext
37{
38 namespace detail
39 {
40 // some helper functions
41 struct BuildSeed
42 {
43 template <class PixelType>
44 PixelType operator()(PixelType const& v1, PixelType const& v2) const
45 {
46 return (v1 & 1) | (v2 & 2);
47 }
48 };
49
50 template <typename t>
51 inline double square(t x)
52 {
53 return x * x;
54 }
55
56 struct BuildDiff
57 {
58 template <class PixelType>
59 double operator()(PixelType const& v1, PixelType const& v2) const
60 {
61 return std::abs(static_cast<double>(v1 - v2));
62 };
63 template <class PixelType>
64 double operator()(vigra::RGBValue<PixelType> const& v1, vigra::RGBValue<PixelType> const& v2) const
65 {
66 return sqrt(square(v1.red() - v2.red()) + square(v1.green() - v2.green()) + square(v1.blue() - v2.blue()));
67 };
68 };
69
71 {
72 template <class PixelType>
73 PixelType operator()(PixelType const& v1, PixelType const& v2) const
74 {
75 if ((v1 & 2) & v2)
76 {
77 return vigra::NumericTraits<PixelType>::max();
78 }
79 else
80 {
81 return vigra::NumericTraits<PixelType>::zero();
82 };
83 };
84 };
85
87 {
88 template <class PixelType>
89 PixelType operator()(PixelType const& v1, PixelType const& v2) const
90 {
91 if ((v2 & 2) & v1)
92 {
93 return 5;
94 }
95 else
96 {
97 if (v1 > 0)
98 {
99 return v2;
100 }
101 else
102 {
103 return vigra::NumericTraits<PixelType>::zero();
104 };
105 };
106 };
107 };
108
109 template <class ImageType>
110 ImageType ResizeImage(const ImageType& image, const vigra::Size2D& newSize)
111 {
112 ImageType newImage(std::max(image.size().width(), newSize.width()), std::max(image.size().height(), newSize.height()));
113 vigra::omp::copyImage(vigra::srcImageRange(image), vigra::destImage(newImage));
114 return newImage;
115 };
116 }; // namespace detail
117
118 // blend 2 images with Poisson blending
119 // labels contains the map which images should deliver the input, see comment in MergeImages for the values
120 template <class ImageType, class MaskType>
121 void PoissonBlend(ImageType& image1, const ImageType& image2, const MaskType& mask2, const vigra::BImage& labels, const vigra::Point2D& offsetPoint, const bool doWrap)
122 {
123 // mark edges in labels for solving Poisson equation with different boundary conditions
124 vigra::ImagePyramid<vigra::Int8Image> seams;
125 const int minLength = 8;
127 // create gradient map
128 typedef typename vigra::NumericTraits<typename ImageType::PixelType>::RealPromote ImageRealPixelType;
129 vigra::BasicImage<ImageRealPixelType> gradient(image2.size());
130 vigra::BasicImage<ImageRealPixelType> target(image2.size());
131 // build gradient map with special handling of both boundary conditions
133 // we start with the values of the image2 as begin
134 vigra::omp::copyImageIf(vigra::srcImageRange(image2), vigra::srcImage(seams[0], vigra_ext::poisson::MaskGreaterAccessor<vigra::Int8>(2)), vigra::destImage(target));
135 // solve poisson equation
137 // copy result back into output
138 vigra::omp::copyImageIf(vigra::srcImageRange(target), vigra::srcImage(seams[0], vigra_ext::poisson::MaskGreaterAccessor<vigra::Int8>(2)), vigra::destImage(image1, offsetPoint));
139 }
140
141 template <class ImageType, class MaskType>
142 void MergeImages(ImageType& image1, MaskType& mask1, const ImageType& image2, const MaskType& mask2, const vigra::Diff2D offset, const bool wrap, const bool hardSeam)
143 {
144 const vigra::Point2D offsetPoint(offset);
145 const vigra::Rect2D offsetRect(offsetPoint, mask2.size());
146 //increase image size if necessary
147 if (image1.width() < offsetRect.lowerRight().x || image1.height() < offsetRect.lowerRight().y)
148 {
149 image1 = detail::ResizeImage(image1, vigra::Size2D(offsetRect.lowerRight()));
150 mask1 = detail::ResizeImage(mask1, image1.size());
151 }
152 // generate seed mask
153 vigra::BImage labels(image2.size());
154 // create a seed mask
155 // value 0: pixel is not contained in image 1 or 2
156 // value 1: pixel contains only information from image 1
157 // value 2: pixel contains only information from image 2
158 // value 3: pixel contains information from image 1 and 2
159 vigra::omp::combineTwoImages(vigra::srcImageRange(mask1, offsetRect), vigra::srcImage(mask2), vigra::destImage(labels), detail::BuildSeed());
160 // find bounding rectangles for all values
161 vigra::ArrayOfRegionStatistics<vigra::FindBoundingRectangle> roi(3);
162 vigra::inspectTwoImages(vigra::srcIterRange<vigra::Diff2D>(vigra::Diff2D(0, 0), labels.size()), vigra::srcImage(labels), roi);
163 // handle some special cases
164 const bool haveOverlap = roi.regions[3].size().area() > 0;
165 if (!haveOverlap && hardSeam)
166 {
167 // images do not overlap, simply copy image2 into image1
168 vigra::copyImageIf(vigra::srcImageRange(image2), vigra::srcImage(mask2), vigra::destImage(image1, offsetPoint, image1.accessor()));
169 // now merge masks
170 vigra::copyImageIf(vigra::srcImageRange(mask2), vigra::srcImage(mask2), vigra::destImage(mask1, offsetPoint, mask1.accessor()));
171 return;
172 };
173 if (roi.regions[2].size().area() == 0)
174 {
175 // image 2 is fully overlapped by image 1
176 // we don't need to do anything
177 return;
178 };
179 if (roi.regions[1].size().area() == 0)
180 {
181 // image 1 is fully overlapped by image 2
182 // copy image 2 into output
183 vigra::copyImageIf(vigra::srcImageRange(image2), vigra::srcImage(mask2), vigra::destImage(image1, offsetPoint, image1.accessor()));
184 // now merge masks
185 vigra::copyImageIf(vigra::srcImageRange(mask2), vigra::srcImage(mask2), vigra::destImage(mask1, offsetPoint, mask1.accessor()));
186 return;
187 }
188 const double smoothRadius = std::max(1.0, std::max(roi.regions[3].size().width(), roi.regions[3].size().height()) / 1000.0);
189 // build seed map
190 vigra::omp::transformImage(vigra::srcImageRange(labels), vigra::destImage(labels), vigra::functor::Arg1() % vigra::functor::Param(3));
191 if (haveOverlap)
192 {
193 // build difference, only consider overlapping area
194 // increase size by 1 pixel in each direction if possible
195 vigra::Point2D p1(roi.regions[3].upperLeft);
196 if (p1.x > 0)
197 {
198 --(p1.x);
199 };
200 if (p1.y > 0)
201 {
202 --(p1.y);
203 };
204 vigra::Point2D p2(roi.regions[3].lowerRight);
205 if (p2.x + 1 < image2.width())
206 {
207 ++(p2.x);
208 };
209 if (p2.y + 1 < image2.height())
210 {
211 ++(p2.y);
212 };
213 vigra::DImage diff(p2 - p1);
214 const vigra::Rect2D rect1(offsetPoint + p1, diff.size());
215 // build difference map
216 vigra::omp::combineTwoImages(vigra::srcImageRange(image1, rect1), vigra::srcImage(image2, p1), vigra::destImage(diff), detail::BuildDiff());
217 // scale to 0..255 to faster watershed
218 vigra::FindMinMax<double> diffMinMax;
219 vigra::inspectImage(vigra::srcImageRange(diff), diffMinMax);
220 diffMinMax.max = std::min<double>(diffMinMax.max, 0.25f * vigra::NumericTraits<typename vigra::NumericTraits<typename ImageType::PixelType>::ValueType>::max());
221 vigra::BImage diffByte(diff.size());
222 vigra::omp::transformImage(vigra::srcImageRange(diff), vigra::destImage(diffByte), vigra::functor::Param(255) - vigra::functor::Param(255.0f / diffMinMax.max) * vigra::functor::Arg1());
223 diff.resize(0, 0);
224 // run watershed algorithm
225 vigra::ArrayOfRegionStatistics<vigra::SeedRgDirectValueFunctor<vigra::UInt8> > stats(3);
226 if (wrap && (roi.regions[3].size().width() == image1.width()))
227 {
228 // handle wrapping
229 // only if requested and if overlap extends above 360 deg boundary
230 const int oldWidth = labels.width();
231 const int oldHeight = labels.height();
232 vigra::BImage labelsWrapped(oldWidth * 2, oldHeight);
233 vigra::omp::copyImage(vigra::srcImageRange(labels), vigra::destImage(labelsWrapped));
234 vigra::omp::copyImage(labels.upperLeft(), labels.lowerRight(), labels.accessor(), labelsWrapped.upperLeft() + vigra::Diff2D(oldWidth, 0), labelsWrapped.accessor());
235 vigra::BImage diffWrapped(oldWidth * 2, diffByte.height());
236 vigra::omp::copyImage(vigra::srcImageRange(diffByte), vigra::destImage(diffWrapped));
237 vigra::omp::copyImage(diffByte.upperLeft(), diffByte.lowerRight(), diffByte.accessor(), diffWrapped.upperLeft() + vigra::Diff2D(oldWidth, 0), diffWrapped.accessor());
238 // apply gaussian smoothing with size depending radius
239 // we need a minimum size of window to apply gaussianSmoothing
240 if (diffWrapped.width() > 3 * smoothRadius && diffWrapped.height() > 3 * smoothRadius)
241 {
242 vigra::gaussianSmoothing(vigra::srcImageRange(diffWrapped), vigra::destImage(diffWrapped), smoothRadius);
243 };
244 vigra::fastSeededRegionGrowing(vigra::srcImageRange(diffWrapped), vigra::destImage(labelsWrapped, p1), stats, vigra::CompleteGrow, vigra::FourNeighborCode(), 255);
245 vigra::omp::copyImage(labelsWrapped.upperLeft() + vigra::Diff2D(oldWidth / 2, 0), labelsWrapped.upperLeft() + vigra::Diff2D(oldWidth, oldHeight), labelsWrapped.accessor(),
246 labels.upperLeft() + vigra::Diff2D(oldWidth / 2, 0), labels.accessor());
247 vigra::omp::copyImage(labelsWrapped.upperLeft() + vigra::Diff2D(oldWidth, 0), labelsWrapped.upperLeft() + vigra::Diff2D(oldWidth + oldWidth / 2, oldHeight), labelsWrapped.accessor(),
248 labels.upperLeft(), labels.accessor());
249 }
250 else
251 {
252 // apply gaussian smoothing with size depending radius
253 // we need a minimum size of window to apply gaussianSmoothing
254 if (diffByte.width() > 3 * smoothRadius && diffByte.height() > 3 * smoothRadius)
255 {
256 vigra::gaussianSmoothing(vigra::srcImageRange(diffByte), vigra::destImage(diffByte), smoothRadius);
257 };
258 vigra::fastSeededRegionGrowing(vigra::srcImageRange(diffByte), vigra::destImage(labels, p1), stats, vigra::CompleteGrow, vigra::FourNeighborCode(), 255);
259 };
260 };
261 // now we can merge the images
262 // merging the mask is straightforward
263 vigra::initImageIf(vigra::destImageRange(mask1, offsetRect), vigra::srcImage(mask2), vigra::NumericTraits<typename MaskType::value_type>::max());
264 if (hardSeam)
265 {
266 // the watershed algorithm could also reached area where no informations are available
267 vigra::omp::combineTwoImages(vigra::srcImageRange(labels), vigra::srcImage(mask2), vigra::destImage(labels), detail::CombineMasks());
268 // now we can merge the images
269 vigra::copyImageIf(vigra::srcImageRange(image2), vigra::srcImage(labels), vigra::destImage(image1, offsetPoint));
270 }
271 else
272 {
273 // find all boundaries in new mask
274 // first filter out unused pixel the watershed algorithm has also processed
275 vigra::omp::combineTwoImages(vigra::srcImageRange(mask1, offsetRect), vigra::srcImage(labels), vigra::destImage(labels), detail::CombineMasksForPoisson());
276 const bool doWrapBlending = wrap && (
277 // overlap regions wrap around 360 deg border
278 (roi.regions[3].size().width() == image1.width()) ||
279 // touching region wrap around 360 deg border
280 (roi.regions[2].size().width() == image1.width())
281 );
282 // labels has now the following values:
283 // 0: no image here
284 // 1: use information from image 1
285 // 5: use information from image 2
287 };
288 };
289
290}
solve poisson equation for blending images
vigra::FRGBImage ImageType
void copyImageIf(SrcImageIterator src_upperleft, SrcImageIterator src_lowerright, SrcAccessor src_acc, MaskImageIterator mask_upperleft, MaskAccessor mask_acc, DestImageIterator dest_upperleft, DestAccessor dest_acc)
void combineTwoImages(SrcImageIterator1 src1_upperleft, SrcImageIterator1 src1_lowerright, SrcAccessor1 src1_acc, SrcImageIterator2 src2_upperleft, SrcAccessor2 src2_acc, DestImageIterator dest_upperleft, DestAccessor dest_acc, const Functor &func)
void copyImage(SrcImageIterator src_upperleft, SrcImageIterator src_lowerright, SrcAccessor src_acc, DestImageIterator dest_upperleft, DestAccessor dest_acc)
void transformImage(SrcImageIterator src_upperleft, SrcImageIterator src_lowerright, SrcAccessor src_acc, DestImageIterator dest_upperleft, DestAccessor dest_acc, const Functor &func)
ImageType ResizeImage(const ImageType &image, const vigra::Size2D &newSize)
void Multigrid(Image &out, const Image &gradient, const vigra::ImagePyramid< SeamMask > &seamMaskPyramid, int minLen, const float errorThreshold, const int maxIter, const bool doWrap)
void BuildSeamPyramid(const Image &input, vigra::ImagePyramid< PyramidImage > &seams, const int minLength)
void BuildGradientMap(const Image &image1, const Image &image2, const Mask &mask2, const SeamMask &seam, GradientType &gradient, const vigra::Point2D &offset, const bool doWrap)
void PoissonBlend(ImageType &image1, const ImageType &image2, const MaskType &mask2, const vigra::BImage &labels, const vigra::Point2D &offsetPoint, const bool doWrap)
void MergeImages(ImageType &image1, MaskType &mask1, const ImageType &image2, const MaskType &mask2, const vigra::Diff2D offset, const bool wrap, const bool hardSeam)
double operator()(PixelType const &v1, PixelType const &v2) const
double operator()(vigra::RGBValue< PixelType > const &v1, vigra::RGBValue< PixelType > const &v2) const
PixelType operator()(PixelType const &v1, PixelType const &v2) const
PixelType operator()(PixelType const &v1, PixelType const &v2) const
PixelType operator()(PixelType const &v1, PixelType const &v2) const
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