opencv-python形态学计算

发布时间 2023-07-11 14:41:00作者: 寒水浮云

形态学运算包括腐蚀,膨胀,开运算,闭运算,形态学梯度,顶帽运算,底帽运算7种,其中膨胀与腐蚀是最常用的两种基础形态学方法,可以用来消除噪声,元素分割和连接,形态学运算主要在图像去噪,图像分割等方面有着广泛的运用。

形态学指一系列处理图像形状特征的图像处理技术,形态学的基本思想是利用一种特殊的结构单元(本质上是卷积核)来测量或提取图像中相应的形状或特征,方面后续处理。 这些处理方法基本上是对二值图像(黑白图)进行处理,卷积核决定了图像处理后的效果。
1 图像二值化
二值化将图像的每个像素变成两种值,比如0,255。首先介绍全局二值化,整幅图像采用同一个数作为阈值,全局二值化的函数时threshold,thresh,dst = threshold(img, thresh, maxval, type) thresh是设定的阈值,maxval表示type参数为THRESH_BINARY或者THRESH_BINARY_INV时的最大值,type表示阈值类型,由ThresholdTypes定义(常用的THRESH_BINARY表示高于阈值的被设置为最大值maxval,低于阈值的被设置为0)。

import cv2

img =cv2.imread('./cat.jpg')

gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) 

thresh,dst = cv2.threshold(gray_img,150,255,type=cv2.THRESH_BINARY)  #返回值有两个,一个是阈值,一个是二值图像


cv2.imshow('img',gray_img)
cv2.imshow('dst',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

全局阈值对于一幅图像上的不同部分具有不同亮度时不适用,可以用自适应阈值,此时的阈值是根据图像上每一小块区域计算与其对应的阈值),因此在同一幅图像上的不同区域采用的是不同的阈值,这样能在亮度不同的情况下得到更好的结果。opencv中自适应阈值函数为 adaptiveThreshold,dst = adaptiveThreshold(img, maxval, adaptiveMethod, thresholdTypes, blocksize, C) maxval表示分配给满足条件的像素的非零像素值;adaptiveMethod表示自适应算法,有平均法(ADAPTIVE_THRESH_MEAN_C)和高斯法(ADAPTIVE_THRESH_GAUSSIAN_C);thresholdType表示阈值类型,blocksize表示计算阈值的块的大小,C是常数。

import cv2

img =cv2.imread('./cat.jpg')

gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

dst = cv2.adaptiveThreshold(gray_img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,9,0)

cv2.imshow('img',gray_img)
cv2.imshow('dst',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

2 腐蚀和膨胀

膨胀是求局部最大值的操作,就是将图像与核(全为1的卷积核)进行卷积,计算核矩阵覆盖区域的像素点的最大值,并把这个最大值赋值给参考点指定的元素,会使图像中的高亮区域逐渐增长。腐蚀是求局部最小值的操作,腐蚀操作会使图像中的高亮区逐渐减小。腐蚀:dst = erode(img, kernel, anchor=None, iterations=None)  腐蚀:dst = dilate(img, kernel, anchor=None, iteration=None)  kernel表示用于运算的核结构,可以用numpy定义,也可以用opencv的getStructuringElement结构元。anchor表示锚点位置,默认是kernel对应区域的中心位置;iteration表示迭代次数。

img =cv2.imread('./dige.png')  #读取二值图像(这里不是二维图像,是rgb图像,像素值只有(0,0,0)和(255,255,255)组成)
print(img.shape)
#img =cv2.imread('./book3.jpg')

# img[:] = 255-img[:]  #图像颜色反转

kernel = np.ones((3,3),np.uint8)     #用于运算的核矩阵(3*3)
#kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))  #用cv构建的形态学卷积核,可以选择膨胀或腐蚀核的类型,矩形,椭圆,十字型

erode_img = cv2.erode(img,kernel,iterations=1)  #腐蚀
dilate_img = cv2.dilate(img,kernel,iterations=1) #膨胀


cv2.imshow('img',img)
cv2.imshow('erode_img',erode_img)
cv2.imshow('dilate_img',dilate_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

 3 其他形态学计算

形态学运算计算方法和用途简单归纳如下:

形态学运算 计算方法 用途
膨胀 核覆盖范围内的局部最大值 连通内部的小块区域,物体主体也会胀大
腐蚀 核覆盖范围内的局部最小值 消除斑点噪声,物体主体也会受到腐蚀
开运算 先腐蚀后膨胀 消除(图形外部的)噪点,去除小的干扰块,不影响原图
闭运算 先膨胀后腐蚀 消除“闭合”物体里面的孔洞(图形内部的噪点)
形态学梯度 膨胀减去腐蚀 寻找图像边界
顶帽运算 原始图像减去开运算 提取明亮区域(提取主体外部噪声)
底帽运算 闭运算减去原始图像 提取阴暗区域(提取主体内部孔洞)

1)开运算和闭运算(MORPH_OPEN  MORPH_CLOSE)

除了腐蚀和膨胀,其他形态学操作都可以用函数morphologyEx来定义,dst = morphologyEx(img, op, kernel, anchor=None, iterations=None)  op 表示形态学操作类型,kernel表示形态学的核结构,anchor表示锚点位置,iterations表示迭代次数。(迭代次数和核结构的大小可以根据实际情况自行设置,核结构越大,迭代次数越多,去噪点能力越强,但是同时对主体的影响也会越大)

开运算和闭运算的例子如下:

import cv2      #开运算和闭运算:开运算:先腐蚀再膨胀操作;闭运算:先膨胀再腐蚀操作
import numpy as np  #开运算:消除(图形外部的)噪点,去除小的干扰块,不影响原图; 闭运算:消除“闭合”物体里面的孔洞(图形内部的噪点).

img2 =cv2.imread('./img2.jpg') 
img3 =cv2.imread('./img3.jpg') 

kernel = np.ones((7,7),np.uint8)     #用于运算的核矩阵(3*3) 如果噪点比较多,可以把size设大一些

open_img = cv2.morphologyEx(img2,cv2.MORPH_OPEN,kernel,iterations=2) #开运算

close_img = cv2.morphologyEx(img3,cv2.MORPH_CLOSE,kernel,iterations=3) #闭运算

cv2.imshow('img_open',np.hstack([img2,open_img]))
cv2.imshow('img_close',np.hstack([img3,close_img]))

cv2.waitKey(0)
cv2.destroyAllWindows()

 

开运算可以消除小人外部的细小噪声而不影响主体大小,而闭运算可以消除小人内部的斑点,基本上不影响主体大小。

 2)形态学梯度(MORPH_GRADIENT)

