import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
一、图像平滑
1、2D卷积
我们可以对 2D 图像实施低通滤波(LPF),高通滤波(HPF)等。
LPF 帮助我们去除噪音,模糊图像。HPF 帮助我们找到图像的边缘。
OpenCV 提供的函数 cv.filter2D() 可以让我们对一幅图像进行卷积操作。
'''
下面我们将对一幅图像使用平均滤波器(kernel核中的参数和为1,所有参数值相同),将核放在图像的一个像素 A 上,求与核对应的图像上 25(5x5)个像素的和,在取平均数,用这个平均数替代像素 A 的值。
重复以上操作直到将图像的每一个像素值都更新一遍。
'''img = cv.imread('open_cv_logo.png')
kernel = np.ones((5,5),np.float32) / 25
print(kernel)
[[0.04 0.04 0.04 0.04 0.04][0.04 0.04 0.04 0.04 0.04][0.04 0.04 0.04 0.04 0.04][0.04 0.04 0.04 0.04 0.04][0.04 0.04 0.04 0.04 0.04]]
dst = cv.filter2D(img,-1,kernel)plt.subplot(121),plt.imshow(img),plt.title('Original'),plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title('Averaging'),plt.xticks([]), plt.yticks([])
plt.show()
img = cv.imread('./data/xiaoren.png')plt.figure(figsize=(10,5))kernel = np.array([[1,2,1],[0,-8,0],[1,2,1]]
)dst = cv.filter2D(img,-1,kernel)plt.subplot(121),plt.imshow(cv.cvtColor(img,cv.COLOR_BGR2RGB)),plt.title('Original'),plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(cv.cvtColor(dst,cv.COLOR_BGR2RGB)),plt.title('Define kernel'),plt.xticks([]), plt.yticks([])
plt.show()
2、图像模糊
使用低通滤波器可以达到图像模糊的目的。这对与去除噪音很有帮助。
其实就是去除图像中的高频成分(比如:噪音,边界)。所以边界也会被模糊一点。(当然,也有一些模糊技术不会模糊掉边界)。
OpenCV 提供了四种模糊技术。
'''
1、平均这是由一个归一化卷积框完成的。用卷积框覆盖区域所有像素的平均值来代替中心元素。
可以使用函数 cv2.blur() 和 cv2.boxFilter() 来完这个任务。
我们需要设定卷积框的宽和高。下面是一个 3x3 的归一化卷积框:
K = [[1,1,1],[1,1,1],[1,1,1]] / 9如果你不想使用归一化卷积框,你应该使用 cv2.boxFilter(),这时要传入参数 normalize=False。
'''
img = cv.imread('./data/xiaoren.png')
blur = cv.blur(img,ksize=(11,11))plt.subplot(121),plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB)),plt.title('Original'),plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(cv.cvtColor(blur, cv.COLOR_BGR2RGB)),plt.title('Blurred'),plt.xticks([]), plt.yticks([])
plt.show()
'''
2、高斯模糊把卷积核换成高斯核(简单来说,方框不变,将原来每个方框的值是相等的,现在里面的值是符合高斯分布的,方框中心的值最大,其余方框根据
距离中心元素的距离递减,构成一个高斯小山包。原来的求平均数现在变成求加权平均数,全就是方框里的值)。实现的函数是 cv2.GaussianBlur()。
我们需要指定高斯核的宽和高(必须是奇数)。以及高斯函数沿 X,Y 方向的标准差。如果我们只指定了 X 方向的的标准差,Y 方向也会取相同值。如果两个标准差都是 0,那么函数会根据核函数的大小自己计算。
高斯滤波可以有效的从图像中去除高斯噪音。
'''plt.figure(figsize=(20,10))img = cv.imread('./data/koala.png')
#0 是指根据窗口大小(5,5)来计算高斯函数标准差
blur = cv.GaussianBlur(img, ksize=(5,5), sigmaX=10.0, sigmaY=10.0)plt.subplot(121),plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB)),plt.title('Original'),plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(cv.cvtColor(blur, cv.COLOR_BGR2RGB)),plt.title('Blurred'),plt.xticks([]), plt.yticks([])
plt.show()
'''
3、中值模糊顾名思义就是用与卷积框对应像素的中值来替代中心像素的值。这个滤波器经常用来去除椒盐噪声。
(即将卷积域内的所有像素按照从小到大排序,然后获取中间值作为卷积的输出。)前面的滤波器都是用计算得到的一个新值来取代中心像素的值,而中值滤波是用中心像素周围(也可以使他本身)的值来取代他。
他能有效的去除噪声。卷积核的大小也应该是一个奇数。
'''
# 加载图像
img = cv.imread('./data/xiaoren.png')# 加噪声数据
noisy_img = np.random.normal(10, 10, (img.shape[0], img.shape[1], img.shape[2]))
noisy_img = np.clip(noisy_img, 0, 255).astype(np.uint8)
img = img + noisy_img# 转换为灰度图像
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)# 做一个中值过滤
dst = cv.medianBlur(img, ksize=5)# 可视化
plt.subplot(121)
plt.imshow(img, 'gray')
plt.title('Original')plt.subplot(122)
plt.imshow(dst, 'gray')
plt.title('medianBlur')
plt.show()
'''
4、双边滤波函数 cv2.bilateralFilter() 能在保持边界清晰的情况下有效的去除噪音。但是这种操作与其他滤波器相比会比较慢。
我们已经知道高斯滤波器是求中心点邻近区域像素的高斯加权平均值。
这种高斯滤波器只考虑像素之间的空间关系,而不会考虑像素值之间的关系(像素的相似度)。
所以这种方法不会考虑一个像素是否位于边界。因此边界也不会模糊掉,而这正不是我们想要。双边滤波在同时使用空间高斯权重和灰度值相似性高斯权重。空间高斯函数确保只有邻近区域的像素对中心点有影响,灰度值相似性高斯函数确保只有与中心像素灰度值相近的才会被用来做模糊运算。
所以这种方法会确保边界不会被模糊掉,因为边界处的灰度值变化比较大。
'''
# 双边滤波: 中间的纹理删除,保留边缘信息
# 加载图像
img = cv.imread('./data/xiaoren.png')# 做一个双边滤波
dst = cv.bilateralFilter(img, d=9, sigmaColor=75, sigmaSpace=75)# 可视化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('bilateralFilter')
plt.show()
二、形态学转换
主要包括腐蚀、扩张、打开、关闭等操作;主要操作是基于kernel核的操作
常见的核主要有:矩阵、十字架、椭圆结构的kernel
kernel1 = cv.getStructuringElement(cv.MORPH_RECT, ksize=(5,5))
print("矩形kernel:\n{}".format(kernel1))kernel2 = cv.getStructuringElement(cv.MORPH_CROSS, ksize=(5,5))
print("十字架kernel:\n{}".format(kernel2))kernel3 = cv.getStructuringElement(cv.MORPH_ELLIPSE, ksize=(5,5))
print("椭圆kernel:\n{}".format(kernel3))
矩形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:
[[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]]
椭圆kernel:
[[0 0 1 0 0][1 1 1 1 1][1 1 1 1 1][1 1 1 1 1][0 0 1 0 0]]
1、腐蚀
腐蚀的意思是将边缘的像素点进行一些去除的操作;
腐蚀的操作过程就是让kernel核在图像上进行滑动,当内核中的所有像素被视为1时,原始图像中对应位置的像素设置为1,否则设置为0.
- 其主要效果是:可以在图像中减少前景图像(白色区域)的厚度,有助于减少白色噪音,可以用于分离两个连接的对象
- 一般应用与只有黑白像素的灰度情况
'''第一种方式
'''
kernel = cv.getStructuringElement(cv.MORPH_ERODE, (5,5))
dst = cv.morphologyEx(img, cv.MORPH_ERODE, kernel)# 可视化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('erode')
plt.show()
'''第二种方式
'''
img = cv.imread('./data/j.png',0)# a. 定义一个核(全部设置为1表示对核中5*5区域的所有像素均进行考虑,设置为0表示不考虑)
kernel = np.ones((5,5),np.uint8)
# b. 腐蚀操作
dst = cv.erode(img,kernel,iterations=1,borderType=cv.BORDER_REFLECT)# 可视化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('erode')
plt.show()
2、扩张、膨胀
和腐蚀的操作相反,其功能是增加图像的白色区域的值
只要在kernel中所有像素中有可以视为1的像素值,那么就将原始图像中对应位置的像素值设置为1,否则设置为0。
通常情况下,在去除噪音后,可以通过扩张在恢复图像的目标区域信息。
'''第一种方式
'''
kernel = cv.getStructuringElement(cv.MORPH_DILATE, (5,5))
dst = cv.morphologyEx(img, cv.MORPH_DILATE, kernel)# 可视化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('dilate')
plt.show()
'''第二种方式
'''
img = cv.imread('./data/j.png',0)# a. 定义一个核(全部设置为1表示对核中5*5区域的所有像素均进行考虑,设置为0表示不考虑)
kernel = np.ones((5,5),np.uint8)
# b. 膨胀操作
dst = cv.dilate(img,kernel,iterations=1,borderType=cv.BORDER_REFLECT)# 可视化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('dilate')
plt.show()
3、Open
Open其实指的就是先做一次腐蚀,然后再做一次扩张操作,一般用于去除噪音数据。
# 加载图像
img = cv.imread('./data/j.png', 0)# 加载噪音数据(白色噪音)
rows, cols = img.shape
for i in range(100):x = np.random.randint(cols)y = np.random.randint(rows)img[y,x] = 255'''第一种方式
'''
# a. 定义一个核(全部设置为1表示对核中5*5区域的所有像素均进行考虑,设置为0表示不考虑)
kernel = np.ones((5,5),np.uint8)
# b. 先进行腐蚀操作
dst1 = cv.erode(img,kernel,iterations=1,borderType=cv.BORDER_REFLECT)
# c. 后进行膨胀操作
dst2 = cv.dilate(dst1,kernel,iterations=1,borderType=cv.BORDER_REFLECT)# 可视化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')plt.subplot(122)
plt.imshow(cv.cvtColor(dst2, cv.COLOR_BGR2RGB))
plt.title('open')
plt.show()
'''第二种方式
'''
# a. 定义一个核(全部设置为1表示对核中5*5区域的所有像素均进行考虑,设置为0表示不考虑)
kernel = np.ones((5,5), np.uint8)
# b. Open操作
dst = cv.morphologyEx(img, op=cv.MORPH_OPEN, kernel=kernel, iterations=1)# 可视化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('open')
plt.show()
4、Closing
Closing其实指的就是先做一次扩张,再做一次腐蚀
对前景图像中的如果包含黑色点,有去除的效果。
# 加载图像
img = cv.imread('./data/j.png', 0)# 加载噪音数据
rows, cols = img.shape
# 加白色点
for i in range(100):x = np.random.randint(cols)y = np.random.randint(rows)img[y,x] = 255# 加黑色点
for i in range(1000):x = np.random.randint(cols)y = np.random.randint(rows)img[y,x] = 0
'''第一种方式
'''# a. 定义一个核(全部设置为1表示对核中5*5区域的所有像素均进行考虑,设置为0表示不考虑)
kernel = np.ones((5,5), np.uint8)
# b. 再膨胀
dst = cv.dilate(img, kernel, iterations=1)# c. 先腐蚀
dst = cv.erode(dst, kernel, iterations=1)# 可视化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('open')
plt.show()
'''第二种方式
'''# a. 定义一个核(全部设置为1表示对核中5*5区域的所有像素均进行考虑,设置为0表示不考虑)
kernel = np.ones((5,5), np.uint8)
# b. Closing操作
dst = cv.morphologyEx(img, op=cv.MORPH_CLOSE, kernel=kernel, iterations=1)# c. 可视化
# 可视化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('open')
plt.show()
5、Morphological Gradient(形态梯度)
在膨胀和腐蚀图像之间获取一个差集,也就是Gradient=Dilate - Erode;
该操作的作用能够提取边缘特征信息。
img = cv.imread('./data/j.png', 0)# a. 定义一个核(全部设置为1表示对核中5*5区域的所有像素均进行考虑,设置为0表示不考虑)
kernel = np.ones((5,5), np.uint8)# b. 形态梯度(dilate - erode)
dst = cv.morphologyEx(img, op=cv.MORPH_GRADIENT, kernel=kernel, iterations=1)# 可视化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('Gradient')
plt.show()
6、Top Hat
在原始图像和Open操作的图像上做一个差集,也就是Top Hat=image - Open;
该操作的主要作用是可以提取一些非交叉点的信息。一般不用。
img = cv.imread('./data/j.png', 0)# a. 定义一个核(全部设置为1表示对核中9*9区域的所有像素均进行考虑,设置为0表示不考虑)
kernel = np.ones((9,9), np.uint8)# b1. Open(先腐蚀,再扩展)
dst1 = cv.morphologyEx(img, op=cv.MORPH_OPEN, kernel=kernel, iterations=1)
# b2. Top Hat(src - open)
dst2 = cv.morphologyEx(img, op=cv.MORPH_TOPHAT, kernel=kernel, iterations=1)# 可视化
plt.subplot(131)
plt.imshow(img, 'gray')
plt.title('img')plt.subplot(132)
plt.imshow(dst1, 'gray')
plt.title('Open')plt.subplot(133)
plt.imshow(dst2, 'gray')
plt.title('TopHat')
plt.show()
7、Black Hat
在Close操作的图像和原始图像上做一个差集,也就是Black Hat = Close -image;
该操作的主要作用是可以提取一些交叉点附件的位置特征信息。一般不用。
# a. 定义一个核(全部设置为1表示对核中9*9区域的所有像素均进行考虑,设置为0表示不考虑)
kernel = np.ones((9,9), np.uint8)# b1. Close(先膨胀,再腐蚀)
dst1 = cv.morphologyEx(img, op=cv.MORPH_CLOSE, kernel=kernel, iterations=1)
# b2. Black Hat(close - src)
dst2 = cv.morphologyEx(img, op=cv.MORPH_BLACKHAT, kernel=kernel, iterations=1)
# dst2 = dst1 - img# c. 可视化
plt.subplot(131)
plt.imshow(img, 'gray')
plt.title('img')plt.subplot(132)
plt.imshow(dst1, 'gray')
plt.title('Close')plt.subplot(133)
plt.imshow(dst2, 'gray')
plt.title('BlackHat')
plt.show()
三、图像梯度
除了前面介绍的普通滤波/卷积操作外,在图像空域上而言,还存在一些比较重要的特征信息,
比如:边缘(Edge)特征信息;
边缘信息指的就是像素值明显变化的区域,具有非常丰富的语义信息,常用于物体识别等领域
通过对图像梯度的操作,可以发现图像的边缘信息
在OpenCV中提供了三种类型的高通滤波器,常见处理方式:Sobel、Scharr以及Laplacian导数
1、Sobel
主要就是梯度和高斯平滑的结合,在求解梯度之前,首先进行一个高斯平滑的操作。
# 加载图像
img = cv.imread('./data/xiaoren.png', 0)# 画几条线条
rows, cols = img.shape
cv.line(img, pt1=(0,rows//3), pt2=(cols,rows//3), color=0, thickness=5)
cv.line(img, pt1=(0,2*rows//3), pt2=(cols,2*rows//3), color=0, thickness=5)
cv.line(img, pt1=(cols//3,0), pt2=(cols//3,rows), color=0, thickness=5)
cv.line(img, pt1=(2*cols//3,0), pt2=(2*cols//3,rows), color=0, thickness=5)
cv.line(img, pt1=(0,0), pt2=(cols,rows), color=0, thickness=1)
print("")
# x方向的的Sobel过滤,ksize:一般取值为3,5,7;
# 第二个参数:ddepth,给定输出的数据类型的取值范围,默认为unit8的,取值为[0,255],如果给定-1,表示输出的数据类型和输入一致。
sobelx = cv.Sobel(img, 6, dx=1, dy=0, ksize=5)
sobely = cv.Sobel(img, cv.CV_64F, dx=0, dy=1, ksize=5)sobelx2 = cv.Sobel(img, cv.CV_64F, dx=2, dy=0, ksize=5)
sobely2 = cv.Sobel(img, cv.CV_64F, dx=0, dy=2, ksize=5)sobel = cv.Sobel(img, cv.CV_64F, dx=1, dy=1, ksize=5)sobelx_y = cv.Sobel(sobelx, cv.CV_64F, dx=0, dy=1, ksize=5)
sobely_x = cv.Sobel(sobely, cv.CV_64F, dx=1, dy=0, ksize=5)# c. 可视化
plt.figure(figsize=(20,10))plt.subplot(241)
plt.imshow(img, 'gray')
plt.title('img')plt.subplot(242)
plt.imshow(sobelx, 'gray')
plt.title('sobelx')plt.subplot(243)
plt.imshow(sobely, 'gray')
plt.title('sobely')plt.subplot(244)
plt.imshow(sobelx2, 'gray')
plt.title('sobelx2')plt.subplot(245)
plt.imshow(sobely2, 'gray')
plt.title('sobely2')plt.subplot(246)
plt.imshow(sobel, 'gray')
plt.title('sobel')plt.subplot(247)
plt.imshow(sobelx_y, 'gray')
plt.title('sobelx_y')plt.subplot(248)
plt.imshow(sobely_x, 'gray')
plt.title('sobely_x')plt.show()
'''
自定义kernel,实现sobel
'''
kernel = np.asarray([[-1, -2, -1],[0, 0, 0],[1, 2, 1]
])
# 做一个卷积操作
# 第二个参数为:ddepth,一般为-1,表示不限制,默认值即可。
sobely = cv.filter2D(img, 6, kernel)
sobelx = cv.filter2D(img, 6, kernel.T)plt.figure(figsize=(10,5))
# 可视化
plt.subplot(131)
plt.imshow(img, 'gray')
plt.title('Original')plt.subplot(132)
plt.imshow(sobelx, 'gray')
plt.title('sobelx')plt.subplot(133)
plt.imshow(sobely, 'gray')
plt.title('sobely')
plt.show()
'''
sobelx
=[[-1, 0, 1],[-2, 0, 2],[-1, 0, 1]]
= [-1 0 1](水平梯度) X [1 2 1].T(高斯平滑)sobely
=[[-1, -2, -1],[0, 0, 0],[1, 2, 1]]
= [1 2 1](高斯平滑) X [-1 0 1].T(垂直梯度)'''
# kernel = np.asarray([
# [-1, -2, -1],
# [0, 0, 0],
# [1, 2, 1]
# ])
kernel1 = np.asarray([[1,2,1]])
kernel2 = np.asarray([[-1],[0],[1]])# 做一个卷积操作
# 第二个参数为:ddepth,一般为-1,表示不限制,默认值即可。
# sobelx = cv.filter2D(img, 6, kernel.T)
# 先高斯平滑
a = cv.filter2D(img, 6, kernel1.T)
# 再水平梯度
sobelx = cv.filter2D(a, 6, kernel2.T)# sobely = cv.filter2D(img, 6, kernel)
# 先高斯平滑
a = cv.filter2D(img, 6, kernel1)
# 再垂直梯度
sobely = cv.filter2D(a, 6, kernel2)plt.figure(figsize=(10,5))
# 可视化
plt.subplot(131)
plt.imshow(img, 'gray')
plt.title('Original')plt.subplot(132)
plt.imshow(sobelx, 'gray')
plt.title('sobelx')plt.subplot(133)
plt.imshow(sobely, 'gray')
plt.title('sobely')
plt.show()
2、Scharr
Scharr可以认为是一种特殊的Sobel方式, 实际上就是一种特殊的kernel
# 加载图像
img = cv.imread('./data/xiaoren.png', 0)# 画几条线条
rows, cols = img.shape
cv.line(img, pt1=(0,rows//3), pt2=(cols,rows//3), color=0, thickness=5)
cv.line(img, pt1=(0,2*rows//3), pt2=(cols,2*rows//3), color=0, thickness=5)
cv.line(img, pt1=(cols//3,0), pt2=(cols//3,rows), color=0, thickness=5)
cv.line(img, pt1=(2*cols//3,0), pt2=(2*cols//3,rows), color=0, thickness=5)
print("")# Scharr中,dx和dy必须有一个为0,一个为1
scharr_x = cv.Scharr(img, cv.CV_64F, dx = 1, dy = 0)
scharr_y = cv.Scharr(img, cv.CV_64F, dx = 0, dy = 1)scharr_x_y = cv.Scharr(scharr_x, cv.CV_64F, dx = 0, dy = 1)
scharr_y_x = cv.Scharr(scharr_y, cv.CV_64F, dx = 1, dy = 0)# c. 可视化
plt.figure(figsize=(20,10))plt.subplot(231)
plt.imshow(img, 'gray')
plt.title('img')plt.subplot(232)
plt.imshow(scharr_x, 'gray')
plt.title('scharr_x')plt.subplot(233)
plt.imshow(scharr_y, 'gray')
plt.title('scharr_y')plt.subplot(234)
plt.imshow(scharr_x_y, 'gray')
plt.title('scharr_x_y')plt.subplot(235)
plt.imshow(scharr_y_x, 'gray')
plt.title('scharr_y_x')plt.show()
3、Laplacian
使用拉普拉斯算子进行边缘提取
# 加载图像
img = cv.imread('./data/xiaoren.png', 0)# 画几条线条
rows, cols = img.shape
cv.line(img, pt1=(0,rows//3), pt2=(cols,rows//3), color=0, thickness=5)
cv.line(img, pt1=(0,2*rows//3), pt2=(cols,2*rows//3), color=0, thickness=5)
cv.line(img, pt1=(cols//3,0), pt2=(cols//3,rows), color=0, thickness=5)
cv.line(img, pt1=(2*cols//3,0), pt2=(2*cols//3,rows), color=0, thickness=5)
print("")# ksize设置为3
ksize = 3sobel_x = cv.Sobel(img, cv.CV_64F, dx=1, dy=0, ksize=ksize)
sobel_y = cv.Sobel(img, cv.CV_64F, dx=0, dy=1, ksize=ksize)laplacian = cv.Laplacian(img, cv.CV_64F, ksize=ksize)
# 对laplacian取绝对值,并且准换为uint8格式
laplacian_v2 = np.uint8(np.absolute(laplacian))scharr_x = cv.Scharr(img, cv.CV_64F, dx=1, dy=0)
scharr_y = cv.Scharr(img, cv.CV_64F, dx=0, dy=1)# c. 可视化
plt.figure(figsize=(20,10))plt.subplot(241)
plt.imshow(img, 'gray')
plt.title('img')plt.subplot(242)
plt.imshow(sobel_x, 'gray')
plt.title('sobel_x')plt.subplot(243)
plt.imshow(sobel_y, 'gray')
plt.title('sobel_y')plt.subplot(244)
plt.imshow(laplacian, 'gray')
plt.title('laplacian')plt.subplot(245)
plt.imshow(laplacian_v2, 'gray')
plt.title('laplacian_v2')plt.subplot(246)
plt.imshow(scharr_x, 'gray')
plt.title('scharr_x')plt.subplot(247)
plt.imshow(scharr_y, 'gray')
plt.title('scharr_y')
plt.show()
'''在Sobel检测中,depth对于结果的影响,当输出的depth设置为比较低的数据格式,那么当梯度值计算为负值的时候,就会将其重置为0,从而导致失真。
在Laplacian检测中,该问题不大。'''
# 构建一个图像
# 构建黑底白框的图像
img = np.zeros((300,300), np.uint8)
img[100:200,100:200] = 255
# 构建白底黑框的图像
# img = np.ones((300,300), np.uint8) * 255
# img[100:200,100:200] = 0ksize = 5# 做Sobel的操作
dst1 = cv.Sobel(img, cv.CV_8U, 1, 0, ksize=ksize)
dst2 = cv.Sobel(img, cv.CV_64F, 1, 0, ksize=ksize)
dst3 = np.uint8(np.absolute(dst2))# 做Laplacian的操作
# dst1 = cv.Laplacian(img, cv.CV_8U, ksize=ksize)
# dst2 = cv.Laplacian(img, cv.CV_64F, ksize=ksize)
# dst3 = np.uint8(np.absolute(dst2))# c. 可视化
plt.figure(figsize=(10,5))plt.subplot(221)
plt.imshow(img, 'gray')
plt.title('img')plt.subplot(222)
plt.imshow(dst1, 'gray')
plt.title('dst1')plt.subplot(223)
plt.imshow(dst2, 'gray')
plt.title('dst2')plt.subplot(224)
plt.imshow(dst3, 'gray')
plt.title('dst3')plt.show()
4、Canday算法
Canny算法是一种比Sobel和Laplacian效果更好的一种边缘检测算法;在Canny算法中,主要包括以下几个阶段:
- Noise Reduction:降噪,使用5*5的kernel做Gaussian filter降噪;
- Finding Intensity Gradient of the Image:求图像像素的梯度值;
- Non-maximum Suppression:删除可能不构成边缘的像素,即在渐变方向上相邻区域的像素梯度值是否是最大值,如果不是,则进行删除。
- Hysteresis Thresholding:基于阈值来判断是否属于边;大于maxval的一定属于边,小于minval的一定不属于边,在这个中间的可能属于边的边缘。
# 加载图像
img = cv.imread('./data/xiaoren.png', 0)# 画几条线条
rows, cols = img.shape
cv.line(img, pt1=(0,rows//3), pt2=(cols,rows//3), color=0, thickness=5)
cv.line(img, pt1=(0,2*rows//3), pt2=(cols,2*rows//3), color=0, thickness=5)
cv.line(img, pt1=(cols//3,0), pt2=(cols//3,rows), color=0, thickness=5)
cv.line(img, pt1=(2*cols//3,0), pt2=(2*cols//3,rows), color=0, thickness=5)
cv.line(img, pt1=(0,0), pt2=(cols,rows), color=0, thickness=1)
print("")# 做一个Canny边缘检测(OpenCV中是不包含高斯去燥的)
# a. 高斯去燥
blur = cv.GaussianBlur(img, (5,5),0)
# b. Canny边缘检测
edges = cv.Canny(blur,threshold1=10, threshold2=250)# 可视化
plt.figure(figsize=(20,10))
plt.subplot(131)
plt.imshow(img,cmap = 'gray')
plt.title('Original Image')plt.subplot(132)
plt.imshow(blur,cmap = 'gray')
plt.title('Gaussian Blur Image')plt.subplot(133)
plt.imshow(edges,cmap = 'gray')
plt.title('Canny Edge Image')
plt.show()
5、轮廓信息
- 轮廓信息可以简单的理解为图像曲线的连接点信息,在目标检测以及识别中有一定的作用。
- 轮廓信息的查找最好是基于灰度图像或者边缘特征图像,因为基于这样的图像比较容易找连接点信息;
NOTE:在OpenCV中,查找轮廓是在黑色背景中查找白色图像的轮廓信息。
# 加载图像
img = cv.imread('./data/xiaoren.png')# 将图像转换为灰度图像
img1 = cv.cvtColor(img, cv.COLOR_BGR2GRAY)# 做一个图像反转(0 -> 255, 255 -> 0)
img1 = cv.bitwise_not(img1)# 做一个二值化
ret, thresh = cv.threshold(img1, 127, 255, cv.THRESH_BINARY)# 发现轮廓信息
# 第一个参数是原始图像,第二个参数是轮廓的检索模型,第三个参数是轮廓的近似方法
# 第一个返回值是轮廓,第二个参数值为层次信息
# CHAIN_APPROX_SIMPLE指的是对于一条直线上的点而言,仅仅保留端点信息,而CHAIN_APPROX_NONE保留所有点
# contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
contours, hierarchy = cv.findContours(thresh, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
print("总的轮廓数目:{}".format(len(contours)))
print('hierarchy.shape = ', hierarchy.shape)# 在图像中绘制图像
# 当contourIdx为-1的时候,表示绘制所有轮廓,当为大于等于0的时候,表示仅仅绘制某一个轮廓
# 这里的返回值img3和img是同一个对象,在当前版本中
# img3 = cv.drawContours(img, contours, contourIdx=-1, color=(0, 0, 255), thickness=2)
max_idx = np.argmax([len(t) for t in contours])
# print(max_idx)
img3 = cv.drawContours(img, contours, contourIdx=max_idx, color=(0, 0, 255), thickness=2)# 可视化
plt.figure(figsize=(10,5))
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original Image')plt.subplot(122)
plt.imshow(thresh,cmap = 'gray')
plt.title('thresh')plt.show()
总的轮廓数目:283
hierarchy.shape = (1, 283, 4)
6、轮廓信息说明
# 构建黑底白框的图像
img = np.zeros((300,300), np.uint8)
img[10:290,10:290] = 255
img[50:200, 50:200] = 0
img[55:100, 55:100] = 255
img[120:190, 120:150] = 255
img[130:160, 130:145] = 0
img[210:250, 210:250] = 0
img[250:270, 150:180] = 0
img[205:220, 205:220] = 0# 可视化
plt.imshow(img, 'gray')
plt.title('img')plt.show()
ret, thresh = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
img3 = cv.drawContours(img, contours, contourIdx=-1, color=128, thickness=2)print("总的轮廓数目:{}".format(len(contours)))
print('hierarchy.shape = ', hierarchy.shape)
hierarchy
# 是一个[1, n, 4]格式,n为轮廓的数目,这个中间保存的是轮廓包含信息# 每个轮廓的层次信息是一个4维的向量值,
# 第一个值表示当前轮廓的上一个同层级的轮廓下标,
# 第二值表示当前轮廓的下一个同层级的轮廓下标,
# 第三个表示当前轮廓的第一个子轮廓的下标,
# 第四个就表示当前轮廓的父轮廓的下标
总的轮廓数目:7
hierarchy.shape = (1, 7, 4)array([[[-1, -1, 1, -1],[ 2, -1, -1, 0],[ 3, 1, -1, 0],[-1, 2, 4, 0],[ 6, -1, 5, 3],[-1, -1, -1, 4],[-1, 4, -1, 3]]], dtype=int32)
7、轮廓属性
获取得到轮廓坐标后,就可以基于轮廓来计算面积、周长等属性。
# 加载图像
img = cv.imread('./data/xiaoren.png')# 将图像转换为灰度图像
img1 = cv.cvtColor(img, cv.COLOR_BGR2GRAY)# 做一个图像反转(0 -> 255, 255 -> 0)
img1 = cv.bitwise_not(img1)# 做一个二值化
ret, thresh = cv.threshold(img1, 127, 255, cv.THRESH_BINARY)# 发现轮廓信息
# 第一个参数是原始图像,第二个参数是轮廓的检索模型,第三个参数是轮廓的近似方法
# 第一个返回值是轮廓,第二个参数值为层次信息
# CHAIN_APPROX_SIMPLE指的是对于一条直线上的点而言,仅仅保留端点信息,而CHAIN_APPROX_NONE保留所有点
# contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
contours, hierarchy = cv.findContours(thresh, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)idx = np.argmax([len(t) for t in contours])
cnt = contours[idx]
print(cnt.shape)
print(cnt[:2,:,:])
# 绘制轮廓
cv.drawContours(img, contours, contourIdx=idx, color=(0, 0, 255), thickness=2)# 计算面积
area = cv.contourArea(cnt)
# 计算周长
perimeter = cv.arcLength(cnt, closed=True)
print("面积为:{}, 周长为:{}".format(area, perimeter))'''
1、获取最大的矩形边缘框, 返回值为矩形框的左上角的坐标以及宽度和高度
'''
x,y,w,h = cv.boundingRect(cnt)
# 绘图
cv.rectangle(img, pt1=(x,y), pt2=(x+w,y+h), color=(255,0,0), thickness=2)'''
2、绘制最小矩形(所有边缘在矩形内)、得到矩形的点(左下角、左上角、右上角、右下角<顺序不一定>)、绘图
'''
# minAreaRect:求得一个包含点集cnt的最小面积的矩形,这个矩形可以有一点的旋转偏转的,输出为矩形的四个坐标点
# rect为三元组,
# 第一个元素为旋转中心点的坐标
# 第二个元素为矩形的高度和宽度
# 第三个元素为旋转大小,正数表示顺时针选择,负数表示逆时针旋转
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int64(box)
cv.drawContours(img, [box], 0, (0, 255, 0), 2)'''
3、绘制最小的圆(所有边缘在圆内)
'''
(x,y), radius = cv.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
cv.circle(img, center, radius, (0, 0, 255), 5)'''
4、绘制最小的椭圆(所有边缘不一定均在圆内)
'''
ellipse = cv.fitEllipse(cnt)
cv.ellipse(img, ellipse, (0, 255, 0), 5)# 可视化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original Image')plt.subplot(122)
plt.imshow(thresh,cmap = 'gray')
plt.title('thresh')plt.show()
(1050, 1, 2)
[[[306 220]][[306 229]]]
面积为:65960.5, 周长为:2487.3636897802353
'''
5、旋转后绘制最小椭圆
'''
# 加载图像
img = cv.imread('./data/xiaoren.png')# 旋转rect = [(302, 420), (317.06085205078125, 372.9076232910156), 18]
M = cv.getRotationMatrix2D(center=rect[0], angle=rect[-1], scale=1)
img = cv.warpAffine(img, M, (cols, rows), borderValue=[255,255,255])# 将图像转换为灰度图像
img1 = cv.cvtColor(img, cv.COLOR_BGR2GRAY)# 做一个图像反转(0 -> 255, 255 -> 0)
img1 = cv.bitwise_not(img1)# 做一个二值化
ret, thresh = cv.threshold(img1, 127, 255, cv.THRESH_BINARY)
# 发现轮廓信息
contours, hierarchy = cv.findContours(thresh, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)idx = np.argmax([len(t) for t in contours])
cnt = contours[idx]# 绘制轮廓
cv.drawContours(img, contours, contourIdx=idx, color=(0, 0, 255), thickness=2)# 绘制最小矩形(所有边缘在矩形内)、得到矩形的点(左下角、左上角、右上角、右下角<顺序不一定>)、绘图
# minAreaRect:求得一个包含点集cnt的最小面积的矩形,这个矩形可以有一点的旋转偏转的,输出为矩形的四个坐标点
# rect为三元组,第一个元素为旋转中心点的坐标
# rect为三元组,第二个元素为矩形的高度和宽度
# rect为三元组,第三个元素为旋转大小,正数表示顺时针选择,负数表示逆时针旋转
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int64(box)
cv.drawContours(img, [box], 0, (0, 255, 0), 2)
print(rect)# 可视化
plt.figure(figsize=(10,5))
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original Image')plt.subplot(122)
plt.imshow(thresh,cmap = 'gray')
plt.title('thresh')plt.show()
((300.05328369140625, 390.378662109375), (290.5243225097656, 397.2838134765625), 88.93909454345703)
8、直方图
'''
OpenCV中的直方图的主要功能是可以查看图像的像素信息以及提取直方图中各个区间的像素值的数目作为当前图像的特征属性进行机器学习模型
'''# 加载图像
img = cv.imread('./data/koala.png', 0)# 两种方式基本结果基本类似
# 1、基于OpenCV的API计算直方图
hist1 = cv.calcHist([img], channels=[0], mask=None, histSize=[256], ranges=[0,256])# 2、基于NumPy计算直方图
hist2, bins = np.histogram(img.ravel(), 256, [0, 256])# 和np.histogram一样的计算方式,但是效率快
hist3 = np.bincount(img.ravel(), minlength=256)# 可视化
plt.figure(figsize=(20,10))
plt.subplot(231)
plt.imshow(img, 'gray')
plt.title('Original Image')plt.subplot(232)
# 可以直接使用matpliab中的hist API直接画直方图
plt.plot(hist1)
plt.title('hist1')plt.subplot(233)
plt.plot(hist2)
plt.title('hist2')plt.subplot(234)
plt.plot(hist3)
plt.title('hist3')# 可以直接使用matpliab中的hist API直接画直方图
plt.subplot(235)
plt.hist(img.ravel(), 256, [0, 256])
plt.title('plt.hist')plt.show()