OpenCV函数简记_第四章数字图像的形态学处理和图像金字塔。(腐蚀、膨胀、开,闭运算、形态学梯度、顶帽和黑帽以及图像金字塔)

发布于:2022-11-29 ⋅ 阅读:(386) ⋅ 点赞:(0)

CSDN话题挑战赛第2期
参赛话题:学习笔记

系列文章目录

  1. OpenCV函数简记_第一章数字图像的基本概念(邻域,连通,色彩空间)
  2. OpenCV函数简记_第二章数字图像的基本操作(图像读写,图像像素获取,图像ROI获取,图像混合,图形绘制)
  3. OpenCV函数简记_第三章数字图像的滤波处理(方框,均值,高斯,中值和双边滤波)
  4. OpenCV函数简记_第四章数字图像的形态学处理和图像金字塔。(腐蚀、膨胀、开,闭运算、形态学梯度、顶帽和黑帽以及图像金字塔)【本文】


前言

本系列文章仅是本人学习OpenCV过程中,针对常用的OpenCV函数的相关参数和简单用法进行记录,方便随时查询和使用。对于具体算法原理不做过多介绍。
本文C++使用的OpenCV版本是的4.5.5。
python使用的OpenCV版本是4.5.3.56
官方文档参考的是 4.6.0-dev
需要查找具体函数可以直接从目录中查询。[]符号内为函数名称,包含C++和python两个语言的代码


本文内容:
本文描述了数字图像的形态学处理和图像金字塔两个部分,其中
形态学处理包括:腐蚀、膨胀、开运算、闭运算、形态学梯度、顶帽和黑帽。
图像金字塔包括:上采样和下采样算法。

参考资料:
1.OpenCV官方文档
2.毛星云-OpenCV3编程入门
3.形态学图像处理(一):膨胀与腐蚀。作者:谢小帅
4.(二十六)图像金字塔----高斯和拉普拉斯, 作者:lowkeyway


1.形态学处理

形态学处理可以理解为对数字图像中形状和结构的处理,其中最基本的操作是腐蚀和膨胀,后续的高级形态学处理都是基于腐蚀和膨胀来实现的。值得注意的是:腐蚀和膨胀都是针对图像中的高亮区域(白色区域)。

腐蚀和膨胀的作用:
(1)消除噪声。腐蚀可以去除白色空洞,膨胀可以去除黑色空洞。
(2)分割图像形状。
(3)寻找图像梯度。

1.1 结构元素创建【getStructuringElement】

getStructuringElement()
作用:
返回指定大小和形状的结构元素,用于形态学操作。
该函数构造并返回可以进一步传递给erode、dilate或morphologyEx函数中使用的结构元素参数。 但是也可以自己构建任意二进制掩码并将其用作结构元素。

函数形式:

C++:
Mat cv::getStructuringElement(
        int shape,
        Size ksize,
        Point anchor = Point(-1,-1) )
Python:
cv.getStructuringElement( shape, ksize[, anchor] ) -> retval

参数解释(以C++展示的参数为例):
1.int shape:可能是MorphShapes之一的结构元素。具体MorphShapes类型如下表所示。
2.Size ksize:结构元素的大小。
3.Point anchor = Point(-1,-1):元素内的锚点位置。 默认值 (-1,-1) 表示锚点位于中心。 请注意,只有十字形元素的形状取决于锚点位置。 在其他情况下,anchor 只是调节形态操作的结果移动了多少。

