opencv-python形态学计算
形态学运算包括腐蚀,膨胀,开运算,闭运算,形态学梯度,顶帽运算,底帽运算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()
处理后的小狗提取效果如下: