OpenCV:06形态学

形态学

  • 形态学概述`morphology`
  • 图像的全局二值化
  • 自适应阈值二值化
  • 腐蚀操作
    • 获取形态学的卷积核
  • 膨胀操作
    • 腐蚀与膨胀结合操作
  • 开闭运算
    • 开运算
    • 闭运算
  • 形态学梯度
  • 顶帽操作
  • 黑帽操作

形态学概述morphology

什么是形态学?

  • 指一系列处理图像形状特征的图像处理技术
  • 形态学的基本思想是利用一种特殊的结构元(本质上就是卷积核,该卷积核只有0和1两个数字),通过01来测量或提取输入图像中相应的形状或特征,以便进一步进行图像分析和目标识别
  • 这些处理方法基本是对二进制图像的处理,即黑白图像
  • 卷积核决定着图像处理后的效果
  • 形态学基本操作有:
    • 膨胀和腐蚀
    • 开运算
    • 闭运算
    • 顶帽
    • 黑帽

通俗地说,你想要获取图像中的任何部分(噪声、图形、轮廓…)形态学都能满足,这就是它的魅力

图像的全局二值化

二值化:将图像的每个像素变成两种值,比如0,255(0是黑色,255是白色)

二值化本质:对整个图片进行统一操作,即人为设置一个阈值,大于这个值的像素点赋一个值,小于这个值的像素点赋另一个值

关键API:cv2.threshold(src, thresh, maxval, type[, dst])
其中:
二值化操作是对灰度图操作的 ——> 因此我们首先要把图像变成灰度图!cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

  • src:进行全局二值化的图片最好不要是多通道的彩色图片,最好是灰度图

  • thresh:阈值,作用于下方的type操作类型的判断条件

  • maxval:最大值,不一定是255(但我们常设置为255)

  • type:操作类型,常见操作类型如下
    OpenCV:06形态学_第1张图片
    THRESH_BINARY:当该点像素大于人为设置的阈值thresh,则该点的像素值被赋值为最大值maxval;当该点的像素值小于阈值thresh(otherwise条件下),则赋值为0

    THRESH_BINARY_INVINV表示invert(反转)的意思,当该点像素小于人为设置的阈值thresh,则该点的像素值被赋值为最大值maxval;当该点的像素值大于阈值thresh(otherwise条件下),则赋值为0

    THRESH_TRUNCTRUNC表示truncate,有将数字截尾取整的意思(夹断),当该点像素大于人为设置的阈值thresh,则该点的像素值统一被赋值为阈值thresh;当该点的像素值小于阈值thresh(otherwise条件下),则像素值还是其本身src(x,y)

    THRESH_TOZERO:当该点像素大于人为设置的阈值thresh,则该点的像素值还是其本身src(x,y);当该点的像素值小于阈值thresh(otherwise条件下),则赋值为0

    THRESH_TOZERO_INV:当该点像素大于人为设置的阈值thresh,则赋值为0;当该点的像素值小于阈值thresh(otherwise条件下),则该点的像素值还是其本身src(x,y)

# 全局二值化

import cv2
import numpy as np

# 导入图片
img = cv2.imread('./cat.jpeg')

# 二值化操作是对灰度图操作的 ——> 因此我们首先要把图像变成灰度图!
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# 进行二值化操作(注意!threshold有两个返回值!一个是阈值,一个是二值化处理后的图片)
thresh,dst = cv2.threshold(gray,127,255,cv2.THRESH_BINARY) # 最终二值化的效果会受到阈值(第二个参数thresh)的影响


