用少量区域标注训练并复用像素分割器

用少量区域标注训练并复用像素分割器

如果能在图像上指出几块“属于同一类”的区域,就可以用这些像素训练一个分类器,再让它给其余像素分配标签。scikit-image 的这个实例不依赖预训练大模型,而是把多尺度局部特征与 scikit-learn 的随机森林组合起来,完成可训练的像素分割。

本文译编自 scikit-image 官方 Trainable segmentation using local features and random forests,维护方为 scikit-image team,原页未单列可确认的个人作者。核验基线为页面标注的 0.26.0,同时核对了官方 v0.26.0 完整示例源码。本文没有安装或执行算法,所有代码仅作静态审查。

在示意网格中用四种标签标注局部区域;图像经多尺度强度与纹理特征转换后训练随机森林,最后用同一特征函数预测另一裁剪区。图形不是真实分割结果
局部标注到像素分类的过程。未完纪原创示意图,不是 skin 图像,也不是算法运行结果。

从稀疏标注学习像素类别

这个方法先为每个像素计算不同尺度上的局部信息,再使用用户提供的标签图来选择训练样本。标签大于或等于 1 的像素进入训练集,标签 0 表示尚未标注,等待分类器预测。原文把这一思路与 ilastik 的像素分类、ImageJ 的 Trainable Weka Segmentation 联系起来。

输入是 data.skin() 提供的公开教学显微图像。本例只做图像区域分类,没有建立医学诊断模型,也没有把标签 1、2、3、4 定义成临床类别。首次获取该数据可能涉及数据下载和缓存;复现前应确认数据来源与网络条件,不把私人图像混入教学输入。

下面各代码块按顺序组成原示例。导入、裁剪和标签坐标保留不变,只翻译注释及绘图标题:

import numpy as np
import matplotlib.pyplot as plt
from skimage import data, segmentation, feature, future
from sklearn.ensemble import RandomForestClassifier
from functools import partial

full_img = data.skin()
img = full_img[:900, :900]

# 构造训练标签。本例用矩形;交互式绘图库也可用于绘制掩码。
training_labels = np.zeros(img.shape[:2], dtype=np.uint8)
training_labels[:130] = 1
training_labels[:170, :400] = 1
training_labels[600:900, 200:650] = 2
training_labels[330:430, 210:320] = 3
training_labels[260:340, 60:170] = 4
training_labels[150:200, 720:860] = 4

训练区取完整图的左上 900×900 像素。标签数组只包含空间维度,不包含 RGB 通道维度。前两次赋值把顶部区域标为第 1 类;其余矩形分别标为第 2、3、4 类,第 4 类由两块分开的区域组成。没有赋值的像素仍为 0,它们不是“第 0 类背景”,也不会作为一个额外类别参与监督训练。

坐标与这张图片绑定。换一张图后照搬这些矩形,可能会把完全不同的对象标为同一类。实际标注应覆盖类内变化、边缘附近和易混淆区域,并核对矩形没有跨越不属于同一类的结构。矩形只是本例便于展示的标注方式,不是算法要求。

提取多尺度强度与纹理特征

原文的引言概括了强度、边缘和纹理等局部特征;实际示例的参数是开启强度、关闭边缘、开启纹理。不要把引言中列出的所有特征误认为都已启用。

sigma_min = 1
sigma_max = 16
features_func = partial(
    feature.multiscale_basic_features,
    intensity=True,
    edges=False,
    texture=True,
    sigma_min=sigma_min,
    sigma_max=sigma_max,
    channel_axis=-1,
)
features = features_func(img)

partial 把参数绑定到一个可复用的特征函数。后续预测另一块图像时必须使用同一个函数,让训练和预测得到相同种类、相同顺序的特征。channel_axis=-1 表示最后一个维度是颜色通道,适用于这里的 RGB 图;对灰度图或不同通道布局,应重新核对参数。

尺度范围从 σ=1 到 σ=16。原文后面的可视化使用 1、2、4、8、16 这些尺度,帮助比较小范围与更大范围的局部信息。特征不是只在已标注像素上计算,而是为整块图像计算,以便稍后对每个位置预测。高分辨率、多通道或更大尺度集合都会增加内存与计算量。

训练随机森林,并查看整幅裁剪区的分割

clf = RandomForestClassifier(
    n_estimators=50,
    n_jobs=-1,
    max_depth=10,
    max_samples=0.05,
)
clf = future.fit_segmenter(training_labels, features, clf)
result = future.predict_segmenter(features, clf)

fig, ax = plt.subplots(
    1, 2, sharex=True, sharey=True, figsize=(9, 4)
)
ax[0].imshow(
    segmentation.mark_boundaries(img, result, mode='thick')
)
ax[0].contour(training_labels)
ax[0].set_title('图像、标注与分割边界')
ax[1].imshow(result)
ax[1].set_title('分割标签')
fig.tight_layout()

森林包含 50 棵树,每棵树最大深度为 10。n_jobs=-1 允许使用全部可用处理器资源;默认自助采样启用时,max_samples=0.05 控制每棵树抽取的训练样本数,而不是说整个算法只计算 5% 图像像素的特征。

fit_segmenter 用标签中的已标注位置训练分类器,返回训练后的对象;predict_segmenter 使用该对象预测特征对应的像素类别。左图把预测边界覆盖在原裁剪图上,并画出训练标签的轮廓;右图直接显示预测标签。这里检查的是训练图所在裁剪区的结果,不能从“边界看起来合理”推出对独立样本同样有效。

复现补充:原例没有设置 random_state,不同运行可能得到不同的随机森林结果。如果需要比较参数,可以固定种子,例如在原构造器中加入 random_state=0,并按机器容量把 n_jobs 调小。这两项属于编辑建议,不是本次已运行验证过的改动,也不能单靠固定种子保证跨库版本逐位一致。

