Bad Apple!!をPythonを使ってMinecraft上で再現する


![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073363/f39e6f04-3021-05ca-87a3-5fffa070209e.jpeg)
<h1>Bad Apple!!</h1>
<p>Bad Apple!!はニコニコ動画で一番再生回数が多い動画として有名ですが、Pythonの画像が読み取れるライブラリ(PIL)を使って作れないかという考えに至り、作成しました。</p>
<p>動画(ニコニコ動画): <a href="https://www.nicovideo.jp/watch/sm8628149?ref=search_key_video&playlist=eyJ0eXBlIjoic2VhcmNoIiwiY29udGV4dCI6eyJrZXl3b3JkIjoiYmFkIGFwcGxlIiwic29ydEtleSI6ImhvdE15bGlzdCIsInNvcnRPcmRlciI6Im5vbmUiLCJwYWdlIjoxLCJwYWdlU2l6ZSI6MzJ9fQ&ss_pos=1&ss_id=21d384e2-7379-4723-9d4b-622243f32ac4">Bad Apple!!</a>

<h1>Raspberry jam mod</h1>
<p>Minecraft pi Editionはスクリプトから動作させることができるエディションとして知られていますが、このmodを導入すればjava版でもpi EditionのようにPython上からMinecraftを操作することが出来ます。</p>

<h1>環境</h1>
<ul>
<li>Python 3.8.7</li>
<li>Minecraft forge 1.12.2</li>
<li>Raspberry jam mod</li>
</ul>
<p>※1.12.2までのバージョンでないと動作しないので注意</p>

<h1>導入</h1>
<p>cmdでまずPythonからminecraftを動かすためのライブラリのインストール</p>
```
pip install mcpi
```
<p>次にデータ解析の時とかによく使われるnumpyのインストール</p>
```
pip install numpy
```
<p>最後に画像処理用のPillow(PIL)のインストール</p>
```
pip install Pillow
```
<p>forgeやmodのインストールは<a href="https://prosense.me/textbooks/GettingStartedMinecraftProgramming/3/#mod%E3%81%A8%E3%81%AF">こちら</a>を参考にしてください</p>

<h1>下準備</h1>
<p>まず画像をPythonに読み込ませるために動画をOpenCVを用い、1フレームごとに分割して1枚ずつ画像として保存します。<br>
動画の分割方法は<a href="https://www.asanohatake.com/entry/2018/11/20/073000">こちら</a>を参考にしました。</p>
<p>分割すると以下のような画像が6572枚作成されました。</p>
![badapples.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073363/26508f26-cac8-f78e-6c55-b275b5cf9aa6.png)


<h1>コード</h1>
```Badapple_for_minecraft.py
#ライブラリのインストール
from mcpi import minecraft
import mcpi.block as block
import numpy as np
from PIL import Image

#ここは決め打ちのようなもの
mc = minecraft.Minecraft.create("localhost")

#プレイヤーの座標を取得
pos = mc.player.getPos()
basex = int(pos.x)
basey = int(pos.y)+130
basez = int(pos.z)

#プレイヤーの向きと位置を調整(画面の真ん中にキャンバスがくるように)
mc.player.setRotation(180)
mc.player.setPitch(0)
mc.player.setPos(basex+60,basey-44,basez+64)

#動画を分割した6572枚分の画像を取得
for jpg_n in range(0,6572):
    #指定した画像をプログラム内で開く
    im=Image.open("C:/Users/User名/Documents/badapple/"+str(jpg_n)+".jpg")
    
    #その画像のRGB値を取得
    rgb_im = im.convert('RGB')

    #画像サイズを取得(x,y)
    size = rgb_im.size

    l=[]
    l2=[]



    for y in range(size[1]):#上の列のピクセルから取得
        if l2!=[]:
            l.append(l2)
        l2=[]
        for x in range(size[0]):#上との組み合わせで左上から右下までの順でピクセルを取得していくことになる
            r,g,b=rgb_im.getpixel((x,y))#ある地点のx座標,y座標のピクセルのrgbを取得
            rgb=(r,g,b)
            rgb_16='%02x%02x%02x' % (rgb[0],rgb[1],rgb[2])#rgb値を16進数に変換
            rgb_10=int(rgb_16,16)#さらに16進数に変換したものを10進数に変換
            l2.append(rgb_10)#配列に値を登録しておく

    l_np=np.array(l)#numpyの配列化
```
<p>ここで重要になってくるのが、Minecraftの高度限界は256ブロックである、ということです。<br>
この画像のピクセルはxが480、yが360です。つまり<b>yの大きさが高度限界よりも上という問題が発生します</b>。y座標ではなくz座標に置くのも手は手ですが、あまりにも大きいと処理に時間がかかります。</p>
<p>そこで.....</p>
![説明1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073363/04a020bb-5e32-ba27-2be6-501a158f587c.png)
<p>四角1個が1ピクセルだとします。</p>
<ol>
<li>まず青と黒のピクセルを抽出します</li>
<li>この画像は4つの塊に分けられていますが、その1つの塊を1グループとします</li>
<li>そのグループの4ピクセル分の色の値の平均値を出し、1つの値として新たな配列に詰め込んでいきます</li>
</ol>
<p>こうすれば今説明した画面上では36個のデータを4個にすることが出来、高度限界問題を解消することが出来ます。</p>

