图像分割(Image Segmentation)是数字图像处理中最核心、最具挑战性的任务之一,其目标是将图像划分为具有一致特征的区域,从而实现对图像中目标或结构的提取、理解与分析。
下面我将从概念、分类、经典方法、实际应用四个层面为你系统性地讲解图像分割。
定义:图像分割是指将图像划分为若干个互不重叠的区域,使得每个区域内部具有某种一致性(如灰度、纹理、颜色、边缘等),而不同区域之间具有明显差异。
举个例子:
对一张风景图像,分割可能将“天空”、“山”、“湖”划分为不同的区域。
在医学图像中,可以将“肿瘤”和“正常组织”分割出来。
类别 | 方法 | 特点 |
---|---|---|
1. 基于灰度 | 阈值分割、Otsu、双阈值 | 简单快速,适合二值图像或对比明显图像 |
2. 基于边缘 | Sobel、Canny、Laplacian、边缘跟踪 | 检测边缘作为分割界限 |
3. 基于区域 | 区域生长、区域合并、分裂合并 | 根据像素相似性扩展或合并区域 |
4. 基于图论 | 图割(Graph Cut)、Normalized Cut | 处理复杂结构,适合图结构表示 |
5. 基于聚类 | K-means、MeanShift | 无监督学习思想 |
6. 基于能量函数 | 活动轮廓(Snake)、水平集 | 曲线演化、形状保持 |
7. 基于深度学习 | FCN、U-Net、Mask R-CNN | 语义分割的主流方法,精度高 |
采用阈值的方法确定图像分割的边界:方法是将整个图像灰度值的值设置为常数,也就是全局阈值。
全局阈值的选择方法有:人工选择法,直方图技术选择法,迭代式阈值选择法,最大类间方差法。
人工选择法:人眼观察,在分析直方图的基础上,人工选择适合的阈值。
直方图技术选择法:含有一个与背景明显对比的物体的图像其有包含双峰的灰度直方图
迭代式阈值选择法:开始时选择一个阈值作为初始值,然后按照某种策略不断改进。
最大类间方差法(OTSU大津法):以最佳阈值将图像的灰度直方图分割成两部分,是两部分之间的方差取最大值,即分离性最大。
具体来说,大津法通过以下几个步骤实现:
计算图像灰度直方图:统计图像中所有像素的灰度值分布,形成灰度直方图.
遍历所有可能的阈值:对于灰度直方图中的每一个灰度值,将其作为阈值.
**计算类间方差:**将图像根据当前阈值分成前景和背景两类,计算这两类之间的类间方差.
寻找最大类间方差:遍历所有阈值,找到使类间方差最大的阈值.
二值化图像:将图像根据最大类间方差对应的阈值进行二值化,即可得到黑白图像.
大津法的优点:
自动化:
无需手动设置阈值,能够根据图像的灰度分布自动确定最佳阈值.
不受图像亮度和对比度影响:
大津法基于灰度直方图,可以有效应对图像亮度变化和对比度差异.
计算效率高:
大津法是一种计算效率较高的阈值分割算法,适用于各种图像处理应用.
大津法的缺点:
对噪声敏感:在存在噪声的图像中,大津法可能导致错误的结果.
仅适用于单一目标分割:大津法主要用于将图像分割成前景和背景两部分,不适用于具有多个目标的图像分割.
当目标与背景面积差异较大时,效果较差:当图像中前景和背景的面积差异较大时,大津法可能不能很好地分割图像.
一下是用大津法的处理实例
import cv2
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 解决中文乱码
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 修改为你本地图片路径
image_path = r'D:\Desktop\small\test_images\test2.jpg'
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if img is None:
print("❌ 图像加载失败,请检查路径")
else:
print("✅ 图像加载成功,尺寸为:", img.shape)
plt.imshow(img, cmap='gray')
plt.title('原始灰度图像')
plt.axis('off')
plt.show()
blur = cv2.GaussianBlur(img, (5, 5), 0)
plt.imshow(blur, cmap='gray')
plt.title('高斯滤波去噪后图像')
plt.axis('off')
plt.show()
# 应用 Otsu 阈值分割
ret, otsu_thresh = cv2.threshold(
blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU
)
print(f" 大津法自动计算出的最佳阈值为:{ret:.2f}")
plt.imshow(otsu_thresh, cmap='gray')
plt.title('大津法阈值分割结果')
plt.axis('off')
plt.show()
分水岭算法是一种与自适应二值化有关的算法。
分水岭算法不是简单地将图像在最佳灰度级进行阈值处理,而是从一个偏低但仍然能正确分割各个物体的阈值开始。然后随着阈值逐渐上升到最佳值,使各个物体不会被合并。这个方法可以解决那些由于物体靠得太近而不能用全局阈值解决的问题。
分水岭算法的最初和最终的阈值灰度级都必须很好地选取。
一、 分水岭算法是什么?
形象理解:“图像 = 地形图”,灰度值 = 海拔高度
分水岭算法模拟下雨积水过程:
把图像看成地形图;
水从低谷(灰度低)向上漫延;
如果两片水域即将汇合,就筑坝;
坝的位置就是 分割边界。
最终目标:将图像中接触的区域(如粘连的硬币、细胞等)精确分开。
二、 算法流程(实际中通常配合距离变换和标记图)
图像预处理(灰度 + 二值化)
import cv2
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
img = cv2.imread(r'D:\Desktop\small\test_images\test2.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
plt.imshow(gray, cmap='gray')
plt.title("原始灰度图像")
plt.axis('off')
plt.show()
去噪 + 闭操作
import cv2
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
img = cv2.imread(r'D:\Desktop\small\test_images\test2.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
plt.imshow(gray, cmap='gray')
plt.title("原始灰度图像")
plt.axis('off')
plt.show()
提取前景和背景区域
sure_bg = cv2.dilate(opening, kernel, iterations=3)
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
plt.figure(figsize=(10,4))
plt.subplot(1,2,1), plt.imshow(sure_bg, cmap='gray'), plt.title("膨胀后的背景")
plt.axis('off')
plt.subplot(1,2,2), plt.imshow(sure_fg, cmap='gray'), plt.title("距离变换后的前景")
plt.axis('off')
plt.tight_layout()
plt.show()
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)
ret, markers = cv2.connectedComponents(sure_fg)
markers = markers + 1 # 确保背景为1
markers[unknown == 255] = 0 # 未知区域设为0
print(f"共检测到前景区域数量(不含背景):{ret - 1}")
输出边界,绘制结果
# 应用分水岭
markers = cv2.watershed(img, markers)
# 将边界(值为 -1)设置为红色(BGR)
img[markers == -1] = [0, 255, 0]
# 显示图像
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.title("分水岭算法分割结果(边界为绿色)")
plt.axis('off')
plt.show()