【Python画像処理】スプライト・アニメーション(Sprite Animation)を試す。

この投稿は最初以下の投稿の再現テストだったのですが…
[Pythonで画像のピクセル操作](https://qiita.com/zaburo/items/0b9db87d0a52191b164b)

* 元投稿では画像処理にPillow(PIL)を使ってるが、私の環境ではそのままでは動かないのでOpenCVに置き換える事にした(<font color=blue>そもそもJupyter notebookで走らせる用のコーディングではなかった？</font>)。
* そして試行錯誤するうち、気付いたら以下のスプライト・アニメーション(Sprite Animation)が出来上がっていた。<br>![sprite04.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/e25f73bb-e539-6adf-9b2f-77033bce034b.gif)<br>!![68747470733a2f2f63646e2d616b2e662e73742d686174656e612e636f6d2f696d616765732f666f746f6c6966652f6f2f6f6368696d7573686130312f32303231303232362f32303231303232363038323635382e676966.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/633be15b-5ccf-ee04-ed94-2aeed5fea0c3.gif)
<br>![sprite06.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/49c66f5a-2551-0c45-4c7f-f676a67f7842.gif)

なるほど、どうやらアフィン変換導入によって(<font color=blue>どうやら多くの人がつまづくらしい</font>)ROI（Region of Interest=関心領域）からのフレームアウト問題がを自然に回避してしまった様です。こいつは便利だ…

#画像の諸元(透過処理なし)

とりあえずの使用画像はこちら。
[数学者のイラスト（女性）](https://www.irasutoya.com/2019/10/blog-post_724.html)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/4ee0f150-b8d9-c7fd-a353-f45978b1a093.png)

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

#イメージ読み込み
image = cv2.imread("desktop/python/suugakusya.jpg",cv2.IMREAD_UNCHANGED)[:,:,::-1]
height, width, channels = image.shape[:3]
plt.imshow(image)
plt.show()
```

諸元を求めてみます。
[OpenCVで画像ファイルの読み書きをしよう (Python)](https://weblabo.oscasierra.net/python/opencv-image.html)
[Python + OpenCVでの画像サイズ取得方法](https://qiita.com/Zumwalt/items/202f2728b6354984e88b)

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

#イメージ読み込み
image = cv2.imread("desktop/python/suugakusya.jpg",cv2.IMREAD_UNCHANGED)[:,:,::-1]
height, width, channels = image.shape[:3]
plt.imshow(image)
plt.show()

# 各チャンネルの表示
height, width, channels = image.shape[:3]
print("width: " + str(width))
print("height: " + str(height))
print("channels: " + str(channels))

#出力結果
width: 858
height: 450
channels: 3
```

αチャンネルが存在しない場合は強制追加…
[OpenCVを用いて画像をRGBAで読み込みたい](https://teratail.com/questions/210200)

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

#イメージ読み込み
image = cv2.imread("desktop/python/suugakusya.jpg",cv2.IMREAD_UNCHANGED)[:,:,::-1]
height, width, channels = image.shape[:3]
if channels == 3:  # RGBならアルファチャンネル追加
    image = cv2.cvtColor(image, cv2.COLOR_RGB2RGBA)

# # 各チャンネルの表示
height, width, channels = image.shape[:3]
print("width: " + str(width))
print("height: " + str(height))
print("channels: " + str(channels))

#出力結果
width: 858
height: 450
channels: 4
```
それぞれのチャンネルの表示。
[OpenCVで透過画像を扱う　～スプライトを舞わせる～](https://qiita.com/mo256man/items/f7524dd34718a01fb3df)

>各要素はndim=2すなわち(height, width)のシェイプとなっている。だから各要素を画像として表示させるとグレースケールになる。

そして…

>RGBA画像を取り込むにはcv2.imread()でflags = cv2.IMREAD_UNCHANGEDと指定する。実際はそんな呪文を覚える必要はなく、2番目の引数として-1を指定すればよい。引数を1にするもしくは省略するとRGBの3チャンネル画像として取り込まれる。

なるほど、そういう事だったのか(<font color=blue>まずはそこから分かってなかった</font>)…アルファ値は0が透明で255が不透明。透明度というより不透明度? 元画像にアルファ値設定がなかったので、このチャンネルはただの真っ黒です。

```python
import cv2
import matplotlib.pyplot as plt
import numpy as np
#イメージ読み込み
image = cv2.imread("desktop/python/suugakusya.jpg")[:,:,::-1]
height, width, channels = image.shape[:3]
if channels == 3:  # RGBならアルファチャンネル追加
    image = cv2.cvtColor(image, cv2.COLOR_RGB2RGBA)
plt.imshow(image)
plt.show()
# 各チャンネルの表示
b = image[:, :, 0]
g = image[:, :, 1]
r = image[:, :, 2]
a = image[:, :, 3]

plt.imshow(b)
plt.show()
plt.imshow(g)
plt.show()
plt.imshow(r)
plt.show()
plt.imshow(a)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/8356c886-9f8e-65ec-22e2-fc122d38a51d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/10e97718-a832-9f70-fdee-8426d76d7cf9.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/c5d0596d-d8b2-e735-9222-f3591da68540.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/ece489be-fd8d-9e3d-69df-a6aad06d3068.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/ca1a649e-6ee2-f2a6-7b21-a3473eb2052f.png)

何故黄ばむ？

#画像の諸元(透過処理あり)
ここで元画像を切り替えます。
[決めポーズを取る戦隊もののキャラクターたち（集合）](https://www.irasutoya.com/2016/01/blog-post_293.html)

```R
import cv2
import matplotlib.pyplot as plt
import numpy as np
#イメージ読み込み
image = cv2.imread("desktop/python/sentai_ranger_5colors.jpg")[:,:,::-1]
#画像の読み込み
plt.imshow(image)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/8d7668e7-6b18-d2a2-da23-8deee3551555.png)
ここでこの話が出てくる訳ですね。
[OpenCVで透過画像を扱う　～スプライトを舞わせる～](https://qiita.com/mo256man/items/f7524dd34718a01fb3df)

>この画像をcv2.imshow()で表示したとき背景が黒になる。お絵かきソフトで透明キャンバスの上に絵を描くと、透明の部分は色要素がないために黒として扱われるわけだ。RGB成分がすべてゼロ＝(0,0,0)＝黒ということ。

なるほど…各チャンネルの画像を生成するとこんな感じ。やはり黄ばむ。そして元画像の表示が…チャンネル順序がRBGAでなくARBGである事と何か関係が?

```python
import cv2
import matplotlib.pyplot as plt
import numpy as np
#イメージ読み込み
image = cv2.imread("desktop/python/sentai_ranger_5colors.jpg",-1)[:,:,::-1]
height, width, channels = image.shape[:3]
if channels == 3:  # RGBならアルファチャンネル追加
    image = cv2.cvtColor(image, cv2.COLOR_RGB2RGBA)
plt.imshow(image)
plt.show()
# 各チャンネルの表示
a = image[:, :, 0]
r = image[:, :, 1]
g = image[:, :, 2]
b = image[:, :, 3]

plt.imshow(a)
plt.show()
plt.imshow(r)
plt.show()
plt.imshow(g)
plt.show()
plt.imshow(b)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/2b685391-d7c0-0559-4846-f1498297ec2b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/1d68d864-d618-7aa4-1216-289f589bf1eb.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/e5cf2500-6fbe-888d-587b-94d187baefce.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/7b57c008-37d3-f7f0-1b23-bf9486cacb2b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/3a455504-e096-9535-7020-33dc0db6f9d3.png)

さらに各色表示を試します。他色成分を0としたRGB画像をあらためて作ってやる場合。

```python

import cv2
import matplotlib.pyplot as plt
import numpy as np

#イメージ読み込み
image = cv2.imread("desktop/python/sentai_ranger_5colors.jpg",-1)[:,:,::-1]
height, width, channels = image.shape[:3]
if channels == 3:  # RGBならアルファチャンネル追加
    image = cv2.cvtColor(image, cv2.COLOR_RGB2RGBA)

# 各チャンネルの合成
a = image[:, :, 0]
r = image[:, :, 1]
g = image[:, :, 2]
b = image[:, :, 3]
z = np.full(image.shape[:2], 0, np.uint8)
imgR = cv2.merge((r,z,z))
imgG = cv2.merge((z,g,z))
imgB = cv2.merge((z,z,b))

#各チャンネルの表示
plt.imshow(imgR)
plt.show()
plt.imshow(imgG)
plt.show()
plt.imshow(imgB)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/6b177563-e8ed-1f41-511f-ff89a90d9e65.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/ef9a96d3-580c-d6d6-9f64-731d3ae2ade3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/fb2c5edd-c39e-012f-110c-7aa5c7b5f837.png)

そして元のRGB画像で不必要な色の輝度を0にする場合。

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

#イメージ読み込み
image = cv2.imread("desktop/python/sentai_ranger_5colors.jpg")[:,:,::-1]
height, width, channels = image.shape[:3]
if channels == 3:  # RGBならアルファチャンネル追加
    image = cv2.cvtColor(image, cv2.COLOR_RGB2RGBA)

# 各チャンネルの合成
imgB = image.copy()
imgR[:, :, (0,1)] = 0  # 3ch(BGR)の1番目(G)と2番目(R)を0にする
imgG = image.copy()
imgG[:, :, (0,2)] = 0  # 3ch(BGR)の0番目(B)と2番目(R)を0にする
imgB = image.copy()
imgR[:, :, (1,2)] = 0  # 3ch(BGR)の0番目(B)と1番目(G)を0にする


#各チャンネルの表示
plt.imshow(imgB)
plt.show()
plt.imshow(imgG)
plt.show()
plt.imshow(imgR)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/658f8d73-a018-49f2-db6a-bbc43d3fc7ee.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/ea48db18-413f-8274-5380-55a5546f271b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/51930c9d-3732-8235-b90b-3bed8a705006.png)

もちろん結果は同じ。RGBとGBRが混ざって頭が混乱してきました。

#画像合成とスプライト・アニメーション(Sprite Animation)

ここまで来たら引用投稿にある画像合成も試さずにはいられません。まずは以下を背景に選び、前景とサイズを合わせます。
[宇宙のイラスト（背景素材）](https://www.irasutoya.com/2017/05/blog-post_564.html)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/544fe13f-3bdd-6368-f007-8a40927c27ef.png)
[OpenCVでトリミングする　～覗き動画を作る～](https://qiita.com/mo256man/items/e36797f9f44a64caf81c)

>OpenCVのタイプはnumpy.ndarray。だからスライスでその一部を取り出すことができる。行列だから行・列の順。行列だから「どの行（列）からどの行（列）まで」を指定する。「左上座標と切り取るサイズ」ではない。要はimg[r1: r2, c1: c2]だが、理屈を理解すれば img[r: r+h, c: c+w] という書き方を暗記するのは容易だ。

サイズが近過ぎてトリミングでは上手くいかないのでリサイズで対応。アスペクト比の狂いはとりあえず黙殺するものとします。
[OpenCVで画像サイズの変更をしてみた](https://qiita.com/kenfukaya/items/dfa548309c301c7087c4)

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

#イメージ読み込み関数
def open_img(file_name):
    img = cv2.imread(file_name,-1)[:,:,::-1]
    height, width, channels = img.shape[:3]
    if channels == 3:  # RGBならアルファチャンネル追加
        image = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)
    return img

#前景(サイズのみ利用)
img_front = open_img("desktop/python/sentai_ranger_5colors.jpg")
height1, width1, channels1 = img_front.shape[:3]

#前景(オリジナル)
img_back = open_img("desktop/python/bg_uchu_space.jpg")
height2, width2, channels2 = img_back.shape[:3]
plt.imshow(img_back)
plt.show()

#前景(リサイズ)チャンネル数が4から3に減ってしまう。
img_resize=cv2.resize(img_back, (width1,height1))
plt.imshow(img_resize)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/87059440-c6fc-e5ce-72b0-2e70f03523f8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/abc32d55-9db9-0ee3-7b0e-86b72e023617.png)

そして透過色設定を試します。
[OpenCVで透過画像を扱う　～スプライトを舞わせる～](https://qiita.com/mo256man/items/f7524dd34718a01fb3df)

>前景画像の中に「この色は主たる画像の一部ではなく、背景として使われているだけだ」という色がある場合、numpy.where()を使って透明色を設定できる。クロマキーのようなものだ。前景に透過色が使われていないことを事前に確認しておかないと透明になったガチャピンのような事態になってしまう。
[ガチャピン、透明になる放送事故　冷静対応のキャスターに称賛の声](https://withnews.jp/article/f0160208004qq000000000000000W02j0601qq000012999A)

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

#イメージ読み込み関数
def open_img(file_name):
    img = cv2.imread(file_name)[:,:,::-1]
    height, width, channels = img.shape[:3]
    if channels == 3:  # RGBならアルファチャンネル追加
        img = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)
    return img

#前景(サイズのみ利用)
img_front = open_img("desktop/python/sentai_ranger_5colors.jpg")
height1, width1, channels1 = img_front.shape[:3]

#背景(オリジナル)
img_back = open_img("desktop/python/bg_uchu_space.jpg")
height2, width2, channels2 = img_back.shape[:3]

#背景(リサイズ)
img_resize=cv2.resize(img_back, (width1,height1))

#透過処理
#backとfrontは同じシェイプ/同じチャンネル数である必要がある。
transparence = (0,0,0)
result = np.where(img_front==transparence, img_resize, img_front)
plt.imshow(result)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/d778fd2f-76dd-3383-aa69-65e7ab41ee93.png)

マスク処理する場合は以下。
[【Python】OpenCVでピクセル毎の論理演算 – AND, OR, XOR, NOT](https://code-graffiti.com/opencv-bitwise-boolean-operation-in-python/)

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

#イメージ読み込み関数
def open_img(file_name):
    img = cv2.imread(file_name,-1)[:,:,::-1]
    height, width, channels = img.shape[:3]
    if channels == 3:  # RGBならアルファチャンネル追加
        img = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)
    return img

#前景(サイズのみ利用)
img_front = open_img("desktop/python/sentai_ranger_5colors.jpg")
height1, width1, channels1 = img_front.shape[:3]

#マスク作成
a = img_front[:, :, 0]
r = img_front[:, :, 1]
g = img_front[:, :, 2]
b = img_front[:, :, 3]
img_front1 = cv2.merge((r, g, b))  # RGBAのうちRGB
mask3 = cv2.merge((a, a, a)) # 3チャンネルのRGB画像とする
mask3r=cv2.bitwise_not(mask3)

#背景(オリジナル)
img_back = open_img("desktop/python/bg_uchu_space.jpg")
height2, width2, channels2 = img_back.shape[:3]

#背景(リサイズ)
img_resize=cv2.resize(img_back, (width1,height1))

print("Front & reverse mask")
result1=cv2.bitwise_and(img_front1,mask3)
plt.imshow(result1)
plt.show()
print("Back & mask")
result2=cv2.bitwise_and(img_resize,mask3)
plt.imshow(result2)
plt.show()
print("Back & reverse mask")
result3=cv2.bitwise_and(img_resize,mask3r)
plt.imshow(result3)
plt.show()
print("(Front & reverse mask)||(Back & reverse mask)")
result4=cv2.bitwise_or(result1,result3)
plt.imshow(result4)
plt.show()
```
Front & reverse mask
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/befa024c-3731-740d-d20b-a8513a5413b7.png)
Back & reverse mask
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/63f61330-e15e-0fea-dfa9-521cd3a3f8a6.png)
Back & reverse mask
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/f4297988-932e-4c16-a9b2-d19f75db07cf.png)
(Front & reverse mask)||(Back & reverse mask)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/8517de6d-b635-f535-52ca-d65030bdd333.png)

