一、图像梯度

cv2.filter2D(src, ddepth, kernel)

filter2D函数是用于对图像进行二维卷积(滤波)操作。它允许自定义卷积核(kernel)来实现各种图像处理效果,如平滑、锐化、边缘检测等

  • src: 输入图像,一般为numpy数组。

  • ddepth: 输出图像的深度,可以是负值(表示与原图相同)、正值或其他特定值(常用-1 表示输出与输入具有相同的深度)。

  • kernel: 卷积核,一个二维数组(通常为奇数大小的方形矩阵),用于计算每个像素周围邻域的加权和。

  • 先用数组模拟一下

import cv2 as cv
import numpy as np
# 模拟一张图像,灰度图
img=np.array([[100,255,240,110,98,20,19,18,21,22],
             [109,240,240,108,102,20,21,19,20,21],
             [109,240,240,108,98,20,22,19,19,18],
             [109,200,240,108,102,20,23,19,20,22],
             [109,240,240,108,98,20,22,19,20,18],
             [100,240,240,110,98,20,19,18,21,22],
             [109,240,240,108,102,20,22,19,20,21],
             [109,240,240,108,98,20,22,19,19,18],
              ],dtype=np.uint8)
# 定义卷积核,模拟3X3的卷积核
kernel=np.array([[-1,0,1],
                 [-2,0,2],
                 [-1,0,1]],dtype=np.float32)
# 二维卷积操作
img2=cv.filter2D(img,-1,kernel)
# 打印卷积后的图
print(img2)
#得到结果,可以发现255这一列和其他差异很大
[[  0 255   0   0   0   0   0   2  12   0]
 [  0 255   0   0   0   0   0   0   7   0]
 [  0 255   0   0   0   0   0   0   3   0]
 [  0 255   0   0   0   0   0   0   4   0]
 [  0 255   0   0   0   0   0   0   5   0]
 [  0 255   0   0   0   0   0   0   9   0]
 [  0 255   0   0   0   0   0   0   7   0]
 [  0 255   0   0   0   0   0   0   2   0]]

二、图像边缘

 

import cv2
import numpy as np
shu = cv2.imread('../images/shudu.png',cv2.IMREAD_GRAYSCALE)
#定义卷积核
#垂直边缘提取
kernel = np.array([[-1,0-1],
                  [-2,0,2],
                  [-1,0-1]],dtype = np.float32
)
dst = cv2.filter2D(shu,-1,kernel)
cv2.imshow('shudu',shu)#原图2.1
cv2.imshow('dst',dst)#垂直边缘提取的图2.2
# 水平边缘提取 kernel.T(转置)
dst2 = cv2.filter2D(shu,-1,kernel.T)
cv2.imshow('dst2',dst2)#水平边缘提取的图2.3
cv2.waitKey(0)
cv2.destroyAllWindows()

图2.1 

 

图2.2                                                                        图2.3 

三、算子

1、Sobel算子

先在垂直方向计算梯度:Gx = k1 × src

再在水平方向计算梯度:Gy = k2 × src

最后求出总梯度:G =(Gx平方+Gy平方)开根号

sobel_image = cv2.Sobel(src, ddepth, dx, dy, ksize)

src:这是输入图像,通常应该是一个灰度图像(单通道图像),因为 Sobel 算子是基于像素亮度梯度计算的。在彩色图像的情况下,通常需要先将其转换为灰度图像。

ddepth:这个参数代表输出图像的深度,即输出图像的数据类型。在 OpenCV 中,-1 表示输出图像的深度与输入图像相同。

dx,dy:当组合为dx=1,dy=0时求x方向的一阶导数,在这里,设置为1意味着我们想要计算图像在水平方向(x轴)的梯度。当组合为 dx=0,dy=1时求y方向的一阶导数(如果同时为1,通常得不到想要的结果,想两个方向都处理的比较好 学习使用后面的算子)

ksize:Sobel算子的大小,可选择3、5、7,默认为3。

import cv2
shu = cv2.imread('../images/shudu.png',cv2.IMREAD_GRAYSCALE)
#sobel 水平方向差分 提取垂直边缘
dst = cv2.Sobel(shu,-1,dx=1,dy=0,ksize=3)
cv2.imshow('dst',dst)
#提取水平边缘
dst1 = cv2.Sobel(shu,-1,dx=0,dy=1,ksize=3)
cv2.imshow('dst1',dst1)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

2、Prewitt算子

3、Scharr算子

4、Laplacian算子

