简介
今天继续学习opencv。
一、 图像形态学
什么是形态学:图像形态学是一种处理图像形状特征的图像处理技术,主要用于描述和处理图像中的形状和结构。形态学可以用于提取图像中的特征、消除噪声、改变图像的形状等。
1.腐蚀
正如这个名字表面上的意思,侵蚀的基本思想就像土壤侵蚀一样,它侵蚀了前景物体foreground object 的边界(在这,我们会让前景保持为白色)。那么侵蚀的作用是什么呢?这样内核可以在图像中滑动(在二维卷积中)。只有当内核下的所有像素都为1时,原始图像中的像素(要么为1,要么为0)才会被认为是1,否则会被侵蚀(变成0)。这样的结果就是,取决于内核的大小,边界附近的所有像素都会被丢弃。因此,前景对象的厚度或大小会减少,或者只是简单地让图像中的白色区域减少。它对于去除小的白色噪音(正如我们在颜色空间colorspace 疑问中看到的那样),分离两个连接的物体等都很有用。
腐蚀作用:可以用来消除小且无意义的物体。在这里,作为一个例子,我将使用一个包含内部全都是1的3x3内核。让我们看看它产生什么样的变化。
原图

python
# 1、图像腐蚀,函数为:
# cv2.erode(src, kernel, dst,anchor,iterations,borderType,borderValue)
# src: 输入的图像
# kernel: 用于腐蚀的结构元件如果element = Mat(), 则使用3 x 3的矩形结构单元。
# dst: 它是与src相同大小和类型的输出图像。
# iterations: 腐蚀操作的迭代次数,默认为1。次数越多, 腐蚀操作执行的次数越多,腐蚀效果越明显
import numpy as np
sun = cv2.imread('sun.png')
cv2.imshow('src',sun)
cv2.waitKey(0)
kernel = np.ones((3,3),np.uint8) # 这里kernel大小,修改为5*5试试
erosion_1 = cv2.erode(sun,kernel,iterations=2) #iterations改为5试试
cv2.imshow('erosion_1',erosion_1)
cv2.waitKey(0)

2.膨胀
膨胀操作正好与腐蚀相反。这里,如果内核下至少有一个像素是"1",那么该像素元素就是"1"。因此,它增加了图像中的白色区域,或则说增加了前景目标对象的尺寸大小。通常情况下,在去除噪声以后,在腐蚀操作之后就是膨胀。因为,腐蚀消除了白色的噪音,但它也缩小了我们的前景物体,所以我们需要扩大回它。因为当噪音消失了,原本应该存在的白色面积也不会自主回来。而且膨胀在连接物体的破碎部分时也很有用。

python
# 2、图像膨胀, 函数为:
# cv2.dilate(img, kernel, iteration)
# 参数含义:
# img -- 目标图片
# kernel -- 进行操作的内核,默认为3×3的矩阵
# iterations -- 膨胀次数,默认为1
wenzi = cv2.imread('wenzi.png')
cv2.imshow('src1',wenzi)
cv2.waitKey(0)
kernel = np.ones((2,2),np.uint8) # 这里kernel大小
wenzi_new = cv2.dilate(wenzi,kernel,iterations=2)
cv2.imshow('wenzi_new',wenzi_new)
cv2.waitKey(0)

3.开运算
先进行腐蚀,再进行膨胀就叫做开运算。 通常情况下,含有噪声的图像二值化后,得到的边界是不平滑的,物体区域具有一些错判的孔洞,背景区域散布着一些小的噪声物体。对一个图像先进行腐蚀运算然后再膨胀的操作过程称为开运算,它可以消除细小的物体、在纤细点处分离物体、平滑较大物体的边界时不明显的改变其面积。
在做对指纹识别的时候,遇到这样的图片对我们的识别效果影响不好,太多嘈杂的点了,我们就需要对其进行开运算。

python
# 开运算:先腐蚀后膨胀 作用:平滑物体的轮廓、断开较窄的狭颈并消除细的突出物。
zhiwen = cv2.imread('zhiwen.png')
cv2.imshow('src2',zhiwen)
cv2.waitKey(0)
kernel = np.ones((2,2),np.uint8) #设置kenenl大小
zhiwen_new = cv2.morphologyEx(zhiwen,cv2.MORPH_OPEN,kernel) #开运算
cv2.imshow('zhiwen_new',zhiwen_new)
cv2.waitKey(0)

