OpenCV学习笔记[3]——图像处理(一)

图像处理

改变颜色空间

  • **cvtColor(input_image, flag):**将图像从一种颜色空间转换为另一种颜色空间。flag决定转换的类型(这函数在之前有演示过)

对于BGR→灰度转换,我们使用标志cv.COLOR_BGR2GRAY。类似地,对于BGR→HSV,我们使用标志cv.COLOR_BGR2HSV。要获取其他标记,只需在Python终端中运行以下命令:

import cv2 as cv
flags = [i for i in dir(cv) if i.startswith('COLOR_')]
print( flags )

知识补充:

  • HSV:HSV色彩空间是一种根据颜色的直观特性创建的一种颜色空间,也称为六角锥体模型。它由三个分量组成:色调(H),饱和度(S)和明度(V)。其优点是它更接近人们对颜色的感知和描述,可以单独调节颜色的色调、饱和度和明度,而不影响其他分量。HSV色彩空间常用于颜色识别、跟踪、分割等图像处理任务

对象追踪

当我们知道了如何将BGR图像转换成HSV,我们可以使用它来提取一个有颜色的对象。在HSV中比在BGR颜色空间中更容易表示颜色。在我们的应用程序中,我们将尝试提取一个蓝色的对象。方法如下: - 取视频的每一帧 - 转换从BGR到HSV颜色空间 - 我们对HSV图像设置蓝色范围的阈值 - 现在单独提取蓝色对象,我们可以对图像做任何我们想做的事情。

# 示例代码
import cv2 as cv
import numpy as np
cap = cv.VideoCapture(0) # 获取设备的默认视频流(传参0表示默认视频流)
while(1):
    # 读取帧
    _, frame = cap.read() # 读取一帧视频,返回一个布尔值和一个图像矩阵,用 _ 接收布尔值
    # 转换颜色空间 BGR 到 HSV
    hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
    # 定义HSV中蓝色的范围
    lower_blue = np.array([110,50,50])
    upper_blue = np.array([130,255,255])
    # 设置HSV的阈值使得只取蓝色
    mask = cv.inRange(hsv, lower_blue, upper_blue) # 获取掩膜(模型为白)
    # 将掩膜和图像逐像素相加
    res = cv.bitwise_and(frame,frame, mask= mask)
    cv.imshow('frame',frame)
    cv.imshow('mask',mask)
    cv.imshow('res',res)
    k = cv.waitKey(5) & 0xFF
    if k == 27: # 当按下Esc时退出
        break
cv.destroyAllWindows()

当我们想找一个 BGR 在 HSV 空间上对应的值,也可以通过 cv.cvtColer(BGR值, cv.COLOR_BGR2HSV)

# 示例代码
>>> green = np.uint8([[[0,255,0 ]]]) # 必须转换成numpy的数组
>>> hsv_green = cv.cvtColor(green,cv.COLOR_BGR2HSV)
>>> print( hsv_green )

图像几何变换

变换

知识补充:

  • 二维仿射变换:是一种在二维平面内对对象进行平移、旋转、缩放等变换的行为。二维仿射变换被广泛应用于图像校正、三维重建和目标识别等领域。在仿射变换中,原始图像中的所有平行线在输出图像中仍将平行。
  • 透视变换:是一种几何变换,它改变了图像中对象的形状和大小,使得图像看起来像是从不同的视角观察的。透视变换被广泛应用于图像校正、三维重建和目标识别等领域。

函数:

  • cv.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) 此函数用于进行二维仿射变换,也就是可以在图像上应用旋转、缩放、剪切等操作。

    • src:输入图像。
    • M:是一个2x3的变换矩阵。这个矩阵定义了图像变换的方式。例如,如果M是一个旋转矩阵,那么图像就会旋转。
    • dsize: 输出图像的大小。如果这个参数为None,那么输出图像的大小将与输入图像相同。
  • cv.warpPerspective(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) 此函数用于执行透视变换,

    • src: 输入图像。
    • M: 一个3x3的变换矩阵,定义了透视变换的性质。
    • dsize: 输出图像的大小。如果这个参数为None,那么输出图像的大小将与输入图像相同。

缩放

缩放即调整图像的大小。

知识补充:

  • 插值方法

函数:

  • cv.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]])
    • src: 输入图像。
    • dsize: 输出图像的大小,例如:(1080, 1920)
    • fxfy: 缩放因子。如果指定了这两个参数,那么函数会根据这两个参数来调整图像的大小,而不是根据dsize参数。fx表示水平方向的缩放因子,fy表示垂直方向的缩放因子。如果只指定了一个缩放因子,那么另一个缩放因子将根据宽高比自动计算。
    • interpolation: 插值方法。这个参数决定了当图像放大或缩小时,如何计算新的像素值。常见的插值方法包括双线性插值(INTER_LINEAR)、最近邻插值(INTER_NEAREST)、双三次插值(INTER_CUBIC) 、像素区域相关插值,适用于缩小图像(INTER_AREA) 、Lanczos插值,适用于图像放大(INTER_LANCZOS4)。