不考虑对角情况二维Laplacian滤波核就是:

考虑对角情况:

cv2.Laplacian(src, ddepth)

src:这是输入图像

ddepth:这个参数代表输出图像的深度,即输出图像的数据类型。在 OpenCV 中,-1 表示输出图像的深度与输入图像相同。

import cv2
shu = cv2.imread('../images/shudu.png',cv2.IMREAD_GRAYSCALE)
#拉普拉斯算子
dst = cv2.Laplacian(shu,-1)

cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

四、图像边缘检测

1、步骤:

(1)、高斯滤波:目的是去除噪点
(2)、计算图像的梯度与方向
梯度值:

使用了sobel算子来计算图像的梯度值

方向:

并且如果梯度方向不是0°、45°、90°、135°这种特定角度,那么就要用到插值算法来计算当前像素点在其方向上进行插值的结果了,然后进行比较并判断是否保留该像素点。这里使用的是单线性插值,通过A1和A2两个像素点获得dTmp1与dTmp2处的插值,然后与中心点C进行比较(非极大值抑制)。

当法线为-22.5°~22.5°,或-157.5°~157.5°,则认为边缘为水平边缘;

当法线方向为22.5°~67.5°,或-112.5°~-157.5°,则认为边缘为45°边缘;

当法线方向为67.5°~112.5°,或-67.5°~-112.5°,则认为边缘为垂直边缘;

当法线方向为112.5°~157.5°,或-22.5°~-67.5°,则认为边缘为135°边缘;

(3)、非极大值抑制

得到每个边缘的方向之后,其实把它们连起来边缘检测就算完了,但是为什么还有这一步与下一步呢?是因为经过第二步得到的边缘不经过处理是没办法使用的,因为高斯滤波的原因,边缘会变得模糊,导致经过第二步后得到的边缘像素点非常多,因此我们需要对其进行一些过滤操作,而非极大值抑制就是一个很好的方法,它会对得到的边缘像素进行一个排除,使边缘尽可能细一点。

在该步骤中,我们需要检查每个像素点的梯度方向上的相邻像素,并保留梯度值最大的像素,将其他像素抑制为零。假设当前像素点为(x,y),其梯度方向是0°,梯度值为G(x,y),那么我们就需要比较G(x,y)与两个相邻像素的梯度值:G(x-1,y)和G(x+1,y)。如果G(x,y)是三个值里面最大的,就保留该像素值,否则将其抑制为零

(4)、双阈值筛选

经过非极大值抑制之后,我们还需要设置阈值来进行筛选,当阈值设的太低,就会出现假边缘,而阈值设的太高,一些较弱的边缘就会被丢掉,因此使用了双阈值来进行筛选,推荐高低阈值的比例为2:1到3:1之间

当某一像素位置的幅值超过最高阈值时,该像素必是边缘像素;当幅值低于最低像素时,该像素必不是边缘像素;幅值处于最高像素与最低像素之间时,如果它能连接到一个高于阈值的边缘时,则被认为是边缘像素,否则就不会被认为是边缘。也就是说,上图中的A和C是边缘,B不是边缘。因为C虽然不超过最高阈值,但其与A相连,所以C就是边缘。

2、API实现

edges = cv2.Canny(image, threshold1, threshold2),即使读到的是彩色图也可以进行处理。

  • image:输入的灰度/二值化图像数据。

  • threshold1:低阈值,用于决定可能的边缘点。

  • threshold2:高阈值,用于决定强边缘点。

import cv2
img = cv2.imread('../images/shudu.png')
#灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#二值化处理
_, G = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
#使用Canny边缘检测
adges = cv2.Canny(G,50,100)

