图像处理-008图像梯度

图像处理-008图像梯度

图像可以用平面空间的二维函数f(x,y)来表示,x,y表示空间平面的坐标。图像梯度则是度量图像亮度在x,y方向上的变化程度。梯度提供了两部分信息:一是梯度值的大小展示了图像亮度的变化快慢程度,二是梯度提供了亮度变化的方向。

为便于解释,可用地形来类比。将图像视为地形,图像中的亮度值看做地形中的高度,则地形中任意点的坡度方向为图像的梯度方向,沿坡度前进时地形高度的变化程度则为图像梯度的变化程度。

鉴于梯度具有方向、大小,则用向量来表示梯度。向量的方向即为梯度的方向,向量的长度为梯度的大小。梯度的方向是函数f(x,y)变化最快的方向,沿着梯度的方向容易找到最大值。

从数学的角度来讲,梯度是图像亮度在x,y方向上求导。

∇ f = [ g x g y ] = [ ∂ f ∂ x ∂ f ∂ y ] (008-1) \displaystyle{ \nabla f=\begin{bmatrix} g_{x} \\ g_{y} \end{bmatrix} = \begin{bmatrix} \frac{\partial f}{\partial x} \\ \frac{\partial f}{\partial y} \end{bmatrix} \tag{008-1} } f=[gxgy]=[xfyf](008-1)

其中: ∂ f ∂ x \displaystyle{ \textstyle\frac{\partial f}{\partial x} } xf表示x方向导数,即x方向的梯度, ∂ f ∂ y \displaystyle{ \textstyle\frac{\partial f}{\partial y} } yf表示y方向的导数,即y方向的梯度。

坐标(x,y)梯度方向由公式008-2计算

θ = t a n − 1 ⁡ [ g y g x ] (008-2) \displaystyle{ \theta = \operatorname{tan{^-}{^1}} \left [ \frac{g_{y}}{g_{x}} \right ] \tag{008-2} } θ=tan1[gxgy](008-2)

坐标(x,y)梯度大小由公式008-3计算

g y 2 + g x 2 (008-3) \displaystyle{ \sqrt{g_{y}^{2} + g_{x}^{2}} \tag{008-3} } gy2+gx2 (008-3)

公式008-3计算时计算量比较大,为提升计算效率,采用公式008-4来计算点(x,y)处的梯度。

∣ g x ∣ + ∣ g y ∣ (008-4) |g_x| + |g_y| \tag{008-4} gx+gy(008-4)

梯度计算时,源图像与大小为ksize*ksize的kernel做卷积运算,卷积时,卷积核分水平和垂直两个方向。卷积核有如下特点:

  • 卷积核以奇数型矩阵的形式存在,奇数一般为3,5,7;

  • 卷积核中同行(行梯度)或同列(列梯度)的系数以互为相反数的形式对称分布;

  • 卷积核中各系数和为0;

  • 卷积核中系数大小及排班顺序决定了对图像区域处理的类型。

在这里插入图片描述

图008-1 图像卷积运算

P 5 = c 1 ∗ p 1 + c 2 ∗ P 2 + c 3 ∗ p 3 + c 4 ∗ p 4 + c 5 ∗ p 5 + c c 6 ∗ p 6 + c 7 ∗ p 7 + c 8 ∗ p 8 + c 9 ∗ p 9 P5=c1*p1+c2*P2 + c3*p3+c4*p4+c5*p5+cc6*p6+c7*p7+c8*p8+c9*p9 P5=c1p1+c2P2+c3p3+c4p4+c5p5+cc6p6+c7p7+c8p8+c9p9

图像中P5位置由P5邻域元素与卷积核系数乘积和来确定,P5值越大则表示P5与周围邻域元素差异越大。

若图像中某像素点的像素值与其邻域的像素值差异大,则该点是边缘的可能性越大,因此,梯度被用做图像边缘检测。

计算像素点(x,y)水平方向梯度时
在这里插入图片描述

图008-2 计算水平方向梯度

P 5 x = ( p 3 − p 1 ) ∗ 1 + ( p 6 − p 4 ) ∗ 1 + ( p 9 − − p 7 ) ∗ 1 (008-6) P5_x=(p3-p1) * 1 + (p6-p4) * 1 + (p9--p7) * 1 \tag{008-6} P5x=(p3p1)1+(p6p4)1+(p9p7)1(008-6)

计算像素点(x,y)垂直方向梯度时

在这里插入图片描述

图008-3 计算垂直方向梯度

P 5 y = ( p 7 − p 1 ) ∗ 1 + ( p 8 − p 2 ) ∗ 1 + ( p 9 − − p 3 ) ∗ 1 (008-7) P5_y=(p7-p1) * 1 + (p8-p2) * 1 + (p9--p3) * 1 \tag{008-7} P5y=(p7p1)1+(p8p2)1+(p9p3)1(008-7)

