OpenCV中的图像处理3

图像梯度

OpenCV提供三种类型的梯度滤波器或高通滤波器,即Sobel, Scharr和Laplacian。

1. Sobel和Scharr算子

Sobel算子是高斯平滑加微分运算的联合运算,因此它更抗噪声。可以指定要采用的导数方向,垂直或水平(yorder或xorder);可以通过参数ksize指定内核的大小,若ksize = -1,则使用3x3 Scharr滤波器,比3x3 Sobel滤波器有更好的结果。

2. Laplacian算子

计算了由关系 Δ s r c = ∂ 2 s r c ∂ x 2 + ∂ 2 s r c ∂ y 2 , \Delta src=\dfrac{\partial ^{2}src}{\partial x^{2}}+\dfrac{\partial ^{2}src}{\partial {y}^{2}} , Δsrc=x22src+y22src, 给出的图像的拉普拉斯图,它是每一阶导数通过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= 010141010 . 下面代码显示了单个图表中的所有算子,内核都是5x5大小,输出图像的深度通过-1得到结果的np.uint8型。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('./OpenCV/sudoku.png', 0)
laplacian = cv.Laplacian(img, cv.CV_64F)
sobelx = cv.Sobel(img, cv.CV_64F, 1, 0, ksize=5)
sobely = cv.Sobel(img, cv.CV_64F, 0, 1, ksize=5)
images = [img, laplacian, sobelx, sobely]
titles = ['Original', 'Laplacian', 'SobelX', 'SobelY']
for i in range(4):
    plt.subplot(2, 2, i + 1)
    plt.imshow(images[i], cmap='gray'), plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

输出类型为:cv.CV_8U(代码中替换)
在这里插入图片描述
输出类型为:cv.CV_64F
在这里插入图片描述

边缘信息

在示例中,若输出数据类型为cv.CV_8Unp.uint8;有一个小问题是,黑色到白色的过渡被视为正斜率(具有正值),而白色到黑色的过渡被视为负斜率(具有负值),因此当将数据转换成np.uint8时,所有负斜率均设为零,简言之,会错过一边缘信息。若要检测两个边缘,更好选择是将输出数据类型保留为cv.CV_16Scv.CV_64F等更高的形式,取其绝对值,然后转换回cv.CV_8U。在上面代码中修改补充下列代码,可演示用于水平Sobel滤波器和结果差异的过程。

