图像处理 形态学 腐蚀_二值图像形态学腐蚀 - CSDN
  •  对图像处理有所了解的人都知道图像的形态学处理里最为基础的膨胀和腐蚀算法。二值图像即只有黑白两种颜色组成的图像,一般的白色为内容,黑色为背景。其实简单点理解二值图像的膨胀与腐蚀腐蚀即是删除对象边界...

    膨胀与腐蚀算法

      对图像处理有所了解的人都知道图像的形态学处理里最为基础的膨胀和腐蚀算法。二值图像即只有黑白两种颜色组成的图像,一般的白色为内容,黑色为背景。其实简单点理解二值图像的膨胀与腐蚀,腐蚀即是删除对象边界某些像素,也就是让白色的区域瘦一圈;而膨胀则是给图像中的对象边界添加像素,即让白色的区域胖上一圈。而这个“圈”的大小,则是由参数来指定的。下面的表展示了一幅图像经过膨胀和腐蚀算法的结果。可以看出膨胀让白色区域大了一圈,白色区域若有较小的黑色洞,则洞被填上;而腐蚀则让白色区域瘦了一圈,一些面积很小的白色区域则消失。

    原图 腐蚀结果 膨胀结果

      腐蚀膨胀的算法原理并不复杂,而且网上有太多的文章都着重于介绍算法的原理思路,对用具体代码实现算法的方式讨论的不多,因而本文专注于几种实现膨胀腐蚀算法的方法。本文介绍了几种不同的腐蚀膨胀算法的实现,每一种实现都各有特点,今后若有更多的方法,也会继续更新加入至本文中。

     

    结构元素与窗口形态

      在介绍算法逻辑之前,有必要先介绍跟腐蚀膨胀算法关系密切的结构元素。结构元素是形态学的基本算子,合理选取结构元素直接影响图像处理的效果和质量。结构元素的选择在于结构元素的形状和尺寸。结构元素可以有不同的形状,圆形、正方形、菱形、六边形、线段形都是可以选择的形状。圆形结构元素,由于各向同性,因此可以得到与方向无关的运算结果,正方形、菱形可以看作是圆盘形的变异。不同形状的结构元素运算结果会有差异,应针对待处理图像的几何形状进行选择。下表对比了正方形,圆形和正菱形三种结构元素形态。

    预览
    ElementSize 121 98 61
    WindowSize 11×11 (r=5) 11×11 (r=5) 11×11 (r=5)
    非空点个数计算方式 |x|<=r&&|y|<=r x2+y2<=r2 |x|+|y|<=r

      在算法实现过程中,往往会将卷积窗口中所有像素相对中心像素的偏移存在一个数组之中,这样在对不规则形状的卷积窗口进行处理时,可以不重复判断结构元素中哪些位置为有效位置,能减少计算次数。在实现之前首先贴上基本数据结构的实现,其中visit_count用来记录像素的访问次数:

    复制代码
    #define byte unsigned char
    struct IntDouble
    {
        int X;
        int Y;
        IntDouble(int x,int y)
        {
            this->X=x;
            this->Y=y;
        }
        IntDouble()
        {
            this->X=0;
            this->Y=0;
        }
    };
    class Bitmap2d
    {
    private:
        byte* data;
        int width;
        int height;
    public:
        int visit_count;
        Bitmap2d(int width,int height,byte v)
        {
            this->data=new byte[width*height];
            memset(data,v,width*height*sizeof(byte));
            this->width=width;
            this->height=height;
            this->visit_count=0;
        }
        Bitmap2d(Bitmap2d& bitmap)
        {
            this->width=bitmap.Width();
            this->height=bitmap.Height();
            this->data=new byte[width*height];
            memcpy(this->data,bitmap.data,sizeof(byte)*Length());
            this->visit_count=0;
        }
        ~Bitmap2d()
        {
            delete[] data;
        }
        inline byte GetValue(int x,int y)
        {
            visit_count++;
            return data[x+y*width];
        }
        inline void SetValue(int x,int y,byte v)
        {
            visit_count++;
            data[x+y*width]=v;
        }
        inline int Width()
        {
            return width;
        }
        inline int Height()
        {
            return height;
        }
        inline int Length()
        {
            return width*height;
        }
        inline bool InRange(int x,int y)
        {
            return x>=0&&x<width&&y>=0&&y<height;
        }
        void ReadRaw(const char* filename)
        {
            FILE* file=fopen(filename,"rb");
            fread(data,sizeof(byte),Length(),file);
            fclose(file);
        }
        void SaveRaw(const char* filename)
        {
            FILE *const file = fopen(filename,"wb");
            fwrite(data,sizeof(byte),Length(),file);
            fclose(file);
        }
    };
    复制代码

     

    实现思路1—根据定义直接算

      首先最为简单的思路是按算法基本原理直接正向求取输出图片的像素值:

    • 膨胀:对于输出图像的所有像素点P,调查原图像中对应窗口中的像素集合S,若S中至少有一个255,则P为255。
    • 腐蚀:对于输出图像的所有像素点P,调查原图像中对应窗口中的像素集合S,若S中至少有一个0,则P为0。

      该算法的腐蚀实现函数(膨胀的类似,就不重复贴,代码工程里有)如下:

    复制代码
    Bitmap2d* Execute()
    {
        Bitmap2d* newBmp=new Bitmap2d(bmp.Width(),bmp.Height(),0);
        for(int j=0;j<bmp.Height();j++)
        {
            for(int i=0;i<bmp.Width();i++)
            {
                if(HasBlackInWindow(this->bmp,i,j))
                    newBmp->SetValue(i,j,0);
                else
                    newBmp->SetValue(i,j,255);
            }
        }
        return newBmp;
    }
    复制代码
    复制代码
    bool HasBlackInWindow(Bitmap2d& bmp,int i,int j)
    {
        for(size_t k=0;k<winOffsets.size();k++)
        {
            int tx=i+winOffsets[k].X;
            int ty=j+winOffsets[k].Y;
            if(!bmp.InRange(tx,ty))
                continue;
            if(bmp.GetValue(tx,ty)==0)
            {
                return true;
            }
        }
        return false;
    }
    复制代码

      膨胀腐蚀算法的主要时间开销来至于对像素的访问,从上述实现不难得该算法对于n*n的位图和k*k大小正方形结构元素访问像素的次数为k2n2。事实上这是实现腐蚀膨胀算法最直接但也是最慢的方式。下图是Engine数据的一个切片二值化之后分别用正方形、圆形和菱形为结构元素膨胀和腐蚀的效果图:

    腐蚀(正方形) 腐蚀(圆形) 腐蚀(菱形)
    原图预览 膨胀(正方形) 膨胀(圆形) 膨胀(菱形)

     

    实现思路2—跳过一些内部区域

      考虑到思路1的算法逻辑耗费在访问黑色区域和白色区域内部的时间较多,我们可以考虑只对黑白交界的边界考察窗口像素。这样的过程我们就可以想象成一把具有尺寸的刷子,膨胀算法刷子为白色,腐蚀算法刷子为黑色,然后让刷子在黑白交界的地方刷过,这样的过程生成的结果等价于思路1的结果。

      其优化的部分是针对远离边界的内部区域的涂刷,这样就能很大程度上减少像素的访问次数。不难想象出,对远离边界的内部区域的涂刷是不起效果的,这就是思路2对思路1改进的主要原因。设算法对n*n的图像按k*k的结构元素腐蚀,则访问像素的次数为a+4b1+(4+k2)b2,其中a为白色像素个数,b1为黑色内部像素个数,b2为黑色边界像素个数,且有a+b1+b2=n2按思路2实现的算法代码如下:

    复制代码
    Bitmap2d* Execute2()
    {
        Bitmap2d* newBmp=new Bitmap2d(bmp);
        for(int j=0;j<bmp.Height();j++)
        {
            for(int i=0;i<bmp.Width();i++)
            {
                if(bmp.GetValue(i,j)==0&&HasWhiteAdjacencyPixel(i,j))
                {
                    SetWindowValue(*newBmp,i,j,0);
                }
            }
        }
        return newBmp;
    }
    复制代码
    复制代码
    bool HasWhiteAdjacencyPixel(int i,int j)
    {
        if(i>0&&bmp.GetValue(i-1,j)==255)
            return true;
        if(i<bmp.Width()-1&&bmp.GetValue(i+1,j)==255)
            return true;
        if(j>0&&bmp.GetValue(i,j-1)==255)
            return true;
        if(j<bmp.Height()-1&&bmp.GetValue(i,j+1)==255)
            return true;
        return false;
    }
    复制代码
    复制代码
    void SetWindowValue(Bitmap2d& bmp,int i,int j,byte v)
    {
        for(size_t k=0;k<winOffsets.size();k++)
        {
            int tx=i+winOffsets[k].X;
            int ty=j+winOffsets[k].Y;
            if(!bmp.InRange(tx,ty))
                continue;
            bmp.SetValue(tx,ty,v);
        }
    }
    复制代码

     

    基于结构元素分解的算法

      对于一些具有规则形状的结构元素,可以利用矩阵分解的原理降低计算次数,例如3*3的正方形结构元素,等价于一个3*3的矩阵,这个矩阵可以为解为{1,1,1}与{1,1,1}-1的乘积。这样使用3*3的矩阵对图像进行卷积等价于先使用{1,1,1}进行卷积,再将结果使用{1,1,1}-1进行卷积。

      由于膨胀腐蚀算法本质上属于卷积的一种特殊形式,这样,正方形结构元素的膨胀腐蚀可以使用如下的方式实现:

    复制代码
    Bitmap2d* Execute4()
    {
        Bitmap2d* newBmp=new Bitmap2d(bmp);
        Bitmap2d* newBmp2=new Bitmap2d(bmp);
        if(this->mode==SQUARE)
        {
            winOffsets.clear();
            for (int i = 0; i < 2 * radius + 1; i++)
            {
                IntDouble t(i-radius,0);
                this->winOffsets.push_back(t);
            }
            for(int j=0;j<bmp.Height();j++)
            {
                for(int i=0;i<bmp.Width();i++)
                {
                    if(HasBlackInWindow(this->bmp,i,j))
                        newBmp->SetValue(i,j,0);
                    else
                        newBmp->SetValue(i,j,255);
                }
            }
            winOffsets.clear();
            for (int j = 0; j < 2 * radius + 1; j++)
            {
                IntDouble t(0,j-radius);
                this->winOffsets.push_back(t);
            }
            for(int j=0;j<newBmp->Height();j++)
            {
                for(int i=0;i<newBmp->Width();i++)
                {
                    if(HasBlackInWindow(*newBmp,i,j))
                        newBmp2->SetValue(i,j,0);
                    else
                        newBmp2->SetValue(i,j,255);
                }
            }
        }
        newBmp2->visit_count+=newBmp->visit_count;
        delete newBmp;
        return newBmp2;
    }
    复制代码

      经过测试可以知道这种方式可以大大减少像素访问次数,以k*k的结构元素腐蚀n*n的图像为例,用思路1的方法需要至少访问k2n2次像素,经过分解再处理两次只需要2kn2次访问。这个思路的详细数学原理可以参考链接

      下图是分解的方法与思路1的方法的结果对比,可以看出这两个算法的结果确实是完全等价的。

    思路1 分解的方法

     

    基于曼哈顿距离的算法

      上述思路1思路2可以适用于任意形状的处理窗口。还有一种基于曼哈顿距离的实现方式,来源于链接,这种方式主要是实现了基于菱形窗口的膨胀腐蚀。这里简单介绍一下曼哈顿距离,曼哈顿距离(Manhattan Distance)是种使用在几何度量空间的几何学用语,用以标明两个点在标准坐标系上的绝对轴距总和。其计算公式为:

      这个距离简单点理解就是“格子距离”,如下图所示:A到B的走格子的最少步数是4,那么AB的曼哈顿距离就是4。

      设我们需要膨胀的图像是下图左这样一个背景为0,内容为1的二值图像。假如我们能够求得所有0像素到离自己最近的1像素的距离的话,我们便做成了一张曼哈顿距离图(下图右)。曼哈顿距离图中像素标的数字代表该像素在左图中寻找最近的1的曼哈顿距离。假如这个像素在左图中本来就是1,则该像素处的曼哈顿距离为0。可以看出,01边界处的0像素的曼哈顿距离较小,而原理边界的0像素曼哈顿距离很大。

    原图 原图得到的曼哈顿距离图

      对于二值图像I,若能够一定处理计算得到他的曼哈顿距离图D,则想求取他的菱形结构元素膨胀结果会非常容易。不难想到,对D进行一个阈值化既可以达到结果。若将曼哈顿图D中曼哈顿距离大于等于1与小于1的像素区分开,则等于原二值图像;若将曼哈顿距离大于等于2与小于2的像素区分开,则等价于对原二值图像进行一个尺寸为1的菱形元素膨胀;若将曼哈顿距离大于等于k(k>1)与小于k的像素区分开,则等价于对原二值图像进行一个尺寸为k的菱形元素膨胀。

      而腐蚀同样可以使用这个思路来完成,前面介绍的曼哈顿距离图是适用与膨胀的,求取的是每个0像素与距离最近的1的距离。在腐蚀的场合下,我们可以求取所有1像素与距离最近的0像素距离的曼哈顿图,这样再进行阈值化,也就完成了腐蚀操作。利用曼哈顿图的好处还体现在需要使用对很多组不同大小的结构元素对相同图像进行膨胀或腐蚀的场合。一旦计算出曼哈顿距离图,就可“一次预处理,多次复用”,预处理的开销只在初次处理产生,之后的所有操作都是阈值化的过程,而阈值化我们知道只需要width*height的访问开销。

      所以问题的关键在与如何实现对二值图像I求取其曼哈顿距离图D。这里以求取膨胀的曼哈顿距离图为例进行说明。其实我们可以利用一种类似于动态规划的思想来解决这个问题。不难发现这个问题是能够分解为规模更小并且可以复用的小型子问题的和。这基于如下的事实:

    1. 对于所有I中为1的像素,D中他们为0。因为他们自己就是1像素显然到自己最近,所以不需要走格子。
    2. 对于I中的0像素p,若其四邻域像素在D中为d0、d1、d2、d3,则D(p)=min(d0,d1,d2,d3)+1。不难看出p到离其最近的1像素的通路必然经过了其四邻域像素。所以0像素p到最近的1的像素的曼哈顿距离可以基于其四邻域的曼哈顿距离求得。

      要实现这个思路,可以使用递归,但也可以使用更加直接的方式,下面的代码使用两次双循环来求得D。首先每个像素d值默认值为最大值width+height,第一次双循环,对每一个像素实际上是考察了上方和左方的像素,经过这一次循环,其d值不一定正确,仅是能够保证每个像素处的d值是相对与上方和左方的最小值加1;但第二次双循环是逆向,从下方和右方访问像素,依次再改变之前的d值,这样就实现了d值确实为min(d0,d1,d2,d3)+1。

    行序正向赋值,每个像素参考了两个父方向的d值 第二次迭代行序逆向复制,每个像素参考4个方向的d值

      采用这个思路实现的一个演示程序如下(不能跑刷新几次试试..):

      其实现的代码如下:

    复制代码
    class DistenceMap
    {
    private:
        int* data;
        int width;
        int height;
    public:
        int visit_count;
        DistenceMap(int width,int height,int v)
        {
            this->data=new int[width*height];
            for(int i=0;i<width*height;i++)
                data[i]=v;
    
            this->width=width;
            this->height=height;
            this->visit_count=0;
        }
        ~DistenceMap()
        {
            delete[] data;
        }
        inline int GetValue(int x,int y)
        {
            visit_count++;
            return data[x+y*width];
        }
        inline void SetValue(int x,int y,int v)
        {
            visit_count++;
            data[x+y*width]=v;
        }
        inline int Width()
        {
            return width;
        }
        inline int Height()
        {
            return height;
        }
        inline int Length()
        {
            return width*height;
        }
    };
    复制代码
    复制代码
    Bitmap2d* Execute3()
    {
        Bitmap2d* newBmp=new Bitmap2d(bmp);
        DistenceMap* dmap=GetDistenceMap();
        for (int i=0; i<bmp.Width(); i++)
        {
            for (int j=0; j<bmp.Height(); j++)
            {
                byte v=dmap->GetValue(i,j)<=radius?0:255;
                newBmp->SetValue(i,j,v);
            }
        }
        newBmp->visit_count+=dmap->visit_count;
        delete dmap;
        return newBmp;
    }
    复制代码
    复制代码
    DistenceMap* GetDistenceMap()
    {
        DistenceMap* distenceMap=new DistenceMap(this->bmp.Width(),this->bmp.Height(),0);
        for (int i=0; i<bmp.Width(); i++)
        {
            for (int j=0; j<bmp.Height(); j++)
            {
                if (bmp.GetValue(i, j) == 0)
                {
                    distenceMap->SetValue(i,j,0);
                } 
                else
                {
                    distenceMap->SetValue(i,j, bmp.Width()+bmp.Height());
                    if (i>0) 
                        distenceMap->SetValue(i,j,Min(distenceMap->GetValue(i,j),distenceMap->GetValue(i-1,j)+1));
                    if (j>0) 
                        distenceMap->SetValue(i,j,Min(distenceMap->GetValue(i,j), distenceMap->GetValue(i,j-1)+1));
                }
            }
        }
    
        for (int i=bmp.Width()-1; i>=0; i--)
        {
            for (int j=bmp.Height()-1; j>=0; j--)
            {
                if (i+1<bmp.Width())
                    distenceMap->SetValue(i,j,Min(distenceMap->GetValue(i,j), distenceMap->GetValue(i+1,j)+1));
                if (j+1<bmp.Height()) 
                    distenceMap->SetValue(i,j,Min(distenceMap->GetValue(i,j), distenceMap->GetValue(i,j+1)+1));
            }
        }
        return distenceMap;
    }
    复制代码

     

    总结

      本文介绍的实现方式,思路1和思路2是基本方法,其中思路2是对思路1的极大改进;矩阵分解方法适用于一些特殊形状的结构元素,其核心是把结构元素所代表的矩阵分解成两个更简单的矩阵的乘积,然后再使用这两个更简单的矩阵作为结构元素。这个思路同样能与思路1和2相配合使用;曼哈顿距离法使用一步预处理先计算出曼哈顿距离图,之后再对这个图进行阈值化,等价于使用菱形结构元素进行的膨胀腐蚀的结果,对于需要多次膨胀腐蚀的场合,这个方法非常适用。

    展开全文
  • 数字图像处理 - 形态学腐蚀

    千次阅读 2017-10-26 20:00:13
    一、理论与概念讲解——...而我们图像处理中指的形态学,往往表示的是数学形态学。下面一起来了解数学形态学的概念。 数学形态学(Mathematical morphology) 是一门建立在格论和拓扑学基础之上的图像分析学科,是数学

    一、理论与概念讲解——从现象到本质



    1.1 形态学概述

     

    形态学(morphology)一词通常表示生物学的一个分支,该分支主要研究动植物的形态和结构。而我们图像处理中指的形态学,往往表示的是数学形态学。下面一起来了解数学形态学的概念。

    数学形态学(Mathematical morphology) 是一门建立在格论和拓扑学基础之上的图像分析学科,是数学形态学图像处理的基本理论。其基本的运算包括:二值腐蚀和膨胀、二值开闭运算、骨架抽取、极限腐蚀、击中击不中变换、形态学梯度、Top-hat变换、颗粒分析、流域变换、灰值腐蚀和膨胀、灰值开闭运算、灰值形态学梯度等。

     

    简单来讲,形态学操作就是基于形状的一系列图像处理操作。OpenCV为进行图像的形态学变换提供了快捷、方便的函数。最基本的形态学操作有二种,他们是:膨胀与腐蚀(Dilation与Erosion)。

    膨胀与腐蚀能实现多种多样的功能,主要如下:

    • 消除噪声
    • 分割(isolate)出独立的图像元素,在图像中连接(join)相邻的元素。
    • 寻找图像中的明显的极大值区域或极小值区域
    • 求出图像的梯度

     


    我们在这里给出下文会用到的,用于对比膨胀与腐蚀运算的“浅墨”字样毛笔字原图:

     

    在进行腐蚀和膨胀的讲解之前,首先需要注意,腐蚀和膨胀是对白色部分(高亮部分)而言的,不是黑色部分。膨胀就是图像中的高亮部分进行膨胀,“领域扩张”,效果图拥有比原图更大的高亮区域。腐蚀就是原图中的高亮部分被腐蚀,“领域被蚕食”,效果图拥有比原图更小的高亮区域。


    1.2 膨胀

     

    其实,膨胀就是求局部最大值的操作。

    按数学方面来说,膨胀或者腐蚀操作就是将图像(或图像的一部分区域,我们称之为A)与核(我们称之为B)进行卷积。

    核可以是任何的形状和大小,它拥有一个单独定义出来的参考点,我们称其为锚点(anchorpoint)。多数情况下,核是一个小的中间带有参考点和实心正方形或者圆盘,其实,我们可以把核视为模板或者掩码。

     

    而膨胀就是求局部最大值的操作,核B与图形卷积,即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素。这样就会使图像中的高亮区域逐渐增长。如下图所示,这就是膨胀操作的初衷。



    膨胀的数学表达式:


    膨胀效果图(毛笔字):

     

    照片膨胀效果图:


     



    1.3 腐蚀


    再来看一下腐蚀,大家应该知道,膨胀和腐蚀是一对好基友,是相反的一对操作,所以腐蚀就是求局部最小值的操作。

    我们一般都会把腐蚀和膨胀对应起来理解和学习。下文就可以看到,两者的函数原型也是基本上一样的。

     

    原理图:

     

    腐蚀的数学表达式:

     

    腐蚀效果图(毛笔字):


    照片腐蚀效果图:

     

     浅墨表示这张狗狗超可爱:D

     

     



    二、深入——OpenCV源码分析溯源

     


    直接上源码吧,在…\opencv\sources\modules\imgproc\src\ morph.cpp路径中 的第1353行开始就为erode(腐蚀)函数的源码,1361行为dilate(膨胀)函数的源码。

    [cpp] view plain copy
    1. //-----------------------------------【erode()函数中文注释版源代码】----------------------------   
    2. //    说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码   
    3. //    OpenCV源代码版本:2.4.8   
    4. //    源码路径:…\opencv\sources\modules\imgproc\src\ morph.cpp   
    5. //    源文件中如下代码的起始行数:1353行   
    6. //    中文注释by浅墨   
    7. //--------------------------------------------------------------------------------------------------------    
    8. void cv::erode( InputArray src, OutputArraydst, InputArray kernel,  
    9.                 Point anchor, int iterations,  
    10.                 int borderType, constScalar& borderValue )  
    11. {  
    12. //调用morphOp函数,并设定标识符为MORPH_ERODE  
    13.    morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType,borderValue );  
    14. }  

    [cpp] view plain copy
    1. //-----------------------------------【dilate()函数中文注释版源代码】----------------------------   
    2. //    说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码   
    3. //    OpenCV源代码版本:2.4.8   
    4. //    源码路径:…\opencv\sources\modules\imgproc\src\ morph.cpp   
    5. //    源文件中如下代码的起始行数:1361行   
    6. //    中文注释by浅墨   
    7. //--------------------------------------------------------------------------------------------------------   
    8. void cv::dilate( InputArray src,OutputArray dst, InputArray kernel,  
    9.                  Point anchor, int iterations,  
    10.                  int borderType, constScalar& borderValue )  
    11. {  
    12. //调用morphOp函数,并设定标识符为MORPH_DILATE  
    13.    morphOp( MORPH_DILATE, src, dst, kernel, anchor, iterations, borderType,borderValue );  
    14. }  


    可以发现erode和dilate这两个函数内部就是调用了一下morphOp,只是他们调用morphOp时,第一个参数标识符不同,一个为MORPH_ERODE(腐蚀),一个为MORPH_DILATE(膨胀)。

    morphOp函数的源码在…\opencv\sources\modules\imgproc\src\morph.cpp中的第1286行,有兴趣的朋友们可以研究研究,这里就不费时费力花篇幅展开分析了。

     

     

     

    三、浅出——API函数快速上手

     



    3.1  形态学膨胀——dilate函数

     


    erode函数,使用像素邻域内的局部极大运算符来膨胀一张图片,从src输入,由dst输出。支持就地(in-place)操作。

    函数原型:

    [cpp] view plain copy
    1. C++: void dilate(  
    2.     InputArray src,  
    3.     OutputArray dst,  
    4.     InputArray kernel,  
    5.     Point anchor=Point(-1,-1),  
    6.     int iterations=1,  
    7.     int borderType=BORDER_CONSTANT,  
    8.     const Scalar& borderValue=morphologyDefaultBorderValue()   
    9. );  

    参数详解:

    • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
    • 第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
    • 第三个参数,InputArray类型的kernel,膨胀操作的核。若为NULL时,表示的是使用参考点位于中心3x3的核。

    我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。

    其中,getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一:

    • 矩形: MORPH_RECT
    • 交叉形: MORPH_CROSS
    • 椭圆形: MORPH_ELLIPSE

    而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。

    我们一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。且需要注意,十字形的element形状唯一依赖于锚点的位置。而在其他情况下,锚点只是影响了形态学运算结果的偏移。

    getStructuringElement函数相关的调用示例代码如下:

    [cpp] view plain copy
    1.  int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸  
    2.    
    3. //获取自定义核  
    4. Mat element = getStructuringElement(MORPH_RECT,  
    5.     Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),  
    6.     Point( g_nStructElementSize, g_nStructElementSize ));  


    调用这样之后,我们便可以在接下来调用erode或dilate函数时,第三个参数填保存了getStructuringElement返回值的Mat类型变量。对应于我们上面的示例,就是填element变量。


    • 第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。
    • 第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
    • 第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
    • 第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。
    •  

    使用erode函数,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。

    调用范例:

    [cpp] view plain copy
    1.         //载入原图   
    2.         Mat image = imread("1.jpg");  
    3. //获取自定义核  
    4.         Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));  
    5.         Mat out;  
    6.         //进行膨胀操作  
    7.         dilate(image, out, element);  

    用上面核心代码架起来的完整程序代码:

     

    [cpp] view plain copy
    1. //-----------------------------------【头文件包含部分】---------------------------------------  
    2. //     描述:包含程序所依赖的头文件  
    3. //----------------------------------------------------------------------------------------------  
    4. #include <opencv2/core/core.hpp>  
    5. #include<opencv2/highgui/highgui.hpp>  
    6. #include<opencv2/imgproc/imgproc.hpp>  
    7. #include <iostream>  
    8.    
    9. //-----------------------------------【命名空间声明部分】---------------------------------------  
    10. //     描述:包含程序所使用的命名空间  
    11. //-----------------------------------------------------------------------------------------------   
    12. using namespace std;  
    13. using namespace cv;  
    14.    
    15. //-----------------------------------【main( )函数】--------------------------------------------  
    16. //     描述:控制台应用程序的入口函数,我们的程序从这里开始  
    17. //-----------------------------------------------------------------------------------------------  
    18. int main(  )  
    19. {  
    20.    
    21.        //载入原图   
    22.        Mat image = imread("1.jpg");  
    23.    
    24.        //创建窗口   
    25.        namedWindow("【原图】膨胀操作");  
    26.        namedWindow("【效果图】膨胀操作");  
    27.    
    28.        //显示原图  
    29.        imshow("【原图】膨胀操作", image);  
    30.    
    31. <span style="white-space:pre">  </span>//获取自定义核  
    32.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));  
    33.        Mat out;  
    34. <span style="white-space:pre">  </span>//进行膨胀操作  
    35.        dilate(image,out, element);  
    36.    
    37.        //显示效果图  
    38.        imshow("【效果图】膨胀操作", out);  
    39.    
    40.        waitKey(0);  
    41.    
    42.        return 0;  
    43. }  

     运行截图:



     

     

     

    3.2 形态学腐蚀——erode函数



    erode函数,使用像素邻域内的局部极小运算符来腐蚀一张图片,从src输入,由dst输出。支持就地(in-place)操作。

     

    看一下函数原型:

    [cpp] view plain copy
    1. C++: void erode(  
    2.     InputArray src,  
    3.     OutputArray dst,  
    4.     InputArray kernel,  
    5.     Point anchor=Point(-1,-1),  
    6.     int iterations=1,  
    7.     int borderType=BORDER_CONSTANT,  
    8.     const Scalar& borderValue=morphologyDefaultBorderValue()  
    9.  );  

    参数详解:

    • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
    • 第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
    • 第三个参数,InputArray类型的kernel,腐蚀操作的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。(具体看上文中浅出部分dilate函数的第三个参数讲解部分)
    • 第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于单位(element)的中心,我们一般不用管它。
    • 第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
    • 第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
    • 第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。

    同样的,使用erode函数,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。

    调用范例:

    [cpp] view plain copy
    1.         //载入原图   
    2.         Mat image = imread("1.jpg");  
    3. //获取自定义核  
    4.         Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));  
    5.         Mat out;  
    6.         //进行腐蚀操作  
    7.         erode(image,out, element);  

    用上面核心代码架起来的完整程序代码:

     

    [cpp] view plain copy
    1. //-----------------------------------【头文件包含部分】---------------------------------------  
    2. //     描述:包含程序所依赖的头文件  
    3. //----------------------------------------------------------------------------------------------  
    4. #include <opencv2/core/core.hpp>  
    5. #include<opencv2/highgui/highgui.hpp>  
    6. #include<opencv2/imgproc/imgproc.hpp>  
    7. #include <iostream>  
    8.    
    9. //-----------------------------------【命名空间声明部分】---------------------------------------  
    10. //     描述:包含程序所使用的命名空间  
    11. //-----------------------------------------------------------------------------------------------   
    12. using namespace std;  
    13. using namespace cv;  
    14.    
    15. //-----------------------------------【main( )函数】--------------------------------------------  
    16. //     描述:控制台应用程序的入口函数,我们的程序从这里开始  
    17. //-----------------------------------------------------------------------------------------------  
    18. int main(  )  
    19. {  
    20.        //载入原图   
    21.        Matimage = imread("1.jpg");  
    22.    
    23.         //创建窗口   
    24.        namedWindow("【原图】腐蚀操作");  
    25.        namedWindow("【效果图】腐蚀操作");  
    26.    
    27.        //显示原图  
    28.        imshow("【原图】腐蚀操作", image);  
    29.    
    30.           
    31. //获取自定义核  
    32.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));  
    33.        Mat out;  
    34.    
    35. //进行腐蚀操作  
    36.        erode(image,out, element);  
    37.    
    38.        //显示效果图  
    39.        imshow("【效果图】腐蚀操作", out);  
    40.    
    41.        waitKey(0);  
    42.    
    43.        return 0;  
    44. }  


    运行结果:

     

     

     

     

    四、综合示例——在实战中熟稔

     

     

    依然是每篇文章都会配给大家的一个详细注释的博文配套示例程序,把这篇文章中介绍的知识点以代码为载体,展现给大家。

    这个示例程序中的效果图窗口有两个滚动条,顾名思义,第一个滚动条“腐蚀/膨胀”用于在腐蚀/膨胀之间进行切换;第二个滚动条”内核尺寸”用于调节形态学操作时的内核尺寸,以得到效果不同的图像,有一定的可玩性。废话不多说,上代码吧:

    [cpp] view plain copy
    1.    
    2. //-----------------------------------【程序说明】----------------------------------------------  
    3. //            程序名称::《【OpenCV入门教程之十】形态学图像处理(一):膨胀与腐蚀  》 博文配套源码  
    4. //            开发所用IDE版本:Visual Studio 2010  
    5. //          开发所用OpenCV版本: 2.4.8  
    6. //            2014年4月14日 Create by 浅墨  
    7. //            浅墨的微博:@浅墨_毛星云  
    8. //------------------------------------------------------------------------------------------------  
    9.    
    10. //-----------------------------------【头文件包含部分】---------------------------------------  
    11. //            描述:包含程序所依赖的头文件  
    12. //----------------------------------------------------------------------------------------------  
    13. #include <opencv2/opencv.hpp>  
    14. #include <opencv2/highgui/highgui.hpp>  
    15. #include<opencv2/imgproc/imgproc.hpp>  
    16. #include <iostream>  
    17.    
    18. //-----------------------------------【命名空间声明部分】---------------------------------------  
    19. //            描述:包含程序所使用的命名空间  
    20. //-----------------------------------------------------------------------------------------------  
    21. using namespace std;  
    22. using namespace cv;  
    23.    
    24.    
    25. //-----------------------------------【全局变量声明部分】--------------------------------------  
    26. //            描述:全局变量声明  
    27. //-----------------------------------------------------------------------------------------------  
    28. Mat g_srcImage, g_dstImage;//原始图和效果图  
    29. int g_nTrackbarNumer = 0;//0表示腐蚀erode, 1表示膨胀dilate  
    30. int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸  
    31.    
    32.    
    33. //-----------------------------------【全局函数声明部分】--------------------------------------  
    34. //            描述:全局函数声明  
    35. //-----------------------------------------------------------------------------------------------  
    36. void Process();//膨胀和腐蚀的处理函数  
    37. void on_TrackbarNumChange(intvoid *);//回调函数  
    38. void on_ElementSizeChange(intvoid *);//回调函数  
    39.    
    40.    
    41. //-----------------------------------【main( )函数】--------------------------------------------  
    42. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
    43. //-----------------------------------------------------------------------------------------------  
    44. int main( )  
    45. {  
    46.        //改变console字体颜色  
    47.        system("color5E");   
    48.    
    49.        //载入原图  
    50.        g_srcImage= imread("1.jpg");  
    51.        if(!g_srcImage.data ) { printf("Oh,no,读取srcImage错误~!\n"); return false; }  
    52.         
    53.        //显示原始图  
    54.        namedWindow("【原始图】");  
    55.        imshow("【原始图】", g_srcImage);  
    56.         
    57.        //进行初次腐蚀操作并显示效果图  
    58.        namedWindow("【效果图】");  
    59.        //获取自定义核  
    60.        Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize ));  
    61.        erode(g_srcImage,g_dstImage, element);  
    62.        imshow("【效果图】", g_dstImage);  
    63.    
    64.        //创建轨迹条  
    65.        createTrackbar("腐蚀/膨胀""【效果图】", &g_nTrackbarNumer, 1, on_TrackbarNumChange);  
    66.        createTrackbar("内核尺寸""【效果图】",&g_nStructElementSize, 21, on_ElementSizeChange);  
    67.    
    68.        //输出一些帮助信息  
    69.        cout<<endl<<"\t嗯。运行成功,请调整滚动条观察图像效果~\n\n"  
    70.               <<"\t按下“q”键时,程序退出~!\n"  
    71.               <<"\n\n\t\t\t\tby浅墨";  
    72.    
    73.        //轮询获取按键信息,若下q键,程序退出  
    74.        while(char(waitKey(1))!= 'q') {}  
    75.    
    76.        return 0;  
    77. }  
    78.    
    79. //-----------------------------【Process( )函数】------------------------------------  
    80. //            描述:进行自定义的腐蚀和膨胀操作  
    81. //-----------------------------------------------------------------------------------------  
    82. void Process()  
    83. {  
    84.        //获取自定义核  
    85.        Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize ));  
    86.    
    87.        //进行腐蚀或膨胀操作  
    88.        if(g_nTrackbarNumer== 0) {     
    89.               erode(g_srcImage,g_dstImage, element);  
    90.        }  
    91.        else{  
    92.               dilate(g_srcImage,g_dstImage, element);  
    93.        }  
    94.    
    95.        //显示效果图  
    96.        imshow("【效果图】", g_dstImage);  
    97. }  
    98.    
    99.    
    100. //-----------------------------【on_TrackbarNumChange( )函数】------------------------------------  
    101. //            描述:腐蚀和膨胀之间切换开关的回调函数  
    102. //-----------------------------------------------------------------------------------------------------  
    103. void on_TrackbarNumChange(intvoid *)  
    104. {  
    105.        //腐蚀和膨胀之间效果已经切换,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来  
    106.        Process();  
    107. }  
    108.    
    109.    
    110. //-----------------------------【on_ElementSizeChange( )函数】-------------------------------------  
    111. //            描述:腐蚀和膨胀操作内核改变时的回调函数  
    112. //-----------------------------------------------------------------------------------------------------  
    113. void on_ElementSizeChange(intvoid *)  
    114. {  
    115.        //内核尺寸已改变,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来  
    116.        Process();  
    117. }  


     

    放出一些效果图吧。原始图:

     


    膨胀效果图:

     






    腐蚀效果图:







    腐蚀和膨胀得到的图,都特有喜感,但千变万变,还是原图好看:



    OK,就放出这些吧,具体更多的运行效果大家就自己下载示例程序回去玩吧。


    本篇文章到这里就基本结束了,最后放出文章配套示例程序的打包下载地址。

     

    本篇文章的配套源代码请点击这里下载:


    【浅墨OpenCV入门教程之十】配套源代码下载



    原文地址:http://blog.csdn.net/poem_qianmo/article/details/23710721



    再提供一个比较好的博客 http://blog.csdn.net/TonyShengTan/article/details/42426033







































    展开全文
  • Matlab 形态学图像处理(原文作者很是细心,感谢!)  形态学是提取图像特征的有力工具,针对二值图像和灰度图像的腐蚀、膨胀和重构的基本操作可以组合使用,以执行非常宽泛的任务。其练习代码和结果如下:   ...

    【转载】:http://www.cnblogs.com/tornadomeet/archive/2012/03/20/2408086.html

    Matlab 形态学图像处理(原文作者很是细心,感谢!)

          形态学是提取图像特征的有力工具,针对二值图像和灰度图像的腐蚀、膨胀和重构的基本操作可以组合使用,以执行非常宽泛的任务。其练习代码和结果如下:

     

      1 %% 第9章 形态学处理
      2 
      3 %% imdilate膨胀
      4 clc
      5 clear
      6 
      7 A1=imread('.\images\dipum_images_ch09\Fig0906(a)(broken-text).tif');
      8 info=imfinfo('.\images\dipum_images_ch09\Fig0906(a)(broken-text).tif')
      9 B=[0 1 0
     10    1 1 1
     11    0 1 0];
     12 A2=imdilate(A1,B);%图像A1被结构元素B膨胀
     13 A3=imdilate(A2,B);
     14 A4=imdilate(A3,B);
     15 
     16 subplot(221),imshow(A1);
     17 title('imdilate膨胀原始图像');
     18 
     19 subplot(222),imshow(A2);
     20 title('使用B后1次膨胀后的图像');
     21 
     22 subplot(223),imshow(A3);
     23 title('使用B后2次膨胀后的图像');
     24 
     25 subplot(224),imshow(A4);
     26 title('使用B后3次膨胀后的图像');
     27%imdilate图像膨胀处理过程运行结果如下:

     28 
     29 %% imerode腐蚀
     30 clc
     31 clear
     32 A1=imread('.\images\dipum_images_ch09\Fig0908(a)(wirebond-mask).tif');
     33 subplot(221),imshow(A1);
     34 title('腐蚀原始图像');
     35 
     36 %strel函数的功能是运用各种形状和大小构造结构元素
     37 se1=strel('disk',5);%这里是创建一个半径为5的平坦型圆盘结构元素
     38 A2=imerode(A1,se1);
     39 subplot(222),imshow(A2);
     40 title('使用结构原始disk(5)腐蚀后的图像');
     41 
     42 se2=strel('disk',10);
     43 A3=imerode(A1,se2);
     44 subplot(223),imshow(A3);
     45 title('使用结构原始disk(10)腐蚀后的图像');
     46 
     47 se3=strel('disk',20);
     48 A4=imerode(A1,se3);
     49 subplot(224),imshow(A4);
     50 title('使用结构原始disk(20)腐蚀后的图像');
     51 %图像腐蚀处理过程运行结果如下:
     52 
     53 %% 开运算和闭运算
     54 clc
     55 clear
     56 f=imread('.\images\dipum_images_ch09\Fig0910(a)(shapes).tif');
     57 %se=strel('square',5');%方型结构元素
     58 se=strel('disk',5');%圆盘型结构元素
     59 imshow(f);%原图像
     60 title('开闭运算原始图像')
     61%运行结果如下:

     62 
     63 %开运算数学上是先腐蚀后膨胀的结果
     64 %开运算的物理结果为完全删除了不能包含结构元素的对象区域,平滑
     65 %了对象的轮廓,断开了狭窄的连接,去掉了细小的突出部分
     66 fo=imopen(f,se);%直接开运算
     67 figure,subplot(221),imshow(fo);
     68 title('直接开运算');
     69 
     70 %闭运算在数学上是先膨胀再腐蚀的结果
     71 %闭运算的物理结果也是会平滑对象的轮廓,但是与开运算不同的是,闭运算
     72 %一般会将狭窄的缺口连接起来形成细长的弯口,并填充比结构元素小的洞
     73 fc=imclose(f,se);%直接闭运算
     74 subplot(222),imshow(fc);
     75 title('直接闭运算');
     76 
     77 foc=imclose(fo,se);%先开后闭运算
     78 subplot(223),imshow(foc);
     79 title('先开后闭运算');
     80 
     81 fco=imopen(fc,se);%先闭后开运算
     82 subplot(224),imshow(fco);
     83 title('先闭后开运算');
     84%开闭运算结果如下:

     85 
     86 %先膨胀再腐蚀
     87 fse=imdilate(f,se);%膨胀
     88 
     89 %gcf为得到当前图像的句柄,当前图像是指例如PLOT,TITLE,SURF等
     90 %get函数为得到物体的属性,get(0,'screensize')为返回所有物体screensize属性值
     91 %set函数为设置物体的属性
     92 figure,set(gcf,'outerposition',get(0,'screensize'));%具体目的是设置当前窗口的大小
     93 subplot(211),imshow(fse);
     94 title('使用disk(5)先膨胀后的图像');
     95 
     96 fes=imerode(fse,se);
     97 subplot(212),imshow(fes);
     98 title('使用disk(5)先膨胀再腐蚀后的图像');
     99%先膨胀后腐蚀图像如下:

    100 
    101 %先腐蚀再膨胀
    102 fse=imerode(f,se);
    103 figure,set(gcf,'outerposition',get(0,'screensize'))
    104 subplot(211),imshow(fse);
    105 title('使用disk(5)先腐蚀后的图像');
    106 
    107 fes=imdilate(fse,se);
    108 subplot(212),imshow(fes);
    109 title('使用disk(5)先腐蚀再膨胀后的图像');
    110%先腐蚀后膨胀的图像如下:

    111 
    112 %% imopen imclose在指纹上的应用
    113 clc
    114 clear
    115 f=imread('.\images\dipum_images_ch09\Fig0911(a)(noisy-fingerprint).tif');
    116 se=strel('square',3);%边长为3的方形结构元素
    117 subplot(121),imshow(f);
    118 title('指纹原始图像');
    119 
    120 A=imerode(f,se);%腐蚀
    121 subplot(122),imshow(A);
    122 title('腐蚀后的指纹原始图像');
    123%指纹原始图像和腐蚀后的图像结果如下:

    124 
    125 fo=imopen(f,se);
    126 figure,subplot(221),imshow(fo);
    127 title('使用square(3)开操作后的图像');
    128 
    129 fc=imclose(f,se);
    130 subplot(222),imshow(fc);
    131 title('使用square闭操作后的图像');
    132 
    133 foc=imclose(fo,se);
    134 subplot(223),imshow(foc);
    135 title('使用square(3)先开后闭操作后的图像')
    136 
    137 fco=imopen(fc,se);
    138 subplot(224),imshow(fco);
    139 title('使用square(3)先闭后开操作后的图像');
    140%指纹图像开闭操作过程结果如下:

    141 
    142 %% bwhitmiss击中或击不中变换
    143 clc
    144 clear
    145 f=imread('.\images\dipum_images_ch09\Fig0913(a)(small-squares).tif');
    146 imshow(f);
    147 title('击中或不击中原始图像');
    148%击中或不击中原始图像显示结果如下:

    149 
    150 B1=strel([0 0 0;0 1 1;0 1 0]);%击中:要求击中所有1的位置
    151 B2=strel([1 1 1;1 0 0;1 0 0]);%击不中,要求击不中所有1的位置
    152 B3=strel([0 1 0;1 1 1;0 1 0]);%击中
    153 B4=strel([1 0 1;0 0 0;0 0 0]);%击不中
    154 B5=strel([0 0 0;0 1 0;0 0 0]);%击中
    155 B6=strel([1 1 1;1 0 0;1 0 0]);%击不中
    156 
    157 g=imerode(f,B1)&imerode(~f,B2)%利用定义来实现击中或击不中
    158 figure,subplot(221),imshow(g);
    159 title('定义实现组1击中击不中图像');
    160 
    161 g1=bwhitmiss(f,B1,B2);
    162 subplot(222),imshow(g1);
    163 title('结构数组1击中击不中后的图像');
    164 
    165 g2=bwhitmiss(f,B3,B4);
    166 subplot(223),imshow(g2);
    167 title('结构数组2击中击不中的图像');
    168 
    169 g3=bwhitmiss(f,B5,B6);
    170 subplot(224),imshow(g3);
    171 title('结构数组3击中击不中的图像');
    172%击中击不中变换后图像如下:

    173 
    174 %%makelut
    175 clc
    176 clear
    177 
    178 f=inline('sum(x(:))>=3');%inline是用来定义局部函数的
    179 lut2=makelut(f,2)%为函数f构造一个接收2*2矩阵的查找表
    180 lut3=makelut(f,3)
    181 
    182 %% Conway生命游戏
    183 clc
    184 clear
    185 lut=makelut(@conwaylaws,3);
    186 bw1=  [0     0     0     0     0     0     0     0     0     0
    187        0     0     0     0     0     0     0     0     0     0
    188        0     0     0     1     0     0     1     0     0     0
    189        0     0     0     1     1     1     1     0     0     0
    190        0     0     1     0     0     0     0     1     0     0
    191        0     0     1     0     1     1     0     1     0     0
    192        0     0     1     0     0     0     0     1     0     0
    193        0     0     0     1     1     1     1     0     0     0
    194        0     0     0     0     0     0     0     0     0     0
    195        0     0     0     0     0     0     0     0     0     0  ];
    196 subplot(221),imshow(bw1,'InitialMagnification','fit');
    197 title('Generation 1');
    198 
    199 bw2=applylut(bw1,lut);
    200 subplot(222),imshow(bw2,'InitialMagnification','fit'),
    201 title('Generation 2');
    202 
    203 bw3=applylut(bw2,lut);
    204 subplot(223),imshow(bw3,'InitialMagnification','fit');
    205 title('Generation 3');
    206 
    207 temp=bw1;
    208 for i=2:100
    209     bw100=applylut(temp,lut);
    210     temp=bw100;
    211 end
    212 subplot(224),imshow(bw100,'InitialMagnification','fit')
    213 title('Generation 100');
    214%显示Generation结果如下:

    215 
    216 %% getsequence
    217 clc
    218 clear
    219 se=strel('diamond',5)
    220 decomp=getsequence(se)%getsequence函数为得到分解的strel序列
    221 decomp(1)
    222 decomp(2)
    223 
    224 %% endpoints
    225 clc
    226 clear
    227 
    228 f1=imread('.\images\dipum_images_ch09\Fig0914(a)(bone-skel).tif');
    229 subplot(121),imshow(f1);
    230 title('原始形态骨架图像');
    231 
    232 g1=endpoints(f1);
    233 %set(gcf,'outerposition',get(0,'screensize'));%运行完后自动生成最大的窗口
    234 subplot(122),imshow(g1);
    235 title('骨架图像的端点图像');
    236 %骨架头像端点检测头像如下:
    237 
    238 f2=imread('.\images\dipum_images_ch09\Fig0916(a)(bone).tif');
    239 figure,subplot(121),imshow(f2);
    240 title('原始骨头图像');
    241 
    242 g2=endpoints(f2);
    243 subplot(122),imshow(g2);
    244 title('骨头图像端点头像');%结果是没有端点
    245%骨头头像端点检测图像如下:

    246 
    247 %% bwmorph组合常见形态学之细化
    248 clc
    249 clear
    250 f=imread('.\images\dipum_images_ch09\Fig0911(a)(noisy-fingerprint).tif');
    251 subplot(221),imshow(f);
    252 title('指纹图像细化原图');
    253 
    254 g1=bwmorph(f,'thin',1);
    255 subplot(222),imshow(g1);
    256 title('指纹图像细化原图');
    257 
    258 g2=bwmorph(f,'thin',2);
    259 subplot(223),imshow(g2);
    260 title('指纹图像细化原图');
    261 
    262 g3=bwmorph(f,'thin',Inf);
    263 subplot(224),imshow(g3);
    264 title('指纹图像细化原图');
    265%指纹图像细化过程显示如下:

    266 
    267 %% bwmorph组合常见形态学之骨骼化
    268 clc
    269 clear
    270 f=imread('.\images\dipum_images_ch09\Fig0911(a)(noisy-fingerprint).tif');
    271 subplot(131),imshow(f);
    272 title('指纹图像骨骼化原图');
    273 
    274 fs=bwmorph(f,'skel',Inf);
    275 subplot(132),imshow(fs);
    276 title('指纹图像骨骼化');
    277 
    278 for k=1:5
    279     fs=fs&~endpoints(fs);
    280 end
    281 subplot(133),imshow(fs);
    282 title('指纹图像修剪后骨骼话');
    283%指纹图像骨骼化过程显示:

    284 
    285 %% 使用函数bwlabel标注连通分量
    286 clc
    287 clear
    288 f=imread('.\images\dipum_images_ch09\Fig0917(a)(ten-objects).tif');
    289 imshow(f),title('标注连通分量原始图像');
    290%其结果显示如下:

    291 
    292 [L,n]=bwlabel(f);%L为标记矩阵,n为找到连接分量的总数
    293 [r,c]=find(L==3);%返回第3个对象所有像素的行索引和列索引
    294 
    295 rbar=mean(r);
    296 cbar=mean(c);
    297 
    298 figure,imshow(f)
    299 hold on%保持当前图像使其不被刷新
    300 for k=1:n
    301     [r,c]=find(L==k);
    302     rbar=mean(r);
    303     cbar=mean(c);
    304     plot(cbar,rbar,'Marker','o','MarkerEdgeColor','k',...
    305          'MarkerFaceColor','k','MarkerSize',10);%这个plot函数用法不是很熟悉
    306     plot(cbar,rbar,'Marker','*','MarkerFaceColor','w');%其中的marker为标记
    307 end
    308 title('标记所有对象质心后的图像');

    309 
    310 %% 由重构做开运算
    311 clc
    312 clear
    313 f=imread('.\images\dipum_images_ch09\Fig0922(a)(book-text).tif');
    314 subplot(321),imshow(f);
    315 title('重构原始图像');
    316 
    317 fe=imerode(f,ones(51,1));%竖线腐蚀
    318 subplot(322),imshow(fe);
    319 title('使用竖线腐蚀后的结果');
    320 
    321 fo=imopen(f,ones(51,1));%竖线做开运算
    322 subplot(323),imshow(fo);
    323 title('使用竖线做开运算结果');
    324 
    325 fobr=imreconstruct(fe,f);%fe做标记
    326 subplot(324),imshow(fobr);
    327 title('使用竖线做重构开运算');
    328 
    329 ff=imfill(f,'holes');%对f进行孔洞填充
    330 subplot(325),imshow(ff);
    331 title('对f填充孔洞后的图像');
    332 
    333 fc=imclearborder(f,8);%清除边界,2维8邻接
    334 subplot(326),imshow(fc);
    335 title('对f清除边界后的图像');
    336%图像重构过程显示如下:

    337 
    338 %% 使用顶帽变换和底帽变换
    339 clc
    340 clear
    341 f=imread('.\images\dipum_images_ch09\Fig0926(a)(rice).tif');
    342 subplot(221),imshow(f);
    343 title('顶帽底帽变换原始图像');
    344 
    345 se=strel('disk',10);%产生结构元素
    346 %顶帽变换是指原始图像减去其开运算的图像
    347 %而开运算可用于补偿不均匀的背景亮度,所以用一个大的结构元素做开运算后
    348 %然后用原图像减去这个开运算,就得到了背景均衡的图像,这也叫做是图像的顶帽运算
    349 f1=imtophat(f,se);%使用顶帽变换
    350 subplot(222),imshow(f1);
    351 title('使用顶帽变换后的图像');
    352 
    353 %底帽变换是原始图像减去其闭运算后的图像
    354 f2=imbothat(imcomplement(f),se);%使用底帽变换,为什么原图像要求补呢?
    355 %f2=imbothat(f,se);%使用底帽变换
    356 subplot(223),imshow(f2);
    357 title('使用底帽变换后的图像');
    358 
    359 %顶帽变换和底帽变换联合起来用,用于增加对比度
    360 f3=imsubtract(imadd(f,imtophat(f,se)),imbothat(f,se));%里面参数好像不合理?
    361 subplot(224),imshow(f3);
    362 title('使用顶帽底帽联合变换后图像');
    363%顶帽底帽变换过程图像如下:

    364 
    365 %%使用开运算和闭运算做形态学平滑
    366 %由于开运算可以除去比结构元素更小的明亮细节,闭运算可以除去比结构元素更小的暗色细节
    367 %所以它们经常组合起来一起进行平滑图像并去除噪声
    368 clc
    369 clear
    370 f=imread('.\images\dipum_images_ch09\Fig0925(a)(dowels).tif');
    371 subplot(221),imshow(f);
    372 title('木钉图像原图');
    373 
    374 se=strel('disk',5);%disk其实就是一个八边形
    375 fo=imopen(f,se);%经过开运算
    376 subplot(222),imshow(f);
    377 title('使用半径5的disk开运算后的图像');
    378 
    379 foc=imclose(fo,se);
    380 subplot(223),imshow(foc);
    381 title('先开后闭的图像');
    382 
    383 fasf=f;
    384 for i=2:5
    385     se=strel('disk',i);
    386     fasf=imclose(imopen(fasf,se),se);
    387 end
    388 subplot(224),imshow(fasf);
    389 title('使用开闭交替滤波后图像');
    390%使用开运算和闭运算做形态学平滑结果如下:

    391 
    392 %% 颗粒分析
    393 clc
    394 clear
    395 f=imread('.\images\dipum_images_ch09\Fig0925(a)(dowels).tif');
    396 
    397 sumpixels=zeros(1,36);
    398 for k=0:35
    399     se=strel('disk',k);
    400     fo=imopen(f,se);
    401     sumpixels(k+1)=sum(fo(:));
    402 end
    403 
    404 %可以看到,连续开运算之间的表面积会减少
    405 plot(0:35,sumpixels),xlabel('k'),ylabel('surface area');
    406 title('表面积和结构元素半径之间的关系');
    407%其运算结果如下:   

    408 
    409 figure,plot(-diff(sumpixels));%diff()函数为差分或者近似倒数,即相邻2个之间的差值
    410 xlabel('k'),ylabel('surface area reduction');
    411 title('减少的表面积和结构元素半径之间的关系');
    412%其运算结果如下:

    413 
    414 %% 使用重构删除复杂图像的背景
    415 clc
    416 clear
    417 f=imread('.\images\dipum_images_ch09\Fig0930(a)(calculator).tif');
    418 subplot(221),imshow(f);
    419 title('灰度级重构原图像');
    420 
    421 f_obr=imreconstruct(imerode(f,ones(1,71)),f);
    422 subplot(222),imshow(f_obr);
    423 title('经开运算重构图');
    424 
    425 f_o=imopen(f,ones(1,71));
    426 subplot(223),imshow(f_o);
    427 title('经开运算后图');
    428 
    429 f_thr=imsubtract(f,f_obr);
    430 subplot(224),imshow(f_thr);
    431 title('顶帽运算重构图')
    432%使用重构删除复杂图像的背景1:

    433 
    434 f_th=imsubtract(f,f_o)
    435 figure,subplot(221),imshow(f_th);
    436 title('经顶帽运算图');
    437 
    438 g_obr=imreconstruct(imerode(f_thr,ones(1,11)),f_thr);
    439 subplot(222),imshow(g_obr);
    440 title('用水平线对f_thr经开运算后重构图');
    441 
    442 g_obrd=imdilate(g_obr,ones(1,2));
    443 subplot(223),imshow(g_obrd);
    444 title('使用水平线对上图进行膨胀');
    445 
    446 f2=imreconstruct(min(g_obrd,f_thr),f_thr);
    447 subplot(224),imshow(f2);
    448 title('最后的重构结果');
    449%使用重构删除复杂图像的背景2:

     

        形态学这一章很有用,因为它还可以应用在图像分割中。

     

     

    展开全文
  • 而我们图像处理中指的形态学,往往表示的是数学形态学。下面一起来了解数学形态学的概念。 数学形态学(Mathematical morphology) 是一门建立在格论和拓扑学基础之上的图像分析学科,是数学形态学图像处理的基本...


    本系列文章由@浅墨_毛星云 出品,转载请注明出处。  

    文章链接: http://blog.csdn.net/poem_qianmo/article/details/23710721

    作者:毛星云(浅墨)    邮箱: happylifemxy@163.com 

    写作当前博文时配套使用的OpenCV版本: 2.4.8



    本篇文章中,我们一起探究了图像处理中,最基本的形态学运算——膨胀与腐蚀。浅墨在文章开头友情提醒,用人物照片做腐蚀和膨胀的素材图片得到的效果会比较惊悚,毁三观的,不建议尝试。。。。。。。。。。


    OK,开始吧,依然是先放一张截图:





    一、理论与概念讲解——从现象到本质



    1.1 形态学概述

     

    形态学(morphology)一词通常表示生物学的一个分支,该分支主要研究动植物的形态和结构。而我们图像处理中指的形态学,往往表示的是数学形态学。下面一起来了解数学形态学的概念。

    数学形态学(Mathematical morphology) 是一门建立在格论和拓扑学基础之上的图像分析学科,是数学形态学图像处理的基本理论。其基本的运算包括:二值腐蚀和膨胀、二值开闭运算、骨架抽取、极限腐蚀、击中击不中变换、形态学梯度、Top-hat变换、颗粒分析、流域变换、灰值腐蚀和膨胀、灰值开闭运算、灰值形态学梯度等。

     

    简单来讲,形态学操作就是基于形状的一系列图像处理操作。OpenCV为进行图像的形态学变换提供了快捷、方便的函数。最基本的形态学操作有二种,他们是:膨胀与腐蚀(Dilation与Erosion)。

    膨胀与腐蚀能实现多种多样的功能,主要如下:

    • 消除噪声
    • 分割(isolate)出独立的图像元素,在图像中连接(join)相邻的元素。
    • 寻找图像中的明显的极大值区域或极小值区域
    • 求出图像的梯度

     


    我们在这里给出下文会用到的,用于对比膨胀与腐蚀运算的“浅墨”字样毛笔字原图:

     

    在进行腐蚀和膨胀的讲解之前,首先需要注意,腐蚀和膨胀是对白色部分(高亮部分)而言的,不是黑色部分。膨胀就是图像中的高亮部分进行膨胀,“领域扩张”,效果图拥有比原图更大的高亮区域。腐蚀就是原图中的高亮部分被腐蚀,“领域被蚕食”,效果图拥有比原图更小的高亮区域。

     





    1.2 膨胀

     

    其实,膨胀就是求局部最大值的操作。

    按数学方面来说,膨胀或者腐蚀操作就是将图像(或图像的一部分区域,我们称之为A)与核(我们称之为B)进行卷积。

    核可以是任何的形状和大小,它拥有一个单独定义出来的参考点,我们称其为锚点(anchorpoint)。多数情况下,核是一个小的中间带有参考点和实心正方形或者圆盘,其实,我们可以把核视为模板或者掩码。

     

    而膨胀就是求局部最大值的操作,核B与图形卷积,即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素。这样就会使图像中的高亮区域逐渐增长。如下图所示,这就是膨胀操作的初衷。



    膨胀的数学表达式:


    膨胀效果图(毛笔字):

     

    照片膨胀效果图:


     



    1.3 腐蚀


    再来看一下腐蚀,大家应该知道,膨胀和腐蚀是一对好基友,是相反的一对操作,所以腐蚀就是求局部最小值的操作。

    我们一般都会把腐蚀和膨胀对应起来理解和学习。下文就可以看到,两者的函数原型也是基本上一样的。

     

    原理图:

     

    腐蚀的数学表达式:

     

    腐蚀效果图(毛笔字):


    照片腐蚀效果图:

     

     浅墨表示这张狗狗超可爱:D

     

     



    二、深入——OpenCV源码分析溯源

     


    直接上源码吧,在…\opencv\sources\modules\imgproc\src\ morph.cpp路径中 的第1353行开始就为erode(腐蚀)函数的源码,1361行为dilate(膨胀)函数的源码。

    //-----------------------------------【erode()函数中文注释版源代码】---------------------------- 
    //    说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码 
    //    OpenCV源代码版本:2.4.8 
    //    源码路径:…\opencv\sources\modules\imgproc\src\ morph.cpp 
    //    源文件中如下代码的起始行数:1353行 
    //    中文注释by浅墨 
    //--------------------------------------------------------------------------------------------------------  
    void cv::erode( InputArray src, OutputArraydst, InputArray kernel,
                    Point anchor, int iterations,
                    int borderType, constScalar& borderValue )
    {
    //调用morphOp函数,并设定标识符为MORPH_ERODE
       morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType,borderValue );
    }

    //-----------------------------------【dilate()函数中文注释版源代码】---------------------------- 
    //    说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码 
    //    OpenCV源代码版本:2.4.8 
    //    源码路径:…\opencv\sources\modules\imgproc\src\ morph.cpp 
    //    源文件中如下代码的起始行数:1361行 
    //    中文注释by浅墨 
    //-------------------------------------------------------------------------------------------------------- 
    void cv::dilate( InputArray src,OutputArray dst, InputArray kernel,
                     Point anchor, int iterations,
                     int borderType, constScalar& borderValue )
    {
    //调用morphOp函数,并设定标识符为MORPH_DILATE
       morphOp( MORPH_DILATE, src, dst, kernel, anchor, iterations, borderType,borderValue );
    }


    可以发现erode和dilate这两个函数内部就是调用了一下morphOp,只是他们调用morphOp时,第一个参数标识符不同,一个为MORPH_ERODE(腐蚀),一个为MORPH_DILATE(膨胀)。

    morphOp函数的源码在…\opencv\sources\modules\imgproc\src\morph.cpp中的第1286行,有兴趣的朋友们可以研究研究,这里就不费时费力花篇幅展开分析了。

     

     

     

    三、浅出——API函数快速上手

     



    3.1  形态学膨胀——dilate函数

     


    erode函数,使用像素邻域内的局部极大运算符来膨胀一张图片,从src输入,由dst输出。支持就地(in-place)操作。

    函数原型:

    C++: void dilate(
    	InputArray src,
    	OutputArray dst,
    	InputArray kernel,
    	Point anchor=Point(-1,-1),
    	int iterations=1,
    	int borderType=BORDER_CONSTANT,
    	const Scalar& borderValue=morphologyDefaultBorderValue() 
    );

    参数详解:

    • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
    • 第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
    • 第三个参数,InputArray类型的kernel,膨胀操作的核。若为NULL时,表示的是使用参考点位于中心3x3的核。

    我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。

    其中,getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一:

      • 矩形: MORPH_RECT
      • 交叉形: MORPH_CROSS
      • 椭圆形: MORPH_ELLIPSE

    而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。

    我们一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。且需要注意,十字形的element形状唯一依赖于锚点的位置。而在其他情况下,锚点只是影响了形态学运算结果的偏移。

    getStructuringElement函数相关的调用示例代码如下:

     int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
     
    //获取自定义核
    Mat element = getStructuringElement(MORPH_RECT,
    	Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),
    	Point( g_nStructElementSize, g_nStructElementSize ));


    调用这样之后,我们便可以在接下来调用erode或dilate函数时,第三个参数填保存了getStructuringElement返回值的Mat类型变量。对应于我们上面的示例,就是填element变量。


    • 第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。
    • 第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
    • 第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
    • 第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。
    •  

    使用erode函数,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。

    调用范例:

           	//载入原图 
           	Mat image = imread("1.jpg");
    	//获取自定义核
           	Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
           	Mat out;
           	//进行膨胀操作
           	dilate(image, out, element);

    用上面核心代码架起来的完整程序代码:

     

    //-----------------------------------【头文件包含部分】---------------------------------------
    //     描述:包含程序所依赖的头文件
    //----------------------------------------------------------------------------------------------
    #include <opencv2/core/core.hpp>
    #include<opencv2/highgui/highgui.hpp>
    #include<opencv2/imgproc/imgproc.hpp>
    #include <iostream>
     
    //-----------------------------------【命名空间声明部分】---------------------------------------
    //     描述:包含程序所使用的命名空间
    //----------------------------------------------------------------------------------------------- 
    using namespace std;
    using namespace cv;
     
    //-----------------------------------【main( )函数】--------------------------------------------
    //     描述:控制台应用程序的入口函数,我们的程序从这里开始
    //-----------------------------------------------------------------------------------------------
    int main(  )
    {
     
           //载入原图 
           Mat image = imread("1.jpg");
     
           //创建窗口 
           namedWindow("【原图】膨胀操作");
           namedWindow("【效果图】膨胀操作");
     
           //显示原图
           imshow("【原图】膨胀操作", image);
     
    	//获取自定义核
           Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
           Mat out;
    	//进行膨胀操作
           dilate(image,out, element);
     
           //显示效果图
           imshow("【效果图】膨胀操作", out);
     
           waitKey(0);
     
           return 0;
    }

     运行截图:



     

     

     

    3.2 形态学腐蚀——erode函数



    erode函数,使用像素邻域内的局部极小运算符来腐蚀一张图片,从src输入,由dst输出。支持就地(in-place)操作。

     

    看一下函数原型:

    C++: void erode(
    	InputArray src,
    	OutputArray dst,
    	InputArray kernel,
    	Point anchor=Point(-1,-1),
    	int iterations=1,
    	int borderType=BORDER_CONSTANT,
    	const Scalar& borderValue=morphologyDefaultBorderValue()
     );

    参数详解:

    • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
    • 第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
    • 第三个参数,InputArray类型的kernel,腐蚀操作的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。(具体看上文中浅出部分dilate函数的第三个参数讲解部分)
    • 第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于单位(element)的中心,我们一般不用管它。
    • 第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
    • 第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
    • 第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。

    同样的,使用erode函数,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。

    调用范例:

           	//载入原图 
           	Mat image = imread("1.jpg");
    	//获取自定义核
           	Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
           	Mat out;
           	//进行腐蚀操作
           	erode(image,out, element);

    用上面核心代码架起来的完整程序代码:

     

    //-----------------------------------【头文件包含部分】---------------------------------------
    //     描述:包含程序所依赖的头文件
    //----------------------------------------------------------------------------------------------
    #include <opencv2/core/core.hpp>
    #include<opencv2/highgui/highgui.hpp>
    #include<opencv2/imgproc/imgproc.hpp>
    #include <iostream>
     
    //-----------------------------------【命名空间声明部分】---------------------------------------
    //     描述:包含程序所使用的命名空间
    //----------------------------------------------------------------------------------------------- 
    using namespace std;
    using namespace cv;
     
    //-----------------------------------【main( )函数】--------------------------------------------
    //     描述:控制台应用程序的入口函数,我们的程序从这里开始
    //-----------------------------------------------------------------------------------------------
    int main(  )
    {
           //载入原图 
           Matimage = imread("1.jpg");
     
            //创建窗口 
           namedWindow("【原图】腐蚀操作");
           namedWindow("【效果图】腐蚀操作");
     
           //显示原图
           imshow("【原图】腐蚀操作", image);
     
            
    //获取自定义核
           Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
           Mat out;
     
    //进行腐蚀操作
           erode(image,out, element);
     
           //显示效果图
           imshow("【效果图】腐蚀操作", out);
     
           waitKey(0);
     
           return 0;
    }


    运行结果:

     

     

     

     

    四、综合示例——在实战中熟稔

     

     

    依然是每篇文章都会配给大家的一个详细注释的博文配套示例程序,把这篇文章中介绍的知识点以代码为载体,展现给大家。

    这个示例程序中的效果图窗口有两个滚动条,顾名思义,第一个滚动条“腐蚀/膨胀”用于在腐蚀/膨胀之间进行切换;第二个滚动条”内核尺寸”用于调节形态学操作时的内核尺寸,以得到效果不同的图像,有一定的可玩性。废话不多说,上代码吧:

     
    //-----------------------------------【程序说明】----------------------------------------------
    //            程序名称::《【OpenCV入门教程之十】形态学图像处理(一):膨胀与腐蚀  》 博文配套源码
    //            开发所用IDE版本:Visual Studio 2010
    //          开发所用OpenCV版本: 2.4.8
    //            2014年4月14日 Create by 浅墨
    //            浅墨的微博:@浅墨_毛星云
    //------------------------------------------------------------------------------------------------
     
    //-----------------------------------【头文件包含部分】---------------------------------------
    //            描述:包含程序所依赖的头文件
    //----------------------------------------------------------------------------------------------
    #include <opencv2/opencv.hpp>
    #include <opencv2/highgui/highgui.hpp>
    #include<opencv2/imgproc/imgproc.hpp>
    #include <iostream>
     
    //-----------------------------------【命名空间声明部分】---------------------------------------
    //            描述:包含程序所使用的命名空间
    //-----------------------------------------------------------------------------------------------
    using namespace std;
    using namespace cv;
     
     
    //-----------------------------------【全局变量声明部分】--------------------------------------
    //            描述:全局变量声明
    //-----------------------------------------------------------------------------------------------
    Mat g_srcImage, g_dstImage;//原始图和效果图
    int g_nTrackbarNumer = 0;//0表示腐蚀erode, 1表示膨胀dilate
    int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
     
     
    //-----------------------------------【全局函数声明部分】--------------------------------------
    //            描述:全局函数声明
    //-----------------------------------------------------------------------------------------------
    void Process();//膨胀和腐蚀的处理函数
    void on_TrackbarNumChange(int, void *);//回调函数
    void on_ElementSizeChange(int, void *);//回调函数
     
     
    //-----------------------------------【main( )函数】--------------------------------------------
    //            描述:控制台应用程序的入口函数,我们的程序从这里开始
    //-----------------------------------------------------------------------------------------------
    int main( )
    {
           //改变console字体颜色
           system("color5E"); 
     
           //载入原图
           g_srcImage= imread("1.jpg");
           if(!g_srcImage.data ) { printf("Oh,no,读取srcImage错误~!\n"); return false; }
          
           //显示原始图
           namedWindow("【原始图】");
           imshow("【原始图】", g_srcImage);
          
           //进行初次腐蚀操作并显示效果图
           namedWindow("【效果图】");
           //获取自定义核
           Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize ));
           erode(g_srcImage,g_dstImage, element);
           imshow("【效果图】", g_dstImage);
     
           //创建轨迹条
           createTrackbar("腐蚀/膨胀", "【效果图】", &g_nTrackbarNumer, 1, on_TrackbarNumChange);
           createTrackbar("内核尺寸", "【效果图】",&g_nStructElementSize, 21, on_ElementSizeChange);
     
           //输出一些帮助信息
           cout<<endl<<"\t嗯。运行成功,请调整滚动条观察图像效果~\n\n"
                  <<"\t按下“q”键时,程序退出~!\n"
                  <<"\n\n\t\t\t\tby浅墨";
     
           //轮询获取按键信息,若下q键,程序退出
           while(char(waitKey(1))!= 'q') {}
     
           return 0;
    }
     
    //-----------------------------【Process( )函数】------------------------------------
    //            描述:进行自定义的腐蚀和膨胀操作
    //-----------------------------------------------------------------------------------------
    void Process()
    {
           //获取自定义核
           Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize ));
     
           //进行腐蚀或膨胀操作
           if(g_nTrackbarNumer== 0) {   
                  erode(g_srcImage,g_dstImage, element);
           }
           else{
                  dilate(g_srcImage,g_dstImage, element);
           }
     
           //显示效果图
           imshow("【效果图】", g_dstImage);
    }
     
     
    //-----------------------------【on_TrackbarNumChange( )函数】------------------------------------
    //            描述:腐蚀和膨胀之间切换开关的回调函数
    //-----------------------------------------------------------------------------------------------------
    void on_TrackbarNumChange(int, void *)
    {
           //腐蚀和膨胀之间效果已经切换,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来
           Process();
    }
     
     
    //-----------------------------【on_ElementSizeChange( )函数】-------------------------------------
    //            描述:腐蚀和膨胀操作内核改变时的回调函数
    //-----------------------------------------------------------------------------------------------------
    void on_ElementSizeChange(int, void *)
    {
           //内核尺寸已改变,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来
           Process();
    }


     

    放出一些效果图吧。原始图:

     


    膨胀效果图:

     






    腐蚀效果图:







    腐蚀和膨胀得到的图,都特有喜感,但千变万变,还是原图好看:



    OK,就放出这些吧,具体更多的运行效果大家就自己下载示例程序回去玩吧。


    本篇文章到这里就基本结束了,最后放出文章配套示例程序的打包下载地址。

     

    本篇文章的配套源代码请点击这里下载:


    【浅墨OpenCV入门教程之十】配套源代码下载

     


    OK,今天的内容大概就是这些,我们下篇文章见:)




    展开全文
  • 二值图像形态学运算时图像形态学运算的基础。二值图像形态学运算的过程就是在图像...二值图像形态学处理的基本运算有腐蚀、膨胀、开运算、闭运算,击中与击不中、骨架抽取等。注意,本文所有例子都是24位真彩色图...
  • 腐蚀:用结构元对图像进行腐蚀可以达到缩小或者细化图像的效果。同时,结构元大小选择的不同,腐蚀的次数不同,都会对腐蚀结果造成影响 特点:缩小 细化 应用:去除图像中的某些部件 膨胀:用结构单元对图像进行...
  • 数学形态学诞生于1964年,膨胀是在图像中目标结界周围增添图像,结构元素。 腐蚀:移除图像中目标边界的像素,结构元素B对集合A的腐蚀。 开运算与闭运算 matlab code: clc; clear all; close all; %灰度膨胀、腐蚀...
  • 通常极限腐蚀法经常用在从粘连细胞或颗粒中得到种子点,并以此为基础构建分离线分割出单个细胞或颗粒图像。下面是matlab极限腐蚀代码参考,原图、效果图如下: 代码如下,可以用不同形状的卷积核进行腐蚀,得到...
  • 目录 1 形态学操作 2 图像腐蚀 3 图像膨胀 参考资料 1 形态学操作 ...形态学(morphology)一词通常表示生物学的一个分支,该分支...形态学处理主要针对的是二值图像(0或1)。 形态学通常使用图像腐蚀图像膨胀...
  • 膨胀就是求局部最大值的操作(dilate),腐蚀就是求局部最小值的操作(erode),无论是膨胀操作还是腐蚀操作就是蒋图片或者额图像的一部分区域,标记为A与核标记为B进行卷积。 代码: #include"stdafx.h" #include #...
  •  形态学(morphology)一词通常表示生物学的一个分支,该分支主要研究动植物的形态和结构,而图像处理中的形态学主要是指数学形态学。  数学形态学(Mathematical morphology)是一门建立在格论和拓扑学基础之上...
  • 数学形态学是基于集合论的图像处理方法,最早出现在生物学的形态与结构中,图像处理中的形态学操作用于图像与处理操作(去噪,形状简化)图像增强(骨架提取,细化,凸包及物体标记)、物体背景分割及物体形态量化等...
  • 图像的形态学操作 接着前面的图像入门,现在介绍一些简单的图像处理方法,同时在介绍方法之后,还会附上Python的实现,主要是基于Opencv这一库来进行实现。 网上关于简单的图像处理的文章有很多很多,后面简单的...
  • 数字图像处理第九章数字图像处理---形态学图像处理(一)预备知识1.1 预备知识1.1.1 集合理论中的基本概念1.2 二值图像、集合及逻辑算子(二)膨胀和腐蚀2.1 膨胀2.2 结构元的分解2.3 strel函数2.4 腐蚀(三) 膨胀...
  • 小白学习图像处理——图像的形态学处理(MATLAB)

    万次阅读 多人点赞 2019-06-27 05:26:49
    腐蚀在数学形态学运算中的作用是消除物体的边界点。在数字图像处理中,对于确定的结构元素,通过腐蚀运算可以消除小于结构元素的点。同时,若一个目标区域中含有细小的连接部分,那么通过笃实处理可以对区域进行分割...
  • 形态学图像处理

    万次阅读 多人点赞 2016-12-31 16:17:01
    形态学,即数学形态学(mathematical Morphology),是图像处理中应用最为广泛的技术之一,主要用于从图像中提取对表达和描绘区域形状有意义的图像分量,使后续的识别工作能够抓住目标对象最为本质〈最具区分能力-...
1 2 3 4 5 ... 20
收藏数 4,306
精华内容 1,722
关键字:

图像处理 形态学 腐蚀