Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Tetragramm
GitHub Repository: Tetragramm/opencv
Path: blob/master/modules/stitching/src/stitcher.cpp
16337 views
1
/*M///////////////////////////////////////////////////////////////////////////////////////
2
//
3
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
4
//
5
// By downloading, copying, installing or using the software you agree to this license.
6
// If you do not agree to this license, do not download, install,
7
// copy or use the software.
8
//
9
//
10
// License Agreement
11
// For Open Source Computer Vision Library
12
//
13
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
14
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
15
// Third party copyrights are property of their respective owners.
16
//
17
// Redistribution and use in source and binary forms, with or without modification,
18
// are permitted provided that the following conditions are met:
19
//
20
// * Redistribution's of source code must retain the above copyright notice,
21
// this list of conditions and the following disclaimer.
22
//
23
// * Redistribution's in binary form must reproduce the above copyright notice,
24
// this list of conditions and the following disclaimer in the documentation
25
// and/or other materials provided with the distribution.
26
//
27
// * The name of the copyright holders may not be used to endorse or promote products
28
// derived from this software without specific prior written permission.
29
//
30
// This software is provided by the copyright holders and contributors "as is" and
31
// any express or implied warranties, including, but not limited to, the implied
32
// warranties of merchantability and fitness for a particular purpose are disclaimed.
33
// In no event shall the Intel Corporation or contributors be liable for any direct,
34
// indirect, incidental, special, exemplary, or consequential damages
35
// (including, but not limited to, procurement of substitute goods or services;
36
// loss of use, data, or profits; or business interruption) however caused
37
// and on any theory of liability, whether in contract, strict liability,
38
// or tort (including negligence or otherwise) arising in any way out of
39
// the use of this software, even if advised of the possibility of such damage.
40
//
41
//M*/
42
43
#include "precomp.hpp"
44
45
namespace cv {
46
47
Stitcher Stitcher::createDefault(bool try_use_gpu)
48
{
49
Stitcher stitcher;
50
stitcher.setRegistrationResol(0.6);
51
stitcher.setSeamEstimationResol(0.1);
52
stitcher.setCompositingResol(ORIG_RESOL);
53
stitcher.setPanoConfidenceThresh(1);
54
stitcher.setWaveCorrection(true);
55
stitcher.setWaveCorrectKind(detail::WAVE_CORRECT_HORIZ);
56
stitcher.setFeaturesMatcher(makePtr<detail::BestOf2NearestMatcher>(try_use_gpu));
57
stitcher.setBundleAdjuster(makePtr<detail::BundleAdjusterRay>());
58
59
#ifdef HAVE_OPENCV_CUDALEGACY
60
if (try_use_gpu && cuda::getCudaEnabledDeviceCount() > 0)
61
{
62
#ifdef HAVE_OPENCV_XFEATURES2D
63
stitcher.setFeaturesFinder(makePtr<detail::SurfFeaturesFinderGpu>());
64
#else
65
stitcher.setFeaturesFinder(makePtr<detail::OrbFeaturesFinder>());
66
#endif
67
stitcher.setWarper(makePtr<SphericalWarperGpu>());
68
stitcher.setSeamFinder(makePtr<detail::GraphCutSeamFinderGpu>());
69
}
70
else
71
#endif
72
{
73
#ifdef HAVE_OPENCV_XFEATURES2D
74
stitcher.setFeaturesFinder(makePtr<detail::SurfFeaturesFinder>());
75
#else
76
stitcher.setFeaturesFinder(makePtr<detail::OrbFeaturesFinder>());
77
#endif
78
stitcher.setWarper(makePtr<SphericalWarper>());
79
stitcher.setSeamFinder(makePtr<detail::GraphCutSeamFinder>(detail::GraphCutSeamFinderBase::COST_COLOR));
80
}
81
82
stitcher.setExposureCompensator(makePtr<detail::BlocksGainCompensator>());
83
stitcher.setBlender(makePtr<detail::MultiBandBlender>(try_use_gpu));
84
85
stitcher.work_scale_ = 1;
86
stitcher.seam_scale_ = 1;
87
stitcher.seam_work_aspect_ = 1;
88
stitcher.warped_image_scale_ = 1;
89
90
return stitcher;
91
}
92
93
94
Ptr<Stitcher> Stitcher::create(Mode mode, bool try_use_gpu)
95
{
96
Stitcher stit = createDefault(try_use_gpu);
97
Ptr<Stitcher> stitcher = makePtr<Stitcher>(stit);
98
99
switch (mode)
100
{
101
case PANORAMA: // PANORAMA is the default
102
// already setup
103
break;
104
105
case SCANS:
106
stitcher->setWaveCorrection(false);
107
stitcher->setFeaturesMatcher(makePtr<detail::AffineBestOf2NearestMatcher>(false, try_use_gpu));
108
stitcher->setBundleAdjuster(makePtr<detail::BundleAdjusterAffinePartial>());
109
stitcher->setWarper(makePtr<AffineWarper>());
110
stitcher->setExposureCompensator(makePtr<detail::NoExposureCompensator>());
111
break;
112
113
default:
114
CV_Error(Error::StsBadArg, "Invalid stitching mode. Must be one of Stitcher::Mode");
115
break;
116
}
117
118
return stitcher;
119
}
120
121
122
Stitcher::Status Stitcher::estimateTransform(InputArrayOfArrays images)
123
{
124
CV_INSTRUMENT_REGION();
125
126
return estimateTransform(images, std::vector<std::vector<Rect> >());
127
}
128
129
130
Stitcher::Status Stitcher::estimateTransform(InputArrayOfArrays images, const std::vector<std::vector<Rect> > &rois)
131
{
132
CV_INSTRUMENT_REGION();
133
134
images.getUMatVector(imgs_);
135
rois_ = rois;
136
137
Status status;
138
139
if ((status = matchImages()) != OK)
140
return status;
141
142
if ((status = estimateCameraParams()) != OK)
143
return status;
144
145
return OK;
146
}
147
148
149
150
Stitcher::Status Stitcher::composePanorama(OutputArray pano)
151
{
152
CV_INSTRUMENT_REGION();
153
154
return composePanorama(std::vector<UMat>(), pano);
155
}
156
157
158
Stitcher::Status Stitcher::composePanorama(InputArrayOfArrays images, OutputArray pano)
159
{
160
CV_INSTRUMENT_REGION();
161
162
LOGLN("Warping images (auxiliary)... ");
163
164
std::vector<UMat> imgs;
165
images.getUMatVector(imgs);
166
if (!imgs.empty())
167
{
168
CV_Assert(imgs.size() == imgs_.size());
169
170
UMat img;
171
seam_est_imgs_.resize(imgs.size());
172
173
for (size_t i = 0; i < imgs.size(); ++i)
174
{
175
imgs_[i] = imgs[i];
176
resize(imgs[i], img, Size(), seam_scale_, seam_scale_, INTER_LINEAR_EXACT);
177
seam_est_imgs_[i] = img.clone();
178
}
179
180
std::vector<UMat> seam_est_imgs_subset;
181
std::vector<UMat> imgs_subset;
182
183
for (size_t i = 0; i < indices_.size(); ++i)
184
{
185
imgs_subset.push_back(imgs_[indices_[i]]);
186
seam_est_imgs_subset.push_back(seam_est_imgs_[indices_[i]]);
187
}
188
189
seam_est_imgs_ = seam_est_imgs_subset;
190
imgs_ = imgs_subset;
191
}
192
193
UMat pano_;
194
195
#if ENABLE_LOG
196
int64 t = getTickCount();
197
#endif
198
199
std::vector<Point> corners(imgs_.size());
200
std::vector<UMat> masks_warped(imgs_.size());
201
std::vector<UMat> images_warped(imgs_.size());
202
std::vector<Size> sizes(imgs_.size());
203
std::vector<UMat> masks(imgs_.size());
204
205
// Prepare image masks
206
for (size_t i = 0; i < imgs_.size(); ++i)
207
{
208
masks[i].create(seam_est_imgs_[i].size(), CV_8U);
209
masks[i].setTo(Scalar::all(255));
210
}
211
212
// Warp images and their masks
213
Ptr<detail::RotationWarper> w = warper_->create(float(warped_image_scale_ * seam_work_aspect_));
214
for (size_t i = 0; i < imgs_.size(); ++i)
215
{
216
Mat_<float> K;
217
cameras_[i].K().convertTo(K, CV_32F);
218
K(0,0) *= (float)seam_work_aspect_;
219
K(0,2) *= (float)seam_work_aspect_;
220
K(1,1) *= (float)seam_work_aspect_;
221
K(1,2) *= (float)seam_work_aspect_;
222
223
corners[i] = w->warp(seam_est_imgs_[i], K, cameras_[i].R, INTER_LINEAR, BORDER_REFLECT, images_warped[i]);
224
sizes[i] = images_warped[i].size();
225
226
w->warp(masks[i], K, cameras_[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]);
227
}
228
229
230
LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
231
232
// Compensate exposure before finding seams
233
exposure_comp_->feed(corners, images_warped, masks_warped);
234
for (size_t i = 0; i < imgs_.size(); ++i)
235
exposure_comp_->apply(int(i), corners[i], images_warped[i], masks_warped[i]);
236
237
// Find seams
238
std::vector<UMat> images_warped_f(imgs_.size());
239
for (size_t i = 0; i < imgs_.size(); ++i)
240
images_warped[i].convertTo(images_warped_f[i], CV_32F);
241
seam_finder_->find(images_warped_f, corners, masks_warped);
242
243
// Release unused memory
244
seam_est_imgs_.clear();
245
images_warped.clear();
246
images_warped_f.clear();
247
masks.clear();
248
249
LOGLN("Compositing...");
250
#if ENABLE_LOG
251
t = getTickCount();
252
#endif
253
254
UMat img_warped, img_warped_s;
255
UMat dilated_mask, seam_mask, mask, mask_warped;
256
257
//double compose_seam_aspect = 1;
258
double compose_work_aspect = 1;
259
bool is_blender_prepared = false;
260
261
double compose_scale = 1;
262
bool is_compose_scale_set = false;
263
264
std::vector<detail::CameraParams> cameras_scaled(cameras_);
265
266
UMat full_img, img;
267
for (size_t img_idx = 0; img_idx < imgs_.size(); ++img_idx)
268
{
269
LOGLN("Compositing image #" << indices_[img_idx] + 1);
270
#if ENABLE_LOG
271
int64 compositing_t = getTickCount();
272
#endif
273
274
// Read image and resize it if necessary
275
full_img = imgs_[img_idx];
276
if (!is_compose_scale_set)
277
{
278
if (compose_resol_ > 0)
279
compose_scale = std::min(1.0, std::sqrt(compose_resol_ * 1e6 / full_img.size().area()));
280
is_compose_scale_set = true;
281
282
// Compute relative scales
283
//compose_seam_aspect = compose_scale / seam_scale_;
284
compose_work_aspect = compose_scale / work_scale_;
285
286
// Update warped image scale
287
float warp_scale = static_cast<float>(warped_image_scale_ * compose_work_aspect);
288
w = warper_->create(warp_scale);
289
290
// Update corners and sizes
291
for (size_t i = 0; i < imgs_.size(); ++i)
292
{
293
// Update intrinsics
294
cameras_scaled[i].ppx *= compose_work_aspect;
295
cameras_scaled[i].ppy *= compose_work_aspect;
296
cameras_scaled[i].focal *= compose_work_aspect;
297
298
// Update corner and size
299
Size sz = full_img_sizes_[i];
300
if (std::abs(compose_scale - 1) > 1e-1)
301
{
302
sz.width = cvRound(full_img_sizes_[i].width * compose_scale);
303
sz.height = cvRound(full_img_sizes_[i].height * compose_scale);
304
}
305
306
Mat K;
307
cameras_scaled[i].K().convertTo(K, CV_32F);
308
Rect roi = w->warpRoi(sz, K, cameras_scaled[i].R);
309
corners[i] = roi.tl();
310
sizes[i] = roi.size();
311
}
312
}
313
if (std::abs(compose_scale - 1) > 1e-1)
314
{
315
#if ENABLE_LOG
316
int64 resize_t = getTickCount();
317
#endif
318
resize(full_img, img, Size(), compose_scale, compose_scale, INTER_LINEAR_EXACT);
319
LOGLN(" resize time: " << ((getTickCount() - resize_t) / getTickFrequency()) << " sec");
320
}
321
else
322
img = full_img;
323
full_img.release();
324
Size img_size = img.size();
325
326
LOGLN(" after resize time: " << ((getTickCount() - compositing_t) / getTickFrequency()) << " sec");
327
328
Mat K;
329
cameras_scaled[img_idx].K().convertTo(K, CV_32F);
330
331
#if ENABLE_LOG
332
int64 pt = getTickCount();
333
#endif
334
// Warp the current image
335
w->warp(img, K, cameras_[img_idx].R, INTER_LINEAR, BORDER_REFLECT, img_warped);
336
LOGLN(" warp the current image: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");
337
#if ENABLE_LOG
338
pt = getTickCount();
339
#endif
340
341
// Warp the current image mask
342
mask.create(img_size, CV_8U);
343
mask.setTo(Scalar::all(255));
344
w->warp(mask, K, cameras_[img_idx].R, INTER_NEAREST, BORDER_CONSTANT, mask_warped);
345
LOGLN(" warp the current image mask: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");
346
#if ENABLE_LOG
347
pt = getTickCount();
348
#endif
349
350
// Compensate exposure
351
exposure_comp_->apply((int)img_idx, corners[img_idx], img_warped, mask_warped);
352
LOGLN(" compensate exposure: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");
353
#if ENABLE_LOG
354
pt = getTickCount();
355
#endif
356
357
img_warped.convertTo(img_warped_s, CV_16S);
358
img_warped.release();
359
img.release();
360
mask.release();
361
362
// Make sure seam mask has proper size
363
dilate(masks_warped[img_idx], dilated_mask, Mat());
364
resize(dilated_mask, seam_mask, mask_warped.size(), 0, 0, INTER_LINEAR_EXACT);
365
366
bitwise_and(seam_mask, mask_warped, mask_warped);
367
368
LOGLN(" other: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");
369
#if ENABLE_LOG
370
pt = getTickCount();
371
#endif
372
373
if (!is_blender_prepared)
374
{
375
blender_->prepare(corners, sizes);
376
is_blender_prepared = true;
377
}
378
379
LOGLN(" other2: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");
380
381
LOGLN(" feed...");
382
#if ENABLE_LOG
383
int64 feed_t = getTickCount();
384
#endif
385
// Blend the current image
386
blender_->feed(img_warped_s, mask_warped, corners[img_idx]);
387
LOGLN(" feed time: " << ((getTickCount() - feed_t) / getTickFrequency()) << " sec");
388
LOGLN("Compositing ## time: " << ((getTickCount() - compositing_t) / getTickFrequency()) << " sec");
389
}
390
391
#if ENABLE_LOG
392
int64 blend_t = getTickCount();
393
#endif
394
UMat result, result_mask;
395
blender_->blend(result, result_mask);
396
LOGLN("blend time: " << ((getTickCount() - blend_t) / getTickFrequency()) << " sec");
397
398
LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
399
400
// Preliminary result is in CV_16SC3 format, but all values are in [0,255] range,
401
// so convert it to avoid user confusing
402
result.convertTo(pano, CV_8U);
403
404
return OK;
405
}
406
407
408
Stitcher::Status Stitcher::stitch(InputArrayOfArrays images, OutputArray pano)
409
{
410
CV_INSTRUMENT_REGION();
411
412
Status status = estimateTransform(images);
413
if (status != OK)
414
return status;
415
return composePanorama(pano);
416
}
417
418
419
Stitcher::Status Stitcher::stitch(InputArrayOfArrays images, const std::vector<std::vector<Rect> > &rois, OutputArray pano)
420
{
421
CV_INSTRUMENT_REGION();
422
423
Status status = estimateTransform(images, rois);
424
if (status != OK)
425
return status;
426
return composePanorama(pano);
427
}
428
429
430
Stitcher::Status Stitcher::matchImages()
431
{
432
if ((int)imgs_.size() < 2)
433
{
434
LOGLN("Need more images");
435
return ERR_NEED_MORE_IMGS;
436
}
437
438
work_scale_ = 1;
439
seam_work_aspect_ = 1;
440
seam_scale_ = 1;
441
bool is_work_scale_set = false;
442
bool is_seam_scale_set = false;
443
UMat full_img, img;
444
features_.resize(imgs_.size());
445
seam_est_imgs_.resize(imgs_.size());
446
full_img_sizes_.resize(imgs_.size());
447
448
LOGLN("Finding features...");
449
#if ENABLE_LOG
450
int64 t = getTickCount();
451
#endif
452
453
std::vector<UMat> feature_find_imgs(imgs_.size());
454
std::vector<std::vector<Rect> > feature_find_rois(rois_.size());
455
456
for (size_t i = 0; i < imgs_.size(); ++i)
457
{
458
full_img = imgs_[i];
459
full_img_sizes_[i] = full_img.size();
460
461
if (registr_resol_ < 0)
462
{
463
img = full_img;
464
work_scale_ = 1;
465
is_work_scale_set = true;
466
}
467
else
468
{
469
if (!is_work_scale_set)
470
{
471
work_scale_ = std::min(1.0, std::sqrt(registr_resol_ * 1e6 / full_img.size().area()));
472
is_work_scale_set = true;
473
}
474
resize(full_img, img, Size(), work_scale_, work_scale_, INTER_LINEAR_EXACT);
475
}
476
if (!is_seam_scale_set)
477
{
478
seam_scale_ = std::min(1.0, std::sqrt(seam_est_resol_ * 1e6 / full_img.size().area()));
479
seam_work_aspect_ = seam_scale_ / work_scale_;
480
is_seam_scale_set = true;
481
}
482
483
if (rois_.empty())
484
feature_find_imgs[i] = img;
485
else
486
{
487
feature_find_rois[i].resize(rois_[i].size());
488
for (size_t j = 0; j < rois_[i].size(); ++j)
489
{
490
Point tl(cvRound(rois_[i][j].x * work_scale_), cvRound(rois_[i][j].y * work_scale_));
491
Point br(cvRound(rois_[i][j].br().x * work_scale_), cvRound(rois_[i][j].br().y * work_scale_));
492
feature_find_rois[i][j] = Rect(tl, br);
493
}
494
feature_find_imgs[i] = img;
495
}
496
features_[i].img_idx = (int)i;
497
LOGLN("Features in image #" << i+1 << ": " << features_[i].keypoints.size());
498
499
resize(full_img, img, Size(), seam_scale_, seam_scale_, INTER_LINEAR_EXACT);
500
seam_est_imgs_[i] = img.clone();
501
}
502
503
// find features possibly in parallel
504
if (rois_.empty())
505
(*features_finder_)(feature_find_imgs, features_);
506
else
507
(*features_finder_)(feature_find_imgs, features_, feature_find_rois);
508
509
// Do it to save memory
510
features_finder_->collectGarbage();
511
full_img.release();
512
img.release();
513
feature_find_imgs.clear();
514
feature_find_rois.clear();
515
516
LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
517
518
LOG("Pairwise matching");
519
#if ENABLE_LOG
520
t = getTickCount();
521
#endif
522
(*features_matcher_)(features_, pairwise_matches_, matching_mask_);
523
features_matcher_->collectGarbage();
524
LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
525
526
// Leave only images we are sure are from the same panorama
527
indices_ = detail::leaveBiggestComponent(features_, pairwise_matches_, (float)conf_thresh_);
528
std::vector<UMat> seam_est_imgs_subset;
529
std::vector<UMat> imgs_subset;
530
std::vector<Size> full_img_sizes_subset;
531
for (size_t i = 0; i < indices_.size(); ++i)
532
{
533
imgs_subset.push_back(imgs_[indices_[i]]);
534
seam_est_imgs_subset.push_back(seam_est_imgs_[indices_[i]]);
535
full_img_sizes_subset.push_back(full_img_sizes_[indices_[i]]);
536
}
537
seam_est_imgs_ = seam_est_imgs_subset;
538
imgs_ = imgs_subset;
539
full_img_sizes_ = full_img_sizes_subset;
540
541
if ((int)imgs_.size() < 2)
542
{
543
LOGLN("Need more images");
544
return ERR_NEED_MORE_IMGS;
545
}
546
547
return OK;
548
}
549
550
551
Stitcher::Status Stitcher::estimateCameraParams()
552
{
553
/* TODO OpenCV ABI 4.x
554
get rid of this dynamic_cast hack and use estimator_
555
*/
556
Ptr<detail::Estimator> estimator;
557
if (dynamic_cast<detail::AffineBestOf2NearestMatcher*>(features_matcher_.get()))
558
estimator = makePtr<detail::AffineBasedEstimator>();
559
else
560
estimator = makePtr<detail::HomographyBasedEstimator>();
561
562
if (!(*estimator)(features_, pairwise_matches_, cameras_))
563
return ERR_HOMOGRAPHY_EST_FAIL;
564
565
for (size_t i = 0; i < cameras_.size(); ++i)
566
{
567
Mat R;
568
cameras_[i].R.convertTo(R, CV_32F);
569
cameras_[i].R = R;
570
//LOGLN("Initial intrinsic parameters #" << indices_[i] + 1 << ":\n " << cameras_[i].K());
571
}
572
573
bundle_adjuster_->setConfThresh(conf_thresh_);
574
if (!(*bundle_adjuster_)(features_, pairwise_matches_, cameras_))
575
return ERR_CAMERA_PARAMS_ADJUST_FAIL;
576
577
// Find median focal length and use it as final image scale
578
std::vector<double> focals;
579
for (size_t i = 0; i < cameras_.size(); ++i)
580
{
581
//LOGLN("Camera #" << indices_[i] + 1 << ":\n" << cameras_[i].K());
582
focals.push_back(cameras_[i].focal);
583
}
584
585
std::sort(focals.begin(), focals.end());
586
if (focals.size() % 2 == 1)
587
warped_image_scale_ = static_cast<float>(focals[focals.size() / 2]);
588
else
589
warped_image_scale_ = static_cast<float>(focals[focals.size() / 2 - 1] + focals[focals.size() / 2]) * 0.5f;
590
591
if (do_wave_correct_)
592
{
593
std::vector<Mat> rmats;
594
for (size_t i = 0; i < cameras_.size(); ++i)
595
rmats.push_back(cameras_[i].R.clone());
596
detail::waveCorrect(rmats, wave_correct_kind_);
597
for (size_t i = 0; i < cameras_.size(); ++i)
598
cameras_[i].R = rmats[i];
599
}
600
601
return OK;
602
}
603
604
605
Ptr<Stitcher> createStitcher(bool try_use_gpu)
606
{
607
CV_INSTRUMENT_REGION();
608
609
return Stitcher::create(Stitcher::PANORAMA, try_use_gpu);
610
}
611
612
Ptr<Stitcher> createStitcherScans(bool try_use_gpu)
613
{
614
CV_INSTRUMENT_REGION();
615
616
return Stitcher::create(Stitcher::SCANS, try_use_gpu);
617
}
618
} // namespace cv
619
620