# 示例代码
import numpy as np
import cv2 as cv
img = cv.imread('1.jpg',-1)
resize_img = cv.resize(img, (img.shape[1]//2,img.shape[0]//2), interpolation=cv.INTER_AREA) # 这里直接传入原图的一半宽/高信息
cv.imshow("img", resize_img)
cv.waitKey(0)

平移

若是确定了图像在(x, y)方向上的位移,则可以设位移(a, b),创建转换矩阵
M = [ 1 0 a 0 1 b ] M=\left[\begin{matrix} 1 & 0 & a\\ 0 & 1 & b \end{matrix}\right] M=[1001ab]
然后放入 np.float32 类型的Numpy数组中,并传递给 cv.warpAffine 函数。

# 示例代码
import numpy as np
import cv2 as cv
img = cv.imread('1.jpg')
rows,cols = img.shape
M = np.float32([[1,0,100],[0,1,50]])
dst = cv.warpAffine(img,M,(cols,rows))
cv.imshow('img',dst)
cv.waitKey(0)
cv.destroyAllWindows()

旋转

函数:

  • cv.getRotationMatrix2D(img_center, angle, scale) 基于目标图像中心点、旋转角度、缩放因子,生成适用于OpenCV函数的变换矩阵
  • img_center:这是图像旋转的中心点,所有旋转都是围绕这个点进行的。
  • angle:这是你要旋转的角度。需要注意的是,这个角度是逆时针方向的。
  • scale:这是旋转后图像的缩放因子。如果为1,那么旋转后图像的大小不会改变。
  • cv.warpAffine() 实现图像的变换,此函数在变换篇详细说明了,在此不多赘述

知识补充:

确定了旋转角度 θ 后,创建变换矩阵
M = [ c o s θ − s i n θ s i n θ c o s θ ] M=\left[\begin{matrix} cosθ & -sinθ\\ sinθ & cosθ \end{matrix}\right] M=[cosθsinθsinθcosθ]
但是OpenCV提供了可缩放的旋转以及可调整的旋转中心,因此可以在自己喜欢的任何位置旋转。修改后的变换矩阵为。

[ α β ( 1 − α ) ⋅ c e n t e r . x − β ⋅ c e n t e r . y − β α β ⋅ c e n t e r . x + ⋅ ( 1 − α ) ⋅ c e n t e r . y ] \left[\begin{matrix} α & β & (1-α)·center.x-β·center.y\\ -β & α & β·center.x+·(1-α)·center.y \end{matrix}\right] [αββα(1α)center.xβcenter.yβcenter.x+(1α)center.y]
其中:
α = s c a l e ⋅ c o s θ β = s c a l e ⋅ s i n θ α = scale · cosθ\\ β = scale · sinθ α=scalecosθβ=scalesinθ
方便的是,OpenCV提供了 getRotationMatrix2D 函数来获取变换矩阵

# 示例代码
import numpy as np
import cv2 as cv
img = cv.imread('1.jpg',0)
rows,cols = img.shape
# cols-1 和 rows-1 是由于坐标从0开始,img.shape取出的列和行是从1开始的
M = cv.getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),90,1) # 将图像相对于中心旋转90度而没有任何缩放比例。
dst = cv.warpAffine(img,M,(cols,rows))
cv.imshow('img',dst)
cv.waitKey(0)
cv.destroyAllWindows()

仿射变换

在仿射变换中,原始图像中的所有平行线在输出图像中仍将平行。为了找到变换矩阵,我们需要输入图像中的三个点及其在输出图像中的对应位置。

函数:

  • cv.getAffineTransform(srcPoints, dstPoints, flags=None) 基于原图像的三原点与目标图像的三原点获取一个仿射变换矩阵,实现图像的
    • srcPoints:源点,是一个包含N个点(例如,(x,y)坐标对)的列表或NumPy数组。这些点在源图像中定义了一个多边形。
    • dstPoints:目标点,是一个包含N个点(例如,(x,y)坐标对)的列表或NumPy数组。这些点在目标图像中对应于srcPoints定义的多边形的顶点。
    • flags:返回的变换矩阵类型,是一个可选参数。如果提供,必须是cv2.WARP_INVERSE_MAPcv2.WARP_AUTO_INVMAP中的一个。如果设置为cv2.WARP_INVERSE_MAP,则函数返回的变换矩阵是逆映射变换矩阵(逆矩阵)。如果设置为cv2.WARP_AUTO_INVMAP,则函数根据srcPointsdstPoints的数量和类型自动选择返回逆映射还是直接映射。默认情况下,它返回直接映射变换矩阵。