# 展示
# cv2.imshow('dst',dst)
cv2.imshow('img and dst ',np.hstack((gray,dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

结果:
OpenCV:06形态学_第2张图片
左边的灰度图的值在0-255,即可以有128、254、222…这些值;而右边的黑白图只有0和255两个值,任取一个像素点不是0就是255

我们可以不断调整阈值大小来美化图片


自适应阈值二值化

我们可以发现上一个方法还是不太好,调了半天阈值,想要的东西都显示不出来。我们需要一个更灵活的阈值,能够根据当前的区域去自己计算出一个阈值,这就是我们的——自适应阈值

在前面的部分我们采用的是全局阈值,整幅图像采用同一个数作为阈值。当然这种方法并不适用于所有情况,尤其是当同一幅图像上的不同地方具有不同亮度时,这种情况我们需要采取自适应阈值。此时的阈值是根据图像上的每一个小区域计算对应的阈值。因此在同一幅图像的不同区域采取的是不同的阈值,从而我们能够在亮度不同的情况下得到更好的结果

关键API:cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst])

这种方法需要指定6个参数,返回值只有一个
其中

  • src:操作的图像
  • maxValue:最大值,一般写为255
  • adaptiveMethod:自适应的方法,常用的两种
    • cv2.ADAPTIVE_THRESH_MEAN_C:阈值取自相邻区域的平均值,类似我们的卷积操作,效果一般般,不如下面的高斯方法
    • cv2.ADAPTIVE_THRESH_GAUSSIAN_C:阈值取相邻区域的加权和,越靠近中心区域的点权重越大,权重是一个高斯窗口
  • thresholdType:阈值类型,和上面的type一模一样
  • blockSize:卷积核的大小,写一个数字就可以了(不用写元组)
  • C:一个常数,阈值就等于平均值或者加权平均值加上这个常数

例子1:

# 全局二值化

import cv2
import numpy as np

# 导入图片
img = cv2.imread('./dog.jpeg')

# 二值化操作是对灰度图操作的 ——> 因此我们首先要把图像变成灰度图!
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# 进行二值化操作(注意!自适应二值化只有一个返回值:阈值)
dst = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,9,0)

# 展示
cv2.imshow('dst',dst)

cv2.waitKey(0)
cv2.destroyAllWindows()

结果:

OpenCV:06形态学_第3张图片

可以很清楚地看到脖子上的钥匙(有个轮廓)
OpenCV:06形态学_第4张图片

例子2:更具代表性的例子
我们的目标是看清楚这张图中的题目:由于光线的问题,拍出来上半部分亮度高一点,下半部分暗一点。如果使用全局二值化,效果非常差;我们选用自适应二值化
OpenCV:06形态学_第5张图片

先看看全局二值化的效果:

thresh,dst = cv2.threshold(gray,200,255,cv2.THRESH_BINARY)OpenCV:06形态学_第6张图片我们可以通过调小阈值,使得图片更加清晰

thresh,dst = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)

OpenCV:06形态学_第7张图片
我们发现全局二值化对光线不好的地方效果不太好,尽管可以通过调整阈值改进,但效果始终不如自适应阈值二值化!

# 全局二值化 更具有代表性的例子

import cv2
import numpy as np

# 导入图片
img = cv2.imread('./math.jpeg')

# 二值化操作是对灰度图操作的 ——> 因此我们首先要把图像变成灰度图!
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# 进行二值化操作(注意!自适应二值化只有一个返回值:阈值)
dst = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,9,0)

# 展示
cv2.imshow('dst',dst)

cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCV:06形态学_第8张图片
如果我们想要把背景改成白色:只需要INV一下,把参数 thresholdType中的THRESH_BINARY改成THRESH_BINARY_INV即可

OpenCV:06形态学_第9张图片


腐蚀操作

腐蚀操作也是用卷积核扫描图像,只不过腐蚀操作的卷积核一般都是1,如果卷积核内所有像素点都是白色(255),那么锚点(中心点)即为白色;如果有一个点不是白的,那么锚点就是黑色
OpenCV:06形态学_第10张图片
通过腐蚀操作后:只有中心的虚线框能够是白色,其余地方都被腐蚀掉了。卷积核越大,那么腐蚀的就越多

大部分时候腐蚀操作使用的都是全为1的卷积核
OpenCV:06形态学_第11张图片

关键API:erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
其中:

  • src:操作的原图
  • kernel:腐蚀操作使用的卷积核的大小 (写为元组的形式)
  • anchor:锚点(不用写)
  • iterations:迭代器,代表腐蚀操作的迭代次数,次数越多,腐蚀操作执行的次数越多,腐蚀效果越明显(相当于在原先腐蚀的基础上再扫描一次)

文字中多余的部分比较细小,因此卷积核扫描时一定会扫描到!会被除去。只有纯白的地方,经过卷积核的扫描才会留下了,而且我们会发现字都小了一圈

# opencv的腐蚀操作

import cv2
import numpy as np

# 导入图片
img = cv2.imread('./erode.jpeg')

# 定义一个卷积核
kernel = np.ones((5,5),np.uint8)

# 进行腐蚀操作
dst = cv2.erode(img,kernel)