せっかくなのでこちらのアニメーション処理と組み合わせてみましょう。
[【Python画像処理】アフィン変換(Affine Transformation)を試す。](https://qiita.com/ochimusha01/items/4e9e8667e44f6c009f68)

![sprite04.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/e25f73bb-e539-6adf-9b2f-77033bce034b.gif)

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

#画像の準備(img_front,imag_back,)

#イメージ読み込み関数
def open_img(file_name):
    img = cv2.imread(file_name,-1)[:,:,::-1]
    height, width, channels = img.shape[:3]
    if channels == 3:  # RGBならアルファチャンネル追加
        img = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)
    return img

#前景(サイズのみ利用)
img_front_org = open_img("sentai_ranger_5colors.jpg")
height1, width1, channels1 = img_front_org.shape[:3]

#マスク作成
a = img_front_org[:, :, 0]
r = img_front_org[:, :, 1]
g = img_front_org[:, :, 2]
b = img_front_org[:, :, 3]
img_front = cv2.merge((r, g, b))  # RGBAのうちRGB
mask3 = cv2.merge((a, a, a)) # 3チャンネルのRGB画像とする

#背景(オリジナル)
img_back_org = open_img("bg_uchu_space.jpg")
height2, width2, channels2 = img_back_org.shape[:3]

