Moth project
image segmentation
Tool
檔案命名規則
# 解壓縮時指定繁體中文字碼
unzip -O big5 file
目視篩選過濾品質不佳樣本
成大(CARS)資料
由於圖片太小,較有切邊情形發生
部分標本過小偵測不到
多樣性中心(SJTT)資料
scale_w = 0.1 if file == "SJTT" else 0.1
scale_ht, scale_hb = (0.0, 0.0) if file == "SJTT" else (0.1, 0.1)
bboxes_ = np.asarray([float(i) for i in row.bboxes.split(',')])
# 改變原本框選範圍
w = bboxes_[2] - bboxes_[0]
h = bboxes_[3] - bboxes_[1]
x = bboxes_[0] + w/2
y = bboxes_[1] + h/2
bboxes = np.asarray([
x - 0.5*(w * (1 + scale_w)), # left
y - 0.5*(h * (1 + scale_ht)), # top
x + 0.5*(w * (1 + scale_w)), # right
y + 0.5*(h * (1 + scale_hb)) # bottom
])
image = Image.fromarray(img)
# bboxes_reset = restrict_boundry(bboxes)
image_cropped = image.crop(bboxes)
使用 手動去背+非監督式去背(Unsup_train.py),產生想要的mask(label)
去背影像示意
這邊的label是像素等級的,例如:
將前一步驟產生的mask(label)作為Y,標本照片做為X
訓練模型至去背結果與輸入的Y(mask)一致
使用訓練好的去背模型(Sup_predict_rmbg.py)進行去背,獲得mask
輔助的後處理工具(Postprocess.py)
按資料特性採用不同處理方案
核心操作為:
非監督+後處理
過的MASK作為標籤(以像素為單位)learning rate
加入early stopping
增強方式:
型變: ximage)與y(label)同時
色彩調整、加入雜訊: 僅x(image)
效果示意
成效
採用較強的資料增強方式對未知類型資料預測(泛化)能力較好,但對於邊緣細節表現則會弱化
訓練指標表現(黑:強、藍:弱)
預測與訓練資料不同取得來源的資料
預測與訓練資料同來源的資料
當資料集中要加入新類型資料時,可採用較強的資料增強方式取得初步label(稍微犧牲資料細節),當資料累積夠大時(涵蓋各類型資料來源後),或可以在訓練個低資料增強強度的版本去fit已知資料範圍來取得較好的細節
損失函數:
;
;
;
;
加權效果
左圖為原loss,右圖為loss加權。輪廓部分表現變好,不過因為加權比例太高,導致部分輪廓之外的預測表現下降(可以搭配Postprocess.py簡易處理)
輪廓加權計算範圍示意
from utils.utils import get_masks_contour
def get_masks_contour(masks: np.ndarray, iterations: int = 5, weighted: float = 2.0) -> torch.Tensor:
if masks.ndim == 2:
masks = masks[np.newaxis, ...]
assert masks.ndim == 3, f'Shape of mask must be (h,w) or (batch, h, w), mask.shape : {masks.shape}'
assert masks.dtype == 'uint8', f'dtype of mask need to be "uint8", masks.dtype : {masks.dtype}.\nuse .astype("uint8") convert dtype'
kernel_ELLIPSE = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, ksize=(3, 3))
masks_contour = []
for mask_ in masks:
mask_dilate = cv2.dilate(mask_, kernel_ELLIPSE, iterations=6)
mask_erode = cv2.erode(mask_, kernel_ELLIPSE, iterations=iterations)
mask_contour = mask_dilate - mask_erode
masks_contour.append(mask_contour)
masks_contour_tensor = torch.tensor(masks_contour, dtype=torch.long)
return masks_contour_tensor
# ----------------------------------------------------------------------
# get mask_contour and weight it for loss calculation (optional)
# ----------------------------------------------------------------------
masks_contour = get_masks_contour(
masks_true.cpu().numpy().astype(np.uint8)).to(device) # (b, h, w), torch.int64, [0, 1]
weight_CrossEntropy = 5
y = torch.ones(1, dtype=torch.int64).to(device)
masks_weighted = torch.where(
masks_contour == 1, weight_CrossEntropy*y, y) # (b, h, w)
# caculate weighted loss based on countour
masks_pred_one_hot = F.softmax(
masks_pred, dim=1, dtype=torch.float32) # (b, class, h, w)
masks_true_one_hot = F.one_hot(
masks_true, net.n_classes).float().permute(0, 3, 1, 2) # (b, h, w) > (b, h, w, class) > (b, class, h, w)
masks_contour_pred_one_hot = masks_pred_one_hot * \
torch.stack((masks_contour, masks_contour), dim=1)
masks_contour_true_one_hot = masks_true_one_hot * \
torch.stack((masks_contour, masks_contour), dim=1)
cross_entrophy_weighted = (criterion_3d( # nn.CrossEntropyLoss(reduction='none') : (b, h, w)
masks_pred, masks_true) * masks_weighted).mean()
dice_loss_allArea = dice_loss(
masks_pred_one_hot, masks_true_one_hot, multiclass=True)
dice_loss_contour = dice_loss(
masks_contour_pred_one_hot, masks_contour_true_one_hot, multiclass=True)
train_loss_contour_weighted = cross_entrophy_weighted + \
dice_loss_allArea + dice_loss_contour*3
optimizer.zero_grad()
train_loss_contour_weighted.backward()
optimizer.step()
直接系統化採樣 在每張照片固定位置取一個5x5或3x3的方塊,計算HSV平均值,假設取12個點,得到36個值,然後做分群 然後按照群去分層取樣
取樣點分布示意(翅膀部位取樣(3x3),每個取樣點計算h,s,v平均)
依據取樣點計算h,s,v後,降維成3個維度,並使用Birch分群視覺化
翅膀顏色偏白與透明的分別被分到cluster1與cluster6
分群結果討論
當樣本數量增加時,如果資料來源(建檔方式)有明顯系統性偏差,則分群結果很可能只是反映資料來源
分群結果並不穩定,會依照取樣點的數量、位置,樣本本身(大小、性質),降維演算法的參數(n_components、n_neighbors),而有較大的浮動
取樣點盡可能分佈在翅膀位置,讓分群結果盡可能依照翅膀色型
去背模型難處理的類型(白翅、透明翅等)未必會是小樣本(or 失衡樣本),因此
if args.stratify:
try:
df = pd.read_csv('../data/imgs_label_byHSV.csv', index_col=0)
except:
print('You need provide label of imgs at "../data/imgs_label_byHSV.csv".')
assert len(df.Name) == len(
img_paths), f'number of imgs: {len(img_paths)} and imgs_label_byHSV.csv: {len(df.label)} need equal '
print(
f'Stratified sampling by "imgs_label_byHSV.csv", clustering: {np.unique(df.label).size}')
X_train, X_valid, y_train, y_valid = train_test_split(
img_paths, mask_paths, test_size=val_percent, random_state=1,
stratify=df.label if args.stratify else None)
單張影像批次增強(SingleImgBatch)
多張同類型影像批次增強(MultiImgBatch)
實作:
成效討論:
兩者針對特定類型影像成效好於隨機抽樣(Randombatch),可將SingleImgBatch+MultiImgBatch混合一併訓練(見code中ImgBatchAugmentSampler裡面Mix的實作)
左-右: true_label、RandomBatch、SingleImgBatch、MultiImgBatch
實務上可以分別訓練Random 與 Mix的模型,取得預測的mask後再篩選適合的
# ------------------------------------------------------
# AddBatchArgmentation
dir_img_arg = Path('../data/data_for_Sup_train/imgs_batch_arg')
img_arg_paths = list(dir_img_arg.glob('**/*' + 'png'))
dir_img_arg = Path('../data/data_for_Sup_train/masks_batch_arg')
masks_arg_paths = list(dir_img_arg.glob('**/*' + 'png'))
# ------------------------------------------------------
# ------------------------------------------------------
# AddBatchArgmentation
X_train_arg = X_train + img_arg_paths
y_train_arg = y_train + masks_arg_paths
size_X_train = len(X_train)
# Sampler : SingleImgBatchAugmentSampler, MultiImgBatchAugmentSampler, RandomImgBatchAugmentSampler
batchsampler = MultiImgBatchAugmentSampler(X_train_arg, size_X_train, batch_size)
train_set = MothDataset(
X_train_arg, y_train_arg, input_size=input_size, output_size=output_size, img_aug=True)
train_loader = DataLoader(
train_set, batch_sampler=batchsampler, num_workers=2, pin_memory=True)
==========================================================