本文共 16328 字,大约阅读时间需要 54 分钟。
multibandblend是目前图像融和方面比较好的方法。原始论文为《a multivesolution spline with application to image mosaics 》,可以用百度学术找到。原始论文中采用的方法是直接对带拼接的两个图片进行拉普拉斯金字塔分解,而后一半对一半进行融合。国人也有许多改进,比如“首先采用拉普拉斯分辨率金字塔结构,将输入的图像分解成一系列不同频率上的图像,而后在每个频段上面进行加权平均,把这些不同频率上的图像最后合并一个图像”。这个说法来源于下面这篇论文,根据其他获得的一些知识,应该是可行的。 在opencv内部已经实现了multibandblend,但是耦合度比较高,想拿出来单独研究要对代码有深入理解;网络上面也有这样的实现,这个下文提及[]。为了深入理解并运用,还是要自己实现,再复用别人的代码 一、首先实现laplacian金字塔的分解和重建 #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/features2d/features2d.hpp> #include <opencv2/calib3d/calib3d.hpp> int _tmain(int argc, _TCHAR* argv[]) Mat src = imread("Lena.jpg"); src.convertTo(src,CV_32F,1.0/255); resize(tmp,tmp,src.size()); resize(lastmat,lastmat,vecMats[i].size()); lastmat = lastmat + vecMats[i]; 二、实现每个金字塔层面的linearblend融合 #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/features2d/features2d.hpp> #include <opencv2/calib3d/calib3d.hpp> int _tmain(int argc, _TCHAR* argv[]) Mat srcLeft = imread("apple.png"); Mat srcRight= imread("orange.png"); srcLeft.convertTo(srcLeft,CV_32F,1.0/255); srcRight.convertTo(srcRight,CV_32F,1.0/255); Mat rawLeft = srcLeft.clone(); Mat rawRight=srcRight.clone(); imshow("srcRight",srcRight); imshow("srcLeft",srcLeft); Mat srcLeft2= srcLeft.clone(); Mat srcRight2=srcRight.clone(); Mat lastmatLeft; Mat lastmatRight; vector<Mat> vecMatsLeft; vector<Mat> vecMatsRight; pyrDown(srcLeft2,srcLeft2); resize(tmp,tmp,srcLeft.size()); vecMatsLeft.push_back(tmp); pyrDown(srcRight2,srcRight2); resize(tmp,tmp,srcRight.size()); vecMatsRight.push_back(tmp); int ioffset =vecMatsLeft[0].cols - 100;//这里-100 的操作是linearblend的小技巧 vector<Mat> vecMatResult;//保存结果 tmpResult = Mat::zeros(vecMatsLeft[i].rows,vecMatsLeft[i].cols*2,vecMatsLeft[i].type()); roi = tmpResult(Rect(ioffset,0,vecMatsLeft[i].cols,vecMatsLeft[i].rows)); vecMatsRight[i].copyTo(roi); roi = tmpResult(Rect(0,0,vecMatsLeft[i].cols,vecMatsLeft[i].rows)); vecMatsLeft[i].copyTo(roi); for (int j = 0;j<(100/istep);j++) tmpResult.col(ioffset + j)= tmpResult.col(ioffset+j)*(1-dblend) + vecMatsRight[i].col(j)*dblend; dblend = dblend +0.01*istep; vecMatResult.push_back(tmpResult); Mat latmatresult = Mat::zeros(lastmatLeft.rows,lastmatLeft.cols*2,lastmatLeft.type()); roi = latmatresult(Rect(0,0,lastmatLeft.cols,lastmatLeft.rows)); roi = latmatresult(Rect(ioffset,0,lastmatLeft.cols,lastmatLeft.rows)); lastmatRight.copyTo(roi); for (int j=0;j<(100/istep);j++) latmatresult.col(ioffset + j)= latmatresult.col(ioffset+j)*(1-dblend) + lastmatRight.col(j)*dblend; dblend = dblend +0.01*istep; pyrUp(lastmatLeft,lastmatLeft); resize(lastmatLeft,lastmatLeft,vecMatsLeft[i].size()); lastmatLeft = lastmatLeft + vecMatsLeft[i]; pyrUp(lastmatRight,lastmatRight); resize(lastmatRight,lastmatRight,vecMatsRight[i].size()); lastmatRight = lastmatRight + vecMatsRight[i]; pyrUp(latmatresult,latmatresult); resize(latmatresult,latmatresult,vecMatResult[i].size()); latmatresult = latmatresult + vecMatResult[i]; imshow("lastmatLeft",lastmatLeft); lastmatLeft.convertTo(lastmatLeft,CV_8U,255); imwrite("lastmatleft.png",lastmatLeft); imshow("lastmatRight",lastmatRight); imshow("multibandblend",latmatresult); 观看细节,可以看到过渡的地方一点都不违和,苹果上面的白色小斑点都过渡了过来。这个结果应该是非常接近论文上面的要求了。 #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/features2d/features2d.hpp> #include <opencv2/calib3d/calib3d.hpp> // Read input images 这里的命名最好为imageleft和imageright cv::Mat image1= cv::imread("Univ4.jpg",1); cv::Mat image2= cv::imread("Univ5.jpg",1); if (!image1.data || !image2.data) rmatcher.setConfidenceLevel(0.98); rmatcher.setMinDistanceToEpipolar(1.0); rmatcher.setRatio(0.65f); cv::Ptr<cv::FeatureDetector> pfd= new cv::SurfFeatureDetector(10); rmatcher.setFeatureDetector(pfd); std::vector<cv::DMatch> matches; std::vector<cv::KeyPoint> keypoints1, keypoints2; cv::Mat fundemental= rmatcher.match(image1,image2,matches, keypoints1, keypoints2); cv::drawMatches(image1,keypoints1, // 1st image and its keypoints image2,keypoints2, // 2nd image and its keypoints imageMatches, // the image produced cv::Scalar(255,255,255)); // color of the lines // Convert keypoints into Point2f std::vector<cv::Point2f> points1, points2; for (std::vector<cv::DMatch>::const_iterator it= matches.begin(); it!= matches.end(); ++it) { // Get the position of left keypoints float x= keypoints1[it->queryIdx].pt.x; float y= keypoints1[it->queryIdx].pt.y; points1.push_back(cv::Point2f(x,y)); // Get the position of right keypoints x= keypoints2[it->trainIdx].pt.x; y= keypoints2[it->trainIdx].pt.y; points2.push_back(cv::Point2f(x,y)); std::cout << points1.size() << " " << points2.size() << std::endl; // Find the homography between image 1 and image 2 std::vector<uchar> inliers(points1.size(),0); cv::Mat homography= cv::findHomography( cv::Mat(points1),cv::Mat(points2), // corresponding points inliers, // outputed inliers matches CV_RANSAC, // RANSAC method 1.); // max distance to reprojection point // Warp image 1 to image 2 cv::warpPerspective(image1, // input image homography, // homography cv::Size(2*image1.cols,image1.rows)); // size of output image result.copyTo(resultback); // Copy image 1 on the first half of full image cv::Mat half(result,cv::Rect(0,0,image2.cols,image2.rows)); // Display the warp image cv::namedWindow("After warping"); cv::imshow("After warping",result); //需要注意的一点是,原始文件的图片是按照从右至左边进行移动的。 //进行Multiblend的融合,融合的输入图像为image2(左)和resultback(右) Mat srcLeft = image2.clone(); Mat srcRight= resultback.clone(); srcLeft.convertTo(srcLeft,CV_32F,1.0/255); srcRight.convertTo(srcRight,CV_32F,1.0/255); Mat rawLeft = srcLeft.clone(); Mat rawRight=srcRight.clone(); Mat srcLeft2= srcLeft.clone(); Mat srcRight2=srcRight.clone(); Mat lastmatLeft; Mat lastmatRight; vector<Mat> vecMatsLeft; vector<Mat> vecMatsRight; pyrDown(srcLeft2,srcLeft2); resize(tmp,tmp,srcLeft.size()); vecMatsLeft.push_back(tmp); pyrDown(srcRight2,srcRight2); resize(tmp,tmp,srcRight.size()); vecMatsRight.push_back(tmp); int ioffset =vecMatsLeft[0].cols - 100;//这里-100 的操作是linearblend的小技巧 vector<Mat> vecMatResult;//保存结果 tmpResult = Mat::zeros(vecMatsLeft[i].rows,vecMatsLeft[i].cols*2,vecMatsLeft[i].type()); roi = tmpResult(Rect(0,0,vecMatsRight[i].cols,vecMatsRight[i].rows)); vecMatsRight[i].copyTo(roi); roi = tmpResult(Rect(0,0,vecMatsLeft[i].cols,vecMatsLeft[i].rows)); vecMatsLeft[i].copyTo(roi); for (int j = 0;j<(128/istep);j++) tmpResult.col(ioffset + j)= tmpResult.col(ioffset+j)*(1-dblend) + vecMatsRight[i].col(ioffset+j)*dblend; dblend = dblend +0.0078125*istep; vecMatResult.push_back(tmpResult); Mat latmatresult = Mat::zeros(lastmatLeft.rows,lastmatLeft.cols*2,lastmatLeft.type()); roi = latmatresult(Rect(0,0,lastmatRight.cols,lastmatRight.rows)); lastmatRight.copyTo(roi); roi = latmatresult(Rect(0,0,lastmatLeft.cols,lastmatLeft.rows)); for (int j=0;j<(128/istep);j++) latmatresult.col(ioffset+j)= latmatresult.col(ioffset+j)*(1-dblend) + lastmatRight.col(ioffset+j)*dblend; dblend = dblend +0.00725*istep; pyrUp(lastmatLeft,lastmatLeft); resize(lastmatLeft,lastmatLeft,vecMatsLeft[i].size()); lastmatLeft = lastmatLeft + vecMatsLeft[i]; pyrUp(lastmatRight,lastmatRight); resize(lastmatRight,lastmatRight,vecMatsRight[i].size()); lastmatRight = lastmatRight + vecMatsRight[i]; pyrUp(latmatresult,latmatresult); resize(latmatresult,latmatresult,vecMatResult[i].size()); latmatresult = latmatresult + vecMatResult[i]; } 上文提及了已经实现的代码,这个代码写的还是相当不错的,其原理完全按照 《a multivesolution spline with application to image mosaics 》来实现,为了应用于实际的图像拼接,做了一些修改 #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/features2d/features2d.hpp> #include <opencv2/calib3d/calib3d.hpp> class LaplacianBlending { vector<Mat_<Vec3f> > topLapPyr,downLapPyr,resultLapPyr;//Laplacian Pyramids Mat topHighestLevel, downHighestLevel, resultHighestLevel; vector<Mat_<Vec3f> > maskGaussianPyramid; //masks are 3-channels for easier multiplication with RGB //参数的解释 top就是top ,topLapPyr就是top的laplacian的pyr,而topHighestLevel保存的是最高端的高斯金字塔 buildLaplacianPyramid(top,topLapPyr,topHighestLevel); buildLaplacianPyramid(down,downLapPyr,downHighestLevel); void buildGaussianPyramid() { //金字塔内容为每一层的掩模 assert(topLapPyr.size()>0); maskGaussianPyramid.clear(); cvtColor(blendMask, currentImg, CV_GRAY2BGR);//store color img of blend mask into maskGaussianPyramid maskGaussianPyramid.push_back(currentImg); //0-level for (int l=1; l<levels+1; l++) { if (topLapPyr.size() > l) pyrDown(currentImg, _down, topLapPyr[l].size()); pyrDown(currentImg, _down, topHighestLevel.size()); //lowest level cvtColor(_down, down, CV_GRAY2BGR); maskGaussianPyramid.push_back(down);//add color blend mask into mask Pyramid void buildLaplacianPyramid(const Mat& img, vector<Mat_<Vec3f> >& lapPyr, Mat& HighestLevel) { for (int l=0; l<levels; l++) { pyrDown(currentImg, down); pyrUp(down, up,currentImg.size()); Mat lap = currentImg - up; //存储的就是残差 currentImg.copyTo(HighestLevel); Mat_<Vec3f> reconstructImgFromLapPyramid() { //将左右laplacian图像拼成的resultLapPyr金字塔中每一层 //从上到下插值放大并相加,即得blend图像结果 Mat currentImg = resultHighestLevel; for (int l=levels-1; l>=0; l--) { pyrUp(currentImg, up, resultLapPyr[l].size()); currentImg = up + resultLapPyr[l]; //获得每层金字塔中直接用左右两图Laplacian变换拼成的图像resultLapPyr resultHighestLevel = topHighestLevel.mul(maskGaussianPyramid.back()) + downHighestLevel.mul(Scalar(1.0,1.0,1.0) - maskGaussianPyramid.back()); for (int l=0; l<levels; l++) { Mat A = topLapPyr[l].mul(maskGaussianPyramid[l]); Mat antiMask = Scalar(1.0,1.0,1.0) - maskGaussianPyramid[l]; Mat B = downLapPyr[l].mul(antiMask); Mat_<Vec3f> blendedLevel = A + B; resultLapPyr.push_back(blendedLevel); LaplacianBlending(const Mat_<Vec3f>& _top, const Mat_<Vec3f>& _down, const Mat_<float>& _blendMask, int _levels)://缺省数据,使用 LaplacianBlending lb(l,r,m,4); top(_top),down(_down),blendMask(_blendMask),levels(_levels) assert(_top.size() == _down.size()); assert(_top.size() == _blendMask.size()); buildPyramids(); //创建laplacian金字塔和gauss金字塔 blendLapPyrs(); //将左右金字塔融合成为一个图片 return reconstructImgFromLapPyramid();//reconstruct Image from Laplacian Pyramid Mat_<Vec3f> LaplacianBlend(const Mat_<Vec3f>& t, const Mat_<Vec3f>& d, const Mat_<float>& m) { LaplacianBlending lb(t,d,m,4); // Read input images 这里的命名最好为imageleft和imageright cv::Mat image1= cv::imread("Univ3.jpg",1); cv::Mat image2= cv::imread("Univ4.jpg",1); if (!image1.data || !image2.data) rmatcher.setConfidenceLevel(0.98); rmatcher.setMinDistanceToEpipolar(1.0); rmatcher.setRatio(0.65f); cv::Ptr<cv::FeatureDetector> pfd= new cv::SurfFeatureDetector(10); rmatcher.setFeatureDetector(pfd); std::vector<cv::DMatch> matches; std::vector<cv::KeyPoint> keypoints1, keypoints2; cv::Mat fundemental= rmatcher.match(image1,image2,matches, keypoints1, keypoints2); cv::drawMatches(image1,keypoints1, // 1st image and its keypoints image2,keypoints2, // 2nd image and its keypoints imageMatches, // the image produced cv::Scalar(255,255,255)); // color of the lines // Convert keypoints into Point2f std::vector<cv::Point2f> points1, points2; for (std::vector<cv::DMatch>::const_iterator it= matches.begin(); it!= matches.end(); ++it) { // Get the position of left keypoints float x= keypoints1[it->queryIdx].pt.x; float y= keypoints1[it->queryIdx].pt.y; points1.push_back(cv::Point2f(x,y)); // Get the position of right keypoints x= keypoints2[it->trainIdx].pt.x; y= keypoints2[it->trainIdx].pt.y; points2.push_back(cv::Point2f(x,y)); std::cout << points1.size() << " " << points2.size() << std::endl; // Find the homography between image 1 and image 2 std::vector<uchar> inliers(points1.size(),0); cv::Mat homography= cv::findHomography( cv::Mat(points1),cv::Mat(points2), // corresponding points inliers, // outputed inliers matches CV_RANSAC, // RANSAC method 1.); // max distance to reprojection point // Warp image 1 to image 2 cv::warpPerspective(image1, // input image homography, // homography cv::Size(2*image1.cols,image1.rows)); // size of output image result.copyTo(resultback); // Copy image 1 on the first half of full image cv::Mat half(result,cv::Rect(0,0,image2.cols,image2.rows)); // Display the warp image cv::namedWindow("After warping"); cv::imshow("After warping",result); //需要注意的一点是,原始文件的图片是按照从右至左边进行移动的。 //进行Multiblend的融合,融合的输入图像为image2(左)和resultback(右) Mat srcLeft = image2.clone(); Mat srcRight= resultback.clone(); int ioffset = srcLeft.cols -100; Mat imageL = srcLeft(Rect(ioffset,0,100,srcLeft.rows)).clone(); Mat imageR = srcRight(Rect(ioffset,0,100,srcLeft.rows)).clone(); Mat_<Vec3f> t; imageL.convertTo(t,CV_32F,1.0/255.0);//Vec3f表示有三个通道,即 l[row][column][depth] Mat_<Vec3f> d; imageR.convertTo(d,CV_32F,1.0/255.0); Mat_<float> m(t.rows,d.cols,0.0); //将m全部赋值为0 m(Range::all(),Range(0,m.cols/2)) = 1.0; //取m全部行&[0,m.cols/2]列,赋值为1.0 Mat_<Vec3f> matblend = LaplacianBlend(t,d, m); matblend.convertTo(re,CV_8UC3,255); Mat roi = srcLeft(Rect(ioffset,0,100,srcLeft.rows)); roi = resultback(Rect(0,0,srcLeft.cols,srcLeft.rows)); } 应该说我采用的算法,虽然也有一定程度上效果,但是“鬼影”并没有减少。采用linearblend的方法,不管分不分金字塔,都是“鬼影”产生的原因。研究问题,还是应该读原始论文,找到好的代码,真正地解决问题。
目前方向:图像拼接融合、图像识别 联系方式:jsxyhelu@foxmail.com
转载地址:http://brbfa.baihongyu.com/