zoukankan      html  css  js  c++  java
  • 【计算机视觉】形态学滤波

    【计算机视觉】形态学滤波

    标签(空格分隔): 【图像处理】 【信号处理】

    版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/lg1259156776/


    说明:本文主要想弄清楚形态学滤波在图象处理和信号处理中的应用,图像处理中非常直观的通过腐蚀膨胀获得开闭运算的效果,而在数据实时滤波中,形态学滤波也是可以使用的。


    形态学滤波基本知识

    原理:在特殊领域运算形式——结构元素(Sturcture Element),在每个像素位置上与二值图像对应的区域进行特定的逻辑运算。运算结构是输出图像的相应像素。运算效果取决于结构元素大小内容以及逻辑运算性质。

    膨胀、腐蚀、开、闭运算是数学形态学最基本的变换。

    结构元素简单地定义为像素的结构(形状)以及一个原点(又称为锚点),使用形态学滤波涉及对图像的每个像素应用这个结构元素,当结构元素的原点与给定的像素对齐时,它与图像相交部分定义了一组进行形态学运算的像素。原则上,结构元素可以是任何形状,但通常使用简单的形状,比如方形、圆形和菱形,而原点位于中心位置(基于效率的考虑)。

    腐蚀和膨胀两个滤波操作也运算在每个像素周围像素集合上(邻域),这是由结构元素定义的。当应用到一个给定的像素时,结构元素的锚点与该像素的位置对齐,而所有与他相交的像素都被包括在当前像素集合中。腐蚀替换当前像素为像素集合中找到的最小的像素值,而膨胀则替换为像素集合中找到的最大像素值。当然,对于二值图像,每个像素只能被替换为白色像素或黑色像素。

    这一段的论述,可以参考Opencv计算机视觉 编程手册。对于腐蚀和膨胀的差别,可以通过想象,腐蚀呢, 如果给定像素的结构元素触碰到背景,那么该像素被设置为背景,而在膨胀的情况下,如果触碰到前景,该像素被设置为前景,所以很明显,腐蚀操作后物体的尺寸会减小,而膨胀操作后物体的尺寸会增大,同时内部的一些洞被填满。

    事实上,腐蚀一副图像两次,就像是让结构元素对自己进行膨胀后再去腐蚀同一幅图像;反过来对膨胀也是合适的。

    有下面两种说法:

    1. 对图像的腐蚀操作等于对图像负片的膨胀操作的负片;
    2. 对图像的膨胀操作等于对图像负片的腐蚀操作的负片;

    实际上说的是对目标进行的腐蚀,等效于对背景进行的膨胀;反之亦然。

    腐蚀的最简单的应用是从图中消除不相关的细节,而膨胀的最简单的应用是将裂缝桥接起来。开运算是先腐蚀再膨胀,开运算一般断开狭窄的间断和消除细的突出物,而闭操作通常消弥狭窄的间断和长细的鸿沟,消除小的孔洞,并填充轮廓线中的断裂。开运算与闭运算的结合使用能使作用对象的轮廓变得光滑

    开运算是先腐蚀再膨胀,而闭运算是先膨胀再腐蚀;
    在检验闭滤波器的结果时,可以看到白色前景物体中的小洞被填充,该滤波器同时连接多个相邻物体,基本上,无法完全包含结构元素的洞洞或者缝隙都将被滤波器移除。反过来,开滤波器则是移除掉场景中比较小的物体,因为它门无法完全包含结构元素。

    这些滤波器通常在物体检测中应用,闭滤波器将误分割为碎片的物体重新连接起来,而开滤波器则去除掉图像噪声点引起的小像素块(Blob)。因此,在视频序列中使用他们很有帮助,如果测试的二值图像相继使用闭、开操作,获得图像将只显示场景中的主要物体。如果优先处理噪点,也可以先进行开运算,再进行闭运算,但是有可能去除掉一些分散的物体。

    需要注意的是,对于对于一幅图像多次使用相同的开运算或者闭运算是没有效果的,因为在第一次闭(开)运算填充图像中的洞洞后,再次应用相同的滤波,不会对图像产生任何变化。用数学的术语讲,这些运算是等幂的。

    使用形态学滤波对图像进行边缘及角点检测

    形态学滤波可以用于检测图像中指定的特征。
    一种比较形象的方法是将灰度图像看做是“等高线”(比如分水岭图像分割算法):亮的区域代表山峰,而暗的区域代表山谷,图像的边沿就对应于峭壁。如果腐蚀一幅图像,会导致山谷被扩展,而峭壁减少了。相反的,如果膨胀一幅图像,峭壁则会增加。但是这两种情况下,中间的部分(大片的谷底和高原)基本保持不变。

    在上述理解的基础上,如果我们对图像的腐蚀和膨胀的结果做差,就能提取图像的边界:因为边界区域,二者完全不同。(实际上,我们也可以用腐蚀或者膨胀的结果与源图像做差得出类似结果,但提取的边界会比较细)。可以看出,结构元越大,边界越粗。在OpenCV中,将形态学操作函数morphologyEx 的第4个参数设为MORPH_GRADIENT,就能完成上述工作。

    利用形态学操作获取角点稍微有一些复杂,它试用了四种不同的结构元素,基本方法是对一幅图像先腐蚀,在膨胀但是这两次操作使用的结构元却不同。这些结构元的选取使得直线保持不变,但是由于他们各自作用的效果,角点处的边沿被影响了。我们结合一幅图来说明:

    此处输入图片的描述

    第一幅图是原图。在被十字形元素膨胀后,方块的边缘被扩张,而由于十字形元素没有击中角点,此处不受影响。中间的方块描述了这个结果;膨胀后的图像接着被菱形元素腐蚀,这次运算将大多数的边缘恢复到原始位置,但之前没有膨胀过的角点被向内推动,之后得到了左边的方块,可以看到,他缺少明显的角点。同样的处理过程通过X形与方形元素得到重复。这两个元素结构是先前元素的旋转版本,捕获的将是45°旋转后的角点。最后,对两次过程的结果做差值,提取角点特征。

    代码可以参考参考文献3.

    形态学滤波之图象处理

    一般腐蚀操作对二值图进行处理,腐蚀操作如下图,中心位置的像素点是否与周围领域的像素点颜色一样(即是否是白色点,即值是否为255),若一致,则保留,不一致则该点变为黑色(值即为0)
    此处输入图片的描述
    opencv中的腐蚀操作:

    CVAPI(void)  cvErode( const CvArr* src, CvArr* dst,
                          IplConvKernel* element CV_DEFAULT(NULL),
                          int iterations CV_DEFAULT(1) );
    

    前两个参数比较熟悉,第三个参数是用于传递模板的信息,默认是(NULL),即为3*3的模板,第四个参数是迭代的次数(即该腐蚀操作做几次);

    opencv中的膨胀操作其实就是腐蚀的反操作:

    CVAPI(void)  cvDilate( const CvArr* src, CvArr* dst,
                           IplConvKernel* element CV_DEFAULT(NULL),
                           int iterations CV_DEFAULT(1) );
    

    测试代码:
    #include “stdafx.h”
    #include “cv.h”
    #include “highgui.h”

    int main(){
        IplImage *img= cvLoadImage("C:/fu.jpg");//读取图片
        cvNamedWindow("Example1",CV_WINDOW_AUTOSIZE);
        cvNamedWindow("Example2",CV_WINDOW_AUTOSIZE);
        cvNamedWindow("Example3",CV_WINDOW_AUTOSIZE);
    
        cvShowImage("Example1",img);//在Example1显示图片
        //    cvCopy(img,temp);
        IplImage* temp=cvCreateImage( //创建一个size为image,三通道8位的彩色图
            cvGetSize(img),
            IPL_DEPTH_8U,
            3
            );
    
        cvErode(img,temp,0,1);//腐蚀
        cvShowImage("Example2",temp);
    
        cvDilate(img,temp,0,1);//膨胀
        cvShowImage("Example3",temp);
    
    
        cvWaitKey(0);//暂停用于显示图片
    
    
        cvReleaseImage(&img);//释放img所指向的内存空间并且
        cvDestroyWindow("Example1");
        cvDestroyWindow("Example2");
        cvDestroyWindow("Example3");
    
        return 0;
    }
    

    此处输入图片的描述
    以上都是在模板3*3的情况下处理的,要是我们期望使用自己定义的模板时候,就需要自己做模板。

    CVAPI(IplConvKernel*)  cvCreateStructuringElementEx(
                int cols, int  rows, int  anchor_x, int  anchor_y,
                int shape, int* values CV_DEFAULT(NULL) );
    

    前两个参数是定义模板的大小,后两个参数是参考点的坐标(比如默认3*3模板的参考点坐标是2*2),第五个参数是模板的类型(可以是矩形,十字形,椭圆形,甚至是用户自己定义形状),最后一个参数是在使用自自定义形状的时候,通过value传递模板的形状。

    模板的类型:
    此处输入图片的描述

    CVAPI(void)  cvReleaseStructuringElement( IplConvKernel** element ); //释放模板所占用的内存
    

    自定义5*5,参考点(3,3)的矩形模板的测试代码:

    #include "stdafx.h"
    #include "cv.h"
    #include "highgui.h"
    
    int main(){
        IplImage *img= cvLoadImage("C:/fu.jpg");//读取图片
        cvNamedWindow("Example1",CV_WINDOW_AUTOSIZE);
        cvNamedWindow("Example2",CV_WINDOW_AUTOSIZE);
        cvNamedWindow("Example3",CV_WINDOW_AUTOSIZE);
    
        cvShowImage("Example1",img);//在Example1显示图片
        //    cvCopy(img,temp);
        IplImage* temp=cvCreateImage( //创建一个size为image,三通道8位的彩色图
            cvGetSize(img),
            IPL_DEPTH_8U,
            3
            );
    
        IplConvKernel * myModel;
        myModel=cvCreateStructuringElementEx( //自定义5*5,参考点(3,3)的矩形模板
            5,5,2,2,CV_SHAPE_RECT
            );
    
        cvErode(img,temp,myModel,1);
        cvShowImage("Example2",temp);
    
        cvDilate(img,temp,myModel,1);
        cvShowImage("Example3",temp);
    
    
        cvWaitKey(0);//暂停用于显示图片
    
        cvReleaseStructuringElement(&myModel);
        cvReleaseImage(&img);//释放img所指向的内存空间并且
        cvDestroyWindow("Example1");
        cvDestroyWindow("Example2");
        cvDestroyWindow("Example3");
    
        return 0;
    }
    

    效果图:
    此处输入图片的描述

    形态学滤波之信号处理

    数学形态学的方法可以理解为一个具有一定直径的小球滚过一段特定的路径,各种信号可以看作是路径上的小坑。由于所有的噪声都有个共同特征一一高频低峰(它们构成非常复杂,由各种混合在眼电信号中的其他成份或眼球快速、微小、空间大小不超过1“的运动导致),这些小坑的径长明显小于小球直径,因此小球球心的滚动轨迹不会受噪声的干扰一一小球球心滚动轨迹即可以看成数学形态学处理后的信号。本系统中用到的数学形态学算子包括腐蚀运算、膨胀运算、开操作、闭操作。

    腐蚀的最简单的应用是从图中消除不相关的细节,而膨胀的最简单的应用是将裂缝桥接起来。开运算是先腐蚀再膨胀,开运算一般断开狭窄的间断和消除细的突出物,而闭操作通常消弥狭窄的间断和长细的鸿沟,消除小的孔洞,并填充轮廓线中的断裂。开运算与闭运算的结合使用能使作用对象的轮廓变得光滑

    #include <stdio.h>
    #include <fcntl.h>
    //#include <sys/types.h>
    //#include <sys/stats.h>
    #include <time.h>
    #define N 5  //结构元素。大小设置根据滤除波形一个周期中点个数来定。例如:采样率250,滤除50hz,
                                                                     //因为50hz一个周期中有5个点,所以N设为5。          
    
    
    float x[3*N+2]={0.0};
    
    void openoperate(float input[],float dilation[]) //开运算:先腐蚀再膨胀
    {
        int i,k,t;
        float tmp;
        t=2*N+3;
        float erosion[2*N+3];
        for(k=0;k<t;k++){
            tmp=input[k];
            for(i=k+1;i<k+N;i++){
                if(tmp>input[i])
                    tmp=input[i];
            }
            erosion[k]=tmp;
        }
        t=t-3;
        for(k=0;k<t;k++){
            tmp=erosion[k];
            for(i=k+1;i<k+N;i++){
                if(tmp<erosion[i])
                    tmp=erosion[i];
            }
            dilation[k]=tmp;
        }
    }
    
    float closeoperate(float input[]) //闭运算:先膨胀再腐蚀
    {
        int i,k,t;
        float tmp;
    
        t=N*2-1;
        float dilation[N];
    
        for(k=0;k<N;k++){
            tmp=input[k];
            for(i=k+1;i<k+N;i++){
                if(tmp<input[i])
                    tmp=input[i];
            }
            dilation[k]=tmp;
        }
    
        tmp=dilation[0];
        for(k=1;k<N;k++){
            if(tmp>dilation[k])
                tmp=dilation[k];
        }
        return tmp;
    }
    
    int main()
    {
        clock_t start,end;
        double duration;
        FILE *fd,*m_fd;
        float buffer;
        float filter_data;
        float openresult[2*N-1];
    
        m_fd=fopen("D:/data.txt","r+");
        if(m_fd==NULL){
            perror("open error!");
            return -1;
        }
    
        fd=fopen("D:/data1.txt","w+");  
        if(fd==NULL){
            perror("open error!");
            return -1;
        }
        start=clock();
        while(fscanf(m_fd,"%f",&buffer)!=EOF){
              x[3*N+1]=buffer;
    
              openoperate(x,openresult);
              filter_data=closeoperate(openresult);
              fprintf(fd,"%f ",filter_data);
    
              for(int y=0;y<3*N+1;y++)
                   x[y]=x[y+1];
        }
        end=clock();
        duration=(double)(end-start)/CLOCKS_PER_SEC;
        printf("%f seconds
    ",duration);
        fclose(fd);
        fclose(m_fd);
        return 0;
    }
    

    结果如下图所示,对有干扰原始眼电数据进行形态学滤波处理,其中结构元素17个,窗宽5.有图可以看到,形态学对于尖峰的滤波效果特别明显。
    此处输入图片的描述

    图二,将形态学滤波算法加入Qt中,对眼电进行实时滤波处理,效果如图:

    此处输入图片的描述

    【引】这部分内容主要来自参考文献2

    总结

    以前认为形态学滤波只在图像处理中有所应用,现在看了参考文献2,才发觉自己实在有点固步自封,对知识的来龙去脉掌握的不够清楚,一言以蔽之,囫囵吞枣,并不清楚思考方向,而只掌握具体的技巧细节,不注重顶层设计,所以才会有此感慨。

    在进行数据处理中,常常用到的滤波方法有中值滤波,均值滤波,FIR,IIR,卡尔曼滤波,自适应滤波等。而很多知识都是相符相通的,切不可死学死记。

    参考文献:
    1. http://blog.csdn.net/thefutureisour/article/details/7574819
    2. http://m.blog.csdn.net/blog/gylltq/33799347
    3. OpenCV 2 计算机视觉编程手册


    2015-11-28 学习笔记 张朋艺

  • 相关阅读:
    标准C语言(9)
    标准C语言(8)
    标准C语言(7)
    标准C语言(6)
    标准C语言(5)
    标准C语言(4)
    标准C语言(3)
    标准C语言(1)
    Linux基础
    Kafka 学习笔记之 Kafka0.11之console-producer/console-consumer
  • 原文地址:https://www.cnblogs.com/huty/p/8518929.html
Copyright © 2011-2022 走看看