OpenCV自学总结:图像梯度与边缘检测

本文从原理及代码方面讲解图像梯度与边缘检测


文章目录

  • 一. 图像梯度
    • 1. 梯度
    • 2. 图像的梯度
    • 3. 图像梯度的应用及简单数学原理
    • 4. 代码
      • ①. Scharr算子
      • ②. Sobel算子
      • ③. Laplacian算子
    • 5. 代码讲解
  • 二. Canny边缘检测
    • 1. 边缘检测
    • 2. 原理
    • 3. 代码
    • 4. 代码讲解
  • 三. 总结


一. 图像梯度

1. 梯度

梯度是数学中的常用术语,其本质是一个向量,代表的是,函数在某个位置导数取得最大值,即函数的变化速度最大。一般情况下,我们用倒三角表示每个函数的梯度。对于一个线性函数来说,梯度就是其导数,可以可拓展来细细想想,这里就不多说。

2. 图像的梯度

图像的梯度自然是与梯度有联系的,但是两个并不是一个概念的。对于图像来说,将其梯度视为一个二维的离散函数,或者说白一点就是视为一个二维数组,每个位置有一个值,从(0,0)开始遍历,利用所有值从头至尾绘制一个函数图像,而图像梯度就是利用这样一幅图像求的。
假设离散函数图像如下。
OpenCV自学总结:图像梯度与边缘检测_第1张图片
那么梯度图线也很容易得出。
OpenCV自学总结:图像梯度与边缘检测_第2张图片

3. 图像梯度的应用及简单数学原理

图像梯度主要应用在边缘检测上,而图像的边缘检测应用非常广,一般的目标识别包括一些其他功能都需要用到边缘检测。这里解释一下为什么边缘检测会需要图像梯度,用一张图来举例子。
OpenCV自学总结:图像梯度与边缘检测_第3张图片这样一幅图,为什么能说,我们一眼能看出这个熊的边缘,其实并不是因为其他景象模糊(当然也有一定关系)但是本质上是因为熊的边缘像素点到周围环境的像素点变化很大,突然之间的大变化,就体现出了边缘。再者说,为什么我们头发和脸很容易区别而皮肤黑一些的朋友却可能在很远的地方将皮肤看成头发,也是如此。虽然说是很直观的感受,但是这里的原理却很有意思。一旦变化很大,那么图像的梯度图在这里就会出现很大的波峰。所以可以利用梯度图求出边缘。
Sobel算子和Scharr算子是OpenCV提供常用与计算边缘的算子。
Sobel算子是高斯模糊与微分操作的结合体,因此它的抗噪声能力非常好,同时我们可以设置求导的方向,卷积核的大小等,当设置卷积核的大小为-1时,那么就会使用Scharr算子。在小图时,Scharr滤波的效果更好。3x3的Scharr滤波器卷积核如下:OpenCV自学总结:图像梯度与边缘检测_第4张图片

拉普拉斯算子(Laplacian算子)
拉普拉斯算子可以使用二阶导数的形式定义,可假设其离散实现类似于二阶Sobel导数,其实OpenCV在调用拉普拉斯算子时,本质也是调用着Sobel算子。其公式如下:
在这里插入图片描述

这里注意算子的使用,卷积的过程,都是可以自己写出来的,因此可以在调用API用利用filter2D做卷积计算,进行对比效果。

4. 代码

①. Scharr算子

#图像梯度  可以使用filter2D 对比。
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt


#记载图像
def load_iamge():
    src=cv.imread('num.jpg')
    src=cv.resize(src,(512,512))
    return src
#sobel算子和Scharr算子
def sobi_charr_operator(image):
    #1 0 表示只求x方向
    x=cv.Scharr(image,cv.CV_64F,1,0)
    x=cv.convertScaleAbs(x)
    #1 0表示只求y方向
    y=cv.Scharr(image,cv.CV_64F,0,1)#,ksize=3
    y=cv.convertScaleAbs(y)
    #1 1表示求xy方向即二阶导 还有另一种方法,加权求和
    #x_y=cv.Sobel(image,cv.CV_64F,1,1,ksize=3)
    x_y=cv.addWeighted(x,0.5,y,0.5,0)
    return x,y,x_y
    
x,y,x_y=sobi_charr_operator(load_iamge())
cv.imshow('x',x)
cv.imshow('y',y)
cv.imshow('x_y',x_y)
cv.waitKey(0)
cv.destroyAllWindows()

效果如下:

②. Sobel算子

#图像梯度  可以使用filter2D 对比。
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt


#记载图像
def load_iamge():
    src=cv.imread('num.jpg')
    src=cv.resize(src,(512,512))
    return src
#sobel算子和Scharr算子
def sobi_charr_operator(image):
    #1 0 表示只求x方向
    x=cv.Sobel(image,cv.CV_64F,1,0)
    x=cv.convertScaleAbs(x)
    #1 0表示只求y方向
    y=cv.Sobel(image,cv.CV_64F,0,1)#,ksize=3
    y=cv.convertScaleAbs(y)
    #1 1表示求xy方向即二阶导 还有另一种方法,加权求和
    #x_y=cv.Sobel(image,cv.CV_64F,1,1,ksize=3)
    x_y=cv.addWeighted(x,0.5,y,0.5,0)
    return x,y,x_y
    
x,y,x_y=sobi_charr_operator(load_iamge())
cv.imshow('x',x)
cv.imshow('y',y)
cv.imshow('x_y',x_y)
cv.waitKey(0)
cv.destroyAllWindows()

作对比可以明显发现,这种小图,Scharr算子的处理确实要好于Sobel算子

③. Laplacian算子

#图像梯度  可以使用filter2D 对比。
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt


#记载图像
def load_iamge():
    src=cv.imread('num.jpg')
    src=cv.resize(src,(512,512))
    return src

#laplacian算子
def laplacian_operator(image):
    image=cv.Laplacian(image,cv.CV_64F)
    image=cv.convertScaleAbs(image)
    cv.imshow('input_1',image)
    cv.waitKey(0)
    cv.destroyAllWindows()
    
laplacian_operator(load_image())

OpenCV自学总结:图像梯度与边缘检测_第5张图片

5. 代码讲解

cv.Scharr()方法

  • 参数一:原图
  • 参数二:深度,也就是数据类型,这里用到了F是因为从白到黑的过程中求导,其导数为负,如果采用整形,数据会损失。
  • 参数三:1表示选定x方向,0表示不考虑x方向。
  • 参数四:1表示考虑y方向,0表示不考虑y方向,当x,y都为1时,即求x和y方向的导数
  • 参数五:ksize:卷积核的尺寸 这里注意必须为奇数,以前的博客说过。

cv.Sobel()方法

  • 参数与cv.Scharr相同

cv.convertScaleAbs()方法

  • 将图像转化为先前的uint8形式,使得图像可以显示出来,该方法必须要使用,否则会出现。如下情况

OpenCV自学总结:图像梯度与边缘检测_第6张图片

cv.addWeighted()

  • 加权合并图像。详解

cv.Laplacian()

  • 参数一:原图
  • 参数二:深度(数据类型)
  • 参数三:ksize(可选)
  • 其他的一般设置为默认。

二. Canny边缘检测

1. 边缘检测

Canny边缘检于在1986年提出来,但是一直沿用至今,它拥有之前讲解到的方法没有考虑到的优点。

  1. 它做了基于边缘梯度方向的非极大值抑制。
  2. 做了双阈值的滞后阈值处理。

非极大值抑制(NMS)
可以去这里细细了解。

2. 原理

Canny边缘检测大概分为几步。

  • 高斯模糊
  • 灰度转化
  • 梯度计算(这里就会利用第一部分的知识)
  • 非极大值信号抑制
  • 高低阈值处理输出二值图像

这里注意高低阈值处理其实可以简单的解释。

如果一个像素的梯度大于上限值,则被认为是边缘像素,保留。

如果小于下限阈值,则被抛弃。

那么如果该点的梯度位于两者之间,则当其与高于上限值的像素点连接时我们才保留,否则删除。

3. 代码

#Canny 边缘检测
import cv2 as cv
import numpy as np

#加载图像
def load_image():
    src=cv.imread('con_1.jpg')
    src=cv.resize(src,(512,512))
    cv.imshow('src',src)
    return src
#求边缘
def edge_demo(image):
    #模糊
    gau=cv.GaussianBlur(image,(3,3),0)
    #灰度图
    grey=cv.cvtColor(gau,cv.COLOR_BGR2GRAY)
    #求图像梯度 cannyAPi要求不能为浮点数
    x=cv.Sobel(grey,cv.CV_16SC1,1,0)
    y=cv.Sobel(grey,cv.CV_16SC1,0,1)
    #canny边缘检测
    edge=cv.Canny(x,y,50,150)
    cv.imshow('edge',edge)
    dst=cv.bitwise_and(image,image,mask=edge)
    cv.imshow('dst',dst)
    cv.waitKey(0)
    cv.destroyAllWindows()
edge_demo(load_image())   

结果如下:

4. 代码讲解

cv.Canny(x,y,50,150)

  • 参数一:x方向梯度图
  • 参数二:y方向梯度图
  • 参数三:低阈值
  • 参数四:高阈值
  • 返回值:边缘二值图像

有几个需要注意的点

  • 这里的图像深度,也就是数据类型没有用到F是因为,canny是不接受浮点数的否则会报错,因此改为此数据类型,记住即可。
  • bitwise_and方法可以获取到彩色的边缘。该方法在先前的博客中也讲到。加入掩膜之后就很容易得到彩色图像。

三. 总结

今天的内容就到这里,边缘检测属于必须要掌握的重要技能,因此不清楚的还需要去网上查询其他的资料。

你可能感兴趣的:(机器学习,计算机视觉,opencv)