#背景(リサイズ)
img_back=cv2.resize(img_back_org, (width1,height1))

#アニメーション前準備

#画面をデフォルトの640*480と設定した場合。
#fig = plt.figure(figsize = (6.4, 4.8))
fig = plt.figure()
#Indx数列を作成。
c0=np.arange(0,width1,3)
c1=np.append(c0,[width1])
c2=c0[::-1]
indx=np.append(c1,c2)

def shift_x(image,shift):
    h, w = image.shape[:2]
    src = np.array([[0.0, 0.0],[0.0, 1.0],[1.0, 0.0]], np.float32)
    dest = src.copy()
    dest[:,0] += shift # シフトするピクセル値
    affine = cv2.getAffineTransform(src, dest)
    return cv2.warpAffine(image, affine, (w, h));

def update(i):
    plt.cla()
    img_front1 = shift_x(img_front,indx[i])
    mask_a = shift_x(mask3,indx[i])
    mask_r = cv2.bitwise_not(mask_a)
    result1 = cv2.bitwise_and(img_front1,mask_a)
    result2 = cv2.bitwise_and(img_back,mask_r)
    result3 = cv2.bitwise_or(result1,result2)
    plt.imshow(result3)
    plt.title("Sift "+'x='+str(indx[i]));

ani = animation.FuncAnimation(fig, update, interval=50,frames=len(indx))
ani.save("sprite04.gif", writer="pillow")
```
[OpenCVで透過画像を扱う　～スプライトを舞わせる～](https://qiita.com/mo256man/items/f7524dd34718a01fb3df)

>スプライトを名乗るならば背景画像の範囲外にも描写できなければ話にならない。という解説を書くつもりだったが、すでに前回の記事「OpenCVで日本語フォントを描写する　を関数化する　を汎用的にする」で実装してしまったので説明は略。
[OpenCVで日本語フォントを描写する　を関数化する　を汎用的にする](https://qiita.com/mo256man/items/b6e17b5a66d1ea13b5e3#%E7%94%BB%E5%83%8F%E5%A4%96%E3%81%B8%E3%81%AE%E6%8F%8F%E5%86%99%E3%81%B8%E3%81%AE%E5%AF%BE%E5%BF%9C)

アフィン変換導入によってROI（Region of Interest=関心領域）からのフレームアウト問題は自然解決。さらにはこんな風に拡大縮小/クルクル回しも思うがまま。
![sprite08.gif](https://cdn-ak.f.st-hatena.com/images/fotolife/o/ochimusha01/20210226/20210226082658.gif)

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

#画像の準備(img_front,imag_back,)

#イメージ読み込み関数
def open_img(file_name):
    img = cv2.imread(file_name,-1)[:,:,::-1]
    height, width, channels = img.shape[:3]
    if channels == 3:  # RGBならアルファチャンネル追加
        image = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)
    return img

#前景(サイズのみ利用)
img_front_org = open_img("sentai_ranger_5colors.jpg")
height1, width1, channels1 = img_front_org.shape[:3]

#マスク作成
a = img_front_org[:, :, 0]
r = img_front_org[:, :, 1]
g = img_front_org[:, :, 2]
b = img_front_org[:, :, 3]
img_front = cv2.merge((r, g, b))  # RGBAのうちRGB
mask3 = cv2.merge((a, a, a)) # 3チャンネルのRGB画像とする

#背景(オリジナル)
img_back_org = open_img("bg_uchu_space.jpg")
height2, width2, channels2 = img_back_org.shape[:3]

#背景(リサイズ)
img_back=cv2.resize(img_back_org, (width1,height1))

#アニメーション前準備

#画面をデフォルトの640*480と設定した場合。
#fig = plt.figure(figsize = (6.4, 4.8))
fig = plt.figure()
#Indx数列を作成。
indx=np.arange(0,360,3)

def rotate_center(image, angle):
    h, w = image.shape[:2]
    affine = cv2.getRotationMatrix2D((w/2.0, h/2.0), angle, 1.0)
    return cv2.warpAffine(image, affine, (w, h))

def update(i):
    plt.cla()
    img_front1 = rotate_center(img_front,indx[i])
    mask_a = rotate_center(mask3,indx[i])
    mask_r = cv2.bitwise_not(mask_a)
    result1 = cv2.bitwise_and(img_front1,mask_a)
    result2 = cv2.bitwise_and(img_back,mask_r)
    result3 = cv2.bitwise_or(result1,result2)
    plt.imshow(result3)
    plt.title("Rotate "+'x='+str(indx[i]));

ani = animation.FuncAnimation(fig, update, interval=50,frames=len(indx))
ani.save("sprite11.gif", writer="pillow")
```
![sprite06.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/49c66f5a-2551-0c45-4c7f-f676a67f7842.gif)

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