特征重要性可以帮助理解,不能直接替代验证

原文接着读取分类器的 feature_importances_,把它按 RGB 三个通道分组。每个尺度在这套参数下包含一项强度特征和两项二维纹理特征,因此绘图使用步长为 3 的切片:

fig, ax = plt.subplots(1, 2, figsize=(9, 4))
l = len(clf.feature_importances_)
feature_importance = (
    clf.feature_importances_[: l // 3],
    clf.feature_importances_[l // 3 : 2 * l // 3],
    clf.feature_importances_[2 * l // 3 :],
)
sigmas = np.logspace(
    np.log2(sigma_min),
    np.log2(sigma_max),
    num=int(np.log2(sigma_max) - np.log2(sigma_min) + 1),
    base=2,
    endpoint=True,
)
for ch, color in zip(range(3), ['r', 'g', 'b']):
    ax[0].plot(
        sigmas, feature_importance[ch][::3], 'o', color=color
    )
    ax[0].set_title('强度特征')
    ax[0].set_xlabel(r'$\sigma$')

for ch, color in zip(range(3), ['r', 'g', 'b']):
    ax[1].plot(
        sigmas, feature_importance[ch][1::3], 'o', color=color
    )
    ax[1].plot(
        sigmas, feature_importance[ch][2::3], 's', color=color
    )
    ax[1].set_title('纹理特征')
    ax[1].set_xlabel(r'$\sigma$')

fig.tight_layout()

原文报告,其示例图中强度特征的重要性明显高于纹理特征。这是原文的观察,不是本稿重跑得到的结果。很容易据此想删掉纹理特征以节约时间,但原文明确提醒:这样可能带来过拟合,也可能降低区域交界处的分割质量。

这些重要性来自当前模型和当前标注分布,不是每个特征的普遍因果贡献。类别比例、标注位置和相关特征都会影响理解。若要删特征,应在独立留出的数据上比较结果,并专门查看边界区域,而不是只看重要性排名。

上面的分组三等分、每组三步一取也绑定了当前 RGB、二维、关闭边缘特征的配置。如果改为灰度、多光谱、三维图像或开启边缘特征,需要重新确认特征的组织方式;不能继续用同一段绘图切片而假定标签仍然对应。

把分类器用于另一块图像

当图像中对象相似、采集条件也相似时,可以复用已经训练好的分类器。原文演示的是同一张图的另一个区域,而不是一个独立样本:

img_new = full_img[:700, 900:]

features_new = features_func(img_new)
result_new = future.predict_segmenter(features_new, clf)

fig, ax = plt.subplots(
    1, 2, sharex=True, sharey=True, figsize=(6, 4)
)
ax[0].imshow(
    segmentation.mark_boundaries(img_new, result_new, mode='thick')
)
ax[0].set_title('另一裁剪区')
ax[1].imshow(result_new)
ax[1].set_title('分割标签')
fig.tight_layout()
plt.show()

新区域取前 700 行以及第 900 列之后的像素,与左上训练裁剪在像素位置上分开。程序直接使用原来的 features_func 和 clf,没有再次拟合森林。它说明如何把已经训练的分类器用于新位置,并未给出跨患者、跨设备、跨批次或不同成像条件的泛化验证。

要判断模型是否可复用,应让验证集按真正独立的样本或采集批次划分。若训练和验证像素都来自高度相似的邻近区域,结果可能显得过于乐观。原文没有提供定量测试集指标,本稿也没有补造准确率、Dice、IoU 或推理耗时。

版本与执行边界

本例依赖 NumPy、Matplotlib、scikit-image 和 scikit-learn;data.skin() 的数据获取还应按安装版本核对下载依赖与缓存。研究页建议的复现基线是 CPython 3.11、scikit-image 0.26.0、NumPy 2.2.6、SciPy 1.15.3、Matplotlib 3.10.3、scikit-learn 1.7.0 和 pooch 1.8.2。这份组合来自已有的静态依赖核对,不是本次安装生成的锁文件或实测通过环境。

另外,官方 API 文档 明确把 skimage.future 标为实验性接口:版本更新时 API 可能改变,不遵循通常的两个版本弃用周期。依赖 fit_segmenter 与 predict_segmenter 的流程应固定并验证版本,升级时重新检查接口。

静态审查没有发现本例含硬编码秘密、shell 命令拼接或主动覆盖文件的代码。需要注意的是数据获取、全核并行与特征内存成本,以及随机性和验证范围;没有发现这些常见问题不代表已经完成全面漏洞审计。本文没有运行训练、下载 skin 原图或加载不可信的序列化模型;示意图也没有冒充实验产物。

来源与许可:scikit-image team,官方示例;原页 © 2013–2025 the scikit-image team。v0.26.0 仓库对此示例适用的通用许可为 BSD-3-Clause,Copyright © 2009–2022 the scikit-image team;完整适用条款见下方 BSD-3-Clause 许可全文。原文参考 RandomForestClassifier、ilastik 与 ImageJ 的链接已保留。本文为另行许可的中文译编,保留原作者与来源;中文图注、静态补充和原创示意图由未完纪整理。

BSD-3-Clause License 全文
Files: doc/examples/segmentation/plot_trainable_segmentation.py
Copyright: 2009-2022 the scikit-image team
License: BSD-3-Clause

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in
   the documentation and/or other materials provided with the distribution.
3. Neither the name of the University nor the names of its contributors
   may be used to endorse or promote products derived from this software
   without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Source: https://raw.githubusercontent.com/scikit-image/scikit-image/v0.26.0/LICENSE.txt
This is the applicable general BSD-3-Clause notice from the v0.26.0
repository license; the full repository license also lists exceptions
for other files, not copied into this article.
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容