元素形状 公式
MORPH_RECT
Python: cv.MORPH_RECT
一个矩形结构元素: E i j = 1 E_{ij}=1 Eij=1
MORPH_CROSS
Python: cv.MORPH_CROSS
一个十字形的结构元素: E i j = { 1 i f i = a n c h o r . x   o r   j = a n c h o r . y 0 o t h e r w i s e E_{ij}=\begin{cases}1& if i=anchor.x\ or\ j=anchor.y \\ 0& otherwise\end{cases} Eij={10ifi=anchor.x or j=anchor.yotherwise
MORPH_ELLIPSE
Python: cv.MORPH_ELLIPSE
一个椭圆结构元素

代码示例:
注意:由于OpenCV显示出来的结构元素太小了,因此使用python中matplotlib包来显示结构元素。python代码和输出图像如下所示:

# PYTHON
import cv2
import numpy as np
import matplotlib.pyplot as plt

def main():
    # 生成十字结构元素
    crossElement = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
    plt.imshow(crossElement, cmap='gray')
    plt.show()
    
    # 生成矩形结构元素
    rectElement = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
    # 由于rectElement是全1,显示不出差别,因此在外边添加黑色背景,方便和其他结构元素对比
    show_rectElement = np.zeros((rectElement.shape[0]+1, rectElement.shape[1]+1))
    show_rectElement[0:rectElement.shape[0], 0:rectElement.shape[1]] = rectElement
    plt.imshow(show_rectElement, cmap='gray')
    plt.show()

    # 生成椭圆结构元素
    ellipseElement = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
    plt.imshow(ellipseElement, cmap='gray')
    plt.show()

if __name__ == "__main__":
    main()

十字结构元素
在这里插入图片描述

矩形结构元素
在这里插入图片描述

椭圆结构元素
在这里插入图片描述

//C++
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;

void main() {
	// 生成十字结构元素
	Mat crossElement = getStructuringElement(MORPH_CROSS, Size(5, 5));
	// 生成矩形结构元素
	Mat rectElement = getStructuringElement(MORPH_RECT, Size(5, 5));
	// 生成椭圆结构元素
	Mat ellipseElement = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));

	cout<< "生成十字结构元素" << endl;
	for (int i = 0; i < crossElement.rows; i++) {
		for (int j = 0; j < crossElement.cols; j++) {
		
			cout << (int)crossElement.at<uchar>(i, j);
		}
		cout << endl;
	}
	cout << "生成矩形结构元素" << endl;
	for (int i = 0; i < rectElement.rows; i++) {
		for (int j = 0; j < rectElement.cols; j++) {

			cout << (int)rectElement.at<uchar>(i, j);
		}
		cout << endl;
	}
	cout << "生成椭圆结构元素" << endl;
	for (int i = 0; i < ellipseElement.rows; i++) {
		for (int j = 0; j < ellipseElement.cols; j++) {

			cout << (int)ellipseElement.at<uchar>(i, j);
		}
		cout << endl;
	}
}

生成结构元素结果如下
在这里插入图片描述

1.2 腐蚀【erode】

erode()
作用:
通过使用指定的结构元素腐蚀图像。过程如下:
【gif合成图代码,见附录1】
在这里插入图片描述
从图中可以看出,当结构B(腐蚀图)以中心为锚点,划过结构A(被腐蚀图)上的元素时,对结构A上该元素进行卷积操作,来提取结构A中对应结构B上非零值所在位置的元素。然后,计算提取后元素的最小值,并赋值给结构B锚点对应的元素。

其公式表达如下:
dst ( x , y ) = min ⁡ ( x ′ , y ′ ) :   element ( x ′ , y ′ ) ≠ 0 src ( x + x ′ , y + y ′ ) \texttt{dst} (x,y) = \min _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y') dst(x,y)=min(x,y):element(x,y)=0src(x+x,y+y)

公式解释: x , y x, y x,y指的是结构A的元素坐标, x ’ x^’ x y ’ y^’ y指的是结构B锚点的坐标(上例指的是中心点)。 element ( x ′ , y ′ ) ≠ 0 \texttt{element} (x',y') \ne0 element(x,y)=0指的是结构B中非零的元素坐标。 src ( x + x ′ , y + y ′ ) \texttt{src} (x+x',y+y') src(x+x,y+y)指的是在结构A上对应结构B中非零值所在的元素值。

函数形式:

C++:
void cv::erode (
        InputArray src,
        OutputArray dst,
        InputArray kernel,
        Point anchor = Point(-1,-1),
        int iterations = 1,
        int borderType = BORDER_CONSTANT,
        const Scalar& borderValue = morphologyDefaultBorderValue())
Python:
cv.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] ) ->dst

参数解释(以C++展示的参数为例):
1.InputArray src: 输入图像;通道的数量可以是任意的,但是深度应该是CV_8U, CV_16U, CV_16S, CV_32F或CV_64F中的一个。
2. OutputArray dst: 输出图像,类型和src一致。
3. InputArray kernel: 用于腐蚀的结构;如果element=Mat()(即为NULL的时候),则使用3 × 3的矩形结构元素。腐蚀核可以使用getStructuringElement创建。
4. Point anchor = Point(-1,-1) : 锚在腐蚀结构中的位置;默认值(-1,-1)表示锚位于元素中心。
5. int iterations = 1: 腐蚀的次数。
6. int borderType = BORDER_CONSTANT:用于推断图像边界框的类型,默认BORDER_DEFAULT。其他参考下表。
7. const Scalar& borderValue= morphologyDefaultBorderValue():在边界为常数的情况下的边界值。有默认值morphologyDefaultBorderValue()。一般不用管。(个人没用过,不太理解,需要使用时参考官方技术文档中的createMorphologyFilter()函数)。

