二值图像--形态学处理2

学习DIP第12天

开篇废话

今天来介绍形态学中最基础也是最重要的两个操作,腐蚀和膨胀,腐蚀和膨胀基本上是所有形态学操作的基础,除此之外还有补集(即二值图全部取反的操作,0变1,1变0),和反射(将所有坐标去反)。

之前使用过腐蚀和膨胀,仅仅是去噪,那时候连结构元(SE)的概念都没有,其实所有形态学操作,核心都是结构元(包括其形状和中心位置,中心位置可以不在结构元区域中),他的变化可以产生千奇百怪的效果,如果你能很好的设计结构元,那么你将能得到你想要的效果。

对于写博客,我觉得坚持下来还是不错的,一是可以反思总结一下学习结果,很多收获都是写博客的时候想到的,虽然写起来浪费很多时间,包括作图之类的工作。二是可以作为资料,以后查看,查漏补缺。三是与别人分享知识,如果有问题,可以有人及时指正,良师益友。

应用

腐蚀和膨胀的应用应该很多,因为其操作简单,属于基础运算,这里简单列举下用途,还是那句话,SE是关键,设计好了可以处理很多二值图像的问题。

膨胀:

  • 桥接缝隙(缝隙点为0,且宽度比SE的宽度小)
  • 消除细小的黑点(二值图像中的0,黑点比SE小)

腐蚀:

  • 消除“桥梁”(细线装的白色条纹,值为1,宽度小于SE的宽度)
  • 消除细小的白点(二值图像中的1,白点比SE小)

此外,经过组合,腐蚀和膨胀将完成基本全部的形态学操作,例如后面介绍的,开操作,闭操作,命中与不命中,提取边缘,提取骨骼,裁剪等等。

数学基础

数学形态学的数学基础是集合论,Milan Sonka etc.的《图像处理、分析与机器视觉》(以下简称IPAMV)中提到的一篇论文【Serra,1982】(此文在结尾附下载地址,版权归IEEE所有,请勿用于商业用途),文中给出了数学形态学的基本操作,和定义,如下:

上图给出了最基本的腐蚀和膨胀操作,以及平移操作,由于上图中,英文较为简单,这里不再过多的解释,基本操作都是集合操作,如,交并补集操作。

首先,我在看这篇文章之前,对于腐蚀膨胀一直停留在SE划过窗口的模式,也就是,SE在图像上扫描,满足某些条件时进行某些操作,但这么做的问题在于,如果SE比窗口大,按照上述将无法操作,而且,我们无法验证膨胀的交换不变性,A膨胀B=B膨胀A,因为除非A,B大小相等,否者大的无法在小的上滑动。

而文中的标准定义是,对图像X进行移动,包括b1,b2,b3,b4...bn即集合B中的所有移动的结构的并集,换句话说,就是集合A使用SE B集合在膨胀,等于B的子集分别腐蚀的并集(子集的并集等于B)。

上图,做了好久的图:

STEP1:首先观察左侧SE,红色为中心,和明显,SE具有各向同性,这也是一个结构元的重要特点,即各向同性与各向异性有不同的效果,也有不同的应用。

STEP2:SE显示,集合X需要向左移1个单位。

STEP3:SE显示,集合X需要向右移1个单位。

STEP4:SE显示,集合X需要向上移1个单位。

STEP5:SE显示,集合X需要向下移1个单位。

STEP last:将所有结果取并集,两种颜色的表示两种操作都能产生那个单元。黑色为原始集合。

其实这种解释方法比SE滑过窗口的解释更加严谨,也更容易理解,其实SE滑过窗口的原理与着相同,只是用了类似分治的思想。但我实现起来好像两种方法的算法复杂度差不多。

说道复杂度,由于腐蚀和膨胀属于集合操作,所以,不属于线性操作。

腐蚀不是膨胀的逆操作,虽然有时可以经过腐蚀后膨胀来恢复原图,但他们并不是一对互逆的操作。其具有对偶性,后续介绍(因为数学公式不好输入)

腐蚀的具体操作与膨胀类似,但有以下不同:

首先,移动方向,与膨胀不同,如果SE为:

0 1 0

1 1
1

0 1 0

红色为SE原点,那么绿色代表的位移不是向右移动而是向左移动,即为反方向。

上面数学基础中给出的腐蚀公式并不准确,IPAMV中给出了准确的公式,位移b前应有负号,即-b。

其次,集合操作是交集,这个很关键。

腐蚀和膨胀的性质

这一节先空着,因为公式不好输入,后续会填上。。敬请期待。(广告:本人找工作,211本科,14年毕业,无工作经验,爱好图像处理,有人要的话随时牵走,工作地点限深圳,联系方式:下面留言)。

代码

上代码:

#include <cv.h>
#include <highgui.h>
#include <stdio.h>
#define isSIZEEQU(x,y) (((x)->width)==((y)->width)&&((x)->height)==((y)->height))
typedef int DataType;
struct Position_{
    int x;
    int y;
};
typedef struct Position_ Position;
typedef struct Position_ MoveDirection;
//位移操作,将图像整体移动,如果超出边界舍去
void Translation(IplImage *src,IplImage *dst,MoveDirection *direction){
    int width=src->width;
    int height=src->height;
    //printf("%d,%d\n",direction->x,direction->y);
    IplImage *temp=cvCreateImage(cvSize(width, height), src->depth, src->nChannels);
    cvZero(temp);
    for(int i=0;i<width;i++)
        for(int j=0;j<height;j++){
                if(j+direction->y<height &&
                   i+direction->x<width  &&
                   j+direction->y>=0      &&
                   i+direction->x>=0        )
                cvSetReal2D(temp, j+direction->y, i+direction->x, cvGetReal2D(src, j, i));

        }
    cvCopy(temp, dst, NULL);
    cvReleaseImage(&temp);
}
//将小的图像弄到大的黑色图像中间,或者说是给图像加黑色边框
void Zoom(IplImage *src,IplImage *dst){
    if(dst->width<src->width         ||
       dst->height<src->height       ||
       (dst->height-src->height)%2==1||
       (dst->width-src->width)%2==1){
        if(dst->width<src->width )
            printf("Zoom wrong:dst's width too small!\n");
        if(dst->height<src->height )
            printf("Zoom wrong:dst's height too small!\n");
        if((dst->height-src->height)%2==1||(dst->width-src->width)%2==1)
            printf("Zoom wrong:dst-src not a oushu!\n");
        exit(0);
    }
    MoveDirection m;
    m.x=(dst->width-src->width)/2;
    m.y=(dst->height-src->height)/2;
    cvZero(dst);
    for(int i=m.x,j=0;j<src->width;i++,j++){
        for(int k=m.y,n=0;n<src->height;k++,n++){
            cvSetReal2D(dst, k, i, cvGetReal2D(src, n, j));

        }
    }

}
//逻辑与操作
void And(IplImage *src0,IplImage *src1,IplImage *dst){
    if(!isSIZEEQU(src0,src1)){
        printf("And wrong !\n");
        exit(0);
    }
    if(!isSIZEEQU(src0,dst)){
        printf("And wrong !\n");
        exit(0);
    }
    int width=src0->width;
    int height=src0->height;
    for(int i=0;i<width;i++){
        for(int j=0;j<height;j++){
            if(cvGetReal2D(src0, j, i)>100.0&&
               cvGetReal2D(src1, j, i)>100.0)
                cvSetReal2D(dst, j, i, 255.0);
            else
                cvSetReal2D(dst, j, i, 0.0);
        }
    }
}
//逻辑或操作
void Or(IplImage *src0,IplImage *src1,IplImage *dst){
    if(!isSIZEEQU(src0,src1)){
        printf("And wrong !\n");
        exit(0);
    }
    if(!isSIZEEQU(src0,dst)){
        printf("And wrong !\n");
        exit(0);
    }
    int width=src0->width;
    int height=src0->height;
    for(int i=0;i<width;i++){
        for(int j=0;j<height;j++){
            if(cvGetReal2D(src0, j, i)>100.0||
               cvGetReal2D(src1, j, i)>100.0)
                cvSetReal2D(dst, j, i, 255);

        }
    }
}
//将所有元素设为1
void One(IplImage *src){
    for(int i=0;i<src->width;i++)
        for(int j=0;j<src->height;j++)
            cvSetReal2D(src, j, i, 255.0);

}
//膨胀
void Dilate(IplImage *src,IplImage *dst,IplImage *se,Position *center){
    if(center==NULL){
        Position temp;
        temp.x=se->width/2;
        temp.y=se->height/2;
        center=&temp;
    }
    //printf("%d,%d",center->x,center->y);
    MoveDirection m;
    IplImage *temp=cvCreateImage(cvGetSize(dst), dst->depth,dst->nChannels);
    IplImage *tempdst=cvCreateImage(cvGetSize(dst), dst->depth,dst->nChannels);
    IplImage *realdst=cvCreateImage(cvGetSize(dst), dst->depth,dst->nChannels);
    cvZero(realdst);
    Zoom(src,temp);
    int width=se->width;
    int height=se->height;
    for(int i=0;i<width;i++){
        for(int j=0;j<height;j++){
            if(cvGetReal2D(se, j, i)>100.0){
                m.x=i-center->x;
                m.y=j-center->y;
                Translation(temp,tempdst, &m);
                Or(tempdst, realdst, realdst);
            }
        }
    }
    cvCopy(realdst, dst, NULL);
    cvReleaseImage(&temp);
    cvReleaseImage(&realdst);
    cvReleaseImage(&tempdst);
}
//腐蚀
void Erode(IplImage *src,IplImage *dst,IplImage *se,Position *center){
    if(center==NULL){
        Position temp;
        temp.x=se->width/2;
        temp.y=se->height/2;
        center=&temp;
    }
    MoveDirection m;
    IplImage *temp=cvCreateImage(cvGetSize(dst), dst->depth,dst->nChannels);
    IplImage *tempdst=cvCreateImage(cvGetSize(dst), dst->depth,dst->nChannels);
    IplImage *realdst=cvCreateImage(cvGetSize(dst), dst->depth,dst->nChannels);
    One(realdst);
    Zoom(src,temp);
    int width=se->width;
    int height=se->height;
    for(int i=0;i<width;i++){
        for(int j=0;j<height;j++){
            if(cvGetReal2D(se, j, i)>100.0){
                m.x=center->x-i;
                m.y=center->y-j;
                Translation(temp,tempdst, &m);
                And(tempdst, realdst, realdst);
            }
        }
    }
    cvCopy(realdst, dst, NULL);
    cvReleaseImage(&tempdst);
    cvReleaseImage(&temp);
    cvReleaseImage(&realdst);
}

