Stitcher::Status Stitcher::estimateCameraParams() { detail::HomographyBasedEstimator estimator; if (!estimator(features_, pairwise_matches_, cameras_)) return ERR_HOMOGRAPHY_EST_FAIL; for (size_t i = 0; i < cameras_.size(); ++i) { Mat R; cameras_[i].R.convertTo(R, CV_32F); cameras_[i].R = R; LOGLN("Initial intrinsic parameters #" << indices_[i] + 1 << ":\n " << cameras_[i].K()); } bundle_adjuster_->setConfThresh(conf_thresh_); if (!(*bundle_adjuster_)(features_, pairwise_matches_, cameras_)) return ERR_CAMERA_PARAMS_ADJUST_FAIL; // Find median focal length and use it as final image scale std::vector<double> focals; for (size_t i = 0; i < cameras_.size(); ++i) { LOGLN("Camera #" << indices_[i] + 1 << ":\n" << cameras_[i].K()); focals.push_back(cameras_[i].focal); } std::sort(focals.begin(), focals.end()); if (focals.size() % 2 == 1) warped_image_scale_ = static_cast<float>(focals[focals.size() / 2]); else warped_image_scale_ = static_cast<float>(focals[focals.size() / 2 - 1] + focals[focals.size() / 2]) * 0.5f; if (do_wave_correct_) { std::vector<Mat> rmats; for (size_t i = 0; i < cameras_.size(); ++i) rmats.push_back(cameras_[i].R); detail::waveCorrect(rmats, wave_correct_kind_); for (size_t i = 0; i < cameras_.size(); ++i) cameras_[i].R = rmats[i]; } return OK; }
void estimateFocal(const vector<ImageFeatures> &features, const vector<MatchesInfo> &pairwise_matches, vector<double> &focals) { const int num_images = static_cast<int>(features.size()); focals.resize(num_images); vector<double> all_focals; for (int i = 0; i < num_images; ++i) { for (int j = 0; j < num_images; ++j) { const MatchesInfo &m = pairwise_matches[i*num_images + j]; if (m.H.empty()) continue; double f0, f1; bool f0ok, f1ok; focalsFromHomography(m.H, f0, f1, f0ok, f1ok); if (f0ok && f1ok) all_focals.push_back(sqrt(f0 * f1)); } } if (static_cast<int>(all_focals.size()) >= num_images - 1) { double median; sort(all_focals.begin(), all_focals.end()); if (all_focals.size() % 2 == 1) median = all_focals[all_focals.size() / 2]; else median = (all_focals[all_focals.size() / 2 - 1] + all_focals[all_focals.size() / 2]) * 0.5; for (int i = 0; i < num_images; ++i) focals[i] = median; } else { LOGLN("Can't estimate focal length, will use naive approach"); double focals_sum = 0; for (int i = 0; i < num_images; ++i) focals_sum += features[i].img_size.width + features[i].img_size.height; for (int i = 0; i < num_images; ++i) focals[i] = focals_sum / num_images; } }
void MultiBandBlender::feed(InputArray _img, InputArray mask, Point tl) { #if ENABLE_LOG int64 t = getTickCount(); #endif UMat img = _img.getUMat(); CV_Assert(img.type() == CV_16SC3 || img.type() == CV_8UC3); CV_Assert(mask.type() == CV_8U); // Keep source image in memory with small border int gap = 3 * (1 << num_bands_); Point tl_new(std::max(dst_roi_.x, tl.x - gap), std::max(dst_roi_.y, tl.y - gap)); Point br_new(std::min(dst_roi_.br().x, tl.x + img.cols + gap), std::min(dst_roi_.br().y, tl.y + img.rows + gap)); // Ensure coordinates of top-left, bottom-right corners are divided by (1 << num_bands_). // After that scale between layers is exactly 2. // // We do it to avoid interpolation problems when keeping sub-images only. There is no such problem when // image is bordered to have size equal to the final image size, but this is too memory hungry approach. tl_new.x = dst_roi_.x + (((tl_new.x - dst_roi_.x) >> num_bands_) << num_bands_); tl_new.y = dst_roi_.y + (((tl_new.y - dst_roi_.y) >> num_bands_) << num_bands_); int width = br_new.x - tl_new.x; int height = br_new.y - tl_new.y; width += ((1 << num_bands_) - width % (1 << num_bands_)) % (1 << num_bands_); height += ((1 << num_bands_) - height % (1 << num_bands_)) % (1 << num_bands_); br_new.x = tl_new.x + width; br_new.y = tl_new.y + height; int dy = std::max(br_new.y - dst_roi_.br().y, 0); int dx = std::max(br_new.x - dst_roi_.br().x, 0); tl_new.x -= dx; br_new.x -= dx; tl_new.y -= dy; br_new.y -= dy; int top = tl.y - tl_new.y; int left = tl.x - tl_new.x; int bottom = br_new.y - tl.y - img.rows; int right = br_new.x - tl.x - img.cols; // Create the source image Laplacian pyramid UMat img_with_border; copyMakeBorder(_img, img_with_border, top, bottom, left, right, BORDER_REFLECT); LOGLN(" Add border to the source image, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); #if ENABLE_LOG t = getTickCount(); #endif std::vector<UMat> src_pyr_laplace; if (can_use_gpu_ && img_with_border.depth() == CV_16S) createLaplacePyrGpu(img_with_border, num_bands_, src_pyr_laplace); else createLaplacePyr(img_with_border, num_bands_, src_pyr_laplace); LOGLN(" Create the source image Laplacian pyramid, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); #if ENABLE_LOG t = getTickCount(); #endif // Create the weight map Gaussian pyramid UMat weight_map; std::vector<UMat> weight_pyr_gauss(num_bands_ + 1); if(weight_type_ == CV_32F) { mask.getUMat().convertTo(weight_map, CV_32F, 1./255.); } else // weight_type_ == CV_16S { mask.getUMat().convertTo(weight_map, CV_16S); UMat add_mask; compare(mask, 0, add_mask, CMP_NE); add(weight_map, Scalar::all(1), weight_map, add_mask); } copyMakeBorder(weight_map, weight_pyr_gauss[0], top, bottom, left, right, BORDER_CONSTANT); for (int i = 0; i < num_bands_; ++i) pyrDown(weight_pyr_gauss[i], weight_pyr_gauss[i + 1]); LOGLN(" Create the weight map Gaussian pyramid, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); #if ENABLE_LOG t = getTickCount(); #endif int y_tl = tl_new.y - dst_roi_.y; int y_br = br_new.y - dst_roi_.y; int x_tl = tl_new.x - dst_roi_.x; int x_br = br_new.x - dst_roi_.x; // Add weighted layer of the source image to the final Laplacian pyramid layer for (int i = 0; i <= num_bands_; ++i) { Rect rc(x_tl, y_tl, x_br - x_tl, y_br - y_tl); #ifdef HAVE_OPENCL if ( !cv::ocl::useOpenCL() || !ocl_MultiBandBlender_feed(src_pyr_laplace[i], weight_pyr_gauss[i], dst_pyr_laplace_[i](rc), dst_band_weights_[i](rc)) ) #endif { Mat _src_pyr_laplace = src_pyr_laplace[i].getMat(ACCESS_READ); Mat _dst_pyr_laplace = dst_pyr_laplace_[i](rc).getMat(ACCESS_RW); Mat _weight_pyr_gauss = weight_pyr_gauss[i].getMat(ACCESS_READ); Mat _dst_band_weights = dst_band_weights_[i](rc).getMat(ACCESS_RW); if(weight_type_ == CV_32F) { for (int y = 0; y < rc.height; ++y) { const Point3_<short>* src_row = _src_pyr_laplace.ptr<Point3_<short> >(y); Point3_<short>* dst_row = _dst_pyr_laplace.ptr<Point3_<short> >(y); const float* weight_row = _weight_pyr_gauss.ptr<float>(y); float* dst_weight_row = _dst_band_weights.ptr<float>(y); for (int x = 0; x < rc.width; ++x) { dst_row[x].x += static_cast<short>(src_row[x].x * weight_row[x]); dst_row[x].y += static_cast<short>(src_row[x].y * weight_row[x]); dst_row[x].z += static_cast<short>(src_row[x].z * weight_row[x]); dst_weight_row[x] += weight_row[x]; } } } else // weight_type_ == CV_16S { for (int y = 0; y < y_br - y_tl; ++y) { const Point3_<short>* src_row = _src_pyr_laplace.ptr<Point3_<short> >(y); Point3_<short>* dst_row = _dst_pyr_laplace.ptr<Point3_<short> >(y); const short* weight_row = _weight_pyr_gauss.ptr<short>(y); short* dst_weight_row = _dst_band_weights.ptr<short>(y); for (int x = 0; x < x_br - x_tl; ++x) { dst_row[x].x += short((src_row[x].x * weight_row[x]) >> 8); dst_row[x].y += short((src_row[x].y * weight_row[x]) >> 8); dst_row[x].z += short((src_row[x].z * weight_row[x]) >> 8); dst_weight_row[x] += weight_row[x]; } } } } #ifdef HAVE_OPENCL else {
Stitcher::Status Stitcher::matchImages() { if ((int)imgs_.size() < 2) { LOGLN("Need more images"); return ERR_NEED_MORE_IMGS; } work_scale_ = 1; seam_work_aspect_ = 1; seam_scale_ = 1; bool is_work_scale_set = false; bool is_seam_scale_set = false; Mat full_img, img; features_.resize(imgs_.size()); seam_est_imgs_.resize(imgs_.size()); full_img_sizes_.resize(imgs_.size()); LOGLN("Finding features..."); int64 t = getTickCount(); for (size_t i = 0; i < imgs_.size(); ++i) { full_img = imgs_[i]; full_img_sizes_[i] = full_img.size(); if (registr_resol_ < 0) { img = full_img; work_scale_ = 1; is_work_scale_set = true; } else { if (!is_work_scale_set) { work_scale_ = min(1.0, sqrt(registr_resol_ * 1e6 / full_img.size().area())); is_work_scale_set = true; } resize(full_img, img, Size(), work_scale_, work_scale_); } if (!is_seam_scale_set) { seam_scale_ = min(1.0, sqrt(seam_est_resol_ * 1e6 / full_img.size().area())); seam_work_aspect_ = seam_scale_ / work_scale_; is_seam_scale_set = true; } if (rois_.empty()) (*features_finder_)(img, features_[i]); else (*features_finder_)(img, features_[i], rois_[i]); features_[i].img_idx = (int)i; LOGLN("Features in image #" << i+1 << ": " << features_[i].keypoints.size()); resize(full_img, img, Size(), seam_scale_, seam_scale_); seam_est_imgs_[i] = img.clone(); } // Do it to save memory features_finder_->collectGarbage(); full_img.release(); img.release(); LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); LOG("Pairwise matching"); t = getTickCount(); (*features_matcher_)(features_, pairwise_matches_, matching_mask_); features_matcher_->collectGarbage(); LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); // Leave only images we are sure are from the same panorama indices_ = detail::leaveBiggestComponent(features_, pairwise_matches_, (float)conf_thresh_); vector<Mat> seam_est_imgs_subset; vector<Mat> imgs_subset; vector<Size> full_img_sizes_subset; for (size_t i = 0; i < indices_.size(); ++i) { imgs_subset.push_back(imgs_[indices_[i]]); seam_est_imgs_subset.push_back(seam_est_imgs_[indices_[i]]); full_img_sizes_subset.push_back(full_img_sizes_[indices_[i]]); } seam_est_imgs_ = seam_est_imgs_subset; imgs_ = imgs_subset; full_img_sizes_ = full_img_sizes_subset; if ((int)imgs_.size() < 2) { LOGLN("Need more images"); return ERR_NEED_MORE_IMGS; } return OK; }
Stitcher::Status Stitcher::composePanorama(InputArray images, OutputArray pano) { LOGLN("Warping images (auxiliary)... "); vector<Mat> imgs; images.getMatVector(imgs); if (!imgs.empty()) { CV_Assert(imgs.size() == imgs_.size()); Mat img; seam_est_imgs_.resize(imgs.size()); for (size_t i = 0; i < imgs.size(); ++i) { imgs_[i] = imgs[i]; resize(imgs[i], img, Size(), seam_scale_, seam_scale_); seam_est_imgs_[i] = img.clone(); } vector<Mat> seam_est_imgs_subset; vector<Mat> imgs_subset; for (size_t i = 0; i < indices_.size(); ++i) { imgs_subset.push_back(imgs_[indices_[i]]); seam_est_imgs_subset.push_back(seam_est_imgs_[indices_[i]]); } seam_est_imgs_ = seam_est_imgs_subset; imgs_ = imgs_subset; } Mat &pano_ = pano.getMatRef(); int64 t = getTickCount(); vector<Point> corners(imgs_.size()); vector<Mat> masks_warped(imgs_.size()); vector<Mat> images_warped(imgs_.size()); vector<Size> sizes(imgs_.size()); vector<Mat> masks(imgs_.size()); // Prepare image masks for (size_t i = 0; i < imgs_.size(); ++i) { masks[i].create(seam_est_imgs_[i].size(), CV_8U); masks[i].setTo(Scalar::all(255)); } // Warp images and their masks Ptr<detail::RotationWarper> w = warper_->create(float(warped_image_scale_ * seam_work_aspect_)); for (size_t i = 0; i < imgs_.size(); ++i) { Mat_<float> K; cameras_[i].K().convertTo(K, CV_32F); K(0,0) *= (float)seam_work_aspect_; K(0,2) *= (float)seam_work_aspect_; K(1,1) *= (float)seam_work_aspect_; K(1,2) *= (float)seam_work_aspect_; corners[i] = w->warp(seam_est_imgs_[i], K, cameras_[i].R, INTER_LINEAR, BORDER_REFLECT, images_warped[i]); sizes[i] = images_warped[i].size(); w->warp(masks[i], K, cameras_[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]); } vector<Mat> images_warped_f(imgs_.size()); for (size_t i = 0; i < imgs_.size(); ++i) images_warped[i].convertTo(images_warped_f[i], CV_32F); LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); // Find seams exposure_comp_->feed(corners, images_warped, masks_warped); seam_finder_->find(images_warped_f, corners, masks_warped); // Release unused memory seam_est_imgs_.clear(); images_warped.clear(); images_warped_f.clear(); masks.clear(); LOGLN("Compositing..."); t = getTickCount(); Mat img_warped, img_warped_s; Mat dilated_mask, seam_mask, mask, mask_warped; //double compose_seam_aspect = 1; double compose_work_aspect = 1; bool is_blender_prepared = false; double compose_scale = 1; bool is_compose_scale_set = false; Mat full_img, img; for (size_t img_idx = 0; img_idx < imgs_.size(); ++img_idx) { LOGLN("Compositing image #" << indices_[img_idx] + 1); // Read image and resize it if necessary full_img = imgs_[img_idx]; if (!is_compose_scale_set) { if (compose_resol_ > 0) compose_scale = min(1.0, sqrt(compose_resol_ * 1e6 / full_img.size().area())); is_compose_scale_set = true; // Compute relative scales //compose_seam_aspect = compose_scale / seam_scale_; compose_work_aspect = compose_scale / work_scale_; // Update warped image scale warped_image_scale_ *= static_cast<float>(compose_work_aspect); w = warper_->create((float)warped_image_scale_); // Update corners and sizes for (size_t i = 0; i < imgs_.size(); ++i) { // Update intrinsics cameras_[i].focal *= compose_work_aspect; cameras_[i].ppx *= compose_work_aspect; cameras_[i].ppy *= compose_work_aspect; // Update corner and size Size sz = full_img_sizes_[i]; if (std::abs(compose_scale - 1) > 1e-1) { sz.width = cvRound(full_img_sizes_[i].width * compose_scale); sz.height = cvRound(full_img_sizes_[i].height * compose_scale); } Mat K; cameras_[i].K().convertTo(K, CV_32F); Rect roi = w->warpRoi(sz, K, cameras_[i].R); corners[i] = roi.tl(); sizes[i] = roi.size(); } } if (std::abs(compose_scale - 1) > 1e-1) resize(full_img, img, Size(), compose_scale, compose_scale); else img = full_img; full_img.release(); Size img_size = img.size(); Mat K; cameras_[img_idx].K().convertTo(K, CV_32F); // Warp the current image w->warp(img, K, cameras_[img_idx].R, INTER_LINEAR, BORDER_REFLECT, img_warped); // Warp the current image mask mask.create(img_size, CV_8U); mask.setTo(Scalar::all(255)); w->warp(mask, K, cameras_[img_idx].R, INTER_NEAREST, BORDER_CONSTANT, mask_warped); // Compensate exposure exposure_comp_->apply((int)img_idx, corners[img_idx], img_warped, mask_warped); img_warped.convertTo(img_warped_s, CV_16S); img_warped.release(); img.release(); mask.release(); // Make sure seam mask has proper size dilate(masks_warped[img_idx], dilated_mask, Mat()); resize(dilated_mask, seam_mask, mask_warped.size()); mask_warped = seam_mask & mask_warped; if (!is_blender_prepared) { blender_->prepare(corners, sizes); is_blender_prepared = true; } // Blend the current image blender_->feed(img_warped_s, mask_warped, corners[img_idx]); } Mat result, result_mask; blender_->blend(result, result_mask); LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); // Preliminary result is in CV_16SC3 format, but all values are in [0,255] range, // so convert it to avoid user confusing result.convertTo(pano_, CV_8U); return OK; }
void BlockBase::print(LogId logId, bool recursive) { LOGLN(logId, "(", pos.x, ",", pos.y, "), size=", m_size); }
void RhoanaGainCompensator::feed(const vector<Point> &corners, const vector<UMat> &images, const vector<pair<UMat,uchar> > &masks) { LOGLN("Exposure compensation..."); #if ENABLE_LOG int64 t = getTickCount(); #endif CV_Assert(corners.size() == images.size() && images.size() == masks.size()); const int num_images = static_cast<int>(images.size()); Mat_<int> N(num_images, num_images); N.setTo(0); Mat_<double> I(num_images, num_images); I.setTo(0); //Rect dst_roi = resultRoi(corners, images); Mat subimg1, subimg2; Mat_<uchar> submask1, submask2, intersect; for (int i = 0; i < num_images; ++i) { //std::cout << "Here1.1" << std::endl; for (int j = i; j < num_images; ++j) { Rect roi; if (overlapRoi(corners[i], corners[j], images[i].size(), images[j].size(), roi)) { subimg1 = images[i](Rect(roi.tl() - corners[i], roi.br() - corners[i])).getMat(ACCESS_READ); subimg2 = images[j](Rect(roi.tl() - corners[j], roi.br() - corners[j])).getMat(ACCESS_READ); submask1 = masks[i].first(Rect(roi.tl() - corners[i], roi.br() - corners[i])).getMat(ACCESS_READ); submask2 = masks[j].first(Rect(roi.tl() - corners[j], roi.br() - corners[j])).getMat(ACCESS_READ); intersect = (submask1 == masks[i].second) & (submask2 == masks[j].second); N(i, j) = N(j, i) = std::max(1, countNonZero(intersect)); double Isum1 = 0, Isum2 = 0; for (int y = 0; y < roi.height; ++y) { ///const Point3_<uchar>* r1 = subimg1.ptr<Point3_<uchar> >(y); ///const Point3_<uchar>* r2 = subimg2.ptr<Point3_<uchar> >(y); const uchar* r1 = subimg1.ptr<uchar>(y); const uchar* r2 = subimg2.ptr<uchar>(y); for (int x = 0; x < roi.width; ++x) { if (intersect(y, x)) { ///Isum1 += sqrt(static_cast<double>(sqr(r1[x].x) + sqr(r1[x].y) + sqr(r1[x].z))); ///Isum2 += sqrt(static_cast<double>(sqr(r2[x].x) + sqr(r2[x].y) + sqr(r2[x].z))); Isum1 += sqrt(static_cast<double>(sqr(r1[x]))); Isum2 += sqrt(static_cast<double>(sqr(r2[x]))); } } } I(i, j) = Isum1 / N(i, j); I(j, i) = Isum2 / N(i, j); } } //std::cout << "Here1.2" << std::endl; } double alpha = 0.01; double beta = 100; Mat_<double> A(num_images, num_images); A.setTo(0); Mat_<double> b(num_images, 1); b.setTo(0); for (int i = 0; i < num_images; ++i) { for (int j = 0; j < num_images; ++j) { b(i, 0) += beta * N(i, j); A(i, i) += beta * N(i, j); if (j == i) continue; A(i, i) += 2 * alpha * I(i, j) * I(i, j) * N(i, j); A(i, j) -= 2 * alpha * I(i, j) * I(j, i) * N(i, j); } } solve(A, b, gains_); LOGLN("Exposure compensation, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); }
void GuiSystem::handleInput(InputEvent &ev) { if (ev.isConsumed()) return; switch (ev.type) { case InputEvent::EV_KEY_DOWN: if (pFocusedElement_) { pFocusedElement_->keyDown(ev.key); ev.consume(); } break; case InputEvent::EV_KEY_UP: if (pFocusedElement_) { pFocusedElement_->keyUp(ev.key); ev.consume(); } break; case InputEvent::EV_KEY_CHAR: if (pFocusedElement_) { pFocusedElement_->keyChar(ev.ch); ev.consume(); } break; case InputEvent::EV_MOUSE_DOWN: if (pCaptured) { pCaptured->mouseDown((MouseButtons)ev.mouseButton); ev.consume(); } else { if (lastUnderMouse) { lastUnderMouse->mouseDown((MouseButtons)ev.mouseButton); int lastZ = 1; if (pFocusedElement_ != lastUnderMouse) { if (pFocusedElement_) { pFocusedElement_->focusLost(); lastZ += pFocusedElement_->getZValue(); } pFocusedElement_ = lastUnderMouse; pFocusedElement_->focusGot(); pFocusedElement_->setZValue(lastZ+1); normalizeZValuesAndSort(pFocusedElement_); } ev.consume(); } } break; case InputEvent::EV_MOUSE_UP: if (pCaptured) { pCaptured->mouseUp((MouseButtons)ev.mouseButton); ev.consume(); } else { if (lastUnderMouse) { lastUnderMouse->mouseUp((MouseButtons)ev.mouseButton); ev.consume(); } } break; case InputEvent::EV_MOUSE_MOVED: if (pCaptured) { pCaptured->mouseMoved(glm::vec2(ev.dx, ev.dy), glm::vec2(ev.x, ev.y)); ev.consume(); } else { IGuiElement *crt = getElementUnderMouse(ev.x, ev.y); if (crt != lastUnderMouse) { if (lastUnderMouse) lastUnderMouse->mouseLeave(); lastUnderMouse = crt; if (lastUnderMouse) { lastUnderMouse->mouseEnter(); } } if (lastUnderMouse) lastUnderMouse->mouseMoved(glm::vec2(ev.dx, ev.dy), glm::vec2(ev.x, ev.y)); } break; case InputEvent::EV_MOUSE_SCROLL: if (pCaptured) { pCaptured->mouseScroll(ev.dz); ev.consume(); } else { if (lastUnderMouse) { lastUnderMouse->mouseScroll(ev.dz); ev.consume(); } } break; default: LOGLN("unknown event type: " << ev.type); } }
Stitcher::Status Stitcher::matchImages() { if ((int)imgs_.size() < 2) { LOGLN("Need more images"); return ERR_NEED_MORE_IMGS; } work_scale_ = 1; seam_work_aspect_ = 1; seam_scale_ = 1; bool is_work_scale_set = false; bool is_seam_scale_set = false; features_.resize(imgs_.size()); seam_est_imgs_.resize(imgs_.size()); full_img_sizes_.resize(imgs_.size()); LOGLN("Finding features..."); #if ENABLE_LOG int64 t = getTickCount(); #endif std::vector<UMat> feature_find_imgs(imgs_.size()); std::vector<UMat> feature_find_masks(masks_.size()); for (size_t i = 0; i < imgs_.size(); ++i) { full_img_sizes_[i] = imgs_[i].size(); if (registr_resol_ < 0) { feature_find_imgs[i] = imgs_[i]; work_scale_ = 1; is_work_scale_set = true; } else { if (!is_work_scale_set) { work_scale_ = std::min(1.0, std::sqrt(registr_resol_ * 1e6 / full_img_sizes_[i].area())); is_work_scale_set = true; } resize(imgs_[i], feature_find_imgs[i], Size(), work_scale_, work_scale_, INTER_LINEAR_EXACT); } if (!is_seam_scale_set) { seam_scale_ = std::min(1.0, std::sqrt(seam_est_resol_ * 1e6 / full_img_sizes_[i].area())); seam_work_aspect_ = seam_scale_ / work_scale_; is_seam_scale_set = true; } if (!masks_.empty()) { resize(masks_[i], feature_find_masks[i], Size(), work_scale_, work_scale_, INTER_NEAREST); } features_[i].img_idx = (int)i; LOGLN("Features in image #" << i+1 << ": " << features_[i].keypoints.size()); resize(imgs_[i], seam_est_imgs_[i], Size(), seam_scale_, seam_scale_, INTER_LINEAR_EXACT); } // find features possibly in parallel detail::computeImageFeatures(features_finder_, feature_find_imgs, features_, feature_find_masks); // Do it to save memory feature_find_imgs.clear(); feature_find_masks.clear(); LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); LOG("Pairwise matching"); #if ENABLE_LOG t = getTickCount(); #endif (*features_matcher_)(features_, pairwise_matches_, matching_mask_); features_matcher_->collectGarbage(); LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); // Leave only images we are sure are from the same panorama indices_ = detail::leaveBiggestComponent(features_, pairwise_matches_, (float)conf_thresh_); std::vector<UMat> seam_est_imgs_subset; std::vector<UMat> imgs_subset; std::vector<Size> full_img_sizes_subset; for (size_t i = 0; i < indices_.size(); ++i) { imgs_subset.push_back(imgs_[indices_[i]]); seam_est_imgs_subset.push_back(seam_est_imgs_[indices_[i]]); full_img_sizes_subset.push_back(full_img_sizes_[indices_[i]]); } seam_est_imgs_ = seam_est_imgs_subset; imgs_ = imgs_subset; full_img_sizes_ = full_img_sizes_subset; if ((int)imgs_.size() < 2) { LOGLN("Need more images"); return ERR_NEED_MORE_IMGS; } return OK; }