# 示例代码
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt # 
img = cv.imread('1.jpg',-1)
rows,cols,ch = img.shape
pts1 = np.float32([[50,50],[200,50],[50,200]]) # 创建原三坐标数组
pts2 = np.float32([[10,100],[200,50],[100,250]]) # 创建目标三坐标数组
M = cv.getAffineTransform(pts1,pts2) # 获取转换矩阵
dst = cv.warpAffine(img,M,(cols,rows)) # 进行图像变换
cv.imshow('img', img)
cv.imshow('dst', dst)
cv.waitKey(0)

图像阈值

知识补充:

  • 阈值:阈值是指一个效应能够产生的最低值或最高值,在图像处理领域,阈值通常用于将像素值转换为黑白,即进行二值化操作。

简单阈值

在这里,问题直截了当。对于每个像素,应用相同的阈值。如果像素值小于阈值,则将其设置为0,否则将其设置为最大值。(具体效果会根据下面 threshold 函数的 type 传参变化)

函数:

  • cv.threshold(src, thresh[, maxval[, type[, dst]]] ) 对图像进行二值化操作,此函数返回两个输出。第一个是使用的阈值,第二个输出是阈值后的图像。
    • src: 输入图像,必须是8位或32位单通道图像。
    • thresh: 阈值,用于分类像素值。
    • maxval: 最大值,如果给定,则像素值大于阈值的像素将设置为该值。否则,它们将被设置为255。
    • type: 阈值类型,指示如何对像素进行分类。以下是一些常见的阈值类型:
      • cv.THRESH_BINARY:这是默认值,将像素值大于阈值的像素设置为maxval,否则设置为0。
      • cv.THRESH_BINARY_INV:与THRESH_BINARY相反,将像素值大于阈值的像素设置为0,否则设置为maxval。
      • cv.THRESH_TRUNC:将像素值大于阈值的像素设置为maxval,否则设置为阈值。
      • cv.THRESH_TOZERO:与THRESH_TRUNC相反,将像素值大于阈值的像素设置为0,否则设置为阈值。
      • cv.THRESH_TOZERO_INV:与THRESH_TOZERO相反,将像素值大于阈值的像素设置为阈值,否则设置为0。
    • dst: 输出图像,不选的话即将输出图像作为函数返回值返回
# 示例代码
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('1.jpg',-1)
ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY) # 获得 取阈值后图像
ret,thresh2 = cv.threshold(img,127,255,cv.THRESH_BINARY_INV) # 获得 取阈值后图像
ret,thresh3 = cv.threshold(img,127,255,cv.THRESH_TRUNC) # 获得 取阈值后图像
ret,thresh4 = cv.threshold(img,127,255,cv.THRESH_TOZERO) # 获得 取阈值后图像
ret,thresh5 = cv.threshold(img,127,255,cv.THRESH_TOZERO_INV) # 获得 取阈值后图像
titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
for i in range(6):
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])
plt.show()

自适应阈值

简单阈值使用一个全局值作为阈值。但这可能并非在所有情况下都很好,例如,如果图像在不同区域具有不同的光照条件。在这种情况下,自适应阈值阈值化可以提供帮助。在此,算法基于像素周围的小区域确定像素的阈值。因此,对于同一图像的不同区域,我们获得了不同的阈值,这为光照度变化的图像提供了更好的结果。

函数:

  • cv.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C, dst)
    • src: 输入图像,必须是8位或32位单通道图像。
    • maxValue:超过阈值的像素值将被设置为的最大值。
    • adaptiveMethod: 决定阈值计算方式
      • cv.ADAPTIVE_THRESH_MEAN_C:阈值是邻域区域的平均值减去常数C。
      • cv.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域区域的高斯加权和减去常数C。
    • thresholdType: 阈值类型,指示如何对像素进行分类。以下是一些常见的阈值类型:
      • cv.THRESH_BINARY:这是默认值,将像素值大于阈值的像素设置为maxval,否则设置为0。
      • cv.THRESH_BINARY_INV:与THRESH_BINARY相反,将像素值大于阈值的像素设置为0,否则设置为maxval。
      • cv.THRESH_TRUNC:将像素值大于阈值的像素设置为maxval,否则设置为阈值。
      • cv.THRESH_TOZERO:与THRESH_TRUNC相反,将像素值大于阈值的像素设置为0,否则设置为阈值。
      • cv.THRESH_TOZERO_INV:与THRESH_TOZERO相反,将像素值大于阈值的像素设置为阈值,否则设置为0。
    • blockSize: 用于计算阈值的区域大小
    • C: 从计算出的阈值或平均值中减去的常数
