> 技术文档 > 【opencv】OpenCV 图像分割介绍及代码实现

【opencv】OpenCV 图像分割介绍及代码实现


文章目录

    • OpenCV 图像分割介绍
        • 常用分割方法
      • 代码实现示例
        • 1. 阈值分割
        • 2. 边缘检测分割
        • 3. K-Means 聚类分割
        • 4. 分水岭算法
        • 5. GrabCut 交互式分割
      • 使用建议
    • K-Means 聚类分割详解
        • 核心原理
        • 数学表示
      • OpenCV 实现代码详解
        • 基础实现(RGB 颜色空间)
        • 进阶技巧 1:Lab 颜色空间(更符合人眼感知)
        • 进阶技巧 2:加入空间位置信息
      • 关键参数解析
      • 优化策略
        • 1. 自动确定最佳K值(肘部法则)
        • 2. 后处理优化
        • 3. 分层聚类
      • 应用场景
    • 分水岭算法(Watershed Algorithm)详解
        • 算法原理
        • 核心特点
      • 算法步骤(OpenCV实现流程)
        • 1. 预处理
        • 2. 二值化处理
        • 3. 形态学去噪
        • 4. 确定背景区域
        • 5. 确定前景区域
        • 6. 计算未知区域
        • 7. 创建标记图
        • 8. 应用分水岭算法
      • 关键参数说明
      • 过分割问题解决方案
      • 典型应用场景
    • GrabCut 交互式分割详解
        • 核心原理
      • OpenCV 实现详解
        • 基础实现(矩形初始化)
        • 进阶实现(手动标记优化)
      • 参数详解
        • 掩码值含义
      • 优化技巧
        • 1. 边界平滑处理
        • 2. 透明背景生成
        • 3. 性能优化
      • 与深度学习方法对比
      • 应用场景

OpenCV 图像分割介绍

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

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

代码实现示例

1. 阈值分割
import cv2import 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 cv2import 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 cv2import 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 cv2import 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 += 1markers[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 cv2import numpy as npimport matplotlib.pyplot as plt# 读取图像并转换维度img = cv2.imread(\'fruit.jpg\')img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # OpenCV读取为BGR,转为RGBpixel_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版本相同)...# 显示时转回RGBsegmented_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 = 0for 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 cv2import numpy as npimport 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,未知区域=0markers += 1markers[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 # 对象1markers[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 LRA[用户输入矩形/标记] --> B[初始化GMM]B --> C[分配像素到GMM分量]C --> D[更新GMM参数]D --> E[构建图并最小割]E --> F{收敛?}F -->|否| CF -->|是| G[输出分割结果]

OpenCV 实现详解

基础实现(矩形初始化)
import cv2import 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) # 根据目标物体调整# 应用GrabCutcv2.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 # 蓝色->背景# 使用掩码重新运行GrabCutcv2.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.5small_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的补充。