当前位置: 首页 > news >正文

人工智能——图像梯度处理、边缘检测、绘制图像轮廓、凸包特征检测

一、图像梯度处理

图像梯度是一种描述图像中像素灰度值变化剧烈程度的量,常用G表示。对于二维图像 i (x,y)(这里 x、y 表示图像中像素的坐标位置),图像梯度本质上是一个矢量,它指向了图像灰度变化最快的方向,而其模值(大小)则反映了灰度变化的速率,

        直观地理解,在图像中如果从某个像素点到相邻像素点的灰度值变化很明显(比如从黑突然过渡到白),那么在这个位置就有较大的图像梯度;相反,如果灰度值变化平缓,图像梯度就比较小

        图像梯度的应用:

  1. 边缘检测:图像的边缘往往是灰度值发生突变的地方,通过计算图像梯度能够突出这些边缘信息,帮助我们准确地找到图像中物体的轮廓,这在目标识别、图像分割等众多计算机视觉任务中是非常关键的前置步骤。
  2. 特征提取:可以基于图像梯度提取具有代表性的特征,这些特征能够反映图像局部的纹理、结构等特点,进而用于图像分类、匹配等工作,例如一些基于梯度直方图的特征描述符(如 HOG 特征)就是利用了图像梯度信息来有效表征图像内容。
  3. 图像增强:根据图像梯度的大小来调整像素的灰度值,能够增强图像中细节部分,使得原本不太清晰的细节变得更加明显,提升图像的视觉效果。

        原理:

计算图像的梯度的方法是基于离散的差分来近似实现的

什么是差分呢:
简单来说就是相邻数据元素之间的差值计算,例如水平方向的差分就是用该像素点右侧相邻
像素的灰度值减去左侧相邻像素的灰度值,用于衡量水平方向灰度的变化情况

水平方向梯度(以中心差分近似为例)也是垂直方向的边缘提取
对于图像中坐标为 (x,y) 的像素点,其水平方向的梯度近似计算公式通常为:

也就是用该像素点右侧相邻像素的灰度值减去左侧相邻像素的灰度值来近似表示水平方向灰度变化情况

垂直方向梯度(以中心差分近似为例)也是水平方向的边缘提取:

对于图像中坐标为 (x,y) 的像素点,其垂直方向的梯度近似计算公式通常为:

即用该像素点下方相邻像素的灰度值减去上方相邻像素的灰度值来体现垂直方向灰度变化程度

        怎么去理解水平方向梯度对应提取哪个方向的边缘,垂直方向梯度对应提取哪个方向的边缘呢:

首先理解水平方向梯度,衡量的是图像中水平方向上相邻像素灰度值的变化情况。垂直边缘在图像中是什么样子的呢?它是沿着垂直方向,像素灰度值从上到下(或者从下
到上)发生了突变,比如一条垂直的黑白分界线,在这条分界线的两侧,水平方向上跨越这条
线时,灰度值会瞬间改变,这就导致了水平方向上相邻像素的灰度差值很大,也就是水平方向梯
度很大

(一)边缘提取

        结合图像梯度的处理我们可以看出,垂直边缘提取实际上是在进行水平方向梯度处理,结合差分计算的公式,可以得到一个卷积核,通过卷积操作进行提取垂直边缘

        原理:

利用特定的卷积核来实现垂直边缘提取。例如给定的卷积核(只要能实现像素点左右两侧像素值进行相减)

                                                

其原理是通过对图像当前列左右两侧的元素进行差分操作来提取垂直边缘。因为图像中边缘的值通常明显小于(或大于)周边像素,经过这样的差分计算后,边缘处的差分结果会与非边缘处有明显不同,从而能够突出垂直边缘特征

上述是进行垂直方向边缘提取,同样的水平方向的边缘提取是反过来的(就是进行垂直方向的梯度计算)将用于垂直边缘提取的卷积核转置,如

                                                  

        代码中API

cv2.filter2D(src, ddepth, kernel)

src:代表输入图像,一般以numpy数组的形式传入

ddepth:指定输出图像的深度,常用值为 -1,表示输出与输入具有相同的深度