//开操作
void Open(IplImage *src,IplImage *dst,IplImage *se,Position *center){
    Erode(src, dst, se, center);
    Dilate(dst, dst, se, center);

}
//关操作
void Close(IplImage *src,IplImage *dst,IplImage *se,Position *center){
    Dilate(src, dst, se, center);
    Erode(dst, dst, se, center);

}

int main(){
    IplImage *se=cvLoadImage("/Users/Tony/Binary_Image/mask6.jpg",0);

    IplImage *src=cvLoadImage("/Users/Tony/lena/lena_BW.jpg", 0);
    IplImage *dst=cvCreateImage(cvGetSize(src), 8, 1);
    IplImage *subdst=cvCreateImage(cvGetSize(src), 8, 1);

    Close(src, dst, se, NULL);
    cvSub(dst,src,subdst, NULL);
    cvNamedWindow("SRC", 1);
    cvShowImage("SRC", src);
    cvNamedWindow("DST", 1);
    cvShowImage("DST", dst);
    cvNamedWindow("SUB", 1);
    cvShowImage("SUB", subdst);
    cvSaveImage("/Users/Tony/Binary_Image/lena_close_sub.jpg", subdst, 0);
    cvSaveImage("/Users/Tony/Binary_Image/lena_close.jpg", dst, 0);
    cvWaitKey(0);
    return 0;
}

结果

膨胀:第一行SE各向同性,后两行SE各向异性

膨胀结果                  结构元                  与原图的差

腐蚀:第一行SE各向同性,后两行SE各向异性

腐蚀结果                  结构元                  与原图的差

同一结构元不同中心位置的不同结果:

结构元为简单,各向同性:

0 1 0

1 1 1

0 1 0

实际中的应用,lena图,灰度图100为阈值后的二值图:

原图

腐蚀                                       与原图的差

膨胀                                       与原图的差

-------------------------------------------------------------------------------------------------------------

附录:论文下载

时间: 2024-10-08 21:18:32

二值图像--形态学处理2的相关文章

二值图像--形态学处理1

学习DIP第11天 形态学 数学形态学(Mathematical morphology)是一门建立在格论和拓扑学基础之上的图像分析学科,是数学形态学图像处理的基本理论.其基本的运算包括:二值腐蚀和膨胀 (形态学).二值开闭运算.骨架抽取.极限腐蚀.击中击不中变换.形态学梯度.Top-hat变换.颗粒分析.流域变换.灰值腐蚀和膨胀.灰值开闭运算.灰值形态学梯度等. 由于二值图像为离散的点集,所以我们将二维离散点定义为栅格,坐标定义为光栅坐标,光栅间的距离为采样间隔. 形态学目的 图像预处理(去噪声

二值图像--形态学处理0

学习DIP第10天 二值图像 二值图像(Binary Image),按名字来理解只有两个值,0和1,0代表黑,1代表白,或者说0表示背景,而1表示前景.其保存也相对简单,每个像素只需要1Bit就可以完整存储信息.如果把每个像素看成随机变量,一共有N个像素,那么二值图有2的N次方种变化,而8位灰度图有255的N次方种变化,8为三通道RGB图像有255*255*255的N次方种变化.也就是说同样尺寸的图像,二值图保存的信息更少. 上图中,彩色图像和灰度图像很好的可以看出右侧的镜子,而二值图像无法看出

二值图像--形态学处理3 开操作和闭操作

学习DIP第13天 开篇废话 简单来说所谓开操作和闭操作就是把腐蚀和膨胀结合起来,先腐蚀后膨胀就是开,膨胀后腐蚀就是关,至于为什么是开为什么是关,我一开始也记不住,记得老师好像也没告诉我为啥叫开,为啥叫闭,不过在下面的介绍中,会给出叫开和关的原因. 数学原理 额,公式还没准备好... 性质 开操作,一般会平滑物体轮廓,断开较窄的狭颈(细长的白色线条),所以叫开,并消除细小的突出物. 闭操作,一般也会平滑物体轮廓,但与开操作相反,弥合较窄的间断和细长的沟壑,所以叫闭,消除小的空洞,填补轮廓线的中的

数字图像处理【一】基础理论

1. 基本处理流程: a) 图像预处理: 1) 点运算(灰度直方图/灰度线性变换/灰度对数变换/伽马变换/灰度阈值变换/分段线性变换/直方图均衡化/直方图规定化) 2) 几何变换(图像平移/图像镜像/图像转置/图像缩放/图像旋转/插值算法/图像配准) 3) 空间域图像增强(空间域滤波/图像平滑/中值滤波/图像锐化) 4) 频域图像增强(傅里叶变换/频率滤波) 5) 彩色图像处理(彩色模型(RGB/CMY/CMYK/HSI/HSV/YUV/YIQ/Lab)/彩色补偿/彩色平衡) 6) 形态学图像处

