胞分割:U-Net從數(shù)據(jù)預(yù)處理到評(píng)估全流程解析)
簡介乳腺癌細(xì)胞分割圖片數(shù)據(jù)集是一套面向醫(yī)學(xué)圖像處理與深度學(xué)習(xí)研究者的病理圖像資源內(nèi)含58張HE染色組織病理學(xué)圖像及配套真實(shí)標(biāo)注主要解決細(xì)胞分割與良惡性分類環(huán)節(jié)的標(biāo)注數(shù)據(jù)需求。壓縮包共232個(gè)文件包括116張tif格式原始切片圖像與116個(gè)xml格式標(biāo)注文件整體大小約93.7MB圖像命名包含病例編號(hào)與良性/惡性信息便于按需篩選。該數(shù)據(jù)集源自常規(guī)蘇木素-伊紅染色流程能清晰展示細(xì)胞核與細(xì)胞質(zhì)形態(tài)細(xì)節(jié)適合用于訓(xùn)練細(xì)胞分割模型、評(píng)估分割算法魯棒性也可作為醫(yī)學(xué)圖像深度學(xué)習(xí)課程的實(shí)戰(zhàn)樣本。已有270人學(xué)習(xí)使用配套XML標(biāo)注提供了準(zhǔn)確的邊界真值可幫助研究者免去人工標(biāo)注成本專注模型設(shè)計(jì)與優(yōu)化是開展病理圖像分析、智慧醫(yī)療方向?qū)嶒?yàn)的高質(zhì)量基礎(chǔ)數(shù)據(jù)。1. 乳腺癌細(xì)胞分割圖片數(shù)據(jù)集58張HE切片夠干什么拿到這份“乳腺癌細(xì)胞分割圖片數(shù)據(jù)集”時(shí)我第一反應(yīng)是只有58張tif但干過醫(yī)學(xué)圖像分割的人都知道這個(gè)規(guī)模恰恰是驗(yàn)證訓(xùn)練流程最舒服的量級(jí)。它是HE染色組織病理學(xué)圖像附帶標(biāo)注用于檢測乳腺癌細(xì)胞任務(wù)邊界很明確先做細(xì)胞分割把每個(gè)細(xì)胞從組織背景里摳出來再做良性惡性分類。常規(guī)組織學(xué)切片里細(xì)胞基本透明必須用蘇木素和伊紅染色才能看到結(jié)構(gòu)所以這類數(shù)據(jù)的顏色信息不是裝飾而是先驗(yàn)。適合正在搭病理分割pipeline、準(zhǔn)備用U-Net做第一次細(xì)胞分割實(shí)驗(yàn)的從業(yè)者也適合想快速驗(yàn)證圖像分割算法在真實(shí)醫(yī)學(xué)數(shù)據(jù)上表現(xiàn)的團(tuán)隊(duì)。2. 讀懂HE染色與文件名編碼先學(xué)會(huì)看片再談?dòng)?xùn)練2.1 為什么HE染色是醫(yī)學(xué)圖像分割繞不開的起點(diǎn)HE染色的原理不難但直接決定了你怎么讀這張圖。蘇木素帶正電荷會(huì)和細(xì)胞核里帶負(fù)電的核酸結(jié)合把細(xì)胞核染成藍(lán)紫色伊紅帶負(fù)電荷與細(xì)胞質(zhì)中帶正電的蛋白質(zhì)結(jié)合把細(xì)胞質(zhì)和間質(zhì)染成粉紅色。于是藍(lán)紫色區(qū)域基本就是細(xì)胞核候選區(qū)粉紅色區(qū)域是細(xì)胞質(zhì)和背景組織。對(duì)分割模型來說顏色通道不是冗余信息而是最直接的監(jiān)督信號(hào)。import tifffile as tiff import numpy as np image tiff.imread(ytma49_042203_benign2_ccd.tif) print(shape:, image.shape, dtype:, image.dtype) print(channel均值:, image.mean(axis(0, 1)))這段代碼先把tif讀進(jìn)來打印形狀、數(shù)據(jù)類型和每個(gè)通道的均值。shape如果是(H, W, 3)說明是RGB三通道病理tif多數(shù)是這個(gè)布局打印dtype是為了確認(rèn)是不是uint8有些掃描儀存的是uint16后面歸一化時(shí)除以255還是65535要區(qū)分。很多初學(xué)者拿到HE圖直接轉(zhuǎn)灰度等于把核質(zhì)對(duì)比度這個(gè)最重要的先驗(yàn)丟掉了后面模型再怎么調(diào)也很難收斂出干凈的細(xì)胞邊界。正確做法是把RGB三通道都保留在歸一化的時(shí)候單獨(dú)處理。另外病理圖像的分割難點(diǎn)在于細(xì)胞核經(jīng)常粘連重疊邊界模糊加上染色深淺不一這不是單純調(diào)模型結(jié)構(gòu)能解決的而是要同時(shí)考慮染色歸一化、數(shù)據(jù)增強(qiáng)和損失函數(shù)設(shè)計(jì)。2.2 從文件名拆解數(shù)據(jù)組織方式患者、取材塊與良惡性文件名本身就是一份標(biāo)注文檔學(xué)會(huì)讀它比翻說明文檔更快。ytma49_042203_benign2_ccd.tif這個(gè)命名里有幾層信息我拆一下字段示例通常含義ytma49ytma49患者/蠟塊編號(hào)同一編號(hào)代表同一來源042203042203取材位置或坐標(biāo)編號(hào)benign2benign2良性樣本第2個(gè)視野/組織塊malignant3malignant3惡性樣本第3個(gè)視野/組織塊ccdccd采集設(shè)備標(biāo)識(shí)常見于病理掃描儀tif.tif無損圖像格式保留完整細(xì)節(jié)這里要特別強(qiáng)調(diào)benign和malignant是圖像級(jí)別的標(biāo)簽標(biāo)注的是這張視野對(duì)應(yīng)的組織來源是良性還是惡性不代表圖像里所有細(xì)胞都屬于同一類。實(shí)際做分類任務(wù)時(shí)以文件名中的benign/malignant作為弱標(biāo)簽是可以的但如果做像素級(jí)分割這個(gè)標(biāo)簽?zāi)芙o你的是圖像級(jí)線索不是每個(gè)像素的類別。這個(gè)區(qū)分在第5章還會(huì)再踩一遍。文件名的規(guī)律性決定了數(shù)據(jù)加載的寫法??吹酵磺熬YytmaXX多次出現(xiàn)就要意識(shí)到數(shù)據(jù)是按患者分組的后續(xù)劃分訓(xùn)練集驗(yàn)證集時(shí)不能隨機(jī)亂切否則同一個(gè)患者的內(nèi)容會(huì)同時(shí)出現(xiàn)在訓(xùn)練和驗(yàn)證里指標(biāo)虛高到?jīng)]法看。2.3 數(shù)據(jù)集的三個(gè)硬邊界規(guī)模、標(biāo)注與適用場景先說規(guī)模。58張圖在深度學(xué)習(xí)里確實(shí)不算多但它的價(jià)值不在樣本量而在數(shù)據(jù)質(zhì)量。HE染色的病理切片每張tif分辨率通常很高可以切出幾十上百個(gè)patch數(shù)據(jù)量可以成倍放大。這個(gè)規(guī)模很適合跑通一套“讀取-預(yù)處理-訓(xùn)練-評(píng)估”的完整流程也適合做遷移學(xué)習(xí)實(shí)驗(yàn)但不適合從零訓(xùn)練一個(gè)大參數(shù)量模型。再說標(biāo)注。項(xiàng)目描述里的“真實(shí)數(shù)據(jù)”指的是能用于監(jiān)督學(xué)習(xí)的ground truth標(biāo)注。用它做細(xì)胞分割需要把每張圖對(duì)應(yīng)的掩碼找出來配對(duì)配上訓(xùn)練腳本后就能得到像素級(jí)的分割監(jiān)督信號(hào)。第三說適用場景這套數(shù)據(jù)的任務(wù)鏈條是“細(xì)胞分割→良惡性分類”最合適的模型路線是先分割出細(xì)胞區(qū)域再做統(tǒng)計(jì)特征或分類。直接拿58張圖端到端訓(xùn)練一個(gè)良惡性分類器會(huì)吃樣本量不足的虧效果遠(yuǎn)不如“先分割再提取特征”的兩階段方案穩(wěn)。3. 把tif原圖變成訓(xùn)練對(duì)讀取、染色歸一化與Patch提取3.1 數(shù)據(jù)組織方式與tif文件讀取拿到數(shù)據(jù)集先別急著訓(xùn)練第一步是把目錄結(jié)構(gòu)摸清楚。常見做法是圖像和ground truth分開兩個(gè)文件夾或者掩碼和原圖同名、后綴改成_mask.tif、_gt.tif。我一般會(huì)先寫一個(gè)清單腳本把目錄下所有文件列出來看命名規(guī)律再寫配對(duì)邏輯。import os from pathlib import Path data_dir Path(./breast_cancer_cells) image_files sorted(data_dir.glob(*_ccd.tif)) mask_files sorted(data_dir.glob(*_mask.tif)) print(原圖數(shù)量:, len(image_files)) print(掩碼數(shù)量:, len(mask_files)) print(前5個(gè)原圖:, [f.name for f in image_files[:5]]) for img_path in image_files: mask_path data_dir / img_path.name.replace(_ccd.tif, _mask.tif) if not mask_path.exists(): print(缺少掩碼:, img_path.name)這段代碼的核心是檢查原圖和掩碼是否一一對(duì)應(yīng)。用glob匹配*_ccd.tif拿到全部原圖再試著把_ccd.tif替換成_mask.tif去找掩碼找不到就打印出來。這一步能提前暴露文件名不統(tǒng)一、掩碼缺失、后綴混亂的問題比訓(xùn)練到一半才發(fā)現(xiàn)數(shù)據(jù)對(duì)不上要省時(shí)間得多。如果實(shí)際掩碼后綴不是_mask.tif把replace那行的后綴按實(shí)際情況改掉就行。我習(xí)慣把文件配對(duì)做成一個(gè)config而不是散落在各個(gè)腳本里這樣后面切patch、做歸一化、訓(xùn)練加載都用同一份配置不會(huì)出現(xiàn)訓(xùn)練和推理用了不同文件列表的低級(jí)錯(cuò)誤。3.2 顏色歸一化解決切片之間的染色差異病理切片最折磨人的問題就是染色不均。同一批切片不同批次染色、不同掃描儀出來的顏色深淺能差出一大截。模型如果只在一種染色風(fēng)格上訓(xùn)練換一批數(shù)據(jù)性能就崩這是醫(yī)學(xué)圖像分割里最常見的泛化性問題。import cv2 import numpy as np def reinhard_normalize(img, target_mean, target_std): lab cv2.cvtColor(img, cv2.COLOR_RGB2LAB) for i in range(3): mean lab[..., i].mean() std lab[..., i].std() lab[..., i] ((lab[..., i] - mean) / (std 1e-6) * target_std[i] target_mean[i]) lab np.clip(lab, 0, 255).astype(np.uint8) return cv2.cvtColor(lab, cv2.COLOR_LAB2RGB) # target_mean和target_std用全部訓(xùn)練圖逐通道統(tǒng)計(jì)得到 target_mean [135.0, 145.0, 155.0] target_std [45.0, 40.0, 35.0] image_norm reinhard_normalize(image, target_mean, target_std)這段實(shí)現(xiàn)的是Reinhard顏色歸一化思路是把圖像從RGB轉(zhuǎn)到LAB空間在LAB里逐通道對(duì)齊均值和標(biāo)準(zhǔn)差。LAB空間的L通道管亮度、A和B通道管顏色分開對(duì)齊不容易破壞組織結(jié)構(gòu)的對(duì)比度。轉(zhuǎn)回RGB后整張圖的染色風(fēng)格就被拉到接近目標(biāo)分布。參數(shù)里target_mean和target_std不是拍腦袋定的正確做法是把所有訓(xùn)練原圖讀進(jìn)來逐通道統(tǒng)計(jì)LAB空間的均值和標(biāo)準(zhǔn)差再取平均。實(shí)際項(xiàng)目里有的是參考一張標(biāo)準(zhǔn)切片有的用全部訓(xùn)練數(shù)據(jù)的統(tǒng)計(jì)量。這個(gè)歸一化操作必須在切patch之前做因?yàn)橥惣?xì)胞的染色一致性是后續(xù)分割效果的基礎(chǔ)。3.3 Patch切分與按患者劃分的訓(xùn)練驗(yàn)證集病理圖像分辨率高整張圖直接進(jìn)模型顯存必然爆常規(guī)做法是切成patch。這里有兩個(gè)關(guān)鍵參數(shù)patch大小和stride。patch太小區(qū)分上下文不夠太大又吃顯存stride等于patch大小時(shí)無重疊小于patch大小時(shí)有重疊能緩解邊緣信息丟失。import numpy as np def extract_patches(image, mask, patch_size256, stride256): patches_img, patches_mask [], [] h, w image.shape[:2] for y in range(0, h - patch_size 1, stride): for x in range(0, w - patch_size 1, stride): img_patch image[y:y patch_size, x:x patch_size] mask_patch mask[y:y patch_size, x:x patch_size] if mask_patch.sum() 10: continue patches_img.append(img_patch) patches_mask.append(mask_patch) return np.array(patches_img), np.array(patches_mask)這段代碼有兩個(gè)關(guān)鍵點(diǎn)。第一用stride控制切塊密度stride256時(shí)塊與塊不重疊適合細(xì)胞密度均勻的圖如果細(xì)胞分布稀疏可以設(shè)成128讓塊與塊重疊50%增加細(xì)胞被完整包含的概率。第二mask_patch.sum() 10的過濾條件是必須的病理圖有大片空白背景這種純背景patch會(huì)拉低訓(xùn)練效率過濾閾值按實(shí)際掩碼數(shù)值范圍調(diào)整。劃分訓(xùn)練驗(yàn)證集合時(shí)必須按患者編號(hào)分組這是這套數(shù)據(jù)集最容易踩的坑。from sklearn.model_selection import GroupKFold groups [img_path.name.split(_)[0] for img_path in image_files] gkf GroupKFold(n_splits5) for train_idx, val_idx in gkf.split(image_files, groupsgroups): train_files [image_files[i] for i in train_idx] val_files [image_files[i] for i in val_idx] breaksplit傳進(jìn)去的第二個(gè)參數(shù)是groups也就是每個(gè)樣本所屬的患者編號(hào)。GroupKFold會(huì)保證同一個(gè)患者的圖不會(huì)同時(shí)出現(xiàn)在訓(xùn)練和驗(yàn)證集合里這是從源頭上防數(shù)據(jù)泄漏。否則模型在驗(yàn)證集上見過同一患者的內(nèi)容指標(biāo)虛高橫向?qū)Ρ群翢o意義。拿到這套數(shù)據(jù)后第一步就是把所有文件名前綴跑一遍確認(rèn)患者數(shù)量再看分組數(shù)夠不夠支撐交叉驗(yàn)證。4. 用U-Net做細(xì)胞分割模型選型、損失函數(shù)與評(píng)估4.1 為什么U-Net是這個(gè)小數(shù)據(jù)集的穩(wěn)妥選擇58張圖、像素級(jí)分割這個(gè)場景下U-Net是相當(dāng)穩(wěn)的選擇不是因?yàn)樗钚露且驗(yàn)樗慕Y(jié)構(gòu)正好匹配小樣本醫(yī)學(xué)圖像分割的痛點(diǎn)。U-Net的編碼器逐層下采樣提取高層語義解碼器逐步上采樣恢復(fù)分辨率中間的跳躍連接把編碼器各層的細(xì)節(jié)特征直接拼給解碼器讓模型在恢復(fù)細(xì)胞邊界時(shí)能同時(shí)看到淺層的邊緣信息和深層的語義信息。相比之下FCN對(duì)淺層細(xì)節(jié)利用不足細(xì)胞邊界容易糊成一片DeepLab用空洞卷積擴(kuò)大感受野但空洞卷積在小目標(biāo)密集的場景下對(duì)邊界的定位精度反而不如U-Net的跳躍連接直接。細(xì)胞分割是密集預(yù)測任務(wù)邊界質(zhì)量直接決定分割質(zhì)量。在樣本量只有幾十張的情況下U-Net的參數(shù)量也適中不容易過擬合得收不住。醫(yī)學(xué)圖像分割社區(qū)里對(duì)U-Net的變體研究非常多比如Attention U-Net、ResUNet、U-Net。但我的建議是先在基礎(chǔ)U-Net上把數(shù)據(jù)流程和訓(xùn)練參數(shù)跑通再考慮變體。數(shù)據(jù)沒準(zhǔn)備好換再花哨的網(wǎng)絡(luò)結(jié)構(gòu)都是白搭。4.2 訓(xùn)練配置與組合損失函數(shù)訓(xùn)練配置我直接給一份能用的參數(shù)表這是多次實(shí)驗(yàn)后比較穩(wěn)定的組合。參數(shù)取值說明輸入尺寸256×256細(xì)胞核尺度小無需更大patchbatch size8單卡12GB顯存可承受優(yōu)化器AdamW權(quán)重衰減設(shè)1e-5防過擬合初始學(xué)習(xí)率1e-4配合ReduceLROnPlateau調(diào)度訓(xùn)練輪數(shù)50早停patience設(shè)10損失函數(shù)BCE Dice權(quán)重0.5/0.5分割任務(wù)最常用的損失函數(shù)是二元交叉熵和Dice loss的組合。BCE逐像素計(jì)算分類損失對(duì)每個(gè)像素一視同仁Dice loss從區(qū)域重疊角度優(yōu)化緩解類別不平衡。細(xì)胞分割里背景像素遠(yuǎn)多于細(xì)胞像素只用BCE會(huì)讓模型偏向預(yù)測背景加上Dice loss能強(qiáng)制模型關(guān)注前景區(qū)域。import torch import torch.nn as nn class CombinedLoss(nn.Module): def __init__(self, bce_weight0.5, dice_weight0.5): super().__init__() self.bce nn.BCEWithLogitsLoss() self.bce_weight bce_weight self.dice_weight dice_weight def forward(self, logits, targets): bce_loss self.bce(logits, targets) probs torch.sigmoid(logits) smooth 1e-6 inter (probs * targets).sum() dice_loss 1 - (2 * inter smooth) / (probs.sum() targets.sum() smooth) return self.bce_weight * bce_loss self.dice_weight * dice_loss這個(gè)實(shí)現(xiàn)里logits是模型輸出的原始值targets是二值掩碼。BCE用的是帶logits的版本數(shù)值上更穩(wěn)定不會(huì)因?yàn)閟igmoid后概率接近0或1導(dǎo)致梯度消失。Dice loss里的smooth是平滑項(xiàng)防止分子分母都是0時(shí)出現(xiàn)除零錯(cuò)誤。bce_weight和dice_weight控制兩個(gè)損失的權(quán)重如果發(fā)現(xiàn)訓(xùn)練初期Dice loss波動(dòng)大可以把dice_weight降到0.3BCE權(quán)重提到0.7穩(wěn)定后再調(diào)回來。數(shù)據(jù)增強(qiáng)方面病理圖像和自然圖像有個(gè)重要區(qū)別隨機(jī)旋轉(zhuǎn)90度、上下左右翻轉(zhuǎn)是安全的因?yàn)榻M織沒有方向性但隨機(jī)裁剪、縮放這類幾何變換以及過強(qiáng)的顏色擾動(dòng)在低倍鏡下可能改變細(xì)胞核的相對(duì)尺度關(guān)系或者制造出染色風(fēng)格不一致的假樣本反而讓模型學(xué)歪。我一般只做旋轉(zhuǎn)、翻轉(zhuǎn)、微弱亮度抖動(dòng)顏色增強(qiáng)用HED空間擾動(dòng)比簡單的HSV調(diào)色更符合病理圖像的染色退化規(guī)律。4.3 Dice和IoU在重疊細(xì)胞場景下怎么用評(píng)估指標(biāo)上Dice和IoU是醫(yī)學(xué)圖像分割報(bào)告里必寫的兩個(gè)數(shù)。Dice是兩倍交集除以并集長度的兩倍之和IoU是交集除以并集。兩者高度相關(guān)但行為有細(xì)微差別IoU對(duì)錯(cuò)誤的懲罰更嚴(yán)格同樣的預(yù)測結(jié)果IoU數(shù)值會(huì)比Dice低一截。def dice_coef(pred, target, smooth1e-6): pred (pred 0.5).astype(np.float32) inter (pred * target).sum() return (2 * inter smooth) / (pred.sum() target.sum() smooth) def iou_score(pred, target, smooth1e-6): pred (pred 0.5).astype(np.float32) inter (pred * target).sum() union pred.sum() target.sum() - inter return (inter smooth) / (union smooth)兩個(gè)函數(shù)都先做閾值化把模型輸出的連續(xù)概率轉(zhuǎn)成二值掩碼再計(jì)算交集。細(xì)胞重疊區(qū)域的像素在mask里本身就是連通的閾值化后如果預(yù)測偏保守Dice下降得比IoU慢看起來還行但I(xiàn)oU已經(jīng)很難看了。所以報(bào)告指標(biāo)時(shí)我習(xí)慣兩個(gè)都寫如果Dice和IoU的差距明顯拉大說明模型預(yù)測偏保守邊界收縮得厲害需要調(diào)低閾值或者檢查后處理。細(xì)胞分割還有一個(gè)常見誤區(qū)用像素級(jí)accuracy評(píng)估。因?yàn)楸尘跋袼卣急忍吣P腿A(yù)測成背景也能拿到90%以上的accuracy這個(gè)指標(biāo)在這個(gè)場景下沒有參考價(jià)值。要么用Dice/IoU要么做實(shí)例級(jí)評(píng)估統(tǒng)計(jì)檢測到的細(xì)胞數(shù)、漏檢率和誤檢率。5. 避坑記錄病理圖像分割最容易翻車的四個(gè)環(huán)節(jié)5.1 驗(yàn)證集指標(biāo)虛高Dice 0.88但可視化全是噪點(diǎn)現(xiàn)象訓(xùn)練到一半驗(yàn)證集Dice一路漲到0.88看著喜人結(jié)果把預(yù)測掩碼疊加到原圖上一看滿屏小噪點(diǎn)細(xì)胞邊界一塌糊涂。原因這個(gè)問題我排查過多次一是同一患者的patch在無分組的情況下同時(shí)進(jìn)了訓(xùn)練集和驗(yàn)證集模型等于“見過”驗(yàn)證數(shù)據(jù)指標(biāo)自然虛高二是沒有做按患者分組數(shù)據(jù)泄漏在醫(yī)學(xué)圖像數(shù)據(jù)集里是重災(zāi)區(qū)。解決第一時(shí)間用3.3節(jié)的GroupKFold按ytma編號(hào)重新劃分?jǐn)?shù)據(jù)把驗(yàn)證指標(biāo)清零重跑。指標(biāo)出來之后必須抽至少5張驗(yàn)證集圖像做可視化人眼確認(rèn)預(yù)測mask和細(xì)胞輪廓對(duì)得上再認(rèn)這個(gè)指標(biāo)。從那以后我每次拿到病理數(shù)據(jù)集都會(huì)先打印患者編號(hào)分布再談?dòng)?xùn)練。5.2 良惡性標(biāo)簽被當(dāng)成像素級(jí)標(biāo)簽訓(xùn)練直接跑偏現(xiàn)象文件名里有benign2、malignant3有人直接把圖像對(duì)應(yīng)的掩碼和良惡性做匹配把整個(gè)patch標(biāo)注成單一類別訓(xùn)練分類器。結(jié)果模型在訓(xùn)練集上loss很低換一個(gè)患者的數(shù)據(jù)就全亂套。原因benign/malignant這個(gè)標(biāo)簽是圖像級(jí)或組織塊級(jí)別的來源標(biāo)注不保證patch內(nèi)所有像素都屬于同一類。病理組織中良性區(qū)域也可能有少量異型細(xì)胞惡性區(qū)域同樣存在正常間質(zhì)。把它當(dāng)成像素級(jí)標(biāo)簽等于用錯(cuò)誤的監(jiān)督信號(hào)訓(xùn)練分割模型。解決把“分割”和“分類”分成兩個(gè)任務(wù)。分割任務(wù)只用掩碼做像素級(jí)監(jiān)督良惡性標(biāo)簽用于分割后的區(qū)域統(tǒng)計(jì)或作為弱監(jiān)督參考。你可以在分割完成后對(duì)每個(gè)連通域提取形狀、灰度、核質(zhì)比等特征再做良惡性判別而不是在分割階段混入圖像級(jí)標(biāo)簽。5.3 全圖輸入顯存爆炸無重疊推理又出現(xiàn)拼接線現(xiàn)象直接把整張tif讀進(jìn)模型12GB顯存報(bào)OOM被迫切patch訓(xùn)練。訓(xùn)練完了用patch推理預(yù)測結(jié)果拼回原圖時(shí)patch邊界出現(xiàn)一條明顯的“拼接線”細(xì)胞在拼接處被切斷。原因OOM不用多說整張圖分辨率太高。拼接線的根源是推理時(shí)patch之間沒有重疊模型只看到當(dāng)前patch的上下文邊界處感受野不足預(yù)測出的掩碼在patch邊緣和中心區(qū)域置信度不一致拼接時(shí)灰度就出現(xiàn)臺(tái)階。解決推理時(shí)采用overlap-tile策略patch重疊部分取平均或加權(quán)平均。重疊區(qū)域權(quán)重可以簡單設(shè)計(jì)成中心高、邊緣低這樣拼接處過渡自然。具體做法在第6章給出實(shí)現(xiàn)我一般設(shè)patch256、stride128重疊50%顯存足夠的情況下重疊越多拼接痕跡越輕。5.4 顏色歸一化之后細(xì)胞核反而看不清了現(xiàn)象做完Reinhard歸一化訓(xùn)練出來的Dice不升反降可視化一看歸一化后的圖像細(xì)胞核和細(xì)胞質(zhì)對(duì)比度明顯下降有些圖藍(lán)紫色都發(fā)灰了。原因歸一化的參考統(tǒng)計(jì)量沒有算對(duì)。有人拿單張圖算target_mean和target_std或者用了灰度圖的統(tǒng)計(jì)量往LAB通道里套把核質(zhì)對(duì)比度洗沒了。更隱蔽的問題是把uint16的圖像直接當(dāng)uint8處理所有數(shù)值都偏移了255倍。解決先把所有訓(xùn)練圖統(tǒng)一轉(zhuǎn)成uint8確認(rèn)通道順序是RGB再逐通道統(tǒng)計(jì)LAB空間的均值和標(biāo)準(zhǔn)差最后取全體訓(xùn)練圖的平均值作為target。歸一化完成后把處理后的圖與原圖并排顯示出來用肉眼看一眼核質(zhì)對(duì)比度有沒有保住。這一步花兩分鐘能給后面省下幾小時(shí)調(diào)試時(shí)間。6. 進(jìn)階驗(yàn)證技巧把分割結(jié)果還原到原圖坐標(biāo)系6.1 重疊Patch預(yù)測的加權(quán)拼接訓(xùn)練時(shí)切了patch推理時(shí)就要把預(yù)測結(jié)果拼回去。直接把預(yù)測掩碼按原始坐標(biāo)放回原圖重疊區(qū)域會(huì)留下拼接線。用帶重疊的tile推理在重疊部分做加權(quán)平均能有效消除邊界跳變。權(quán)重用距離圖中心權(quán)重1.0邊緣權(quán)重0.0這樣patch中心的預(yù)測擁有最高置信度。import numpy as np def reconstruct_mask(model, image, patch_size256, stride128): h, w image.shape[:2] weight_map np.zeros((h, w), dtypenp.float32) pred_map np.zeros((h, w), dtypenp.float32) for y in range(0, h - patch_size 1, stride): for x in range(0, w - patch_size 1, stride): patch image[y:y patch_size, x:x patch_size] pred model.predict(patch[None, ...])[0, ..., 0] patch_weight np.outer( np.hanning(patch_size), np.hanning(patch_size) ) pred_map[y:y patch_size, x:x patch_size] pred * patch_weight weight_map[y:y patch_size, x:x patch_size] patch_weight pred_map pred_map / (weight_map 1e-6) return pred_map這段代碼里np.hanning(patch_size)生成一個(gè)從0到1再回到0的窗函數(shù)np.outer把它擴(kuò)展成二維權(quán)重矩陣中心像素權(quán)重最大。累加預(yù)測時(shí)乘以權(quán)重最后除以總權(quán)重重疊區(qū)域的預(yù)測就按置信度融合了。stride小于patch_size是核心stride256等于沒重疊拼接線肯定還在。6.2 從掩碼到細(xì)胞實(shí)例連通域分析分割出的二值掩碼是像素級(jí)的“細(xì)胞區(qū)域/背景”標(biāo)記但實(shí)際生物學(xué)統(tǒng)計(jì)需要知道有幾個(gè)細(xì)胞、每個(gè)細(xì)胞多大。用連通域分析把掩碼拆成獨(dú)立實(shí)例是分割后最常用的下游處理。from skimage import measure def extract_cell_instances(pred_mask, min_area20): labels measure.label(pred_mask 0.5, connectivity2) props measure.regionprops(labels) for prop in props: if prop.area min_area: labels[labels prop.label] 0 return labels, propsmeasure.label用二值連通性把掩碼分成獨(dú)立編號(hào)的區(qū)域connectivity2表示8鄰域連通適合細(xì)胞核這種形狀不規(guī)則的物體。min_area是過濾小噪點(diǎn)的閾值在256×256的patch下面積小于20像素的區(qū)域大概率是染色雜質(zhì)或預(yù)測噪聲。這個(gè)閾值不是固定的先統(tǒng)計(jì)所有連通域的面積分布再根據(jù)直方圖的低谷位置設(shè)定更合理。到這里每個(gè)細(xì)胞實(shí)例的面積、周長、圓度、質(zhì)心坐標(biāo)都能從regionprops里拿出來后續(xù)做良惡性分類時(shí)這些形態(tài)學(xué)特征比raw pixel特征穩(wěn)定得多。惡性細(xì)胞通常核更大、核質(zhì)比更高、形態(tài)更不規(guī)則這些統(tǒng)計(jì)量可以在實(shí)例級(jí)別計(jì)算。6.3 抽檢可視化流程訓(xùn)練指標(biāo)再漂亮不如花幾分鐘做一次實(shí)際驗(yàn)證。我從一個(gè)固定的驗(yàn)證流程里受益很多隨機(jī)抽10張驗(yàn)證集原圖跑一遍推理拼接把預(yù)測掩碼以半透明紅色疊加到原圖上同時(shí)把原圖、真實(shí)mask、預(yù)測mask并排存成一張對(duì)比圖用肉眼看細(xì)胞邊界對(duì)得準(zhǔn)不準(zhǔn)有沒有粘連的細(xì)胞被錯(cuò)誤分成一個(gè)有沒有細(xì)長偽影被當(dāng)成細(xì)胞。這一步能發(fā)現(xiàn)指標(biāo)根本暴露不了的問題比如模型對(duì)某個(gè)患者編號(hào)的切片特別不適應(yīng)、邊界整體偏膨脹、小細(xì)胞全被吃掉。從那以后我每次跑完訓(xùn)練都強(qiáng)制走一遍這個(gè)流程再?zèng)Q定要不要繼續(xù)調(diào)參。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取