cv2.imshow('edges', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
#效果如下图所示

五、图像轮廓查找

轮廓是一系列相连的点组成的曲线,代表了物体的基本外形。相对于边缘,轮廓是连续的,边缘不一定连续,如下图所示。轮廓是一个闭合的、封闭的形状。

1、API

contours,hierarchy = cv2.findContours(image,mode,method)

  • 返回值:[ 轮廓点坐标 ] 和 [ 层级关系 ]。

  • contours:表示获取到的轮廓点的列表。检测到有多少个轮廓,该列表就有多少子列表,每一个子列表都代表了一个轮廓中所有点的坐标。

  • hierarchy:表示轮廓之间的关系。对于第i条轮廓,$hierarchyi$, $hierarchyi$ , $hierarchyi$ ,$ hierarchyi$分别表示其后一条轮廓、前一条轮廓、(同层次的第一个)子轮廓、父轮廓的索引(如果没有相应的轮廓,则对应位置为-1)。该参数的使用情况会比较少。

  • image:表示输入的二值化图像。

  • mode:表示轮廓的检索模式。

  • method:轮廓的表示方法。

2、mode参数 

mode参数共有四个选项分别为:RETR_LIST,RETR_EXTERNAL,RETR_CCOMP,RETR_TREE。

(1)、RETR_EXTERNAL

表示只查找最外层的轮廓。并且在hierarchy里的轮廓关系中,每一个轮廓只有前一条轮廓与后一条轮廓的索引,而没有父轮廓与子轮廓的索引。

(2)、RETR_LIST

表示列出所有的轮廓。并且在hierarchy里的轮廓关系中,每一个轮廓只有前一条轮廓与后一条轮廓的索引,而没有父轮廓与子轮廓的索引。

(3)、RETR_CCOMP

表示列出所有的轮廓。并且在hierarchy里的轮廓关系中,轮廓会按照成对的方式显示。

RETR_CCOMP 模式下,轮廓被分为两个层级:

  • 层级 0:所有外部轮廓(最外层的边界)。

  • 层级 1:所有内部轮廓(孔洞或嵌套的区域)

(4)、 RETR_TREE

表示列出所有的轮廓。并且在hierarchy里的轮廓关系中,轮廓会按照树的方式显示,其中最外层的轮廓作为树根,其子轮廓是一个个的树枝。

3、

3、method参数

method参数有三个选项:CHAIN_APPROX_NONE、CHAIN_APPROX_SIMPLE、CHAIN_APPROX_TC89_L1。

  • CHAIN_APPROX_NONE表示将所有的轮廓点都进行存储

  • CHAIN_APPROX_SIMPLE表示只存储有用的点,比如直线只存储起点和终点,四边形只存储四个顶点,默认使用这个方法

4、绘制轮廓

cv2.drawContours(image, contours, contourIdx, color, thickness)

  • image:原始图像,一般为单通道或三通道的 numpy 数组。

  • contours:包含多个轮廓的列表,每个轮廓本身也是一个由点坐标构成的二维数组(numpy数组)。

  • contourIdx:要绘制的轮廓索引。如果设为 -1,则会绘制所有轮廓。根据索引找到轮廓点绘制出来。默认是-1。

  • color:绘制轮廓的颜色,可以是 BGR 值或者是灰度值(对于灰度图像)。

  • thickness:轮廓线的宽度,如果是正数,则画实线;如果是负数,则填充轮廓内的区域。

5、API使用

import cv2
img = cv2.imread('../images/num.png')
# 灰度图
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# 二值化
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
#查找轮廓
counters,hierarchy = cv2.findCountours(binary,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawCountours(img,counters,-1,(0,255,0),2,cv2.LINE_AA)

cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

六、凸包

1、获取凸包点

cv2.convexHull(points)

  • points:输入参数,图像的轮廓

2、绘制凸包

cv2.polylines(image, pts, isClosed, color, thickness=1)

  • image:要绘制线条的目标图像,它应该是一个OpenCV格式的二维图像数组(如numpy数组)。

  • pts:一个二维 numpy 数组,每个元素是一维数组,代表一个多边形的一系列顶点坐标。

  • isClosed:布尔值,表示是否闭合多边形,如果为 True,会在最后一个顶点和第一个顶点间自动添加一条线段,形成封闭的多边形。

  • color:线条颜色,可以是一个三元组或四元组,分别对应BGR或BGRA通道的颜色值,或者是灰度图像的一个整数值。

  • thickness(可选):线条宽度,默认值为1

import cv2
# 读图
tu = cv2.imread('../images/tu.png')

# 灰度化
gray = cv2.cvtColor(tu, cv2.COLOR_BGR2GRAY)
# 二值化
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 查找轮廓
counters, th = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

#获取凸包
hull = cv2.convexHull(counters[0])
print(hull)
#打印结果就是每个凸包点的坐标
"""
[[[378 113]]

 [[422 157]]

 [[430 217]]

 [[375 274]]

 [[221 316]]

 [[ 87 233]]

 [[ 85 163]]

 [[ 92  88]]

 [[317  61]]]
"""
#绘制凸包
hull1 = cv2.polylines(tu,[hull],isClosed=True,color=(0, 255, 0), thickness=1)
cv2.imshow('tu', tu)
cv2.waitKey(0)
cv2.destroyAllWindows()

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