kernel:就是前面提到的卷积核,通常是一个二维数组(往往采用奇数大小的方形矩阵形式),其作用是计算每个像素周围邻域的加权和,对于垂直边缘提取就是使用对应的垂直边缘提取卷积核

import cv2 as cv
import numpy as np
# # 模拟一张图像,灰度图
# img=np.array([[100,255,255,110,98,20,19,18,21,22],
#              [109,240,98,240,102,20,21,19,20,21],
#              [109,240,240,108,98,20,22,19,19,18],
#              [109,240,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)
# # 定义卷积核,
# kernel=np.array([[-1,0,1],
#                  [-2,0,2],
#                  [-1,0,1]],dtype=np.float32)
# # 二维卷积操作
# img2=cv.filter2D(img,-1,kernel)
# # 打印卷积后的图
# print(img2)
img=cv.imread('../QQ20250725-101713.png')
# # 转为灰度图
img=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# # 定义卷积核
kernel=np.array([[-1,0,1],[-2,0,2],[-1,0,1]],dtype=np.float32)
# # 二维卷积操作 cv.filter2D(src,ddepth,kernel)
img2=cv.filter2D(img,-1,kernel)#水平边缘提取
img3 = cv.filter2D(img,-1,kernel.T)cv.imshow('img',img)
cv.imshow('img2',img2)
cv.imshow('img3',img3)
cv.waitKey(0)
cv.destroyAllWindows()
# # 打印卷积后的图
print(img2)

(二)sobel算子

        刚刚上面的垂直方向、水平方向的卷积核都叫做Sobel算子。Sobel 算子是一种常用的离散微分算子,通过计算水平和垂直两个方向上的灰度差分近似来获取图像的梯度信息,并且会对差分结果进行加权处理,相比于简单的差分计算,它能在一定程度上减少噪声对边缘检测的影响

        Sobel 算子由两个 3×3 的卷积核(也叫滤波器)组成,一个用于检测水平方向的梯度,另一个用于检测垂直方向的梯度:

水平方向 Sobel 卷积核

                                               

这个卷积核与图像进行卷积运算时(可以想象成将卷积核覆盖在图像的每个像素位置依次进行计算),能够突出水平方向上的灰度变化情况,也就是近似地计算出水平方向的图像梯度。

垂直方向 Sobel 卷积核

                                                

同理,它用于检测图像垂直方向的灰度变化,从而得到垂直方向的图像梯度。

代码API

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

src:这是输入图像,通常应该是一个灰度图像(单通道图像),因为 Sobel 算子是基于像素亮度梯度计算的

ddepth:这个参数代表输出图像的深度,通常为-1

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

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

(三)Laplacian算法

Laplacian 算法也被称为拉普拉斯算子,是一种基于二阶导数的图像处理方法,相比于sobel算子,Laplacian算子直接对灰度值的二阶变化进行计算,能更敏锐地捕捉到灰度变化剧烈的地方,也就是边缘区域

        原理:

常见的一种 3×3 拉普拉斯卷积核(滤波器)形式如下:

                                                

计算原理是:将这个卷积核与图像进行卷积运算,对于图像中的每个像素点,以该像素点为中心,把卷积核的每个元素与对应的图像像素值相乘,然后将这些乘积相加,得到的结果就是该像素点处的拉普拉斯值

代码API:

cv2.Laplacian(src, ddepth)

src:这是输入图像

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

​​

二、边缘检测

图像边缘检测是指通过一系列的算法和技术,从图像中识别出像素灰度值发生急剧变化的位置,这些位置通常对应着图像中物体的轮廓、边界以及不同区域的分界线等,也就是所谓的 “边缘

        边缘检测的步骤:

总结下来有六步:

1、高斯滤波(去除噪点)

2、计算图像的梯度

3、计算梯度的振幅

4、计算表梯度的方向

5、非极大值抑制

6、双阈值筛选

(一)高斯滤波(去除噪点)

        图像边缘检测属于锐化操作,本身对噪点比较敏感,所以第一步需要进行平滑处理,也就是采用高斯滤波来去除图像中的噪点。这里使用的是一个 5×5 的高斯核对图像进行操作,其目的在于减少图像中的噪声干扰,使后续的边缘检测能更准确地聚焦在真正的边缘上,而不是被噪声引起的灰度变化误导