import cv2
import numpy as np  # 梯度运算:膨胀-腐蚀,可以获取图像的边缘

img =cv2.imread('./dige.png')

kernel = np.ones((3,3),np.uint8)     #用于运算的核矩阵(3*3)

erode_img = cv2.erode(img,kernel,iterations=1)  #腐蚀
dilate_img = cv2.dilate(img,kernel,iterations=1) #膨胀

gradient = dilate_img - erode_img

gradient_img = cv2.morphologyEx(erode_img,cv2.MORPH_GRADIENT,kernel,iterations=1) #梯度运算函数
#img_g = cv2.bitwise_or(img,gradient_img)  #原图与边缘进行或运算,可以达到图像边缘增强的目的

cv2.imshow('img',img)
cv2.imshow('gradient',gradient)
cv2.imshow('gradient_img',gradient_img)
#cv2.imshow('img_g',img_g)


cv2.waitKey(0)
cv2.destroyAllWindows()

从上面的结果可以看出,直接用形态学梯度函数的效果要比膨胀减去腐蚀的操作好一些,函数不会把边缘噪声统计进去,此外可以用原图与边缘进行或运算,可以达到图像边缘增强的目的。

3)顶帽和底帽(MORPH_TOPHAT  MORPH_BLACKHAT)

import cv2
import numpy as np  #顶帽和黑帽。   顶帽:原始图像 - 开运算结果;可以用来提取背景(图形外部的)噪声。 
                        # 黑帽:闭运算 - 元素图像,用来分离比邻近点暗一些的斑块(提取图形内部的噪声)

img2 =cv2.imread('./img2.jpg')
img3 =cv2.imread('./img3.jpg') 

kernel = np.ones((5,5),np.uint8)     #用于运算的核矩阵(3*3)

tophat_img = cv2.morphologyEx(img2,cv2.MORPH_TOPHAT,kernel,iterations=2) #顶帽运算

blackhat_img = cv2.morphologyEx(img3,cv2.MORPH_BLACKHAT,kernel,iterations=3) #黑帽运算

cv2.imshow('tophat_img',np.hstack([img2,tophat_img]))
cv2.imshow('blackhat_img',np.hstack([img3,blackhat_img]))

cv2.waitKey(0)
cv2.destroyAllWindows()

  

窗口左边是原图,右边是对应的顶帽和底帽操作后的结果图。

4 小狗提取优化

上次提取下面图片的小狗时,把图像空间转换到了hsv空间,然后用通过hsv的掩膜提取了狗的图片(如下):

 大致看起来还是可以,但是跟原图比还是差了不少,主要是用颜色提取的掩膜还是不够理想,可以用形态学操作对提取的掩膜进行适当处理,比如用开运算去除小狗外部的少量噪声,用闭运算去除小狗内部的一些孔洞和细线,代码如下:

import cv2
import numpy as np  #转换到HSV根据颜色提取roi区域 + 形态学处理


img = cv2.imread('./cat_dog.jpg')
print(img.shape)
HSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
H, S, V = cv2.split(HSV)

kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))  #获取形态学运算结构

LowerBlue = np.array([0, 100, 150])
UpperBlue = np.array([20, 160, 240])

mask = cv2.inRange(HSV,LowerBlue,UpperBlue)
#mask = cv2.inRange(img,(70,90,160),(105,150,225))

#gas_mask = cv2.GaussianBlur(mask,(5,5),0.8)

dst_close = cv2.morphologyEx(mask,cv2.MORPH_CLOSE,kernel)          #闭运算
dst_open = cv2.morphologyEx(dst_close,cv2.MORPH_OPEN,kernel)       #开运算



dog = cv2.bitwise_and(img,img,mask=dst_open)   #与运算获取roi区域

cv2.imshow('images',img)
cv2.imshow('mask',mask)
cv2.imshow('dst_close',dst_close)
cv2.imshow('dst_open',dst_open)
cv2.imshow('dog',dog)
cv2.waitKey(0)
cv2.destroyAllWindows()

处理后的小狗提取效果如下: