【opencv】OpenCV 图像分割介绍及代码实现
图像分割是将图像划分为多个有意义的区域或对象的过程,是计算机视觉的核心任务之一
OpenCV 图像分割介绍
图像分割是将图像划分为多个有意义的区域或对象的过程,是计算机视觉的核心任务之一。OpenCV 提供了多种图像分割方法:
常用分割方法
- 阈值分割:基于像素灰度值进行分割(全局/自适应阈值)
- 边缘检测:通过检测图像边缘实现分割(Canny、Sobel等)
- 区域生长:基于相似性准则从种子点扩展区域
- 分水岭算法:用于分离相互接触的对象
- 聚类分割:K-Means、GrabCut 等基于聚类的算法
- 深度学习方法:结合深度学习模型(需集成其他库)
代码实现示例
1. 阈值分割
import cv2
import numpy as np
# 读取图像
img = cv2.imread('image.jpg', 0) # 灰度模式
# 全局阈值分割
_, thresh_global = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 自适应阈值分割
thresh_adaptive = cv2.adaptiveThreshold(
img, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2
)
cv2.imshow('Global Threshold', thresh_global)
cv2.imshow('Adaptive Threshold', thresh_adaptive)
cv2.waitKey(0)
2. 边缘检测分割
import cv2
import numpy as np
# 读取图像
img = cv2.imread('image.jpg', 0) # 灰度模式
# 边缘检测
edges = cv2.Canny(img, 100, 200) # 调整阈值控制边缘检测灵敏度
# 形态学操作优化边缘
kernel = np.ones((3,3), np.uint8)
dilated_edges = cv2.dilate(edges, kernel, iterations=1)
cv2.imshow('Canny Edges', edges)
cv2.imshow('Dilated Edges', dilated_edges)
cv2.waitKey(0)
3. K-Means 聚类分割
import cv2
import numpy as np
# 读取彩色图像
color_img = cv2.imread('image.jpg')
data = color_img.reshape((-1,3)).astype(np.float32)
# K-Means聚类
K = 3 # 分割簇数
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
_, labels, centers = cv2.kmeans(
data, K, None,
criteria, 10, cv2.KMEANS_RANDOM_CENTERS
)
# 重建分割图像
centers = np.uint8(centers)
segmented = centers[labels.flatten()]
segmented_img = segmented.reshape(color_img.shape)
cv2.imshow('K-Means Segmentation', segmented_img)
cv2.waitKey(0)
4. 分水岭算法
import cv2
import numpy as np
# 读取图像
img = cv2.imread('image.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 阈值处理获取前景
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# 去除噪声
kernel = np.ones((3,3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# 确定背景区域
sure_bg = cv2.dilate(opening, kernel, iterations=3)
# 确定前景区域
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
_, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
# 获取未知区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)
# 创建标记
_, markers = cv2.connectedComponents(sure_fg)
markers += 1
markers[unknown == 255] = 0
# 应用分水岭
markers = cv2.watershed(img, markers)
img[markers == -1] = [255, 0, 0] # 标记边界为红色
cv2.imshow('Watershed Segmentation', img)
cv2.waitKey(0)
5. GrabCut 交互式分割
img = cv2.imread('image.jpg')
mask = np.zeros(img.shape[:2], np.uint8)
# 初始化GrabCut所需数组
bgdModel = np.zeros((1,65), np.float64)
fgdModel = np.zeros((1,65), np.float64)
# 手动指定矩形区域 (x,y,w,h)
rect = (50, 50, 400, 300)
# GrabCut迭代
cv2.grabCut(img, mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
# 创建掩码
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
result = img * mask2[:, :, np.newaxis]
cv2.imshow('GrabCut Result', result)
cv2.waitKey(0)
使用建议
- 简单场景:优先尝试阈值分割或边缘检测
- 复杂背景:使用聚类方法或分水岭算法
- 精确提取:采用交互式GrabCut
- 性能要求:阈值法最快,分水岭和聚类计算量较大
- 参数调整:根据具体图像特性调整阈值/迭代次数等参数
注意:实际应用中常需组合多种方法(如边缘检测+形态学操作),并配合后处理优化结果。
K-Means 聚类分割详解
K-Means 是一种无监督聚类算法,通过将图像像素按颜色/纹理特征分组实现分割,特别适合基于颜色的图像分割任务。
核心原理
- 特征空间映射:将每个像素视为 N 维特征空间中的点(常用 RGB/Lab 颜色空间)
- 聚类中心:初始化 K 个聚类中心(簇心)
- 迭代优化:
- 分配步骤:将每个像素分配给最近的簇心
- 更新步骤:重新计算簇心位置(取簇内像素均值)
- 收敛:当簇心不再变化或达到最大迭代次数时停止
数学表示
最小化目标函数(簇内平方和):
J = ∑ i = 1 K ∑ x ∈ C i ∥ x − μ i ∥ 2 J = \sum_{i=1}^{K} \sum_{x \in C_i} \|x - \mu_i\|^2 J=i=1∑Kx∈Ci∑∥x−μi∥2
其中:
- K K K:预设的聚类数量
- C i C_i Ci:第 i 个簇
- μ i \mu_i μi:第 i 个簇的中心
- x x x:像素特征向量
OpenCV 实现代码详解
基础实现(RGB 颜色空间)
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像并转换维度
img = cv2.imread('fruit.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # OpenCV读取为BGR,转为RGB
pixel_values = img_rgb.reshape((-1, 3)) # 重塑为(N,3)数组
pixel_values = np.float32(pixel_values) # 转换为浮点型
# K-Means参数设置
K = 3 # 聚类数量(分割区域数)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.85)
flags = cv2.KMEANS_RANDOM_CENTERS
# 执行K-Means
_, labels, centers = cv2.kmeans(
pixel_values, K, None,
criteria, 10, flags
)
# 重建分割图像
centers = np.uint8(centers) # 转换为0-255整数
segmented_data = centers[labels.flatten()] # 用簇中心颜色替换像素
segmented_img = segmented_data.reshape(img_rgb.shape) # 恢复原始形状
# 可视化
plt.figure(figsize=(15,10))
plt.subplot(121), plt.imshow(img_rgb), plt.title('原始图像')
plt.subplot(122), plt.imshow(segmented_img), plt.title(f'K-Means分割 (K={K})')
plt.show()
进阶技巧 1:Lab 颜色空间(更符合人眼感知)
# 转换到Lab颜色空间
img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
pixel_values = img_lab.reshape((-1, 3))
pixel_values = np.float32(pixel_values)
# ...(后续步骤与RGB版本相同)...
# 显示时转回RGB
segmented_rgb = cv2.cvtColor(segmented_img, cv2.COLOR_LAB2RGB)
进阶技巧 2:加入空间位置信息
# 创建空间坐标网格
height, width = img.shape[:2]
x_coords = np.linspace(0, 1, width).reshape(1, -1)
y_coords = np.linspace(0, 1, height).reshape(-1, 1)
# 将空间坐标与颜色特征拼接
spatial_feat = np.dstack([x_coords, y_coords]).reshape(-1, 2)
color_feat = img_rgb.reshape(-1, 3)
combined_feat = np.hstack([color_feat, spatial_feat * 50]) # 空间特征权重因子
# 使用组合特征进行聚类
pixel_values = np.float32(combined_feat)
# ...(后续步骤相同)...
关键参数解析
| 参数 | 作用 | 推荐值 |
|---|---|---|
K |
聚类数量(分割区域数) | 3-8(根据图像复杂度) |
criteria |
停止条件 | (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.85) |
attempts |
运行次数(取最佳结果) | 10-20 |
flags |
初始化方法 | cv2.KMEANS_PP_CENTERS(更优的K-Means++) |
优化策略
1. 自动确定最佳K值(肘部法则)
inertias = []
K_range = range(2, 11)
for k in K_range:
_, _, _, inertia = cv2.kmeans(
pixel_values, k, None,
criteria, 10, flags
)
inertias.append(inertia)
# 绘制肘部曲线
plt.plot(K_range, inertias, 'bo-')
plt.xlabel('K值'), plt.ylabel('簇内平方和')
plt.title('肘部法则确定最佳K值')
plt.show()
选择曲线拐点处的K值作为最佳聚类数
2. 后处理优化
# 形态学开运算消除小噪点
kernel = np.ones((3,3), np.uint8)
cleaned = cv2.morphologyEx(segmented_img, cv2.MORPH_OPEN, kernel)
# 边界平滑
smoothed = cv2.medianBlur(cleaned, 5)
3. 分层聚类
# 第一次聚类(粗分割)
_, labels1, centers1 = cv2.kmeans(..., K=3, ...)
# 对每个簇进行二次聚类
final_labels = np.zeros_like(labels1)
current_label = 0
for i in range(3):
cluster_mask = (labels1 == i).flatten()
cluster_pixels = pixel_values[cluster_mask]
# 仅对大型簇进行细分
if len(cluster_pixels) > 1000:
_, sub_labels, _ = cv2.kmeans(cluster_pixels, K=2, ...)
final_labels[cluster_mask] = sub_labels + current_label
current_label += 2
else:
final_labels[cluster_mask] = current_label
current_label += 1
应用场景
- 主色调提取:海报设计、主题色分析
- 背景分离:产品图像处理
- 图像量化:减少颜色数量(艺术效果)
- 预处理步骤:为更复杂分割算法提供初始区域
优势:计算效率高、实现简单、对颜色分布敏感
局限:需预设K值、对纹理不敏感、可能产生非连续区域
实际应用中常与其他方法结合:
# K-Means + GrabCut 示例
kmeans_result = segmented_img.copy()
mask = np.where(kmeans_result > threshold, 1, 0).astype('uint8')
cv2.grabCut(original_img, mask, None, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_MASK)
分水岭算法(Watershed Algorithm)详解
算法原理
分水岭算法是一种基于拓扑形态学的图像分割方法,灵感来源于地理学中的分水岭概念。它将图像视为地形图:
- 灰度值 表示海拔高度(低灰度=山谷,高灰度=山峰)
- 局部最小值 作为注水起点
- 水淹没区域 形成"集水盆地"
- 盆地交界处 形成分水岭(即分割边界)
核心特点
- 适用场景:完美分割相互接触/重叠的物体(如细胞、硬币等)
- 优势:能处理闭合边界,分割精度高
- 挑战:对噪声敏感,容易产生过分割
算法步骤(OpenCV实现流程)
1. 预处理
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread('objects.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
plt.imshow(gray, cmap='gray'), plt.title('Original Gray')
2. 二值化处理
# Otsu阈值处理
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
plt.imshow(thresh, cmap='gray'), plt.title('Threshold')
3. 形态学去噪
# 开运算去除噪声
kernel = np.ones((3,3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
plt.imshow(opening, cmap='gray'), plt.title('After Morphology')
4. 确定背景区域
# 膨胀获取确定背景
sure_bg = cv2.dilate(opening, kernel, iterations=3)
plt.imshow(sure_bg, cmap='gray'), plt.title('Sure Background')
5. 确定前景区域
# 距离变换+阈值获取确定前景
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
_, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)
sure_fg = sure_fg.astype(np.uint8)
plt.imshow(sure_fg, cmap='gray'), plt.title('Sure Foreground')
6. 计算未知区域
# 背景减去前景得到未知区域
unknown = cv2.subtract(sure_bg, sure_fg)
plt.imshow(unknown, cmap='gray'), plt.title('Unknown Region')
7. 创建标记图
# 连通域标记
_, markers = cv2.connectedComponents(sure_fg)
# 标记准备:背景=1,未知区域=0
markers += 1
markers[unknown==255] = 0
# 可视化标记
plt.imshow(markers, cmap='jet'), plt.title('Markers')
plt.colorbar()
8. 应用分水岭算法
# 关键步骤:应用分水岭
markers = cv2.watershed(img, markers)
# 边界标记为-1(显示为红色)
img[markers == -1] = [255, 0, 0]
# 显示结果
plt.figure(figsize=(12,6))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.title('Segmentation Result'), plt.axis('off')
# 显示标记图
plt.subplot(122), plt.imshow(markers, cmap='jet')
plt.title('Watershed Markers'), plt.colorbar()
plt.show()
关键参数说明
| 参数/步骤 | 作用 | 调整建议 |
|---|---|---|
cv2.distanceTransform() |
计算像素到最近零像素的距离 | 使用DIST_L2获取精确距离 |
| 距离变换阈值(0.7*) | 控制前景区域大小 | 值越小前景越大 |
| 形态学迭代次数 | 控制噪声去除强度 | 根据噪声程度调整(通常2-3次) |
cv2.watershed() |
执行分水岭算法 | 必须使用彩色图像作为输入 |
过分割问题解决方案
-
标记控制法:手动添加前景/背景标记
# 创建标记图像(全0) markers = np.zeros(gray.shape, dtype=np.int32) # 手动标记前景(用不同正整数标记不同对象) markers[50:100, 50:100] = 1 # 对象1 markers[150:200, 150:200] = 2 # 对象2 # 标记背景(用1) markers[0:30, :] = 1 # 顶部背景区域 # 应用分水岭 cv2.watershed(img, markers) -
预处理优化:
# 高斯模糊减少噪声 blurred = cv2.GaussianBlur(gray, (7,7), 0) # 使用形态学梯度增强边缘 gradient = cv2.morphologyEx(gray, cv2.MORPH_GRADIENT, kernel)
典型应用场景
- 医学图像分析:细胞计数与分割
- 材料科学:颗粒状材料分析
- 工业检测:零件接触分离
- 地质学:岩石结构分析
提示:实际应用中常结合其他方法(如阈值分割+分水岭)提升效果。对于复杂图像,建议先使用边缘检测或深度学习方法获取初始标记。
GrabCut 交互式分割详解
GrabCut 是一种先进的交互式图像分割算法,结合了图割(Graph Cut)和迭代能量最小化技术。它通过少量用户交互就能实现高质量的前景提取,特别适合复杂背景下的物体分割。
核心原理
-
图割理论基础:
- 将图像建模为图结构:像素=节点,像素关系=边
- 通过最小化能量函数实现分割:
E ( α , k , θ , z ) = U ( α , k , θ , z ) + V ( α , z ) E(\alpha, k, \theta, z) = U(\alpha, k, \theta, z) + V(\alpha, z) E(α,k,θ,z)=U(α,k,θ,z)+V(α,z)- U U U:区域项(像素与GMM模型的匹配度)
- V V V:边界项(相邻像素的相似度)
-
高斯混合模型(GMM):
- 为前景和背景各建立包含5个高斯分量的颜色模型
- 迭代优化模型参数
-
迭代能量最小化:
graph LR A[用户输入矩形/标记] --> B[初始化GMM] B --> C[分配像素到GMM分量] C --> D[更新GMM参数] D --> E[构建图并最小割] E --> F{收敛?} F -->|否| C F -->|是| G[输出分割结果]
OpenCV 实现详解
基础实现(矩形初始化)
import cv2
import numpy as np
# 读取图像
img = cv2.imread('object.jpg')
mask = np.zeros(img.shape[:2], np.uint8) # 创建初始掩码
# 初始化GrabCut所需模型
bgd_model = np.zeros((1, 65), np.float64)
fgd_model = np.zeros((1, 65), np.float64)
# 用户指定包含前景的矩形区域 (x, y, w, h)
rect = (50, 50, 400, 300) # 根据目标物体调整
# 应用GrabCut
cv2.grabCut(img, mask, rect, bgd_model, fgd_model, 5, cv2.GC_INIT_WITH_RECT)
# 创建结果掩码:将可能前景设为前景
result_mask = np.where((mask == cv2.GC_PR_FGD) | (mask == cv2.GC_FGD), 255, 0).astype('uint8')
# 应用掩码获取前景
result = cv2.bitwise_and(img, img, mask=result_mask)
# 显示结果
cv2.imshow('Original', img)
cv2.imshow('GrabCut Result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
进阶实现(手动标记优化)
# 初始化(同上)
# ...
# 第一次分割后用户添加标记
def add_markers():
# 创建标记图像
markers = np.zeros_like(img)
# 红色标记前景 (BGR格式)
markers[100:150, 200:250] = [0, 0, 255] # 示例位置
# 蓝色标记背景
markers[300:350, 50:100] = [255, 0, 0]
return markers
# 添加用户标记
marker_img = add_markers()
# 更新掩码
mask[(marker_img[:,:,2] > 200) & (marker_img[:,:,0] < 50)] = cv2.GC_FGD # 红色->前景
mask[(marker_img[:,:,0] > 200) & (marker_img[:,:,2] < 50)] = cv2.GC_BGD # 蓝色->背景
# 使用掩码重新运行GrabCut
cv2.grabCut(img, mask, None, bgd_model, fgd_model, 3, cv2.GC_INIT_WITH_MASK)
# 处理结果(同上)
# ...
参数详解
| 参数 | 描述 | 推荐值 |
|---|---|---|
iterCount |
迭代次数 | 5-10(复杂图像可增加) |
mode |
初始化模式 | GC_INIT_WITH_RECT 或 GC_INIT_WITH_MASK |
| 矩形大小 | 包含目标的区域 | 覆盖目标+小部分背景 |
| GMM分量 | 颜色模型复杂度 | 固定5个(OpenCV默认) |
掩码值含义
| 值 | 常量 | 含义 |
|---|---|---|
| 0 | GC_BGD |
确定背景 |
| 1 | GC_FGD |
确定前景 |
| 2 | GC_PR_BGD |
可能背景 |
| 3 | GC_PR_FGD |
可能前景 |
优化技巧
1. 边界平滑处理
# 形态学操作优化边界
kernel = np.ones((3,3), np.uint8)
smoothed_mask = cv2.morphologyEx(result_mask, cv2.MORPH_CLOSE, kernel)
# 高斯模糊柔化边缘
blurred_mask = cv2.GaussianBlur(smoothed_mask, (5,5), 0)
2. 透明背景生成
# 创建带alpha通道的结果
b, g, r = cv2.split(img)
alpha = np.where(result_mask > 0, 255, 0).astype(np.uint8)
rgba = cv2.merge([b, g, r, alpha])
3. 性能优化
# 下采样处理大图
scale = 0.5
small_img = cv2.resize(img, None, fx=scale, fy=scale)
small_mask = cv2.resize(mask, None, fx=scale, fy=scale,
interpolation=cv2.INTER_NEAREST)
# 在小图上运行GrabCut
# ...
# 上采样结果
result_mask = cv2.resize(small_mask, (img.shape[1], img.shape[0]),
interpolation=cv2.INTER_NEAREST)
与深度学习方法对比
| 特性 | GrabCut | 深度学习分割 |
|---|---|---|
| 数据需求 | 无需训练数据 | 需要大量标注数据 |
| 计算资源 | CPU实时 | 需要GPU加速 |
| 用户交互 | 需要简单交互 | 通常无交互 |
| 分割精度 | ★★★★☆ | ★★★★★ |
| 泛化能力 | ★★☆☆☆ | ★★★★★ |
| 复杂背景 | ★★★☆☆ | ★★★★★ |
应用场景
- 电商产品图抠图
- 证件照背景替换
- 艺术创作中的元素提取
- 视频会议虚拟背景
- 医学图像感兴趣区域提取
# 实际应用示例:背景替换
background = cv2.imread('scenery.jpg')
foreground = cv2.bitwise_and(img, img, mask=result_mask)
# 调整前景大小
h, w = foreground.shape[:2]
background = cv2.resize(background, (w, h))
# 合成图像
inverse_mask = cv2.bitwise_not(result_mask)
bg_part = cv2.bitwise_and(background, background, mask=inverse_mask)
final = cv2.add(foreground, bg_part)
提示:对于毛发、透明物体等复杂场景,可结合Matting算法优化结果。最新OpenCV版本已集成基于深度学习的肖像分割模型,可作为GrabCut的补充。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)