borderType枚举类型 解释[各种边界类型,图像边界用"|"表示]
BORDER_CONSTANT
Python: cv.BORDER_CONSTANT
iiiiii|abcdefgh|iiiiiii 带有一些特定的 i值
BORDER_REPLICATE
Python: cv.BORDER_REPLICATE
aaaaaa|abcdefgh|hhhhhhh
BORDER_REFLECT
Python: cv.BORDER_REFLECT
fedcba|abcdefgh|hgfedcb
BORDER_WRAP
Python: cv.BORDER_WRAP
cdefgh|abcdefgh|abcdefg
BORDER_REFLECT_101
Python: cv.BORDER_REFLECT_101
gfedcb|abcdefgh|gfedcba
BORDER_TRANSPARENT
Python: cv.BORDER_TRANSPARENT
uvwxyz|abcdefgh|ijklmno
BORDER_REFLECT101
Python: cv.BORDER_REFLECT101
和 BORDER_REFLECT_101一样
BORDER_DEFAULT
Python: cv.BORDER_DEFAULT
和BORDER_REFLECT_101一样
BORDER_ISOLATED
Python: cv.BORDER_ISOLATED
不看ROI之外的值

代码示例:

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
using namespace std;

int main() {
	Mat img = imread("./gray_yukun.jpg", 0);
	// 生成十字结构元素
	Mat crossElement = getStructuringElement(MORPH_CROSS, Size(5, 5));
	//腐蚀
	Mat dst;
	erode(img, dst, crossElement);
	//显示
	namedWindow("src");
	namedWindow("erodeDst");
	imshow("src", img);
	imshow("erodeDst", dst);
	waitKey(0);
	return 0;
}

结果如下,可以看到细线条,小白点都被消除掉了。
在这里插入图片描述

# PYTHON
import cv2

def main():
    img = cv2.imread("./gray_yukun.jpg", 0)
    # 生成十字结构元素
    crossElement = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
    # 腐蚀
    dst = cv2.erode(img, crossElement)
    # 显示
    cv2.namedWindow("src")
    cv2.namedWindow("erodeDst")
    cv2.imshow("src", img)
    cv2.imshow("erodeDst", dst)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

结果同C++。

1.3 膨胀【dilate】

dilate()
作用:
通过使用指定的结构元素腐蚀图像。过程如下:
【gif合成图代码,见附录1】
在这里插入图片描述
从图中可以看出,当结构B(膨胀图)以中心为锚点,划过结构A(被膨胀图)上的元素时,对结构A上该元素进行卷积操作,来提取结构A中对应结构B上非零值所在位置的元素。然后,计算提取后元素的最大值,并赋值给结构B锚点对应的元素。

其公式表达如下:
dst ( x , y ) = max ⁡ ( x ′ , y ′ ) :   element ( x ′ , y ′ ) ≠ 0 src ( x + x ′ , y + y ′ ) \texttt{dst} (x,y) = \max _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y') dst(x,y)=max(x,y):element(x,y)=0src(x+x,y+y)

公式解释: x , y x, y x,y指的是结构A的元素坐标, x ’ x^’ x y ’ y^’ y指的是结构B锚点的坐标(上例指的是中心点)。 element ( x ′ , y ′ ) ≠ 0 \texttt{element} (x',y') \ne0 element(x,y)=0指的是结构B中非零的元素坐标。 src ( x + x ′ , y + y ′ ) \texttt{src} (x+x',y+y') src(x+x,y+y)指的是在结构A上对应结构B中非零值所在的元素值。

函数形式:

C++:
void cv::dilate(
        InputArray src,
        OutputArray dst,
        InputArray kernel,
        Point anchor = Point(-1,-1),
        int iterations = 1,
        int borderType = BORDER_CONSTANT,
        const Scalar & borderValue = morphologyDefaultBorderValue() )
Python:
cv.dilate( src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] ) -> dst