#画像の準備(img_front,imag_back,)

#イメージ読み込み関数
def open_img(file_name):
    img = cv2.imread(file_name,-1)[:,:,::-1]
    height, width, channels = img.shape[:3]
    if channels == 3:  # RGBならアルファチャンネル追加
        img = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)
    return img

#前景(サイズのみ利用)
img_front_org = open_img("sentai_ranger_5colors.jpg")
height1, width1, channels1 = img_front_org.shape[:3]

#マスク作成
a = img_front_org[:, :, 0]
r = img_front_org[:, :, 1]
g = img_front_org[:, :, 2]
b = img_front_org[:, :, 3]
img_front = cv2.merge((r, g, b))  # RGBAのうちRGB
mask3 = cv2.merge((a, a, a)) # 3チャンネルのRGB画像とする

#背景(オリジナル)
img_back_org = open_img("bg_uchu_space.jpg")
height2, width2, channels2 = img_back_org.shape[:3]

#背景(リサイズ)
img_back=cv2.resize(img_back_org, (width1,height1))

#アニメーション前準備

#画面をデフォルトの640*480と設定した場合。
#fig = plt.figure(figsize = (6.4, 4.8))
fig = plt.figure()
#Indx数列を作成。
indx=np.arange(0,360,3)

def rotate_fit(image, angle):
    h, w = image.shape[:2]
    # 回転後のサイズ
    radian = np.radians(angle)
    sine = np.abs(np.sin(radian))
    cosine = np.abs(np.cos(radian))
    tri_mat = np.array([[cosine, sine],[sine, cosine]], np.float32)
    old_size = np.array([w,h], np.float32)
    new_size = np.ravel(np.dot(tri_mat, old_size.reshape(-1,1)))
    # 回転アフィン
    affine = cv2.getRotationMatrix2D((w/2.0, h/2.0), angle, 1.0)
    # 平行移動
    affine[:2,2] += (new_size-old_size)/2.0
    # リサイズ
    affine[:2,:] *= (old_size / new_size).reshape(-1,1)
    return cv2.warpAffine(image, affine, (w, h))

