图像拼接(十一):双摄像头实时拼接+stitching_detailed
来源:互联网 发布:北京蓝天软件 编辑:程序博客网 时间:2024/05/21 10:04
OpenCV自带的stitching模块在追求拼接质量方面已经做得很好了,但是实时性不够,即使是拼接两幅图像。比如源程序拼接两幅640*480分辨率的图像,拼接时间为4.78″。
对stitching_detailed.cpp源码进行改造,有利于提高实时性的举措有:
调节初始化参数。比如使用GPU,这个需要重新编译OPenCV库。
减少视频帧的分辨率。减少为320*240。
将特征提取、匹配、变换矩阵计算等步骤归为初始化部分。变换矩阵不再逐帧计算。
程序代码:
#include <iostream>#include <fstream>#include <string>#include "opencv2/core/core.hpp"#include "opencv2/imgproc/imgproc.hpp"#include "opencv2/opencv_modules.hpp"#include "opencv2/highgui/highgui.hpp"#include "opencv2/stitching/detail/autocalib.hpp"#include "opencv2/stitching/detail/blenders.hpp"#include "opencv2/stitching/detail/camera.hpp"#include "opencv2/stitching/detail/exposure_compensate.hpp"#include "opencv2/stitching/detail/matchers.hpp"#include "opencv2/stitching/detail/motion_estimators.hpp"#include "opencv2/stitching/detail/seam_finders.hpp"#include "opencv2/stitching/detail/util.hpp"#include "opencv2/stitching/detail/warpers.hpp"#include "opencv2/stitching/warpers.hpp"using namespace cv;using namespace std;using namespace cv::detail;#define ENABLE_LOG 1// Default command line argsvector<string> img_names;bool preview = false;bool try_gpu = true;double work_megapix = 0.6;double seam_megapix = 0.1;double compose_megapix = 1;float conf_thresh = 1.f;string features_type = "surf";string ba_cost_func = "ray";string ba_refine_mask = "xxxxx";bool do_wave_correct = false;WaveCorrectKind wave_correct = detail::WAVE_CORRECT_HORIZ;bool save_graph = false;std::string save_graph_to;//string warp_type = "spherical";string warp_type = "cylindrical";//int expos_comp_type = ExposureCompensator::GAIN_BLOCKS;int expos_comp_type = ExposureCompensator::NO;float match_conf = 0.3f;//string seam_find_type = "gc_color";string seam_find_type = "no";int blend_type = Blender::MULTI_BAND;float blend_strength = 3;string result_name = "result.jpg";int main(){ //打开摄像头 VideoCapture cap1(0); VideoCapture cap2(1); double rate = 60; int delay = 1000 / rate; bool stop(false); Mat frame1; Mat frame2; Mat frame; int k = 100; namedWindow("cam1", CV_WINDOW_AUTOSIZE); namedWindow("cam2", CV_WINDOW_AUTOSIZE); namedWindow("stitch", CV_WINDOW_AUTOSIZE); if (cap1.isOpened() && cap2.isOpened()) { cout << "*** ***" << endl; cout << "摄像头已启动!" << endl; } else { cout << "*** ***" << endl; cout << "警告:请检查摄像头是否安装好!" << endl; cout << "程序结束!" << endl << "*** ***" << endl; return -1; } cap1.set(CV_CAP_PROP_FRAME_WIDTH, 320); cap1.set(CV_CAP_PROP_FRAME_HEIGHT, 240); cap2.set(CV_CAP_PROP_FRAME_WIDTH, 320); cap2.set(CV_CAP_PROP_FRAME_HEIGHT, 240); cap1.set(CV_CAP_PROP_FOCUS, 0); cap2.set(CV_CAP_PROP_FOCUS, 0); //获取两幅图像,通过这两幅图像来估计摄像机参数 while (k--) { if (cap1.read(frame1) && cap2.read(frame2)) { imshow("cam1", frame1); imshow("cam2", frame2); imwrite("frame1.bmp", frame1); imwrite("frame2.bmp", frame2); } } //计算相机内参数及旋转矩阵等参数#if ENABLE_LOG int64 app_start_time = getTickCount();#endif cv::setBreakOnError(true); //读入图片 img_names.push_back("frame1.bmp"); img_names.push_back("frame2.bmp"); // Check if have enough images int num_images = static_cast<int>(img_names.size()); if (num_images < 2) { LOGLN("Need more images"); return -1; } double work_scale = 1, seam_scale = 1, compose_scale = 0.5; bool is_work_scale_set = false, is_seam_scale_set = false, is_compose_scale_set = false; cout << "Finding features..." << endl;#if ENABLE_LOG int64 t = getTickCount();#endif Ptr<FeaturesFinder> finder; if (features_type == "surf") {#if defined(HAVE_OPENCV_NONFREE) && defined(HAVE_OPENCV_GPU) if (try_gpu && gpu::getCudaEnabledDeviceCount() > 0) finder = new SurfFeaturesFinderGpu(); else#endif finder = new SurfFeaturesFinder(); } else if (features_type == "orb") { finder = new OrbFeaturesFinder(); } else { cout << "Unknown 2D features type: '" << features_type << "'.\n"; return -1; } Mat full_img, img; vector<ImageFeatures> features(num_images); vector<Mat> images(num_images); vector<Size> full_img_sizes(num_images); double seam_work_aspect = 1; for (int i = 0; i < num_images; ++i) { full_img = imread(img_names[i]); full_img_sizes[i] = full_img.size(); if (full_img.empty()) { LOGLN("Can't open image " << img_names[i]); return -1; } if (work_megapix < 0) { img = full_img; work_scale = 1; is_work_scale_set = true; } else { if (!is_work_scale_set) { work_scale = min(1.0, sqrt(work_megapix * 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_megapix * 1e6 / full_img.size().area())); seam_work_aspect = seam_scale / work_scale; is_seam_scale_set = true; } (*finder)(img, features[i]); features[i].img_idx = i; LOGLN("Features in image #" << i + 1 << ": " << features[i].keypoints.size()); resize(full_img, img, Size(), seam_scale, seam_scale); images[i] = img.clone(); } finder->collectGarbage(); full_img.release(); img.release(); cout << "Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec" << endl; cout << ("Pairwise matching") << endl;#if ENABLE_LOG t = getTickCount();#endif vector<MatchesInfo> pairwise_matches; BestOf2NearestMatcher matcher(try_gpu, match_conf); matcher(features, pairwise_matches); matcher.collectGarbage(); cout << ("Pairwise matching, time: ") << ((getTickCount() - t) / getTickFrequency()) << " sec" << endl; // Check if we should save matches graph if (save_graph) { LOGLN("Saving matches graph..."); ofstream f(save_graph_to.c_str()); f << matchesGraphAsString(img_names, pairwise_matches, conf_thresh); } // Leave only images we are sure are from the same panorama vector<int> indices = leaveBiggestComponent(features, pairwise_matches, conf_thresh); vector<Mat> img_subset; vector<string> img_names_subset; vector<Size> full_img_sizes_subset; for (size_t i = 0; i < indices.size(); ++i) { img_names_subset.push_back(img_names[indices[i]]); img_subset.push_back(images[indices[i]]); full_img_sizes_subset.push_back(full_img_sizes[indices[i]]); } images = img_subset; img_names = img_names_subset; full_img_sizes = full_img_sizes_subset; // Check if we still have enough images num_images = static_cast<int>(img_names.size()); if (num_images < 2) { LOGLN("Need more images"); return -1; } HomographyBasedEstimator estimator; vector<CameraParams> cameras; estimator(features, pairwise_matches, cameras); for (size_t i = 0; i < cameras.size(); ++i) { Mat R; cameras[i].R.convertTo(R, CV_32F); cameras[i].R = R; cout << ("Initial intrinsics #") << indices[i] + 1 << ":\n" << cameras[i].K() << endl; } Ptr<detail::BundleAdjusterBase> adjuster; if (ba_cost_func == "reproj") adjuster = new detail::BundleAdjusterReproj(); else if (ba_cost_func == "ray") adjuster = new detail::BundleAdjusterRay(); else { cout << "Unknown bundle adjustment cost function: '" << ba_cost_func << "'.\n"; return -1; } adjuster->setConfThresh(conf_thresh); Mat_<uchar> refine_mask = Mat::zeros(3, 3, CV_8U); if (ba_refine_mask[0] == 'x') refine_mask(0, 0) = 1; if (ba_refine_mask[1] == 'x') refine_mask(0, 1) = 1; if (ba_refine_mask[2] == 'x') refine_mask(0, 2) = 1; if (ba_refine_mask[3] == 'x') refine_mask(1, 1) = 1; if (ba_refine_mask[4] == 'x') refine_mask(1, 2) = 1; adjuster->setRefinementMask(refine_mask); (*adjuster)(features, pairwise_matches, cameras); // Find median focal length vector<double> focals; for (size_t i = 0; i < cameras.size(); ++i) { cout << ("Camera #") << indices[i] + 1 << ":\n" << cameras[i].K() << endl; focals.push_back(cameras[i].focal); } sort(focals.begin(), focals.end()); float warped_image_scale; 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) { vector<Mat> rmats; for (size_t i = 0; i < cameras.size(); ++i) rmats.push_back(cameras[i].R.clone()); waveCorrect(rmats, wave_correct); for (size_t i = 0; i < cameras.size(); ++i) cameras[i].R = rmats[i]; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// cout << ("Warping images (auxiliary)... ") << endl;#if ENABLE_LOG t = getTickCount();#endif vector<Point> corners(num_images); vector<Mat> masks_warped(num_images); vector<Mat> images_warped(num_images); vector<Size> sizes(num_images); vector<Mat> masks(num_images); // Preapre images masks for (int i = 0; i < num_images; ++i) { masks[i].create(images[i].size(), CV_8U); masks[i].setTo(Scalar::all(255)); } // Warp images and their masks Ptr<WarperCreator> warper_creator;#if defined(HAVE_OPENCV_GPU) if (try_gpu && gpu::getCudaEnabledDeviceCount() > 0) { if (warp_type == "plane") warper_creator = new cv::PlaneWarperGpu(); else if (warp_type == "cylindrical") warper_creator = new cv::CylindricalWarperGpu(); else if (warp_type == "spherical") warper_creator = new cv::SphericalWarperGpu(); } else#endif { if (warp_type == "plane") warper_creator = new cv::PlaneWarper(); else if (warp_type == "cylindrical") warper_creator = new cv::CylindricalWarper(); else if (warp_type == "spherical") warper_creator = new cv::SphericalWarper(); else if (warp_type == "fisheye") warper_creator = new cv::FisheyeWarper(); else if (warp_type == "stereographic") warper_creator = new cv::StereographicWarper(); else if (warp_type == "compressedPlaneA2B1") warper_creator = new cv::CompressedRectilinearWarper(2, 1); else if (warp_type == "compressedPlaneA1.5B1") warper_creator = new cv::CompressedRectilinearWarper(1.5, 1); else if (warp_type == "compressedPlanePortraitA2B1") warper_creator = new cv::CompressedRectilinearPortraitWarper(2, 1); else if (warp_type == "compressedPlanePortraitA1.5B1") warper_creator = new cv::CompressedRectilinearPortraitWarper(1.5, 1); else if (warp_type == "paniniA2B1") warper_creator = new cv::PaniniWarper(2, 1); else if (warp_type == "paniniA1.5B1") warper_creator = new cv::PaniniWarper(1.5, 1); else if (warp_type == "paniniPortraitA2B1") warper_creator = new cv::PaniniPortraitWarper(2, 1); else if (warp_type == "paniniPortraitA1.5B1") warper_creator = new cv::PaniniPortraitWarper(1.5, 1); else if (warp_type == "mercator") warper_creator = new cv::MercatorWarper(); else if (warp_type == "transverseMercator") warper_creator = new cv::TransverseMercatorWarper(); } if (warper_creator.empty()) { cout << "Can't create the following warper '" << warp_type << "'\n"; return 1; } Ptr<RotationWarper> warper = warper_creator->create(static_cast<float>(warped_image_scale * seam_work_aspect)); for (int i = 0; i < num_images; ++i) { Mat_<float> K; cameras[i].K().convertTo(K, CV_32F); float swa = (float)seam_work_aspect; K(0, 0) *= swa; K(0, 2) *= swa; K(1, 1) *= swa; K(1, 2) *= swa; corners[i] = warper->warp(images[i], K, cameras[i].R, INTER_LINEAR, BORDER_REFLECT, images_warped[i]); sizes[i] = images_warped[i].size(); warper->warp(masks[i], K, cameras[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]); } vector<Mat> images_warped_f(num_images); for (int i = 0; i < num_images; ++i) images_warped[i].convertTo(images_warped_f[i], CV_32F); cout << "Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec" << endl; ////////////////////////////////////warp end///////////////////////////////////////////////////////////////////////////////////// Ptr<ExposureCompensator> compensator = ExposureCompensator::createDefault(expos_comp_type); compensator->feed(corners, images_warped, masks_warped); Ptr<SeamFinder> seam_finder; if (seam_find_type == "no") seam_finder = new detail::NoSeamFinder(); else if (seam_find_type == "voronoi") seam_finder = new detail::VoronoiSeamFinder(); else if (seam_find_type == "gc_color") {#if defined(HAVE_OPENCV_GPU) if (try_gpu && gpu::getCudaEnabledDeviceCount() > 0) seam_finder = new detail::GraphCutSeamFinderGpu(GraphCutSeamFinderBase::COST_COLOR); else#endif seam_finder = new detail::GraphCutSeamFinder(GraphCutSeamFinderBase::COST_COLOR); } else if (seam_find_type == "gc_colorgrad") {#if defined(HAVE_OPENCV_GPU) if (try_gpu && gpu::getCudaEnabledDeviceCount() > 0) seam_finder = new detail::GraphCutSeamFinderGpu(GraphCutSeamFinderBase::COST_COLOR_GRAD); else#endif seam_finder = new detail::GraphCutSeamFinder(GraphCutSeamFinderBase::COST_COLOR_GRAD); } else if (seam_find_type == "dp_color") seam_finder = new detail::DpSeamFinder(DpSeamFinder::COLOR); else if (seam_find_type == "dp_colorgrad") seam_finder = new detail::DpSeamFinder(DpSeamFinder::COLOR_GRAD); if (seam_finder.empty()) { cout << "Can't create the following seam finder '" << seam_find_type << "'\n"; return 1; } seam_finder->find(images_warped_f, corners, masks_warped); // Release unused memory images.clear(); images_warped.clear(); images_warped_f.clear(); masks.clear(); ///////////////////////////////////exposure&seam end/////////////////////////////////////////////////////////////////////// //实时拼接 while (!stop) { if (cap1.read(frame1) && cap2.read(frame2)) { imshow("cam1", frame1); imshow("cam2", frame2); imwrite("frame1.bmp", frame1); imwrite("frame2.bmp", frame2); //彩色帧转灰度 //cvtColor(frame1, frame1, CV_RGB2GRAY); //cvtColor(frame2, frame2, CV_RGB2GRAY); //拼接过程 //读入图片 cout << "Compositing..." << endl;#if ENABLE_LOG t = getTickCount();#endif Mat img_warped, img_warped_s; Mat dilated_mask, seam_mask, mask, mask_warped; Ptr<Blender> blender; //double compose_seam_aspect = 1; double compose_work_aspect = 1; img_names.pop_back(); img_names.pop_back(); img_names.push_back("frame1.bmp"); img_names.push_back("frame2.bmp"); for (int img_idx = 0; img_idx < num_images; ++img_idx) { LOGLN("Compositing image #" << indices[img_idx] + 1); // Read image and resize it if necessary full_img = imread(img_names[img_idx]);/////////////////!!!!!!!!!!!!!!!!!!!!!!!!!!参数固定,可以试着读取不同图像 if (!is_compose_scale_set) { if (compose_megapix > 0) compose_scale = min(1.0, sqrt(compose_megapix * 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); warper = warper_creator->create(warped_image_scale); // Update corners and sizes for (int i = 0; i < num_images; ++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 = warper->warpRoi(sz, K, cameras[i].R); corners[i] = roi.tl(); sizes[i] = roi.size(); } } if (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 warper->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)); warper->warp(mask, K, cameras[img_idx].R, INTER_NEAREST, BORDER_CONSTANT, mask_warped); // Compensate exposure compensator->apply(img_idx, corners[img_idx], img_warped, mask_warped); img_warped.convertTo(img_warped_s, CV_16S); img_warped.release(); img.release(); mask.release(); dilate(masks_warped[img_idx], dilated_mask, Mat()); resize(dilated_mask, seam_mask, mask_warped.size()); mask_warped = seam_mask & mask_warped; if (blender.empty()) { blender = Blender::createDefault(blend_type, try_gpu); Size dst_sz = resultRoi(corners, sizes).size(); float blend_width = sqrt(static_cast<float>(dst_sz.area())) * blend_strength / 100.f; if (blend_width < 1.f) blender = Blender::createDefault(Blender::NO, try_gpu); else if (blend_type == Blender::MULTI_BAND) { MultiBandBlender* mb = dynamic_cast<MultiBandBlender*>(static_cast<Blender*>(blender)); mb->setNumBands(static_cast<int>(ceil(log(blend_width) / log(2.)) - 1.)); cout << "Multi-band blender, number of bands: " << mb->numBands() << endl; } else if (blend_type == Blender::FEATHER) { FeatherBlender* fb = dynamic_cast<FeatherBlender*>(static_cast<Blender*>(blender)); fb->setSharpness(1.f / blend_width); LOGLN("Feather blender, sharpness: " << fb->sharpness()); } blender->prepare(corners, sizes); } // Blend the current image blender->feed(img_warped_s, mask_warped, corners[img_idx]); } Mat result, result_mask; blender->blend(result, result_mask); cout << "Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec" << endl; result.convertTo(frame, CV_8UC1); imshow("stitch", frame); } else { cout << "----------------------" << endl; cout << "waitting..." << endl; } if (waitKey(1) == 13) { stop = true; cout << "程序结束!" << endl; cout << "*** ***" << endl; } } return 0;}
测试结果
调整相机采集图像分辨率为320*240,算法处理间隔为0.05″~0.07″,可以满足实时应用。
1 0
- 图像拼接(十一):双摄像头实时拼接+stitching_detailed
- 图像拼接(四):双摄像头实时视频拼接(平移模型)
- 图像拼接(五):双摄像头实时视频拼接(平移模型+多线程)
- 图像拼接(九):双摄像头实时视频拼接(单应变换模型)
- 图像拼接(十):OPenCV stitching和stitching_detailed
- OPenCV 图像拼接之------stitching和stitching_detailed
- 基于opencv的图像拼接(三): 基于stitch类的实时图像拼接
- OpenCV编程->四路摄像头拼接实时显示
- 图像拼接-硬拼接
- 图像拼接
- 图像拼接
- 图像拼接
- 图像拼接
- 图像拼接
- 图像拼接
- 图像拼接
- 图像拼接
- 图像拼接
- 主要打包流程 xcode8 shell
- char以及string的转换
- 代码(1)
- 排序之堆排序
- 九度OJ-1155:鸡兔同笼
- 图像拼接(十一):双摄像头实时拼接+stitching_detailed
- 定时器,第1次就是在间隔的秒数之后才触发
- 移动开发课堂作业2_____SimpleAdapterTest
- Android Volley完全解析
- Spring 和 Struts2整合总结
- 文奇的WY实习连载日记3.7-今天的存在是因为昨天的无知
- 从csdn的原创文章被扒说起
- C#程序集和反射
- linux如何挂载U盘