边缘
在OpenCV中,边缘检测是一种重要的图像处理技术,用于标识图像中亮度变化明显的点或区域。以下是OpenCV提供的三种常见边缘检测方法的解释:
-
Canny边缘检测:
Canny边缘检测器由John F. Canny在1986年提出,并被广泛应用于计算机视觉和图像处理领域。它是一种多阶段的边缘检测算法,主要包括以下步骤:
- 噪声抑制:首先,通过使用高斯滤波器对图像进行平滑处理,以去除图像中的噪声。
- 计算梯度幅值和方向:使用Sobel算子计算图像中每个像素点的水平和垂直方向的梯度值,并根据这些梯度值计算每个像素点的梯度幅值和方向。
- 非极大值抑制:在梯度幅值图像上进行非极大值抑制,将边缘宽度变窄,使边缘更加细化和明确。
- 双阈值处理:根据设定的高阈值和低阈值,将梯度幅值图像中的像素点分为强边缘、弱边缘和非边缘三类。
- 边缘连接:最后,通过连接强边缘像素点和与之相邻的弱边缘像素点,得到完整的边缘图像。
-
Sobel边缘检测:
Sobel边缘检测器是一种基于离散性差分的边缘检测算子。它结合了高斯平滑和微分求导,用来计算图像亮度函数的灰度之近似值。Sobel算子有两个,一个是检测水平边缘的,另一个是检测垂直边缘的。在图像的任何一点使用此算子,将会产生对应的灰度矢量或是其法矢量。Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。它对噪声具有平滑作用,提供较为精确的边缘方向信息,但边缘定位精度可能不够高。
-
Laplacian边缘检测:
Laplacian边缘检测器是一种基于二阶导数的边缘检测器。它具有旋转不变性,可以满足不同方向的图像边缘锐化(边缘检测)的要求。Laplacian算子是一种二阶导数算子,对噪声比较敏感,因此常需要配合高斯滤波一起使用。Laplacian算子具有各方向同性的特点,能够对任意方向的边缘进行提取,具有无方向性的优点。然而,由于其对噪声敏感,所以在使用Laplacian算子进行边缘检测时,通常需要先对图像进行平滑处理。
以上三种边缘检测方法各有优缺点,适用于不同的应用场景。在实际使用中,可以根据具体需求选择合适的边缘检测方法。
轮廓
轮廓可以简单认为成将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。轮廓是图像目标的外部特征,这种特征对于我们进行图像分析,目标识别和理解等更深层次的处理都有很重要的意义。
轮廓提取的基本原理:对于一幅背景为黑色、目标为白色的二值图像,如果在图中找到一个白色点,且它的8邻域(或4邻域)也均为白色,则说明该点是目标的内部点,将其置为黑色,视觉上就像内部被掏空一样;否则保持白色不变,该点是目标的轮廓点。
一般在寻找轮廓之前,都要将图像进行阈值化或Canny边缘检测,转换为二值化图像。
边缘检测和轮廓检测区别:
边缘检测主要是通过一些手段检测数字图像中明暗变化剧烈(即梯度变化比较大)像素点,偏向于图像中像素点的变化。如canny边缘检测,结果通常保存在和源图片一样尺寸和类型的边缘图中。
轮廓检测指检测图像中的对象边界,更偏向于关注上层语义对象。如OpenCV中的findContours()函数,它会得到每一个轮廓并以点向量方式存储,除此也得到一个图像的拓扑信息,即一个轮廓的后一个轮廓、前一个轮廓等的索引编号。
查找轮廓
binary,contours,hierarchy = cv2.findContours(img,mode,method)
版本不一样,opencv4是删掉第一个参数
contours,hierarchy = cv2.findContours(img,mode,method)
参数:
img:输入图像,二值图
mode:轮廓的检索模式,主要有四种方式:
1.cv2.RETR_EXTERNAL:只检测外轮廓,所有子轮廓被忽略
2.cv2.RETR_LIST:检测的轮廓不建立等级关系,所有轮廓属于同一等级
3.cv2.RETR_CCOMP:返回所有的轮廓,只建立两个等级的轮廓。一个对象的外轮廓为第1级组织结构。而对象内部中空洞的轮廓为第2级组织结构,空洞中的任何对象的轮廓又是第1级组织结构。
4.cv2.RETR_TREE:返回所有的轮廓,建立一个完整的组织结构的轮廓。
method:轮廓的近似方法,主要有以下两种:
1.cv2.CHAIN_APPROX_NONE︰存储所有的轮廓点,相邻的两个点的像素位置差不超过1。
2.cv2.CHAIN_APPROX_SIMPLE:压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息。
返回:
contours:检测出的轮廓,所有轮廓的列表结构,每个轮廓是目标对象边界点的坐标的数组
hierarchy:轮廓的层次结构。
hierarchy使用包含四个元素的数组来表示:[Next,Previous,First_Child,Parent]
Next 同一级组织结构中的下一个轮廓
Previous 同一级组织结构中的前一个轮廓
First_Child它的第一个子轮廓,从上往下从左往右的顺序
Parent父轮廓 如果轮廓没有父轮廓或子轮廓时,则将其置为-1。
在检测轮廓时︰有时对象可能位于不同的位置,也有可能一个形状在另外一个形状的内部,这种情况下我们称外部的形状为父,内部的形状为子。按照这种方式分类,一幅图像中的所有轮廓之间就建立父子关系。这样我们就可以确定一个轮廓与其他轮廓是怎样连接的,比如它是不是某个轮廓的子轮廓,或者是父轮廓。这种关系就是轮廓的层次关系。
绘制轮廓
cv2.drawContours(img,contours,indes,color,width)
参数:
img:轮廓检测的原图像. contours:检测出的轮廓。 Index:轮廓的索引,绘制单个轮廓时指定其索引,绘制全部的轮廓时设为-1即可. color和width:绘制时轮廓的颜色及线型的宽度。
#检测并绘制轮廓
import cv2
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
%matplotlib inline
#1.图像输入,转为二值图
img = cv2.imread('hhh.png')
gra_img=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#2.边缘检测
canny=cv2.Canny(gra_img,50,255,0)
#3.轮廓提取
contours,hierarchy = cv2.findContours(canny,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
#4.轮廓绘制
img=cv2.drawContours(img,contours,-1,(255,0,0),3)
#显示
plt.imshow(img[:,:,::-1])
plt.xticks([]),plt.yticks([])
plt.show()
运行结果
轮廓的特征
轮廓面积是轮廓所包围的区域的面积,在OpenCV中使用的API是:
area=cv2.contourArea(cnt)
轮廓周长也被成为弧长,在OpenCV中使用的API是:
perimeter=cv2.arcLength(cnt,isclosed)
isclosed:闭合True,开放是False
轮廓近似是将轮廓形状近似为到另外一种由更少点组成的轮廓形状,新轮廓的点的数目由我们设定的准确度来决定。
import cv2
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
%matplotlib inline
# 1. 图像输入,转为二值图
img = cv2.imread('hhh.png')
if img is None:
print("Error: Could not read the image file.")
exit()
gra_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gra_img, 127, 255, cv2.THRESH_BINARY)
# 2. 查找轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
# 3. 绘制原始轮廓
img_contours = img.copy() # 创建一个副本,以便在原始图像上绘制轮廓
cv2.drawContours(img_contours, contours, -1, (0, 0, 255), 2)
# 显示原始轮廓
plt.imshow(img_contours[:,:,::-1]), plt.title('原始轮廓')
plt.show()
# 4. 轮廓处理(选择第一个轮廓作为示例)
if contours:
cnt = contours[0]
# 轮廓面积
area = cv2.contourArea(cnt)
print("轮廓面积:", area)
# 周长
L = cv2.arcLength(cnt, True)
print("轮廓周长:", L)
# 轮廓近似
epsilon = 0.1 * L
approx = cv2.approxPolyDP(cnt, epsilon, True)
# 绘制近似轮廓
img_approx = img.copy() # 创建一个副本,以便在原始图像上绘制近似轮廓
cv2.drawContours(img_approx, [approx], 0, (0, 255, 0), 2) # 使用绿色绘制近似轮廓
# 显示近似轮廓
plt.imshow(img_approx[:,:,::-1]), plt.title('轮廓近似结果')
plt.show()
else:
print("没有检测到轮廓。")
轮廓面积: 2.0
轮廓周长: 5.65685415267944