参数解释(以C++展示的参数为例):
1.InputArray src: 输入图像;通道的数量可以是任意的,但是深度应该是CV_8U, CV_16U, CV_16S, CV_32F或CV_64F中的一个。
2. OutputArray dst: 输出图像,类型和src一致。
3. InputArray kernel: 用于膨胀的结构;如果element=Mat()(即为NULL的时候),则使用3 × 3的矩形结构元素。膨胀核可以使用getStructuringElement创建。
4. Point anchor = Point(-1,-1) : 锚在膨胀结构中的位置;默认值(-1,-1)表示锚位于元素中心。
5. int iterations = 1: 膨胀的次数。
6. int borderType = BORDER_CONSTANT:用于推断图像边界框的类型,默认BORDER_DEFAULT。其他参考borderType表
7. const Scalar& borderValue= morphologyDefaultBorderValue():在边界为常数的情况下的边界值。有默认值morphologyDefaultBorderValue()。一般不用管。(个人没用过,不太理解,需要使用时参考官方技术文档中的createMorphologyFilter()函数)。

代码示例:

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
using namespace std;

int main() {
	Mat img = imread("./gray_yukun.jpg", 0);
	// 生成十字结构元素
	Mat crossElement = getStructuringElement(MORPH_CROSS, Size(5, 5));
	//膨胀
	Mat dst;
	dilate(img, dst, crossElement);
	//显示
	namedWindow("src");
	namedWindow("dilateDst");
	imshow("src", img);
	imshow("dilateDst", dst);
	waitKey(0);
	return 0;
}

结果如下,可以看到小黑洞被消除了。
在这里插入图片描述

# PYTHON
import cv2
def main():
    img = cv2.imread("./gray_yukun.jpg", 0)
    # 生成十字结构元素
    crossElement = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
    # 膨胀
    dst = cv2.dilate(img, crossElement)
    # 显示
    cv2.namedWindow("src")
    cv2.namedWindow("dilateDst")
    cv2.imshow("src", img)
    cv2.imshow("dilateDst", dst)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

结果同C++。

1.4 高级形态学处理函数【morphologyEx()】

作用:
morphologyEx()利用腐蚀、膨胀等基础基础形态学操作,来实现高级形态学处理,例如:开运算、闭运算、形态学梯度、顶帽和黑帽。
注意:在多通道图像的情况下,每个通道都独立处理。

函数形式:

C++:
void cv::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() )
Python:
cv.morphologyEx( src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] ) -> dst

参数解释(以C++展示的参数为例):
1.InputArray src: 输入图像;通道的数量可以是任意的,但是深度应该是CV_8U, CV_16U, CV_16S, CV_32F或CV_64F中的一个。
2. OutputArray dst: 输出图像,类型和src一致。
3. int op:形态操作的类型,请参见MorphTypes表格,即下表。
4. InputArray kernel: 用于形态学处理的结构;如果element=Mat()(即为NULL的时候),则使用3 × 3的矩形结构元素。膨胀核可以使用getStructuringElement创建。
5. Point anchor = Point(-1,-1) : 锚在形态学处理结构中的位置;默认值(-1,-1)表示锚位于元素中心。
6. int iterations = 1: 形态学处理的次数。
7. int borderType = BORDER_CONSTANT:用于推断图像边界框的类型,默认BORDER_DEFAULT。其他参考borderType表
8. const Scalar& borderValue= morphologyDefaultBorderValue():在边界为常数的情况下的边界值。有默认值morphologyDefaultBorderValue()。一般不用管。(个人没用过,不太理解,需要使用时参考官方技术文档中的createMorphologyFilter()函数)。

标识符 含义
MORPH_OPEN 开运算
MORPH_CLOSE 闭运算
MORPH_GRADIENT 形态学梯度
MORPH_TOPHAT 顶帽
MORPH_BLACKHAT 黑帽
MORPH_ERODE 腐蚀
MORPH_DILATE 膨胀

腐蚀,膨胀在前两节已经介绍,后续不做示例

1.4.1 开运算【morphologyEx(op=MORPH_OPEN)】

morphologyEx(op=MORPH_OPEN)
作用:
开运算是先腐蚀后膨胀的过程。即:

dst = o p e n ( src,   element ) = d i l a t e ( e r o d e ( src,   element ) ) \texttt{dst}=open(\texttt{src, element})=dilate(erode(\texttt{src, element})) dst=open(src, element)=dilate(erode(src, element))

