アノテーションデータの正確性ってどうなの？

# 1.概要
　これまで公開されているDatasetを用いて、Pytorchによる**Semantic Segmentation**の方法についていくつか紹介しました。
　
**Semantic Segmentation紹介記事**
[Pytorchによる航空画像の建物セグメンテーションの作成方法.](https://qiita.com/nigo1973/items/1d7495a963c23c97189c)
[PyTorchによるMulticlass Segmentation - 車載カメラ画像のマルチクラスセグメンテーションについて．](https://qiita.com/nigo1973/items/c62578fccc7230ba48f8)
[MMSegmentationによる多数クラス画像(Multi Class)のセマンティックセグメンテーション(Semantic Segmentation).](https://qiita.com/nigo1973/items/37c0980b00e4f1eb2c3f)
[航空写真から道路モデルを作成する方法](https://qiita.com/nigo1973/items/e5722c9e859343427293)

　ただ、これまでは**どなたかが用意していただいたアノテーション（マスク）データ**を用いていたけども、**自分でアノテーションデータを作る**場合、どれだけ正確につくらないといけないのか？　と疑問になりました。
　そこで、先日紹介した[航空写真から道路モデルを作成する方法](https://qiita.com/nigo1973/items/e5722c9e859343427293)のアノテーションデータをベースにマスク画像を縮小・膨張処理することで、*学習モデルからの予測（推測、Prediction)がどのように異なるのか**、比較してみましたので紹介します。

[GSIデータセット](https://gisstar.gsi.go.jp/gsi-dataset/index_ja.html)
出典：国土地理院（2022）


![Screenshot from 2023-01-08 23-20-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187602/15bc054c-82f4-94b3-13eb-f74feda52cd7.png)
**道路のアノテーション画像と膨張処理したアノテーション画像の比較**

![Screenshot from 2023-01-08 23-20-25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187602/c617442e-acfc-ba8f-999f-83ef5e78be8a.png)
**膨張処理したアノテーションマスクより作成した学習モデルの推定結果の比較**
　
　ここで用いた道路Datasetの入手方法やアノテーションデータの縮小・膨張処理のサンプルコードを[Google Colaboratory](https://colab.research.google.com/drive/1wmu8Uvkt7SmuhRnMzLnDn8Dxlmm_rugE?usp=share_link)にアップしましたので，ご興味のある方は試してみてください．ご参考になれば幸いです。

# 2. アノテーションデータの縮小・膨張処理
 アノテーションデータの縮小・膨張処理は、OpenCVの機能である**モフォロジー変換**を用いました。
詳しくは、モフォロジー変換の[紹介ページ](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.html)を参考にいただければと思いますが、縮小処理(erosion)の場合、”画像に対して(フィルタリング，2D convolutionに使われる)カーネルをスライドさせていきいます．原画像中の(1か0のどちらかの値を持つ)画素は，カーネルの領域に含まれる画素の画素値が全て1であれば1となり，そうでなければ0として出力されます．”となります。

出典：[モルフォロジー変換](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.html)

　例えば、こちらの**J**というイメージ（Jの部分が１，それ以外が０）に対して、縮小（erosion）処理します。

![Screenshot from 2023-01-08 23-27-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187602/4c7f51b1-0bf2-d346-91b2-fc9087570155.png)

```python
import cv2
import numpy as np

img = cv2.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
erosion = cv2.erode(img,kernel,iterations = 1)
```
　ここでは、５x５のピクセルの中に０が含まれていれば、その部分を０にします。　つまり、Jの周囲を削る処理になります。

![Screenshot from 2023-01-08 23-27-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187602/df146329-eabb-7103-211f-c9bf32ab3319.png)

　すると、上記のように処理前のイメージとして**J**が細くなっているのがわかります。

　このとき、**iterations=1**として、１回のみ処理していますが、こちらの数字を増やすことで、処理後の画像に同じ処理を複数回実施することができます。

　逆に、膨張（Dilation)するときは、以下のコードとなります。

```python
import cv2
import numpy as np

img = cv2.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
dilation = cv2.dilate(img,kernel,iterations = 1)
```
![Screenshot from 2023-01-08 23-32-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187602/6dec79a6-dee7-bd94-9188-1db975747267.png)

　処理後は、上記のように膨張(Dilation) しているのがわかります。

　こちらの機能をもちいて、道路のアノテーションデータを縮小・膨張処理を行います。

![Screenshot from 2023-01-08 23-34-52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187602/8e2fbc94-851b-5a10-cb97-6c9067674896.png)
**道路アノテーションデータの縮小・膨張処理結果**

　道路マスクが縮小・膨張されているのがわかります。特に、縮小時は、細い道路の場合、複数回縮小処理をするとそのものがなくなります。（Erosion2やErosion3は、5x5マスク処理を２回、３回していることを意味しています。）

　実際の道路画像に重畳させてみます。

![Screenshot from 2023-01-08 23-37-48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187602/2a4807e9-2509-3831-ce66-562449643169.png)
**道路アノテーションデータの縮小処理**
![Screenshot from 2023-01-08 23-40-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187602/f5ddc157-5bfc-8f74-e875-57b4666f1aab.png)

**道路アノテーションデータの膨張処理**

　先程確認したように、細い道路は複数回の縮小処理でマスクがなくなっています。一方、膨張処理した場合、道路脇の森林や建物まで道路マスクが広がっているのがわかります。

　このように、**道路を道路でない(縮小処理)** や **道路以外を道路である（膨張処理)** したアノテーションデータを用いた学習モデルが、**推定（Prediction)にどのような影響** を及ぼすのか確認します。


# 3. セグメンテーションモデルの作成と評価
## 3.1 データセットの作成
　セグメンテーションモデルの準備や学習モデルの作成については、こちらの記事をご参考ください。
　
[航空写真から道路モデルを作成する方法](https://qiita.com/nigo1973/items/e5722c9e859343427293)

　今回は、前回準備した道路のアノテーションデータに、２項の縮小・膨張処理をほどこし、それをマスクデータとして用いた学習モデルを作成します。

　このとき、上記の記事との違いは、**TrainとValidationのアノテーションデータは縮小・膨張処理したもの** 、**Testのアノテーションデータは未処理のデータ（正確）** を用いて評価します。

　例えば、下記のようにします。

```python
# モデル作成用と検証用にデータを確認します。

DATA_DIR = './output/'
mask_dir = './mask286_dil3/' #dilation 3のアノテーションマスク

#分割した画像データのディレクトリを指定する．
x_train_dir = os.path.join(DATA_DIR, 'train/org286/')
y_train_dir = os.path.join(DATA_DIR, 'train/' +mask_dir )


#Validataion用データ
x_valid_dir = os.path.join(DATA_DIR, 'val/org286/')
y_valid_dir = os.path.join(DATA_DIR, 'val/' + mask_dir)

#test用データ
x_test_dir = os.path.join(DATA_DIR, 'test/org286/')
y_test_dir = os.path.join(DATA_DIR, 'test/mask286/') #test maskは標準データとして、評価する

#ファイル数の確認
print(f"Train size: {len(os.listdir(x_train_dir ))}")
print(f"Valid size: {len(os.listdir(x_valid_dir ))}")
print(f"Test size: {len(os.listdir(x_test_dir ))}")

#分割した画像データのファイルリストを作成する．
x_train_files = glob.glob(x_train_dir +'/*')
y_train_files = glob.glob(y_train_dir +'/*')
```
 このときの学習モデルのlossおよびiouの結果が以下となります。
（Google Colaboratory（無償）ではGPUが制限されるため、こちらはローカルのPC(RTX 3080)の処理結果になります。）

![Screenshot from 2023-01-08 23-48-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187602/fe31de2d-dc69-3744-2474-b1fc52c1d17a.png)
**erosion3の学習推移**
![Screenshot from 2023-01-08 23-48-39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187602/c5a60bc7-44b2-b430-deda-fcc6f67cb4f4.png)
**dilation3の学習推移**

　ここで気になるのが、**膨張（Dilation)処理したアノテーションデータを用いた学習モデルの精度（IoU)が未処理のものと比べて高く** なったことです。これが何を示すのかは、未処理のアノテーションデータ（test) を用いて確認します。

## 3.2 モデルの評価
　次に、今回作成したモデルを評価します。まずは、モデルをロードします。
```python
# 学習モデルの確認
best_model = torch.load('./best_model_Unet_resnet50.pth')

best_model
```
　作成したモデルにテストデータを適用し評価します。まずは、テスト用のDatasetの準備です。

```python
# create test dataset
test_dataset = Dataset(
    x_test_dir, 
    y_test_dir, 
    augmentation=get_validation_augmentation(), 
    preprocessing=get_preprocessing(preprocessing_fn),
    classes=CLASSES,
)

test_dataloader = DataLoader(test_dataset)

# evaluate model on test set
test_epoch = smp.utils.train.ValidEpoch(
    model=best_model,
    loss=loss,
    metrics=metrics,
    device=DEVICE,
)

```
　次に、テスト用データによりモデルを評価します。

**出力**
| Annotation | dice_loss | iou_score |
| :--- | :---: | ---: |
| Normal | 0.3117 | 0.6244 |
| Erosion1 | 0.3707 | 0.5654 |
| Erosion2 | 0.4435 | 0.4933 |
| Erosion3 | 0.5241 | 0.4359 |
| Dilation1 | 0.3486 | 0.6117 |
| Dilation2 | 0.355 | 0.5662 |
| Dilation3 | 0.398 | 0.5196 |

　この結果より、**膨張したアノテーションデータのほうが縮小したものよりも影響が少ない** 、ということです。特に、複数回の縮小処理をしたものは影響が大きいことがわかります。これは、先程紹介したように、細い道路がアノテーションからなくなったためうまく道路を認識できなくなったことが原因と思われます。
　一方、**膨張処理したものはその影響があまり大きくありません。** 特に、今回用いたアノテーションデータのサイズは286x286であり、5x5サイズの膨張は道路からはみ出した部分がある程度大きいにもかかわらず、その影響が少ないことがわかりました。
 これらの結果は、アノテーションのデータセットを作成時の参考になると思います。


# 4. 学習したモデルによるテスト画像の評価
　Test画像を表示するためのDatasetを作成します。

```python
#test 画像に学習モデルを適用し、道路を検出する

test_dataset_vis = Dataset(
    x_test_dir, y_test_dir, 
    classes=CLASSES,
)
```
　では、準備はできたのでテスト画像より学習したモデルを用いて道路情報を作成します。
　学習モデルは、それぞれの学習時に作成したモデルを読み込んで処理します。
　例えばこんな感じです。

```python
best_model_ero1 = torch.load('./best_model_Unet_resnet50_ero1.pth')
best_model_ero2 = torch.load('./best_model_Unet_resnet50_ero2.pth')
best_model_ero3 = torch.load('./best_model_Unet_resnet50_ero3.pth')
```

```python
best_model_dil1 = torch.load('./best_model_Unet_resnet50_dil1.pth')
best_model_dil2 = torch.load('./best_model_Unet_resnet50_dil2.pth')
best_model_dil3 = torch.load('./best_model_Unet_resnet50_dil3.pth')
```

　では、1000枚のテスト画像からランダムに5枚選んで出力します。

縮小（Erosion）処理結果

```python
for i in range(5):
    n = np.random.choice(len(test_dataset))
    
    image_vis = test_dataset_vis[n][0].astype('uint8')
    image, gt_mask = test_dataset[n]
    
    gt_mask = gt_mask.squeeze()
    
    x_tensor = torch.from_numpy(image).to(DEVICE).unsqueeze(0)
    
    pr_mask = best_model.predict(x_tensor)
    pr_mask = (pr_mask.squeeze().cpu().numpy().round())
    
    pr_mask_ero1 = best_model_ero1.predict(x_tensor)
    pr_mask_ero1 = (pr_mask_ero1.squeeze().cpu().numpy().round())
    
    pr_mask_ero2 = best_model_ero2.predict(x_tensor)
    pr_mask_ero2 = (pr_mask_ero2.squeeze().cpu().numpy().round())
    
    pr_mask_ero3 = best_model_ero3.predict(x_tensor)
    pr_mask_ero3 = (pr_mask_ero3.squeeze().cpu().numpy().round())
        
    visualize(
        image=image_vis, 
        ground_truth_mask=gt_mask, 
        predicted_mask=pr_mask,
        
        predicted_mask_erosion1=pr_mask_ero1,
        predicted_mask_erosion2=pr_mask_ero2,
        predicted_mask_erosion3=pr_mask_ero3,
    )
```

![Screenshot from 2023-01-09 00-24-25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187602/e0dc93d7-7f51-18a6-7dea-3528d13dbe91.png)
**縮小処理のアノテーションデータを用いた学習モデルより推定した道路地図**


　この結果から、**大きな道路は検出できていますが、細い道路は縮小処理によりアノテーションデータからなくなったためか、識別できていない** のがわかります。また、誤検知もみられました。
　次に、膨張処理の結果です。

縮小（Erosion）処理結果
```python
for i in range(5):
    n = np. random.choice(len(test_dataset))
    
    image_vis = test_dataset_vis[n][0].astype('uint8')
    image, gt_mask = test_dataset[n]
    
    gt_mask = gt_mask.squeeze()
    
    x_tensor = torch.from_numpy(image).to(DEVICE).unsqueeze(0)
    
    pr_mask = best_model.predict(x_tensor)
    pr_mask = (pr_mask.squeeze().cpu().numpy().round())
    
    pr_mask_dil1 = best_model_dil1.predict(x_tensor)
    pr_mask_dil1 = (pr_mask_dil1.squeeze().cpu().numpy().round())
    
    pr_mask_dil2 = best_model_dil2.predict(x_tensor)
    pr_mask_dil2 = (pr_mask_dil2.squeeze().cpu().numpy().round())
    
    pr_mask_dil3 = best_model_dil3.predict(x_tensor)
    pr_mask_dil3 = (pr_mask_dil3.squeeze().cpu().numpy().round())
        
    visualize(
        image=image_vis, 
        ground_truth_mask=gt_mask, 
        predicted_mask=pr_mask,
        
        predicted_mask_dilation1=pr_mask_dil1,
        predicted_mask_dilation2=pr_mask_dil2,
        predicted_mask_dilation3=pr_mask_dil3,
    )
```
![Screenshot from 2023-01-09 00-27-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187602/3728b720-18e2-2d6f-ad49-8a2710413a37.png)
**膨張処理のアノテーションデータを用いた学習モデルより推定した道路地図**

　こちらは、**縮小処理の結果と比べると、多くの道路を検出できている** のがわかります。ただし、大きく膨張したデータセット（Dilation3)を用いた学習モデルより推定した道路情報は、未処理のアノテーションデータより学習したモデルの推測結果（Predicted Mask)では検出できなかった道路が検出できています。

　深層学習（AI)をどのように使うかによりますが、例えば **対象を漏れなく検出したい場合(高い再現率)は、アノテーションデータは対象物よりも大きくすることで、精度を上げることができるかもしれません。** 使い方によりますね。


# ５. まとめ
　国土地理院が公開したGISデータセットを用いて、航空写真から道路を識別するデータセットを用いて、アノテーションデータの正確性がモデル（推定）に与える影響を調べてみました。

　自分でアノテーションデータを作る場合、対象にたいしてどこまで（ピクセルレベルで）正確につくらないといけないのか、気になるところです（心配にもなります）。今回の結果から、その傾向を共有することができたのであれば幸いです。
　
　私が誤解している部分や知らないことなど多々あるかと思いますので、本記事に対して忌憚なくご意見いただければ嬉しいです。また、みなさの経験をコメントにて共有いただければ、さらに嬉しいです。


# 6. 参考記事
[GSIデータセット](https://gisstar.gsi.go.jp/gsi-dataset/index_ja.html)
[衛星画像のSegmentation（セグメンテーション）により建物地図を作成する．](https://qiita.com/nigo1973/items/78dea24fd997674ca8b9)
[Pytorchによる航空画像の建物セグメンテーションの作成方法.](https://qiita.com/nigo1973/items/1d7495a963c23c97189c)
[航空写真から道路モデルを作成する方法](https://qiita.com/nigo1973/items/e5722c9e859343427293)
[モルフォロジー変換](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.html)