一、什么是图像分割
图像分割(Segmentation)是图像处理和机器视觉一个重要分支,其目标是精确理解图像场景与内容。图像分割是在像素级别上的分类,属于同一类的像素都要被归为一类,因此图像分割是从像素级别来理解图像的。如下图所示的照片,属于人的像素部分划分成一类,属于摩托车的像素划分成一类,背景像素划分为一类。
图像分割的类型
- 语义分割(Semantic Segmentation):对图像中的每个像素划分到不同的类别;
- 实例分割(Instance Segmentation):对图像中每个像素划分到不同的个体(可以理解为目标检测和语义分割的结合);
- 全景分割(Panoptic Segmentation):语义分割和实例分割的结合,即要对所有目标都检测出来,又要区分出同个类别中的不同实例。
图像分割的应用
1) 🩺医学、生物图像分割(如病灶识别)
将医学影像(如CT、MRI、超声图像)中的器官、组织、病灶区域进行像素级分割,提取出病变位置、形状和大小。
2) 🏭工业质检
对工业产品(如金属板、电子元件、纺织品等)表面图像进行分割,检测裂纹、划痕、瑕疵、缺陷区域。
3) 🛰️ 遥感图像分割
将遥感卫星或无人机拍摄的高空图像进行像素级分割,提取地物信息,如建筑物、道路、水体、植被等。为城市规划、环境监测、灾害评估、国土调查等提供精确数据支撑。
4)🚦交通图像分析
对交通场景中的车道线、车辆、行人、交通标志等元素进行像素级分割,作为自动驾驶、智能交通系统的重要基础。确保车辆按照正确的车道行驶、避障、识别交通标志。
5) 📷 自动抠图
将图像中的前景(如人物、物体)与背景分割开,便于替换背景、制作合成图像或生成特效。广泛应用于手机拍照、美颜、虚拟直播、视频会议等。
图像分割的难点
- 数据标注成本高:分割不像检测等任务,只需要标注边框就可以使用,分割需要精确到像素级标注,包括每一个目标的轮廓等信息;
- 计算资源消耗大:要想得到较高的精度就需要使用更深的网络、进行更精确的计算,对计算资源要求较高。目前业界有一些轻量级网络,但总体精度较低;
- 小目标、细节分割难:目前很多算法对于道路、建筑物等类别分割精度很高,能达到98%,而对于细小的类别,由于其轮廓太小,而无法精确的定位轮廓;
- 上下文信息依赖强:分割中上下文信息很重要,否则会造成一个目标被分成多个部分,或者不同类别目标分类成相同类别;
二、基于阈值化的分割方法
将图像像素灰度值与设定的阈值进行比较,根据比较结果将像素分为前景和背景,达到图像分割目的。当一个图像有双峰现象时,其直方图会出现两个峰,分别对应图像中两种不同的颜色或亮度区域。这时我们可以使用直方图双峰法来自动确定合适的阈值。
threshold(src, thresh, maxval, type[, dst])
参数名 | 数据类型 | 说明 | 典型值/选项 |
---|---|---|---|
src | Mat | 输入图像,必须是单通道灰度图(8位或32位浮点型) | - |
thresh | double | 设定的阈值T,用于像素分类 | 0~255之间的值(如127) |
maxval | double | 当type使用非二进制类型时(如THRESH_TRUNC/THRESH_TOZERO),指定的最大值 | 通常为255(8位图像的最大值) |
type | int | 阈值化类型,决定如何处理像素值与阈值的关系 | 以下选项之一: • cv2.THRESH_BINARY • cv2.THRESH_BINARY_INV • cv2.THRESH_TRUNC • cv2.THRESH_TOZERO • cv2.THRESH_TOZERO_INV |
dst | Mat | 输出图像,与输入图像大小和类型相同 | - |
类型 | 含义 | 说明 |
---|---|---|
THRESH_BINARY | 二值化 | 像素值 ≥ 阈值 → 255,否则 0 |
THRESH_BINARY_INV | 反二值化 | 像素值 < 阈值 → 255,否则 0 |
THRESH_TRUNC | 截断 | 像素值 ≥ 阈值 → 阈值,否则保持不变 |
THRESH_TOZERO | 零阈值化 | 像素值 ≥ 阈值 → 保持原值,否则 0 |
THRESH_TOZERO_INV | 反零阈值化 | 像素值 < 阈值 → 保持原值,否则 0 |
# 导入OpenCV库
import cv2
# 读取图像文件(默认以彩色BGR格式加载)
img = cv2.imread('./images/nezha.png')
# 将彩色图像转换为灰度图像(单通道)
# COLOR_BGR2GRAY 表示从BGR颜色空间转换到灰度空间
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 对灰度图像进行阈值处理
# 参数说明:
# img_gray: 输入的灰度图像
# 150: 设定的阈值(T)
# 255: 当像素值超过阈值时赋予的新值(maxval)
# cv2.THRESH_BINARY: 阈值化类型(大于阈值设为maxval,否则设为0)
# 返回值:
# ret: 实际使用的阈值(当使用自适应阈值时有用)
# img_threshold: 阈值处理后的二值图像
ret, img_threshold = cv2.threshold(img_gray, 150, 255, cv2.THRESH_BINARY)
# 显示原始彩色图像
cv2.imshow('原始彩色图像', img)
# 显示灰度图像
cv2.imshow('灰度图像', img_gray)
# 显示阈值处理后的二值图像
cv2.imshow('二值化图像', img_threshold)
# 等待键盘输入(0表示无限等待)
cv2.waitKey(0)
# 关闭所有OpenCV创建的窗口
cv2.destroyAllWindows()
三、基于自适应阈值图像分割
自适应阈值化(Adaptive Thresholding)是根据图像中每个像素邻域的局部特性,动态计算阈值进行分割的方法。相比固定阈值化(全局阈值),自适应阈值化对光照不均匀、局部对比度变化较大的图像表现更好。
adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
参数名 | 数据类型 | 说明 | 可选值/范围 |
---|---|---|---|
src | Mat | 输入图像(必须为 8位单通道灰度图) | - |
maxValue | double | 满足条件的像素被赋予的新值(一般设为 255,表示白色) | 0~255(8位图像) |
adaptiveMethod | int | 自适应阈值算法: • 基于局部均值 • 基于高斯加权和 | cv2.ADAPTIVE_THRESH_MEAN_C cv2.ADAPTIVE_THRESH_GAUSSIAN_C |
thresholdType | int | 阈值化类型: • 二进制:大于阈值设为 maxValue ,否则为0• 反二进制:与二进制相反 | cv2.THRESH_BINARY cv2.THRESH_BINARY_INV |
blockSize | int | 计算阈值的局部邻域大小(必须为 奇数,如3,5,7...) | ≥3 的奇数(如 3, 5, 7) |
C | double | 从均值或加权均值中减去的常数(用于微调阈值,正值提高阈值,负值降低阈值) | 一般取小整数(如 -2, 0, 2, 5) |
方法 | 说明 |
---|---|
ADAPTIVE_THRESH_MEAN_C | 邻域内像素均值减去常数 C 作为阈值 |
ADAPTIVE_THRESH_GAUSSIAN_C | 邻域内像素加权均值(高斯权重)减去常数 C 作为阈值 |
# 导入OpenCV库
import cv2
# 读取图像文件(默认以BGR三通道格式加载)
# './images/nezha.png'是图像路径,请确保文件存在
img = cv2.imread('./images/nezha.png')
# 检查图像是否成功加载
if img is None:
print("错误:图像加载失败,请检查路径!")
exit()
# 将BGR彩色图像转换为灰度图像(单通道)
# COLOR_BGR2GRAY表示从BGR颜色空间转换到灰度空间
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 自适应阈值处理(基于局部均值的方法)
# 参数说明:
# img_gray: 输入的灰度图像
# 255: 满足条件时像素设置的最大值(白色)
# cv2.ADAPTIVE_THRESH_MEAN_C: 使用邻域均值作为阈值计算方法
# cv2.THRESH_BINARY: 二值化类型(大于阈值设为255,否则0)
# 11: 邻域大小(必须为奇数,这里使用11x11的窗口)
# 2: 从均值中减去的常数(用于微调阈值)
img_adaptiveThreshold_mean = cv2.adaptiveThreshold(
img_gray, 255,
cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 11, 2
)
# 自适应阈值处理(基于高斯加权的方法)
# 与均值方法的主要区别:
# 使用高斯加权计算邻域阈值,对噪声更鲁棒
img_adaptiveThreshold_gaussian = cv2.adaptiveThreshold(
img_gray, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2
)
# 显示原始彩色图像
cv2.imshow('原始彩色图像', img)
# 显示灰度图像
cv2.imshow('灰度图像', img_gray)
# 显示基于局部均值的自适应阈值结果
cv2.imshow('均值法自适应阈值', img_adaptiveThreshold_mean)
# 显示基于高斯加权的自适应阈值结果
cv2.imshow('高斯法自适应阈值', img_adaptiveThreshold_gaussian)
# 等待键盘输入(0表示无限等待,按任意键继续)
cv2.waitKey(0)
# 关闭所有OpenCV创建的窗口
cv2.destroyAllWindows()
四、基于边缘的图像分割
基于边缘的图像分割方法,是通过检测图像中像素灰度值变化显著的边缘区域,将图像分割成若干区域的方法。边缘通常对应着物体的轮廓或区域之间的分界线,边缘检测是这类方法的核心步骤。
常用的边缘检测方法有:
算子 | 特点说明 |
---|---|
Roberts 罗伯茨算子 | 计算邻域对角线方向差分,适合检测细节 |
Sobel 索贝尔算子 | 引入平滑,增强抗噪性,检测水平和垂直边缘 |
Scharr 沙尔算子 | 类似Sobel,计算速度更快,适用于简单场景 |
Laplacian 拉普拉斯算子 | 基于二阶导数,响应所有方向的边缘 |
Canny 卡尼边缘检测 | 综合多步骤优化,效果最好,应用最广 |
推荐
Canny 算法是一种多阶段、鲁棒性强、抗噪声能力优秀的边缘检测方法,广泛应用于实际项目。
cv2.Canny(image, threshold1, threshold2)
参数 | 说明 |
---|---|
image | 输入图像,需为单通道灰度图 |
threshold1 | 低阈值 |
threshold2 | 高阈值 |
Canny 算法会根据这两个阈值:
判断强边缘(大于高阈值)
判断弱边缘(介于低阈值和高阈值之间,且与强边缘相连)
过滤非边缘区域(低于低阈值)
举个例子
假设有一张图像:
threshold1 = 50
threshold2 = 150
- 如果某个像素值大于 150,它被认为是强边缘。
- 如果某个像素值介于 50 和 150 之间,它被认为是弱边缘,但只有在它与强边缘连接时才被保留。
- 如果某个像素值小于 50,它被认为不是边缘,直接忽略。
# 导入OpenCV库
import cv2
# 读取图像文件(默认以BGR三通道格式加载)
# './images/nezha.png'是图像路径,请确保文件存在
img = cv2.imread('./images/nezha.png')
# 检查图像是否成功加载
if img is None:
print("错误:图像加载失败,请检查路径!")
exit()
# 将BGR彩色图像转换为灰度图像(单通道)
# COLOR_BGR2GRAY表示从BGR颜色空间转换到灰度空间
# Canny边缘检测需要输入单通道灰度图像
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Canny边缘检测
# 参数说明:
# img_gray: 输入的灰度图像
# 150: 第一个阈值(低阈值),用于边缘连接
# 255: 第二个阈值(高阈值),用于强边缘检测
# 说明:介于150-255之间的像素会被认为是边缘(如果连接到强边缘)
# 建议高低阈值比例在1:2或1:3之间(如100:200或150:300)
edges = cv2.Canny(img_gray, 150, 255)
# 显示边缘检测结果
# 白色像素表示检测到的边缘,黑色表示背景
cv2.imshow('Canny边缘检测结果', edges)
# 等待键盘输入(0表示无限等待,按任意键继续)
cv2.waitKey(0)
# 关闭所有OpenCV创建的窗口
cv2.destroyAllWindows()
contours, hierarchy = cv2.findContours(image, mode, method)
参数名 | 数据类型 | 说明 | 可选值/模式 |
---|---|---|---|
image | Mat | 输入的二值图像(通常为Canny边缘检测结果或阈值化结果) | 必须是8位单通道图像,非零像素视为前景 |
mode | int | 轮廓检索模式,决定如何组织返回的轮廓 | cv2.RETR_EXTERNAL cv2.RETR_LIST cv2.RETR_CCOMP cv2.RETR_TREE |
method | int | 轮廓近似方法,决定如何存储轮廓的点集 | cv2.CHAIN_APPROX_NONE cv2.CHAIN_APPROX_SIMPLE cv2.CHAIN_APPROX_TC89_L1 等 |
返回值说明
返回值 | 数据类型 | 说明 | 示例/结构 |
---|---|---|---|
contours | list[ndarray] | 检测到的轮廓列表,每个轮廓是N×1×2的ndarray(N为轮廓点数量) | 单个轮廓形状:(N,1,2),其中N是点的坐标(如[[[x1,y1]], [[x2,y2]], ...]) |
hierarchy | ndarray (可选) | 轮廓的层级关系,形状为(1,M,4)(M为轮廓数量),每个元素包含:[Next, Previous, First_Child, Parent] | 若不存在层级关系,则为None (如使用RETR_LIST 时) |
详细说明与示例
1. mode
轮廓检索模式
模式 | 说明 | 适用场景 |
---|---|---|
cv2.RETR_EXTERNAL | 只检测最外层轮廓(如物体外边框) | 简单物体计数或外轮廓提取 |
cv2.RETR_LIST | 检测所有轮廓,不建立层级关系 | 快速检测所有轮廓 |
cv2.RETR_TREE | 检测所有轮廓,并建立完整的层级树(包含父子关系) | 需要分析轮廓嵌套关系(如文字中的孔洞) |
cv2.RETR_CCOMP | 检测所有轮廓,组织为两级层级(外层轮廓和孔洞) | 简化版层级关系 |
2. method
轮廓近似方法
方法 | 说明 | 效果对比 |
---|---|---|
cv2.CHAIN_APPROX_NONE | 存储轮廓所有点 | 精确但内存占用高 |
cv2.CHAIN_APPROX_SIMPLE | 压缩水平、垂直、对角线方向的冗余点,只保留端点 | 减少内存占用(如矩形只存4个顶点) |
cv2.CHAIN_APPROX_TC89_L1 | 使用Teh-Chin链式近似算法(更高效的压缩) | 平衡精度和效率 |
Canny + findContours 图像分割流程
转换为灰度图
高斯滤波降噪
Canny 边缘检测
使用
cv2.findContours
提取轮廓绘制或处理轮廓实现分割
# 导入OpenCV库
import cv2
# 读取图像文件(默认以BGR三通道格式加载)
# './images/nezha.png'是图像路径,请确保文件存在
img = cv2.imread('./images/nezha.png')
# 检查图像是否成功加载
if img is None:
print("错误:图像加载失败,请检查路径!")
exit()
# 将BGR彩色图像转换为灰度图像(单通道)
# COLOR_BGR2GRAY表示从BGR颜色空间转换到灰度空间
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 对灰度图像进行高斯模糊处理
# 参数说明:
# img_gray: 输入的灰度图像
# (3,3): 高斯核大小(宽度和高度,必须是正奇数)
# 0: 高斯核在X和Y方向的标准差(0表示自动计算)
img_gauss = cv2.GaussianBlur(img_gray, (3, 3), 0)
# Canny边缘检测
# 参数说明:
# img_gauss: 经过高斯模糊的图像
# 50: 低阈值(低于此值的边缘被丢弃)
# 150: 高阈值(高于此值的边缘被保留)
# 说明:介于50-150之间的边缘会根据连通性决定是否保留
img_canny = cv2.Canny(img_gauss, 50, 150)
# 查找图像中的轮廓
# 返回值:
# contours: 检测到的轮廓列表,每个轮廓是点的集合
# hierarchy: 轮廓的层级信息(本示例未使用)
# 参数说明:
# img_canny: 输入的二值图像(Canny边缘检测结果)
# cv2.RETR_EXTERNAL: 只检测最外层轮廓
# cv2.CHAIN_APPROX_SIMPLE: 压缩水平、垂直和对角方向的轮廓,只保留端点
contours, hierarchy = cv2.findContours(img_canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 创建结果图像的副本(避免修改原始图像)
result = img.copy()
# 在结果图像上绘制检测到的轮廓
# 参数说明:
# result: 要绘制轮廓的目标图像
# contours: 轮廓列表
# -1: 表示绘制所有轮廓(如果指定索引,则只绘制对应轮廓)
# (0, 255, 0): 轮廓颜色(BGR格式,这里是绿色)
# 2: 轮廓线宽(像素)
cv2.drawContours(result, contours, -1, (0, 255, 0), 2)
# 显示原始图像
cv2.imshow('原始图像', img)
# 显示灰度图像
cv2.imshow('灰度图像', img_gray)
# 显示高斯模糊后的图像
cv2.imshow('高斯模糊', img_gauss)
# 显示Canny边缘检测结果
cv2.imshow('Canny边缘', img_canny)
# 显示带轮廓标记的结果图像
cv2.imshow('轮廓检测结果', result)
# 等待键盘输入(0表示无限等待,按任意键继续)
cv2.waitKey(0)
# 关闭所有OpenCV创建的窗口
cv2.destroyAllWindows()