可以用来消除小白点,细线条,平滑边缘等作用。

注意:白色指的是高亮区域,黑色指的是背景区域。

函数形式:
参考morphologyEx(),此处int op参数选择MORPH_OPEN。

代码示例:

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
using namespace std;
int main() {
	Mat img = imread("./gray_yukun.jpg", 0);
	// 生成十字结构元素
	Mat crossElement = getStructuringElement(MORPH_CROSS, Size(5, 5));
	//开运算
	Mat dst;
	morphologyEx(img, dst, MORPH_OPEN, crossElement);
	//显示
	namedWindow("src");
	namedWindow("openDst");
	imshow("src", img);
	imshow("openDst", dst);
	waitKey(0);
	return 0;
}

结果如下:
在这里插入图片描述

# PYTHON
import cv2
def main():
    img = cv2.imread("./gray_yukun.jpg", 0)
    # 生成十字结构元素
    crossElement = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
    # 开运算
    dst = cv2.morphologyEx(img, cv2.MORPH_OPEN, crossElement)
    # 显示
    cv2.namedWindow("src")
    cv2.namedWindow("openDst")
    cv2.imshow("src", img)
    cv2.imshow("openDst", dst)
    cv2.waitKey(0)
    
if __name__ == "__main__":
    main()  

结果同C++。

1.4.2 闭运算【morphologyEx(op=MORPH_CLOSE)】

morphologyEx(op=MORPH_CLOSE)
作用:
开运算是先膨胀后腐蚀的过程。即:

dst = c l o s e ( src,   element ) = e r o d e ( d i l a t e ( src,   element ) ) \texttt{dst}=close(\texttt{src, element})=erode(dilate(\texttt{src, element})) dst=close(src, element)=erode(dilate(src, element))

可以用来连接两个较近的白色区域,消除小黑洞。

注意:白色指的是高亮区域,黑色指的是背景区域。

函数形式:
参考morphologyEx(),此处int op参数选择MORPH_CLOSE。

代码示例:

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
using namespace std;
int main() {
	Mat img = imread("./gray_yukun.jpg", 0);
	// 生成十字结构元素
	Mat crossElement = getStructuringElement(MORPH_CROSS, Size(5, 5));
	//闭运算
	Mat dst;
	morphologyEx(img, dst, MORPH_CLOSE, crossElement);
	//显示
	namedWindow("src");
	namedWindow("closeDst");
	imshow("src", img);
	imshow("closeDst", dst);
	waitKey(0);
	return 0;
}

结果如下:
在这里插入图片描述

# PYTHON
import cv2
def main():
    img = cv2.imread("./gray_yukun.jpg", 0)
    # 生成十字结构元素
    crossElement = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
    # 闭运算
    dst = cv2.morphologyEx(img, cv2.MORPH_CLOSE, crossElement)
    # 显示
    cv2.namedWindow("src")
    cv2.namedWindow("closeDst")
    cv2.imshow("src", img)
    cv2.imshow("closeDst", dst)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

结果同C++。

个人为了记忆开运算和闭运算。针对白色区域,我们要打开它俩(开运算),就要先腐蚀。这对两个白色区域,想要合并他俩(闭运算),就要先膨胀。这个仅是我的记忆方法。( ͡❛ ͜ʖ ͡❛)

1.4.3 形态学梯度【morphologyEx(op=MORPH_GRADIENT)】

morphologyEx(op=MORPH_GRADIENT)
作用:
形态学梯度是膨胀图和腐蚀图之差。即:

dst = g r a d i e n t ( src,   element ) = d i l a t e ( src,   element ) − e r o d e ( src,   element ) \texttt{dst}=gradient(\texttt{src, element})=dilate(\texttt{src, element})-erode(\texttt{src, element}) dst=gradient(src, element)=dilate(src, element)erode(src, element)

可以用来提取白色物体的边缘轮廓。

函数形式:
参考morphologyEx(),此处int op参数选择MORPH_GRADIENT。

代码示例:

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
using namespace std;
int main() {
	Mat img = imread("./gray_yukun.jpg", 0);
	// 生成十字结构元素
	Mat crossElement = getStructuringElement(MORPH_CROSS, Size(5, 5));
	//形态学梯度运算
	Mat dst;
	morphologyEx(img, dst, MORPH_GRADIENT, crossElement);
	//显示
	namedWindow("src");
	namedWindow("gradientDst");
	imshow("src", img);
	imshow("gradientDst", dst);
	waitKey(0);
	return 0;
}

