NumPy配列をシフトさせるnp.rollを使って単一画像のスクロールGIFを作ってみる

#はじめに
NumPy配列ndarrayをシフト（スクロール）させるnp.rollを用いて画像をスクロールさせて遊んでみます。スクロールした画像をGIFに保存していきます。
せっかくなので子供が喜びそうなネタを扱っていきます。遊び心満載でお届けしますので、最後まで気楽にご覧ください。

![test57.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1559455/e6c84481-7aa2-d0db-4d0b-7e895a231c35.jpeg)
↓↓↓↓↓↓↓↓↓
![2-2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1559455/080c177e-d3ec-44f0-18d3-6b5a429408eb.gif)

#NumPy配列ndarrayをシフトするnp.rollとは

まずは以下のように一次元配列を考えます。

```ruby:python
import numpy as np

a = np.arange(10)
print(a)
# [0 1 2 3 4 5 6 7 8 9]
```
これにnp.roll()の処理をしてシフトさせます。
第一引数aに元のndarray、第二引数shiftにシフトさせる要素数を指定します。

```ruby:python
a_roll = np.roll(a, 3)
print(a_roll)
# [7 8 9 0 1 2 3 4 5 6]

print(a)
# [0 1 2 3 4 5 6 7 8 9]
```

第二引数shiftには負の値や全体の要素数を超える値も指定可能です。


```ruby:python
print(np.roll(a, -3))
# [3 4 5 6 7 8 9 0 1 2]

print(np.roll(a, 12))
# [8 9 0 1 2 3 4 5 6 7]

# print(np.roll(a, 0.5))
```

二次元配列の場合は以下の通りです。

```ruby:python
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

print(np.roll(a_2d, 2))
# [[10 11  0  1]
#  [ 2  3  4  5]
#  [ 6  7  8  9]]

print(np.roll(a_2d, 5))
# [[ 7  8  9 10]
#  [11  0  1  2]
#  [ 3  4  5  6]]

```

np.roll()の第三引数axisに軸を指定すると、軸に沿ってシフトされます。

```ruby:python
print(np.roll(a_2d, 1, axis=0))
# [[ 8  9 10 11]
#  [ 0  1  2  3]
#  [ 4  5  6  7]]


print(np.roll(a_2d, 2, axis=1))
# [[ 2  3  0  1]
#  [ 6  7  4  5]
#  [10 11  8  9]]
```



第二引数shiftと第三引数axisにタプルで複数の値を指定すると、複数の軸に沿ってシフトさせることも可能です。

```ruby:python
print(np.roll(a_2d, (1, 2), axis=(0, 1)))
# [[10 11  8  9]
#  [ 2  3  0  1]
#  [ 6  7  4  5]]
```
以上、基本的な扱いとなります。

#画像処理への応用（画像をスクロール）

次にnp.roll()を画像にあてはめてみます。
np.roll()を利用するとNumPy配列ndarrayとして読み込んだ画像をスクロールさせることができるようになります。



```ruby:python
import cv2
import numpy as np
from PIL import Image

input_file = "1.png"
output_file = "1.gif"
speed = -2

def cv2pil(imgCV):
    imgCV_RGB = imgCV[:, :, ::-1]
    imgPIL = Image.fromarray(imgCV_RGB)
    return imgPIL    

img = cv2.imread(input_file)
imgs = []
for t in range(19):
    img = np.roll(img, speed*t , axis=1) 
    imgPIL = cv2pil(img)
    imgs.append(imgPIL)        
imgs[0].save(output_file,save_all=True, append_images=imgs[1:],optimize=False, duration=400, loop=0)
```

`speed`でスクロールする速度を調整します。
また`axis=1`とすることで横方法にスクロールするように指定しています。
最後にスクロールしたフレームをつなぎ合わせてGIFファイルを出力します。

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1559455/7d6f3378-b337-1c6c-cb75-d02bf11bda46.png)

![4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1559455/778d37ed-d5d8-455d-b1c9-0e91533f3bd6.gif)

#画像の一部をスクロール

次に画像の一部分のみのをスクロールしてみます。

