Python 在科研中的应用 09:Python 环境下的数字图像分割
数字图像分割是计算机视觉中的一项基础且关键的任务,其目的是将图像划分成若干个互不相交的区域,使得这些区域内部的像素在某种特性上表现出一致性或相似性,而区域间的像素则表现出明显的差异。
图像直方图
计算图像的灰度直方图
在Python中,统计灰度图像的灰度直方图并绘制,通常可以使用matplotlib库来绘制直方图,以及使用numpy库来处理图像数据。此外,PIL(Python Imaging Library)或其分支Pillow库可以用来读取图像文件。
<img src=”https://s21.ax1x.com/2024/05/14/pkmBI4s.jpg“ width = 50% div align=center / title=”直方图均衡化演示图像,请将此图存储为’your_image_path.jpg’。”>
以下是一个完整的示例,展示如何使用这些库来统计灰度图像的直方图并绘制:
1 | import numpy as np |
- 使用
Pillow
的Image.open()
函数打开图像文件,并使用convert('L')
确保图像是灰度格式。 - 使用
numpy
的array()
函数将图像转换为一个二维数组,数组中的每个元素代表一个像素的灰度值。 - 使用
numpy
的histogram()
函数来计算图像的灰度直方图。bins=256表示将灰度范围(0到255)分成256个等大小的箱子,range=(0, 255)定义了直方图的范围。 - 使用
matplotlib.pyplot
的plot()
函数绘制直方图,并通过figure()
设置图形的大小,xlim()
设置x轴的范围。
图像直方图均衡化
直方图均衡化是图像处理领域中利用图像直方图对对比度进行调整的方法。这种方法通常用来增加许多图像的全局对比度,尤其是当图像的有用数据的对比度相当接近的时候。通过这种方法,亮度可以更好地在直方图上分布。这样就可以用于增强局部的对比度而不影响整体的对比度,直方图均衡化通过有效地扩展常用的亮度来实现这种功能。这种方法对于背景和前景都太亮或者太暗的图像非常有用,这种方法尤其是可以带来X光图像中更好的骨骼结构显示以及曝光过度或者曝光不足照片中更好的细节。这种方法的一个主要优势是它是一个相当直观的技术并且是可逆操作,如果已知均衡化函数,那么就可以恢复原始的直方图,并且计算量也不大。这种方法的一个缺点是它对处理的数据不加选择,它可能会增加背景噪声的对比度并且降低有用信号的对比度。
考虑一个离散的灰度图像${x}$,让 $n_i$ 表示灰度 $i$ 出现的次数,这样图像中灰度为 $i$ 的像素的出现概率是:
<img src=”https://wikimedia.org/api/rest_v1/media/math/render/svg/2085ca8d9ae45213103bff0b9c786ca717e55bba“ div align=center / >
$L$是图像中所有的灰度数(通常为256),$n$是图像中所有的像素数,$p_x(i)$实际上是像素值为$i$的图像的直方图,归一化到$[0,1]$。把对应于 $p_x$ 的累积分布函数,定义为:
<img src=”https://wikimedia.org/api/rest_v1/media/math/render/svg/4a4c8dccf53825085974fc78c1612f30b8410bf9“ div align=center / >
是图像的累计归一化直方图。
我们创建一个形式为 $y = T(x)$ 的变换,对于原始图像中的每个值它就产生一个$y$,这样$y$的累计概率函数就可以在所有值范围内进行线性化,转换公式定义为:
<img src=”https://wikimedia.org/api/rest_v1/media/math/render/svg/1786f939592d82e19d416f548c1e3e23ea5302c8“ div align=center / >
对于常数K。CDF的性质允许我们做这样的变换(参见逆分布函数);定义为
<img src=”https://wikimedia.org/api/rest_v1/media/math/render/svg/9322f34cc135a86f8f58fc0d563be67aa11d685f“ div align=center / >
其中 k 属于区间 [0,L)。注意 T 将不同的等级映射到$0..1$域,为了将这些值映射回它们最初的域,需要在结果上应用下面的简单变换:
<img src=”https://wikimedia.org/api/rest_v1/media/math/render/svg/19d4b57f5ee70a919eeafad1c8b8019d89ce77ab“ div align=center / >
上面描述了灰度图像上使用直方图均衡化的方法,但是通过将这种方法分别用于图像RGB颜色值的红色、绿色和蓝色分量,从而也可以对彩色图像进行处理。
1 | import cv2 |
- 使用
cv2.imread()
函数以灰度模式读取图像文件。如果图像未正确加载,将打印错误消息。 - 使用
cv2.equalizeHist()
函数对图像进行直方图均衡化处理。 - 使用
matplotlib.pyplot
的subplot()
和imshow()
函数显示原始图像和均衡化后的图像。 - 请确保将
image_path
变量的值替换为你的灰度图像文件的路径。
如果你还没有安装OpenCV库,可以使用pip进行安装:
1 | pip install opencv-python |
直方图均衡化对于改善低对比度图像的质量非常有效,它通过拉伸直方图分布到整个范围,从而增加图像的全局对比度。
<img src=”https://s21.ax1x.com/2024/05/13/pkm3Pw6.jpg“ width = 75% div align=center / title=”直方图均衡化的效果示例。”>
图像分割(Image Segementation)
边缘检测
边缘检测是图像处理中的一项关键技术,用于识别图像中亮度变化最显著的区域,这些区域通常对应于物体的边界。边缘是图像的重要特征,可以用于后续的图像分割、对象识别和图像理解等多种应用。
以下是一些经典的边缘检测算法:
Sobel算子:通过计算图像中每个像素点的上下和左右邻居的灰度值差异来得出边缘强度。Sobel算子包括两组3x3的卷积核,分别用于检测水平和垂直边缘。
Prewitt算子:类似于Sobel算子,但是卷积核的系数都是1,这使得Prewitt算子对边缘的响应更加“柔和”。
Roberts算子:是一种简单的2x2卷积核,用于检测边缘。Roberts算子通过检测对角线方向的灰度变化来识别边缘。
Canny边缘检测:这是一个多阶段算法,包括噪声降低、梯度计算、非极大值抑制和滞后阈值。Canny算法被认为是最可靠的边缘检测技术之一。
Laplacian算子:是一个二阶导数算子,用于增强图像的边缘。它对图像进行卷积,以突出图像灰度变化的局部极大值和极小值点。
Marr-Hildreth算子或Gaussian of Difference of Gaussians (DoG):这些算子使用高斯函数和它们的差分来检测边缘。
Sobel算子
obel算子是一种用于边缘检测的离散微分算子,它结合了高斯平滑和微分求导。该算子用于计算图像明暗程度近似值,根据图像边缘旁边明暗程度把该区域内超过某个数的特定点记为边缘。Sobel算子在Prewitt算子的基础上增加了权重的概念,认为相邻点的距离远近对当前像素点的影响是不同的,距离越近的像素点对应当前像素的影响越大,从而实现图像锐化并突出边缘轮廓。
Sobel算子的边缘定位更准确,常用于噪声较多、灰度渐变的图像。其算法模板如下式所示,其中$G_x$表示水平方向,$G_y$表示垂直方向。
<img src=”https://s21.ax1x.com/2024/05/14/pkmRnjs.jpg“ width = 40% div align=center / >
<img src=”https://s21.ax1x.com/2024/05/14/pkmRmcj.jpg“ width = 20% div align=center / >
在Python中,可以使用OpenCV库中的cv2.Sobel
函数来应用Sobel算子。
Canny 边缘检测
Canny算法是一种著名的边缘检测技术,由John F. Canny在1986年提出。它是一个多阶段算法,旨在从图像中准确地检测出边缘,同时保持边缘的精确定位。Canny算法的基本步骤包括:首先使用高斯滤波器对图像进行平滑以减少噪声;然后计算图像的梯度幅度和方向;接着应用非极大值抑制步骤来细化边缘,只保留局部最大值;最后,通过滞后阈值方法确定和连接边缘像素,这通常涉及两个阈值:高阈值用于检测强边缘,低阈值用于连接这些强边缘以形成完整的边缘。Canny算法因其出色的性能和对边缘的精确识别而广泛应用于计算机视觉和图像处理领域。
<img src=”https://s21.ax1x.com/2024/05/14/pkmRr4O.jpg“ width = 40% div align=center / title=”边缘检测、图像分割演示图像,请将此图存储为’your_iamge_path_02.jpg’。”>
下面是一个使用OpenCV实现Canny边缘检测的示例代码:
1 | import cv2 |
- 使用
cv2.imread()
函数以灰度模式读取图像文件。 - 使用
cv2.Canny()
函数进行Canny边缘检测。这个函数接受三个参数:输入图像和两个阈值,分别用于连接边缘和检测强边缘。 - 使用
matplotlib.pyplot
的subplot()
和imshow()
函数显示原始图像和边缘检测后的图像。
阈值分割
阈值分割是一种基于像素值的图像处理技术,用于将图像的前景和背景分离。该方法通过设定一个或多个阈值,将像素根据其灰度值分配到不同的类别中。单阈值分割使用一个固定值将图像分为两部分,而多阈值分割则可以用于更细致地将图像分为多个区域。阈值分割简单、快速,适用于目标和背景在灰度上具有明显差异的场景。
以下是使用Python和OpenCV库进行简单阈值分割的示例代码:
1 | import cv2 |
- 使用
cv2.imread()
函数以灰度模式读取图像文件。 - 使用
cv2.threshold()
函数进行阈值分割,其中threshold_value
是设定的阈值,max_value
是二值化图像中前景的灰度值(通常设置为255表示白色)。 - 使用
matplotlib.pyplot
的subplot()
和imshow()
函数显示原始图像和阈值分割后的图像。
Top-hat 变换
Top-hat 变换是一种形态学图像处理技术,用于突出图像中相对于背景的较小目标或细节。Top-hat变换的结果是一个图像,其中包含了原始图像与背景(通过开运算处理的图像)之间的差异。这种变换通常用于分离图像中的小物体或特征,这些物体或特征在背景中可能不那么明显。
以下是使用Python和OpenCV库进行Top-hat分割以提取小目标的示例代码:
1 | import cv2 |
- 使用
cv2.getStructuringElement()
定义用于形态学操作的卷积核。 - 使用
cv2.morphologyEx()
函数进行Top-hat变换,其中cv2.MORPH_TOPHAT
指定了Top-hat操作。 - 使用
matplotlib.pyplot的subplot()
和imshow()
函数显示原始图像和Top-hat变换结果。
膨胀与腐蚀
图像膨胀与腐蚀是两种基本的形态学图像处理技术,用于修改图像中物体的轮廓和结构。膨胀操作通过增加物体边界像素来“膨胀”或增厚图像中的前景对象,而腐蚀操作则通过移除物体边界像素来“腐蚀”或减薄前景对象。这两种技术可以用于分离相邻对象、平滑轮廓、填充小孔以及从背景中分离出前景对象。
以下是使用Python和OpenCV库进行膨胀与腐蚀操作的示例代码:
1 | import cv2 |
- 使用
cv2.getStructuringElement()
函数定义一个卷积核,用于膨胀和腐蚀操作。 - 使用
cv2.dilate()
函数对图像进行膨胀操作,iterations
=1表示膨胀操作应用的次数。 - 使用
cv2.erode()
函数对图像进行腐蚀操作,同样可以指定腐蚀的次数。 - 使用
matplotlib.pyplot
的imshow()
函数显示原始图像、膨胀后的图像和腐蚀后的图像。
膨胀和腐蚀是形态学图像处理中非常有用的工具,它们可以单独使用,也可以结合使用,或者与其他图像处理技术结合使用,以实现更复杂的图像分析任务。
开运算与闭运算
开运算和闭运算是两种用于图像处理的形态学操作。开运算首先进行腐蚀操作,然后是对腐蚀结果进行膨胀,目的是移除小的物体或细节(如噪声),并断开接近的物体。闭运算先进行膨胀操作,然后是腐蚀,目的是填充小的空洞和缝隙,以及平滑较大物体的边界。
以下是使用Python和OpenCV库进行开运算与闭运算的示例代码:
1 | import cv2 |
- 使用
cv2.getStructuringElement()
定义卷积核。 - 使用
cv2.morphologyEx()
函数进行开运算和闭运算,其中cv2.MORPH_OPEN和cv2.MORPH_CLOSE
分别指定了开运算和闭运算。 - 使用
matplotlib.pyplot
的subplot()
和imshow()
函数显示原始图像、开运算结果和闭运算结果。
Watershed 算法
Watershed算法是一种基于图像分割的技术,用于在灰度图中区分重叠的物体或不同的区域。它模拟了水文学中的分水岭概念,将图像的灰度值视为地形高度,而将局部最小值(如空洞或凹陷)视为集水盆。
在图像处理中,Watershed算法通常用于将接触或重叠的对象分开,特别适用于分离那些在传统阈值分割中难以区分的相邻对象。算法首先标记图像中的已知区域(这些区域可以是用户定义的或通过其他分割技术获得的),然后模拟水流,让“水”从高到低流入这些区域,直到它们相遇或被边界阻挡。相遇的点形成了算法所说的“分水岭”。
<img src=”https://s21.ax1x.com/2024/05/14/pkmhpjJ.jpg“ width = 25% div align=center / title=”watershed分割演示图像,请将此图存储为’water_coins.jpg’。”>
下面我们将看到一个关于如何使用距离变换和分水岭分割相互接触的对象的示例。考虑下面的硬币图片,硬币互相接触。即使你启动它,它也会相互接触。我们从找到硬币的大致估计数开始。为此,我们可以使用Otsu的二值化。
1 | import numpy as np |
现在我们需要去除图像中的任何小的白噪声。为此,我们可以使用形态开口。要去除物体上的任何小孔,我们可以使用形态闭合。所以,现在我们可以确定靠近物体中心的区域是前景,而远离物体的区域是背景。唯一我们不确定的区域是硬币的边界区域。
所以我们需要提取出我们确定它们是硬币的区域。侵蚀移除边界像素。所以不管剩下什么,我们都可以确定是硬币。如果物体不互相接触,那就行了。但由于它们彼此接触,另一个好的选择是找到距离变换并应用适当的阈值。下一步我们需要找到我们确信它们不是硬币的地方。为此,我们扩大了结果。膨胀增加对象边界到背景。这样,我们就可以确定结果中背景中的任何区域都是真实的背景,因为边界区域被移除了。见下图。
剩下的区域是那些我们不知道的区域,无论是硬币还是背景。分水岭算法应该找到它。这些区域通常围绕着硬币的边界,前景和背景相交(甚至两个不同的硬币相交)。我们称之为边界。它可以从sure-bg区域减去sure-fg区域得到。
1 | # noise removal |
1 | # Finding sure foreground area |
看看结果。在阈值图像中,我们得到一些区域的硬币,我们确定的硬币,他们现在分离。(在某些情况下,您可能只对前景分割感兴趣,而不是分离相互接触的对象。在这种情况下,你不需要使用距离变换,只要侵蚀就足够了。侵蚀只是另一种提取前景区域的方法,仅此而已。)
现在我们可以确定哪些是硬币的区域,哪些是背景和全部。因此,我们创建了marker(它是一个与原始图像大小相同的数组,但具有int32数据类型)并标记其中的区域。我们确定的区域(无论是前景还是背景)被标记为任何正整数,但是不同的整数,我们不确定的区域被保留为零。我们用这个 连接组件. 它用0标记图像的背景,然后用从1开始的整数标记其他对象。
但我们知道,如果背景标记为0,流域会将其视为未知区域。所以我们想用不同的整数来标记它。相反,我们将标记未知区域,定义为 unknown
,使用0。
1 | # Marker labelling |
请参见JET colormap中显示的结果。深蓝色区域显示未知区域。当然硬币有不同的颜色。与未知区域相比,确定背景显示为浅蓝色的剩余区域。
现在我们的标记准备好了。现在是最后一步,应用分水岭。然后标记图像将被修改。边界区域将标记为-1。
1 | markers = cv2.watershed(img,markers) |
请注意,分水岭算法的结果是一个标记图像,其中每个连通区域被赋予了一个唯一的标记值。通常,这个标记图像可以转换为二值图像以可视化分割结果。
数据拟合
数据拟合是数据分析和科学计算中的一个重要环节,它涉及到使用数学模型来模拟或预测数据。Python是一个功能强大的编程语言,它提供了许多库来帮助我们进行数据拟合,其中最常用的是NumPy和SciPy库。
线性拟合、多项式拟合
多项式拟合是使用多项式函数来拟合数据的一种方法。在Python中,我们可以使用numpy库中的polyfit函数或者scipy库中的polyval函数来进行多项式拟合。下面是一个使用numpy进行多项式拟合的示例:
1 | import numpy as np |
在这个示例中,np.polyfit
函数用于计算最佳拟合多项式的系数。参数3表示我们希望得到的是3次多项式(即包含x^3,x^2,x和常数项的多项式)。np.poly1d
函数用于根据拟合得到的系数创建一个多项式函数对象,这个对象可以用来计算任意x值的多项式函数值。
最后,我们使用matplotlib库绘制了原始数据点和拟合的多项式曲线。通过调整多项式的阶数,我们可以得到与数据拟合程度不同的多项式曲线。
非线性拟合
指数函数拟合是一种常用的非线性拟合方法,特别适用于数据随时间或空间指数增长或衰减的情况。在Python中,我们可以使用scipy.optimize.curve_fit函数来实现指数函数的拟合。
下面是一个使用指数函数拟合数据的示例:
1 | import numpy as np |
我们首先定义了一个指数函数模型exponential_func,它具有三个参数a、b和c。
然后,我们生成了一些模拟的指数增长数据,并添加了一些正态分布的噪声。
使用curve_fit函数进行指数函数拟合。这个函数将我们的指数函数模型和数据作为输入,并返回最佳拟合参数。
我们打印出拟合得到的参数,并使用这些参数生成了拟合的y值。
最后,我们使用matplotlib库绘制了原始数据点和拟合的指数曲线。
请注意,指数函数拟合的准确性很大程度上取决于数据的质量和模型的选择。在实际应用中,你可能需要尝试不同的模型和参数来找到最佳的拟合结果。