结果如下,边界轮廓显示的很清晰。
在这里插入图片描述

# PYTHON
import cv2
def main():
    img = cv2.imread("./gray_yukun.jpg", 0)
    # 生成十字结构元素
    crossElement = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
    # 形态学梯度运算
    dst = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, crossElement)
    # 显示
    cv2.namedWindow("src")
    cv2.namedWindow("gradientDst")
    cv2.imshow("src", img)
    cv2.imshow("gradientDst", dst)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

结果同C++。

1.4.4 顶帽【morphologyEx(op=MORPH_TOPHAT)】

morphologyEx(op=MORPH_TOPHAT)
作用:
顶帽即原图和开运算之差。即:

dst = t o p h a t ( src,   element ) = src − o p e n ( src,   element ) \texttt{dst}=tophat(\texttt{src, element})=\texttt{src}-open(\texttt{src, element}) dst=tophat(src, element)=srcopen(src, element)

个人感觉是获取了被消除的细线条和白色噪点。

函数形式:
参考morphologyEx(),此处int op参数选择TOPHAT。

代码示例:

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
using namespace std;
int main() {
	Mat img = imread("./gray_yukun.jpg", 0);
	// 生成十字结构元素
	Mat crossElement = getStructuringElement(MORPH_CROSS, Size(5, 5));
	//顶帽运算
	Mat dst;
	morphologyEx(img, dst, MORPH_TOPHAT, crossElement);
	//显示
	namedWindow("src");
	namedWindow("topHatDst");
	imshow("src", img);
	imshow("topHatDst", dst);
	waitKey(0);
	return 0;
}

结果如下:
在这里插入图片描述

# PYTHON
import cv2
def main():
    img = cv2.imread("./gray_yukun.jpg", 0)
    # 生成十字结构元素
    crossElement = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
    # 顶帽运算
    dst = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, crossElement)
    # 显示
    cv2.namedWindow("src")
    cv2.namedWindow("topHatDst")
    cv2.imshow("src", img)
    cv2.imshow("topHatDst", dst)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

结果同C++。

1.4.5 黑帽【morphologyEx(op=MORPH_BLACKHAT)】

morphologyEx(op=MORPH_BLACKHAT)
作用:
黑帽即闭运算和原图之差。即:

dst = b l a c k h a t ( src,   element ) = c l o s e ( src,   element ) − src \texttt{dst}=blackhat(\texttt{src, element})=close(\texttt{src, element})-\texttt{src} dst=blackhat(src, element)=close(src, element)src

个人感觉是获取被消除的黑色空洞。

函数形式:
参考morphologyEx(),此处int op参数选择BLACKHAT。

代码示例:

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
using namespace std;

int main() {
	Mat img = imread("./gray_yukun.jpg", 0);
	// 生成十字结构元素
	Mat crossElement = getStructuringElement(MORPH_CROSS, Size(5, 5));
	//黑帽运算
	Mat dst;
	morphologyEx(img, dst, MORPH_BLACKHAT, crossElement);
	//显示
	namedWindow("src");
	namedWindow("blackHatDst");
	imshow("src", img);
	imshow("blackHatDst", dst);
	waitKey(0);
	return 0;
}

结果如下:
在这里插入图片描述

# PYTHON
import cv2
def main():
    img = cv2.imread("./gray_yukun.jpg", 0)
    # 生成十字结构元素
    crossElement = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
    # 黑帽运算
    dst = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, crossElement)
    # 显示
    cv2.namedWindow("src")
    cv2.namedWindow("blackHatDst")
    cv2.imshow("src", img)
    cv2.imshow("blackHatDst", dst)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

结果同C++。

2. 尺度变换

2.1 图像金字塔

图像金字塔包含pryUp()与pryDown()这两个函数,实现对图片进行上下采样。虽然OpenCV的resize函数就可以对图做几何变化,但是图像金字塔几何变化和resize函数的实现方式不同,较广泛运用于各种图像之中。

图像金字塔是由许多图像组成,由原图像层层采样获取。基于其采样方式可以分为两种,分别是Gaussian金字塔和Laplacian金字塔。本文主要介绍Gaussian金字塔的上采样和下采样函数。Laplacian金字塔会在附录2简单介绍原理。