4.闭运算
先膨胀再腐蚀。它经常被用来填充前景物体中的小洞,或者前景物体上面的小黑点。
对于这样的指纹,我们发现它中间好多断开了,有很多空洞不太符合我们正常的指纹,需要进行闭运算进行处理

python
# # 闭运算:先膨胀后腐蚀 作用:弥合较窄的间断和细长的沟壑,消除小的孔洞,填补轮廓线中的断裂。
zhiwen_duan = cv2.imread('zhiwen_duan.png')
cv2.imshow('src3',zhiwen_duan)
cv2.waitKey(0)
kernel = np.ones((4,4),np.uint8) #设置kenenl大小
zhiwen_new = cv2.morphologyEx(zhiwen_duan,cv2.MORPH_CLOSE,kernel) #闭运算
cv2.imshow('zhiwen_new1',zhiwen_new)
cv2.waitKey(0)
#

5.梯度运算
其实就是一幅图像膨胀和腐蚀的差别。突出显示图像中强度变化剧烈的地方

python
wenzi = cv2.imread('wenzi.png')
cv2.imshow('wenzi_new',wenzi)
cv2.waitKey(0)
kernel = np.ones((2,2),np.uint8) #设置kenenl大小
# 膨胀
pz_wenzi=cv2.dilate(wenzi,kernel,iterations=1)
cv2.imshow('pz_wenzi',pz_wenzi)
cv2.waitKey(0)
#腐蚀
fs_wenzi=cv2.erode(wenzi,kernel,iterations=1)
cv2.imshow('fs_wenzi',fs_wenzi)
cv2.waitKey(0)
# 膨胀-腐蚀
bianyuan = cv2.morphologyEx(wenzi,cv2.MORPH_GRADIENT,kernel)
cv2.imshow('bianyuan',bianyuan)
cv2.waitKey(0)
cv2.destroyAllWindows()

6.顶帽
顶帽 = 原始图像 - 开运算结果(先腐蚀后膨胀)

python
sun = cv2.imread('sun.png')
cv2.imshow('sun_yuantu',sun)
cv2.waitKey(0)
kernel = np.ones((2,2),np.uint8) #设置kenenel大小
#开运算
open_sun=cv2.morphologyEx(sun,cv2.MORPH_OPEN,kernel)
cv2.imshow('open_sun',open_sun)
cv2.waitKey(0)
#顶帽
tophat = cv2.morphologyEx(sun,cv2.MORPH_TOPHAT,kernel)
cv2.imshow('TOPHAT',tophat)
cv2.waitKey(0)

7.黑帽

python
sun = cv2.imread('sun.png')
cv2.imshow('sun_yuantu',sun)
cv2.waitKey(0)
kernel = np.ones((2,2),np.uint8) #设置kenenel大小
# #闭运算
close_sun=cv2.morphologyEx(sun,cv2.MORPH_CLOSE,kernel)
cv2.imshow('close_sun',close_sun)
cv2.waitKey(0)
#黑帽
blackhat = cv2.morphologyEx(sun,cv2.MORPH_BLACKHAT,kernel)
cv2.imshow('BLACKHAT',blackhat)
cv2.waitKey(0)

二、图像边缘检测
边缘检测:是图形图像处理、计算机视觉和机器视觉中的一个基本工具,通常用于特征提取和特征检测,旨在检测一张数字图像中有明显变化的边缘或者不连续的区域。
1.Sobel算子
Sobel 算子是一种离散的微分算子,该算子结合了高斯平滑和微分求导运算。该算子利用局部差分寻找边缘,计算所得的是一个梯度的近似值。Sobel算子包含2组3×3的矩阵,分别为横向和纵向模板,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。