# 示例代码
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

cap = cv.VideoCapture(0)

while(1):
  _, img = cap.read()
  img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
  rst = cv.adaptiveThreshold(img, 255/2, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 0)
  cv.imshow("img", rst)
  key = cv.waitKey(5)
  if(key == 27): # 当按下ESC时退出循环
    break
cv.destroyAllWindows()

Otsu的二值化

知识补充:

  • Otsu:大津法(OTSU)是一种确定图像二值化分割阈值的算法,Otsu是一种常用的阈值分割算法,它可以根据图像自动生成最佳分割阈值。该方法又称作最大类间方差法,因为按照大津法求得的阈值进行图像二值化分割后,前景与背景图像的类间方差最大。此算法计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用。它是按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。缺点是对图像噪声敏感,只能针对单一目标分割;当目标和背景大小比例悬殊、类间方差函数可能呈现双峰或者多峰,这个时候效果不好。

在全局阈值化中,我们使用任意选择的值作为阈值。相反,Otsu的方法避免了必须选择一个值并自动确定它的情况。

考虑仅具有两个不同图像值的图像(双峰图像),其中直方图将仅包含两个峰。一个好的阈值应该在这两个值的中间。类似地,Otsu的方法从图像直方图中确定最佳全局阈值。

函数:

  • cv.threshold() 前面有介绍,请使用Ctrl+F自行定位看介绍,使用此函数将cv.THRESH_OTSU标志位作为传参,再附上其他阈值化的标志。如:cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU),将二值化作为阈值化的方式,OTSU作为获取阈值的算法依据,此时cv.threshold函数传参的阈值已无意义
# 示例代码 1
# 输入图像为噪点图像。在第一种情况下,采用值为127的全局阈值。在第二种情况下,直接采用Otsu阈值法。在第三种情况下,首先使用5x5高斯核对图像进行滤波以去除噪声,然后应用Otsu阈值处理。了解噪声滤波如何改善结果。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('noisy.jpg',0) # 找个噪点越多的图,三个图像区别越明显
# 全局阈值
ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
# Otsu阈值
ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# 高斯滤波后再采用Otsu阈值
blur = cv.GaussianBlur(img,(5,5),0)
ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# 绘制所有图像及其直方图
images = [img, 0, th1,
          img, 0, th2,
          blur, 0, th3]
titles = ['Original Noisy Image','Histogram','Global Thresholding (v=127)',
          'Original Noisy Image','Histogram',"Otsu's Thresholding",
          'Gaussian filtered Image','Histogram',"Otsu's Thresholding"]
for i in range(3):
    plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],'gray')
    plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
    plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256)
    plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
    plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],'gray')
    plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])
plt.show()
# 示例代码 2
# 输入图像来自默认摄像头。在第一种情况下,采用值为127的全局阈值。在第二种情况下,直接采用Otsu阈值法。在第三种情况下,首先使用5x5高斯核对图像进行滤波以去除噪声,然后应用Otsu阈值处理。了解噪声滤波如何改善结果。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
cap = cv.VideoCapture(0)
while(True):
  plt.close()
  _, img = cap.read()
  cv.imshow("origin", img)
  img = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # 将摄像头采集的图像转灰度
  cv.imshow("origin2gray", img)
  # 全局阈值
  ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
  cv.imshow("gray2globel", th1)
  # Otsu阈值
  ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
  cv.imshow("gray2Otsu", th2)
  # 高斯滤波后再采用Otsu阈值
  blur = cv.GaussianBlur(img,(5,5),0)
  ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
  cv.imshow("gray2OtsuWithGaus", th2)
  # 绘制所有图像及其直方图
  key = cv.waitKey(5)
  if(key == 27):
    break
cv.destroyAllWindows()

图像平滑

2D卷积(图像过滤)

知识补充:

  • 图像卷积:图像卷积是一种数学运算,它可以用来处理图像数据。它的基本思想是将一个图像分割成许多小块,然后对每个小块进行运算,最后将所有小块的结果合并起来。这样可以用来提取图像中的特征,例如边缘、角点、纹理等。

与一维信号一样,图像也可以使用各种低通滤波器(LPF),高通滤波器(HPF)等对图像进行滤波。LPF有助于消除噪声,使图像模糊等。HPF滤波器有助于在图像中找到边缘。

函数:

  • cv.filter2D(src, ddepth, kernel, dst, anchor, delta, borderType):用于执行二维卷积操作。
    • src:原图像
    • ddepth:输出图像的深度
    • kernel:卷积核

例如,我们将尝试对图像进行平均滤波。5x5平均滤波器内核如下所示:
K = 1 25 [ 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 ] K=\frac{1}{25}\left[\begin{matrix} 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\\ \end{matrix}\right] K=251 1111111111111111111111111
操作如下:
保持这个内核在一个像素上,将以此像素为中心,所有低于这个内核的25个像素相加,取其平均值,然后用新的平均值替换中心像素。