(二)计算图像的梯度

        想找边缘,得先知道每个像素 “变化有多剧烈”(这就是 “梯度值”),利用 Sobel 算子来计算图像的梯度值,通过将水平核垂直方向的两个卷积核分别与图像进行卷积运算,来计算图像中每个中心像素点在水平方向(记为G(x))和垂直方向(记为G(y))上的梯度值

(三)计算梯度幅度:

在得到Gx和Gy后,有两种常见的计算梯度幅值G的方式:

                                        

另一种是

                                        

在 OpenCV 中默认使用后者来计算梯度值,这个梯度幅值能够反映出该像素点处边缘的明显程度。G 越大,说明这像素所在位置越像 “边缘”(变化越剧烈)

(四)计算梯度的方向θ(往哪个方向变)

        根据公式,进而得到来计算角度值,这个角度值其实就是当前边缘的梯度方向。通过对图像中所有像素点都进行上述计算,就能获取每个像素点的梯度值与梯度方向,并且可以依据梯度方向来推断边缘的方向

        特殊情况处理(插值算法与边缘方向分类)

        插值算法:
如果计算得到的梯度方向不是 0°、45°、90°、135° 这种特定角度,那么就要用到单线性插值算法来计算当前像素点在其方向上进行插值的结果,然后进行比较并判断是否保留该像素点

        边缘方向分类
为了简化计算过程,一般会将边缘方向归为四个方向,依据θ的值来划分:

  • 当\(\theta\)值为 - 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° 边缘

(五)非极大值抑制

        经过前面计算得到的边缘像素点往往比较多,这是因为高斯滤波的影响使得边缘变得模糊了,所以需要对其进行过滤操作,非极大值抑制就是一种有效的方法,在这一步骤中,需要检查每个像素点的梯度方向上的相邻像素,并保留梯度值最大的像素,将其他像素抑制为零

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

(六)双阈值筛选

        经过了非极大值抑制,仍然需要进一步设置阈值来筛选边缘像素,因为如果阈值设得太低,就会出现假边缘(把一些非边缘像素误判为边缘);而阈值设得太高,一些较弱的边缘就会被丢掉。所以采用双阈值筛选的方法,推荐高低阈值的比例为 2:1 到 3:1 之间,其原理如下

        

  • 当某一像素位置的幅值超过最高阈值时,该像素必是边缘像素;
  • 当幅值低于最低阈值时,该像素必不是边缘像素;
  • 幅值处于最高阈值与最低阈值之间时,如果它能连接到一个高于阈值的边缘时,则被认为是边缘像素,否则就不会被认为是边缘像素。

通过这样的双阈值筛选机制,进一步优化边缘检测的结果,减少误判和漏判情况,得到更准确、高质量的边缘信息

代码API调用:

 edges = cv2.Canny(image, threshold1, threshold2)
  • image:输入的是灰度 / 二值化图像数据,这要求在使用该函数前,如果是彩色图像可能需要先转换为灰度图或者进行二值化等预处理操作;
  • threshold1:代表低阈值,用于决定可能的边缘点;
  • threshold2:代表高阈值,用于决定强边缘点。

三、绘制图像轮廓

图像轮廓(Contour)是指图像中连续的、具有相同颜色 / 灰度值的像素点构成的曲线,它代表了物体的边界形状。轮廓是 “连续的整体”,强调像素之间的连接性(必须是闭合或连续的曲线)

对比维度图像轮廓(Contour)图像边缘(Edge)
定义连续的、闭合的像素曲线,代表物体整体边界像素灰度值突变的位置,可能是零散的点或线段
连接性必须连续(算法会确保轮廓的连通性)可以是孤立的、不连续的(比如边缘检测可能产生断裂)
处理前提通常基于二值化图像(黑白分明),需先阈值化处理可直接基于灰度图计算(如 Sobel、Canny 算子)

(一)提取轮廓

