OpenCV 图像分割介绍

图像分割是将图像划分为多个有意义的区域或对象的过程,是计算机视觉的核心任务之一。OpenCV 提供了多种图像分割方法:

常用分割方法
  1. 阈值分割:基于像素灰度值进行分割(全局/自适应阈值)
  2. 边缘检测:通过检测图像边缘实现分割(Canny、Sobel等)
  3. 区域生长:基于相似性准则从种子点扩展区域
  4. 分水岭算法:用于分离相互接触的对象
  5. 聚类分割:K-Means、GrabCut 等基于聚类的算法
  6. 深度学习方法:结合深度学习模型(需集成其他库)

代码实现示例

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)

使用建议

  1. 简单场景:优先尝试阈值分割或边缘检测
  2. 复杂背景:使用聚类方法或分水岭算法
  3. 精确提取:采用交互式GrabCut
  4. 性能要求:阈值法最快,分水岭和聚类计算量较大
  5. 参数调整:根据具体图像特性调整阈值/迭代次数等参数

注意:实际应用中常需组合多种方法(如边缘检测+形态学操作),并配合后处理优化结果。

K-Means 聚类分割详解

K-Means 是一种无监督聚类算法,通过将图像像素按颜色/纹理特征分组实现分割,特别适合基于颜色的图像分割任务。

核心原理
  1. 特征空间映射:将每个像素视为 N 维特征空间中的点(常用 RGB/Lab 颜色空间)
  2. 聚类中心:初始化 K 个聚类中心(簇心)
  3. 迭代优化
    • 分配步骤:将每个像素分配给最近的簇心
    • 更新步骤:重新计算簇心位置(取簇内像素均值)
  4. 收敛:当簇心不再变化或达到最大迭代次数时停止
数学表示

最小化目标函数(簇内平方和):
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=1KxCixμi2
其中:

  • 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

应用场景

  1. 主色调提取:海报设计、主题色分析
  2. 背景分离:产品图像处理
  3. 图像量化:减少颜色数量(艺术效果)
  4. 预处理步骤:为更复杂分割算法提供初始区域

优势:计算效率高、实现简单、对颜色分布敏感
局限:需预设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)详解

算法原理

分水岭算法是一种基于拓扑形态学的图像分割方法,灵感来源于地理学中的分水岭概念。它将图像视为地形图:

  • 灰度值 表示海拔高度(低灰度=山谷,高灰度=山峰)
  • 局部最小值 作为注水起点
  • 水淹没区域 形成"集水盆地"
  • 盆地交界处 形成分水岭(即分割边界)
核心特点
  1. 适用场景:完美分割相互接触/重叠的物体(如细胞、硬币等)
  2. 优势:能处理闭合边界,分割精度高
  3. 挑战:对噪声敏感,容易产生过分割

算法步骤(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() 执行分水岭算法 必须使用彩色图像作为输入

过分割问题解决方案

  1. 标记控制法:手动添加前景/背景标记

    # 创建标记图像(全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)
    
  2. 预处理优化

    # 高斯模糊减少噪声
    blurred = cv2.GaussianBlur(gray, (7,7), 0)
    
    # 使用形态学梯度增强边缘
    gradient = cv2.morphologyEx(gray, cv2.MORPH_GRADIENT, kernel)
    

典型应用场景

  1. 医学图像分析:细胞计数与分割
  2. 材料科学:颗粒状材料分析
  3. 工业检测:零件接触分离
  4. 地质学:岩石结构分析

提示:实际应用中常结合其他方法(如阈值分割+分水岭)提升效果。对于复杂图像,建议先使用边缘检测或深度学习方法获取初始标记。

GrabCut 交互式分割详解

GrabCut 是一种先进的交互式图像分割算法,结合了图割(Graph Cut)和迭代能量最小化技术。它通过少量用户交互就能实现高质量的前景提取,特别适合复杂背景下的物体分割。

核心原理
  1. 图割理论基础

    • 将图像建模为图结构:像素=节点,像素关系=边
    • 通过最小化能量函数实现分割:
      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:边界项(相邻像素的相似度)
  2. 高斯混合模型(GMM)

    • 为前景和背景各建立包含5个高斯分量的颜色模型
    • 迭代优化模型参数
  3. 迭代能量最小化

    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_RECTGC_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加速
用户交互 需要简单交互 通常无交互
分割精度 ★★★★☆ ★★★★★
泛化能力 ★★☆☆☆ ★★★★★
复杂背景 ★★★☆☆ ★★★★★

应用场景

  1. 电商产品图抠图
  2. 证件照背景替换
  3. 艺术创作中的元素提取
  4. 视频会议虚拟背景
  5. 医学图像感兴趣区域提取
# 实际应用示例:背景替换
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的补充。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