一、动态调整效果
在之前我们学习了利用窗口对一幅图像进行展示,但有时我们计算了一个阈值,想通过滑动进度条的方式
改变阈值,从而观察不同阈值对处理效果的影响。有时需要观察处理前后的差别,这些都可以用进度条完成。
这就是本部分要说的内容。在OpenCV中已经非常人性化地内置了这种进度条,我们不用去管内部实现原理,
只需要简单调用即可。cv2.createTrackbar()
用于创建一个进度条,cv2.getTrackbarPos()
用于获取一个进度条。
代码如下:
# coding=utf-8
import numpy as np
import cv2
# 定义滑动条的回调函数,这里什么也不做,直接pass即可。这里的x便是传递回来的滑动条的位置。我们可以print到控制台中。
def nothing(x):
pass
# 首先读取一个图像用于处理
img = cv2.imread("E:\\bin.jpg")
# 将图像转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 一些变量的初始化
threshold = 128
result = gray
# 新建一个窗口,并命名为Threshold Test
cv2.namedWindow("Threshold Test")
# 创建两个进度条,分别用于控制阈值大小和效果开关
# 第一个参数是进度条的名字,也是识别进度条的唯一ID
# 第二个参数是需要绑定进度条的窗体的名字
# 第三个参数是进度条的初始值
# 第四个参数是进度条的最大值
# 第五个参数是回调函数,每次滑动都会调用回调函数。通常包含一个默认参数,即滑动条的位置。在这里我们不做任何事情直接定义一个函数然后pass即可
cv2.createTrackbar('Threshold', 'Threshold Test', threshold, 255, nothing)
# 进度条的第二个作用便是当Switch开关。其赋值只有0和1。
cv2.createTrackbar('0:Off 1:On', 'Threshold Test', 1, 1, nothing)
# 循环执行语句
while 1:
# 设置显示的图片,由于之前对result赋过值,这里显示的是灰度图像
cv2.imshow('Threshold Test', result)
# 实现Esc退出,因为这是一个死循环,所以必须要有退出代码。如果点击窗体上的×是关不了的。
k = cv2.waitKey(1) & 0xff
if k == 27:
break
# 获取当前进度条的状态,返回值是进度条当前位置
# 第一个参数是要获取的进度条的ID
# 第二个参数是窗体的名称
threshold = cv2.getTrackbarPos('Threshold', 'Threshold Test')
s = cv2.getTrackbarPos('0:Off 1:On', 'Threshold Test')
# 对是否开启效果进行判断
if s == 0:
# 如果没有开启,那么直接显示读取的灰度图像
result = gray
else:
# 如果开启效果,那么调用二值化函数,并将Threshold进度条的位置传递给它,实现动态变化
ret, result = cv2.threshold(gray, threshold, 255, cv2.THRESH_BINARY)
# 最后完成之后别忘销毁窗口
cv2.destroyAllWindows()
实现的效果如下:
二、Canny边缘检测
1.噪声去除
由于边缘检测很容易受到噪声影响,因此 第一步使用5×5的高斯滤波器去除噪声。
2.计算图像梯度
对平滑后的图像利用Sobel算子计算水平方向和竖直方向的一阶导数(图像梯度,Gx和Gy)。根据得到的这两幅梯度图(Gx和Gy) 找到边界的梯度和方向。计算梯度与方向的公式与之前相同,这里就不再赘述。梯度的方向一般与边界垂直,其一般被归为 4类:垂直、水平、和两个对角线方向。
3.非极大值抑制
得到梯度方向和大小后,应该对图像做一个整体扫描,去除那些非边界上的点。具体做法是对每一个像素进行检查,看这个点 的梯度是不是周围具有相同梯度方向的点中最大的。 如图所示在同一梯度方向上有A、B、C三个点。其中A位于边界上,而B、C不在边界上。所以A的梯度要大于B、C。因此要去除B、C, 保留A。会得到一个包含“窄边界”的图像。
4.滞后阈值
现在需要确定哪些边界是真正的边界。需要设定两个阈值:minVal、maxVal。当图像的灰度梯度高于maxVal时,认为是真正的边界, 小于minVal时认为不是边界。介于两者之间时,若其与某个被确定为真正边界的点相连,则认为它是边界点,否则不是。如下图: 图中A高于maxVal,因此认为是真边界。C虽然低于maxVal,但与A相连且大于minVal,所以也认为是真边界。而B虽介于两者之间, 但不与真边界相连,所以不认为是边界。在这一步一些小的噪声点也会被去除,因为我们认为边界都是一些长的线段。
5.OpenCV实现
在OpenCV中,只需要cv2.Canny()
函数便可以实现以上步骤,且无需关心原理。实现代码如下:
# coding=utf-8
import numpy as np
import cv2
def nothing(x):
pass
img = cv2.imread("E:\\bin.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
minVal = 100
maxVal = 200
switch = 1
result = gray
cv2.namedWindow("Canny Test")
cv2.createTrackbar('minVal', 'Canny Test', minVal, 255, nothing)
cv2.createTrackbar('maxVal', 'Canny Test', maxVal, 255, nothing)
cv2.createTrackbar('0:Off 1:On', 'Canny Test', switch, 1, nothing)
while 1:
cv2.imshow('Canny Test', result)
k = cv2.waitKey(1) & 0xff
if k == 27:
break
minVal = cv2.getTrackbarPos('minVal', 'Canny Test')
maxVal = cv2.getTrackbarPos('maxVal', 'Canny Test')
switch = cv2.getTrackbarPos('0:Off 1:On', 'Canny Test')
if switch == 1:
# 第一个参数是待处理的图像
# 第二个参数是最小阈值minVal
# 第三个参数是最大阈值maxVal
# 第四个参数是Sobel算子卷积核大小,默认为3,可省略
# 第五个参数是L2Gradient,用于设定求梯度大小的方程。如果为True,则用开根号的那个,否则用绝对值的那个,默认为False,可省略
result = cv2.Canny(gray, minVal, maxVal)
else:
result = gray
cv2.destroyAllWindows()
效果如下: 这里初始设置的阈值分别是1000,200。还可以拖动进度条改变阈值的大小观察不同效果:
本文作者原创,未经许可不得转载,谢谢配合