img2 = cv.imread('./OpenCV/box.png', 0)
sobelx8u = cv.Sobel(img2, cv.CV_8U, 1, 0, ksize=5)
sobelx64f = cv.Sobel(img2, cv.CV_64F, 1, 0, ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel64to8u = np.uint8(abs_sobel64f)
images = [img, laplacian, sobelx, sobely, img2, sobelx8u, sobel64to8u]
titles = ['Original', 'Laplacian', 'SobelX', 'SobelY', 'Box', 'Sobel CV_8U', 'Sobel abs(CV_64F']
for i in range(7):
    plt.subplot(2, 4, i + 1)

在这里插入图片描述

Canny边缘检测

Canny Edge Detection是一种流行的边缘检测算法。
由于边缘检测容易受到图像中噪声的影响,因此第一步是使用5x5高斯滤波器消除图像中的噪声。

查找图像的强度梯度

使用Sobel核在水平和垂直方向上对平滑的图像进行滤波,以在水平方向(Gx)和垂直方向(Gy)上获得一阶导数。我们可以找到每个像素的边缘渐变和方向,如下所示:
E d g e _ G r a d i e n t    ( G ) = G x 2 + G y 2 A n g l e    ( θ ) = tan ⁡ − 1 ( G y G x ) Edge\_Gradient \; (G) = \sqrt{G_x^2 + G_y^2} \\ Angle \; (\theta) = \tan^{-1} \bigg(\frac{G_y}{G_x}\bigg) Edge_Gradient(G)=Gx2+Gy2 Angle(θ)=tan1(GxGy)
渐变方向始终垂直于边缘,将其舍入为代表垂直、水平和两个对角线方向的四个角度之一。

1. 非极大值抑制

在获得梯度大小和方向后,将对图像进行全面扫描,以去除可能不构成边缘的所有不需要的像素。因此在每个像素处,检查像素是否是其在梯度方向上附近的局部最大值。查看下面图片:
在这里插入图片描述
点A在边缘(垂直方向)上,渐变方向垂直于边缘;点B和C在梯度方向上,因此将A点与B点和C点进行检查,看是否形成局部最大值,如果是,则考虑将其用于下一阶段,否则将其抑制(置为零);简言之,得到的结果是带有“细边”的二进制图像。

2. 磁滞阈值

该阶段确定哪些边缘全部是真正的边缘,哪些不是;需要两个阈值minVal和maxVal,强度梯度大于maxVal的任何边缘必定是边缘,而小于minVal的那些边缘必定是非边缘,将其丢弃;介于两个阈值之间的对象根据其联通性被分类为边缘或非边缘;如果将它们连接到“边缘”像素,则将它们视为边缘的一部分,否则丢弃它们。查看下图:
在这里插入图片描述
边缘A在maxVal之上,因此被视为“确定边缘”;尽管边缘C低于maxVal,但其连接A,因此也被视为有效边缘;而边缘B尽管也是处于两个阈值之间,但它没有连接到任何“确保边缘”,所以被丢弃。因此,选择相应的minVal和maxVal对获取正确的结果非常重要。在边缘为长线的假设下,该阶段还消除了小像素噪声,最终得到的是图像中的强边缘。

OpenCV中的Canny Edge检测

OpenCV将上述内容放在函数cv.Canny()中。第一个参数是输入图像;第二和第三个参数分别为minVal和maxVal;第四个参数是perture_size,它用于查找图像渐变的Sobel内核的大小,默认为3;第五个参数是L2gradient,它指定用于查找梯度幅度的方程式,若为True,使用上面提到的更精确的公式,否则使用下面公式 E d g e _ G r a d i e n t    ( G ) = ∣ G ( x ) ∣ + ∣ G ( y ) ∣ Edge\_Gradient \; (G) =\left| G\left( x\right) \right| +\left| G\left( y\right) \right| Edge_Gradient(G)=G(x)+G(y),默认为False.

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('./OpenCV/box.png', 0)
edges = cv.Canny(img, 100, 200)
images = [img, edges]
titles = ['Original', 'Edge']
for i in range(2):
    plt.subplot(1, 2, i + 1)
    plt.imshow(images[i], cmap='gray')
    plt.title(titles[i])
    plt.xticks([], plt.yticks([]))
plt.show()

在这里插入图片描述

图像金字塔

通常使用的是恒定大小的图像,但在某些情况下,需使用不同分辨率的(相同)图像;如在图像中搜索某些东西(如人脸)时,不确定对象将以多大的尺寸显示在图像中;在此情况下,需创建一组具有不同分辨率的相同图像,并在所有图像中搜索图像;这些具有不同分辨率的图像集称为“图像金字塔”(因为当它们堆叠在底部时,最高分辨率的图像位于底部,最低分辨率的图像位于顶部时,看起来像金字塔)。
有高斯金字塔和拉普拉斯金字塔两种图像金字塔。

高斯金字塔

它其中的较高级别(低分辨率)是通过删除较低级别(较高分辨率)图像中的连续行和列而形成的;较高级别的每个像素由基础级别的5个像素的贡献与高斯权重形成;这样操作, M × N M \times N M×N图像变成 M / 2 × N / 2 M / 2 \times N / 2 M/2×N/2图像,因此面积减少为原始面积的四分之一,它称为Octave;当我们在金字塔中向上时(分辨率下降),这种模式会继续;同样,在扩展时,面积变为原来的4倍,我们可以使用cv.pyrDown()(向下采样,缩小图片)和cv.pyrUp()(向上采样,扩大照片)函数找到高斯金字塔。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('./OpenCV/fusi1.jpg')
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
lower_reso = cv.pyrDown(img)
lower_reso2 = cv.pyrDown(lower_reso)
higher_reso = cv.pyrUp(img)
higher_reso2 = cv.pyrUp(higher_reso)
higher_reso3 = cv.pyrUp(higher_reso2)
higher_fromlower = cv.pyrUp(lower_reso2)
higher_fromlower2 = cv.pyrUp(higher_fromlower)
higher_fromlower3 = cv.pyrUp(higher_fromlower2)
images = [img, lower_reso, lower_reso2, higher_reso, higher_reso2, higher_reso3, higher_fromlower, higher_fromlower2, higher_fromlower3]
titles = ['Original', 'Low', 'Low2', 'High1', 'High2', 'High3', 'HighFrowLow', 'HighFromLow2', 'HighFromLow3']
for i in range(9):
    # cv.imshow(titles[i], images[i])
    plt.subplot(3, 3, i + 1)
    plt.imshow(images[i]), plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()
# cv.waitKey(0)

使用cv.imshow()显示图像金子塔中的3个级别
在这里插入图片描述
使用plt.imshow()显示各个级别(包含下采样和对原图和另一级别图像的上采样)
在这里插入图片描述

拉普拉斯金字塔

由高斯金字塔形成,没有专用功能,拉普拉斯金字塔图像仅像边缘图像;它的大多数元素为零,用于图像压缩。拉普拉斯金字塔的层由高斯金字塔的层与高斯金字塔的高层的扩展版本之间的差形成;拉普拉斯等级的三个等级如下所示(调整对比度以增强内容):

使用金字塔进行图像融合

金字塔的一种应用是图像融合。例如在图像拼接中,需要将两个图像堆叠在一起,由于图像之间的不连续性,看起来效果不太好。在此情况下,使用金字塔混合图像可以无缝混合,而不会在图像中保留大量数据。
进行下面步骤:

  1. 加载苹果和橙子的两个图像
  2. 查找苹果和橙子的高斯金字塔(在此示例中,级别数为6)
  3. 在高斯金字塔中,找到其拉普拉斯金字塔
  4. 在每个拉普拉斯金字塔级别中加入苹果的左半部分和橙子的右半部分
  5. 从此联合图像金字塔中重建原始图像

如下代码:

import cv2 as cv
import numpy as np,sys
from matplotlib import pyplot as plt

A = cv.imread('./OpenCV/apple.png')
O = cv.imread('./OpenCV/orange.png')
A = cv.cvtColor(A, cv.COLOR_BGR2RGB)
O = cv.cvtColor(O, cv.COLOR_BGR2RGB)
A = cv.resize(A, (800, 800), interpolation=cv.INTER_AREA)
O = cv.resize(O, (800, 800), interpolation=cv.INTER_AREA)
# 生成传入图像的高斯金字塔
def gp(img):
    G = img.copy()
    gpImg = [G]
    for i in range(6):
        G = cv.pyrDown(G)
        gpImg.append(G)
    return gpImg
# 生成传入高斯金字塔的拉普拉斯金字塔
def lp(gpImg):
    lpImg = [gpImg[5]]
    for i in range(5, 0, -1):
        GE = cv.pyrUp(gpImg[i])
        L = cv.subtract(gpImg[i - 1], GE)
        lpImg.append(L)
    return lpImg

gpA = gp(A)
gpO = gp(O)
lpA = lp(gpA)
lpO = lp(gpO)
# 现在在每个级别中添加左右两半图像
LS = []
for la, lo in zip(lpA, lpO):
    rows, cols, dpt = la.shape
    ls = np.hstack((la[:, 0:cols // 2], lo[:, cols // 2:]))
    LS.append(ls)
# 现在重建
ls_ = LS[0]
for i in range(1, 6):
    ls_ = cv.pyrUp(ls_)
    ls_ = cv.add(ls_, LS[i])
# 图像两半直接连接
real = np.hstack((A[:, :cols // 2], O[:, cols // 2:]))
images = [A, O, real, ls_]
titles = ['Apple', 'Orange', 'Direct_blendig.jpg', 'Pyramid_blendig.jpg']
for i in range(4):
    plt.subplot(2, 2, i + 1)
    plt.imshow(images[i]), plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()
cv.imwrite('./OpenCV/Pyramid_blendig.png', cv.cvtColor(ls_, cv.COLOR_RGB2BGR))
cv.imwrite('./OpenCV/Direct_blendig.png', cv.cvtColor(real, cv.COLOR_RGB2BGR))

在这里插入图片描述
注意图像拼接时,cols / 2时需使用整除//或int(cols / 2)进行强制类型转换,否则将出现错误:

ls = np.hstack((la[:, 0:cols / 2], lo[:, cols / 2:]))
TypeError: slice indices must be integers or None or have an __index__ method

需将两个图像resize为同一大小,图像大小不一定要是2的幂(但有的可以,有的不可以,不太明白),若不一样大小将出现错误:

error: (-209:Sizes of input arguments do not match) The operation is neither 'array op array' (where arrays have the same size and the same number of channels), nor 'array op scalar', nor 'scalar op array' in function 'cv::arithm_op'

学习来源:OpenCV-Python中文文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值