图像金字塔如下图所示(图片来自百度):
在这里插入图片描述

2.1.1 上采样【pyrUp()】

pyrUp()
作用:
上采样原理:将图像在偶数行和列,插入零值。然后再对整体图像做一个高斯模糊。

默认情况下,输出图像的大小计算为 S i z e ( s r c . c o l s ∗ 2 , s r c . r o w s ∗ 2 ) Size(src.cols*2, src.rows*2) Size(src.cols2,src.rows2),但是无论什么情况,应满足以下条件:
∣ dstSize.width − src.cols ∗ 2 ∣ ≤ ( dstSize.width   mod   2 ) |\texttt{dstSize.width}-\texttt{src.cols}*2|\leq(\texttt{dstSize.width mod 2} ) dstSize.widthsrc.cols2∣(dstSize.width mod 2)
∣ dstSize.height − src.rows ∗ 2 ∣ ≤ ( dstSize.height   mod   2 ) |\texttt{dstSize.height}-\texttt{src.rows}*2|\leq(\texttt{dstSize.height mod 2} ) dstSize.heightsrc.rows2∣(dstSize.height mod 2)

函数形式:

C++:
void cv::pyrUp(
        InputArray src,
        OutputArray dst,
        const Size & dstsize = Size(),
        int borderType = BORDER_DEFAULT)
Python:
cv.pyrUp( src[, dst[, dstsize[, borderType]]] ) -> dst

参数解释(以C++展示的参数为例):
1.InputArray src: 输入图像。
2. OutputArray dst: 输出图像,类型和src一致。
3. const Size & dstsize = Size(): 输出图像的大小,有默认值Sisze(),即在默认的情况下,为
S i z e ( s r c . c o l s ∗ 2 , s r c . r o w s ∗ 2 ) Size(src.cols*2, src.rows*2) Size(src.cols2,src.rows2)。其他情况需满足上文提及的条件。
4. int borderType = BORDER_CONSTANT:用于推断图像边界框的类型,默认BORDER_DEFAULT。其他参考borderType表

代码示例:

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
using namespace std;

int main() {
	Mat img = imread("./gray_yukun.jpg", 0);

	//上采样
	Mat dst;
	pyrUp(img, dst);

	//显示
	namedWindow("src");
	namedWindow("pyrUpDst");
	imshow("src", img);
	imshow("pyrUpDst", dst);
	waitKey(0);
	return 0;
}

结果如下:
在这里插入图片描述

# PYTHON
import cv2

def main():
    img = cv2.imread("./gray_yukun.jpg", 0)
    # 上采样
    dst = cv2.pyrUp(img)
    # 显示
    cv2.namedWindow("src")
    cv2.namedWindow("pyrUpDst")
    cv2.imshow("src", img)
    cv2.imshow("pyrUpDst", dst)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

结果同C++。

2.1.2 下采样【pyrDown()】

pyrDown()
作用:
下采样原理:先对图像做高斯平滑,然后删除图像的偶数行和偶数列,即将图像在x和y方向均减少一半。

默认情况下,输出图像的大小计算为 S i z e ( ( s r c . c o l s + 1 ) / 2 , ( s r c . r o w s + 1 ) / 2 ) Size((src.cols+1)/2, (src.rows+1)/2) Size((src.cols+1)/2,(src.rows+1)/2),但是无论什么情况,应满足以下条件:
∣ dstSize.width   *   2 − src.cols ∣ ≤ 2 |\texttt{dstSize.width * 2}-\texttt{src.cols}|\leq2 dstSize.width * 2src.cols2
∣ dstSize.height   *   2 − src.rows ∣ ≤ 2 |\texttt{dstSize.height * 2}-\texttt{src.rows}|\leq2 dstSize.height * 2src.rows2

函数形式:

C++:
void cv::pyrDown (
        InputArray src,
        OutputArray dst,
        const Size & dstsize = Size(),
        int borderType = BORDER_DEFAULT )
Python:
cv.pyrDown( src[, dst[, dstsize[, borderType]]] ) -> dst

