图像形态学介绍
- 形态学的应用: 主要针对二值图进行,消除噪声、边界提取、区域填充、连通分量提取、凸壳、细化、粗化等;分割出独立的图像元素,或者图像中相邻的元素;求取图像中明显的极大值区域和极小值区域;求取图像梯度。
结构和元素形状
- 膨胀和腐蚀操作的核心内容是结构元素,(后面的开闭运算等重要的也是结构元素的设计,一个合适的结构元素的设计可以带来很好的处理效果)一般来说结构元素是由元素为1或者0的矩阵组成。结构元素为1的区域定义了图像的领域,领域内的像素在进行膨胀和腐蚀等形态学操作时要进行考虑。
- 一般来说,二维或者平面结构的结构元素要比处理的图像小得多。结构元素的中心像素,即结构元素的原点,与输入图像中感兴趣的像素值(即要处理的像素值)相对应。三维的结构元素使用0和1来定义x-y平面中结构元素的范围,使用高度值定义第三维。
- API :
getStructuringElement
CV_EXPORTS_W Mat getStructuringElement(int shape, Size ksize, Point anchor = Point(-1,-1));
//shape: \MORPH_CROSS(十字交叉形核) \MORPH_ELLIPSE(椭圆形核));
// ksize: 结构元素大小;
// anchor: 锚点 默认是Point(-1, -1)意思就是中心像素,也可以自己指定
//结构元素设计
Mat elementRect, elementCross, elementEllipse;
elementRect = getStructuringElement(MORPH_RECT, cv::Size(3, 3), cv::Point(-1, -1));//矩形
elementCross = getStructuringElement(MORPH_CROSS, cv::Size(3, 3), cv::Point(-1, -1));//十字
elementEllipse = getStructuringElement(MORPH_ELLIPSE, cv::Size(5, 5), cv::Point(-1, -1));//椭圆
cout << "3X3矩形核:" << endl << elementRect << endl;
cout << "3X3十字交叉形核:" << endl << elementCross << endl;
cout << "5X5椭圆形核:" << endl << elementEllipse << endl << endl;
- 自定义结构元素
使用Mat_模板类自定义5×5大小十字形、菱形、方形、x形结构元素:
//自定义核(结构元素)
Mat_<uchar> cross(5, 5);
Mat_<uchar> diamond(5, 5);
Mat_<uchar> x(5, 5);
Mat_<uchar> square(5, 5);
// Creating the cross-shaped structuring element
cross <<
0, 0, 1, 0, 0,
0, 0, 1, 0, 0,
1, 1, 1, 1, 1,
0, 0, 1, 0, 0,
0, 0, 1, 0, 0;
// Creating the diamond-shaped structuring element
diamond <<
0, 0, 1, 0, 0,
0, 1, 1, 1, 0,
1, 1, 1, 1, 1,
0, 1, 1, 1, 0,
0, 0, 1, 0, 0;
// Creating the x-shaped structuring element
x <<
1, 0, 0, 0, 1,
0, 1, 0, 1, 0,
0, 0, 1, 0, 0,
0, 1, 0, 1, 0,
1, 0, 0, 0, 1;
// Creating the square-shaped structuring element
square <<
1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1;
//打印 x
int xnr = x.rows;
int xnl = x.cols;
for (int j = 0; j < xnr; j++)
{
char* data = x.ptr<char>(j);
for (int i = 0; i < xnl; i++)
{
int value = data[i];
std::cout << value << " ";
}
std::cout << std::endl;
}
腐蚀和膨胀
-
膨胀
膨胀的具体操作:用一个结构元素(一般是3×3的大小)扫描图像中的每一个像素,用结构元素中的每一个像素与其覆盖的像素做“与”操作,如果都为0,则该像素为0,否则为1。用最大值来替换中心像素
膨胀的作用: 是将与物体接触的所有背景点合并到物体中,使目标增大,可添补目标中的空洞,通过对图像的胀大,使图像中的物体的轮廓向外发散,体积变大。
作用: -
腐蚀
腐蚀的具体操作:用一个结构元素(一般是3×3的大小)扫描图像中的每一个像素,用结构元素中的每一个像素与其覆盖的像素做“与”操作,如果都为1,则该像素为1,否则为0。用最小值来替换中心像素
腐蚀的作用:是消除物体边界点,使目标缩小,可以消除小于结构元素的噪声点;腐蚀了就意味着图像中物体的边界被侵蚀了,轮廓向内收缩,体积变小了。
-
PS: 要注意的是这里的膨胀和腐蚀的概念是针对图像中高亮区域而言的(二值图像中对应像素值255,白色),变大和缩小也是针对图像中高亮的部分。所以对图像执行膨胀,对高亮区域是胀大,面积增大,对暗区域相当于是腐蚀,面积缩小。
-
API:
dilate
和erode
CV_EXPORTS_W void erode( InputArray src, OutputArray dst, InputArray kernel,
Point anchor = Point(-1,-1), int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue = morphologyDefaultBorderValue() );
void dilate(
InputArray src,//输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一
OutputArray dst,//目标图像,需要和源图片有一样的尺寸和类型
InputArray kernel,//操作的核。若为NULL时,表示的是使用参考点位于中心3x3的核,可以使用getStructuringElement来创建结构元素
Point anchor=Point(-1,-1),//锚点的位置,其有默认值(-1,-1),表示锚位于中心
int iterations=1,//迭代使用该函数的次数,默认值为1
int borderType=BORDER_CONSTANT,//用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT
const Scalar& borderValue=morphologyDefaultBorderValue() //当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释
);
//使用dilate函数时,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。
- 实例
Mat src = imread("F:/code/images/hist_02.jpg");
namedWindow("input", WINDOW_AUTOSIZE);
imshow("input", src);
//二值图
Mat gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
namedWindow("binary", WINDOW_AUTOSIZE);
imshow("binary", binary);
//腐蚀
Mat elementRect = getStructuringElement(MORPH_RECT, cv::Size(3, 3), cv::Point(-1, -1));//矩形
erode(binary, binary, elementRect);
namedWindow("erode", WINDOW_AUTOSIZE);
imshow("erode", binary);
//膨胀
Mat elementRect2 = getStructuringElement(MORPH_RECT, cv::Size(3, 3), cv::Point(-1, -1));//矩形
dilate(binary, binary, elementRect2);
namedWindow("dilate", WINDOW_AUTOSIZE);
imshow("dilate", src);
开闭操作
- 开运算:先腐蚀后膨胀,作用:用来消除图像中细小对象,在纤细点处分离物体和平滑较大物体的边界而有不明显改变其面积和形状,所有小到不能容纳结构元素的物体都会被移除。删除小的干扰快
- 闭运算:先膨胀后腐蚀,作用:用来填充目标内部的细小孔洞(fill hole),将断开的邻近目标连接,在不明显改变物体面积和形状的情况下平滑其边界,基本上所有小到不能完整容纳结构元素的空隙或间隙,都会被闭运算消除(即连起来),填充闭合区域
- API :
morphologyEx
CV_EXPORTS_W void morphologyEx( InputArray src, OutputArray dst,
int op, //操作的类型:MORPH_ERODE = 0, //腐蚀
MORPH_DILATE = 1, //膨胀
MORPH_OPEN = 2, //开操作
MORPH_CLOSE = 3, //闭操作
MORPH_GRADIENT = 4, //梯度操作
MORPH_TOPHAT = 5, //顶帽操作
MORPH_BLACKHAT = 6, //黑帽操作
MORPH_HITMISS = 7
InputArray kernel,//结构元素
Point anchor = Point(-1,-1), //锚点
int iterations = 1,//迭代使用函数的次数
int borderType = BORDER_CONSTANT,
const Scalar& borderValue = morphologyDefaultBorderValue() );
- 实例
Mat src = imread("F:/code/images/cells.png");
CV_Assert(!src.empty());
Mat gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
bitwise_not(binary, binary, Mat());
imshow("binary", binary);
//开操作
Mat dst;
Mat kernel = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1));//Size(15,1)为水平方向结构元素 Size(1, 15)为垂直方向结构元素
morphologyEx(binary, dst, MORPH_OPEN, kernel, Point(-1, -1), 1);
imshow("open-demo", dst);
//闭操作
Mat dst2;
Mat kerne2 = getStructuringElement(MORPH_RECT, Size(15, 15), Point(-1, -1));
morphologyEx(binary, dst2, MORPH_CLOSE, kerne2, Point(-1, -1), 1);
imshow("close-demo", dst2);
形态学梯度
- 通常所说形态学梯度(Morphological Gradient)是膨胀图像与腐蚀图像的之差得到的图像,也是基本梯度。数学表达式如下:
dst = morph_grad(src,element) = dilate(src,element) - erode(src,element)
- 梯度用于刻画目标边界或边缘位于图像灰度级剧烈变化的区域,形态学梯度根据膨胀或者腐蚀与原图作差组合来实现增强结构元素领域中像素的强度,突出高亮区域的外围。计算图像的形态学梯度是形态学重要操作,常常将膨胀和腐蚀基础操作组合起来一起使用实现一些复杂的图像形态学梯度。可以计算的梯度常见如下四种:
- 形态学梯度操作的输出图像像素值是在对应结构元素而非局部过渡区域所定义的领域中灰度级强度变化的最大值。对二值图像进行形态学操作可以将团块(blob)的边缘突出出来,可以用形态学梯度来保留物体的边缘轮廓。
- API:
MORPH_GRADIENT
- 实例
Mat src = imread("F:/code/images/yuan_test.png");
Mat gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
imshow("input", gray);
Mat basic_grad, inter_grad, exter_grad;
//基本梯度
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(gray, basic_grad, MORPH_GRADIENT, kernel, Point(-1, -1), 1);
imshow("basic gradient", basic_grad);
//内部和外部梯度
Mat dst1, dst2;
erode(gray, dst1, kernel);
dilate(gray, dst2, kernel);
subtract(gray, dst1, inter_grad);
subtract(dst2, gray, exter_grad);
imshow("internal gradient", inter_grad);
imshow("external gradient", exter_grad);
//二值图
threshold(basic_grad, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binary", binary);
黑帽与顶帽
- 当我们在处理一张图片的时候(例如提取眼底彩照的血管等信息),很多情况需要对这张图片进行一定的预处理,而通常我们就会用顶帽或者底帽变换。
- 顶帽变换:原图像与开操作对象的差,
dst = blackhat(src,element) = src - open(src,element);
因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。因此,顶帽变换用于凸显暗背景上的亮物体。对二值图来说,进行顶帽变换或之后底帽变换看起来就像是加一个阴影,有一种立体的效果。顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。 - 黑帽变换:闭操作与原图像的差值。,数学表达式为:
dst = blackhat(src,element) = close(src,element) - src;
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。所以,黑帽运算用来分离比邻近点暗一些的斑块。黑帽变换可以用于凸显亮背景上的暗物体。二值图效果与顶帽变换相比,就是一个方向相反的阴影。 - 实例
Mat src = imread("F:/code/images/cross.png");
imshow("input", src);
Mat gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binary", binary);
//顶帽
Mat k = getStructuringElement(MORPH_ELLIPSE, Size(15, 15), Point(-1, -1));
Mat hothat;
morphologyEx(binary, hothat, MORPH_TOPHAT, k);
imshow("top hat demo", hothat);
//黑帽
morphologyEx(binary, hothat, MORPH_BLACKHAT, k);
imshow("black hat demo", hothat);
击中击不中
- 实例
例如怎么检测球网的好坏,采用设计十字结构元素,进行原二值图击中,球网节点完好的会检测出绳节点!
Mat src = imread("F:/code/images/cross.png");
imshow("input", src);
Mat gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
imshow("binary", binary);
Mat hitmiss;
Mat k = getStructuringElement(MORPH_CROSS, Size(15, 15), Point(-1, -1));
morphologyEx(binary, hitmiss, MORPH_HITMISS, k);
imshow("hit and miss", hitmiss);