数学形态学实际上可以理解为一种滤波行为,所以很多地方称它为形态学滤波。有了个这概念,我们就能更好的理解它。我们滤波中用的滤波器(kernel)在这里被称为结构元素,结构元素往往是由一个特殊的形状构成,如:线条、矩形、圆、菱形等。我们把结构元素的中心(Anchor Point)与图像上像素点对齐,然后结构元素覆盖的领域像素就是我们要分析的像素,我们定义一种操作就形成了一种形态学运算。

我们在这里不解释形态学操作的算法原理及它们的意义,有兴趣的可以参见相关数字图像处理方面的教材,或关注本博客,博主打算在OpenCV系列写完后,开始写图像处理方面算法系列的文章。

一、形态学的基本操作

腐蚀运算:erode

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

src:输入图像,很多场合下我们使用的是二值图像,当然灰度图像也可以。

dst:输出图像,格式和输入图像一致。

kernel:定义的结构元素。

anchor:结构元素的中心,如果是默认参数(-1,-1),程序会自动将其设置为结构元素的中心。

iterations:迭代次数,我们可以选择对图像进行多次形态学运算。

后面两个参数是边界类型,由于要处理领域问题,所以图像需要扩充边界。一般情况下使用默认即可。

膨胀运算:dilate

膨胀跟腐蚀的参数完全一致,就不过多的说明了。这两个形态学操作是最基本的两个操作。

int main()
{
Mat image=imread("../cat.png");
// 彩色转灰度
cvtColor(image,image,CV_BGR2GRAY);
// 阈值化
threshold(image,image,255*(0.5),255,THRESH_BINARY); // 形态学操作
// 如果把结构元素设置为Mat(),则将用默认的3*3的矩形结构元素
Mat eroded;
erode(image,eroded,Mat());
Mat dilated;
dilate(image,dilated,Mat()); return 0;
}

下面要介绍的两个形态学操作,在实际应用中要比上面两个更加广泛,但实际上它们是上面两种操作的一个组合式的操作。

开运算与闭运算

这两个运算都是使用函数morphologyEx来实现的,这个函数的接口如下:

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

函数的大部分参数都与上面介绍的erode函数参数是一样的,这里面的op是我们要进行的形态学的类型:

MORPH_OPEN:对图像进行开运算。

MORPH_CLOSE:对图像进行闭运算。

下面我们还是以小猫图像为例显示一下对二值图像进行开运算和闭运算后得到的结果。

int main()
{
Mat image=imread("../cat.png");
// 彩色转灰度
cvtColor(image,image,CV_BGR2GRAY);
// 阈值化
threshold(image,image,255*(0.5),255,THRESH_BINARY); // 定义结构元素
Mat se(5,5,CV_8U,Scalar(1));
Mat closed;
morphologyEx(image,closed,MORPH_CLOSE,se);
Mat opened;
morphologyEx(image,opened,MORPH_OPEN,se); return 0;
}

从图片中我们可以得出结论:

闭运算可以填充图像中的孔洞,连接一些缺口;开运算可以去除图像中一些较小的结构。前提是这些孔洞或碎片要与进行运算的结构元素尺度相当。

二、用形态学操作来检测边缘和角点

其实用形态学来检测边缘的原理非常简单,我们打开源码看它是怎么操作的:

case CV_MOP_GRADIENT:
erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
dst -= temp;
break;

可以看出来,它是对图像先做了一个腐蚀,再做了一次膨胀,然后将两次的结果相减即可。

int main()
{
Mat image=imread("../cat.png");
// 彩色转灰度
cvtColor(image,image,CV_BGR2GRAY);
Mat catEdge;
morphologyEx(image,catEdge,MORPH_GRADIENT,Mat()); // 阈值化
threshold(catEdge,catEdge,40,255,THRESH_BINARY);
namedWindow("catEdge");imshow("catEdge",catEdge); waitKey();
return 0;
}

下面我们来实现用形态学操作来检测角点。

首先我们需要定义几个特殊的结构元素,我们这里都用Mat来定义,并像素式的赋值,你可以选择OpenCV里的getStructElement来更快的实现。

// 定义结构元素
Mat cross(5,5,CV_8U,Scalar(0));
Mat diamond(5,5,CV_8U,Scalar(1));
Mat square(5,5,CV_8U,Scalar(1));
Mat x(5,5,CV_8U,Scalar(0)); for(int i=0;i<5;i++)
{
cross.at<uchar>(2,i)=1;
cross.at<uchar>(i,2)=1; }
diamond.at<uchar>(0,0)=0;
diamond.at<uchar>(0,1)=0;
diamond.at<uchar>(1,0)=0;
diamond.at<uchar>(4,4)=0;
diamond.at<uchar>(3,4)=0;
diamond.at<uchar>(4,3)=0;
diamond.at<uchar>(4,0)=0;
diamond.at<uchar>(4,1)=0;
diamond.at<uchar>(3,0)=0;
diamond.at<uchar>(0,4)=0;
diamond.at<uchar>(0,3)=0;
diamond.at<uchar>(1,4)=0; for(int i=0;i<5;i++){
x.at<uchar>(i,i)=1;
x.at<uchar>(4-i,i)=1;
}

第一个为一个十字型的结构元素,第二个为菱形,第三个是矩形,第四个是一个“X”

型。

然后我们按下面的顺序对一幅图像进行操作,并对最后的结果进行阈值化。

Mat result;
dilate(image,result,cross);
erode(result,result,diamond); Mat result2;
dilate(image,result2,x);
erode(result2,result2,square);
absdiff(result2,result,result); threshold(result,result,40,255,THRESH_BINARY);

经过上面步骤,我们得到了一张二值图像,显示了图像的一些角点的位置。

为了更形象的说明,我们将上面的这些点在原彩色图像上标出来:

// 标记角点
void drawOnImage(const Mat& binary,Mat& image)
{
for(int i=0;i<binary.rows;i++)
{
// 获取行指针
const uchar* data=binary.ptr<uchar>(i);
for(int j=0;j<binary.cols;j++)
{
if(data[j]) //角点图像上的白点
circle(image,Point(j,i),8,Scalar(0,255,0));// 画圈
}
}
}

三、用数学形态学进行车牌定位

智能交通中车牌的检测与识别一直是核心问题,而车牌识别发展了20余年,已经存在了很多的解决方案,我们这里采用数学形态学来进行车牌检测。本部分并不是一个完整的车牌定位程序,后面还需要进行连通区域的标记和筛选以及伪车牌去除等,有兴趣的读者可以继续现实或和我交流。

第一步:应用形态学做竖直方向的边缘检测。

文章第二部分中已经介绍了用形态学做边缘检测,我们这里只需要修改结构元素就可以实现只检测竖直方向上的边缘。

第二步:定义水平方向的闭运算和竖直方向的闭运算,将竖直的线条连成一块。

第三步:遍历连通区域,按照车牌的一些限制条件进行筛选即可。

int main()
{
Mat cimage=imread("../car.png");
Mat image;
cvtColor(cimage,image,CV_BGR2GRAY); Mat result;
//检测竖直边缘
morphologyEx(image,result,MORPH_GRADIENT,Mat(1,2,CV_8U,Scalar(1)));
//阈值化
threshold(result,result,255*(0.2),255,THRESH_BINARY);
//水平方向闭运算
morphologyEx(result,result,MORPH_CLOSE,Mat(1,20,CV_8U,Scalar(1)));
//竖起方向闭运算
morphologyEx(result,result,MORPH_CLOSE,Mat(10,1,CV_8U,Scalar(1))); return 0;
}

注:上面的例程只是简单的用来介绍数学形态学的用法,想应用在实际工程中,还需要进行一步改进。

OpenCV成长之路(6):数学形态学基本操作及其应用的更多相关文章

  1. OpenCV成长之路:图像直方图的应用

    OpenCV成长之路:图像直方图的应用 2014-04-11 13:57:03 标签:opencv 图像 直方图 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否 ...

  2. OpenCV成长之路:图像滤波

    http://ronny.blog.51cto.com/8801997/1394138 OpenCV成长之路:图像滤波 2014-04-11 14:28:44 标签:opencv 边缘检测 sobel ...

  3. OpenCV成长之路:直线、轮廓的提取与描述

    http://ronny.blog.51cto.com/8801997/1394139 OpenCV成长之路:直线.轮廓的提取与描述 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 . ...

  4. OpenCV成长之路(2):图像的遍历

    我们在实际应用中对图像进行的操作,往往并不是将图像作为一个整体进行操作,而是对图像中的所有点或特殊点进行运算,所以遍历图像就显得很重要,如何高效的遍历图像是一个很值得探讨的问题. 一.遍历图像的4种方 ...

  5. OpenCV成长之路(10):视频的处理

    视频中包含的信息量要远远大于图片,对视频的处理分析也越来越成为计算机视觉的主流,而本质上视频是由一帧帧的图像组成,所以视频处理最终还是要归结于图像处理,但在视频处理中,有更多的时间维的信息可以利用.本 ...

  6. OpenCV成长之路(9):特征点检测与图像匹配

    特征点又称兴趣点.关键点,它是在图像中突出且具有代表意义的一些点,通过这些点我们可以用来识别图像.进行图像配准.进行3D重建等.本文主要介绍OpenCV中几种定位与表示关键点的函数. 一.Harris ...

  7. OpenCV成长之路(8):直线、轮廓的提取与描述

    基于内容的图像分析的重点是提取出图像中具有代表性的特征,而线条.轮廓.块往往是最能体现特征的几个元素,这篇文章就针对于这几个重要的图像特征,研究它们在OpenCV中的用法,以及做一些简单的基础应用. ...

  8. OpenCV成长之路(7):图像滤波

    滤波实际上是信号处理里的一个概念,而图像本身也可以看成是一个二维的信号.其中像素点灰度值的高低代表信号的强弱. 高频:图像中灰度变化剧烈的点. 低频:图像中平坦的,灰度变化不大的点. 根据图像的高频与 ...

  9. OpenCV成长之路(5):图像直方图的应用

    正如第4篇文章所说的图像直方图在特征提取方面有着很重要的作用,本文将举两个实际工程中非常实用的例子来说明图像直方图的应用. 一.直方图的反向映射. 我们以人脸检测举例,在人脸检测中,我们第一步往往需要 ...

随机推荐

  1. Markdown 简明语法手册

    Markdown 简明语法手册 本文原文http://www.jianshu.com/p/fdb5cbdaf244 根据个人使用情况有所修改. Markdown是一种轻量级标记语言,简称md.创始人为 ...

  2. time模块

    In [1]: import time In [2]: import datetime In [3]: date_time = datetime.datetime.now() In [4]: prin ...

  3. C++ 以费波纳茨数列为权重的加权均值计算方法 wMA

    #pragma once #include <iostream> using namespace std; template <typename T> double *wMA( ...

  4. js获取锚点名称 #

    var thisId = window.location.hash; alert(thisId); 输出 #2

  5. 扩展服务 修改新增Service的默认主题

    问题描述:想要在F:\PHPnow-1.5.6\htdocs\yt\Yourphp\Tpl\      目录下新增一个Service\Default(A)   和Service\new(B) 两个主题 ...

  6. 集合 ArrayList

    /* * 功能:演示java集合的用法:ArrayList */ package com.jihe; //先引入一个包 import java.util.ArrayList; public class ...

  7. inline-block元素overflow:hidden对齐问题

    inline-block元素设置overflow:hidden后,其本身会上移 解决方法:在该元素或其父元素上设置vertical-align:bottom 原因解释:inline-block元素被设 ...

  8. 为IIS Express添加MIME映射

    VS2013自带IIS Express,无法发布JSON文件,需添加MIME映射. 没有图形界面,只能命令行. 进入C:\Program Files(x86)\IIS Express文件夹,输入:ap ...

  9. linux脚本编程(shell)浅介 (转载)

    linux脚本(shell)编程 啊,昨天上网看到一篇讲 linux/unix shell 的文章,想想自己最后写这东西也是一年前的事了,想想都快忘光了. 还是整理一下,做一次回顾,以后说不定还用得上 ...

  10. shell脚本笔记(原创不断记录)

    今天开始自己的shell脚本练习,刚好公司有太服务器,要时间对数据的cp是按月的: 考虑:首先寻找规律,发现都放置在/opt/www/aaa/  里面有很多的2级和3级目录和文件,但我追踪要备份的是年 ...