python
# sobel算子
# cv2.Sobel(src, ddepth, dx, dy[, ksize[, scale[, delta[, borderType]]]])
# 参数:
# src: 输入图像
# ddepth: 输出图像的深度(可以理解为数据类型),-1表示与原图相同的深度
# dx,dy: 当组合为dx=1,dy=0时为x方向的一阶导数,当组合为dx=0,dy=1时为y方向的一阶导数(如果同时为1,通常效果不佳)
# ksize:(可选参数)Sobel算子的大小,必须是1,3,5或者7,默认为3。
yuan = cv2.imread('yuan.png',0)
yuan = cv2.resize(yuan,dsize=None,fy=0.5,fx=0.5)
cv2.imshow('yuan',yuan)
cv2.waitKey(0)
# # x方向上的边缘
yuan_x = cv2.Sobel(yuan,-1,dx=1,dy=0)
cv2.imshow('yuan_x',yuan_x)
cv2.waitKey(0)
# x方向上的边缘,包括负数信息(右减),但显示不出来,因为范围是(0~255)
yuan_x_64 = cv2.Sobel(yuan,cv2.CV_64F,dx=1,dy=0)#数据uint8改为float64,可保存负数
cv2.imshow('yuan_x_64',yuan_x_64)
cv2.waitKey(0)
# #x方向上的边缘,包括负数信息,进行取绝对值的操作,右端的负值信息就可以显示出来了
yuan_x_full = cv2.convertScaleAbs(yuan_x_64)#转换为绝对值,负数转换为正数
cv2.imshow('yuan_x_full',yuan_x_full)
cv2.waitKey(0)
# # y方向上的边缘
yuan_y = cv2.Sobel(yuan,-1,dx=0,dy=1)
cv2.imshow('yuan_y',yuan_y)
cv2.waitKey(0)
# y方向上的边缘,包括负数信息(下减),但显示不出来,因为范围是(0~255)
yuan_y_64 = cv2.Sobel(yuan,cv2.CV_64F,dx=0,dy=1)#数据uint8改为float64,可保存负数
yuan_y_full = cv2.convertScaleAbs(yuan_y_64)#转换为绝对值,负数转换为正数
cv2.imshow('yuan_y_full',yuan_y_full)
cv2.waitKey(0)
# # 如果同时使用x,y方向的结果如何呢?(不建议使用!)
yuan_xy = cv2.Sobel(yuan,-1,dx=1,dy=1)
cv2.imshow('yuan_xy',yuan_xy)
cv2.waitKey(0)
# # 使用图像加权运算组合x和y方向的2个边缘。
yuan_xy_full = cv2.addWeighted(yuan_x_full, 1,yuan_y_full, 1, gamma=0)
cv2.imshow('yuan_xy_full',yuan_xy_full)
cv2.waitKey(0)


python
dama = cv2.imread('dama.jpg', 0)#不用灰度试试效果
dama=cv2.resize(dama,dsize=None,fx=0.5,fy=0.5)
dama_x_64 = cv2.Sobel(dama,cv2.CV_64F,dx=1,dy=0)#默认int8改为float64,可保存负数
dama_x_full = cv2.convertScaleAbs(dama_x_64)#转换为绝对值,负数转换为正数
dama_y_64 = cv2.Sobel(dama,cv2.CV_64F,dx=0,dy=1)#默认int8改为float64,可保存负数
dama_y_full = cv2.convertScaleAbs(dama_y_64)#转换为绝对值,负数转换为正数
dama_xy_sobel_full = cv2.addWeighted(dama_x_full, 1,dama_y_full, 1, 0)
cv2.imshow('dama_xy_sobel_full',dama_xy_sobel_full)
cv2.waitKey(0)
2.Scharr 算子
Scharr 算子是 Soble 算子在 ksize=3 时的优化,与 Soble 的速度相同,且精度更高。Scharr 算子与 Sobel 算子的不同点是在平滑部分,其中心元素占的权重更重,相当于使用较小标准差的高斯函数,也就是更瘦高的模板。