# 展示
cv2.imshow('img and dst ',np.hstack((img,dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

结果:
如果发现第一次没有完全消除,我们可以改变kernel的大小或是改变迭代器iterations的大小
OpenCV:06形态学_第12张图片


获取形态学的卷积核

上方的腐蚀操作,我们需要自己设置卷积核的大小kernel,而opencv提供了API,可以自动地生成卷积核,不用我们再自己设置啦

关键API:getStructuringElement(shape, ksize[, anchor])
其中:

StructuringElement意为结构元,也是我们常说的卷积核

  • shape:值卷积核的形状,注意并不是指卷积核的长宽,而是指卷积核中1形成的形状
    • cv2.MORPH_RECT:卷积核中的1会形成矩形,常用
    • cv2.MORPH_ELLIPSE:卷积核中的1会形成椭圆
    • cv2.MORPH_CROSS:卷积核中的1会形成十字
"矩形"
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
print(kernel)
结果:
[[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]]
 
----------------------------------------------------------------
"椭圆"
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(7,7))
print(kernel)
结果:
[[0 0 0 1 0 0 0]
 [0 1 1 1 1 1 0]
 [1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1]
 [0 1 1 1 1 1 0]
 [0 0 0 1 0 0 0]]
 
 ----------------------------------------------------------------
 "十字架"
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
print(kernel)
结果:
[[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]]
 

就算改成了椭圆或十字架,腐蚀操作的原理还是一样的,只要卷积核中1的部分有黑色,该锚点(中心点)就是黑色,只有1的位置全为白色,锚点才能是白色的

  • ksize:卷积核的长宽
# 获取形态学的卷积核

import cv2
import numpy as np

# 导入图片
img = cv2.imread('./j.jpeg')

# 利用API获取形态学的卷积核
kernel_RECT = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
kernel_ELLIPSE = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
kernel_CROSS = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))

# 进行腐蚀操作
dst = cv2.erode(img,kernel_RECT)
# dst = cv2.erode(img,kernel_ELLIPSE)
# dst = cv2.erode(img,kernel_CROSS)