# 示例代码
# 此代码展示了平均滤波的算法过程
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('opencv_logo.png')
kernel = np.ones((5,5),np.float32)/25 # 创建卷积核
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()

图像模糊(图像平滑)

通过将图像与低通滤波器内核进行卷积来实现图像模糊。这对于消除噪音很有用。它实际上从图像中消除了高频部分(例如噪声,边缘)。因此,在此操作中边缘有些模糊。(有一些模糊技术也可以不模糊边缘)。OpenCV主要提供四种类型的模糊技术。

  1. 平均滤波
  2. 高斯模糊
  3. 中位模糊
  4. 双边滤波
平均滤波

这是通过将图像与归一化框滤镜进行卷积来完成的。它仅获取内核区域下所有像素的平均值,并替换中心元素。

函数:

  • cv.blur(src, ksize [, dst [, anchor [, borderType]]]) 使用归一化的盒型滤波器对图像进行模糊处理
    • src:需要模糊的图像。
    • ksize:模糊核的大小,表示为一个元组。
    • dst:与src具有相同大小和类型的输出图像。
    • anchor:表示锚点的整数变量,默认值为Point(-1,-1),表示锚点位于核的中心。
    • borderType:表示要添加的边界类型,由诸如cv2.BORDER_CONSTANTcv2.BORDER_REFLECT等标志定义。
  • cv.boxFilter(src, ddepth, ksize [, dst [, anchor [, normalize [, borderType]]]]) 使用盒型滤波器对图像进行模糊处理
    • src:源图像。
    • ddepth:目标图像的深度。
    • ksize:平滑核的大小。
    • dst:与src具有相同大小和类型的输出图像。
    • anchor:表示锚点的整数变量,默认值为Point(-1,-1),表示锚点位于核的中心。
    • normalize:标志指定是否对核进行归一化,如果为false,则每个像素的值将是其邻域像素值的总和,而不是平均值。
    • borderType:表示要添加的边界类型,由诸如cv2.BORDER_CONSTANTcv2.BORDER_REFLECT等标志定义。

很明显,前者函数是指定使用归一化的盒型滤波器

使用上述两函数时,我们应该指定内核的宽度和高度。3x3归一化盒型滤波器如下所示:
K = 1 9 [ 1 1 1 1 1 1 1 1 1 ] K=\frac{1}{9}\left[\begin{matrix} 1 & 1 & 1\\ 1 & 1 & 1\\ 1 & 1 & 1\\ \end{matrix}\right] K=91 111111111
注意:如果不想使用标准化的盒型滤波器,请使用cv.boxFilter()。将参数normalize = False传递给函数。)

# 示例代码
# 以下代码将图片以归一化盒型滤波器进行平均滤波
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('opencv_logo.png')
blur = cv.blur(img,(5,5)) # 使用5x5归一化盒型滤波器滤波
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()
高斯模糊

知识补充:

  • 高斯分布:是正态分布的别名
  • 高斯核:服从正态分布的二维矩阵,由高斯核函数产生
  • 高斯标准差 sigmaX和sigmaY:sigma参数控制着高维空间中的内积伸缩变化的大小。具体来说,sigma越小,内积全为0,相互垂直;sigma越大,内积全为1,失去区别

在这种情况下,代替盒式滤波器,使用了高斯核。这是通过功能cv.GaussianBlur() 完成的。我们应指定内核的宽度和高度,该宽度和高度应为正数和奇数。我们还应指定X和Y方向的标准偏差,分别为sigmaX和sigmaY。如果仅指定sigmaX,则sigmaY与sigmaX将默认相同。如果两个都为零,则根据内核大小进行计算。高斯模糊对于从图像中去除高斯噪声非常有效。

如果需要,可以使用函数cv.getGaussianKernel() 创建高斯内核。

函数:

  • cv.getGaussianKernel(ksize, sigma, ktype=cv.CV_64F) 创建一维高斯内核,可以通过 返回值*返回值.T来计算出二维高斯核
    • ksize:滤波器的大小,应为正数和奇数。
    • sigma:高斯标准差。
    • ktype:滤波器系数的类型,可以是CV_32F或CV_64F。
  • cv.GaussianBlur(src, ksize, sigmaX, sigmaY=0, borderType=cv.BORDER_DEFAULT) 对图像进行高斯模糊处理,此函数会自动生成高斯内核,只需要传入内核大小以及标准差
    • src:输入图像,可以是任意通道数的图片,但深度应该为CV_8U, CV_16U, CV_16S, CV_32F或CV_64F。
    • ksize:高斯内核大小,ksize.width和ksize.height可以不同,但它们都必须为正数和奇数。
    • sigmaX和sigmaY:分别是高斯核在X和Y方向上的标准差。
    • borderType:像素外推方法。

