【画像分割】セマンティックセグメンテーション:U-Netを用いた医療・衛星画像のピクセル解析

プログラミング

自動運転車が「道路標識」と「歩行者」と「車道」をピクセル単位で見分けたり、医療AIがMRI画像から数ミリの「腫瘍」を正確に塗りつぶす技術。それが「セマンティックセグメンテーション(Semantic Segmentation)」です。

単なる「画像の中に車がある(分類)」や「車を四角で囲む(物体検出)」の一歩先を行く、「全ピクセルに対して何が映っているかを分類する」非常に高度なコンピュータビジョンです。
この分野における絶対的王者のアルゴリズム「U-Net」の仕組みと驚異的なアーキテクチャについて解説します。

1. U-Netアーキテクチャの圧倒的な美しさ

2015年に医療用の細胞画像分割のために発表されたU-Netは、ネットワークの形がアルファベットの「U」の字になっていることからその名が付きました。

左半分の「エンコーダ(収縮パス)」では、通常のCNNと同じように画像を何度もプーリングしてサイズを小さくしながら、画像全体の「ぼんやりとした意味(What)」の特徴を抽出します。

右半分の「デコーダ(拡張パス)」では、エンコーダで小さくされた意味の情報を、逆にアップサンプリングして元の画像サイズまで戻していきます。
この時、U-Net最大の武器である「スキップ接続(Skip Connection)」が発動します。エンコーダの途中で保存していた「解像度が高くて細かい位置情報(Where)」の別ルートのデータを、デコーダ側に直接つなぎ合わせるのです。

import torch
import torch.nn as nn
class UNet(nn.Module):
    def __init__(self, in_channels=3, out_classes=2):
        super(UNet, self).__init__()
        
        # エンコーダ(ダウンサンプリング)
        self.enc_conv1 = self.double_conv(in_channels, 64)
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
        
        self.enc_conv2 = self.double_conv(64, 128)
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        
        # ボトルネック層(一番深い底の部分)
        self.bottleneck = self.double_conv(128, 256)
        
        # デコーダ(アップサンプリングとスキップ接続)
        self.upconv2 = nn.ConvTranspose2d(256, 128, kernel_size=2, stride=2)
        self.dec_conv2 = self.double_conv(256, 128) # enc_conv2(128) + upconv(128) = 256
        
        self.upconv1 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)
        self.dec_conv1 = self.double_conv(128, 64) # enc_conv1(64) + upconv(64) = 128
        
        # 最終出力
        self.final_conv = nn.Conv2d(64, out_classes, kernel_size=1)
    def double_conv(self, in_c, out_c):
        return nn.Sequential(
            nn.Conv2d(in_c, out_c, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_c, out_c, kernel_size=3, padding=1),
            nn.ReLU(inplace=True)
        )
    def forward(self, x):
        # エンコーダ実行
        e1 = self.enc_conv1(x)
        p1 = self.pool1(e1)
        
        e2 = self.enc_conv2(p1)
        p2 = self.pool2(e2)
        
        # ボトルネック
        b = self.bottleneck(p2)
        
        # デコーダ実行(スキップ接続 torch.cat(e2, d2) の結合がキモ)
        d2 = self.upconv2(b)
        d2 = torch.cat((e2, d2), dim=1) # 空間的な細かい情報をそのまま渡す!
        d2 = self.dec_conv2(d2)
        
        d1 = self.upconv1(d2)
        d1 = torch.cat((e1, d1), dim=1) # 空間的な細かい情報をそのまま渡す!
        d1 = self.dec_conv1(d1)
        
        return self.final_conv(d1)

まとめ:圧倒的なデータ要件への挑戦

画像分類(写真一枚に対して猫か犬か答えるだけ)に比べて、セマンティックセグメンテーションは「ピクセル単位でアノテーション(正解の塗り絵)を作成する」という狂気的な手作業のデータ作成が求められます。

そのため、U-Netは「わずか数十枚の医療画像(アメーバの顕微鏡写真など)の学習データからでも過学習せずに強い予測ができる」ように、データオーグメンテーション(水増し弾力変形)などを強烈に機能させる前提で作られています。現場でU-Netを扱う際には、モデルの構造よりも「如何に賢くデータを水増しするか」という前処理に一番の心血を注ぐことになります。

コメント

タイトルとURLをコピーしました