本文主要参考自 OpenCV官方文档
一、色彩空间
- HSV
① 色调(H
),取值范围:[0, 179]
② 饱和度(S
),取值范围:[0, 255]
③ 明亮度(V
),取值范围:[0, 255]
- 色彩空间转换
效果图如下import cv2 import numpy as np img = cv2.imread("../../Resources/cctv2.jpg") # 转换为HSV色彩空间 hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 利用cv2.inRange()设置阈值,去除背景 lower = np.array([0, 0, 0]) upper = np.array([40, 255, 255]) mask = cv2.inRange(hsv, lower, upper) res = cv2.bitwise_and(img, img, mask=mask) cv2.imshow('frame', img) cv2.imshow('mask', mask) cv2.imshow('res', res) cv2.waitKey(0)
二、图像阈值
-
简单阈值
简单阈值类型:
cv2.THRESH_BINARY
(阈值二值化):小于等于阈值的像素点置 0,大于阈值的像素点置 255cv2.THRESH_BINARY_INV
(阈值反二值化):小于等于阈值的像素点置 255,大于阈值的像素点置 0cv2.THRESH_TRUNC
(截断):小于等于阈值的像素点保持原色,大于阈值的像素点置灰(与阈值threshold一致)cv2.THRESH_TOZERO
(阈值取零):小于等于阈值的像素点置 0,大于阈值的像素点保持原色cv2.THRESH_TOZERO_INV
(阈值反取零):小于等于阈值的像素点保持原色,大于阈值的像素点置 0import cv2 import numpy as np from matplotlib import pyplot as plt img = np.uint8(np.arange(400 * 400).reshape([400, 400]) / (400 * 400) * 255) # 阈值二值化(threshold binary) # 小于等于阈值的像素点置0,大于阈值的像素点置255. ret1, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # cv2.imshow("",thresh1) # 阈值反二值化(threshold binary Inverted) # 小于等于阈值的像素点置255,大于阈值的像素点置0. ret2, thresh2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) # cv2.imshow("",thresh2) # 截断(truncate) # 小于等于阈值的像素点保持原色,大于阈值的像素点置灰(与阈值threshold一致). ret3, thresh3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC) # cv2.imshow("",thresh3) # 阈值取零(threshold to zero) # 小于等于阈值的像素点置0,大于阈值的像素点保持原色。 ret4, thresh4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO) # cv2.imshow("",thresh4) # 阈值反取零(threshold to zero inverted) # 小于等于阈值的像素点保持原色,大于阈值的像素点置0。 ret5, thresh5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV) # cv2.imshow("",thresh5) titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV'] images = [img, thresh1, thresh2, thresh3, thresh4, thresh5] for i in range(len(images)): # 划分2行*3列的区域,在第(i+1)个区域上画图 plt.subplot(2, 3, i + 1) # 将图片显示在画板上 # cmap颜色映射: 当img形状是三维矩阵(M,N,3)或(M,N,4)时,值被解释为RGB或RGBA,此时cmap将被忽略。 # 当img形状是二维矩阵(M,N)时,此时cmap用于值到颜色的一个映射。 plt.imshow(images[i], "gray") # 添加标题 plt.title(titles[i]) # 清空x轴、y轴刻度 plt.xticks([]), plt.yticks([]) plt.show()
效果图如下
-
自适应阈值
cv2.adaptiveThreshold 包含三个输入参数:
其中 adaptiveMethod 决定阈值是如何计算:
cv2.ADAPTIVE_THRESH_MEAN_C
:阈值是邻近区域的平均值减去常数 Ccv2.ADAPTIVE_THRESH_GAUSSIAN_C
:阈值是邻域值的高斯加权总和减去常数 Cimport cv2 from matplotlib import pyplot as plt img = cv2.imread('../../Resources/paper.jpg', 0) # cv2.imshow("", img) ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_OTSU) # blockSize = 9 表示图片中分块大小,C = 3 阈值方法中的常数项 th2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 3) th3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 9, 3) titles = ['Original Image', 'Global Thresholding (v = 127)', 'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding'] images = [img, th1, th2, th3] for i in range(4): plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray') plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.imshow(th3, "gray") plt.show() cv2.waitKey(0)
效果图如下
-
OTSU(最大类间方差法)二值化
在全局阈值化中,我们使用任意选择的值作为阈值。相反,OTSU的方法避免了必须选择一个值并自动确定它的情况。
import cv2 import matplotlib.pyplot as plt "# OTSU二值化" # 将彩色图转化为灰度图 img = cv2.imread("../../Resources/beautiful_girl.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 将灰度图转化为二值化图(0,255),otsu是自动求取阈值 ret1, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) ret2, binary_inv = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) ret3, truncate = cv2.threshold(gray, 0, 255, cv2.THRESH_TRUNC | cv2.THRESH_OTSU) ret4, to_zero = cv2.threshold(gray, 0, 255, cv2.THRESH_TOZERO | cv2.THRESH_OTSU) ret5, to_zero_inv = cv2.threshold(gray, 0, 255, cv2.THRESH_TOZERO_INV | cv2.THRESH_OTSU) # print(ret1, ret2, ret3, ret4, ret5) # 结合matplotlib,展示多张二值化图 titles = ['Gray Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV'] images = [gray, binary, binary_inv, truncate, to_zero, to_zero_inv] for i in range(6): plt.subplot(2, 3, i + 1) plt.imshow(images[i], cmap="gray") plt.title(titles[i]) plt.xticks([]) plt.yticks([]) plt.show()
效果图如下
想要具体了解 Otsu的二值化如何实现 的朋友可以参阅官方文档
三、图像几何变换
-
图像缩放
图像的大小可以手动指定,也可以指定缩放比例,也可使用不同的插值方法
几种插值方式
① 双线性插值(默认):cv2.INTER_LINEAR
② 最邻近插值:
cv2.INTER_NEAREST
③ 基于4x4像素邻域内的三次样条插值:
cv2.INTER_CUBIC
④ 基于8x8像素邻域内的Lanczos插值:
cv2.INTER_LANCZOS4
⑤ 基于局部像素的重采样:
cv2.INTER_AREA
首选的插值方法是
cv.INTER_AREA
用于缩小,cv.INTER_CUBIC
(慢)和cv.INTER_LINEAR
用于缩放几种常用方法的效率是:最邻近插值 > 双线性插值 > 双立方插值 > Lanczos插值;
但是效率和效果成反比,所以根据自己的情况酌情使用。import cv2 # import time img = cv2.imread("../../Resources/messi15.jpg") rows, cols, channels = img.shape # time_start = time.time() # 开始时间 # 图片缩放: resize() resize_1 = cv2.resize(img, dsize=(cols * 2, rows * 2)) # 按尺寸 resize_2 = cv2.resize(img, dsize=(0, 0), fx=2, fy=2) # 按因子 # 几种插值方式: # 1.双线性插值(默认) linear = cv2.resize(img, dsize=(cols * 2, rows * 2), interpolation=cv2.INTER_LINEAR) # 2.最邻近插值 nearest = cv2.resize(img, dsize=(cols * 2, rows * 2), interpolation=cv2.INTER_NEAREST) # 3.基于4x4像素邻域内的三次样条插值 cubic = cv2.resize(img, dsize=(cols * 2, rows * 2), interpolation=cv2.INTER_CUBIC) # 4.基于8x8像素邻域内的Lanczos插值 lanczos = cv2.resize(img, dsize=(cols * 2, rows * 2), interpolation=cv2.INTER_LANCZOS4) # 5.- 基于局部像素的重采样 area = cv2.resize(img, dsize=(cols * 2, rows * 2), interpolation=cv2.INTER_AREA) cv2.imshow("image", img) cv2.imshow("resize_1", resize_1) cv2.imshow("resize_2", resize_2) cv2.imshow("linear", linear) cv2.imshow("nearest", nearest) cv2.imshow("cubic", cubic) cv2.imshow("lanczos", lanczos) cv2.imshow("area", area) # time_end = time.time() # print(time_end - time_start) cv2.waitKey(0) cv2.destroyAllWindows()
-
图像翻转
通过 cv2.flip() 来实现翻转效果,其中参数 flipCode 用来控制翻转样式。flipCode = 0
:绕x轴翻转,即上下颠倒
flipcode = 1
:绕y轴翻转,左右翻转
flipcode = -1
:绕x,y轴同时翻转,即上下+左右翻转import cv2 from OpenCv_Review.Part_1.Chapter6_Trackbar_Operation.section1_stack import stack_images img = cv2.imread('../../Resources/Maserati.jpg') dst1 = cv2.flip(img, flipCode=0) dst2 = cv2.flip(img, flipCode=1) dst3 = cv2.flip(img, flipCode=-1) flip_stack = stack_images(0.6, ([img, dst1], [dst2, dst3])) cv2.imshow("Flip_Stack", flip_stack) cv2.waitKey(0) cv2.destroyAllWindows()
实现的效果图如下
-
图像旋转
图像旋转角度为 θ θ θ 是通过以下形式的变换矩阵实现的:
[ c o s θ − sin θ sin θ cos θ ] \begin{bmatrix} \ cos \theta & - \sin\theta \\ \sin\theta & \cos\theta \end{bmatrix} [ cosθsinθ−sinθcosθ]
但是OpenCV提供了可缩放的旋转以及可调整的旋转中心,因此您可以在自己喜欢的任何位置旋转。修改后的变换矩阵为
[ α β ( 1 − α ) ⋅ center.x − β ⋅ center.y − β α β ⋅ center.x + ( 1 − α ) ⋅ center.y ] \begin{bmatrix} \alpha & \beta & (1- \alpha ) \cdot \texttt{center.x} - \beta \cdot \texttt{center.y} \\ - \beta & \alpha & \beta \cdot \texttt{center.x} + (1- \alpha ) \cdot \texttt{center.y} \end{bmatrix} [α−ββα(1−α)⋅center.x−β⋅center.yβ⋅center.x+(1−α)⋅center.y]
其中:
α = scale ⋅ cos angle , β = scale ⋅ sin angle \begin{array}{l} \alpha = \texttt{scale} \cdot \cos \texttt{angle} , \\ \beta = \texttt{scale} \cdot \sin \texttt{angle} \end{array} α=scale⋅cosangle,β=scale⋅sinangle为了找到此转换矩阵,OpenCV提供了一个函数 cv.getRotationMatrix2D
下列代码采用提供的函数和根据公式的自定义函数实现
import cv2 import numpy import matplotlib.pyplot as plt from math import * def rotate_pic(scale, angle): angle = (angle / 180) * pi alpha = scale * cos(angle) beta = scale * sin(angle) rotate_arr = numpy.float32([[alpha, beta, (1 - alpha) * center_x - beta * center_y], [-beta, alpha, beta * center_x + (1 - alpha) * center_y]]) return rotate_arr img = cv2.imread("../../Resources/Maserati.jpg") width, height, channels = img.shape center_x = width // 2 center_y = height // 2 M = cv2.getRotationMatrix2D((center_x, center_y), 45, scale=0.8) # 采用函数实现 m = rotate_pic(0.8, 45) # 按照公式自定义实现 dst = cv2.warpAffine(img, M, (width, height)) res = cv2.warpAffine(img, m, (width, height)) titles = ['Image', 'getRotationMatrix2D', 'rotate_pic'] images = [img, dst, res] for i in range(3): plt.subplot(1, 3, i + 1) plt.imshow(images[i]) plt.title(titles[i]) plt.xticks([]) plt.yticks([]) plt.show()
-
图像仿射变换
仿射变换是把一个二维坐标系转换到另一个二维坐标系的过程,转换过程坐标点的相对位置和属性不发生变换,是一个线性变换,该过程只发生旋转和平移过程。因此,一个平行四边形经过仿射变换后还是一个平行四边形。
所以,仿射= 旋转 + 平移。(该段原理解释来自博客园博主 Baby-Lily)import cv2 import numpy import matplotlib.pyplot as plt img = cv2.imread("../../Resources/opencv-logo.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) rows, cols, channels = img.shape # 1.创建仿射变换矩阵 # 0)原图,不进行变换 M0 = numpy.float32([[1, 0, 0], [0, 1, 0]]) # 1)平移 M1 = numpy.float32([[1, 0, 20], [0, 1, 80]]) # 沿x轴平移+20,沿y轴平移+80 # 2)缩放 M2 = numpy.float32([[0.8, 0, 0], [0, 0.5, 0]]) # x轴变为0.8倍,y轴变为0.5倍 # 3)倾斜 M3 = numpy.float32([[1, 0.5, 0], [0, 1, 0]]) # 沿x轴倾斜0.5倍 M4 = numpy.float32([[1, 0, 0], [0.5, 1, 0]]) # 沿y轴倾斜0.5倍 # 4)翻转/镜像 M5 = numpy.float32([[-1, 0, cols], [0, 1, 0]]) # 绕y转翻转,沿x轴平移cols个像素单位 M6 = numpy.float32([[1, 0, 0], [0, -1, rows]]) # 绕x转翻转,沿y轴平移rows个像素单位 M7 = numpy.float32([[-1, 0, cols], [0, -1, rows]]) # 绕y转翻转、绕x转翻转,最后沿x轴平移cols个像素单位、沿y轴平移rows个像素单位 # 2.进行仿射变换 # M: 仿射变换矩阵 dst0 = cv2.warpAffine(img, M0, dsize=(cols, rows)) # 原图,无平移 dst1 = cv2.warpAffine(img, M1, dsize=(cols, rows)) # 平移 dst2 = cv2.warpAffine(img, M2, dsize=(cols, rows)) # 缩放 dst3 = cv2.warpAffine(img, M3, dsize=(cols, rows)) # 倾斜 dst4 = cv2.warpAffine(img, M4, dsize=(cols, rows)) # 倾斜 dst5 = cv2.warpAffine(img, M5, dsize=(cols, rows)) # 翻转/镜像 dst6 = cv2.warpAffine(img, M6, dsize=(cols, rows)) # 翻转/镜像 dst7 = cv2.warpAffine(img, M7, dsize=(cols, rows)) # 翻转/镜像 titles = ['Image', 'Translation', 'Scaling', 'Tilt_1', 'Tilt_2', 'Flip_1', 'Flip_2', 'Flip_3'] images = [img, dst0, dst1, dst2, dst3, dst4, dst5, dst6, dst7] for i in range(8): plt.subplot(2, 4, i + 1) plt.imshow(images[i]) plt.title(titles[i]) plt.xticks([]) plt.yticks([]) plt.show()
实现的效果图如下所示
-
图像的透视变换
透视变换是把一个图像投影到一个新的视平面的过程,该过程包括:把一个二维坐标系转换为三维坐标系,然后把三维坐标系投影到新的二维坐标系。该过程是一个非线性变换过程,因此,一个平行四边形经过透视变换后只得到四边形,但不平行。(该段原理解释来自博客园博主 Baby-Lily)
import numpy as np import matplotlib.pyplot as plt img = cv2.imread("../../Resources/cards.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) width, height = 250, 350 # 1.获取透视变换矩阵M: getPerspectiveTransform(src, dst) pts1 = np.float32([[111, 219], [287, 188], [154, 482], [352, 440]]) pts2 = np.float32([[0, 0], [width, 0], [0, height], [width, height]]) # 2.进行透视变换 matrix = cv2.getPerspectiveTransform(pts1, pts2) imgOutput = cv2.warpPerspective(img, matrix, (width, height)) titles = ['Image', 'Image_Output'] images = [img, imgOutput] for i in range(2): plt.subplot(1, 2, i + 1) plt.imshow(images[i]) plt.title(titles[i]) plt.xticks([]) plt.yticks([]) plt.show()
实现的效果图如下
四、图像过滤、平滑(模糊)处理
图像的时域(分析每个时间点、空间位置的一个点)和频域(分析两个点、线之间的变化:梯度)
滤波是将信号中特定波段频率滤除的操作,是抑制和防止干扰的一项重要措施。
图像滤波是图像预处理中不可缺少的操作,其处理效果的好坏将直接影响到后续图像处理和分析的有效性和可靠性。图像滤波,即在尽量保留图像细节特征的条件下对目标图像的噪声进行抑制。图像滤波的目的:
1.消除图像中混入的噪声;
2.为图像识别抽取出图像特征。
滤波可分为 低通滤波、高通滤波、中通滤波、阻带滤波。都是从频域上区别的。
低通滤波/平滑滤波:减弱或阻隔高频信号,保留低频信号,只留下变化较小的信号。可使图像变模糊,主要用于去噪。
高通滤波:减弱或阻隔低频信号,保留高频信号,只留下变化较大的信号。一般用于获取图像边缘、轮廓或梯度。
中通滤波:获取已知频率范围内的信号,去掉变化较大和较小的信号,留下变化适中的信号。
阻带滤波:去掉已知频率范围内的信号,去掉变化适中的信号,留下变化较大和较小的信号。
-
图像过滤
操作如下:保持这个内核在一个像素上,将所有低于这个内核的 5*5 个像素相加,取其平均值,然后用新的平均值替换中心像素。它将对图像中的所有像素继续此操作。
import cv2 import numpy as np import matplotlib.pyplot as plt '卷积滤波' img = cv2.imread("../../Resources/opencv-logo.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 1.定义一个卷积核 kernel = np.ones((5, 5), np.float32) / 25 # 2.卷积操作 dst = cv2.filter2D(img, -1, kernel) titles = ['Image', 'Filter_Image'] images = [img, dst] for i in range(2): plt.subplot(1, 2, i + 1) plt.title(titles[i]) plt.imshow(images[i]) plt.xticks([]) plt.yticks([]) plt.show()
实现效果如下
-
图像模糊
通过将图像与低通滤波器内核进行卷积来实现图像模糊,这对于消除噪音很有用。
它实际上从图像中消除了高频部分(例如噪声,边缘)。因此,在此操作中边缘有些模糊。OpenCV主要提供四种类型的模糊技术(具体介绍还可查看博主 TechArtisan6 的博文 ps:点击名字即可进入相应博文):
①
cv2.blur
(均值滤波):是指任意一点的像素值,都是周围 N × M N × M N×M 个像素值的均值。②
cv2.medianBlur
(中值滤波):中值滤波是非线性的图像处理方法,在去噪的同时可以兼顾到边界信息的保留。选一个含有奇数点的窗口 W W W,将这个窗口在图像上扫描,把窗口中所含的像素点按灰度级的升或降序排列,取位于中间的灰度值来代替该点的灰度值。③
cv2.GaussianBlur
(高斯滤波):图像高斯平滑也是邻域平均的思想对图像进行平滑的一种方法,在图像高斯平滑中,对图像进行平均时,不同位置的像素被赋予了不同的权重。高斯平滑与简单平滑不同,它在对邻域内像素进行平均时,给予不同位置的像素不同的权值。高斯滤波让临近的像素具有更高的重要度,对周围像素计算加权平均值,较近的像素具有较大的权重值
。④
cv2.bilateralFilter
(双边滤波):双边滤波是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空间与信息和灰度相似性,达到保边去噪的目的,具有简单、非迭代、局部处理的特点。之所以能够达到保边去噪的滤波效果是因为滤波器由两个函数构成:一个函数是由几何空间距离决定滤波器系数,另一个是由像素差值决定滤波器系数
。(ps:可用于P图)import cv2 import matplotlib.pyplot as plt img = cv2.imread('../../Resources/blur_girl.jpg') img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 1.均值滤波,ksize表示内核大小为 3x3 mean_img = cv2.blur(img, ksize=(3, 3)) # 2.中值滤波,ksize 表示核大小。核必须是大于1的奇数,如3、5、7等 median_img = cv2.medianBlur(img, ksize=7) # 3.高斯滤波,核大小(N, N)必须是奇数,sigmaX 为 X方向方差,主要控制权重。若不指定sigmaY,则sigmaY=sigmaX。 gaussian_img = cv2.GaussianBlur(img, ksize=(7, 7), sigmaX=3, sigmaY=3) # 双边滤波,d:像素的邻域直径,sigmaColor:颜色空间的标准方差,sigmaSpace:坐标空间的标准方差(像素单位) bilateral_img = cv2.bilateralFilter(img, d=34, sigmaColor=68, sigmaSpace=68) titles = ['Image', 'Mean_Image', 'Median_Image', 'Gaussian_Image', 'Bilateral_Image'] images = [img, mean_img, median_img, gaussian_img, bilateral_img] for i in range(len(images)): plt.subplot(2, 3, i + 1) plt.imshow(images[i]) plt.title(titles[i]) plt.xticks([]) plt.yticks([]) plt.show()
实现的效果图如下
五、图像梯度
查找图像梯度,边缘等。OpenCV提供三种类型的梯度滤波器或高通滤波器,即 Sobel
,Scharr
和 Laplacian
-
锐化操作
# 1.自定义锐化核 kernel = numpy.float32([[0, -1, 0], [-1, 5, -1], [0, -1, 0]]) dst1 = cv2.filter2D(src, -1, kernel) # 2.USM锐化(UnsharpMask) gaussian = cv2.GaussianBlur(src, ksize=(17, 17), sigmaX=3, sigmaY=3) dst2 = cv2.addWeighted(src, 2, gaussian, -1, 0) # cv2.imshow("src", src) cv2.imshow("dst1", dst1) cv2.imshow("dst2", dst2) cv2.waitKey(0) cv2.destroyAllWindows()
效果图如下
-
梯度操作(高通滤波):找轮廓
①Sobel算子
是高斯平滑加微分运算的联合运算,因此它更抗噪声。你可以指定要采用的导数方向,垂直或水平(分别通过参数
yorder
和xorder
)。你还可以通过参数
ksize
指定内核的大小。如果ksize = -1
,则使用3x3
Scharr滤波器,比3x3
Sobel滤波器 具有更好的结果。请参阅文档以了解所使用的内核。② Laplacian 算子
它计算了由关系 dst = Δ src = ∂ 2 src ∂ x 2 + ∂ 2 src ∂ y 2 \texttt{dst} = \Delta \texttt{src} = \frac{\partial^2 \texttt{src}}{\partial x^2} + \frac{\partial^2 \texttt{src}}{\partial y^2} dst=Δsrc=∂x2∂2src+∂y2∂2src 给出的图像的拉普拉斯图,它是每一阶导数通过
Sobel算子
计算。如果ksize = 1
,然后使用以下内核用于过滤:k e r n e l = [ 0 1 0 1 − 4 1 0 1 0 ] kernel= \begin{bmatrix} \ 0 & 1 & 0 \\ \ 1 & -4 & 1 \\ \ 0 & 1 & 0 \end{bmatrix} kernel=⎣⎡ 0 1 01−41010⎦⎤
import cv2 import matplotlib.pyplot as plt # 梯度操作/高通滤波:找轮廓 gray = cv2.imread("../../Resources/paper2.jpg", cv2.IMREAD_GRAYSCALE) # 1.Sobel算子: dx和dy表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。 sobel_x = cv2.Sobel(gray, -1, dx=1, dy=0, ksize=3) # x轴方向上的一阶导数 sobel_y = cv2.Sobel(gray, -1, dx=0, dy=1, ksize=3) # y轴方向上的一阶导数 sobel_x_abs = cv2.convertScaleAbs(sobel_x, alpha=2, beta=1) sobel_y_abs = cv2.convertScaleAbs(sobel_y, alpha=2, beta=1) sobel = cv2.addWeighted(sobel_x_abs, 0.5, sobel_y_abs, 0.5, 0) # 近似有|G|=|Gx|+|Gy| # 2.Scharr算子: dx和dy表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。 scharr_x = cv2.Scharr(gray, -1, dx=1, dy=0) # x轴方向上的一阶导数 scharr_y = cv2.Scharr(gray, -1, dx=0, dy=1) # y轴方向上的一阶导数 scharr_x_abs = cv2.convertScaleAbs(scharr_x) scharr_y_abs = cv2.convertScaleAbs(scharr_y) scharr = cv2.addWeighted(scharr_x_abs, 0.5, scharr_y_abs, 0.5, 0) # 近似有|G|=|Gx|+|Gy| # 近似有|G|=|Gx|+|Gy| # 3.Laplacian算子 laplacian = cv2.Laplacian(gray, -1) # 结合matplotlib显示多张图片 titles = ['Original Gray', 'Sobel x', 'Sobel y', 'Sobel', 'Scharr x', 'Scharr y', 'Scharr', 'Laplacian'] images = [gray, sobel_x_abs, sobel_y_abs, sobel, scharr_x, scharr_y, scharr, laplacian] for i in range(8): plt.subplot(2, 4, i + 1) plt.imshow(images[i], cmap="gray") plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.show()
效果图如下
六、图像形态学操作
-
构造一个特定形状和大小的结构元素(核),用于形态学操作。
kernel = cv2.getStructuringElement(shape, ksize, anchor=None)
参数:
shape
: 核的形状。
MORPH_RECT = 0
:矩形
MORPH_CROSS = 1
:交叉形
MORPH_ELLIPSE = 2
:椭圆形
ksize: 核的结构大小 -
几种基本形态学操作
① 膨胀: 原图部分区域(A)与核(B)进行卷积,求局部最大值,并将局部最大值赋值给指定像素,从而增长高亮区域。
dilate(src, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None)
② 腐蚀: 与膨胀相反,用局部极小值替换当前像素,从而缩短高亮区域。
erode(src, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None)
③ 更多形态学操作
morphologyEx(src, op, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None)
参数:
op
:形态学操作类型。
cv2.MORPH_DILATE
:膨胀。–> 增长高亮部分。
cv2.MORPH_ERODE
:腐蚀。–> 缩短高亮部分。
cv2.MORPH_GRADIENT
:梯度,(膨胀 - 腐蚀)。–> 提取轮廓。
cv2.MORPH_OPEN
:开,先腐蚀在膨胀。–> 去除噪点。
cv2.MORPH_CLOSE
:闭,先膨胀再腐蚀。–> 填补漏洞。
cv2.MORPH_TOPHAT
:顶帽/礼帽,(原图 - 开)。–> 获取噪点。
cv2.MORPH_BLACKHAT
:黑帽,(闭 - 原图)。–> 获取漏洞。import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread('../../Resources/number8.jpg') rows, cols, channel = img.shape # 加噪声 for i in range(50): x = np.random.randint(0, rows) y = np.random.randint(0, cols) if x % 2 == 0 and y % 2 == 0: img[x, y, :] = 255 else: img[x, y, :] = 0 # 1.获取指定形状和大小的结构元素(核) kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(3, 3)) # 2.形态学操作:必须是二值化图,膨胀和腐蚀的部分是白颜色 dilate_img = cv2.dilate(img, kernel) # 膨胀 erode_img = cv2.erode(img, kernel) # 腐蚀 morph_dilate_img = cv2.morphologyEx(img, cv2.MORPH_DILATE, kernel) # 膨胀,增长高亮部分 morph_erode_img = cv2.morphologyEx(img, cv2.MORPH_ERODE, kernel) # 腐蚀,缩短高亮部分 morph_gradient_img = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel) # 梯度,提取轮廓 morph_open_img = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) # 开: 先腐蚀再膨胀,用于去噪 morph_close_img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) # 闭: 先膨胀后腐蚀,用于填补漏洞 morph_top_hat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel) # 顶帽/礼帽: 原图-开,用于获取噪点 morph_black_hat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel) # 黑帽: 闭-原图,用于获取漏洞 titles = ['Image', 'Dilate', 'Erode', 'Morph_Dilate', 'Morph_Erode', 'Morph_Gradient', 'Morph_Open', 'Morph_Close', 'Morph_Top_Hat', 'Morph_Black_Hat'] images = [img, dilate_img, erode_img, morph_dilate_img, morph_erode_img, morph_gradient_img, morph_open_img, morph_close_img, morph_top_hat, morph_black_hat] for i in range(len(images)): plt.subplot(4, 3, i + 1) plt.imshow(images[i], cmap='gray') plt.title(titles[i]) plt.xticks([]) plt.yticks([]) plt.show()
实现的效果图如下