Python案例教你用OpenCV做图像分割(附完整代码)
目录导读
- 图像分割是什么?为什么用OpenCV?
- 环境搭建与核心库导入
- 基于阈值的分割(Otsu算法)
- 边缘检测分割(Canny算子)
- 分水岭算法分割(Watershed)
- K-Means聚类分割
- 常见问题与优化技巧(FAQ)
- 总结与进阶方向
图像分割是什么?为什么用OpenCV?
图像分割是计算机视觉的核心任务之一,旨在将图像划分为多个具有语义意义的区域(如前景与背景、不同物体),它在自动驾驶、医学影像分析、工业质检等领域至关重要。

问:为什么不直接用深度学习?
答:对于中小型项目或资源受限场景,OpenCV提供的传统方法(阈值、边缘、聚类)速度快、可解释性强,且无需大量标注数据。
Python+OpenCV组合因其轻量、跨平台、社区成熟,成为入门图像分割的首选工具,本文将通过四个经典案例,手把手带你掌握图像分割的实战技巧。
环境搭建与核心库导入
必备库安装
pip install opencv-python numpy matplotlib scikit-learn
代码模板
import cv2
import numpy as np
import matplotlib.pyplot as plt
def show_images(images, titles, cols=2):
"""辅助显示多张图像"""
rows = (len(images) + cols - 1) // cols
plt.figure(figsize=(12, 6))
for i, (img, title) in enumerate(zip(images, titles)):
plt.subplot(rows, cols, i+1)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB) if len(img.shape)==3 else img, cmap='gray')
plt.title(title)
plt.axis('off')
plt.tight_layout()
plt.show()
注:所有案例均使用经典测试图 coins.jpg(硬币分割),可从OpenCV官方库获取或自行准备简单图像。
案例一:基于阈值的分割(Otsu算法)
核心原理
当图像直方图呈双峰分布时(如暗背景+亮物体),Otsu自动寻找最佳阈值使类间方差最大。
完整代码
img = cv2.imread('coins.jpg', 0) # 灰度读取
# 高斯滤波去噪
blurred = cv2.GaussianBlur(img, (5,5), 0)
# Otsu阈值分割
_, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
show_images([img, binary], ['原图', 'Otsu分割结果'])
效果优化
- 预处理:适当高斯模糊可消除噪声,避免伪分割。
- 局限性:光照不均时效果差,需配合自适应阈值(如
cv2.adaptiveThreshold)。
问:Otsu为何对光照敏感?
答:Otsu假设全局单一阈值,若图像存在渐变阴影(如边缘暗中心亮),直方图不再是双峰,需改用局部阈值方法。
案例二:边缘检测分割(Canny算子)
核心原理
通过梯度计算找到像素突变点,再通过双阈值连接边缘,形成封闭轮廓。
完整代码
# 边缘检测 edges = cv2.Canny(blurred, 30, 150) # 参数:低阈值、高阈值 # 边缘填充轮廓(可选) kernel = np.ones((3,3), np.uint8) dilated = cv2.dilate(edges, kernel, iterations=1) show_images([img, edges, dilated], ['原图', 'Canny边缘', '膨胀后边缘'])
参数调优指南
- 低阈值:控制弱边缘的灵敏度,过低会引入噪声。
- 高阈值:确定强边缘,过高会丢失细节。
- 经验公式:高阈值≈低阈值×2~3倍。
问:如何自动选择阈值?
答:可用cv2.medianBlur配合Otsu计算梯度幅值的统计值,详见OpenCV官方文档。
案例三:分水岭算法分割(Watershed)
核心原理
将图像看作地形图,灰度值视为海拔,从标记点开始“注水”,不同水域交汇处即为分割边界。
完整代码(硬币分割实战)
# 1. 二值化
_, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# 2. 确定前景(硬币区域)
dist = cv2.distanceTransform(thresh, cv2.DIST_L2, 5)
_, sure_fg = cv2.threshold(dist, 0.5*dist.max(), 255, 0)
sure_fg = np.uint8(sure_fg)
# 3. 确定背景
kernel = np.ones((3,3), np.uint8)
sure_bg = cv2.dilate(thresh, kernel, iterations=3)
# 4. 标记未知区域
unknown = cv2.subtract(sure_bg, sure_fg)
# 5. 创建标记并应用分水岭
_, markers = cv2.connectedComponents(sure_fg)
markers = markers + 1 # 背景标记设为1
markers[unknown == 255] = 0 # 未知区域标记为0
markers = cv2.watershed(cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), markers)
# 6. 可视化分割边界
img_seg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
img_seg[markers == -1] = [0, 0, 255] # 边界标红
show_images([img, thresh, sure_fg, img_seg],
['原图', '二值化', '确定前景', '分水岭分割结果'])
踩坑提醒
- 过度分割:可通过形态学操作(开运算)减少噪声标记。
- 标记点不足:若硬币堆叠,需用
cv2.erode分离粘连物体。
问:分水岭算法适合什么场景?
答:适合物体边缘模糊但轮廓明显的图像(如细胞核分割),对噪声敏感需预处理。
案例四:K-Means聚类分割
核心原理
将每个像素的RGB值视为三维空间中的点,通过K-Means聚类将像素分为K类。
完整代码
# 将图像转为数据矩阵
img_color = cv2.imread('coins.jpg')
data = img_color.reshape((-1, 3)).astype(np.float32)
# K-Means聚类(K=2,分为前景和背景)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
_, labels, centers = cv2.kmeans(data, 2, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
# 重建分割图像
centers = np.uint8(centers)
segmented = centers[labels.flatten()].reshape(img_color.shape)
show_images([img_color, segmented], ['原图', f'K-Means分割 (K=2)'])
参数调整
- K值:需根据物体数量手动设定,可通过肘部法则(Elbow Method)辅助选择。
- 距离度量:默认欧氏距离,对颜色差异敏感。
问:K-Means分割与阈值分割有何本质区别?
答:K-Means基于颜色空间聚类,可处理多通道彩色图像;阈值分割仅依赖灰度分布,仅适合单通道。
常见问题与优化技巧(FAQ)
Q1:分割结果出现大量噪点怎么办?
解决方案:
- 先进行中值滤波(
cv2.medianBlur)或高斯滤波。 - 对二值图进行开闭运算(
cv2.morphologyEx)。 - 使用
cv2.connectedComponentsWithStats滤除小面积区域。
Q2:如何精准分割重叠物体?
推荐方法:
- 分水岭算法 + 距离变换。
- 先做腐蚀确定分离点,再应用分水岭。
Q3:处理高分辨率图像太慢怎么优化?
策略:
- 缩小图像尺寸(
cv2.resize),分割后再映射回原图。 - 使用OpenCV的并行版本(如
cv2.ximgproc.superpixel超像素预分割)。
Q4:有没有不需要标注的分割方法?
答:有!如基于区域生长的cv2.floodFill或基于图论的Felzenszwalb算法(需安装skimage.segmentation)。
总结与进阶方向
本文通过阈值分割(Otsu)、边缘检测(Canny)、分水岭算法和K-Means聚类四个案例,覆盖了图像分割的三大主流思路:灰度分布、边缘过渡和颜色聚类。
下一步学习建议
- 深度学习替代:尝试U-Net、Mask R-CNN进行语义/实例分割(需GPU和数据)。
- 实时分割:结合OpenCV的DNN模块加载预训练模型(如YOLO)。
- 医学图像:专注于MRI/CT的特定方法(如水平集算法)。
最后提醒:没有万能的分割算法,选择方法时需先分析图像特点——对比度、噪声、物体形态,多动手调试参数,你也能成为图像分割高手!