噪点图获取链接

# 示例代码(取图建议找多噪点图,模糊效果明显)
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('noisy2.png')
blur = cv.GaussianBlur(img,(5,5), 0) # 使用5x5高斯核滤波
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()
中位滤波

在这里,函数cv.medianBlur() 提取内核区域下所有像素的中值,并将中心元素替换为该中值。这对于消除图像中的椒盐噪声非常有效。有趣的是,在上述过滤器中,中心元素是新计算的值,该值可以是图像中的像素值或新值。但是在中值模糊中,中心元素总是被图像中的某些像素值代替。有效降低噪音。其内核大小应为正奇数整数。

函数:

  • medianBlur(src, ksize, dst=None) 对图像进行中值滤波处理
    • src:输入图像
    • ksize:滤波核大小,必须是大于1的奇数
    • dst:输出图像,如果没指定则会以返回值的形式返回新图像
# 示例代码(取图建议找椒盐噪点图,模糊效果明显)
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('noisy3.jpg')
blur = cv.medianBlur(img,5) # 使用5×5中值滤波核进行图像滤波
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()                                                   
双边滤波

知识补充:

  • 双边滤波:双边滤波的特点是对于每个像素点,它不仅考虑了它的空间邻域,也考虑了它的值域(即像素值的相似度)。其原理是对于邻域内的每个像素点,它给予一个权重,这个权重由两个因素决定:一是空间距离,即像素点距离中心点的远近;二是值域差异,即像素点的颜色或灰度与中心点的差别。这样,距离中心点近且颜色或灰度相似的像素点会有较大的权重,而距离中心点远或颜色或灰度不同的像素点会有较小的权重。然后,用这些权重对邻域内的像素点进行加权平均,得到中心点的新像素值。这样,就可以在去除噪声的同时,保留边缘和细节。
  • sigmaColor:双边滤波函数的一个参数,它表示颜色空间的标准差,也就是像素值的相似度的度量。一般来说,如果滤波后图像的颜色变化比较平滑,可以选择较大的sigmaColor,例如100或150;如果滤波后图像的颜色变化比较剧烈,可以选择较小的sigmaColor,例如10或20。
  • sigmaSpace:双边滤波函数的一个参数,它表示坐标空间的标准差,也就是像素位置的相似度的度量。一般来说,如果滤波后图像的空间变化比较平滑,可以选择较大的sigmaSpace,例如50或75;如果滤波后图像的空间变化比较剧烈,可以选择较小的sigmaSpace,例如10或15。

函数:

  • cv.bilateralFilter(src, d, sigmaColor, sigmaSpace, dst, borderType) 此函数对图像进行双边滤波
  • src:输入图像
  • d:滤波器直径,即每个像素的邻域大小
  • sigmaColor:颜色空间的标准差
  • sigmaSpace:坐标空间的标准差
  • borderType:边界处理模式
  • dst:输出图像

cv.bilateralFilter() 在去除噪声的同时保持边缘清晰锐利非常有效。但是,与其他过滤器相比,该操作速度较慢。我们已经看到,高斯滤波器采用像素周围的邻域并找到其高斯加权平均值。高斯滤波器仅是空间的函数,也就是说,滤波时只会考虑附近的像素,而不考虑像素是否具有几乎相同的强度。由于它不考虑像素是否是边缘像素,所以它也模糊了边缘,这是我们不想做的。

双边滤波器在空间中也采用高斯滤波器,但是又有一个高斯滤波器,它是像素差的函数。空间的高斯函数确保仅考虑附近像素的模糊,而强度差的高斯函数确保仅考虑强度与中心像素相似的那些像素的模糊。由于边缘的像素强度变化较大,因此可以保留边缘。

# 示例代码
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('noisy2.png')
blur = cv.bilateralFilter(img,9,175,75)
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()

形态转换

理论

  • 形态变换是一些基于图像形状的简单操作,比如改变物体的大小位置方向连接性等。
  • 形态变换通常用于二值图像,也就是只有黑白两种颜色的图像,因为这样可以更清楚地看出物体的轮廓结构
  • 形态变换需要两个输入,一个是我们的原始图像,第二个是结构元素内核,它们是一个小的点集,用来定义操作的性质,比如操作的范围方向强度等。
  • 形态变换的两种基本操作是腐蚀膨胀,它们是对图像中亮色区域(前景)的缩小和扩大,可以用来去除噪声分离物体填补空洞等。
  • 形态变换的其他操作,如开运算、闭运算、梯度运算等,都是由腐蚀和膨胀的组合而成的,可以用来实现更复杂的效果,比如平滑边缘提取边界增强对比度等。

