数字图像处理-图像分割方法

我这个人走得很慢,但是我从不后退。——亚伯拉罕·林肯

本文主要介绍图像分割方法中,基于阈值分割的方法;并通过实验进行分析。

图像分割方法

  1. 基于阈值分割方法
  2. 基于边缘分割方法
  3. 基于区域分割方法
  4. 基于特定理论分割方法
  5. 基于深度学习分割方法。

基于阈值处理的图像分割算法

灰度阈值基础

给定灰度图像,假设该图像有目标物体和背景像素所构成,现在要从图像中提取目标物体,一个最为直接的方法就是使用一个固定阈值将目标物体像素与背景像素分开,以区域为目标物体区域,否则为背景区域。

阈值的选择

  • 全局阈值
  • 自适应阈值
  • 最优化阈值

阈值直方图的特点

直方图呈现双峰特征:

  • 一个峰表示前景
  • 一个峰表示后景

全局阈值处理

当图像中存在较大的灰度变化时,使用全局阈值处理的方法一般就能够取得较好的效果。但我们仍希望对于一幅图像,能够找到一个相对合理的阈值来作为全局阈值。因而就有迭代的阈值图像分割算法。算法流程如下:

初始化全局阈值

基于分割该图像,产生两组像素:由灰度值大于的像素组成,由灰度值小于等于的像素组成

对和像素分别计算平均灰度值和

计算一个新的阈值

重复第2到第4步,直到连续迭代中的值间的差小于一个预定的参数为止。

下面来看一个使用全局阈值的图像分割例子。直接读入图像,先基于Numpy进行灰度化和二值化处理。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('25.png')
img1 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img1)
plt.show()

# 灰度化
y = 0.2126*img[:,:,2] + 0.7152*img[:,:,1] + 0.0722*img[:,:,0]
img[:,:,0] = y
img[:,:,1] = y
img[:,:,2] = y
# 以128为阈值进行二值化
y[y>=128] = 255
y[y<128] = 0
img[:,:,0] = y
img[:,:,1] = y
img[:,:,2] = y
plt.imshow(img);

效果

原图
分割效果图

opencv也提供了全局阈值的分割方法,处理代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import cv2
import numpy as np
from matplotlib import pyplot as plt

img1 = cv2.imread('25.png')
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
plt.imshow(img1)
plt.show()

img = cv2.imread('25.png',0)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
ret, thresh1 = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY)
plt.imshow(img)
plt.show()

plt.imshow(thresh1);

效果

原图

灰度图
分割后的效果图

可以看到:小矮人--艾泽--的胡子几乎不被标记出来。

一是因为他的胡子覆盖了整个脸部,导致没有明显的边缘信息;

我们说边缘,其实就是像素值变化大的地方。

阈值法的核心就是选择阈值,来很好的标记边缘。

因此我们可以看到阈值法的优点:快、简单

但是缺点也很明显,不够聪明、灵活,当出现背景颜色变化不明显的时候,可能不能很好的分割边缘。

大津法(OTSU)

基于阈值的图像二值化方法的一个关键在于如何选定阈值,这可以视作为一个全局寻优问题。大津法也即OTSU法,是由日本学者大津展之于1979年提出的一种图像阈值分割方法。该方法将阈值划分视作是一个统计决策问题,其目的在于将像素分配给两组或多组的过程中使得引入的平均误差最小。大津法给出的方案是使得两组之间的类间方差最大时的阈值为最优阈值。所以大津法也叫最大类间方差法。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import cv2
import numpy as np
from matplotlib import pyplot as plt

OTSU = cv2.imread('25.png')
# img = img.astype(np.float)
H, W, C = OTSU.shape
# 灰度化
out = 0.2126*OTSU[:,:,2] + 0.7152*OTSU[:,:,1] + 0.0722*OTSU[:,:,0]
out = out.astype(np.uint8)
# 初始化类间方差和最佳阈值
max_sigma = 0
max_t = 0

# 遍历迭代
for _t in range(1, 255):
# 小于阈值t的类v0
v0 = out[np.where(out<_t)]
# 计算v0均值
M0 = np.mean(v0) if len(v0) > 0 else 0.
# v0类像素占比
w0 = len(v0)/(H*W)
# 大于阈值t的类v1
v1 = out[np.where(out>=_t)]
# 计算v1均值
M1 = np.mean(v1) if len(v1) > 0 else 0.
# v1类像素占比
w1 = len(v1)/(H*W)
# 类间方差
Sb2 = w0*w1*((M0-M1)**2)
# 寻优
if Sb2 > max_sigma:
max_sigma = Sb2
max_t = _t

# 打印最佳阈值
print(max_t)

输出

108

