尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python图像分割完全指南:从经典算法到U-Net深度模型

Python图像分割完全指南:从经典算法到U-Net深度模型 简介基于Python实现图像分割算法是一套面向高校学生课程设计及图像处理初学者的完整源码与报告资料包。项目通过设定恰当的阈值将每张图像分割为5070个区域并额外约束任一分割区域的像素个数不少于50个兼顾了分割粒度与区域完整性。资源共45个文件压缩包仅1.38MB核心为2个Python脚本含ImageSegmentation主算法与IOUcalc精度评估、1份Word版课程报告论文以及40张实验过程与结果PNG图像并附带README与LICENSE说明文档结构简洁清晰。已有1843人浏览学习热度可观。从内容预览看资源内置data原始图像、gt真值标签、imgs与output输出目录读者可直接运行代码复现分割效果也可借助IOU计算脚本量化评估算法精度非常适合作为计算机视觉方向课程设计参考或图像分割入门项目的实操范本。1. 图像分割是什么像素级分类这个问题的Python解法工厂的AOI检测屏上一个划痕是否越过了安全线病理切片里要框出癌变区域的范围卫星影像上要把水体从田地中分出来。这些需求落到算法层不是“识别出它是什么”就能交差而是要输出一张同样大小的图把每一个像素标记成背景或目标这就是图像分割——像素级的分类任务。反直觉的地方在于图像分类只要给整张图一个标签分割却要对几十万像素逐个作答所以它看着像“切图”实际是计算机视觉里对标注和数据要求最严苛的方向之一。Python的优势在于转译成本低从OpenCV读图到scikit-image做形态学处理再到PyTorch训练U-Net都在同一个解释型语言里完成。适合理清算法谱系的入门者也适合需要快速建立基线的预研场景。2. 图像分割的Python环境库选型与最小复现命令做图像分割不需要一开始就上深度学习。很多真实项目里目标和背景只要在灰度上可分Otsu加分水岭就能给出一版可量化的结果耗时是毫秒级。深度学习负责处理纹理相似、边缘不清晰的场景。这里先梳理清楚技术谱系再搭环境后续代码才能落在同一个工作目录下。2.1 图像分割算法的三条路线与分界线第一条路线是基于灰度阈值适合直方图呈现明显双峰的场景比如暗色传送带上的亮色工件第二条路线是基于聚类的方法把像素的颜色或特征向量划分到K个簇中适合多类别目标但需要预设类别数第三条路线是深度学习分割网络适合语义信息复杂、边界模糊的自然图像或医学影像。分界线可以这样划如果仅凭像素颜色差异就能区分目标先用经典方法如果人眼必须理解上下文才能圈出边界直接走深度学习的路。2.2 OpenCV、scikit-image 与 PyTorch 的分工图像分割并不依赖孤儿库三套主流工具链各司其职配套关系如下表库主要用途常用入口适用阶段OpenCV图像读写、滤波、阈值、边缘、形态学cv2.threshold / cv2.watershed / cv2.kmeans经典算法与预处理scikit-image研究型图像处理接口更友好skimage.segmentation.watershed / slic快速验证原型算法PyTorch构建和训练分割网络torch.nn.Conv2d / torch.utils.data.DataLoader深度学习训练与推理NumPy数组索引、类型转换、掩膜运算np.where / arr.astype(np.uint8)所有阶段的通用基础个人习惯是把 OpenCV 当主力图像操作库scikit-image 只在需要 SLIC 这类更高级的分割接口时才引入。功能有重叠但不要混用读取管道一个项目里统一用 cv2.imread 读图统一做 BGR 转 RGB避免颜色通道互换的隐性 bug。2.3 用 venv 搭建可复现的分割实验环境很多 Python 入门教程只讲了 pip install没有强调环境隔离。实际项目里最容易出现的情况是A 项目要求 opencv-python 4.8B 项目升级到 4.10 后分水岭返回类型发生变化代码突然跑不通。所以新机器上第一件事是建虚拟环境而不是全局装包。2.3.1 创建环境并安装依赖# 检查默认 Python 版本3.9 到 3.12 之间均可 python --version # 在项目目录内创建虚拟环境 .venv python -m venv .venv # 激活环境 source .venv/bin/activate # Linux / macOS # .venv\Scripts\activate # Windows PowerShell # 先升级 pip避免旧版本解析依赖出错 pip install --upgrade pip # 安装图像分割主力库 pip install opencv-python scikit-image numpy matplotlib # PyTorch CPU 版本Windows / Linux 通用 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu第一行先确认解释器版本是因为很多旧环境里python指向的可能是 2.7 或系统自带的版本venv 会连带复制这个解释器后续装包会直接出现不兼容。--index-url指定 PyTorch 的 CPU 轮子源分割实验前期跑通代码CPU 版足以支撑 batch size 为 2 的小模型验证。opencv-python装的是预编译轮子自带图像编解码能力不需要单独配置系统依赖。2.3.2 验证三个核心库是否可用import cv2 import numpy as np import skimage import torch print(cv2.__version__) # 4.x print(skimage.__version__) # 0.2x print(torch.__version__) # 2.x # 造一张 64x64 的模拟灰度图验证 OpenCV 阈值函数开箱即用 sim np.full((64, 64), 50, dtypenp.uint8) sim[20:40, 20:40] 180 ret, th cv2.threshold(sim, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) print(ret, np.unique(th))如果是在 VS Code 里开发记得把 Python 解释器指向.venv命令面板里执行 Python: Select Interpreter选项目目录下的那个路径。终端里激活了环境编辑器却还在用全局解释器运行脚本时会频繁出现模块找不到的假故障。提示np.full生成的模拟图直方图非常干净Otsu 算出的阈值在 110 到 120 之间属于正常如果阈值偏离过大检查数组 dtype 是否被默认建成了 float64。3. Python里的经典分割实现Otsu阈值、分水岭与K-Means经典方法在工业界没有被淘汰原因很简单很多任务是目标物体在拍摄参数可控的固定光源下背景和前景的灰度统计差异足够大。此时深度学习方法不划算因为要标注、要训练、要维护。把图像二值化再配合连通域与形态学操作效果已经能用于业务统计。下面的实现按从易到难排列全部可以在没有 GPU 的机器上运行。3.1 Otsu阈值在优化什么类间方差最大Otsu 做的事情是在 0 到 255 的灰度范围里遍历所有候选阈值选出使分割后两类类间方差最大的取值。类间方差越大说明前景与背景的均值离得越远分割置信度越高。它的参数不在算法内部而在使用方式上cv2.threshold的第二个参数在THRESH_OTSU模式下会被忽略算法自行计算最优阈值并通过第一个返回值传出。3.2 分水岭把二值图看成地形图分水岭的思路是把灰度图当成高度图像素值越高代表该点海拔越高梯度大的位置形成山脊属于同一物体的区域成为“积水盆地”。算法必须依赖 markers 参数指定种子点否则会把每个局部极小值都当成盆地源头产生严重过分割。合理做法是先做形态学腐蚀让前景区域保留可靠的核心点把这些点作为前景标记。提示分水岭最常见的错误是直接拿梯度图当输入。正确做法是拿距离变换结果作为地形高度标记图来自形态学处理。3.3 K-Means对图像做了什么把每个像素的 RGB 或 Lab 颜色看作三维空间里的点聚成 K 个簇每个簇的中心就是该类别的主色。为什么用 Lab 而不是 RGBRGB 三个通道相关性高颜色距离的欧氏度量不直观Lab 的 L 通道与 a、b 通道解耦空间距离更接近人的颜色感知。K-Means 需要预设 K 值实际项目中先观察颜色分布再取 K 为目标类别数加 1背景单独占一个簇。3.4 三种算法的一次性实现与对比表格把一张硬币图同时用三种算法跑一遍直观看出各自掩膜的差异。import cv2 import numpy as np from skimage.feature import peak_local_max from skimage.segmentation import watershed from scipy import ndimage img_bgr cv2.imread(coins.png) img_rgb cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) gray cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) # ---------- 1. Otsu 阈值 ---------- _, th cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU) # THRESH_BINARY_INV 让前景为白配合分水岭时符合距离变换习惯 # ---------- 2. 分水岭 ---------- dist cv2.distanceTransform(th, cv2.DIST_L2, 3) # 距离变换值越大说明该像素离背景边界越远 coords peak_local_max(dist, min_distance15) markers np.zeros(dist.shape, dtypenp.int32) markers[tuple(coords.T)] 1 _, labels watershed(-dist, markers, return_valuesTrue) # 对距离图取负山峰变山谷标记点变成盆地种子 # ---------- 3. K-Means ---------- h, w gray.shape pixel_vals gray.reshape((-1, 1)).astype(np.float32) # astype 是 python 类型转换在 numpy 数组上的表现kmeans 必须吃 float32 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2) _, best_labels, centers cv2.kmeans(pixel_vals, 2, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS) kmeans_mask best_labels.reshape(h, w).astype(np.uint8)分水岭里min_distance15的含义只有局部极大值点与更近的其他极大值点间距大于 15 像素时才被认定为独立种子点。粘连物体多时调大到 30物体容易被拆开时调小到 8。KMEANS_RANDOM_CENTERS是初始中心选择策略attempts10表示从 10 次随机初始化中挑误差最小的一次保证结果可复现。astype(np.float32)这一步是 python 类型转换在图像数组上的典型用法kmeans 对 uint8 数据做距离计算会出现截断必须转浮点。三种算法放在同一段代码里是为了直接对比掩膜差异。实际项目选型逻辑是Otsu 用于确认灰度直方图是否可分分水岭用于把已二值化的前景拆成独立个体K-Means 跳过灰度二值化直接以颜色聚类。输出 mask 之后下一步通常是cv2.connectedComponentsWithStats统计每个连通域的面积与个数用来做颗粒计数、缺陷数量统计这类业务指标。算法核心 API关键参数适合场景主要局限Otsu 阈值cv2.thresholdTHRESH_OTSU光照均匀的工业前景/背景分离对阴影和光照渐变敏感分水岭skimage.segmentation.watershedmarkers, min_distance粘连颗粒计数、细胞核分割过分割需要形态学预处理K-Meanscv2.kmeansK, attempts颜色差异明显的多类别分割类别数需要人工确定4. 用PyTorch实现一个最小U-Net分割网络当目标与背景的边界必须靠上下文信息推断时比如肿瘤在CT影像中和周围软组织灰度接近经典方法的效果就到头了这时轮到卷积网络。分割网络的可选范围不大U-Net 是绝大多数项目的第一个基线。4.1 U-Net为什么是分割默认基线U-Net 的结构是编码器-解码器加跳连编码器通过卷积和池化逐步缩小特征图提取越来越抽象的语义解码器把特征图逐步恢复到原图分辨率。没有跳连的话解码器只能从压缩后的特征里恢复细节小目标边缘会丢失。跳连把编码器同一层的特征直接拼到解码器中把“这里是边缘”这类低层信息保留下来。对医学影像、工业缺陷这类小样本数据U-Net 参数量在百万级配合数据增强就能训练这是它比 DeepLab 更适合做第一个基线的关键原因。4.2 最小U-Net的PyTorch实现import torch import torch.nn as nn class DoubleConv(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.conv nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), ) def forward(self, x): return self.conv(x) class MiniUNet(nn.Module): def __init__(self, in_ch3, out_ch2): super().__init__() # 编码器 self.enc1 DoubleConv(in_ch, 32) self.enc2 DoubleConv(32, 64) self.pool nn.MaxPool2d(2) # 瓶颈 self.bottleneck DoubleConv(64, 128) # 解码器 self.up2 nn.ConvTranspose2d(128, 64, 2, stride2) self.dec2 DoubleConv(128, 64) self.up1 nn.ConvTranspose2d(64, 32, 2, stride2) self.dec1 DoubleConv(64, 32) # 输出层 self.out nn.Conv2d(32, out_ch, 1) def forward(self, x): e1 self.enc1(x) e2 self.enc2(self.pool(e1)) b self.bottleneck(self.pool(e2)) d2 self.dec2(torch.cat([self.up2(b), e2], dim1)) d1 self.dec1(torch.cat([self.up1(d2), e1], dim1)) return self.out(d1)torch.cat(..., dim1)是跳连的核心dim1 指通道维拼接特征图宽高不变。nn.ConvTranspose2d负责把空间尺寸放大一倍卷积核为 2、步长为 2 时输入输出尺寸有确定对应关系不产生碎片。各层张量尺寸变化如下模块输入张量输出张量enc1(B, 3, H, W)(B, 32, H, W)pool(B, 32, H, W)(B, 32, H/2, W/2)enc2(B, 32, H/2, W/2)(B, 64, H/2, W/2)bottleneck(B, 64, H/4, W/4)(B, 128, H/4, W/4)dec2(B, 128, H/4, W/4) 与 (B, 64, H/2, W/2)(B, 64, H/2, W/2)dec1(B, 64, H/2, W/2) 与 (B, 32, H, W)(B, 32, H, W)out(B, 32, H, W)(B, C, H, W)输入尺寸必须是 4 的倍数否则池化两步后尺寸取整会错位跳连拼接时张量宽高对不上。4.3 训练循环与DiceLoss分割任务里前景区域往往只占图像几个百分点交叉熵会偏向背景类。Dice Loss 直接优化预测掩膜与真实掩膜的重叠率对类别不平衡不敏感。def dice_loss(logits, mask): probs torch.softmax(logits, dim1) # 取前景通道背景类通常不参与损失计算 p probs[:, 1, :, :].contiguous().view(-1) t (mask 1).float().view(-1) intersection (p * t).sum() return 1 - (2 * intersection 1) / (p.sum() t.sum() 1) model MiniUNet(in_ch3, out_ch2) opt torch.optim.Adam(model.parameters(), lr1e-3) for x, y in train_loader: opt.zero_grad() out model(x) loss dice_loss(out, y) loss.backward() opt.step()1是平滑项两图完全没有交集时避免分母为 0也让损失曲线更平滑。lr1e-3是 Adam 在分割任务里的常用起点观察到损失前几个 epoch 下降缓慢时把学习率改到 1e-4 再观察。4.4 推理阶段为什么要先softmax再取argmax模型输出通道数为类别数时每个通道对应一类得分。直接对通道维取最大值得到类别索引但缺少置信度概念先经过 softmax 得到归一化概率分布可以同时输出概率图用于后续阈值调整。model.eval() with torch.no_grad(): logits model(x_batch) # (B, 2, H, W) probs torch.softmax(logits, dim1) pred torch.argmax(probs, dim1) # (B, H, W)5. 用逐类IoU验证分割效果先给结果挑错再调参跑通掩膜只是开始真正决定项目能不能上线的是量化指标。分类里常用的 accuracy 在分割里会欺骗人前景只占 5% 时全预测背景也有 95% 准确率。分割必看 IoU而逐类计算的 IoU 能定位出到底是哪一类在拖后腿。def iou_per_class(mask, pred, num_classes2): ious [] for c in range(num_classes): gt (mask c) pd (pred c) inter (gt pd).sum() union (gt | pd).sum() ious.append(inter / union if union 0 else 1.0) return ious逐类输出的第二重价值在诊断裂缝类 IoU 低说明缺陷像素太细、边缘预测抖动大优先加大裁剪尺寸和翻转增强背景类 IoU 低通常不是背景本身的问题而是前景误检扩散优先检查后处理里有没有做最小连通域过滤。统计指标时还有两个容易被忽略的点一是验证集上的 mask 与预测图必须保持同一个插值方式直接用模型输出的 logits 上采样会引入边缘偏移二是计算 IoU 前先统一类别编号顺序PyTorch 的 argmax 输出和标注文件的 0/1 定义必须对齐。调参方向确定后还有个便宜好用的增强手段把训练图随机裁剪到 256x256并做水平垂直翻转这比堆更多网络层更直接。验证时用model.eval()配合torch.no_grad()前者关闭 dropout 和 batchnorm 的统计更新后者不保存计算图两者缺一个都会让指标失真。本文还有配套的精品资源点击获取
返回列表