从公式008-2, 008-3可以得出:水平方向梯度计算后得到垂直方向边界,垂直方向梯度计算后得到水平方向梯度。

opencv中提供了 cv.Sobel(), cv.Scharr(), cv.Laplacian()算子来处理图像梯度。

Prewitt

prewitt是一种一阶微分算子的边缘检测,它依据其四邻域中上下、左右点灰度像素差值来计算,在边缘处差值达到最大。prewitt提供两种梯度算子:

沿x轴方向的水平梯度算子;

沿y轴方向的垂直梯度算子。

在这里插入图片描述

图008-4 prewitt垂直(y轴)kernel(左)和水平(x轴)kernel(右)

Soble

soble结合高斯模糊与微分求导,利用局部差分寻找边缘,所得的值是梯度的近似值,与prewitt相比,sobel强调与边缘相邻的像素点对边缘的影响。

同prewitt算子一样,sobel算子也按x,y轴分水平梯度和垂直梯度。

在这里插入图片描述

图008-5 sobel垂直(y轴)kernel(左)和水平(x轴)kernel(右)

opencv提供函数cv.Sobel()来处理sobel变换,其详情如下:

c/c++
void cv::Sobel(InputArray     src,  #输入图像
               OutputArray     dst,  #输出图像,与输入图像同类型,同大小
               int     ddepth,       #图像深度
               int     dx,           #x轴的导数阶数 
               int     dy,           #y轴的导数阶数 
               int     ksize = 3,    #卷积核大小,一般为奇数 如:3579
               double     scale = 1,#计算导数时值时的缩放因子
               double     delta = 0,#输入dst前,添加到dst上的值 
               int     borderType = BORDER_DEFAULT #边界像素展拓类型
               )        
Python:
cv.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]]) ->    dst

实现代码: C++

/**
 * sobel算子
 * @param origin_img
 * @return
 */
int ImageGradient::sobel(const Mat &origin_img) {
  cout << origin_img << endl;
  Mat sobel_x, sobel_y;
  Sobel(origin_img, sobel_x, CV_64F, 1, 0, 5);
  imshow("sobel_x", sobel_x);
  convertScaleAbs(sobel_x, sobel_x);
  imshow("sobel_x_1", sobel_x);
  Sobel(origin_img, sobel_y, CV_64F, 0, 1, 5);
  imshow("sobel_y", sobel_y);
  convertScaleAbs(sobel_y, sobel_y);
  imshow("sobel_y_1", sobel_y);

  Mat sobel_xy;
  Sobel(origin_img, sobel_xy, CV_64F, 1, 1, 5);
  imshow("sobel_xy", sobel_xy);
  convertScaleAbs(sobel_xy, sobel_xy);
  imshow("sobel_xy_1", sobel_xy);

  return EXIT_SUCCESS;
}

实现代码: python

def sobel(origin_img):
    titles = ["origin_image"]
    images = [origin_img]
    sobel_x = cv.Sobel(origin_img, cv.CV_64F, 1, 0, 5)
    titles.append("sobel_x")
    images.append(sobel_x)

    sobel_y = cv.Sobel(origin_img, cv.CV_64F, 0, 1, 5)
    titles.append("sobel_y")
    images.append(sobel_y)

    sobel_xy = cv.Sobel(origin_img, cv.CV_64F, 1, 1, 5)
    titles.append("sobel_xy")
    images.append(sobel_xy)

    sobel_x_abs = cv.convertScaleAbs(sobel_x)
    titles.append("sobel_x_abs")
    images.append(sobel_x_abs)

    sobel_y_abs = cv.convertScaleAbs(sobel_y)
    titles.append("sobel_y_abs")
    images.append(sobel_y_abs)

    sobel_xy_abs = cv.convertScaleAbs(sobel_xy)
    titles.append("sobel_xy_abs")
    images.append(sobel_xy_abs)

    for i in range(7):
        plt.subplot(2, 4, i + 1)
        plt.imshow(images[i], 'gray', vmin=0, vmax=255)
        plt.title(titles[i])
        plt.xticks(), plt.yticks([])
    plt.show()

效果如图008-7所示:

在这里插入图片描述

图008-7 sobel算子梯度效果

Scharr

scharr与sobel一样用来计算(x,y)处的梯度,与sobel相比仅kernel存在差异。scharr算子如图008-8所示:

在这里插入图片描述

图008-8 scharr水平(x轴)kernel(左)和(y轴)kernel(右)垂直

从图008-8可以看出,scharr卷积核中的系数与sobel中的卷积核系数不同。scharr算子增大了锚点邻域的权重,使得scharr算子更易于探测边缘不太明显的图像的边缘。

