画像の任意の座標を中心とする余白を追加する方法

# 概要
画像の任意の座標が中心にくるように余白を追加(Padding)する方法を紹介する。

# 実装
Google Colabで作成した本記事のコードは、[こちら](https://colab.research.google.com/drive/1SNymN6glCkmowuq0V_Y1ohl04xOwmEPj?usp=share_link)にあります。


## Paddingサイズを最小限に位置合わせ
画像の任意の座標が中心にくるように最小限のPaddingをする方法です。メリットは、最小限のPaddingのため、画像サイズが大きくなりにくいところです。

```Python:Paddingサイズを最小限に位置合わせ
import numpy as np
import matplotlib.pyplot as plt

def trim_img_coord2centering(img, coord):
    x_shift = img.shape[1]-1-2*coord[0]
    y_shift = img.shape[0]-1-2*coord[1]
    if x_shift > 0:
        pad_x = (x_shift, 0)
    else:
        pad_x = (0, -x_shift)
    if y_shift > 0:
        pad_y = (y_shift, 0)
    else:
        pad_y = (0, -y_shift)
    centering_img = np.pad(img, (pad_y, pad_x), 'constant')
    return centering_img


img = np.arange(5*7).reshape(5, 7)
coord = (2, 1)
centering_img = trim_img_coord2centering(img, coord)

# グラフに表示
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
ax1.imshow(img)
ax1.scatter(*coord, c='r', label='coord')
ax1.legend()
ax2.imshow(centering_img)
ax2.scatter(centering_img.shape[1]//2, centering_img.shape[0]//2, c='r', label='coord2centering')
ax2.legend()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/78a6ce33-3035-86ef-c929-129e93623083.png)

左図の赤点が中心に位置合わせする座標で、右図は中心に位置合わせした後の画像です。

* コードの簡単な説明
``x_shift = img.shape[1]-1-2*coord[0]``で、画像の端の座標と任意の座標の2倍のした値を比較します。ここで、x座標は``x_shift``が正であれば、任意の座標は画像サイズの半分よりも小さく、負であれば半分をより大きい座標であると判定できます。``x_shift``の正負に合わせ、任意の座標が中心にくるように``pad_x``を定義します。y座標も同様に計算します。


<details><summary>数式による説明</summary><div>

簡単のため1次元配列で考えます。
```math
  img_{index} = [0, 1, 2, ..., i,  ..., n-1]
```
$img_{index}$は画像のindexとします。座標$i$が中心となるようにPaddingします。コードに従い
```math
  shift = n-1-2i
```
とします。$shift>0$の時、$img_{index}$の左端に追加するのでPadding後の$centeringimg_{index}$は、
```math
  centeringimg_{index} = [0, 1, 2, ...,i+shift,  ..., n-1+shift] = [0, 1, 2, ...,n-1-i,  ..., 2n-2-2i]
```
となります。$centeringimg$の中心の座標のindexは、$centeringimg$が奇数なので最後のindexの半分となり、
```math
\frac{2n-2-2i}{2}=n-1-i=i+shift
```
となります。これは$i$番目の座標の移動した値と等しいので$shift>0$の時に成り立ちます。
一方、$shift\leq 0$の時、$img_{index}$の右端に追加するのでPadding後の$centeringimg_{index}$は、
```math
  centeringimg_{index} = [0, 1, 2, ...,i,  ..., n-1-shift] = [0, 1, 2, ...,i,  ..., 2i]
```
となります。$centeringimg$の中心の座標のindexは、$centeringimg$が奇数なので最後のindexの半分となり、
```math
\frac{2i}{2}=i
```
これは$i$番目の座標の移動した値と等しいので$shift\leq 0$の時も成り立ちます。従って、任意の$i$においてこのコードは成り立ちます。

</div></details>

## 出力画像サイズを固定して位置合わせ
出力画像が入力サイズによって一意に決まるコードになります。こちらは[stack overflow: Rotate a 2D image around specified origin in Python](https://stackoverflow.com/questions/25458442/rotate-a-2d-image-around-specified-origin-in-python)を参考にしています。参考にした記事では、本記事で対応させると``pad_x = (img.shape[1] - coord[0], coord[0])``と同じ意味で書かれると思いますが、本記事では中心の座標を一意に決めたいため、``pad_x = (img.shape[1] - coord[0]+1, coord[0]+1)``と書き換えています。参考にした記事では、出力サイズが入力サイズの2倍のため**偶数となり中心の座標を1座標で表せない**ため、``+1``しています。中心の座標を厳密に定義して実装したいかの違いです。
メリットは、コードがシンプルであるのと、複数の同じサイズの画像で中心を位置合わせした場合に画像毎に出力画像が一意に決まるため、管理がしやすいところです。
```Python:出力画像サイズを固定して位置合わせ
import numpy as np
import matplotlib.pyplot as plt

def fix_size_coord2centering(img, coord):
    pad_x = (img.shape[1] - coord[0], coord[0]+1)
    pad_y = (img.shape[0] - coord[1], coord[1]+1)
    centering_img = np.pad(img, (pad_y, pad_x), 'constant')
    return centering_img


img = np.arange(5*7).reshape(5, 7)
coord = (2, 1)
centering_img = fix_size_coord2centering(img, coord)

# グラフに表示
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
ax1.imshow(img)
ax1.scatter(*coord, c='r', label='coord')
ax1.legend()
ax2.imshow(centering_img)
ax2.scatter(centering_img.shape[1]//2, centering_img.shape[0]//2, c='r', label='coord2centering')
ax2.legend()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/55c9e0d0-b790-ca7e-67ed-bb451a903f00.png)

左図の赤点が中心に位置合わせする座標で、右図は中心に位置合わせした後の画像です。

* コードの簡単な説明
x座標の``pad_x = (img.shape[1] - coord[0], coord[0]+1)``より、Paddingする数は、img.shape[1] - coord[0] + coord[0] + 1=img.shape[1] + 1となり、``coord[0]``の値によらず常に一定であることが分かると思います。y軸も同様です。このコードは一見処理が分かりにくいと思いますが数式による説明で成り立つことを確認できます。

<details><summary>数式による説明</summary><div>


簡単のため1次元配列で考えます。
```math
  img_{index} = [0, 1, 2, ..., i,  ..., n-1]
```
$img_{index}$は画像のindexとします。座標$i$が中心となるようにPaddingします。まず、$img_{index}$の左端に$n-i$個分追加すると、
処理後の$centeringimg_{index}$は、
```math
  centeringimg_{index} = [0, 1, 2, ...,i+(n-i),  ..., n-1+(n-i)] = [0, 1, 2, ...,n,  ..., 2n-1-i]
```
となります。次に、右端にも$i+1$個分追加すると、
```math
  centeringimg_{index} = [0, 1, 2, ...,n,  ..., 2n-1-i+(i+1)] = [0, 1, 2, ...,n,  ..., 2n]
```
となります。
```math
\frac{2n}{2}=n
```
これは$i$番目の座標の移動した値と等しいので任意の$i$においてこのコードは成り立ちます。

</div></details>

# 補足
RGB画像の場合は、
```Python:
centering_img = np.pad(img, (pad_y, pad_x, (0, 0)), 'constant')
```
とすると動きます。

# まとめ
状況に合わせて好きなものを使いましょう。

# 参考資料
https://stackoverflow.com/questions/25458442/rotate-a-2d-image-around-specified-origin-in-python