参数解释(以C++展示的参数为例):
1.InputArray src: 输入图像。
2. OutputArray dst: 输出图像,类型和src一致。
3. const Size & dstsize = Size(): 输出图像的大小,有默认值Size(),即在默认的情况下,为
S i z e ( ( s r c . c o l s + 1 ) / 2 , ( s r c . r o w s + 1 ) / 2 ) Size((src.cols+1)/2, (src.rows+1)/2) Size((src.cols+1)/2,(src.rows+1)/2)。其他情况需满足上文提及的条件。
4. int borderType = BORDER_CONSTANT:用于推断图像边界框的类型,默认BORDER_DEFAULT。其他参考borderType表

代码示例:

//C++
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
using namespace std;

int main() {
	Mat img = imread("./gray_yukun.jpg", 0);

	//下采样
	Mat dst;
	pyrDown(img, dst);

	//显示
	namedWindow("src");
	namedWindow("pyrDownDst");
	imshow("src", img);
	imshow("pyrDownDst", dst);
	waitKey(0);
	return 0;
}

结果如下:
在这里插入图片描述

# PYTHON
import cv2

def main():
    img = cv2.imread("./gray_yukun.jpg", 0)
    # 下采样
    dst = cv2.pyrDown(img)
    # 显示
    cv2.namedWindow("src")
    cv2.namedWindow("pyrDownDst")
    cv2.imshow("src", img)
    cv2.imshow("pyrDownDst", dst)
    cv2.waitKey(0)

if __name__ == "__main__":
    main()

结果同C++。

2.2 缩放函数【resize】

OpenCV函数简记_第二章数字图像的基本操作(图像读写,图像像素获取,图像ROI获取,图像混合,图形绘制)中的缩放函数章节。

总结

本文仅对我认为常用的函数进行总结,在查看Opencv官方文档中,还有许多函数未介绍。如果本文没有写的,可以查询OpenCV官方文档。感谢各位读者的阅读。如果您觉得对您有帮助的话,可以给小弟一个赞。

最后,缅怀毛星云先生,感谢毛星云先生的引领,本文主要参考了毛星云先生的《OpenCV3编程入门》。

附录1 GIF合成代码

首先得有多张尺度一致的图像,然后可以通过该代码合成GIF。

__data__ = "2022.9.30"
__author__ = "yukun.csdn"

import imageio
import glob

def create_gif(imageList, outputPath):
    """
    生成GIF
    :param imageList: 图像列表->list
    :param outputPath: 输出路径->str
    """
    gifImagelist = []
    for iImagePath in imageList:
        gifImagelist.append(imageio.imread(iImagePath))
    imageio.mimsave(outputPath + "\\erode.gif", gifImagelist, fps=2)

if __name__ == "__main__":
    imageList = glob.glob(r"C:\Users\Lee\Pictures\Opencv\gif\*.jpg")
    outputPath = r"C:\Users\Lee\Pictures\Opencv\gif"
    create_gif(imageList, outputPath)

附录2 Laplacian金字塔

Laplacian金字塔是由Gaussian金字塔生成。
假设Gaussian金字塔为 G G G,其中 G i G_i Gi表示从下往上的每一层。如下图所示:
在这里插入图片描述
Laplacian金字塔的每一层都是由Gaussian金字塔对应层减去Gaussian金字塔对应层先下采样后上采样的图。
即:
L i = G i − p r y U p ( G i + 1 ) L_i = G_i -pryUp(G_{i+1}) Li=GipryUp(Gi+1)
注意:下标是金字塔从下往上排序,下标越大,图像越小。

因此,Laplacian金字塔是通过源图像减去先缩小后再放大的图像的一系列图像构成的。保留的是残差,为图像还原做准备。

Laplacian金字塔如下图所示:
在这里插入图片描述
生成代码:

#PYTHON
import cv2
import copy

def main():
    img = cv2.imread("./cat.jpg")
    cv2.imwrite("./pry/G0.jpg", img)
    count = 1
    L = [copy.deepcopy(img)]
    # 生成高斯金字塔
    for i in range(4):
        img = cv2.pyrDown(img)
        L.append(copy.deepcopy(img))
        cv2.imwrite("./pry/G"+str(count)+".jpg", img)
        count += 1
    # 生成拉普拉斯金字塔
    count -= 1
    cv2.imwrite("./pry/L"+str(count)+".jpg", L[count])
    for i in range(4):
        img = L[count-1] - cv2.pyrUp(L[count])
        cv2.imwrite("./pry/L" + str(count-1) + ".jpg", img)
        count -= 1
if __name__ == "__main__":
    main()

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到