目录
一、基础知识
本章的多数分割算法都是基于灰度值的不连续性和相似性的,第一种是以灰度突变为基础分割一幅图像;第二种主要是根据一组预定义的准则将一幅图像分割为相似的区域。
分割中的基本问题就是把一幅图像分成满足如下4个条件的多个区域
- 必须是完全的
- 一个区域中的点以某些预定义的方式来连接
- 各个区域必须不相交
- 分割后的区域中的像素必须满足一些属性
二、点、线和边缘检测
边缘像素是图像中灰度突变的像素,边缘是连接的边缘像素的集合。一条线可以视为一个边缘线段,孤立点可以视为长度和宽度都为一个像素的线。
1.背景
可以使用一阶微分和二阶微分来检测局部的变化,有着以下结论:
- 一阶导数通常在图像中产生较粗的边缘
- 二阶导数对精细细节有较强的响应
- 二阶导数在灰度斜坡和灰度台阶过渡处会产生双边缘响应
- 二阶导数的符号可用于确定边缘的过渡是从亮到暗还是从暗到亮
2.孤立点的检测
是以二阶导数为基础的,使用拉普拉斯:
输出图像中把已被检测到的点标注为1,所有其他点标注为0,以此产生一幅二值图像。是否检测到通过判断该点在该模板的响应的绝对值是否超过一个指定的阈值。
3.线检测
它的复杂度更高,对于线检测,二阶导数将导致更强的响应,并产生比一阶导数更细的线。可以使用拉普拉斯模板,并且对于二阶导数的双线效应要做适当的处理。对于如下4个模板,第一个对水平方向的线有最佳响应;第二个对45°方向的线有最佳响应;第三个对垂直线有最佳响应;第四个对-45°方向的线有最佳响应。模板的系数之和为0表明恒定灰度区域中的响应为0。
如果对检测图像中由给定模板定义的方向上所有的线感兴趣只须简单的对该图像运行这个模板,并对结果的绝对值进行阈值处理。
4.边缘模型
边缘检测是基于灰度突变来分割图像最常用的方法。边缘模型根据灰度剖面来分类,分为台阶模型、斜坡模型和屋顶边缘模型,台阶边缘是指在1个像素的距离上发生两个灰度级间理想的过度。数字图像都存在被模糊且带有噪声的边缘,模糊的程度取决于聚焦机理中的限制,噪声水平取决于成像系统的电子元件。
屋顶边缘是通过一个区域的线的模型,其基底由线的宽度和尖锐度来决定。零灰度轴和二阶导数极值间的连线的交点叫做二阶导数的零交叉点。一阶导数的幅度可用于检测图像中的某个点处是否存在一个边缘;二阶导数的符号可用于确定一个边缘像素位于该边缘的暗的一侧还是亮的一侧。
5.基本边缘检测
为了达到寻找边缘的目的,检测灰度变化可用一阶或二阶导数来完成。梯度是用来在一幅图像f的(x,y)处寻找边缘的强度和方向的,使用来表示,其指出了f在(x,y)处的最大变化率方向。梯度向量有时也称为边缘法线,向量除以其幅值而归一化为单位长度时的结果向量称为边缘单位法线。
罗伯特交叉梯度算子是最早尝试使用具有对角优势的二维模板之一,其表示为
Prewitt算子,考虑了中心点对端数据的性质,携带有关边缘方向的更多信息
Sobel算子,可以更好的抑制噪声
在计算梯度前对图像进行平滑处理,边缘检测可做更多的选择,还可以使用对梯度图像进行阈值处理来实现相同的目标。如下是一个边缘检测算子的实验,其代码为:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread(r'D:\test\121.png')
b, g, r = cv2.split(img)
img2 = cv2.merge([r, g, b])
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(grayImage, 127, 255, cv2.THRESH_BINARY)
# Sobel算子
x = cv2.Sobel(grayImage, cv2.CV_16S, 1, 0)
y = cv2.Sobel(grayImage, cv2.CV_16S, 0, 1)
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
Sobel = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
# Roberts算子
kernelx = np.array([[-1, 0], [0, 1]], dtype=int)
kernely = np.array([[0, -1], [1, 0]], dtype=int)
x = cv2.filter2D(grayImage, cv2.CV_16S, kernelx)
y = cv2.filter2D(grayImage, cv2.CV_16S, kernely)
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
Roberts = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
# Prewitt算子
kernelx = np.array([[1, 1, 1], [0, 0, 0], [-1, -1, -1]], dtype=int)
kernely = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=int)
x = cv2.filter2D(grayImage, cv2.CV_16S, kernelx)
y = cv2.filter2D(grayImage, cv2.CV_16S, kernely)
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
Prewitt = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.subplot(231), plt.imshow(img2), plt.title('原始图像'), plt.axis('off')
plt.subplot(232), plt.imshow(binary, plt.cm.gray), plt.title('二值图'), plt.axis('off')
plt.subplot(233), plt.imshow(Sobel, plt.cm.gray), plt.title('Sobel算子'), plt.axis('off')
plt.subplot(234), plt.imshow(Roberts, plt.cm.gray), plt.title('Roberts算子'), plt.axis('off')
plt.subplot(235), plt.imshow(Prewitt, plt.cm.gray), plt.title('Prewitt算子'), plt.axis('off')
plt.show()
结果为:
该实验中,一共对本节所讲的三种算子进行了实验,通过对最终的结果进行对比可以看到罗伯特算子的边缘检测的效果最差,部分细节并没有得到展现。对于罗伯特算子先定义x,y方向的卷积核然后进行卷积运算,取绝对值后进行加权和得到的;对于Sobel算子使用了opencv库里面的sobel函数来计算灰度图像分别在x,y方向上的一阶导数,然后在取求完导之后的绝对值,最后在进行加权和即可得到最终的Sobel图像。对于Prewitt算子部分操作和罗伯特算子相同不过卷积核采用的是3*3的。所以Prewitt算子最终得到的效果会比罗伯特算子的更好。
6.先进的边缘检测技术
这些技术考虑了诸如图像噪声和边缘本身特性的因素改进简单的边缘检测算法。
Marr-Hildreth边缘检测器是最早的成功的将更高级的分析结合到边缘检测处理之一的,它们证明了1.灰度变化与图像尺寸无关,要求使用不同尺寸的算子 2.灰度的突然变化会在一阶导数中引起波峰或波谷,或在二阶导数中等效的引起零交叉。最令人满意的算子是,
为拉普拉斯算子,G是标准差的二维高斯函数:
。最终经过整合可以得到高斯拉普拉斯表达式(LoG):
LoG有时也被叫做墨西哥草帽算子。任何尺寸的模板可以通过对上式取样并标定系数以使系数之和为零来生成。生成LoG滤波器还可通过以希望的n*n尺寸对二维高斯函数进行取样,然后将结果阵列与一个拉普拉斯模板进行卷积。
Marr-Hildreth算法由LoG滤波器与一幅输入图像进行卷积组成,可以先使用一个高斯滤波器平滑图像,然后计算该结果的拉普拉斯。Marr-Hildreth边缘检测算法的关键特征是零交叉。其步骤为:
- 用一个n*n的高斯低通滤波器对输入图像滤波
- 计算所的图像的拉普拉斯
- 找到所得图像的零交叉
下面为一个实现Marr-Hildreth边缘检测的实验的结果,其代码为:
import cv2
import numpy as np
# 读取图像并转换为灰度图
image = cv2.imread(r'D:\test\kanni.png', cv2.IMREAD_GRAYSCALE)
blurred = cv2.GaussianBlur(image, (5, 5), 0)
laplacian_kernel = np.array([[0, 1, 0],
[1, -4, 1],
[0, 1, 0]], dtype=np.float32)
laplacian = cv2.filter2D(blurred, -1, laplacian_kernel)
_, edges = cv2.threshold(laplacian, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
cv2.imshow('Original Image', image)
cv2.imshow('Marr-Hildreth Edges', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
其中使用了一个阈值来模拟实际使用中的零交叉点,因为零交叉点的检测需要更加复杂的算法。
坎尼边缘检测器是讨论过的边缘检测器中最优秀的,其目标为:1.低错误率 2.边缘点应被很好的定位 3.单一的边缘点响应。其基本步骤为:
- 用一个高斯滤波器平滑输入图像
- 计算梯度幅值图像和角度图像
- 对梯度幅值图像应用非最大抑制
- 用双阈值处理和连接分析来检测并连接边缘
下面为一个坎尼边缘检测的结果,其代码为:
import cv2
import numpy as np
# 读取图像
image = cv2.imread(r'D:\test\kanni.png', cv2.IMREAD_GRAYSCALE)
blurred = cv2.GaussianBlur(image, (5, 5), 0)
edges = cv2.Canny(blurred, 50, 150)
cv2.imshow('Original Image', image)
cv2.imshow('Canny Edges', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
其中设置的两个阈值分别为50和150,一个负责滞后阈值处理,一个负责边缘连接。可以看到相较于前面所讲的Marr-Hildreth边缘检测算法而言,坎尼算法检测所得的结果的边缘连续性更好,并且不存在影响观察的信息。
一般会在边缘检测后跟着连接算法,这个算法将边缘像素组合成有意义的边缘或区域边界有三种基本的边缘连接算法,第一种需要有关局部区域中边缘点的知识,第二种要求区域边界上的点已知,第三章是处理整个边缘图像的全局方法。
连接边缘点的最简单的方法是在每个点(x,y)处的一个小邻域内分析像素的特点。这个方法主要使用梯度向量的强度和梯度向量的方向来确定边缘像素的相似性。必须要指定两个初始点才能进行多边形拟合,其次所有的点必须排序。对于初始点的选择有两种方法,一为选择点集中最右边的点和最左边的点,二为寻找曲线的极端点。寻找一个多边形拟合开放和闭合曲线的算法为:
- 令P为一个已排序序列,指定两个初始点A和B。
- 指定一个阈值T和两个空堆栈“开”和“闭”。
- 如果P中的点对应于一个闭合曲线,就把A放到“开”,B放到“开”和“闭”之间;如果点对应于一个开发曲线,就把A放到“开”,B放到“闭”。
- 计算从“闭”中最后一个顶点到“开”中最后一个顶点的线的参数。
- 计算4所得直线到P中所有点的距离,序列放到4所得两个顶点间,最大距离
和
- 如果
,就把
放到堆栈末端,转到4;否则就从“开”中移除最后一个顶点,并作为“闭”的最后一个顶点插入。
- 如果“开”非空转到4;否则退出。
霍夫(Hough)变换的原理为考虑xy平面上的点和斜截式形式为
的一条直线,通过点
的直线有无数条,且对a和b的不同值都满足上述斜截式形式。若将等式写为
的形式,并且考虑ab平面,可以得到固定点
的单一直线的方程。此外,第二个点
在参数空间中也有一条对应的直线,当它们不平行时会交于点
,
为斜率,
为包含xy平面中点
和
的直线的截距。其平面图如下:
但是这样子会导致当直线接近垂直时,斜率会无限大,因此可以使用一条直线的法线来表示。即如下图所示:
霍夫变换在计算上可以将参数空间划分为累加单元,
表示直线到原点的垂直距离,范围是
;
表示x轴到直线垂直线的距离,范围为
,D是图像中对角之间的最大距离。霍夫变换的复杂性取决于给定函数表达式中的坐标和系数数量。基于霍夫变换的连接方法为:
- 先使用前面的技术得到一幅二值图像
- 指定
平面的细分
- 对像素高度集中的地方检验其累加单元的数量
- 检验选中单元中像素间的关系
三、阈值处理
1.基本知识
假定灰度直方图上的图像f(x,y)由暗色背景上的较亮物体组成,那么物体像素和背景像素所具有的灰度值组成了两种支配模式。从背景中提取物体的一种方法是选择一个阈值T,将的点叫做对象点,否则就是背景点。当T是适用于整个图像的常数时,叫做全局阈值处理,T值在一幅图像上改变时就是可变阈值处理。也可叫做局部阈值处理或区域阈值处理。如果T取决于空间坐标本身,那么将可变阈值处理称为动态阈值处理或自适应阈值处理。下图为单阈值和双阈值处理的直方图:
双阈值分隔包含三个支配模式,对应于暗色背景上的两个明亮物体,分割模式为下式:
影响波谷特性的关键因素为:(1)波峰间的间隔 (2)图像中的噪声内容 (3)物体和背景的相对尺寸 (4)光源的均匀性 (5)图像反射特性的均匀性。
噪声、光照和反射也会对图像的阈值处理造成影响。
2.基本的全局阈值处理
当物体和背景像素的灰度分布十分明显时,可以使用适用于整个图像的全局阈值,可以使用如下的迭代算法来实现自动估计阈值。
- 为全局阈值T选择一个初始估计值
- 用T分割该图像,产生灰度值大于T的像素用
表示和灰度值小于T的像素用
表示
- 对
和
的像素分别计算平均灰度值
和
- 计算新的阈值为平均灰度值的平均值
- 重复步骤2-4直到T值间的差小于一个预定义的参数为止
其下为一个上述过程的实验,代码为:
import cv2
import numpy as np
import matplotlib.pyplot as plt
image = cv2.imread(r'D:\test\o.png', 0)
mean_gray = np.mean(image)
thresholded_image = cv2.threshold(image, mean_gray, 255, cv2.THRESH_BINARY)[1]
fig, ax = plt.subplots(1, 2, figsize=(12, 6))
ax[0].imshow(image, cmap='gray')
ax[0].set_title('Original Image')
ax[0].axis('off')
ax[1].imshow(thresholded_image, cmap='gray')
ax[1].set_title('Thresholded Image with Mean Gray Threshold')
ax[1].axis('off')
plt.tight_layout()
plt.show()
结果为:
3.Otsu最佳全局阈值处理
Ostu方法在类间方差最大的情况下是最佳的,基本概念是就它的灰度值而言给出最好的类间分类的阈值就是最好的。另一个特性是它完全以在一幅图像的直方图上执行计算为基础。直方图是很容易得到的一维阵列。
类间方差定义为。下面是采用Ostu方法进行的最佳的全局阈值处理实验,其代码为:
import cv2
import numpy as np
import matplotlib.pyplot as plt
image = cv2.imread(r'D:\test\o.png', 0)
ret, thresholded_image = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print(f"Otsu's threshold value: {ret}")
fig, ax = plt.subplots(1, 2, figsize=(12, 6))
ax[0].imshow(image, cmap='gray')
ax[0].set_title('Original Image')
ax[0].axis('off')
ax[1].imshow(thresholded_image, cmap='gray')
ax[1].set_title('Thresholded Image')
ax[1].axis('off')
plt.tight_layout()
plt.show()
其结果或在cmd界面输出最佳的阈值,并且阈值处理后的效果也会比基本的全局阈值处理要好。
4.全局阈值处理的改进
可以使用图像平滑来改善全局阈值处理,噪声会将一个简单的阈值处理问题变为一个不可解决的问题。当噪声不能再源头减少,选择的分割方法又是阈值处理时,那么可以在阈值处理前先平滑图像。
其次可以利用边缘来改进全局阈值处理。其步骤为:
- 计算一幅图像的梯度的幅度或者拉普拉斯的绝对值
- 指定一个阈值T
- 使用2中的T对1中的图像进行阈值处理,将产生一幅二值图像,再从原图像中选取对应于“强”边缘像素的下一步骤作为一幅模板图像
- 仅用原图像中对应于二值图像中像素值为1的位置的像素计算直方图
- 用4中的直方图全局分割原图像
5.多阈值处理
假定对于三个灰度间隔组成的三个类,类间方差为:
多个阈值的可分性度量可以有单个阈值定义扩展而来。
6.可变阈值处理
前面讲的平滑图像和边缘可以改进全局阈值处理来减少噪声的影响,其次也可以使用可变阈值处理的方法。
可变阈值处理最简单的方法就是把一幅图像分成不重叠的矩形,用于补偿光照和反射的不均匀性。
其次更一般一些的方法是在一幅图像中的每一点(x,y)计算阈值,该阈值是以一个或多个在(x,y)邻域计算的特性为基础的。令和
表示一幅图像中以坐标(x,y)为中心的邻域所包含的像素集合的标准差和均值,其通式为:
。分割后的图像将大于T的像素值设为1,小于T的设为0,对图像中的所有像素位置进行求值,并在每个点(x,y)处使用邻域中的像素计算不同的阈值。
其次还有一种特殊的情形是以一幅图像的扫描进行移动平均为基础的。为减少光照偏差扫描是以z字形模式逐行进行的。
7.多变量阈值处理
在一些情况下,传感器可以产生多个可利用的变量来表征图像中的每个像素,因此可以进行多变量阈值处理。RGB中每个像素有三个值来表征,并且可以表示为一个三维变量,这些三维点叫做体素,以便与图像元素相对应表示体积元素。
四、基于区域的分割
1.区域生长
区域生长是根据预先定义的生长准则将像素或子区域组合为更大的区域的过程。方法是从一组种子点开始,将与种子预先定义的性质相似的那些邻域像素添加到每个种子上来形成这些生长区域。相似性准则的选择不仅取决于所面对的问题,还取决于现有的图像数据的类型。区域生长过程中需要使用连通属性,不然单独的描述子会产生错误的结果。其次区域生长还存在终止规则的表示法的问题。
假定f(x,y)表示输入图像阵列,S(x,y)表示种子阵列,Q表示每个位置所用的属性,那么一个基于8连接的区域生长算法的流程为:
- 在S(x,y)中找到所有连通分量,并把每个连通分量腐蚀为一个像素;把找到的这种像素标记为1,其他标记为0。
- 在坐标对(x,y)处形成图像
,如果输入图像在该坐标处满足给定属性Q,那么
=1,否则为0。
- 令g是把
中为8连通种子点的所有1值点,添加到S中的每个种子点。
- 用不同的区域标记g中的每个连通分量,这就是形成的分割图像。
2.区域分裂和聚合
区域分裂和聚合是将一幅图像细分为一组任意的不相交区域,然后聚合和/或分裂这些区域,并力图满足分割的条件。其步骤为:
- 对满足
的任何区域
分裂为4个不相交的区域。
- 当不可能进一步分裂时,对满足条件
的任何两个邻接区域
和
进行聚合。
- 当无法进一步聚合时,停止操作。
五、以形态学分水岭的分割
其将前面讲的三种方法的许多概念进行了具体化,会产生一个更稳定的结果,包括连接的分割边界。
分水岭的概念是以三维形象化为基础的:两个空间坐标作为灰度的函数,如下图所示。在“地形学”的解释中考虑三种点,满足属于一个区域内最小值的点;把一个点看成水滴,把这些点放在任意位置,水滴一定会下落到一个单一的最小值点,满足该条件的集合叫做最小值的汇水盆地或者分水岭;在该点的水会等可能的流到不止一个这样的最小值点,满足该条件形成地表线的峰线叫做分割线或分水线。
形态学分水岭的分割主要目标是找到分割线,其主要目的是从背景中提取近乎一致的物体。
水坝的构造是以二值图像为基础的,构建分离二元点集的水坝最简单的方法是使用形态学膨胀。
直接使用分水岭分割算法会由于噪声和梯度的其他局部不规则性造成过度分割,一个解决其问题的方法是通过融入预处理步骤来限制允许存在的区域的数目。
用于控制过度分割的一种方法是基于标记的,一个标记是属于一幅图像的一个连通分量。与感兴趣物体相联系的标记叫做内部标记,与背景相关联的标记叫做外部标记。选择标记的步骤为:(1)预处理 (2)定义标记必须满足的一个准则集合。
六、分割中的运动
分别检测在时刻和
获取的两帧图像帧之间的最简方法是逐像素的比较这两幅图像。这将会形成一幅差值图像。假定有一幅仅包含固定分量的参考图像,将这幅图像和一幅后续的相同场景但包含一个运动物体的图像进行比较,去掉其中的固定元素,仅保留对应于非固定图像分量的非零项就可以得到两幅图像的差。
考虑一个图像帧序列,令第一个图像帧为参考图像,一幅累积差值图像(ADI)是通过将参考图像与序列中每幅后续图像进行比较形成的。累计差值图像有绝对ADI、正ADI和负ADI三种类型。ADI均从0值开始计数。
实际中,仅用固定元素得到一幅参考图像并不总是可行的,所以要根据一个或多个运动物体的图像来构建一幅参考图像。
在任意静止背景中涉及一个或多个运动物体的一般情况下,对应于静止图像分类的直流处傅里叶变换有一个波峰,且这些位置的波峰与物体的速度成正比。对于一个由K幅大小为M*N的数字图像序列,在任何整数的瞬时点上,图像投影到x轴和y轴上的加权和为:
对上述两式进行傅里叶变换,这些变换的计算是使用FFT算法执行的。
总结
本章主要讲解了图像分割相关的知识,首先就是图像分割任务所需要满足的条件以及图像分割的基本概念就是将图像划分为多个区域或提取感兴趣区域的过程。之后描述了实现图像分割的多种方法,主要为边缘检测、阈值处理、形态学分水岭和区域生长等方法。