```Badapple_for_minecraft.py
    #続き
    l_np_kari=[]
    l_np_kansei=[]

    for n in range(0,360,4):
        if l_np_kari!=[]:
            l_np_kansei.append(l_np_kari)
        l_np_kari=[]
        for n2 in range(0,480,4):
            l_np2=l_np[n:n+2,n2:n2+2]
            l_np2_mean=np.mean(l_np2)
            l_np_kari.append(l_np2_mean)#ここまでさっき説明したことをやるための処理

    l_np_kansei=np.array(l_np_kansei)#numpy形式の配列化


    l_lists=np.array([0,8421504,16777215])#白、灰、黒のカラーコードを10進数に変換したもの


    for y2,line in enumerate(l_np_kansei):
        for x2,c in enumerate(line):
            pos=(np.abs(l_lists-c)).argmin()#近似値の取得
            if l_lists[pos]==0:#近似値が0の場合
                mc.setBlock(basex + x2, basey - y2, basez, block.WOOL.id, 15)#白羊毛の設置

            elif l_lists[pos]==8421504:#近似値が8421504の場合
                mc.setBlock(basex + x2, basey - y2, basez, block.WOOL.id, 7)#灰羊毛の設置

            elif l_lists[pos]==16777215:#近似値が16777215の場合
                mc.setBlock(basex + x2, basey - y2, basez, block.WOOL.id, 0)#黒羊毛の設置
```
![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073363/36eefa3d-8a97-d18e-3539-b49255d6d486.jpeg)

![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073363/fc073793-4b57-3ce5-b0b2-82c1226b8d77.jpeg)

![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073363/de976947-9e79-28f0-4e66-9dbdc9911458.jpeg)
<p>このような感じになります。ただしPythonの処理速度の関係上最初から最後まで動かすまで2時間程度かかりました。</p>

<h1>参考資料</h1>
<ul>
<li>[Raspberry Pi + Minecraft Pi Edition で Hello World]
(https://qiita.com/niwasawa/items/42cf74e053af6478c497)</li>
<li>[Pythonで画像のピクセル操作](https://qiita.com/zaburo/items/0b9db87d0a52191b164b)</li>
<li>[Minecraft API](https://www.stuffaboutcode.com/p/minecraft-api-reference.html)</li>
<li>[MODをインストールしよう](https://prosense.me/textbooks/GettingStartedMinecraftProgramming/3/#mod%E3%81%A8%E3%81%AF)</li>
<li>[Python, OpenCVを用いて動画を画像へ変換する（動画から画像の切り出し）](https://www.asanohatake.com/entry/2018/11/20/073000)</li>
<li>[組み込み関数 - python 近い値を探す](https://code-examples.net/ja/q/b9425e)</li>
<li>[カラーコードの変換](https://code.tiblab.net/python/color_code_transform)</li>