【画像処理】Numpyでモルフォロジー演算

Numpyでモルフォロジー演算を実装してみます。

まず、使用する画像を読み込んでグレースケール画像に変換します。

```python
import numpy as np
import matplotlib.pyplot as plt

original_image = plt.imread(image_name)
if np.issubdtype(original_image.dtype, np.floating):
  original_image = (original_image * 255).astype(np.uint8)
gray_image = (0.2116 * original_image[:,:,0] + 0.7152 * original_image[:,:,1] + 0.0722 * original_image[:,:,2]).astype(np.uint8)
plt.imshow(gray_image, cmap='gray')
```

![GrayImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/9ea41b70-963c-f5e6-4248-abe3f928d194.png)


二値画像も作成します。二値画像作成の詳細については以下の記事を参照してください。
[【画像処理】Numpyで大津の二値化\(判別分析法\) \- Qiita](https://qiita.com/aa_debdeb/items/bc31f5aeb1ee7af0dcea)

```python
def thresholding(image, threshold):
  return np.where(image <= threshold, 0, 255)
  
binary_image = thresholding(gray_image, 128)
plt.imshow(binary_image, cmap='gray')
```

![BinaryImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/390e6781-01d2-4421-d9c4-0af76fd78ab0.png)

## 収縮 (Erosion)

前景(白)を一回り小さくするモルフォロジー演算を収縮(Erosion)と呼びます。注目画素とその近傍画素の最小値を注目画素の値にします。

```python
def erode(image, boundary='edge'):
  pad_image = np.pad(image, 1, boundary)
  areas = np.lib.stride_tricks.as_strided(pad_image, image.shape + (3, 3), pad_image.strides * 2)
  return np.min(areas, axis=(2, 3))
```

収縮を二値画像に適用します。

```python
erode_binary_image = erode(binary_image)
plt.imshow(erode_binary_image, cmap='gray')
```

![ErodeBinaryImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/042f6dbe-2bd0-d8e0-de2c-ba0be28edf65.png)

収縮をグレースケール画像に適用します。

```python
erode_gray_image = erode(gray_image)
plt.imshow(erode_gray_image, cmap='gray')
```

![ErodeGrayImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/8c287f55-57ba-a66c-dea5-51446daa964b.png)

## 膨張 (Dilation)

前景(白)を一回り大きくする処理を膨張(Dilation)と呼びます。注目画素とその近傍画素の最大値を注目画素の値にします。

```python
def dilate(image, boundary='edge'):
  pad_image = np.pad(image, 1, boundary)
  areas = np.lib.stride_tricks.as_strided(pad_image, image.shape + (3, 3), pad_image.strides * 2)
  return np.max(areas, axis=(2, 3))
```

膨張を二値画像に適用します。

```python
dilate_binary_image = dilate(binary_image)
plt.imshow(dilate_binary_image, cmap='gray')
```

![DilateBinaryImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/bb96055f-ee34-dff0-cf55-9eab507f64b4.png)

膨張をグレースケール画像に適用します。

```python
dilate_gray_image = dilate(gray_image)
plt.imshow(dilate_gray_image, cmap='gray')
```

![DilateGrayImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/bf231236-990c-27f3-0cb3-01d66366d933.png)

## オープニング (Opening)

複数回の収縮のあとに、同じ回数膨張する処理をオープニング(Opening)と呼びます。

```python
def opening(image, n=2, boundary='edge'):
  for i in range(n):
    image = erode(image, boundary)
  for i in range(n):
    image = dilate(image, boundary)
  return image
```

オープニングを二値画像に適用します。

```python
opening_binary_image = opening(binary_image)
plt.imshow(opening_binary_image, cmap='gray')
```

![OpeningBinaryImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/473774c0-5131-8bb5-66e0-422b2487954c.png)

オープニングをグレースケール画像に適用します。

```python
opening_gray_image = opening(gray_image)
plt.imshow(opening_gray_image, cmap='gray')
```

![OpeningGrayImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/f6905e48-5f0f-b73a-f49a-0cfdf09f7ef8.png)

## クロージング (Closing)

複数回の膨張のあとに、同じ回数収縮する処理をクロージング(Closing)と呼びます。

```python
def closing(image, n=2, boundary='edge'):
  for i in range(n):
    image = dilate(image, boundary)
  for i in range(n):
    image = erode(image, boundary)
  return image
```

クロージングを二値画像に適用します。

```python
closing_binary_image = closing(binary_image)
plt.imshow(closing_binary_image, cmap='gray')
```

![ClosingBinaryImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/2d861c84-92e7-ed09-afdf-89ab5b56ac86.png)

クロージングをグレースケール画像に適用します。

```python
closing_gray_image = closing(gray_image)
plt.imshow(closing_gray_image, cmap='gray')
```

![ClosingGrayImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/d3e9d6f9-a7bc-e0e6-2cc6-071ecc47ca0b.png)

## トップハット (Top-Hat)

元の画像からオープニングを施した画像を引く処理をトップハット(Top-Hat)と呼びます。
トップハット画像にはオープニングにより削除された値が残ります。

```python
def tophat(image, n=2, boundary='edge'):
  opening_image = opening(image, n, boundary)
  return image - opening_image
```

トップハットを二値画像に適用します。

```python
tophat_binary_image = tophat(binary_image)
plt.imshow(tophat_binary_image, cmap='gray')
```

![TophatBinaryImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/bf4ac91a-0f98-d834-6364-f6e6e43a638f.png)

トップハットをグレースケール画像に適用します。

```python
tophat_gray_image = tophat(gray_image)
plt.imshow(tophat_gray_image, cmap='gray')
```

![TophatGrayImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/28ac06d1-851e-f025-bb3b-9aff010699f7.png)

## ブラックハット (Black-Hat)

クロージングを施した画像から元画像を引く処理をブラックハット(Black-Hat)と呼びます。
ブラックハット画像にはクロージングより追加された値が残ります。

```python
def blackhat(image, n=2, boundary='edge'):
  closing_image = opening(image, n, boundary)
  return closing_image - image
```

ブラックハットを二値画像に適用します。

```python
blackhat_binary_image = blackhat(binary_image)
plt.imshow(blackhat_binary_image, cmap='gray')
```

![BlackhatBinaryImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/4a7b7182-e43b-3db4-fc96-942ffe5676a7.png)

ブラックハットをグレースケール画像に適用します。

```python
blackhat_gray_image = blackhat(gray_image)
plt.imshow(blackhat_gray_image, cmap='gray')
```

![BlackhatGrayImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/ef00f213-6222-eeb2-4a0c-66d1875ccb92.png)

---

実装したコードはGoogle Colabratoryに置いてあります。

https://colab.research.google.com/drive/1ft6pflm0mHn2n_5WRnpHqfiVDs_3NhAr?usp=sharing