最佳阈值是108,带入进行二值化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2
import matplotlib.pyplot as plt
OTSU = cv2.imread('25.png')
# 灰度化
y = 0.2126*OTSU[:,:,2] + 0.7152*OTSU[:,:,1] + 0.0722*OTSU[:,:,0]
img[:,:,0] = y
img[:,:,1] = y
img[:,:,2] = y
# 以108为阈值进行二值化
y[y>=108] = 255
y[y<108] = 0
OTSU[:,:,0] = y
OTSU[:,:,1] = y
OTSU[:,:,2] = y
plt.imshow(OTSU);

效果:

效果图

opencv中直接提供了大津法的实现方法,如下代码所示。

1
2
3
4
5
6
img = cv2.imread('25.png')
# 灰度化
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 大津法阈值化处理
ret, th = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
plt.imshow(th);

效果图

对比试验

1
2
3
4
5
6
7
8
9
# 原图
img1 = cv2.imread('25.png')
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
plt.imshow(img1)
plt.show()
# 对比全局阈值与大津法
plt.imshow(thresh1)
plt.show()
plt.imshow(OTSU);

原图

全局阈值效果图
大津法效果图

实验分析

可以看到,大津法对于艾泽的帽子,处理的很好。脸颊胡子边缘也处理得当。

实验小结

基于阈值的图像分割方法简单直接,计算速度快,在图像灰度差异较大的情况下,是首选的分割方法。

但阈值分割方法本身也存在抗噪能力弱、使用条件严格等缺点,所以往往会配合图像滤波去噪等方法一起使用。

BackgroundSubtractorMOG

这是一个基于混合高斯模型的背景前景分割算法。来自论文:An improved adaptive background mixture model for real-time tracking with shadow detection。

它使用的方法是对每个背景像素由k个混合高斯模型进行建模,k通常为3或5。彩色信息在场景中存在时间的比例作为高斯混合模型的权重大小。最有可能的背景颜色信息是停留时间最长且更为静止的。

编写代码的时候需要使用一个函数来创建的一个背景对象cv2.createBackgroundSubtractorMOG()。

这里有一些可选参数,如历史时间(length of history),高斯模型的数量,阈值等等。这些值全部被设置为缺省。

获取背景后,进入视频循环,使用函数backgroundsubtractor.apply() 来获取前景的蒙板。

函数原型:

retval = cv.bgsegm.createBackgroundSubtractorMOG( [, history[, nmixtures[, backgroundRatio[, noiseSigma]]]] )

参数:

history Length of the history.

nmixtures Number of Gaussian mixtures.

backgroundRatio Background ratio.

noiseSigma Noise strength (standard deviation of the brightness or each color channel). 0 means some automatic value.

代码(以弃用)

1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np
import cv2
cap = cv2.VideoCapture(0)
fgbg = cv2.createBackgroundSubtractorMOG()
while(1):
ret, frame = cap.read()
fgmask = fgbg.apply(frame)
cv2.imshow('frame',fgmask)
if k == 27:
break
cap.release()
cv2.destroyAllWindows()

cv.bgsegm.createBackgroundSubtractorMOG()

截至本文编辑发布,查阅发现该方法以弃用【3】;

BackgroundSubtractorMOG2

这个也是以高斯混合模型为基础的背景/前景分割算法。它是以 2004 年 和 2006 年 Z.Zivkovic 的两篇文章为基础的。 这个算法的一个特点是它为每一个像素选择一个合适数目的高斯分布。(上一个方法中我们使用是 K 高斯分布)。

这样就会对由于亮度等发生变化引起的场景变化产生更好的适应。

和前面一样我们需要创建一个背景对象。但在这里我们我们可以选择是否检测阴影。如果 detectShadows = True(默认值),它就会检测并将影子标记出来,但是这样做会降低处理速度。影子会被标记为灰色。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np
import cv2

cap = cv2.VideoCapture(0)

fgbg = cv2.createBackgroundSubtractorMOG2()

while(1):
ret, frame = cap.read()

fgmask = fgbg.apply(frame)

cv2.imshow('org',frame)
cv2.imshow('frame',fgmask)
k = cv2.waitKey(30) & 0xff
if k == 27:
break

cap.release()
cv2.destroyAllWindows()

实验结果

实验视频

该方法对亮度变换非常敏感!

总结

本文对图像分割方法中的基于阈值分割方法进行代码复现,并参考资料进行其他方法的比较实验。实验效果显示完美。

参考链接:

【1】https://blog.csdn.net/weixin_37737254/article/details/120610103

【2】https://blog.csdn.net/tengfei461807914/article/details/81588808

【3】https://blog.csdn.net/u014737138/article/details/80389977

【4】zhihulianjie


数字图像处理-图像分割方法
https://yelelalearn.github.io/2024/04/21/4-21blog/
作者
Yelearn
发布于
2024年4月21日
更新于
2024年4月21日
许可协议