python
dama = cv2.imread('dama.jpg',cv2.IMREAD_GRAYSCALE) #Scharr算子
# zl=cv2.cvtColor(zl,cv2.COLOR_BGR2GRAY)
dama_x_64 = cv2.Scharr(dama,cv2.CV_64F,dx=1,dy=0)#默认int8改为float64,可保存负数
dama_x_full = cv2.convertScaleAbs(dama_x_64)#转换为绝对值,负数转换为正数
dama_y_64 = cv2.Scharr(dama,cv2.CV_64F,dx=0,dy=1)#默认int8改为float64,可保存负数
dama_y_full = cv2.convertScaleAbs(dama_y_64)#转换为绝对值,负数转换为正数
dama_xy_Scharr_full = cv2.addWeighted(dama_x_full, 1,dama_y_full, 1, 0)
cv2.imshow('dama_xy_Scharr_full',dama_xy_Scharr_full)
cv2.waitKey(0)

3.Laplacian算子
不再以x和y的方向计算,而是以圆方向计算变化率。因此不需要Gx+Gy。

python
# ###### cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
# ###### # src: 输入图像,可以是灰度图像,也可以是多通道的彩色图像
# ###### # ddepth: 输出图片的数据深度;
# ###### # ksize: 计算二阶导数滤波器的孔径大小,必须为正奇数,可选项
# ###### # scale: 缩放比例因子,可选项,默认值为 1
# ###### # delta: 输出图像的偏移量,可选项,默认值为 0
# ###### #
#
dama= cv2.imread('yuan.png',cv2.IMREAD_GRAYSCALE)
dama_lap = cv2.Laplacian(dama,cv2.CV_64F)
dama_lap_full = cv2.convertScaleAbs(dama_lap)#转换为绝对值,负数转换为正数
cv2.imshow('dama_lap_full',dama_lap_full)
cv2.waitKey(0)

4.Canny边缘检测
√ 低错误率。因为一般的边缘检测算子可能存在检测到伪边缘的情况,因此Canny算法检测到的边缘尽可能地是真实的边缘。
√ 较好地定位边缘点。由检测器标记的边缘点与真实边缘点中心尽可能地接近。
√ 单一的边缘响应。图像中的边缘只标记出一次。

1.图像降噪
图像去噪是进行边缘检测的第一步,通过去噪可以去除图像中的一些噪点,从而使边缘检测时免受噪点干扰。高斯滤波
2.梯度计算
要进行边缘检测,就需要得到图像梯度信息,根据图像的梯度幅值和梯度方向来确定边缘,一般均采用sobel算子对图像进行梯度幅值与梯度方向计算。

3.非极大值抑制NMS(Non-Maximal Suppression)
一阶微分在灰度值斜坡过渡时不为零且存在较粗的边缘,较粗的边缘会增大边缘检测的误差,因此需要细化边缘,一种较为常用的方法是非极大值抑制。非极大值抑制:即在梯度图像中寻找梯度方向上的最大值作为边缘,不是梯度方向上的最大值则抑制为0。因为梯度方向是灰度变化最大的方向。比较梯度图像中每一点的灰度值与梯度方向上至少两个梯度图像像素点灰度值的大小,根据上述大小关系来确定是否保留该点的灰度值。
4.双阈值边界跟踪
双阈值处理就是根据实际情况需要设置一个灰度高阈值和一个灰度低阈值对NMS后的图像进行过滤,使得得到的边缘尽可能是真实的边缘。双阈值的基本处理步骤为:


当然上面这些不需要我们写代码进行一步一步处理,只需要调用Canny边缘检测的函数会自己计算、我们只要简单了解有哪些过程就行。每一个过程有啥用

python
# ###### canny边缘检测
# ###### cv2.Canny(image, threshold1, threshold2[, apertureSize[, L2gradient]])
# ###### # image 为输入图像。
# ###### # threshold1 表示处理过程中的第一个阈值。
# ###### # threshold2 表示处理过程中的第二个阈值。
# ###### #
#
zl = cv2.imread('dama.jpg',cv2.IMREAD_GRAYSCALE)
zl=cv2.resize(zl,dsize=None,fy=0.5,fx=0.5)
cv2.imshow('dama',zl)
cv2.waitKey(0)
zl_canny = cv2.Canny(zl, 150, 230)#低,高
cv2.imshow('dama_canny',zl_canny)
cv2.waitKey(0)

总结
读者可以使用自己的图片进行测试,在边缘检测上Canny边缘检测相对于前三个具有更好的检测效果。