opencv提供cv.Scharr()函数用于scharr梯度的计算,其详细描述如下:

c/c++
void cv::Scharr(InputArray     src,      #输入图像
                OutputArray     dst,  #输出图像,与输入图像同类型,同大小
                int     ddepth,       #图像深度    
                int     dx,           #x轴的导数阶数  0(不计算)1(一阶导数)2(二阶导数) 
                int     dy,           #y轴的导数阶数  012
                double     scale = 1,    #计算导数时值时的缩放因子
                double     delta = 0,    #输入dst前,添加到dst上的值 
                int     borderType = BORDER_DEFAULT #边界像素展拓类型
                )        
Python:
cv.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]])->dst

实现代码: C/C++

/**
 * scharr 算子
 * @param origin_img
 * @return
 */
int ImageGradient::scharr(const Mat &origin_img) {

  Mat kernel_x, kernel_y;


  Mat scharr_x, scharr_y;
  Scharr(origin_img, scharr_x, CV_64F, 1, 0, 7);
  imshow("scharr_x", scharr_x);
  convertScaleAbs(scharr_x, scharr_x);
  imshow("scharr_x_1", scharr_x);

  Scharr(origin_img, scharr_y, CV_64F, 0, 1, 7);
  imshow("scharr_y", scharr_y);
  convertScaleAbs(scharr_y, scharr_y);
  imshow("scharr_y_1", scharr_y);


  return EXIT_SUCCESS;
}

实现代码: python

# scharr
def scharr(origin_img):
    titles = ["origin_image"]
    images = [origin_img]
    scharr_x = cv.Scharr(origin_img, cv.CV_64F, 1, 0)
    titles.append("scharr_x")
    images.append(scharr_x)

    scharr_y = cv.Scharr(origin_img, cv.CV_64F, 0, 1)
    titles.append("scharr_y")
    images.append(scharr_y)

    scharr_x_abs = cv.convertScaleAbs(scharr_x)
    titles.append("scharr_x_abs")
    images.append(scharr_x_abs)

    scharr_y_abs = cv.convertScaleAbs(scharr_y)
    titles.append("scharr_y_abs")
    images.append(scharr_y_abs)

    for i in range(5):
        plt.subplot(2, 3, i + 1)
        plt.imshow(images[i], 'gray', vmin=0, vmax=255)
        plt.title(titles[i])
        plt.xticks(), plt.yticks([])
    plt.show()

效果图如图008-9

在这里插入图片描述

图008-9 scharr算子计算后的图像边界

Laplacian

sobel与scharr算子都具有方向性,需要分别计算x轴,y轴方向的梯度后,再整合计算出点(x,y)处的梯度。laplacian算子定义如公式008-8所示:

Δ s r c = ∂ 2 s r c ∂ x 2 + ∂ 2 s r c ∂ y 2 (008-8) \Delta src = \frac{\partial ^2{src}}{\partial x^2} + \frac{\partial ^2{src}}{\partial y^2} \tag{008-8} Δsrc=x22src+y22src(008-8)

laplacian算子卷积核如图008-10所示:

在这里插入图片描述

图008-10 laplacian算子kernel

opencv提供函数cv.Laplacian()用作laplacian运算,其描述如下:

c/c++
void cv::Laplacian(InputArray     src,  #输入图像
                   OutputArray     dst,  #输出图像,与输入图像同类型,同大小
                   int     ddepth,       #图像深度
                   int     ksize = 1,    #卷积核大小,一般为奇数
                   double     scale = 1,#计算导数时值时的缩放因子 
                   double     delta = 0,#输入dst前,添加到dst上的值 
                   int     borderType = BORDER_DEFAULT  #边界像素展拓类型
                   )        
Python:
cv.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]]) ->dst

实现代码: C/C++

/**
 * laplacian算子
 * @param origin_img
 * @return
 */
