• Opencv概述

    2015-07-29 11:00:24
    OpenCV是一个用于图像处理、分析、机器视觉方面的开源函数库.   无论你是做科学研究,还是商业应用,opencv都可以作为你理想的工具库,因为,对于这两者,它完全是免费的。 该库采用C及C++语言编写,可以在...

    opencv是什么

    OpenCV是一个用于图像处理、分析、机器视觉方面的开源函数库. 

          无论你是做科学研究,还是商业应用,opencv都可以作为你理想的工具库,因为,对于这两者,它完全是免费的。
    该库采用C及C++语言编写,可以在windows, linux, mac OSX系统上面运行。该库的所有代码都经过优化,计算效率很高,因为,它更专注于设计成为一种用于实时系统的开源库。opencv采用C语言进行优化,而且,在多核机器上面,其运行速度会更快。它的一个目标是提供友好的机器视觉接口函数,从而使得复杂的机器视觉产品可以加速面世。该库包含了横跨工业产品检测、医学图像处理、安防、用户界面、摄像头标定、三维成像、机器视觉等领域的超过500个接口函数。
          同时,由于计算机视觉与机器学习密不可分,该库也包含了比较常用的一些机器学习算法。或许,很多人知道,图像识别、机器视觉在安防领域有所应用。但,很少有人知道,在航拍图片、街道图片(例如google street view)中,要严重依赖于机器视觉的摄像头标定、图像融合等技术。
          近年来,在入侵检测、特定目标跟踪、目标检测、人脸检测、人脸识别、人脸跟踪等领域,opencv可谓大显身手,而这些,仅仅是其应用的冰山一角。

          如今,来自世界各地的各大公司、科研机构的研究人员,共同维护支持着opencv的开源库开发。这些公司和机构包括:微软,IBM,索尼、西门子、google、intel、斯坦福、MIT、CMU、剑桥。。。

    OpenCV的全称,是Open source Computer Vision Library,开放源代码计算机视觉库。也就是说,它是一套关于计算机视觉的开放源代码的API函数库。这也就意味着,(1)不管是科学研究,还是商业应用,都可以利用它来作开发;(2)所有API函数的源代码都是公开的,你可以看到其内部实现的程序步骤;(3)你可以修改OpenCV的源代码,编译生成你需要的特定API函数。但是,作为一个库,它所提供的,仅仅是一些常用的,经典的,大众化的算法的API。一个典型的计算机视觉算法,应该包含以下一些步骤:(1)数据获取(对OpenCV来说,就是图片);(2)预处理;(3)特征提取;(4)特征选择;(5)分类器设计与训练;(6)分类判别;而OpenCV对这六个部分,分别(记住这个词)提供了API。下面我分别就这六个部分对一些常见问题进行必要的解释。

            对于数据获取,计算机视觉领域的数据,无非就是图片和视频两种。图片,有bmp,jpg,png,tiff....各种压缩和非压缩格式。所以,对压缩格式的图片而言,OpenCV内部必然包含了对应的图片解压缩函数(一般都是包含了开源的图片解压函数库,例如,对于jpg压缩格式而言,就包含了libjpg开源库)。而对于视频而言,常见的有.rmvb,.avi,.asf等格式,不同的格式,代表着不同的视频压缩算法(对于AVI格式,尽管都是avi格式,但内部的压缩算法仍然不相同。具体原因请参考我的另一篇博客:http://blog.csdn.net/carson2005/article/details/6314089),也就需要对应的解压算法来解压。尽管OpenCV提供了一些读写视频文件的API,但是,它也仅仅是一个接口而已,其内部,依然需要调用相应的视频编解码器的API来进行解码。常用的视频编解码器有:xvid,ffmpeg等。也就是说,如果你想利用OpenCV来进行视频读写之类的操作,是需要安装此类视频编解码器的。安装了相应的视频解码器之后,你就可以调用OpenCV的视频相关API来进行视频文件的读取操作了,当然,视频文件被解码之后,变成了一张一张的图片,然后才能被OpenCV所处理。另外,还有一种情况,就是数据来自于相机,包括数字相机和模拟相机。不管是哪种相机,你都要想办法获取到相机发送给PC的图片数据(PC在内存里面接收到的来自相机的数据可能是jpg格式,也可能是bmp格式)。如果,你在PC内存中接收到的是相机发送过来的jpg压缩格式,还需要进行图片数据的内存解压。关于相机和OpenCV的这部分内容,请见我另一篇博客:

    http://blog.csdn.net/carson2005/article/details/6243476

            对于预处理,一般就是去除或者降低噪声,光照归一化,亮度归一化,模糊化,锐化,膨胀,腐蚀、开闭等这些操作(详见,冈萨雷斯,《数字图像处理》一书)。而对于这些操作,OpenCV分别(又提到这个词了)提供了相应API函数。而光照的预处理,OpenCV提供了一个直方图均衡化的API,后续可能会提供一些gammar矫正之类的函数。

            对于特征提取,个人认为,可以算是整个计算机视觉系统中最为复杂也最难的部分(纯属个人意见,如有异议,请保留),到底什么是特征,该如何来理解这个看似简单却又包罗万象的名词呢?其实,要想仔细解释,还真的花费很多时间(有兴趣的可以看看,Richard O.Duda(著),李宏东(译),《模式识别》,机械工业出版社)。简单点说,特征,就是一个可以将若干个类别可以尽量分开的一种描述。举例来说,如果你要进行男人和女人的分类,显然,用“身高和体重”这一描述来衡量,是可以的,但是,这两个描述没有“胸部大小”这一描述更加准确,而“胸部大小”这一描述,又没有“喉结的有无”这一描述更准确。很显然,“身高和体重”,“胸部大小”,“喉结的有无”,这三种描述,都可以用来进行男人和女人的分类,只不过,它们对事物的描述的准确(或者说全面)程度是不同的,而诸如此类的描述,有一个更加专业的称谓,叫做“特征”。OpenCV里面,提供了一些特征描述的API,比如,对于人脸检测而言,它提供了haar特征的API,行人检测,提供了hog特征的API,甚至,它提供了LBP纹理特征的API。但是,这些还远远不够。例如,如果你要进行字符识别,OpenCV并没有提供字符识别所对应的特征。这个时候,就需要你自己来编程实现了。当然,该选择什么特征来描述字符呢?哪些特征更好呢?对于这些问题,我建议你去阅读相应的会议,期刊,杂志,硕士、博士毕业论文(毕竟硕士、博士研究生本就该从事“研究”工作),看看别人写的文章,自然就知道了。

            对于特征选择,OpenCV并没有提供特定的函数来进行衡量。而特征的分类能力的高低评价,有很多种分析方法,有兴趣的朋友,可以阅读"《机器学习》Tom. Mitchell(著),曾华军(译),机械工业出版社"这本书;

            对于分类器部分,OpenCV提供了SVM,CART,boost,bayes,bdt,ANN,这几种常用的算法。而这些基本已经覆盖了常用的分类器。所以,你需要做的,就是知道怎么调用其接口,各种分类器的优点和缺点(该部分,建议阅读“机器学习”这本书)。

            通过以上的分析,你或许已经发现,OpenCV不过是一个工具而已。或者,你可以将它理解为幼儿园小朋友过家家玩的积木,而OpenCV中的函数,则可以理解为一个一个的积木块,利用所有或者部分积木块,你可以快速的搭建起来具体的计算机视觉方面的应用(比如,字符识别,车牌识别,遗留物检测)。想必你也已经发现,在利用OpenCV这个积木来搭建具体的计算机视觉应用的时候,真正核心的,应该是这些积木块,如果你明白了积木块的工作原理,那么,是不是就可以不用这些积木块了呢?完全正确!不过,一般部分情况下,我们不需要这么做,因为,OpenCV已经帮你做好了一些工作(已经帮你做好了一些积木块,直接拿来用就是了)。但是,诸如前面提到的特征提取模块,很多情况下,OpenCV就无能为力了。这个时候,你就需要翻阅计算机视觉、模式识别、机器学习领域顶级会议、期刊、杂志上面发表的文章了。然后,根据这些文章中阐述的原理和方法,来编程实现你要的东西。实际上,也就等于搭建一个属于你私有的积木块。其实,OpenCV中的每一个API函数,也就是这么来的。

    展开全文
  • 基于SURF特征的图像与视频拼接技术的研究和实现(一) 一直有计划研究实时图像拼接,但是直到最近拜读西电2013年张亚娟的《基于SURF特征的图像与视频拼接技术的研究和实现》,条理清晰、内容完整、实现的技术具有...
    基于SURF特征的图像与视频拼接技术的研究和实现(一)
         一直有计划研究实时图像拼接,但是直到最近拜读西电2013年张亚娟的《基于SURF特征的图像与视频拼接技术的研究和实现》,条理清晰、内容完整、实现的技术具有市场价值。因此定下决心以这篇论文为基础脉络,结合实际情况,进行“基于SURF特征的图像与视频拼接技术的研究和实现”。
          一、基于opencv的surf实现
          3.0以后,surf被分到了"opencv_contrib-master"中去,操作起来不习惯,这里仍然选择一直在使用的opencv2.48,其surf的调用方式为:
    // raw_surf.cpp : 本例是对opencv-2.48相关例子的实现
    //
    #include "stdafx.h"
    #include <iostream>
    #include "opencv2/core/core.hpp"
    #include "opencv2/features2d/features2d.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/nonfree/features2d.hpp"
    using namespace std;
    using namespace cv;
    int main( int argc, char** argv )
    {
         
        Mat img_1 = imread( "img_opencv_1.png", 0 );
        Mat img_2 = imread( "img_opencv_2.png", 0 );
        if( !img_1.data || !img_2.data )
        { std::cout<< " --(!) Error reading images " << std::endl; return -1; }
        //-- Step 1: Detect the keypoints using SURF Detector
        int minHessian = 10000;
        SurfFeatureDetector detector( minHessian );
        std::vector<KeyPoint> keypoints_1, keypoints_2;
        detector.detect( img_1, keypoints_1 );
        detector.detect( img_2, keypoints_2 );
        //-- Draw keypoints
        Mat img_keypoints_1; Mat img_keypoints_2;
        drawKeypoints( img_1, keypoints_1, img_keypoints_1, Scalar::all(-1), DrawMatchesFlags::DEFAULT );
        drawKeypoints( img_2, keypoints_2, img_keypoints_2, Scalar::all(-1), DrawMatchesFlags::DEFAULT );
        //-- Step 2: Calculate descriptors (feature vectors)
        SurfDescriptorExtractor extractor;
        Mat descriptors_1, descriptors_2;
        extractor.compute( img_1, keypoints_1, descriptors_1 );
        extractor.compute( img_2, keypoints_2, descriptors_2 );
        //-- Step 3: Matching descriptor vectors with a brute force matcher
        BFMatcher matcher(NORM_L2);
        std::vector< DMatch > matches;
        matcher.match( descriptors_1, descriptors_2, matches );
        //-- Draw matches
        Mat img_matches;
        drawMatches( img_1, keypoints_1, img_2, keypoints_2, matches, img_matches );
        //-- Show detected (drawn) keypoints
        imshow("Keypoints 1", img_keypoints_1 );
        imshow("Keypoints 2", img_keypoints_2 );
        //-- Show detected matches
        imshow("Matches", img_matches );
        waitKey(0);
        return 0;
    }
    这里采用的是surffeaturedector的方法进行点的寻找,而后采用BFMatcher的方法进行数据比对。但这种方法错误的比较多,提供了FLANN的方法进行比对:
    // raw_surf.cpp : 本例是对opencv-2.48相关例子的实现
    //
    #include "stdafx.h"
    #include <iostream>
    #include "opencv2/core/core.hpp"
    #include "opencv2/features2d/features2d.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/nonfree/features2d.hpp"
    using namespace std;
    using namespace cv;
    int main( int argc, char** argv )
    {
         
        Mat img_1 = imread( "img_opencv_1.png", 0 );
        Mat img_2 = imread( "img_opencv_2.png", 0 );
        if( !img_1.data || !img_2.data )
        { std::cout<< " --(!) Error reading images " << std::endl; return -1; }
        //-- Step 1: Detect the keypoints using SURF Detector
        int minHessian = 400;
        SurfFeatureDetector detector( minHessian );
        std::vector<KeyPoint> keypoints_1, keypoints_2;
        detector.detect( img_1, keypoints_1 );
        detector.detect( img_2, keypoints_2 );
        //-- Draw keypoints
        Mat img_keypoints_1; Mat img_keypoints_2;
        drawKeypoints( img_1, keypoints_1, img_keypoints_1, Scalar::all(-1), DrawMatchesFlags::DEFAULT );
        drawKeypoints( img_2, keypoints_2, img_keypoints_2, Scalar::all(-1), DrawMatchesFlags::DEFAULT );
        //-- Step 2: Calculate descriptors (feature vectors)
        SurfDescriptorExtractor extractor;
        Mat descriptors_1, descriptors_2;
        extractor.compute( img_1, keypoints_1, descriptors_1 );
        extractor.compute( img_2, keypoints_2, descriptors_2 );
        //-- Step 3: Matching descriptor vectors using FLANN matcher
        FlannBasedMatcher matcher;
        std::vector< DMatch > matches;
        matcher.match( descriptors_1, descriptors_2, matches );
        double max_dist = 0; double min_dist = 100;
        //-- Quick calculation of max and min distances between keypoints
        forint i = 0; i < descriptors_1.rows; i++ )
        { double dist = matches[i].distance;
        if( dist < min_dist ) min_dist = dist;
        if( dist > max_dist ) max_dist = dist;
        }
        printf("-- Max dist : %f \n", max_dist );
        printf("-- Min dist : %f \n", min_dist );
        //-- Draw only "good" matches (i.e. whose distance is less than 2*min_dist,
        //-- or a small arbitary value ( 0.02 ) in the event that min_dist is very
        //-- small)
        //-- PS.- radiusMatch can also be used here.
        std::vector< DMatch > good_matches;
        forint i = 0; i < descriptors_1.rows; i++ )
        { if( matches[i].distance <= max(2*min_dist, 0.02) )
        { good_matches.push_back( matches[i]); }
        }
        //-- Draw only "good" matches
        Mat img_matches;
        drawMatches( img_1, keypoints_1, img_2, keypoints_2,
            good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
            vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
        //-- Show detected matches
        imshow( "Good Matches", img_matches );
        forint i = 0; i < (int)good_matches.size(); i++ )
        { printf( "-- Good Match [%d] Keypoint 1: %d  -- Keypoint 2: %d  \n", i, good_matches[i].queryIdx, good_matches[i].trainIdx ); }
        waitKey(0);
        return 0;
    }
     
     
    可以发现,除了错误一例,其他都是正确的。
    继续来做,计算出单应矩阵
    // raw_surf.cpp : 本例是对opencv-2.48相关例子的实现
    //
    #include "stdafx.h"
    #include <iostream>
    #include "opencv2/core/core.hpp"
    #include "opencv2/features2d/features2d.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/nonfree/features2d.hpp"
    #include "opencv2/calib3d/calib3d.hpp"
    using namespace std;
    using namespace cv;
    int main( int argc, char** argv )
    {
         
        Mat img_1 = imread( "img_opencv_1.png", 0 );
        Mat img_2 = imread( "img_opencv_2.png", 0 );
        if( !img_1.data || !img_2.data )
        { std::cout<< " --(!) Error reading images " << std::endl; return -1; }
        //-- Step 1: Detect the keypoints using SURF Detector
        int minHessian = 400;
        SurfFeatureDetector detector( minHessian );
        std::vector<KeyPoint> keypoints_1, keypoints_2;
        detector.detect( img_1, keypoints_1 );
        detector.detect( img_2, keypoints_2 );
        //-- Draw keypoints
        Mat img_keypoints_1; Mat img_keypoints_2;
        drawKeypoints( img_1, keypoints_1, img_keypoints_1, Scalar::all(-1), DrawMatchesFlags::DEFAULT );
        drawKeypoints( img_2, keypoints_2, img_keypoints_2, Scalar::all(-1), DrawMatchesFlags::DEFAULT );
        //-- Step 2: Calculate descriptors (feature vectors)
        SurfDescriptorExtractor extractor;
        Mat descriptors_1, descriptors_2;
        extractor.compute( img_1, keypoints_1, descriptors_1 );
        extractor.compute( img_2, keypoints_2, descriptors_2 );
        //-- Step 3: Matching descriptor vectors using FLANN matcher
        FlannBasedMatcher matcher;
        std::vector< DMatch > matches;
        matcher.match( descriptors_1, descriptors_2, matches );
        double max_dist = 0; double min_dist = 100;
        //-- Quick calculation of max and min distances between keypoints
        forint i = 0; i < descriptors_1.rows; i++ )
        { double dist = matches[i].distance;
        if( dist < min_dist ) min_dist = dist;
        if( dist > max_dist ) max_dist = dist;
        }
        printf("-- Max dist : %f \n", max_dist );
        printf("-- Min dist : %f \n", min_dist );
        //-- Draw only "good" matches (i.e. whose distance is less than 2*min_dist,
        //-- or a small arbitary value ( 0.02 ) in the event that min_dist is very
        //-- small)
        //-- PS.- radiusMatch can also be used here.
        std::vector< DMatch > good_matches;
        forint i = 0; i < descriptors_1.rows; i++ )
        { if( matches[i].distance <= /*max(2*min_dist, 0.02)*/3*min_dist )
        { good_matches.push_back( matches[i]); }
        }
        //-- Draw only "good" matches
        Mat img_matches;
        drawMatches( img_1, keypoints_1, img_2, keypoints_2,
            good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
            vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
        //-- Localize the object from img_1 in img_2
        std::vector<Point2f> obj;
        std::vector<Point2f> scene;
        forint i = 0; i < (int)good_matches.size(); i++ )
        {    
            obj.push_back( keypoints_1[ good_matches[i].queryIdx ].pt );
            scene.push_back( keypoints_2[ good_matches[i].trainIdx ].pt );
            printf( "-- Good Match [%d] Keypoint 1: %d  -- Keypoint 2: %d  \n", i, good_matches[i].queryIdx, good_matches[i].trainIdx ); 
        }
        //直接调用ransac
        Mat H = findHomography( obj, scene, CV_RANSAC );
        //-- Get the corners from the image_1 ( the object to be "detected" )
        std::vector<Point2f> obj_corners(4);
        obj_corners[0] = Point(0,0); obj_corners[1] = Point( img_1.cols, 0 );
        obj_corners[2] = Point( img_1.cols, img_1.rows ); obj_corners[3] = Point( 0, img_1.rows );
        std::vector<Point2f> scene_corners(4);
        perspectiveTransform( obj_corners, scene_corners, H);
        //-- Draw lines between the corners (the mapped object in the scene - image_2 )
        Point2f offset( (float)img_1.cols, 0);
        line( img_matches, scene_corners[0] + offset, scene_corners[1] + offset, Scalar(0, 255, 0), 4 );
        line( img_matches, scene_corners[1] + offset, scene_corners[2] + offset, Scalar( 0, 255, 0), 4 );
        line( img_matches, scene_corners[2] + offset, scene_corners[3] + offset, Scalar( 0, 255, 0), 4 );
        line( img_matches, scene_corners[3] + offset, scene_corners[0] + offset, Scalar( 0, 255, 0), 4 );
        //-- Show detected matches
        imshow( "Good Matches & Object detection", img_matches );
        waitKey(0);
        return 0;
    }
     
     
    简化后和注释后的版本
    // raw_surf.cpp : 本例是对opencv-2.48相关例子的实现
    //
     
    #include "stdafx.h"
    #include <iostream>
    #include "opencv2/core/core.hpp"
    #include "opencv2/features2d/features2d.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/nonfree/features2d.hpp"
    #include "opencv2/calib3d/calib3d.hpp"
    using namespace std;
    using namespace cv;
     
    int main( int argc, char** argv )
    {
     
        Mat img_1 = imread( "img_opencv_1.png", 0 );
        Mat img_2 = imread( "img_opencv_2.png", 0 );
        if( !img_1.data || !img_2.data )
        { std::cout<< " --(!) Error reading images " << std::endl; return -1; }
     
        //-- Step 1: 使用SURF识别出特征点
        int minHessian = 400;
        SurfFeatureDetector detector( minHessian );
        std::vector<KeyPoint> keypoints_1, keypoints_2;
        detector.detect( img_1, keypoints_1 );
        detector.detect( img_2, keypoints_2 );
        //-- Step 2: 描述SURF特征
        SurfDescriptorExtractor extractor;
        Mat descriptors_1, descriptors_2;
        extractor.compute( img_1, keypoints_1, descriptors_1 );
        extractor.compute( img_2, keypoints_2, descriptors_2 );
        //-- Step 3: 匹配
        FlannBasedMatcher matcher;//BFMatcher为强制匹配
        std::vector< DMatch > matches;
        matcher.match( descriptors_1, descriptors_2, matches );
        //取最大最小距离
        double max_dist = 0; double min_dist = 100;
        for( int i = 0; i < descriptors_1.rows; i++ )
        { 
            double dist = matches[i].distance;
            if( dist < min_dist ) min_dist = dist;
            if( dist > max_dist ) max_dist = dist;
        }
        std::vector< DMatch > good_matches;
        for( int i = 0; i < descriptors_1.rows; i++ )
        { 
            if( matches[i].distance <= 3*min_dist )//这里的阈值选择了3倍的min_dist
                { 
                    good_matches.push_back( matches[i]); 
                 }
        }
        //画出"good match"
        Mat img_matches;
        drawMatches( img_1, keypoints_1, img_2, keypoints_2,
            good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
            vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
        //-- Localize the object from img_1 in img_2
        std::vector<Point2f> obj;
        std::vector<Point2f> scene;
        for( int i = 0; i < (int)good_matches.size(); i++ )
        {    
            obj.push_back( keypoints_1[ good_matches[i].queryIdx ].pt );
            scene.push_back( keypoints_2[ good_matches[i].trainIdx ].pt );
        }
        //直接调用ransac,计算单应矩阵
        Mat H = findHomography( obj, scene, CV_RANSAC );
        //-- Get the corners from the image_1 ( the object to be "detected" )
        std::vector<Point2f> obj_corners(4);
        obj_corners[0] = Point(0,0); 
        obj_corners[1] = Point( img_1.cols, 0 );
        obj_corners[2] = Point( img_1.cols, img_1.rows ); 
        obj_corners[3] = Point( 0, img_1.rows );
        std::vector<Point2f> scene_corners(4);
        perspectiveTransform( obj_corners, scene_corners, H);
        //-- Draw lines between the corners (the mapped object in the scene - image_2 )
        Point2f offset( (float)img_1.cols, 0);
        line( img_matches, scene_corners[0] + offset, scene_corners[1] + offset, Scalar(0, 255, 0), 4 );
        line( img_matches, scene_corners[1] + offset, scene_corners[2] + offset, Scalar( 0, 255, 0), 4 );
        line( img_matches, scene_corners[2] + offset, scene_corners[3] + offset, Scalar( 0, 255, 0), 4 );
        line( img_matches, scene_corners[3] + offset, scene_corners[0] + offset, Scalar( 0, 255, 0), 4 );
        //-- Show detected matches
        imshow( "Good Matches & Object detection", img_matches );
        waitKey(0);
        return 0;
    }
     
     
     
     
    这里有两点需要注意,一个是除了FlannBasedMatcher之外,还有一种mathcer叫做BFMatcher,后者为强制匹配.
    此外计算所谓GOODFEATURE的时候,采用了 3*min_dist的方法,我认为这里和论文中指出的“误差阈值设为3”是一致的,如果理解错误请指出,感谢!
    同时测试了航拍图片和连铸图片,航拍图片是自然图片,特征丰富;
    连铸图片由于表面干扰大于原始纹理,无法得到单应矩阵
            最后,添加计算RANSAC内点外点的相关代码,这里以3作为分界线
           // raw_surf.cpp : 本例是对opencv-2.48相关例子的实现
    //
    #include "stdafx.h"
    #include <iostream>
    #include "opencv2/core/core.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/features2d/features2d.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/nonfree/features2d.hpp"
    #include "opencv2/calib3d/calib3d.hpp"
    using namespace std;
    using namespace cv;
    //获得两个pointf之间的距离
    float fDistance(Point2f p1,Point2f p2)
    {
        float ftmp = (p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y);
        ftmp = sqrt((float)ftmp);
        return ftmp;
    }
    int main( int argc, char** argv )
    {
        Mat img_1 = imread( "img_opencv_1.png", 0 );
        Mat img_2 = imread( "img_opencv_2.png", 0 );
        ////添加于连铸图像
        //img_1 = img_1(Rect(20,0,img_1.cols-40,img_1.rows));
        //img_2 = img_2(Rect(20,0,img_1.cols-40,img_1.rows));
     //    cv::Canny(img_1,img_1,100,200);
     //    cv::Canny(img_2,img_2,100,200);
        if( !img_1.data || !img_2.data )
        { std::cout<< " --(!) Error reading images " << std::endl; return -1; }
        //-- Step 1: 使用SURF识别出特征点
        int minHessian = 400;
        SurfFeatureDetector detector( minHessian );
        std::vector<KeyPoint> keypoints_1, keypoints_2;
        detector.detect( img_1, keypoints_1 );
        detector.detect( img_2, keypoints_2 );
        //-- Step 2: 描述SURF特征
        SurfDescriptorExtractor extractor;
        Mat descriptors_1, descriptors_2;
        extractor.compute( img_1, keypoints_1, descriptors_1 );
        extractor.compute( img_2, keypoints_2, descriptors_2 );
        //-- Step 3: 匹配
        FlannBasedMatcher matcher;//BFMatcher为强制匹配
        std::vector< DMatch > matches;
        matcher.match( descriptors_1, descriptors_2, matches );
        //取最大最小距离
        double max_dist = 0; double min_dist = 100;
        forint i = 0; i < descriptors_1.rows; i++ )
        { 
            double dist = matches[i].distance;
            if( dist < min_dist ) min_dist = dist;
            if( dist > max_dist ) max_dist = dist;
        }
        std::vector< DMatch > good_matches;
        forint i = 0; i < descriptors_1.rows; i++ )
        { 
            if( matches[i].distance <= 3*min_dist )//这里的阈值选择了3倍的min_dist
                { 
                    good_matches.push_back( matches[i]); 
                 }
        }
        //画出"good match"
        Mat img_matches;
        drawMatches( img_1, keypoints_1, img_2, keypoints_2,
            good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
            vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
        //-- Localize the object from img_1 in img_2
        std::vector<Point2f> obj;
        std::vector<Point2f> scene;
        forint i = 0; i < (int)good_matches.size(); i++ )
        {    
            obj.push_back( keypoints_1[ good_matches[i].queryIdx ].pt );
            scene.push_back( keypoints_2[ good_matches[i].trainIdx ].pt );
        }
        //直接调用ransac,计算单应矩阵
        Mat H = findHomography( obj, scene, CV_RANSAC );
        //-- Get the corners from the image_1 ( the object to be "detected" )
        std::vector<Point2f> obj_corners(4);
        obj_corners[0] = Point(0,0); 
        obj_corners[1] = Point( img_1.cols, 0 );
        obj_corners[2] = Point( img_1.cols, img_1.rows ); 
        obj_corners[3] = Point( 0, img_1.rows );
        std::vector<Point2f> scene_corners(4);
        perspectiveTransform( obj_corners, scene_corners, H);
        //计算内点外点
        std::vector<Point2f> scene_test(obj.size());
        perspectiveTransform(obj,scene_test,H);
        for (int i=0;i<scene_test.size();i++)
        {
           printf("%d is %f \n",i+1,fDistance(scene[i],scene_test[i]));
        }
        
        //-- Draw lines between the corners (the mapped object in the scene - image_2 )
        Point2f offset( (float)img_1.cols, 0);
        line( img_matches, scene_corners[0] + offset, scene_corners[1] + offset, Scalar(0, 255, 0), 4 );
        line( img_matches, scene_corners[1] + offset, scene_corners[2] + offset, Scalar( 0, 255, 0), 4 );
        line( img_matches, scene_corners[2] + offset, scene_corners[3] + offset, Scalar( 0, 255, 0), 4 );
        line( img_matches, scene_corners[3] + offset, scene_corners[0] + offset, Scalar( 0, 255, 0), 4 );
        //-- Show detected matches
        imshow( "Good Matches & Object detection", img_matches );
        waitKey(0);
        return 0;
    }
     
     
            结果显示
           其中,有误差的点就很明显了。
           小结一下,这里实现了使用opencv得到两幅图像之间的单应矩阵的方法。不是所有的图像都能够获得单应矩阵的,必须是两幅本身就有关系的图片才可以;而且最好是自然图像,像生产线上的这种图像,其拼接就需要采用其他方法。
    二、拼接和融合
            由于之前已经计算出了“单应矩阵”,所以这里直接利用这个矩阵就好。需要注意的一点是理清楚“帧”和拼接图像之间的关系。一般来说,我们采用的是“柱面坐标”或平面坐标。书中采用的是若干图像在水平方向上基本上是一字排开,是平面坐标。那么,如果按照文中的“帧到拼接图像”的方法,我们认为图像拼接的顺序就是由左到右,一幅一幅地计算误差,而后进行叠加。
             为了方便说明算法,采用了《学习opencv》中提供的教堂图像
    其结果就是经过surf匹配,而将右边的图像形变成为适合叠加的状态。
    基于此,进行图像对准
    // raw_surf.cpp : 本例是对opencv-2.48相关例子的实现
    //
    #include "stdafx.h"
    #include <iostream>
    #include "opencv2/core/core.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/features2d/features2d.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/nonfree/features2d.hpp"
    #include "opencv2/calib3d/calib3d.hpp"
    using namespace std;
    using namespace cv;
    int main( int argc, char** argv )
    {
        
        Mat img_1 ;
        Mat img_2 ;
        Mat img_raw_1 = imread("c1.bmp");
        Mat img_raw_2 = imread("c3.bmp");
        cvtColor(img_raw_1,img_1,CV_BGR2GRAY);
        cvtColor(img_raw_2,img_2,CV_BGR2GRAY);
        //-- Step 1: 使用SURF识别出特征点
        int minHessian = 400;
        SurfFeatureDetector detector( minHessian );
        std::vector<KeyPoint> keypoints_1, keypoints_2;
        detector.detect( img_1, keypoints_1 );
        detector.detect( img_2, keypoints_2 );
        //-- Step 2: 描述SURF特征
        SurfDescriptorExtractor extractor;
        Mat descriptors_1, descriptors_2;
        extractor.compute( img_1, keypoints_1, descriptors_1 );
        extractor.compute( img_2, keypoints_2, descriptors_2 );
        //-- Step 3: 匹配
        FlannBasedMatcher matcher;//BFMatcher为强制匹配
        std::vector< DMatch > matches;
        matcher.match( descriptors_1, descriptors_2, matches );
        //取最大最小距离
        double max_dist = 0; double min_dist = 100;
        forint i = 0; i < descriptors_1.rows; i++ )
        { 
            double dist = matches[i].distance;
            if( dist < min_dist ) min_dist = dist;
            if( dist > max_dist ) max_dist = dist;
        }
        std::vector< DMatch > good_matches;
        forint i = 0; i < descriptors_1.rows; i++ )
        { 
            if( matches[i].distance <= 3*min_dist )//这里的阈值选择了3倍的min_dist
            { 
                good_matches.push_back( matches[i]); 
            }
        }
        //-- Localize the object from img_1 in img_2
        std::vector<Point2f> obj;
        std::vector<Point2f> scene;
        forint i = 0; i < (int)good_matches.size(); i++ )
        {    
            //这里采用“帧向拼接图像中添加的方法”,因此左边的是scene,右边的是obj
            scene.push_back( keypoints_1[ good_matches[i].queryIdx ].pt );
            obj.push_back( keypoints_2[ good_matches[i].trainIdx ].pt );
        }
        //直接调用ransac,计算单应矩阵
        Mat H = findHomography( obj, scene, CV_RANSAC );
        //图像对准
        Mat result;
        warpPerspective(img_raw_2,result,H,Size(2*img_2.cols,img_2.rows));
        Mat half(result,cv::Rect(0,0,img_2.cols,img_2.rows));
        img_raw_1.copyTo(half);
        imshow("result",result);
        waitKey(0);
        return 0;
    }
     
     
    依据论文中提到的3种方法进行融合
    // raw_surf.cpp : 本例是对opencv-2.48相关例子的实现
    //
    #include "stdafx.h"
    #include <iostream>
    #include "opencv2/core/core.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/features2d/features2d.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/nonfree/features2d.hpp"
    #include "opencv2/calib3d/calib3d.hpp"
    using namespace std;
    using namespace cv;
    int main( int argc, char** argv )
    {
        
        Mat img_1 ;
        Mat img_2 ;
        Mat img_raw_1 = imread("c1.bmp");
        Mat img_raw_2 = imread("c3.bmp");
        cvtColor(img_raw_1,img_1,CV_BGR2GRAY);
        cvtColor(img_raw_2,img_2,CV_BGR2GRAY);
        //-- Step 1: 使用SURF识别出特征点
        int minHessian = 400;
        SurfFeatureDetector detector( minHessian );
        std::vector<KeyPoint> keypoints_1, keypoints_2;
        detector.detect( img_1, keypoints_1 );
        detector.detect( img_2, keypoints_2 );
        //-- Step 2: 描述SURF特征
        SurfDescriptorExtractor extractor;
        Mat descriptors_1, descriptors_2;
        extractor.compute( img_1, keypoints_1, descriptors_1 );
        extractor.compute( img_2, keypoints_2, descriptors_2 );
        //-- Step 3: 匹配
        FlannBasedMatcher matcher;//BFMatcher为强制匹配
        std::vector< DMatch > matches;
        matcher.match( descriptors_1, descriptors_2, matches );
        //取最大最小距离
        double max_dist = 0; double min_dist = 100;
        forint i = 0; i < descriptors_1.rows; i++ )
        { 
            double dist = matches[i].distance;
            if( dist < min_dist ) min_dist = dist;
            if( dist > max_dist ) max_dist = dist;
        }
        std::vector< DMatch > good_matches;
        forint i = 0; i < descriptors_1.rows; i++ )
        { 
            if( matches[i].distance <= 3*min_dist )//这里的阈值选择了3倍的min_dist
            { 
                good_matches.push_back( matches[i]); 
            }
        }
        //-- Localize the object from img_1 in img_2
        std::vector<Point2f> obj;
        std::vector<Point2f> scene;
        forint i = 0; i < (int)good_matches.size(); i++ )
        {    
            //这里采用“帧向拼接图像中添加的方法”,因此左边的是scene,右边的是obj
            scene.push_back( keypoints_1[ good_matches[i].queryIdx ].pt );
            obj.push_back( keypoints_2[ good_matches[i].trainIdx ].pt );
        }
        //直接调用ransac,计算单应矩阵
        Mat H = findHomography( obj, scene, CV_RANSAC );
        //图像对准
        Mat result;
        Mat resultback; //保存的是新帧经过单应矩阵变换以后的图像
        warpPerspective(img_raw_2,result,H,Size(2*img_2.cols,img_2.rows));
        result.copyTo(resultback);
        Mat half(result,cv::Rect(0,0,img_2.cols,img_2.rows));
        img_raw_1.copyTo(half);
        imshow("ajust",result);
        //渐入渐出融合
        Mat result_linerblend = result.clone();
         double dblend = 0.0;
         int ioffset =img_2.cols-100;
         for (int i = 0;i<100;i++)
         {              
             result_linerblend.col(ioffset+i) = result.col(ioffset+i)*(1-dblend) + resultback.col(ioffset+i)*dblend;
             dblend = dblend +0.01;
        }
        imshow("result_linerblend",result_linerblend);
        //最大值法融合
        Mat result_maxvalue = result.clone();
        for (int i = 0;i<img_2.rows;i++)
        {     
            for (int j=0;j<100;j++)
            {
                int iresult= result.at<Vec3b>(i,ioffset+j)[0]+ result.at<Vec3b>(i,ioffset+j)[1]+ result.at<Vec3b>(i,ioffset+j)[2];
                int iresultback = resultback.at<Vec3b>(i,ioffset+j)[0]+ resultback.at<Vec3b>(i,ioffset+j)[1]+ resultback.at<Vec3b>(i,ioffset+j)[2];
                if (iresultback >iresult)
                {
                    result_maxvalue.at<Vec3b>(i,ioffset+j) = resultback.at<Vec3b>(i,ioffset+j);
                }
            }
        }
        imshow("result_maxvalue",result_maxvalue);
        //带阈值的加权平滑处理
        Mat result_advance = result.clone();
        for (int i = 0;i<img_2.rows;i++)
        {  
            for (int j = 0;j<33;j++)
            {   
                int iimg1= result.at<Vec3b>(i,ioffset+j)[0]+ result.at<Vec3b>(i,ioffset+j)[1]+ result.at<Vec3b>(i,ioffset+j)[2];
                //int iimg2= resultback.at<Vec3b>(i,ioffset+j)[0]+ resultback.at<Vec3b>(i,ioffset+j)[1]+ resultback.at<Vec3b>(i,ioffset+j)[2];
                int ilinerblend = result_linerblend.at<Vec3b>(i,ioffset+j)[0]+ result_linerblend.at<Vec3b>(i,ioffset+j)[1]+ result_linerblend.at<Vec3b>(i,ioffset+j)[2];
                if (abs(iimg1 - ilinerblend)<3)
                {
                    result_advance.at<Vec3b>(i,ioffset+j) = result_linerblend.at<Vec3b>(i,ioffset+j);
                }
            }
        }
        for (int i = 0;i<img_2.rows;i++)
        {  
            for (int j = 33;j<66;j++)
            {   
                int iimg1= result.at<Vec3b>(i,ioffset+j)[0]+ result.at<Vec3b>(i,ioffset+j)[1]+ result.at<Vec3b>(i,ioffset+j)[2];
                int iimg2= resultback.at<Vec3b>(i,ioffset+j)[0]+ resultback.at<Vec3b>(i,ioffset+j)[1]+ resultback.at<Vec3b>(i,ioffset+j)[2];
                int ilinerblend = result_linerblend.at<Vec3b>(i,ioffset+j)[0]+ result_linerblend.at<Vec3b>(i,ioffset+j)[1]+ result_linerblend.at<Vec3b>(i,ioffset+j)[2];
                if (abs(max(iimg1,iimg2) - ilinerblend)<3)
                {
                    result_advance.at<Vec3b>(i,ioffset+j) = result_linerblend.at<Vec3b>(i,ioffset+j);
                }
                else if (iimg2>iimg1)
                {
                    result_advance.at<Vec3b>(i,ioffset+j) = resultback.at<Vec3b>(i,ioffset+j);
                }
            }
        }
        for (int i = 0;i<img_2.rows;i++)
        {  
            for (int j = 66;j<100;j++)
            {   
                //int iimg1= result.at<Vec3b>(i,ioffset+j)[0]+ result.at<Vec3b>(i,ioffset+j)[1]+ result.at<Vec3b>(i,ioffset+j)[2];
                int iimg2= resultback.at<Vec3b>(i,ioffset+j)[0]+ resultback.at<Vec3b>(i,ioffset+j)[1]+ resultback.at<Vec3b>(i,ioffset+j)[2];
                int ilinerblend = result_linerblend.at<Vec3b>(i,ioffset+j)[0]+ result_linerblend.at<Vec3b>(i,ioffset+j)[1]+ result_linerblend.at<Vec3b>(i,ioffset+j)[2];
                if (abs(iimg2 - ilinerblend)<3)
                {
                    result_advance.at<Vec3b>(i,ioffset+j) = result_linerblend.at<Vec3b>(i,ioffset+j);
                }
                else
                {
                    result_advance.at<Vec3b>(i,ioffset+j) = resultback.at<Vec3b>(i,ioffset+j);
                }
            }
        }
        imshow("result_advance",result_advance);
        waitKey(0);
        return 0;
    }
     
     
    目前看来,maxvalue是最好的融合方法,但是和论文中提到的一样,此类图片不能很好地体现融合算法的特点,为此我也拍摄了和论文中类似的图片。发现想拍摄质量较好的图片,还是需要一定的硬件和技巧的。因此,软件和硬件,在使用的过程中应该结合起来。
    此外,使用文中图片,效果如下
    换一组图片,可以发现不同的结果
    相比较而言,还是linerblend能够保持不错的质量,而具体到底采取哪种拼接的方式,必须根据实际情况来选择。
    三、多图连续融合拼接
            前面处理的是2图的例子,至少将这种情况推广到3图,这样才能够得到统一处理的经验。
            连续图像处理,不仅仅是在已经处理好的图像上面再添加一幅图,其中比较关键的一点就是如何来处理已经拼接好的图像。
    那么,m2也就是H.at<char>(0,2)就是水平位移。但是在实际使用中,始终无法正确取得这个值
    Mat outImage =H.clone();
        uchar* outData=outImage.ptr<uchar>(0);
        int itemp = outData[2];     //获得偏移
        line(result_linerblend,Point(result_linerblend.cols-itemp,0),Point(result_linerblend.cols-itemp,img_2.rows),Scalar(255,255,255),2);
        imshow("result_linerblend",result_linerblend);
    只好采取编写专门代码的方法进行处理
    //获取已经处理图像的边界
        Mat matmask = result_linerblend.clone();
        int idaterow0 = 0;int idaterowend = 0;//标识了最上面和最小面第一个不为0的树,这里采用的是宽度减去的算法
        for(int j=matmask.cols-1;j>=0;j--)
        {          
            if (matmask.at<Vec3b>(0,j)[0]>0)
            {
                idaterow0 = j;
                break;
            }
        }
         for(int j=matmask.cols-1;j>=0;j--)
        {            
            if (matmask.at<Vec3b>(matmask.rows-1,j)[0]>0)
            {
                idaterowend = j;
                break;
            }
        }
        
        line(matmask,Point(min(idaterow0,idaterowend),0),Point(min(idaterow0,idaterowend),img_2.rows),Scalar(255,255,255),2);
        imshow("result_linerblend",matmask);
    效果良好稳定.目前的实现是将白线以左的区域切割下来进行拼接。
    基于此,编写3图拼接,效果如下。目前的图像质量,在差值上面可能还需要增强,下一步处理
    // blend_series.cpp : 多图拼接
    //
    #include "stdafx.h"
    #include <iostream>
    #include "opencv2/core/core.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/features2d/features2d.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/nonfree/features2d.hpp"
    #include "opencv2/calib3d/calib3d.hpp"
    using namespace std;
    using namespace cv;
    int main( int argc, char** argv )
    {
        Mat img_1 ;
        Mat img_2 ;
        Mat img_raw_1 = imread("Univ3.jpg");
        Mat img_raw_2 = imread("Univ2.jpg");
        cvtColor(img_raw_1,img_1,CV_BGR2GRAY);
        cvtColor(img_raw_2,img_2,CV_BGR2GRAY);
        //-- Step 1: 使用SURF识别出特征点
        int minHessian = 400;
        SurfFeatureDetector detector( minHessian );
        std::vector<KeyPoint> keypoints_1, keypoints_2;
        detector.detect( img_1, keypoints_1 );
        detector.detect( img_2, keypoints_2 );
        //-- Step 2: 描述SURF特征
        SurfDescriptorExtractor extractor;
        Mat descriptors_1, descriptors_2;
        extractor.compute( img_1, keypoints_1, descriptors_1 );
        extractor.compute( img_2, keypoints_2, descriptors_2 );
        //-- Step 3: 匹配
        FlannBasedMatcher matcher;//BFMatcher为强制匹配
        std::vector< DMatch > matches;
        matcher.match( descriptors_1, descriptors_2, matches );
        //取最大最小距离
        double max_dist = 0; double min_dist = 100;
        forint i = 0; i < descriptors_1.rows; i++ )
        { 
            double dist = matches[i].distance;
            if( dist < min_dist ) min_dist = dist;
            if( dist > max_dist ) max_dist = dist;
        }
        std::vector< DMatch > good_matches;
        forint i = 0; i < descriptors_1.rows; i++ )
        { 
            if( matches[i].distance <= 3*min_dist )//这里的阈值选择了3倍的min_dist
            { 
                good_matches.push_back( matches[i]); 
            }
        }
        //-- Localize the object from img_1 in img_2
        std::vector<Point2f> obj;
        std::vector<Point2f> scene;
        forint i = 0; i < (int)good_matches.size(); i++ )
        {    
            //这里采用“帧向拼接图像中添加的方法”,因此左边的是scene,右边的是obj
            scene.push_back( keypoints_1[ good_matches[i].queryIdx ].pt );
            obj.push_back( keypoints_2[ good_matches[i].trainIdx ].pt );
        }
        //直接调用ransac,计算单应矩阵
        Mat H = findHomography( obj, scene, CV_RANSAC );
        //图像对准
        Mat result;
        Mat resultback; //保存的是新帧经过单应矩阵变换以后的图像
        warpPerspective(img_raw_2,result,H,Size(2*img_2.cols,img_2.rows));
        result.copyTo(resultback);
        Mat half(result,cv::Rect(0,0,img_2.cols,img_2.rows));
        img_raw_1.copyTo(half);
        //imshow("ajust",result);
        //渐入渐出融合
        Mat result_linerblend = result.clone();
        double dblend = 0.0;
        int ioffset =img_2.cols-100;
        for (int i = 0;i<100;i++)
        {              
            result_linerblend.col(ioffset+i) = result.col(ioffset+i)*(1-dblend) + resultback.col(ioffset+i)*dblend;
            dblend = dblend +0.01;
        }
        //获取已经处理图像的边界
        Mat matmask = result_linerblend.clone();
        int idaterow0 = 0;int idaterowend = 0;//标识了最上面和最小面第一个不为0的树,这里采用的是宽度减去的算法
        for(int j=matmask.cols-1;j>=0;j--)
        {          
            if (matmask.at<Vec3b>(0,j)[0]>0)
            {
                idaterow0 = j;
                break;
            }
        }
         for(int j=matmask.cols-1;j>=0;j--)
        {            
            if (matmask.at<Vec3b>(matmask.rows-1,j)[0]>0)
            {
                idaterowend = j;
                break;
            }
        }
        
        line(matmask,Point(min(idaterow0,idaterowend),0),Point(min(idaterow0,idaterowend),img_2.rows),Scalar(255,255,255),2);
        imshow("result_linerblend",matmask);
        /////////////////---------------对结果图像继续处理---------------------------------/////////////////
        img_raw_1 = result_linerblend(Rect(0,0,min(idaterow0,idaterowend),img_2.rows));
        img_raw_2 = imread("Univ1.jpg");
        cvtColor(img_raw_1,img_1,CV_BGR2GRAY);
        cvtColor(img_raw_2,img_2,CV_BGR2GRAY);
        ////-- Step 1: 使用SURF识别出特征点
        //
        SurfFeatureDetector detector2( minHessian );
        keypoints_1.clear();
        keypoints_2.clear();
        detector2.detect( img_1, keypoints_1 );
        detector2.detect( img_2, keypoints_2 );
        //-- Step 2: 描述SURF特征
        SurfDescriptorExtractor extractor2;
        extractor2.compute( img_1, keypoints_1, descriptors_1 );
        extractor2.compute( img_2, keypoints_2, descriptors_2 );
        //-- Step 3: 匹配
        FlannBasedMatcher matcher2;//BFMatcher为强制匹配
        matcher2.match( descriptors_1, descriptors_2, matches );
        //取最大最小距离
         max_dist = 0;  min_dist = 100;
        forint i = 0; i < descriptors_1.rows; i++ )
        { 
            double dist = matches[i].distance;
            if( dist < min_dist ) min_dist = dist;
            if( dist > max_dist ) max_dist = dist;
        }
        good_matches.clear();
        forint i = 0; i < descriptors_1.rows; i++ )
        { 
            if( matches[i].distance <= 3*min_dist )//这里的阈值选择了3倍的min_dist
            { 
                good_matches.push_back( matches[i]); 
            }
        }
        //-- Localize the object from img_1 in img_2
        obj.clear();
        scene.clear();
        forint i = 0; i < (int)good_matches.size(); i++ )
        {    
            //这里采用“帧向拼接图像中添加的方法”,因此左边的是scene,右边的是obj
            scene.push_back( keypoints_1[ good_matches[i].queryIdx ].pt );
            obj.push_back( keypoints_2[ good_matches[i].trainIdx ].pt );
        }
        //直接调用ransac,计算单应矩阵
         H = findHomography( obj, scene, CV_RANSAC );
        //图像对准
        warpPerspective(img_raw_2,result,H,Size(img_1.cols+img_2.cols,img_2.rows));
        result.copyTo(resultback);
        Mat half2(result,cv::Rect(0,0,img_1.cols,img_1.rows));
        img_raw_1.copyTo(half2);
        imshow("ajust",result);
        //渐入渐出融合
        result_linerblend = result.clone();
         dblend = 0.0;
         ioffset =img_1.cols-100;
        for (int i = 0;i<100;i++)
        {              
            result_linerblend.col(ioffset+i) = result.col(ioffset+i)*(1-dblend) + resultback.col(ioffset+i)*dblend;
            dblend = dblend +0.01;
        }
        imshow("result_linerblend",result_linerblend);
        waitKey(0);
        return 0;
    }
     
     
    复制粘贴,实现5图拼接。这个时候发现,3图往往是一个极限值(这也可能就是为什么opencv里面的例子提供的是3图),当第四图出现的时候,其单应效果非常差
    为什么会出现这种情况,反思后认识到,论文中采用的是平面坐标,也就是所有的图片都是基本位于一个平面上的,这一点特别通过她后面的那个罗技摄像头的部署能够看出来。但是在现实中,更常见的情况是人站在中间,360度地拍摄,这个时候需要采用柱面坐标系,也就是一开始对于图像要进行相关处理,也就是所谓的柱状投影。
    可以得到这样的效果,这个效果是否正确还有待商榷,但是基于此的确可以更进一步地做东西了。
    // column_transoform.cpp : 桶装投影
    //
    #include "stdafx.h"
    #include <iostream>
    #include "opencv2/core/core.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/features2d/features2d.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/nonfree/features2d.hpp"
    #include "opencv2/calib3d/calib3d.hpp"
    using namespace std;
    using namespace cv;
    #define  PI 3.14159
     
    int main( int argc, char** argv )
    {
        Mat img_1 = imread( "Univ1.jpg");
        Mat img_result = img_1.clone();
        for(int i=0;i<img_result.rows;i++)
        {        for(int j=0;j<img_result.cols;j++)
            {     
                img_result.at<Vec3b>(i,j)=0;
            }
        }
        
        int W = img_1.cols;
        int H = img_1.rows;
        float r = W/(2*tan(PI/6));
        float k = 0;
        float fx=0;
        float fy=0;
        for(int i=0;i<img_1.rows;i++)
        {        for(int j=0;j<img_1.cols;j++)
            {     
                k = sqrt((float)(r*r+(W/2-j)*(W/2-j)));
                fx = r*sin(PI/6)+r*sin(atan((j -W/2 )/r));
                fy = H/2 +r*(i-H/2)/k;
                int ix = (int)fx;
                int iy = (int)fy;
                if (ix<W&&ix>=0&&iy<H&&iy>=0)
                {
                    img_result.at<Vec3b>(iy,ix)= img_1.at<Vec3b>(i,j);
                     
                }
                
            }
        }
        
        imshow( "桶状投影", img_1 );
        imshow("img_result",img_result);
        waitKey(0);
        return 0;
    }
     
     
     
    效果依然是不佳,看来在这个地方,不仅仅是做一个桶形变换那么简单,一定有定量的参数在里面,也可能是我的变换写错了。这个下一步研究。
    【未完待续】
    http://www.cnblogs.com/jsxyhelu/p/4475809.html

    http://blog.csdn.net/jh19871985/article/details/8477935

    转载于:https://www.cnblogs.com/pengkunfan/p/4344055.html

    展开全文
  • 图像拼接在实际的应用场景很广,比如无人机航拍,遥感图像等等,图像拼接是进一步做图像理解基础步骤,拼接效果的好坏直接影响接下来的工作,所以一个好的图像拼接算法非常重要。 再举一个身边的例子吧,你用你的...

     图像拼接在实际的应用场景很广,比如无人机航拍,遥感图像等等,图像拼接是进一步做图像理解基础步骤,拼接效果的好坏直接影响接下来的工作,所以一个好的图像拼接算法非常重要。

    再举一个身边的例子吧,你用你的手机对某一场景拍照,但是你没有办法一次将所有你要拍的景物全部拍下来,所以你对该场景从左往右依次拍了好几张图,来把你要拍的所有景物记录下来。那么我们能不能把这些图像拼接成一个大图呢?我们利用opencv就可以做到图像拼接的效果!

    比如我们有对这两张图进行拼接。

    从上面两张图可以看出,这两张图有比较多的重叠部分,这也是拼接的基本要求。

    那么要实现图像拼接需要那几步呢?简单来说有以下几步:

    1. 对每幅图进行特征点提取
    2. 对对特征点进行匹配
    3. 进行图像配准
    4. 把图像拷贝到另一幅图像的特定位置
    5. 对重叠边界进行特殊处理

    好吧,那就开始正式实现图像配准。

    第一步就是特征点提取。现在CV领域有很多特征点的定义,比如sift、surf、harris角点、ORB都是很有名的特征因子,都可以用来做图像拼接的工作,他们各有优势。本文将使用ORB和SURF进行图像拼接,用其他方法进行拼接也是类似的。

     

    基于SURF的图像拼接

    用SIFT算法来实现图像拼接是很常用的方法,但是因为SIFT计算量很大,所以在速度要求很高的场合下不再适用。所以,它的改进方法SURF因为在速度方面有了明显的提高(速度是SIFT的3倍),所以在图像拼接领域还是大有作为。虽说SURF精确度和稳定性不及SIFT,但是其综合能力还是优越一些。下面将详细介绍拼接的主要步骤。

    1.特征点提取和匹配

    特征点提取和匹配的方法我在上一篇文章《OpenCV探索之路(二十三):特征检测和特征匹配方法汇总》中做了详细的介绍,在这里直接使用上文所总结的SURF特征提取和特征匹配的方法。

    //提取特征点    
    SurfFeatureDetector Detector(2000);  
    vector<KeyPoint> keyPoint1, keyPoint2;
    Detector.detect(image1, keyPoint1);
    Detector.detect(image2, keyPoint2);
    
    //特征点描述,为下边的特征点匹配做准备    
    SurfDescriptorExtractor Descriptor;
    Mat imageDesc1, imageDesc2;
    Descriptor.compute(image1, keyPoint1, imageDesc1);
    Descriptor.compute(image2, keyPoint2, imageDesc2);
    
    FlannBasedMatcher matcher;
    vector<vector<DMatch> > matchePoints;
    vector<DMatch> GoodMatchePoints;
    
    vector<Mat> train_desc(1, imageDesc1);
    matcher.add(train_desc);
    matcher.train();
    
    matcher.knnMatch(imageDesc2, matchePoints, 2);
    cout << "total match points: " << matchePoints.size() << endl;
    
    // Lowe's algorithm,获取优秀匹配点
    for (int i = 0; i < matchePoints.size(); i++)
    {
        if (matchePoints[i][0].distance < 0.4 * matchePoints[i][1].distance)
        {
            GoodMatchePoints.push_back(matchePoints[i][0]);
        }
    }
    
    Mat first_match;
    drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);
    imshow("first_match ", first_match);

     

    2.图像配准

    这样子我们就可以得到了两幅待拼接图的匹配点集,接下来我们进行图像的配准,即将两张图像转换为同一坐标下,这里我们需要使用findHomography函数来求得变换矩阵。但是需要注意的是,findHomography函数所要用到的点集是Point2f类型的,所有我们需要对我们刚得到的点集GoodMatchePoints再做一次处理,使其转换为Point2f类型的点集。

    vector<Point2f> imagePoints1, imagePoints2;
    
    for (int i = 0; i<GoodMatchePoints.size(); i++)
    {
        imagePoints2.push_back(keyPoint2[GoodMatchePoints[i].queryIdx].pt);
        imagePoints1.push_back(keyPoint1[GoodMatchePoints[i].trainIdx].pt);
    }

    这样子,我们就可以拿着imagePoints1, imagePoints2去求变换矩阵了,并且实现图像配准。值得注意的是findHomography函数的参数中我们选泽了CV_RANSAC,这表明我们选择RANSAC算法继续筛选可靠地匹配点,这使得匹配点解更为精确。

    //获取图像1到图像2的投影映射矩阵 尺寸为3*3  
    Mat homo = findHomography(imagePoints1, imagePoints2, CV_RANSAC);
    ////也可以使用getPerspectiveTransform方法获得透视变换矩阵,不过要求只能有4个点,效果稍差  
    //Mat   homo=getPerspectiveTransform(imagePoints1,imagePoints2);  
    cout << "变换矩阵为:\n" << homo << endl << endl; //输出映射矩阵     
    
    //图像配准  
    Mat imageTransform1, imageTransform2;
    warpPerspective(image01, imageTransform1, homo, Size(MAX(corners.right_top.x, corners.right_bottom.x), image02.rows));
    //warpPerspective(image01, imageTransform2, adjustMat*homo, Size(image02.cols*1.3, image02.rows*1.8));
    imshow("直接经过透视矩阵变换", imageTransform1);
    imwrite("trans1.jpg", imageTransform1);

     

    3. 图像拷贝

    拷贝的思路很简单,就是将左图直接拷贝到配准图上就可以了。

    //创建拼接后的图,需提前计算图的大小
    int dst_width = imageTransform1.cols;  //取最右点的长度为拼接图的长度
    int dst_height = image02.rows;
    
    Mat dst(dst_height, dst_width, CV_8UC3);
    dst.setTo(0);
    
    imageTransform1.copyTo(dst(Rect(0, 0, imageTransform1.cols, imageTransform1.rows)));
    image02.copyTo(dst(Rect(0, 0, image02.cols, image02.rows)));
    
    imshow("b_dst", dst);

     

    4.图像融合(去裂缝处理)

    从上图可以看出,两图的拼接并不自然,原因就在于拼接图的交界处,两图因为光照色泽的原因使得两图交界处的过渡很糟糕,所以需要特定的处理解决这种不自然。这里的处理思路是加权融合,在重叠部分由前一幅图像慢慢过渡到第二幅图像,即将图像的重叠区域的像素值按一定的权值相加合成新的图像。

    
    //优化两图的连接处,使得拼接自然
    void OptimizeSeam(Mat& img1, Mat& trans, Mat& dst)
    {
        int start = MIN(corners.left_top.x, corners.left_bottom.x);//开始位置,即重叠区域的左边界  
    
        double processWidth = img1.cols - start;//重叠区域的宽度  
        int rows = dst.rows;
        int cols = img1.cols; //注意,是列数*通道数
        double alpha = 1;//img1中像素的权重  
        for (int i = 0; i < rows; i++)
        {
            uchar* p = img1.ptr<uchar>(i);  //获取第i行的首地址
            uchar* t = trans.ptr<uchar>(i);
            uchar* d = dst.ptr<uchar>(i);
            for (int j = start; j < cols; j++)
            {
                //如果遇到图像trans中无像素的黑点,则完全拷贝img1中的数据
                if (t[j * 3] == 0 && t[j * 3 + 1] == 0 && t[j * 3 + 2] == 0)
                {
                    alpha = 1;
                }
                else
                {
                    //img1中像素的权重,与当前处理点距重叠区域左边界的距离成正比,实验证明,这种方法确实好  
                    alpha = (processWidth - (j - start)) / processWidth;
                }
    
                d[j * 3] = p[j * 3] * alpha + t[j * 3] * (1 - alpha);
                d[j * 3 + 1] = p[j * 3 + 1] * alpha + t[j * 3 + 1] * (1 - alpha);
                d[j * 3 + 2] = p[j * 3 + 2] * alpha + t[j * 3 + 2] * (1 - alpha);
    
            }
        }
    
    }

    多尝试几张,验证拼接效果

    测试一

    测试二

    测试三

    最后给出完整的SURF算法实现的拼接代码。

    #include "highgui/highgui.hpp"    
    #include "opencv2/nonfree/nonfree.hpp"    
    #include "opencv2/legacy/legacy.hpp"   
    #include <iostream>  
    
    using namespace cv;
    using namespace std;
    
    void OptimizeSeam(Mat& img1, Mat& trans, Mat& dst);
    
    typedef struct
    {
        Point2f left_top;
        Point2f left_bottom;
        Point2f right_top;
        Point2f right_bottom;
    }four_corners_t;
    
    four_corners_t corners;
    
    void CalcCorners(const Mat& H, const Mat& src)
    {
        double v2[] = { 0, 0, 1 };//左上角
        double v1[3];//变换后的坐标值
        Mat V2 = Mat(3, 1, CV_64FC1, v2);  //列向量
        Mat V1 = Mat(3, 1, CV_64FC1, v1);  //列向量
    
        V1 = H * V2;
        //左上角(0,0,1)
        cout << "V2: " << V2 << endl;
        cout << "V1: " << V1 << endl;
        corners.left_top.x = v1[0] / v1[2];
        corners.left_top.y = v1[1] / v1[2];
    
        //左下角(0,src.rows,1)
        v2[0] = 0;
        v2[1] = src.rows;
        v2[2] = 1;
        V2 = Mat(3, 1, CV_64FC1, v2);  //列向量
        V1 = Mat(3, 1, CV_64FC1, v1);  //列向量
        V1 = H * V2;
        corners.left_bottom.x = v1[0] / v1[2];
        corners.left_bottom.y = v1[1] / v1[2];
    
        //右上角(src.cols,0,1)
        v2[0] = src.cols;
        v2[1] = 0;
        v2[2] = 1;
        V2 = Mat(3, 1, CV_64FC1, v2);  //列向量
        V1 = Mat(3, 1, CV_64FC1, v1);  //列向量
        V1 = H * V2;
        corners.right_top.x = v1[0] / v1[2];
        corners.right_top.y = v1[1] / v1[2];
    
        //右下角(src.cols,src.rows,1)
        v2[0] = src.cols;
        v2[1] = src.rows;
        v2[2] = 1;
        V2 = Mat(3, 1, CV_64FC1, v2);  //列向量
        V1 = Mat(3, 1, CV_64FC1, v1);  //列向量
        V1 = H * V2;
        corners.right_bottom.x = v1[0] / v1[2];
        corners.right_bottom.y = v1[1] / v1[2];
    
    }
    
    int main(int argc, char *argv[])
    {
        Mat image01 = imread("g5.jpg", 1);    //右图
        Mat image02 = imread("g4.jpg", 1);    //左图
        imshow("p2", image01);
        imshow("p1", image02);
    
        //灰度图转换  
        Mat image1, image2;
        cvtColor(image01, image1, CV_RGB2GRAY);
        cvtColor(image02, image2, CV_RGB2GRAY);
    
    
        //提取特征点    
        SurfFeatureDetector Detector(2000);  
        vector<KeyPoint> keyPoint1, keyPoint2;
        Detector.detect(image1, keyPoint1);
        Detector.detect(image2, keyPoint2);
    
        //特征点描述,为下边的特征点匹配做准备    
        SurfDescriptorExtractor Descriptor;
        Mat imageDesc1, imageDesc2;
        Descriptor.compute(image1, keyPoint1, imageDesc1);
        Descriptor.compute(image2, keyPoint2, imageDesc2);
    
        FlannBasedMatcher matcher;
        vector<vector<DMatch> > matchePoints;
        vector<DMatch> GoodMatchePoints;
    
        vector<Mat> train_desc(1, imageDesc1);
        matcher.add(train_desc);
        matcher.train();
    
        matcher.knnMatch(imageDesc2, matchePoints, 2);
        cout << "total match points: " << matchePoints.size() << endl;
    
        // Lowe's algorithm,获取优秀匹配点
        for (int i = 0; i < matchePoints.size(); i++)
        {
            if (matchePoints[i][0].distance < 0.4 * matchePoints[i][1].distance)
            {
                GoodMatchePoints.push_back(matchePoints[i][0]);
            }
        }
    
        Mat first_match;
        drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);
        imshow("first_match ", first_match);
    
        vector<Point2f> imagePoints1, imagePoints2;
    
        for (int i = 0; i<GoodMatchePoints.size(); i++)
        {
            imagePoints2.push_back(keyPoint2[GoodMatchePoints[i].queryIdx].pt);
            imagePoints1.push_back(keyPoint1[GoodMatchePoints[i].trainIdx].pt);
        }
    
    
    
        //获取图像1到图像2的投影映射矩阵 尺寸为3*3  
        Mat homo = findHomography(imagePoints1, imagePoints2, CV_RANSAC);
        ////也可以使用getPerspectiveTransform方法获得透视变换矩阵,不过要求只能有4个点,效果稍差  
        //Mat   homo=getPerspectiveTransform(imagePoints1,imagePoints2);  
        cout << "变换矩阵为:\n" << homo << endl << endl; //输出映射矩阵      
    
       //计算配准图的四个顶点坐标
        CalcCorners(homo, image01);
        cout << "left_top:" << corners.left_top << endl;
        cout << "left_bottom:" << corners.left_bottom << endl;
        cout << "right_top:" << corners.right_top << endl;
        cout << "right_bottom:" << corners.right_bottom << endl;
    
        //图像配准  
        Mat imageTransform1, imageTransform2;
        warpPerspective(image01, imageTransform1, homo, Size(MAX(corners.right_top.x, corners.right_bottom.x), image02.rows));
        //warpPerspective(image01, imageTransform2, adjustMat*homo, Size(image02.cols*1.3, image02.rows*1.8));
        imshow("直接经过透视矩阵变换", imageTransform1);
        imwrite("trans1.jpg", imageTransform1);
    
    
        //创建拼接后的图,需提前计算图的大小
        int dst_width = imageTransform1.cols;  //取最右点的长度为拼接图的长度
        int dst_height = image02.rows;
    
        Mat dst(dst_height, dst_width, CV_8UC3);
        dst.setTo(0);
    
        imageTransform1.copyTo(dst(Rect(0, 0, imageTransform1.cols, imageTransform1.rows)));
        image02.copyTo(dst(Rect(0, 0, image02.cols, image02.rows)));
    
        imshow("b_dst", dst);
    
    
        OptimizeSeam(image02, imageTransform1, dst);
    
    
        imshow("dst", dst);
        imwrite("dst.jpg", dst);
    
        waitKey();
    
        return 0;
    }
    
    
    //优化两图的连接处,使得拼接自然
    void OptimizeSeam(Mat& img1, Mat& trans, Mat& dst)
    {
        int start = MIN(corners.left_top.x, corners.left_bottom.x);//开始位置,即重叠区域的左边界  
    
        double processWidth = img1.cols - start;//重叠区域的宽度  
        int rows = dst.rows;
        int cols = img1.cols; //注意,是列数*通道数
        double alpha = 1;//img1中像素的权重  
        for (int i = 0; i < rows; i++)
        {
            uchar* p = img1.ptr<uchar>(i);  //获取第i行的首地址
            uchar* t = trans.ptr<uchar>(i);
            uchar* d = dst.ptr<uchar>(i);
            for (int j = start; j < cols; j++)
            {
                //如果遇到图像trans中无像素的黑点,则完全拷贝img1中的数据
                if (t[j * 3] == 0 && t[j * 3 + 1] == 0 && t[j * 3 + 2] == 0)
                {
                    alpha = 1;
                }
                else
                {
                    //img1中像素的权重,与当前处理点距重叠区域左边界的距离成正比,实验证明,这种方法确实好  
                    alpha = (processWidth - (j - start)) / processWidth;
                }
    
                d[j * 3] = p[j * 3] * alpha + t[j * 3] * (1 - alpha);
                d[j * 3 + 1] = p[j * 3 + 1] * alpha + t[j * 3 + 1] * (1 - alpha);
                d[j * 3 + 2] = p[j * 3 + 2] * alpha + t[j * 3 + 2] * (1 - alpha);
    
            }
        }
    
    }

     

    基于ORB的图像拼接

    利用ORB进行图像拼接的思路跟上面的思路基本一样,只是特征提取和特征点匹配的方式略有差异罢了。这里就不再详细介绍思路了,直接贴代码看效果。

    #include "highgui/highgui.hpp"    
    #include "opencv2/nonfree/nonfree.hpp"    
    #include "opencv2/legacy/legacy.hpp"   
    #include <iostream>  
    
    using namespace cv;
    using namespace std;
    
    void OptimizeSeam(Mat& img1, Mat& trans, Mat& dst);
    
    typedef struct
    {
        Point2f left_top;
        Point2f left_bottom;
        Point2f right_top;
        Point2f right_bottom;
    }four_corners_t;
    
    four_corners_t corners;
    
    void CalcCorners(const Mat& H, const Mat& src)
    {
        double v2[] = { 0, 0, 1 };//左上角
        double v1[3];//变换后的坐标值
        Mat V2 = Mat(3, 1, CV_64FC1, v2);  //列向量
        Mat V1 = Mat(3, 1, CV_64FC1, v1);  //列向量
    
        V1 = H * V2;
        //左上角(0,0,1)
        cout << "V2: " << V2 << endl;
        cout << "V1: " << V1 << endl;
        corners.left_top.x = v1[0] / v1[2];
        corners.left_top.y = v1[1] / v1[2];
    
        //左下角(0,src.rows,1)
        v2[0] = 0;
        v2[1] = src.rows;
        v2[2] = 1;
        V2 = Mat(3, 1, CV_64FC1, v2);  //列向量
        V1 = Mat(3, 1, CV_64FC1, v1);  //列向量
        V1 = H * V2;
        corners.left_bottom.x = v1[0] / v1[2];
        corners.left_bottom.y = v1[1] / v1[2];
    
        //右上角(src.cols,0,1)
        v2[0] = src.cols;
        v2[1] = 0;
        v2[2] = 1;
        V2 = Mat(3, 1, CV_64FC1, v2);  //列向量
        V1 = Mat(3, 1, CV_64FC1, v1);  //列向量
        V1 = H * V2;
        corners.right_top.x = v1[0] / v1[2];
        corners.right_top.y = v1[1] / v1[2];
    
        //右下角(src.cols,src.rows,1)
        v2[0] = src.cols;
        v2[1] = src.rows;
        v2[2] = 1;
        V2 = Mat(3, 1, CV_64FC1, v2);  //列向量
        V1 = Mat(3, 1, CV_64FC1, v1);  //列向量
        V1 = H * V2;
        corners.right_bottom.x = v1[0] / v1[2];
        corners.right_bottom.y = v1[1] / v1[2];
    
    }
    
    int main(int argc, char *argv[])
    {
        Mat image01 = imread("t1.jpg", 1);    //右图
        Mat image02 = imread("t2.jpg", 1);    //左图
        imshow("p2", image01);
        imshow("p1", image02);
    
        //灰度图转换  
        Mat image1, image2;
        cvtColor(image01, image1, CV_RGB2GRAY);
        cvtColor(image02, image2, CV_RGB2GRAY);
    
    
        //提取特征点    
        OrbFeatureDetector  surfDetector(3000);  
        vector<KeyPoint> keyPoint1, keyPoint2;
        surfDetector.detect(image1, keyPoint1);
        surfDetector.detect(image2, keyPoint2);
    
        //特征点描述,为下边的特征点匹配做准备    
        OrbDescriptorExtractor  SurfDescriptor;
        Mat imageDesc1, imageDesc2;
        SurfDescriptor.compute(image1, keyPoint1, imageDesc1);
        SurfDescriptor.compute(image2, keyPoint2, imageDesc2);
    
        flann::Index flannIndex(imageDesc1, flann::LshIndexParams(12, 20, 2), cvflann::FLANN_DIST_HAMMING);
    
        vector<DMatch> GoodMatchePoints;
    
        Mat macthIndex(imageDesc2.rows, 2, CV_32SC1), matchDistance(imageDesc2.rows, 2, CV_32FC1);
        flannIndex.knnSearch(imageDesc2, macthIndex, matchDistance, 2, flann::SearchParams());
    
        // Lowe's algorithm,获取优秀匹配点
        for (int i = 0; i < matchDistance.rows; i++)
        {
            if (matchDistance.at<float>(i, 0) < 0.4 * matchDistance.at<float>(i, 1))
            {
                DMatch dmatches(i, macthIndex.at<int>(i, 0), matchDistance.at<float>(i, 0));
                GoodMatchePoints.push_back(dmatches);
            }
        }
    
        Mat first_match;
        drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, first_match);
        imshow("first_match ", first_match);
    
        vector<Point2f> imagePoints1, imagePoints2;
    
        for (int i = 0; i<GoodMatchePoints.size(); i++)
        {
            imagePoints2.push_back(keyPoint2[GoodMatchePoints[i].queryIdx].pt);
            imagePoints1.push_back(keyPoint1[GoodMatchePoints[i].trainIdx].pt);
        }
    
    
    
        //获取图像1到图像2的投影映射矩阵 尺寸为3*3  
        Mat homo = findHomography(imagePoints1, imagePoints2, CV_RANSAC);
        ////也可以使用getPerspectiveTransform方法获得透视变换矩阵,不过要求只能有4个点,效果稍差  
        //Mat   homo=getPerspectiveTransform(imagePoints1,imagePoints2);  
        cout << "变换矩阵为:\n" << homo << endl << endl; //输出映射矩阵      
    
                                                    //计算配准图的四个顶点坐标
        CalcCorners(homo, image01);
        cout << "left_top:" << corners.left_top << endl;
        cout << "left_bottom:" << corners.left_bottom << endl;
        cout << "right_top:" << corners.right_top << endl;
        cout << "right_bottom:" << corners.right_bottom << endl;
    
        //图像配准  
        Mat imageTransform1, imageTransform2;
        warpPerspective(image01, imageTransform1, homo, Size(MAX(corners.right_top.x, corners.right_bottom.x), image02.rows));
        //warpPerspective(image01, imageTransform2, adjustMat*homo, Size(image02.cols*1.3, image02.rows*1.8));
        imshow("直接经过透视矩阵变换", imageTransform1);
        imwrite("trans1.jpg", imageTransform1);
    
    
        //创建拼接后的图,需提前计算图的大小
        int dst_width = imageTransform1.cols;  //取最右点的长度为拼接图的长度
        int dst_height = image02.rows;
    
        Mat dst(dst_height, dst_width, CV_8UC3);
        dst.setTo(0);
    
        imageTransform1.copyTo(dst(Rect(0, 0, imageTransform1.cols, imageTransform1.rows)));
        image02.copyTo(dst(Rect(0, 0, image02.cols, image02.rows)));
    
        imshow("b_dst", dst);
    
    
        OptimizeSeam(image02, imageTransform1, dst);
    
    
        imshow("dst", dst);
        imwrite("dst.jpg", dst);
    
        waitKey();
    
        return 0;
    }
    
    
    //优化两图的连接处,使得拼接自然
    void OptimizeSeam(Mat& img1, Mat& trans, Mat& dst)
    {
        int start = MIN(corners.left_top.x, corners.left_bottom.x);//开始位置,即重叠区域的左边界  
    
        double processWidth = img1.cols - start;//重叠区域的宽度  
        int rows = dst.rows;
        int cols = img1.cols; //注意,是列数*通道数
        double alpha = 1;//img1中像素的权重  
        for (int i = 0; i < rows; i++)
        {
            uchar* p = img1.ptr<uchar>(i);  //获取第i行的首地址
            uchar* t = trans.ptr<uchar>(i);
            uchar* d = dst.ptr<uchar>(i);
            for (int j = start; j < cols; j++)
            {
                //如果遇到图像trans中无像素的黑点,则完全拷贝img1中的数据
                if (t[j * 3] == 0 && t[j * 3 + 1] == 0 && t[j * 3 + 2] == 0)
                {
                    alpha = 1;
                }
                else
                {
                    //img1中像素的权重,与当前处理点距重叠区域左边界的距离成正比,实验证明,这种方法确实好  
                    alpha = (processWidth - (j - start)) / processWidth;
                }
    
                d[j * 3] = p[j * 3] * alpha + t[j * 3] * (1 - alpha);
                d[j * 3 + 1] = p[j * 3 + 1] * alpha + t[j * 3 + 1] * (1 - alpha);
                d[j * 3 + 2] = p[j * 3 + 2] * alpha + t[j * 3 + 2] * (1 - alpha);
    
            }
        }
    
    }

    看一看拼接效果,我觉得还是不错的。

    看一下这一组图片,这组图片产生了鬼影,为什么?因为两幅图中的人物走动了啊!所以要做图像拼接,尽量保证使用的是静态图片,不要加入一些动态因素干扰拼接。

     

    opencv自带的拼接算法stitch

    opencv其实自己就有实现图像拼接的算法,当然效果也是相当好的,但是因为其实现很复杂,而且代码量很庞大,其实在一些小应用下的拼接有点杀鸡用牛刀的感觉。最近在阅读sticth源码时,发现其中有几个很有意思的地方。

    1.opencv stitch选择的特征检测方式

    一直很好奇opencv stitch算法到底选用了哪个算法作为其特征检测方式,是ORB,SIFT还是SURF?读源码终于看到答案。

    #ifdef HAVE_OPENCV_NONFREE
            stitcher.setFeaturesFinder(new detail::SurfFeaturesFinder());
    #else
            stitcher.setFeaturesFinder(new detail::OrbFeaturesFinder());
    #endif

    在源码createDefault函数中(默认设置),第一选择是SURF,第二选择才是ORB(没有NONFREE模块才选),所以既然大牛们这么选择,必然是经过综合考虑的,所以应该SURF算法在图像拼接有着更优秀的效果。

    2.opencv stitch获取匹配点的方式

    以下代码是opencv stitch源码中的特征点提取部分,作者使用了两次特征点提取的思路:先对图一进行特征点提取和筛选匹配(1->2),再对图二进行特征点的提取和匹配(2->1),这跟我们平时的一次提取的思路不同,这种二次提取的思路可以保证更多的匹配点被选中,匹配点越多,findHomography求出的变换越准确。这个思路值得借鉴。

    
    matches_info.matches.clear();
    
    Ptr<flann::IndexParams> indexParams = new flann::KDTreeIndexParams();
    Ptr<flann::SearchParams> searchParams = new flann::SearchParams();
    
    if (features2.descriptors.depth() == CV_8U)
    {
        indexParams->setAlgorithm(cvflann::FLANN_INDEX_LSH);
        searchParams->setAlgorithm(cvflann::FLANN_INDEX_LSH);
    }
    
    FlannBasedMatcher matcher(indexParams, searchParams);
    vector< vector<DMatch> > pair_matches;
    MatchesSet matches;
    
    // Find 1->2 matches
    matcher.knnMatch(features1.descriptors, features2.descriptors, pair_matches, 2);
    for (size_t i = 0; i < pair_matches.size(); ++i)
    {
        if (pair_matches[i].size() < 2)
            continue;
        const DMatch& m0 = pair_matches[i][0];
        const DMatch& m1 = pair_matches[i][1];
        if (m0.distance < (1.f - match_conf_) * m1.distance)
        {
            matches_info.matches.push_back(m0);
            matches.insert(make_pair(m0.queryIdx, m0.trainIdx));
        }
    }
    LOG("\n1->2 matches: " << matches_info.matches.size() << endl);
    
    // Find 2->1 matches
    pair_matches.clear();
    matcher.knnMatch(features2.descriptors, features1.descriptors, pair_matches, 2);
    for (size_t i = 0; i < pair_matches.size(); ++i)
    {
        if (pair_matches[i].size() < 2)
            continue;
        const DMatch& m0 = pair_matches[i][0];
        const DMatch& m1 = pair_matches[i][1];
        if (m0.distance < (1.f - match_conf_) * m1.distance)
            if (matches.find(make_pair(m0.trainIdx, m0.queryIdx)) == matches.end())
                matches_info.matches.push_back(DMatch(m0.trainIdx, m0.queryIdx, m0.distance));
    }
    LOG("1->2 & 2->1 matches: " << matches_info.matches.size() << endl);

    这里我仿照opencv源码二次提取特征点的思路对我原有拼接代码进行改写,实验证明获取的匹配点确实较一次提取要多。

    //提取特征点    
    SiftFeatureDetector Detector(1000);  // 海塞矩阵阈值,在这里调整精度,值越大点越少,越精准 
    vector<KeyPoint> keyPoint1, keyPoint2;
    Detector.detect(image1, keyPoint1);
    Detector.detect(image2, keyPoint2);
    
    //特征点描述,为下边的特征点匹配做准备    
    SiftDescriptorExtractor Descriptor;
    Mat imageDesc1, imageDesc2;
    Descriptor.compute(image1, keyPoint1, imageDesc1);
    Descriptor.compute(image2, keyPoint2, imageDesc2);
    
    FlannBasedMatcher matcher;
    vector<vector<DMatch> > matchePoints;
    vector<DMatch> GoodMatchePoints;
    
    MatchesSet matches;
    
    vector<Mat> train_desc(1, imageDesc1);
    matcher.add(train_desc);
    matcher.train();
    
    matcher.knnMatch(imageDesc2, matchePoints, 2);
    
    // Lowe's algorithm,获取优秀匹配点
    for (int i = 0; i < matchePoints.size(); i++)
    {
        if (matchePoints[i][0].distance < 0.4 * matchePoints[i][1].distance)
        {
            GoodMatchePoints.push_back(matchePoints[i][0]);
            matches.insert(make_pair(matchePoints[i][0].queryIdx, matchePoints[i][0].trainIdx));
        }
    }
    cout<<"\n1->2 matches: " << GoodMatchePoints.size() << endl;
    
    #if 1
    
    FlannBasedMatcher matcher2;
    matchePoints.clear();
    vector<Mat> train_desc2(1, imageDesc2);
    matcher2.add(train_desc2);
    matcher2.train();
    
    matcher2.knnMatch(imageDesc1, matchePoints, 2);
    // Lowe's algorithm,获取优秀匹配点
    for (int i = 0; i < matchePoints.size(); i++)
    {
        if (matchePoints[i][0].distance < 0.4 * matchePoints[i][1].distance)
        {
            if (matches.find(make_pair(matchePoints[i][0].trainIdx, matchePoints[i][0].queryIdx)) == matches.end())
            {
                GoodMatchePoints.push_back(DMatch(matchePoints[i][0].trainIdx, matchePoints[i][0].queryIdx, matchePoints[i][0].distance));
            }
            
        }
    }
    cout<<"1->2 & 2->1 matches: " << GoodMatchePoints.size() << endl;
    #endif

    最后再看一下opencv stitch的拼接效果吧~速度虽然比较慢,但是效果还是很好的。

    #include <iostream>
    #include <opencv2/core/core.hpp>
    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    #include <opencv2/stitching/stitcher.hpp>
    using namespace std;
    using namespace cv;
    bool try_use_gpu = false;
    vector<Mat> imgs;
    string result_name = "dst1.jpg";
    int main(int argc, char * argv[])
    {
        Mat img1 = imread("34.jpg");
        Mat img2 = imread("35.jpg");
    
        imshow("p1", img1);
        imshow("p2", img2);
    
        if (img1.empty() || img2.empty())
        {
            cout << "Can't read image" << endl;
            return -1;
        }
        imgs.push_back(img1);
        imgs.push_back(img2);
    
    
        Stitcher stitcher = Stitcher::createDefault(try_use_gpu);
        // 使用stitch函数进行拼接
        Mat pano;
        Stitcher::Status status = stitcher.stitch(imgs, pano);
        if (status != Stitcher::OK)
        {
            cout << "Can't stitch images, error code = " << int(status) << endl;
            return -1;
        }
        imwrite(result_name, pano);
        Mat pano2 = pano.clone();
        // 显示源图像,和结果图像
        imshow("全景图像", pano);
        if (waitKey() == 27)
            return 0;
    }

     

    原文地址:https://www.cnblogs.com/skyfsm/p/7411961.html

    感谢原作者。

     

    展开全文
  • 不少朋友希望能让我讲讲如何做图像识别。正好 TeguCV 的安装包里有一个测试数据集是我们用无人机航拍的车辆照片,就用它来做个教程吧! 前情提要: MasterPa:只要九分钟,用神经网络构建人脸比对模型 首先,你需要...

    在这里插入图片描述

    上一篇只要九分钟,用神经网络构建人脸比对模型的文章发出去之后,承蒙大家厚爱,得到了不少反馈。不少朋友希望能让我讲讲如何做图像识别。正好 TeguCV 的安装包里有一个测试数据集是我们用无人机航拍的车辆照片,就用它来做个教程吧!

    前情提要:

    MasterPa:只要九分钟,用神经网络构建人脸比对模型

    首先,你需要能够使用神经网络进行深度学习,如果你还不会的话,你有两个选择:

    从头学起,比如从吴恩达的课开始。
    或者直接下载 TeguCV,一款极简又强大的计算机视觉工具,免编程训练神经网络。添加客服获得下载链接:
    在这里插入图片描述

    TeguCV 需要 Win 10 操作系统,并且和其他深度学习工具一样需要英伟达显卡以及最新驱动。

    如果你已经下载完成 TeguCV,在 TeguCV GUI 1.15/Dataset/CarDataset 这个文件夹目录里,我们可以看到不少照片。这都是用无人机在长春航拍所得。我们今天的目标就是利用神经网络把里面的车识别出来。

    在这里插入图片描述

    和上次一样,在安装文件夹中找到 Tegu.exe,它长这样:
    在这里插入图片描述
    Tegu.exe

    这次选择 Image Detection 图像识别
    在这里插入图片描述

    新建项目并且填写必要的信息(项目名称和保存路径)就可以开始了!
    在这里插入图片描述
    在这里插入图片描述
    打开之后,你会看到这样一个界面。左面是参数调整,右边是日志输出。别怕,你看完这篇文章就能学会全部参数的含义。
    在这里插入图片描述
    首先要选择我们这次准备训练用的数据。在「Data Root」选择数据存放的地址:TeguCV GUI 1.15/Dataset/CarDataset/trainset 里面是我们的训练用数据集,双击打开后选择这个文件夹就可以。

    然后是「Serval Path」,是标注文件的路径。因为我们单纯给电脑图片是没用的,还需要告诉电脑图片里有什么。.serval 是 TeguCV 专用的标注文件格式。在 TeguCV GUI 1.15/Dataset/CarDataset/ 可以看到一个名为 car.serval 的文件,选择它。

    「Model Save Path」是模型保存的地址,咱们上一步新建项目的时候已经给它安排上了。

    其实现在我们已经可以开始训练,教电脑如何识别车辆了。但开始之前,我们还有几个地方最好了解一下:

    • Epoch 是最大训练轮次。就是说你打算让电脑一共学习多久这些数据。就好像读完义务教育要 9 年,但是读完博士可能要 19 年。但也不是说时间越长越好,毕竟博士延毕迟迟出不来也不是啥好事对不?如果你是新手,我建议第一次这里写 100,如果你的电脑不是特别快,写 20 也行。
    • Learning Rate 是学习率。通俗地说电脑学习的速度。学的太快可能导致学不明白,但学的太慢则有可能一直学不完。调整学习率需要依赖人的经验,这个过程也就是我们所谓的「调参」。TeguCV 拥有自动搜索合适学习率的功能,但这不是这次的重点。我们下次会讲一讲。
    • Save Epoch 是每多少轮会保存一次。建议 5 或者 10 轮一次。
      Visualization When Save 是每次保存模型时,都让电脑顺便挑 20% 的训练数据去识别一下,方便我们看当前的效果。看着识别越来越准是一件很有成就的事情。不过要注意的是,最开始的轮次模型完全不准很正常。可以在保存模型的路径下找到展示效果。

    这次我们可以选择 Epoch 为 50 或 100,Learning Rate 0.001,每 10 轮保存一次。请务必让全部文件路径都是英文的。

    i5 + 1050ti 的话差不多 10s 跑完一轮。到了需要保存那轮会稍微慢一些。

    最开始你会看到这样的效果:

    在这里插入图片描述
    也可能比这个还差,这都不要紧。蓝色框中是电脑认为是车的位置。

    多轮训练之后,效果会开始变好:

    在这里插入图片描述
    看,准确多了吧!

    至此,你也许是第一个,但肯定是过程最简单的一个神经网络模型就训练出来了!

    如果你想使用这个模型,可以直接用下面的 Predict 功能。或者比如我们想直接实时识别无人机回传的数据,也可以开启 Server。详细介绍可以在下载包里的说明文档找到。这里也有一份在线版:

    TeguCV GUI 1.15 产品简介

    如果感兴趣,也可以直接添加客服,可以点击以下链接,也可以绿色通信工具加:adamgi

    微信扫描二维码
    在这里插入图片描述

    展开全文
  • 种种原因,于8.15开始成为一只社畜,找的工作呢是关于图像处理的。入职第一天,我的组长给了我一个任务,就是处理无人机航拍的图像,主要目的是想提升一下视觉效果。 图像类似下图这种,可以看到拍摄环境是光照比较...

    全文总结:测试了几种对比度提升对于航拍彩色图像的视觉提升效果。总结图像效果差的原因后,利用去雾算法获得了绝大多数场景适用的处理效果,CPU单帧处理时间为160ms(3*1920*1080)。

    种种原因,于8.15开始成为一只社畜,找的工作呢是关于图像处理的。入职第一天,我的组长给了我一个任务,就是处理无人机航拍的图像,主要目的是想提升一下视觉效果。

    图像类似下图这种,可以看到拍摄环境是光照比较强的,但是图像看起来似乎有一层灰蒙蒙的东西。其他光照较弱的图像中,图像质量较这张差更多。这样的图像主要原因,我认为是由于公司设计的无人机的飞行高度比较高,当拍摄地面时,空气中会有较多的散射微粒影响成像,所以出现这样的效果。

    最开始组长让我试试各种对比度提升的算法,让图像看起来好一些,于是我按着这个思路试了一下。

     

    直方图增强

    直方图增强的算法肯定就不必多说了,就是通过扩展像素值的范围,让图像的对比度提升,看起来图像会有更多细节突出。

    在代码中我使用了openCV自带的直方图增强函数,将图像分为三个通道,分别使用再将通道合并。

    equalizeHist( InputArray src, OutputArray dst )

                                                                              图1 起飞场地图

                                                                                   图2 地面植被图

                                                                                     图3 天空效果图

    使用直方图增强时,地面效果图很差,地面植被场景的效果还不错,在天空效果图中,远处的天空则会出现色块,说明这种方法不是场景普遍适用的。

    总结原因:1. 因为RGB通道分开进行直方图均衡化会出现颜色的失真。

                      2.天空有色块,则是因为直方图均衡化对于一些图像区域灰度范围不一致时,效果会很差。

    基于以上的原因,我还想测试的几种简单的图像增强算法,如伽马校正,拉普拉斯锐化等等,就被一一否决了。因为这些算法也大部分是RGB分开处理再融合,避免不了颜色失真的影响。出于对颜色失真的考虑,我决定再使用颜色空间转换到HSI空间或者YCrCb空间处理,直方图增强其强度分量。

    Mat ycrcb;
    Mat result;
    vector<Mat> channels;
    cvtColor(frame, ycrcb, COLOR_RGB2YCrCb);       //RGB到YCRCB
    split(ycrcb, channels);                        //通道分离
    equalizeHist(channels[0], channels[0]);        //直方图增强强度分量
    merge(channels, ycrcb);                         //通道合并
    cvtColor(ycrcb, result, COLOR_YCrCb2RGB);       //YCRCB到RGB

                                                                                               图4 地面图

                                                                                               图5 植被                                               

    图4 图5给出了增强强度分量的图像结果,还是不理想,因为整个图像变得像灰度图一样,并且天空处仍然有色块(此处没图)。

    花了一天测试这些基本的图像增强算法,效果都不能达到要求。于是我开始另外思考航拍图像有这种效果的原因,正如我在开头所讲,是空气中的散射粒子导致了这种现象。我突然觉得这种图像类似于有雾的图像,因为散射粒子就类似于雾对图像的影响,觉得去雾的算法可能有效果,同时我还需要一种算法处理速度较快,来满足我后期的需求。

    全网搜索了一下,发现了这样一篇宝藏文章,文章没有源码,但是讲解十分清楚。看完后,我对于这个算法的流程理解的很好,于是自己用了半天多时间在自己的电脑环境下编写了程序,并且测试了效果。参考文章点下图,作者人very nice,我还问了几个问题都帮我解答了。该文章里面有的内容我不再赘述,我只写一下我自己的代码实现步骤和想法。

    一种可实时处理 O(1)复杂度图像去雾算法的实现。

                                                                                      图6 算法步骤

    1. 求暗通道及图像最大值(0-255),图像均值(0-1)

    		 //step 1 求出暗通道,以及暗通道均值,图像最大值
    		 for (Y = 0; Y < rows; Y++)
    		 {
    			 ImgPt = frame.ptr<uchar>(Y);
    			 DarkPt = dark.ptr<uchar>(Y);
    			 for (X = 0; X < cols; X++)
    			 {
    				 Min = double(*ImgPt) ;
    				 //Max = double(*ImgPt) ;
    				 if (Max < (*(ImgPt + 1))) { Max = *(ImgPt + 1); }
    				 if (Max < (*(ImgPt)))     { Max = *(ImgPt); }
    				 if (Max < (*(ImgPt + 2))) { Max = *(ImgPt + 2); }
    
    
    				 if (Min > (*(ImgPt + 1))) { Min = *(ImgPt + 1);}
    				 if (Min > (*(ImgPt + 2))) { Min = *(ImgPt + 2); }
    				 *DarkPt = uint(Min);                                 //    三通道的最小值
    				 Sum += Min ;                                     //  累积以方便后面求平均值
    				 ImgPt += 3;
    				 DarkPt++;
    
    
    			 }
    		 }
    		 Mean = (double)Sum / (rows*cols*255);

    这一步的基本思路还是按参考文章写得,不同的是,我将求最大值的代码也加入了这段程序中。我觉得值得注意的一点是,平均值Mean的范围。算法步骤和其对应论文中,表示图像的像素值的范围是0-1,但是因为openCV的像素都是0-255,我为了方便,只将平均值算到0-1,而不改变其它的值。如果变量的范围不注意一下,很有可能下面的计算环境光一步得不到结果。

    2. 暗通道图像模糊化

    //step 2 对暗通道均值滤波(0-255)运行时间109ms左右
    blur(dark, mdark, Size(64, 64));

    这一步就比较简单了,就是用openCV内置的blur函数,对图像做了一次均值滤波。这里的size取决于图像大小,也看后面的测试效果。因为我的应用场景比较关心时间,所以测算了一下这个模糊函数的时间在110ms左右(彩色图像1920*1080)。参考文章的作者有个时间更快的方法,如果读者对于CPU速度加快有兴趣,可以参考。因为我的代码最终是要用GPU实现的,所以不再考虑那些优化的方法。

    3. 环境光求解及暗通道最大值

             //step 3 求解Lx,环境光(0-255)
    		 for (Y = 0; Y < rows; Y++)
    		 {
    			 ImgPt = mdark.ptr<uchar>(Y);
    			 p1 = Lx.ptr<uchar>(Y);
    			 DarkPt = dark.ptr<uchar>(Y);
    			 for (X = 0; X < cols; X++)
    			 {
    				 
    				 if (dMax <(*(ImgPt))) { dMax = (*(ImgPt)); }
    
    				 Min = 0.9;
    				 if ((rou*Mean) < Min) Min = rou * Mean;
    
    				 Min = Min * (*ImgPt);
    				 //cout << uint(*ImgPt);
    			     if (Min >(*DarkPt)) Min = (*DarkPt);
    				 *p1 = uint(Min);
    
    				 ImgPt += 1;
    				 DarkPt++;
    				 p1++;
    			 }
    			}

    这一步也很简单,就是根据公式求出环境光,注意环境光的像素值是0到255,这个Lx矩阵是一个单通道矩阵。

    4. 计算原始图像

     //step 4 求解A,大气指数,为1*3数组
    		 A =(0.5*(Max + dMax));
    
     //step 5  计算原始图像,使用LUT方法
    //建立查找表
    	 unsigned char * Table = (unsigned char  *)malloc(256 * 256 * sizeof(unsigned char));
    			 for (Y = 0; Y < 256; Y++)
    			 {
    				 Index = Y << 8;
    				 for (X = 0; X < 256; X++)
    				 {
    					 Value = (Y -X) / (1 - X * (1 / A));
    					 //cout << Value << endl;
    					 if (Value > 255)
    						 Value = 255;
    					 else if (Value < 0)
    						 Value = 0;
    
    					 Table[Index++] = Value;
    
    				 }
    			 }
    
     //查表计算原始图像
    			 for (Y = 0; Y < rows; Y++)
    			 {
    				 ImgPt = frame.ptr<uchar>(Y);
    				 p1 = Ipro.ptr<uchar>(Y);
    				 p2 = Lx.ptr<uchar>(Y);
    				 for (X = 0; X < cols; X++)
    				 {
    					  
    					 *p1= Table[(*ImgPt<< 8) + uint((*p2))];
    					 *(p1 + 1) = Table[(*(ImgPt+1) << 8) + uint((*p2))];
    					 *(p1 + 2) = Table[(*(ImgPt+2) << 8) + uint((*p2))];
    					 ImgPt += 3;
    					 p1+=3; 
    					 p2++;
    
    
    				 }
    			 }

    这里也是根据公式算出原始图像,值得注意的是,参考博文作者用的是查表法。就C++写openCV的程序来说,最费时间的其实就是循环读取像素的过程,再计算每个像素。那么有没有什么较快的方法呢?

    OpenCV官方文档提供了一些参考,How to scan images, lookup tables and time measurement with OpenCV。最快的就是查表法,这是通过牺牲一部分内存的方法来降低运算量。计算前和计算后每一个像素值,存在一种映射关系,如果找到这种映射关系,就可以直接赋值,而不是还在每个像素点计算。官方文档也提供了一些解释,有兴趣可以去看看上面的网址。

    从以上这一段程序,可以看书来,计算的时候就直接用查表计算,免去了很多计算量。openCV还提供一种内置函数来实现查表法,即。其中I是输入图像;lookUpTable就是我们建立的查找表,按照像素一一对应的关系,应该是256*256大小;J就是输出图像。

    LUT(I, lookUpTable, J);

    LUT函数由于是openCV的内置函数,已经实现了加速,我认为其实就是简单的放到了GPU下,每个像素并行计算了,消耗的时间也就是GPU和CPU来回读写的时间。

    以上就完成了去雾算法的全部过程,由于我只是在实现这个算法,并没有对算法的原理有过多研究,可能没能在这方面提供帮助。下面是处理完的图像效果

    这个算法的实现就告一段落,效果以满足公司的要求,由于处理时间还是太长,所以还是要探究加快计算速度的办法。后面也需要将该算法移植到公司的视频播放器中,所以最近也在学习D3D11的部分,希望用GPU并行计算实现这部分代码,还在探索中。这篇博客的内容也是来自两周前了,今天周五接近下班,所以就写了写。

    展开全文
  • 图像拼接在实际的应用场景很广,比如无人机航拍,遥感图像等等,图像拼接是进一步做图像理解基础步骤,拼接效果的好坏直接影响接下来的工作,所以一个好的图像拼接算法非常重要。 再举一个身边的例子吧,你用你的...
  • OpenCV图像处理入门学习教程系列,上一篇第二篇:不同阈值二值化图像图像拼接与融合介绍图像拼接在实际中的应用场景非常广泛,比如无人机的航拍,遥感图像等,甚至小到我们出去游玩用手机拍照时,无奈广角太小,没有...
  • 图像拼接在实际的应用场景很广,比如无人机航拍,遥感图像等等,图像拼接是进一步做图像理解基础步骤,拼接效果的好坏直接影响接下来的工作,所以一个好的图像拼接算法非常重要。 再举一个身边的例子吧,你用你的...
  • 图像增强,即增强图像中的有⽤信息,改善图像的视觉效果。它实际上包含了很多的内容。 (1) 对⽐度增强. ⽤于扩⼤图像中不同物体特征之间的差别,抑制不感兴趣的特征,可⽤于改善图像的识别效果,满⾜某些特殊分析...
  • 图像拼接在实际的应用场景很广,比如无人机航拍,遥感图像等等,图像拼接是进一步做图像理解基础步骤,拼接效果的好坏直接影响接下来的工作,所以一个好的图像拼接算法非常重要。 再举一个身边的例子吧,你用你的...
  • 关于透视变换 cv2.warpPerspective 详情请查看: https://blog.csdn.net/dcrmg/article/details/80273818 https://blog.csdn.net/qq_27261889/article/details/80720359 关于FLANN匹配 详情请查看:...
  • 本文主要介绍对《数字图像处理》第三章书中示例图片实现 反转变换、对数变换以及伽马变换的代码 若要获取更多数字图像处理,python,深度学习,机器学习,计算机视觉等高清PDF以及 更多有意思的 分享,可搜一搜 微信...
  • Java+OpenCV图片对比

    2019-05-16 17:48:35
    经过查询相关资料,OpenCV图像处理还是挺不错的,底层是用c,c++写的,文件小,对python、Java等提供接口。 首先配置环境,在网页上下载开发包,按照配置教程配置。 步骤大致如下: 1.按照上方【下载】链接...
  •   在实际生产生活中,我们所获得的数据集在特征上往往具有很高的维度,对高维度的数据进行处理时消耗的时间很大,并且过多的特征变量也会妨碍查找规律的建立。如何在最大程度上保留数据集的信息量的前提下进行数据...
  • 环境:VS2017+OpenCV3.3+C++ 什么是图像切割?在一幅图像中,如果我们只对其中的...随着计算机图像处理技术的发展,我们可以通过计算机来获取和处理图像信息。图像识别的基础是图像分割,其作用是把反映物体真实情...
  • OpenCV是一个用于图像处理、分析、机器视觉方面的开源函数库. 无论你是做科学研究,还是商业应用,opencv都可以作为你理想的工具库,因为,对于这两者,它完全是免费的。 该库采用C及C++语言编写,可以在windows, ...
  • opencv是什么

    2011-05-02 15:27:00
    OpenCV是一个用于图像处理、分析、机器视觉方面的开源函数库. 无论你是做科学研究,还是商业应用,opencv都可以作为你理想的工具库,因为,对于这两者,它完全是免费的。该库采用C及C++语言编写,可以在windows, ...
  • 介绍了无人机图像拼接一些流程和常用方法,可以通过运动估计来实现图像的拼接
  • OPENCV是什么?

    2019-04-19 20:22:03
    OpenCV是一个用于图像处理、分析、机器视觉方面的开源函数库.
1 2 3 4 5 ... 7
收藏数 137
精华内容 54