【形态学】二值图像

膨胀 = 加长.变粗  映射并平移后的结构元素至少与原二值图的某些部分重叠. 函数imdialate 构造结构元素strel(shape, parameters) %% 膨胀的应用 A = imread('broken_text.tif'); B = [0 1 0; 1 1 1; 0 1 0]; % 结构元素 A2 = imdilate(A, B); % imdilate函数 figure; subplot(1,2,1),imshow(A); subplot(1,2,2), imshow(A2)

学习 opencv---(10)形态学图像处理(2):开运算,闭运算,形态学梯度,顶帽,黒帽合辑

上篇文章中,我们重点了解了腐蚀和膨胀这两种最基本的形态学操作,而运用这两个基本操作,我们可以实现更高级的形态学变换. 所以,本文的主角是OpenCV中的morphologyEx函数,它利用基本的膨胀和腐蚀技术,来执行更加高级的形态学变换,如开闭运算.形态学梯度."顶帽"."黑帽"等等. 第二件事,是浅墨想跟大家做一个关于OpenCV系列文章的书写内容和风格的思想汇报. 是这样的,浅墨发现最近几期写出来的文章有些偏离自己开始开这个专栏的最初的愿望--原理和概念部分占的

形态学操作实现

数学形态学的基本思想是用具有一定形态的结构元素去度量和提取图像中的对应形状以达到对图像分析和识别的目的.数学形态学的基本运算有四个:腐蚀.膨胀.开和闭.基于这些基本运算还可以推导和组合成各种数学形态学实用算法.本实验分别实现针对二值图像和灰度图像的四种形态学操作. 一.二值图像的腐蚀.膨胀.开.闭操作 实验结果: 二.灰度图像的腐蚀.膨胀.开.闭操作 实验结果: 代码:(下载链接) %本实验完成对二值图像和灰度图像的腐蚀.膨胀.开.闭操作 close all; clear all; grayI

图像形态学及更通用的形态学的原理及细节

在试图找到连通分支(具有相似颜色或强度的像素点的大块互相分离的区域)时通常使用膨胀操作.因为在大多数情况下一个大的区域可能被噪声.阴影等类似的东西分割成多个部分,而一次轻微的膨胀又将使这些部分“融合”在一起 如果图像不是二值的,那么膨胀和腐蚀起到的作用不是很明显.在处理灰度和彩色图像时,更通用的cvMorphologyEx可提供更有用的操作. 平滑处理与膨胀腐蚀用的核不同,分别是卷积核与形态核,形态核不需要任何的数值填充核,第132页讲高斯滤波器的时候提到高斯卷积核 开运算先腐蚀后膨胀,可以用来

OpenCV2学习笔记(三):形态学及边缘角点检测

形态学滤波理论于上世纪90年代提出,目前被广泛用于分析及处理离散图像.其基本运算有4个: 膨胀.腐蚀.开启和闭合, 它们在二值图像和灰度图像中各有特点.基于这些基本运算还可推导和组合成各种数学形态学实用算法,用它们可以进行图像形状和结构的分析及处理,包括图像分割.特征抽取.边缘检测. 图像滤波.图像增强和恢复等.数学形态学方法利用一个称作结构元素的"探针"收集图像的信息,当探针在图像中不断移动时, 便可考察图像各个部分之间的相互关系,从而了解图像的结构特征.数学形态学基于探测的思想,与