在计算机图像处理领域,形态变换是一种重要的技术,它可以对图像进行预处理、分析、特征提取、分割等操作,从而为后续的图像识别、图像理解、图像压缩等任务提供有用的信息。

侵蚀

被侵蚀图像是二值图像

侵蚀的基本思想就像土壤侵蚀一样,它侵蚀前景物体的边界(尽量使前景保持白色)。它是做什么的呢?内核滑动通过图像(在2D卷积中),原始图像中的一个像素(无论是1还是0)只有当内核下的所有像素都是1时才被认为是1,否则它就会被侵蚀(变成0)。

结果是,根据内核的大小,边界附近的所有像素都会被丢弃。因此,前景物体的厚度或大小减小,或只是图像中的白色区域减小。它有助于去除小的白色噪声,分离两个连接的对象等。

函数:

  • cv.erode(src, kernel, dst, anchor, iterations, borderType, borderValue):对图像进行腐蚀操作

    • src :输入图像

    • kernel:用于腐蚀的结构元素,它是一个小的点集,用来定义腐蚀操作的性质,可以是任意形状,常为正方向或圆形

      以下为 5x5 的矩形和圆形的kernel示例
      矩形 k e r n e l = [ 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 ]         圆形 k e r n e l = [ 0 0 1 0 0 0 1 1 1 0 1 1 1 1 1 0 1 1 1 0 0 0 1 0 0 ] 矩形kernel=\left[\begin{matrix} 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\\ \end{matrix}\right] \space \space \space \space \space \space \space \space 圆形kernel=\left[\begin{matrix} 0 & 0 & 1 & 0 & 0\\ 0 & 1 & 1 & 1 & 0\\ 1 & 1 & 1 & 1 & 1\\ 0 & 1 & 1 & 1 & 0\\ 0 & 0 & 1 & 0 & 0\\ \end{matrix}\right] 矩形kernel= 1111111111111111111111111         圆形kernel= 0010001110111110111000100

    • anchor:结构元素的锚点位置,用于指定kernel的中心位置,anchor 越靠近 kernel 的边缘,腐蚀效果越强烈。此传参默认为(-1,-1),表示结构元素的几何中心的中心点

    • iterations:腐蚀的迭代次数

    • borderType:边界模式

      • BORDER_CONSTANT:表示用常数值填充边界,常数值由borderValue参数给出。(默认传参)
      • BORDER_REPLICATE:表示用边界像素的值复制填充边界。
      • BORDER_REFLECT:表示用边界像素的对称像素值填充边界,如fedcba|abcdefgh|hgfedcb。
      • BORDER_REFLECT_101:表示用边界像素的对称像素值填充边界,但不包括边界像素本身,如gfedcb|abcdefgh|gfedcba。
      • BORDER_WRAP:表示用另一边的像素值填充边界,如cdefgh|abcdefgh|abcdefg。
      • BORDER_DEFAULT:表示用默认的外推法,相当于BORDER_REFLECT_101。
    • borderValue:可以用一个整数或一个元组表示,如0或(0,0,0)。当 borderType为BORDER_CONSTANT时,该参数才有效,表示用该值填充边界。默认值是-1,表示用最大值填充边界

# 示例代码
import cv2 as cv
import numpy as np
img = cv.imread('noisy1.jpg',0)
cv.imshow("img", img)
cv.waitKey(0)
kernel = np.ones((5,5),np.uint8) # 创建一个全为1的5x5矩形内核
img = cv.erode(img,kernel,iterations = 1) # 使用5x5矩形内核执行1次默认腐蚀
cv.imshow("img", img)
cv.waitKey(0)

膨胀

它与侵蚀正好相反。如果内核下的至少一个像素为“ 1”,则像素元素为“ 1”。因此,它会增加图像中的白色区域或增加前景对象的大小。通常,在消除噪音的目的下,执行腐蚀后会执行膨胀。因为腐蚀会消除白噪声,但也会缩小物体,所以我们会重新膨胀前景,尽量还原原图

函数:

  • cv.dilate(src, kernel, dst, anchor, iterations, borderType, borderValue) 即扩大图像中的亮色区域(前景),从而增强图像的强度和尺寸。
    • src:原图像
    • kernel:用于膨胀的结构元素内核,
    • dst:输出图像,可不填
    • anchor:内核的锚点位置
    • iterations:膨胀迭代次数
    • borderType:边界模式(具体传参参考 “腐蚀” 小节)
    • borderValue:可以用一个整数或一个元组表示,如0或(0,0,0)。当 borderType为BORDER_CONSTANT时,该参数才有效,表示用该值填充边界。默认值是-1,表示用最大值填充边界
