GitHub@ShaneHolmes_OpenCV_image
腐蚀与膨胀( Erosion 与 Dilation) 是OpenCV提供的两种最基本的形态学操作。
1.形态学操作
简单来讲,形态学操作就是基于形状的一系列图像处理操作。通过将 结构元素 作用于输入图像来产生输出图像。
最基本的形态学操作有二:腐蚀与膨胀(Erosion 与 Dilation)。 他们的运用广泛:
消除噪声
分割(isolate)独立的图像元素,以及连接(join)相邻的元素。
寻找图像中的明显的极大值区域或极小值区域。
1.1 膨胀和腐蚀
腐蚀和膨胀是最基本的形态学算子
结构元素
就相当于我们在滤波中所涉及到的模板,也就是说它是一个给定像素的矩阵,这个矩阵可以是任意形状的, 一般情况下都是正方形,圆形或者菱形的但是在结构元素中有一个中心点(也叫做anchor point)。 和模板中心一样,处理后的结果赋值给和这个中心点对齐的像素点。处理的过程也是基本相同。
结构元素和卷积模板的区别在于,膨胀是以集合运算为基础的,卷积是以算数运算为基础的。 (OpenCV里面的腐蚀膨胀都是针对白色目标区域的)
膨胀:用结构元素的中心点对准当前正在遍历的这个像素, 然后取当前结构元素所覆盖下的原图对应区域内的所有像素的最大值,用这个最大值替换当前像素值,给图像中的对象边界添加像素,使二值图像扩大一圈
- 用结构元素,扫描图像的每一个像素
- 用结构元素与其覆盖的二值图像做“与”操作
- 如果都为0,结果图像的该像素为0。否则为1
也就是在结构元素覆盖范围下,只要有一个像素符和结构元素像素相同,那么中心点对应点就为1,否则为0
腐蚀:用结构元素的中心点对准当前正在遍历的这个像素, 然后取当前结构元素所覆盖下的原图对应区域内的所有像素的最小值,用这个最小值替换当前像素值,删除对象边界的某些像素,使二值图像减小一圈
- 用结构元素,扫描图像的每一个像素
- 用结构元素与其覆盖的二值图像做“与”操作
- 如果都为1,结果图像的该像素为1。否则为0
也就是查找被处理图像中能不能找到和结构元素相同的矩阵。如果存在那么中心点所对应的点就为1,否则为0
腐蚀:删除对象边界的某些像素
膨胀:给图像中的对象边界添加像素
腐蚀用于分割(isolate)独立的图像元素, 膨胀用于连接(join)相邻的元素 。腐蚀、膨胀可用于去噪(低尺寸结构元素的腐蚀操作很容易去掉分散的椒盐噪声点),图像轮廓提取、图像分割、寻找图像中的明显的极大值区域或极小值区域等
- 膨胀
此操作将图像 A 与任意形状的内核 (B),通常为正方形或圆形,进行卷积。内核 B 有一个可定义的 锚点, 通常定义为内核中心点。
进行膨胀操作时,将内核 B 划过图像,将内核 B 覆盖区域的最大相素值提取,并代替锚点位置的相素。显然,这一最大化操作将会导致图像中的亮区开始”扩展” (因此有了术语膨胀 dilation )。对上图采用膨胀操作我们得到:
背景(白色)膨胀,而黑色字母缩小了。 - 腐蚀
腐蚀在形态学操作家族里是膨胀操作的孪生姐妹。它提取的是内核覆盖下的相素最小值。进行腐蚀操作时,将内核 B 划过图像,将内核 B 覆盖区域的最小相素值提取,并代替锚点位置的相素。以与膨胀相同的图像作为样本,我们使用腐蚀操作。从下面的结果图我们看到亮区(背景)变细,而黑色区域(字母)则变大了。
源代码:
import cv2
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
img = cv2.imread('C://Users//47463//Desktop//2//car.jpg')
cv2.imshow('jeep', img)
erosion = cv2.erode(img, kernel, iterations=3)
cv2.imshow('erosion', erosion)
dilation = cv2.dilate(img, kernel, iterations=3)
cv2.imshow('dilation', dilation)
cv2.waitKey(0)
cv2.destroyAllWindows()
代码解释:
- getStructuringElement()函数可用于构造一个特定大小和形状的结构元素,用于图像形态学处理。 OpenCV-Python内置的常量定义椭圆(MORPH_ELLIPSE)和十字形结构(MORPH_CROSS)、矩形(MORPH_RECT)。例如(cv2.MORPH_RECT, (3,3))是如下的矩形结构:
而(cv2.MORPH_CROSS,(5,5))的结构为:
- 设置好结构元素,然后分别调用cv2.erode(…)和cv2.dilate(…)函数即可,其中第一个参数是需要处理的图像,第二个是结构元素,第三个参数是迭代的次数。
1.2 开运算和闭运算
开运算和闭运算就是将腐蚀和膨胀按照一定的次序进行处理。但这两者并不是可逆的,即先开后闭并不能得到原先的图像。
源代码:
import cv2
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
img = cv2.imread('C://Users//47463//Desktop//2//car.jpg')
cv2.imshow('org', img)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel, iterations=2)
cv2.imshow('open', opening)
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations=2)
cv2.imshow('close', closing)
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
cv2.imshow('gradient', gradient)
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
cv2.imshow('tophat', tophat)
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
cv2.imshow('blackhat', blackhat)
cv2.waitKey(0)
cv2.destroyAllWindows()
- Opening开运算:先腐蚀再膨胀叫开运算,作用能消除图片上的小标点。
- Closing闭运算:先膨胀后腐蚀。作用是消除图片上的小黑点。
- Gradient 用于获取图片的轮廓,形态梯度图 = 彭胀图 - 腐蚀图
- Top Hat 顶帽= 原图 - 开运算图,显示出原图去除掉的白色部分。突出原图像中比周围亮的区域
- Black Hat 黑帽= 原图 - 闭运算, 显示出原图去除掉的黑色部分。突出原图像中比周围暗的区域
闭运算用来连接被误分为许多小块的对象,而开运算用于移除由图像噪音形成的斑点。因此,某些情况下可以连续运用这两种运算。如对一副二值图连续使用闭运算和开运算,将获得图像中的主要对象。同样,如果想消除图像中的噪声(即图像中的“小点”),也可以对图像先用开运算后用闭运算,不过这样也会消除一些破碎的对象。
1.3 检测边缘
在OpenCV中,边缘检测的方法有以下几种:
Sobel
Scharr
Laplace
Canny
形态学检测边缘的原理:
在膨胀时,图像中的物体会想周围“扩张”;腐蚀时,图像中的物体会“收缩”。比较这两幅图像,由于其变化的区域只发生在边缘。所以这时将两幅图像相减,得到的就是图像中物体的边缘。实际使用时用Canny或Harris等算法。
源代码:
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('C://Users//47463//Desktop//2//car.jpg',0)
edges = cv2.Canny(img,150,250)
plt.figure(figsize = (20, 10))
plt.subplot(1, 2, 1)
plt.imshow(img,cmap = 'gray')
plt.title('Original Image')
plt.axis('off')
plt.subplot(1, 2, 2)
plt.imshow(edges,cmap = 'gray')
plt.title('Edge Image')
plt.axis('off')
plt.show()
代码解释:
- matplotlib是 Python 的绘图库。 它可与 NumPy 一起使用,提供了一种有效的 MatLab 开源替代方案。 它也可以和图形工具包一起使用,如 PyQt 和 wxPython。
在PyCharm中安装matplotlib的方法:
点击Terminal,输入pip install matplotlib
可以使用python -m pip list
命令查看已经安装好的依赖包:
附上:matplotlib教程 - cv2.Canny(img,150,250),第二个(低阈值)和第三个(高阈值)参数都是阈值,canny方法处理的结果和阈值有关
- plt.figure(figsize = (20, 10)):自定义画布大小
- plt.subplot(1, 2, 1):Matplotlib的可以把很多张图画到一个显示界面,这就设计到面板切分成一个一个子图。要生成一行两列,这是第一个图plt.subplot(‘行’,‘列’,‘编号’)。
- plt.subplot(1, 2, 2):这是一行两列的第二个图
附上:subplot教程 - plt.axis(‘off’):关闭坐标轴
另:
由于canny方法那种高低阈值对结果有影响,可以通过以下代码对该影响进行观察:
- 创建滑动条,把滑动条绑定到opencv窗口
- 主要函数:cv2.getTrackbarPos();cv2.creatTrackbar()
- 我们创建一个窗口和一个滑动条,通过滑动条调用回调函数doCanny()。先介绍一下cv2.creatTrackbar()函数,函数的第一个参数时滑动条的名字,第二个参数时滑动条被放置的窗口的名字,第三个参数是滑动条默认值,第四个参数时滑动条的最大值,第五个参数时回调函数,每次滑动都会调用回调函数。
第二个函数cv2.getTrackbarPos(),共有2个参数,第一个参数是滑动条名字,第二个时所在窗口偶,返回值是滑动条位置。
参考博文
源代码:
import cv2
img = cv2.imread("C://Users//47463//Desktop//2//car.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)#灰度图片
gauss = cv2.GaussianBlur(gray, (3, 3), 1)#高斯模糊
def doCanny(x):#回调函数
position = cv2.getTrackbarPos("CannyBar", "Canny")#获取position
canny = cv2.Canny(gauss, position, position*2.5)#使用canny方法进行边缘检测
cv2.imshow("Canny", canny)
cv2.namedWindow("Canny")
cv2.createTrackbar("CannyBar", "Canny", 1, 100, doCanny)#在cv的Canny窗口创建bar
cv2.waitKey(0)
1.4 拐角检测
先用十字形的结构元素膨胀像素,这种情况下只会在边缘处“扩张”,角点不发生变化。接着用菱形的结构元素腐蚀原图像,导致只有在拐角处才会“收缩”,而直线边缘都未发生变化。然后是用X形膨胀原图像,角点膨胀的比边要多。这样第二次用方块腐蚀时,角点恢复原状,而边要腐蚀的更多。所以当两幅图像相减时,只保留了拐角处。
源代码:
# coding=utf-8
import cv2
image = cv2.imread("C://Users//47463//Desktop//2//house.jpg", 0)
origin = cv2.imread("C://Users//47463//Desktop//2//house.jpg")
# 构造5×5的结构元素,分别为十字形、菱形、方形和X型
cross = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
# 菱形结构元素的定义稍麻烦一些
diamond = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
diamond[0, 0] = 0
diamond[0, 1] = 0
diamond[1, 0] = 0
diamond[4, 4] = 0
diamond[4, 3] = 0
diamond[3, 4] = 0
diamond[4, 0] = 0
diamond[4, 1] = 0
diamond[3, 0] = 0
diamond[0, 3] = 0
diamond[0, 4] = 0
diamond[1, 4] = 0
square = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
x = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
# 使用cross膨胀图像
result1 = cv2.dilate(image, cross)
# 使用菱形腐蚀图像
result1 = cv2.erode(result1, diamond)
# 使用X膨胀原图像
result2 = cv2.dilate(image, x)
# 使用方形腐蚀图像
result2 = cv2.erode(result2, square)
# result = result1.copy()
# 将两幅闭运算的图像相减获得角
result = cv2.absdiff(result2, result1)
# 使用阈值获得二值图
retval, result = cv2.threshold(result, 40, 255, cv2.THRESH_BINARY)
# 在原图上用半径为5的圆圈将点标出。
for j in range(result.size):
y = int(j / result.shape[0])
x = j % result.shape[0]
if result[x, y]==255:
cv2.circle(image, (y, x), 5, (255, 0, 0))
cv2.imshow("Result", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
更多文章:https://blog.csdn.net/qq_33208851/article/details/95237054