700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 图像拼接(十一):双摄像头实时拼接+stitching_detailed

图像拼接(十一):双摄像头实时拼接+stitching_detailed

时间:2021-08-06 16:32:47

相关推荐

图像拼接(十一):双摄像头实时拼接+stitching_detailed

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_LOGint64 app_start_time = getTickCount();#endifcv::setBreakOnError(true);//读入图片img_names.push_back("frame1.bmp");img_names.push_back("frame2.bmp");// Check if have enough imagesint 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_LOGint64 t = getTickCount();#endifPtr<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#endiffinder = 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_LOGt = getTickCount();#endifvector<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 graphif (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 panoramavector<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 imagesnum_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 lengthvector<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]);elsewarped_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_LOGt = getTickCount();#endifvector<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 masksfor (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 masksPtr<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#endifseam_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#endifseam_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 memoryimages.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_LOGt = getTickCount();#endifMat 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 necessaryfull_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 scalewarped_image_scale *= static_cast<float>(compose_work_aspect);warper = warper_creator->create(warped_image_scale);// Update corners and sizesfor (int i = 0; i < num_images; ++i){// Update intrinsicscameras[i].focal *= compose_work_aspect;cameras[i].ppx *= compose_work_aspect;cameras[i].ppy *= compose_work_aspect;// Update corner and sizeSize 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);elseimg = full_img;full_img.release();Size img_size = img.size();Mat K;cameras[img_idx].K().convertTo(K, CV_32F);// Warp the current imagewarper->warp(img, K, cameras[img_idx].R, INTER_LINEAR, BORDER_REFLECT, img_warped);// Warp the current image maskmask.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 exposurecompensator->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 imageblender->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″,可以满足实时应用。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。