# 展示
cv2.imshow('img and dst ',np.hstack((img,dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

结果:
OpenCV:06形态学_第13张图片


膨胀操作

膨胀操作是腐蚀的相反操作,你既然可以擦掉它,那么也可以把它补上去

基本原理是:只要保持卷积核的锚点是非0值(白色),周边无论是0值(黑色)还是非0值(白色),都变成非0值(白色)

OpenCV:06形态学_第14张图片

关键API:dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
其中:

  • src:操作的原图
  • kernel:卷积核
  • iterations:迭代器,代表迭代次数
  • 和腐蚀操作的参数一样
# 膨胀操作

import cv2
import numpy as np

# 导入图片
img = cv2.imread('./j.jpeg')

# 利用API获取形态学的卷积核
kernel_RECT = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
kernel_ELLIPSE = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
kernel_CROSS = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))

# 进行膨胀操作
dst = cv2.dilate(img,kernel_RECT)
# dst = cv2.dilate(img,kernel_ELLIPSE)
# dst = cv2.dilate(img,kernel_CROSS)


# 展示
cv2.imshow('img and dst ',np.hstack((img,dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

结果:
OpenCV:06形态学_第15张图片
图像变粗了

腐蚀与膨胀结合操作

由腐蚀操作:虽然我们把图像杂质去掉了,但是字整体变细了,也算是一种失真了。因此我们可以用腐蚀与膨胀结合起来处理图像

原图:
OpenCV:06形态学_第16张图片

图像处理:

注意:如果用到了迭代操作,则腐蚀和膨胀的迭代次数要一样!这样才能把图片恢复过来!

# 腐蚀操作 + 膨胀操作 

import cv2
import numpy as np

# 导入图片
img = cv2.imread('./erode.jpeg')

# 利用API获取形态学的卷积核
kernel_RECT = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
kernel_ELLIPSE = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
kernel_CROSS = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))

# 进行腐蚀操作
temp = cv2.erode(img,kernel_RECT)
# dst = cv2.erode(img,kernel_ELLIPSE)
# dst = cv2.erode(img,kernel_CROSS)

# 进行膨胀操作
dst = cv2.dilate(temp,kernel_RECT)
# dst = cv2.dilate(img,kernel_ELLIPSE)
# dst = cv2.dilate(img,kernel_CROSS)


# 展示
cv2.imshow('img and dst ',np.hstack((img,dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

结果:
OpenCV:06形态学_第17张图片


开闭运算

  • 开运算和闭运算都是腐蚀和膨胀的基本应用

开运算

开运算 = 腐蚀 ——> 膨胀

开运算提供了另一种去除噪声的思路

原图:
OpenCV:06形态学_第18张图片

关键API:cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
其中:

  • src:操作的图像
  • op:开闭运算选择位:cv2.MORPH_OPEN:开运算cv2.MORPH_ CLOSE:闭运算
  • kernel:卷积核
# 开运算 
# 开运算 = 腐蚀 + 膨胀

import cv2
import numpy as np

# 导入图片
img = cv2.imread('./dotj.png')

#我们先手动操作一遍:先腐蚀,后膨胀
# 利用API获取形态学的卷积核
kernel_RECT = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))

# 进行腐蚀操作
temp = cv2.erode(img,kernel_RECT,iterations = 3)

# 进行膨胀操作
dst0 = cv2.dilate(temp,kernel_RECT,iterations = 3)  

# -----------------------------------------------------------------#

# 利用opencv自带的API进行开运算,直接一步到位
dst = cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel_RECT,iterations = 3)

# 展示
cv2.imshow('img and dst ',np.hstack((img,dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

结果:
OpenCV:06形态学_第19张图片


闭运算

闭运算和开运算相反:先膨胀后腐蚀

使用场景:白色中有噪声点,我们就可以用闭运算处理
OpenCV:06形态学_第20张图片

原因:先膨胀,使得图像白色的地方放大一圈,黑色地方不变;后腐蚀,相当于把增大的白色部分连同噪声一同消去

关键API:cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
其中:

  • src:操作的图像
  • op:开闭运算选择位:cv2.MORPH_OPEN:开运算cv2.MORPH_ CLOSE:闭运算
  • kernel:卷积核
# 闭运算 
#闭运算 = 膨胀 + 腐蚀 

import cv2
import numpy as np

# 导入图片
img = cv2.imread('./dot_in_j.png')

# 利用API获取形态学的卷积核
kernel_RECT = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))

# 利用opencv自带的API进行闭运算,直接一步到位
dst = cv2.morphologyEx(img,cv2.MORPH_CLOSE,kernel_RECT,iterations = 3)

# 展示
cv2.imshow('img and dst ',np.hstack((img,dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

结果:

OpenCV:06形态学_第21张图片


形态学梯度

梯度 = 原图 - 腐蚀

腐蚀后原图的边缘变小了,原图 - 腐蚀就可以得到原图被腐蚀掉的部分,即边缘

边缘的粗细我们也可以通过kerneliterations来调整

关键API:cv2.morphologyEx(img,cv2.MORPH_GRADIENT,kernel_RECT,iterations = 1)

  • 关键是参数:op = cv2.MORPH_GRADIENT
  • 其余参数和开闭运算一样!
# 形态学梯度 = 原图 - 腐蚀后的图

import cv2
import numpy as np

# 导入图片
img = cv2.imread('./j.jpeg')

# 利用API获取形态学的卷积核
kernel_RECT = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))

# 利用opencv自带的API进行形态学梯度运算,直接一步到位
dst = cv2.morphologyEx(img,cv2.MORPH_GRADIENT,kernel_RECT,iterations = 1)

# 展示
cv2.imshow('img and dst ',np.hstack((img,dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

结果:
OpenCV:06形态学_第22张图片


顶帽操作

顶帽 = 原图 - 开运算

  • 开运算的效果是:除去图形外的噪点
  • 原图 - 开运算 = 去掉的噪声,即把去掉的噪声留下来(获取去掉的噪声)

关键API:cv2.morphologyEx(img,cv2.MORPH_TOPHAT,kernel_RECT,iterations = 3)

  • 关键是参数:op = cv2.MORPH_TOPHAT
  • 其余参数和开闭运算一样!
# 顶帽操作 = 原图 - 开运算
# 得到图形外的噪声

import cv2
import numpy as np

# 导入图片
img = cv2.imread('./dotj.png')

# 利用API获取形态学的卷积核
kernel_RECT = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))

# 利用opencv自带的API进行形态学梯度运算,直接一步到位
dst = cv2.morphologyEx(img,cv2.MORPH_TOPHAT,kernel_RECT,iterations = 3)

# 展示
cv2.imshow('img and dst ',np.hstack((img,dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

结果:
OpenCV:06形态学_第23张图片


黑帽操作

黑帽 = 原图 - 闭运算

闭运算:把图形内部的噪点去掉,那么 原图 - 闭运算 的结果就是图形内部的噪点

# 黑帽操作 = 原图 - 闭运算
# 得到图形内的噪声

import cv2
import numpy as np

# 导入图片
img = cv2.imread('./dot_in_j.png')

# 利用API获取形态学的卷积核
kernel_RECT = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))

# 利用opencv自带的API进行形态学梯度运算,直接一步到位
dst = cv2.morphologyEx(img,cv2.MORPH_BLACKHAT,kernel_RECT,iterations = 3)

# 展示
cv2.imshow('img and dst ',np.hstack((img,dst)))

cv2.waitKey(0)
cv2.destroyAllWindows()

结果:
OpenCV:06形态学_第24张图片

你可能感兴趣的