```ruby:python
import cv2
import numpy as np
from PIL import Image

input_file = "2.jpg"
output_file = "2-2.gif"
speed = -50

img = cv2.imread(input_file)
height, width, channels = img.shape

#分割する座標を指定する

img_scroll = img[0:117, 0:width]
img_main = img[117:height, 0:width] 

imgs = []

def cv2pil(imgCV):
    imgCV_RGB = imgCV[:, :, ::-1]
    imgPIL = Image.fromarray(imgCV_RGB)
    return imgPIL    
for t in range(8):
    img_scroll = np.roll(img_scroll, speed*t, axis=1) 
    imgPIL = cv2pil(cv2.vconcat([img_scroll, img_main]))　#分割した画像を結合する
    imgs.append(imgPIL)
        
imgs[0].save(output_file, save_all=True, append_images=imgs[1:],optimize=False, duration=400, loop=0)
```
`img_scroll`でスクロールする部分の座標を指定しています。
`cv2.vconcat([img_scroll, img_main])`により画像を結合します。

![test57.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1559455/e6c84481-7aa2-d0db-4d0b-7e895a231c35.jpeg)
↓↓↓↓↓↓↓↓↓
![2-2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1559455/080c177e-d3ec-44f0-18d3-6b5a429408eb.gif)

画像の一部分だけスクロールするGIFが出力できました。

#行ごとにシフト量を調整して揺らぎの画像を作る

![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1559455/674ebb56-68b7-0aca-fe89-9fe0c1bf9750.png)
↓↓↓↓↓↓↓↓↓
![5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1559455/1dcdc64c-c0e3-26d7-2a3e-89930e64fde3.gif)


上の例のように行ごとにシフト量を調整して揺らぎの画像を作ってみます。

```ruby:python
import cv2
import math
import numpy as np
from PIL import Image

input_file = "3.png"
output_file = "3.gif"

img = cv2.imread(input_file)
imgH, imgW = img.shape[:2]

A = imgW/32            # 振幅
f = 2*math.pi/imgH      # 周波数
θ = 0                   # 位相のずれ
step = 1                # 波の粗さ
T = 16                 # 周期

def cv2pil(imgCV):
    imgCV_RGB = imgCV[:, :, ::-1]
    imgPIL = Image.fromarray(imgCV_RGB)
    return imgPIL 
imgs = []
for t in range(16):
    for y in range(0, imgH, step):
        x = int(A*math.sin(2*math.pi*(t-θ)/tt+f*y))
        img[y:y+step] = np.roll(img[y:y+step], x, axis=1)
    imgPIL = cv2pil(img)
    imgs.append(imgPIL)

        
imgs[0].save(output_file, save_all=True, append_images=imgs[1:],optimize=False, duration=20, loop=0)
```
振幅と周波数を変えることで動きを変えることができます。

#一行ずつシフトする方向を変える


![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1559455/674ebb56-68b7-0aca-fe89-9fe0c1bf9750.png)
↓↓↓↓↓↓↓↓↓
![6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1559455/7a93ecaf-8c7d-cb6b-de67-ce7c9e477591.gif)

上の例のように行ごとにシフトして画像を作ってみます。

```ruby:python
import cv2
import math
import numpy as np
from PIL import Image

input_file = "4.png"
output_file = "4.gif"

img = cv2.imread(input_file)

imgH, imgW = img.shape[:2]

A = imgW/16             # 振幅
θ = 0                   # 位相のずれ
step = 1                # 計算のステップ 波の粗さ・計算量に関係
T = 16                 # 周期


def cv2pil(imgCV):
    imgCV_RGB = imgCV[:, :, ::-1]
    imgPIL = Image.fromarray(imgCV_RGB)
    return imgPIL 
imgs = []
for t in range(16):
    for y in range(0, imgH, step):
        sign = 1 if y%2==0 else -1
        x = int(sign*A*math.sin(2*math.pi*(t-θ)/T))
        img[y:y+step] = np.roll(img[y:y+step], x, axis=1)
    imgPIL = cv2pil(img)
    imgs.append(imgPIL)

        
imgs[0].save(output_file, save_all=True, append_images=imgs[1:],optimize=False, duration=100, loop=0)

```

同様に振幅と周波数を変えることで動きを変えることができます。
#
#おわりに
最後までご覧いただきありがとうございました。