# 示例代码
import cv2 as cv
import numpy as np
img = cv.imread('noisy1.jpg',0)
cv.imshow("img", img)
cv.waitKey(0)
kernel = np.ones((5,5),np.uint8) # 创建一个全为1的5x5矩形内核
img = cv.erode(img,kernel,iterations = 1) # 使用5x5矩形内核执行1次默认腐蚀
cv.imshow("img", img)
cv.waitKey(0)
img = cv.dilate(img,kernel,iterations = 1) # 使用5x5矩形内核执行1次默认膨胀
cv.imshow("img", img)
cv.waitKey(0)

开运算

开运算只是侵蚀然后扩张的另一个名称。如上文所述,它对于消除噪音很有用。

函数:

  • cv.morphologyEx(src, op, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None) 用于图像形态学处理的函数,它可以对图像进行不同类型的形态学变换,包括膨胀腐蚀开运算闭运算形态学梯度顶帽变换黑帽变换
    • src:原图像
    • op:形态学操作类型
      • cv2.MORPH_OPEN:开运算,先进行腐蚀操作,再进行膨胀操作。
      • cv2.MORPH_CLOSE:闭运算,先进行膨胀操作,再进行腐蚀操作。
      • cv2.MORPH_GRADIENT:形态学梯度,膨胀图像与腐蚀图像的差。
      • cv2.MORPH_TOPHAT:顶帽变换,原始图像与开运算结果的差。
      • cv2.MORPH_BLACKHAT:黑帽变换,闭运算结果与原始图像的差。
      • cv2.MORPH_HITMISS:击中-击不中变换,用于二值图像的模式匹配。
    • kernel:形态学操作内核
    • dst:输出图像
    • anchor:kernel 的锚点位置
    • iterations:操作迭代次数
    • borderType:边界模式
    • borderValue:边界值
# 示例代码
import cv2 as cv
import numpy as np
img = cv.imread('noisy1.jpg',0)
cv.imshow("img", img)
cv.waitKey(0)
kernel = np.ones((5,5),np.uint8) # 创建一个全为1的5x5矩形内核
img = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)
cv.imshow("img", img)
cv.waitKey(0)

闭运算

闭运算与开运算相反,先扩张然后再侵蚀。在关闭前景对象内部的小孔或对象上的小黑点时很有用。

代码参考 “开运算” 章节,修改 morphology 函数的 op 传参 为 cv.MORPH_CLOSE

形态学梯度

这是图像扩张和侵蚀之间的区别。

结果将看起来像对象的轮廓。

# 示例代码
import cv2 as cv
import numpy as np
img = cv.imread('noisy1.jpg',0)
cv.imshow("img", img) # 先看看原图
cv.waitKey(0)
kernel = np.ones((5,5),np.uint8) # 创建一个全为1的5x5矩形内核
img = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel) # 形态学梯度转换
cv.imshow("img", img)
cv.waitKey(0)

顶帽

它是输入图像和图像开运算之差。

# 示例代码
import cv2 as cv
import numpy as np
img = cv.imread('noisy1.jpg',0)
cv.imshow("img", img)
cv.waitKey(0)
kernel = np.ones((5,5),np.uint8) # 创建一个全为1的5x5矩形内核
img = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel) # 顶帽运算
cv.imshow("img", img)
cv.waitKey(0)

黑帽

这是输入图像和图像闭运算之差。

代码参考 “开运算” 章节,修改 morphology 函数的 op 传参 为 cv.MORPH_BLACKHAT

结构元素(卷积核)

在Numpy的帮助下,我们在前面的示例中手动创建了一个结构元素。它是矩形。但是在某些情况下,我们可能需要椭圆形/圆形的内核。因此,为此,OpenCV具有一个函数cv.getStructuringElement()。我屏幕只需传递内核的形状和大小,即可获得所需的内核。

函数:

  • cv.getStructuringElement(shape, ksize, anchor = Point(-1,-1) ) 返回一个结构元素(卷积核)
    • shape:形状参数
      • MORPH_RECT:表示矩形结构元素
      • MORPH_CROSS:表示十字形结构元素
      • MORPH_ELLIPSE:表示椭圆形结构元素
    • ksize:卷积核大小,是一个两元素元组
    • anchor:十字形的交叉点位置,只有十字形结构元素的形状取决于锚点的位置。其他形状使用此传参无效
# 示例代码
import cv2 as cv
import numpy as np
img = cv.imread('noisy1.jpg',0)
mykernel = cv.getStructuringElement(cv.MORPH_RECT, (7, 7))
print(mykernel)
mykernel = cv.getStructuringElement(cv.MORPH_CROSS, (7, 7), (3,4))
print(mykernel)
mykernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (7, 7), (1,1))
print(mykernel)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值