def update(i):
    plt.cla()
    img_front1 = rotate_fit(img_front,indx[i])
    mask_a = rotate_fit(mask3,indx[i])
    mask_r = cv2.bitwise_not(mask_a)
    result1 = cv2.bitwise_and(img_front1,mask_a)
    result2 = cv2.bitwise_and(img_back,mask_r)
    result3 = cv2.bitwise_or(result1,result2)
    plt.imshow(result3)
    plt.title("Rotate "+'='+str(indx[i]));

ani = animation.FuncAnimation(fig, update, interval=50,frames=len(indx))
ani.save("sprite05.gif", writer="pillow")
```
まさしくアフィン変換は偉大なり…

#本題だった筈の「反転表示」と「グレースケール表示」について。

これでやっと画像のピクセル操作の準備が整いました。

[Pythonで画像のピクセル操作](https://qiita.com/zaburo/items/0b9db87d0a52191b164b)
[Python OpenCVの基礎　画素へのアクセス](https://peaceandhilightandpython.hatenablog.com/entry/2016/01/03/151320)

>画像を読み込んで、y座標10, x座標20の画素のBGR値を取得します。x,yではなくy,xでRGBではなくBGRであるところに注意ですね！

ネガへの反転
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/e5aedce0-3822-37a9-af99-ec156fe84276.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/4361f38d-77ee-f218-298b-72ccecc4e0ee.png)

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

#イメージ読み込み関数
#coding:utf-8
def open_img(file_name):
    img = cv2.imread(file_name)[:,:,::-1]
    height, width, channels = img.shape[:3]
    if channels == 3:  # RGBならアルファチャンネル追加
        image = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)
    return img

#画像の読み込みとサイズ取得
img1 = open_img("sentai_ranger_5colors.jpg")
height, width, channels = img1.shape[:3]

#取得したサイズと同じ空のイメージを新規に作成
z = np.full(img1.shape[:2], 0, np.uint8)
img2 = cv2.merge((z,z,z))

#loop
#x
for y in range(height):
    #y
    for x in range(width):
        #ピクセルを取得
        b,g,r = img1[y,x]
        #反転処理
        b = 255 - b
        g = 255 - g
        r = 255 - r
        #set pixel
        img2[y,x]=[b,g,r]

#show
plt.imshow(img1)
plt.show()
plt.imshow(img2)
plt.show()
```
上掲のスプライトアニメ制作過程で(遥かに簡単な)別解を見つけてしまいました。
[OpenCVで画像の色反転をしてみた](https://qiita.com/kenfukaya/items/245122eb312cdb6ba389)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/a5b21d56-3657-8c44-2d3d-3fb43444e492.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/80e04c59-f315-995b-ef2c-17dbed325828.png)

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

#イメージ読み込み関数
#coding:utf-8
def open_img(file_name):
    img = cv2.imread(file_name)[:,:,::-1]
    height, width, channels = img.shape[:3]
    if channels == 3:  # RGBならアルファチャンネル追加
        image = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)
    return img

#画像の読み込みとサイズ取得
img1 = open_img("sentai_ranger_5colors.jpg")
img2 = cv2.bitwise_not(img1)

#show
plt.imshow(img1)
plt.show()
plt.imshow(img2)
plt.show()
```

[Pythonで画像のピクセル操作](https://qiita.com/zaburo/items/0b9db87d0a52191b164b)

>グレースケールは、r,g,bが同じ値を持つことでグレーに見えます。ただ、そのようなルールで同じ値にするかはケースバイケースです。ここではr,g,bの平均値を取得し、その値にしてみます。

[Pythonのdivmodで割り算の商と余りを同時に取得](https://note.nkmk.me/python-divmod-quotient-remainder/)

グレースケール化
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/8fc70af2-98d6-b730-cf80-f192ddffeee0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/487a6116-c36f-92c7-1b41-79c35164f906.png)

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

#イメージ読み込み関数
#coding:utf-8
def open_img(file_name):
    img = cv2.imread(file_name)[:,:,::-1]
    height, width, channels = img.shape[:3]
    if channels == 3:  # RGBならアルファチャンネル追加
        image = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)
    return img

#画像の読み込みとサイズ取得
img1 = open_img("sentai_ranger_5colors.jpg")
height, width, channels = img1.shape[:3]

#取得したサイズと同じ空のイメージを新規に作成
z = np.full(img1.shape[:2], 0, np.uint8)
img2 = cv2.merge((z,z,z))

#loop
#x
for y in range(height):
    #y
    for x in range(width):
        #ピクセルを取得
        b,g,r = img1[y,x]
        #平均化(整数商)
        gray = (b+g+r)//3
        #set pixel
        img2[y,x]=[gray,gray,gray]

#show
plt.imshow(img1)
plt.show()
plt.imshow(img2)
plt.show()

#エラーが出る
<ipython-input-1-5ea43bffd6a9>:30: RuntimeWarning: overflow encountered in ubyte_scalars
  gray = (b+g+r)//3
```
ぎゃぁぁぁぁぁ!! 繊細なグラデーションが特徴のモモレンジャーが迷彩レンジャーに!! 上掲のスプライトアニメ制作過程で(遥かに簡単な)別解を見つけましたが、困った事にちゃんと動きません。どうやら上掲の「黄ばみ」と原因は同じ様だ？
[OpenCVで画像ファイルの読み書きをしよう (Python)](https://weblabo.oscasierra.net/python/opencv-image.html)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/fc72f4d3-6e57-a396-0384-cacf4e2d7f87.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/44b4683d-dab2-66bd-e35c-dfb3b7d71e08.png)

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

#画像の読み込みとサイズ取得
img1 = cv2.imread("suugakusya.jpg",cv2.IMREAD_COLOR)
img2 = cv2.imread("suugakusya.jpg",cv2.IMREAD_GRAYSCALE)

#show
plt.imshow(img1)
plt.show()
plt.imshow(img2)
plt.show()

#show
plt.imshow(img1)
plt.show()
plt.imshow(img2)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/f5befed0-e722-5e8c-8be0-3cbdbc3efdb3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/312acd82-86a7-9260-69f4-b752e69c0d27.png)

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

#画像の読み込みとサイズ取得
img1 = cv2.imread("sentai_ranger_5colors.jpg",cv2.IMREAD_COLOR)
img2 = cv2.imread("sentai_ranger_5colors.jpg",cv2.IMREAD_GRAYSCALE)

#show
plt.imshow(img1)
plt.show()
plt.imshow(img2)
plt.show()
```
バージョンアップに伴う仕様変更の匂いがします。以下も最終解答じゃない…
[Python でグレースケール(grayscale)化](https://qiita.com/yoya/items/dba7c40b31f832e9bc2a)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/48aed8ab-5474-1b47-50d7-eb1b0aa7a3dc.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/4e760ee7-ae91-0c7a-1dbc-fb42e3fb738d.png)

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

#画像の読み込みとサイズ取得
img1 = cv2.imread("suugakusya.jpg")[:,:,::-1]
img2 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) 
# RGB2〜 でなく BGR2〜 を指定

#show
plt.imshow(img1)
plt.show()
plt.imshow(img2)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/23cbdeba-83cd-d700-d8d6-196576d3c569.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/41a535d7-0644-8fca-7a0e-fd9e9955c389.png)

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

#画像の読み込みとサイズ取得
img1 = cv2.imread("sentai_ranger_5colors.jpg")[:,:,::-1]
img2 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) 
# RGB2〜 でなく BGR2〜 を指定

#show
plt.imshow(img1)
plt.show()
plt.imshow(img2)
plt.show()
```
[NumPy配列ndarrayを直接計算して変換](https://note.nkmk.me/python-opencv-numpy-color-to-gray/)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/b27e620e-6e6d-ff3f-7360-0595772a59e1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/4c284d71-9f27-06b2-221a-b1845382bddc.png)

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

#画像の読み込み
img1 = cv2.imread("suugakusya.jpg")[:,:,::-1]

#グレイスケール化
img2 = 0.299 *  img1[:, :, 2] + 0.587 *  img1[:, :, 1] + 0.114 *  img1[:, :, 0]
    
#show
plt.imshow(img1)
plt.show()
plt.imshow(img2)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/338faa8e-7a39-d6cf-07b7-5204e3698914.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/b4a5830b-2ada-7c73-7ff8-3774340bf1b2.png)

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

