• opencv图像处理8卷积



    卷积是本章所讨论的很多转换的基础。

    先看看效果:

    抽象的说,这个术语意味着我们对图像的每一个部分所做的操作。从这个意义上讲,我们在第五章所看到


    的许多操作可以被理解成普通卷积的特殊情况。一个特殊的卷积所实现的功能是由所用的卷积核的形式决定的。这个核本质上是一个大小固定,

    由数值参数构成的数组,数组的标定点通常位于数组的中心。数组的大小被称为核支撑。单就技术而言,核支撑实际上仅仅由核数组的非零部分

    组成。
    图6-1描述了以数组中心为定标点的3×3卷积核。若要计算一个特定点的卷积值,首先将核的标定点定位到图像的第一个像素点,核的其余元素覆

    盖图像中其相对应的局部像素点。对于每一个核点,我们可以得到这个点的核的值以及图像中相应图像点的值。将这些值相乘并求和,并将这个

    结果放置在与输入图像标定点所相对应的位置。通过在整个图像上扫描卷积核,对图像的每个点重复此操作。

    当然我们可以用方程来表示这个过程,如果我们定义图像为I(x,y),核为G(i,j) (其中 0 < i < Mi –1 和 0 < j < Mj –1),标定点位于相

    应核的(ai,aj)坐标上,则卷积H(x,y)定义为:

    注意到运算次数,至少第一眼看似乎等于图像的像素数乘以核的像素数[63]。这需要很大的计算量并且也不是仅仅用其中的一些for循环以及许多


    指针再分配就能做的事情。类似这种情况,你最好让OpenCV来做这个工作以利用OpenCV已编程实现的最优方法。其函数为cvFilter2D ();
     [63]这里我们说“第一眼看”的意思是在频域中也可能进行卷积操作。在这种情况下,对于一个N×N的图像和一个M×M的核(N>M),计算复杂度

    将会按照N2 log(N)成比例增加,而不是在空间域内预计的N2M2。这是因为频域的计算量同核的大小是相对独立的,对于大核更加有效。OpenCV会

    根据核的大小自动决定是否做频域内的卷积。
    void cvFilter2D(
       const CvArr*    src,
       CvArr*          dst,
       const CvMat*    kernel,
       CvPoint         anchor = cvPoint(-1,-1)
    );
    这里我们创建一个适当大小的矩阵,将系数连同原图像和目标图像一起传递给cvFilter2D()。我们还可以有选择地输入一个CvPoint指出核的中心


    位置,但默认值(cvPoint(-1,-1))就会被认为是核的中心。如果定义了标定点,核的大小可以是任意偶数尺寸,否则大小就是奇数。
    原图像src和目标图像dst大小应该是相同的,有些人可能认为考虑到卷积核的额外的长和宽,原图像src应该大于目标图像dst。但是在OpenCV里


    原图像src和目标图像dst的大小是可以一样的,因为在默认情况下,在卷积之前,OpenCV通过复制原图像src的边界创建了虚拟像素,这样以便于


    目标图像dst边界的像素可以被填充。复制是通过input(–dx, y) = input(0, y), input(w + dx, y) = input(w – 1, y)等实现的,还有一


    些可以替换此默认行为的方法,我们将在下一节讨论。
    提示一下,这里我们所讨论的卷积核的系数应该是浮点类型的,这就意味着我们必须用CV_32F来初始化矩阵。
    做卷积时自然出现的一个问题是如何处理卷积边界。例如,在使用刚才所讨论的卷积核时,当卷积点在图像边缘时会发生什么?许多使用


    cvFilter2D()的OpenCV内置函数必须用各种方式来解决这个问题。同样在你做卷积时,有必要知道如何有效解决这个问题。这个解决方法就是使


    用cvCopyMakeBorder()函数,它可以将特定的图像轻微变大,然后以各种方式自动填充图像边界。
    void cvCopyMakeBorder(
       const CvArr*   src,
       CvArr*         dst,
       CvPoint        offset,
       int            bordertype,
       CvScalar       value     = cvScalarAll(0)
    );
    Offset变量告诉cvCopyMakeBorder()将原图像的副本放到目标图像中什么位置。典型情况是,如果核为N×N(N为奇数)时,那么边界在每一侧的


    宽度都应是 (N – 1)/2,即这幅图像比原图像宽或高N – 1。在这种情况下,可以把Offset设置为cvPoint((N-1)/2,(N-1)/2),使得边界在每


    一侧都是偶数。[64]
     [64]当然,标定点在中心、N×N并且N是奇数时的情形是最简单的。在一般情况下,如果核是N×M并且标定点在(ax,ay),那么目标图像将比原图


    像宽N-1,高M-1个像素。Offset的值仅仅是(ax,ay)。
    Bordertype既可以是IPL_BORDER_CONSTANT,也可以是IPL_BORDER_REPLICATE(见图6-2)。在第一种情况下,value变量被认为是所有在边界的像


    素应该设置的值。在第二种情况下,原始图像边缘的行和列被复制到大图像的边缘。注意到测试的模板图像边缘是比较精细的(注意图6-2右上角


    的图像)。在测试的模板图像中,除了在圆图案边缘附近的像素变白外,有一个像素宽的黑色边界。这里定义了另外两种边界类型,


    IPL_BORDER_REFLECT 和IPL_BORDER_WRAP,目前还没有被OpenCV所实现,但以后可能会在OpenCV中实现。
    图6-2 扩大的图像边界,左边一列显示的是IPL_BORDER_CONSTANT,边界是用零值填充的,右面一列是IPL_BORDER_REPLICATE,在水平和垂直两


    个方向复制边界像素。


     
    我们在前面已经提到,当调用OpenCv库函数中的卷积功能时,cvCopyMakeBorder()函数就会被调用。在大多数情况下,边界类型为


    IPL_BORDER_REPLICATE,但有时并不希望用它。所以在另一种场合,可能用到cvCopyMakeBorder()。你可以创造一幅具有比想要得到的边界稍微


    大一些的图像,无论调用任何常规操作,接下来就可以剪切到对原图像所感兴趣的部分。这样一来, OpenCV的自动加边就不会影响所关心的像素。


    注:高斯模板:
    3*3的是
    1 2 1
    2 4 2
    1 2 1


    5*5的是
    1 2 3 2 1
    2 5 6 5 2  
    3 6 8 6 3
    2 5 6 5 2  
    1 2 3 2 1

    #include "cv.h"  
    #include "highgui.h"  
    #include <stdio.h>  
    int main()  
    {  
    		IplImage *src=0;
    		IplImage*dst =0;
    		IplImage*dst2=0;  
    		float k[9]={  
    				1,2,1,
    				2,4,2,
    				1,2,1};  //高斯卷积3*3的核
    
    				for(int i = 0 ; i< 9;i++){
    			k[i] = float(k[i]/16);
    				}
    		CvMat Km;  
    		Km = cvMat(3,3,CV_32F,k);  
    
    		float k2[25] = {
    			1 ,2, 3, 2, 1,
    			2 ,5 ,6 ,5, 2,  
    			3 ,6 ,8 ,6, 3,
    			2 ,5 ,6 ,5, 2,  
    			1 ,2 ,3 ,2, 1};//高斯卷积5*5的核
    			for(int j = 0 ; j<25; j++){
    			k2[j] = float(k2[j]/85);
    			}
    			CvMat Km2;
    			Km2 = cvMat(5,5,CV_32F,k2);
    		src=cvLoadImage("1.bmp",0);//Force to gray image  
    		dst=cvCloneImage(src);  //使用cvCloneImage时,dst无需初始化,直接复制
    		dst2 = cvCloneImage(src);
    		cvNamedWindow("src",CV_WINDOW_AUTOSIZE);  
    		cvNamedWindow("filter3*3",CV_WINDOW_AUTOSIZE);
    		cvNamedWindow("filter5*5",CV_WINDOW_AUTOSIZE);
    
    		cvShowImage("src",src);  
    		  
    		cvFilter2D(src,dst,&Km,cvPoint(-1,-1));  
    		cvFilter2D(src, dst2, &Km2,cvPoint(-1,-1));
    		//卷积本身公式比较复杂,但是经过舍去高阶小量,简化后成为模板操作
    		cvShowImage("filter3*3",dst);  
    		cvShowImage("filter5*5", dst2);
    		cvWaitKey(0);  
    		cvReleaseImage(&src);  
    		cvReleaseImage(&dst);  
    		cvReleaseImage(&dst2);
    		cvDestroyAllWindows();
    		return 0;  
    } 


  • 相关阅读:
    深入理解 Netty-新连接接入
    深入理解 Netty-Channel架构体系
    深入理解 NioEventLoop启动流程
    深入理解 NioEventLoopGroup初始化
    java8-Stream
    WebSocket+Netty构建web聊天程序
    Jpa 笔记
    观察者模式
    一只垂直的小爬虫
    字符集编码全方位解析
  • 原文地址:https://www.cnblogs.com/libing64/p/2878752.html
Copyright © 2020-2023  润新知