contours,hierarchy = cv2.findContours(image,mode,method)
  • 返回值:[ 轮廓点坐标 ] 和 [ 层级关系 ]。

  • contours:轮廓点坐标的列表。假设检测到 N 个轮廓,contours 就是 [轮廓1, 轮廓2, ..., 轮廓N]。每个 “轮廓” 是 (M, 1, 2) 形状的 Numpy 数组(M 是该轮廓上的点数)。数组中的每个元素是 [[x, y]],表示轮廓上一个点的坐标(x 是水平位置,y 是垂直位置)

  • hierarchy:表示轮廓之间的关系。本质是一个 (1, N, 4) 形状的 Numpy 数组,记录每个轮廓与其他轮廓的嵌套关系。对于第 i 个轮廓(hierarchy[0][i]),有 4 个值分别表示:

    [后一个轮廓索引, 前一个轮廓索引, 子轮廓索引, 父轮廓索引]
    • 后一个 / 前一个轮廓:同一层级中,当前轮廓的下一个 / 上一个轮廓(方便遍历)。

    • 若不存在对应轮廓,值为 -1
    • 父轮廓:包含当前轮廓的外层轮廓(如外环是内环的父轮廓)。
    • 子轮廓:当前轮廓内部包含的第一个子轮廓(如圆环的内环是外环的子轮廓)。举
      • 例:如果 hierarchy[0][2] = [3, 1, -1, 0],表示:第 2 个轮廓的后一个是 3 号,前一个是 1 号,没有子轮廓,父轮廓是 0 号
  • image:表示输入的二值化图像。

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

  • method:轮廓的表示方法

mode:轮廓的检索模式(决定提取哪些轮廓)

控制是否提取所有轮廓,以及是否保留轮廓之间的嵌套关系(比如 “大轮廓里套小轮廓”)

模式值含义适用场景
cv2.RETR_EXTERNAL只提取最外层轮廓(忽略轮廓内部的子轮廓)检测独立物体(如单独的数字、图标)
cv2.RETR_LIST提取所有轮廓,但不记录轮廓之间的层次关系(最常用,简单高效)只需所有轮廓,不关心嵌套关系
cv2.RETR_CCOMP提取所有轮廓,按 “两层结构” 记录层次(外层和内层)处理有内外两层轮廓的物体(如圆环)
cv2.RETR_TREE提取所有轮廓,完整记录所有层次关系(最详细,计算量稍大)需要分析轮廓嵌套关系(如多个同心圆
method:轮廓的表示方法(决定如何存储轮廓点)

控制轮廓点的存储方式,是保存所有点还是只保存关键顶点(简化轮廓)。常用方法:

方法值含义特点
cv2.CHAIN_APPROX_NONE保存轮廓上所有点的坐标(不简化)信息完整,但数据量大
cv2.CHAIN_APPROX_SIMPLE只保存轮廓的关键顶点(如矩形只保存 4 个角点),简化冗余点数据量小,计算效率高(最常用)
对于mode和method这两个参数来说,一般使用RETR_EXTERNAL和CHAIN_APPROX_SIMPLE这两个选项

 (二)绘制轮廓

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

 

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

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

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

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

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

import cv2 as cv
#读图
img = cv.imread("../images/num.png")
img1 =cv.cvtColor(img, cv.COLOR_BGR2GRAY)
#目标区域必须都是白色,其他区域为黑色这张图像是黑子白底的,所以要先反阈值法变
_,binary = cv.threshold(img1, 127, 255, cv.THRESH_BINARY_INV)
#找轮廓cv.findContours(binary,mode,method)
contours,hierarchy = cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)#绘制轮廓cv.drawContours(image,contours,contourIdx,color,thickness)
#contours:轮廓列表,个轮廓本身也是一个由点坐标构成的二维数组
# contourIdx:要绘制的轮廓索引,-1表示绘制所有轮廓,默认-1
# color:轮廓颜色,如果是RGB就传通道,如果是灰度值传值就默认黑色
# thickness:轮廓线条粗细,默认1
cv.drawContours(img, contours, -1, (0, 0, 255), 2)cv.imshow("img", img)
cv.waitKey(0)
cv.destroyAllWindows()# print(contours)
# print(len(contours))
# print('-'*10)
# print(hierarchy)