int ImageGradient::laplacian(const Mat &origin_img) {
  Mat kernel_1;
  Laplacian(origin_img, kernel_1, CV_64F, 1);
  imshow("kernel_1", kernel_1);
  convertScaleAbs(kernel_1, kernel_1);
  imshow("kernel_1_abs", kernel_1);


  Mat kernel_3;
  Laplacian(origin_img, kernel_3, CV_64F, 3);
  imshow("kernel_3", kernel_3);
  convertScaleAbs(kernel_3, kernel_3);
  imshow("kernel_3_abs", kernel_3);

  Mat kernel_5;
  Laplacian(origin_img, kernel_5, CV_64F, 5);
  imshow("kernel_5", kernel_5);
  convertScaleAbs(kernel_5, kernel_5);
  imshow("kernel_5_abs", kernel_5);


  //  滤波处理
  Mat blur;
  GaussianBlur(origin_img, blur, Size(1, 1), 0);
  Laplacian(blur, kernel_1, CV_64F, 1);
  imshow("kernel_1_blur", kernel_1);
  convertScaleAbs(kernel_1, kernel_1);
  imshow("kernel_1_abs_blur", kernel_1);

  GaussianBlur(origin_img, blur, Size(3, 3), 0);
  Laplacian(blur, kernel_3, CV_64F, 3);
  imshow("kernel_3_blur", kernel_3);
  convertScaleAbs(kernel_3, kernel_3);
  imshow("kernel_3_abs_blur", kernel_3);

  GaussianBlur(origin_img, blur, Size(5, 5), 0);
  Laplacian(blur, kernel_5, CV_64F, 5);
  imshow("kernel_5_blur", kernel_5);
  convertScaleAbs(kernel_5, kernel_5);
  imshow("kernel_5_abs_blur", kernel_5);


  return EXIT_SUCCESS;
}

实现代码:python

# laplacian
def laplacian(origin_img):
    titles = ["origin_image"]
    images = [origin_img]
    kernel_1 = cv.Laplacian(origin_img, cv.CV_64F, None, 1)
    titles.append("kernel_1")
    images.append(kernel_1)
    kernel_1_abs = cv.convertScaleAbs(kernel_1)
    titles.append("kernel_1_abs")
    images.append(kernel_1_abs)

    kernel_3 = cv.Laplacian(origin_img, cv.CV_64F, None, 3)
    titles.append("kernel_3")
    images.append(kernel_3)
    kernel_3_abs = cv.convertScaleAbs(kernel_3)
    titles.append("kernel_3_abs")
    images.append(kernel_3_abs)

    kernel_5 = cv.Laplacian(origin_img, cv.CV_64F, None, 5)
    titles.append("kernel_5")
    images.append(kernel_5)
    kernel_5_abs = cv.convertScaleAbs(kernel_5)
    titles.append("kernel_5_abs")
    images.append(kernel_5_abs)

    for i in range(7):
        plt.subplot(2, 4, i + 1)
        plt.imshow(images[i], 'gray', vmin=0, vmax=255)
        plt.title(titles[i])
        plt.xticks(), plt.yticks([])
    plt.show()

图像经laplacian算子计算后的梯度如图008-11所示:

在这里插入图片描述

图008-11 laplacian算子计算图像梯度效果

从图008-11可以看出kernel size越大,图像边缘越明显。但边缘处存在大量的噪声。为消除噪声,对图像进行滤波处理。

# laplacian
def laplacian_after_blur(origin_img):
    titles = ["origin_image"]
    images = [origin_img]

    blur_image = cv.GaussianBlur(origin_img, (1, 1), 0)
    # titles.append("kernel_1_blur")
    # images.append(blur_image)
    kernel_1 = cv.Laplacian(blur_image, cv.CV_64F, None, 1)
    titles.append("kernel_1")
    images.append(kernel_1)
    kernel_1_abs = cv.convertScaleAbs(kernel_1)
    titles.append("kernel_1_abs")
    images.append(kernel_1_abs)

    blur_image = cv.GaussianBlur(origin_img, (3, 3), 0)
    # titles.append("kernel_3_blur")
    # images.append(blur_image)
    kernel_3 = cv.Laplacian(blur_image, cv.CV_64F, None, 3)
    titles.append("kernel_3")
    images.append(kernel_3)
    kernel_3_abs = cv.convertScaleAbs(kernel_3)
    titles.append("kernel_3_abs")
    images.append(kernel_3_abs)

    blur_image = cv.GaussianBlur(origin_img, (5, 5), 0)
    # titles.append("kernel_5_blur")
    # images.append(blur_image)
    kernel_5 = cv.Laplacian(origin_img, cv.CV_64F, None, 5)
    titles.append("kernel_5")
    images.append(kernel_5)
    kernel_5_abs = cv.convertScaleAbs(kernel_5)
    titles.append("kernel_5_abs")
    images.append(kernel_5_abs)

    for i in range(7):
        plt.subplot(3, 3, i + 1)
        plt.imshow(images[i], 'gray', vmin=0, vmax=255)
        plt.title(titles[i])
        plt.xticks(), plt.yticks([])
    plt.show()

在这里插入图片描述

图008-12 高斯滤波后laplacian算子计算图像梯度效果

从滤波后再使用laplacian计算梯度得到的效果来看,滤波后得到的图像边缘更平滑,噪点更少。

参考文献:

  1. https://docs.opencv.org/4.6.0/d5/d0f/tutorial_py_gradients.html

  2. https://handwiki.org/wiki/Image_gradient

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值