#画像の読み込み
img1 = cv2.imread("sentai_ranger_5colors.jpg")[:,:,::-1]

#グレイスケール化
img2 = 0.299 *  img1[:, :, 2] + 0.587 *  img1[:, :, 1] + 0.114 *  img1[:, :, 0]
    
#show
plt.imshow(img1)
plt.show()
plt.imshow(img2)
plt.show()
```
どうやらこの辺りが現時点における私の限界の様です。

* (2021.2.25)こんな拙い初学者投稿なのに、コメント欄で一杯アドバイスを頂きました。なるほど`plt.imshow(img2, cmap="gray", vmin=0, vmax=255)`ですか。そして…<br>[[matplotlib]グレースケール画像の表示](https://qiita.com/Daiki_P/items/844bc3a4b271e6ae179b)

解決策1
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/e450c6ed-f802-5c50-5a9b-2a3aada5f542.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/96c93715-1168-7563-4b06-741b8f5cbb9d.png)

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

#画像の読み込みとサイズ取得
img1 = cv2.imread("sentai_ranger_5colors.jpg")[:,:,::-1]
img2 = cv2.imread("sentai_ranger_5colors.jpg",0)

#show
plt.imshow(img1)
plt.show()
plt.imshow(img2,cmap = "gray")
plt.show()
```
解決策2
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/e450c6ed-f802-5c50-5a9b-2a3aada5f542.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597772/96c93715-1168-7563-4b06-741b8f5cbb9d.png)

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

#画像の読み込みとサイズ取得
img1 = cv2.imread("sentai_ranger_5colors.jpg")[:,:,::-1]
img2 = cv2.imread("sentai_ranger_5colors.jpg",0)

#show
plt.imshow(img1)
plt.show()
plt.imshow(img2)
plt.gray()
plt.show()
```
まだ理屈は全然分かってませんが、どうやらこれが正解の模様?
そんな感じで以下続報…