四、凸包特征检测

凸包(Convex Hull)是指 能包含点集中所有点的最小凸多边形,其边缘由点集中最外层的点连接而成。

  • 通俗理解:想象用一根橡皮筋紧紧套住所有点,橡皮筋形成的封闭形状就是凸包。
  • 核心特征:凸包上的任意两点连线都完全在凸包内部,且点集中所有点要么在凸包上,要么在凸包内。
凸包检测的常用算法
        穷举法
  • 遍历点集中的所有点对,每对两点连成一条直线。
  • 检查其余所有点是否都在这条直线的 同一侧
    • 若所有点都在同一侧,则这两个点是凸包点(构成凸包的边缘)。
    • 若存在点在两侧,则这两个点不是凸包点。

原理:向量叉积

 QuickHull 法(快速凸包算法)

1、获取凸包点:

cv2.convexHull(points)
  • 输入:points 为轮廓点集(如 contours[0],即单个轮廓的坐标数组)。
  • 输出:凸包顶点的坐标数组(形状为 (m, 1, 2) 的 Numpy 数组,m 为凸包顶点数)

2、 绘制凸包

cv2.polylines(image, pts, isClosed, color, thickness)
  • 输入:
    • image:目标图像(原图)。
    • pts:凸包顶点数组(需用 [hull] 包裹,符合函数格式要求)。
    • isClosed:是否闭合多边形(凸包需设为 True)。
    • color:线条颜色(BGR 格式,如红色 (0,0,255))。
    • thickness:线条宽度。
    • 此处的hull就是图像凸包的顶点组成的数组, 是通过 cv2.convexHull() 函数计算得到的结果
import cv2 as cv
#读图
tu =cv.imread('../images/tu.png')
#灰度化
gray = cv.cvtColor(tu,cv.COLOR_BGR2GRAY)
#二值化
_,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
#查找轮廓cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
contours,hierarchy = cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
#获取凸包点cv.convexHull(contours[0])
hull = cv.convexHull(contours[0])
print(contours[0])
# print(hull)#绘制凸包点
cv.polylines(tu,[hull],1,(0,0,255),2)
cv.imshow('tu',tu)
cv.waitKey(0)
cv.destroyAllWindows()

http://www.lryc.cn/news/601281.html

相关文章:

  • 设计模式(十三)结构型:代理模式详解
  • springboot基于Java与MySQL库的健身俱乐部管理系统设计与实现
  • 设计模式(十一)结构型:外观模式详解
  • Qt 窗口 工具栏QToolBar、状态栏StatusBar
  • IDEA安装Key Promoter X插件记录快捷键使用频率提高生产率
  • 【笔记】活度系数推导
  • 07.4-使用 use 关键字引入路径
  • 一、搭建springCloudAlibaba2021.1版本分布式微服务-父工程搭建
  • Kafka——消费者组消费进度监控都怎么实现?
  • SparkSQL — get_json_object函数详解(解析 json)
  • Vue 四个map的使用方法
  • Java面试实战:企业级性能优化与JVM调优全解析
  • mac neo4j install verifcation
  • 1.qt历史版本安装与多版本开发(解决被拦截问题)
  • 前缀和-560.和为k的子数组-力扣(LeetCode)
  • Qt C++ GUI 函数参数速查手册:基础与布局
  • HDFS基础命令
  • Python 列表推导式与生成器表达式
  • 3-基于FZ3B的Vitis AI DPU加速平台搭建
  • Vscode的常用快捷键(摆脱鼠标计划)
  • CodeBLEU:面向代码合成的多维度自动评估指标——原理、演进与开源实践
  • Jmeter的元件使用介绍:(七)后置处理器详解
  • 【NLP实践】一、中文短句情感二分类实现并提供RestfulApi服务调用
  • Mitk教程案例项目编译
  • Java AI面试实战:Spring AI与RAG技术落地
  • 【Qt开发】信号与槽(二)-> 信号和槽的使用
  • LeetCode第349题_两个数组的交集
  • UDS 0x29 身份验证服务 Authentication service
  • KNN 算法中的各种距离:从原理到应用
  • Java面试全攻略:Spring生态与微服务架构实战