ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【Raspberry Pi】Raspberry Pi Zero WにOpenCVの環境を構築する

rikoubou.hatenablog.com
 以前の記事でTinkerBoardにOpenCV環境を構築してみました。
 最後のところで「Debian系なのでRaspberry Piでもいけるはず」と書いていたので、今回Raspberry Pi Zero WにOpenCV環境の構築をやってみました。

 結論から言うとほぼ同じ手順でOpenCVの環境は構築できました。ただやらなくて良いところや、ものすごく時間がかかるところなどがあるので別記事としてまとめた次第です。

 では始めます。


0:Raspberry Pi Zero Wの準備
 以下の記事でRaspberry Piの最低限のセットアップの方法を書いているのでこちらに沿ってRaspberry Pi Zero Wの最低限のセットアップを行います。


1:パッケージの最新化
 LXTerminalを起動させ、以下のコマンドを実行してパッケージを最新化します。

$ sudo apt-get update && sudo apt-get -y upgrade


2:python3用のpipをインストール
 以下のコマンドでpython3用のpipをインストールします。(場合によってはすでにインストールされていることもあります)

$ sudo apt-get install python3-pip


3:OpenCVのインストール
 2のpipを使ってOpenCVをインストールします。

$ pip3 install opencv-python

 Raspberry Pi Zero Wの場合、このコマンドが完了するまで2時間ほどかかります。


4:python3を立ち上げてimport cv2ができるか確認
 「python3」コマンドを実行してpythonを立ち上げ、import cv2と打ち込んでエラーがなければOpenCVが使えるようになっています。

$ python3
>> import cv2
>>


5:import cv2でエラーが発生したときの対処方法
 import cv2を打ち込んだ際に以下のようなエラーが発生することがあります。

ImportError: libjasper.so.1: cannot open shared object file: No such file or directory

 これは「libjasper.so.1」というライブラリが不足しているというメッセージなので、不足しているライブラリを入れていけば良いです。
 該当のライブラリを入れてからもう一度pythonを立ち上げ、import cv2を実行して不足しているライブラリを入れるというのを繰り返していきます。

 自分がやった場合において、不足していたライブラリを解消するために入力したコマンドは以下の通りです。

・libcblas.so.3

$ sudo apt-get install libatlas-base-dev

・libjasper.so.1

$ sudo apt-get install libjasper-dev

・libQtTest.so.4

$ sudo apt-get install qt4-dev-tools qt4-doc qt4-qtconfig libqt4-test


 以上がRaspberry Pi Zero WにOpenCV環境を構築してみた手順になります。

 OSのバージョンなどによっては手順が変わる可能性があるので注意してください。


・参考資料

【Raspberry Pi】Raspberry Piで画面キャプチャをする

 今回はRaspberry Piで画面キャプチャを撮影する方法です。
 scrotというものをインストールして使っていきます。

 では始めます。


1:scrotをインストールする
 LXTerminalを起動させて以下のコマンドを実行することでインストールできます。

$ sudo apt-get update
$ sudo apt-get install scrot


2:scrotの使い方
 使い方としてはLXTerminalでコマンドを実行して画面キャプチャを行います。保存先は/home/pi配下になります。
 今回は使いそうなコマンドを列挙しておきます。
(※コマンドの詳しいオプションなどは参考資料に挙げてあるページ様を参照するようにしてください)

・コマンド実行後、10秒のカウントダウンの後に画面全体をキャプチャする

$ scrot -c -d 10

・コマンド実行後、マウスでクリックした画面をキャプチャする

$ scrot -s


 以上がRaspberry Piで画面キャプチャをする方法です。

 時間差などでキャプチャできたりと色々便利だと思います。


・参考資料

【MacOS/VSCode】MacOSにVisual Studio Codeをインストールして日本語化する

 昨今様々なエディタが存在していますが、自分が最近よく使っているのはVSCodeVisual Studio Code)です。友人からオススメされて入れたのですが、軽い上に拡張機能も豊富にあって「もう全部これでいいんじゃないかな」と思っているくらいです。

 使っているにも関わらず一度も記事にしたことがなかったので、今回は取り上げた次第です。
 基本的には参考資料に挙げたページ様の内容そのままなので、詳しい説明はそちらを参照してください。また今回はMacOSでの方法なので、その他のOSとは手順が違う場合があるので注意してください。

 では始めます。


1:VSCodeをインストールする

 公式ページへ飛ぶと最新バージョンのインストーラがあるのでクリックしてダウンロードします。
f:id:rikoubou:20190517134851p:plain

 zipファイルがダウンロードされるので解凍します。すると以下のように「Visual Studio Code.app」ファイルができます。
f:id:rikoubou:20190517134950p:plain

 そのままこのappファイルを起動させても良いですが、一般的にはMacのアプリケーションフォルダ内に集約されているのでそちらに移動させます。

 あとはVisual Studio Code.appを起動させれば終わりです。


2:VSCodeの日本語化
 VSCodeはメニューなどの表示はデフォルトで英語設定になっているので、これを日本語化します。

 VSCodeの左側にある四角いアイコンのようなものをクリックし、検索欄のところに「japanese」と入力します。すると、一番上にMicrosoft公式が提供している日本語化の拡張機能が表示されるので、そちらを選択してインストールします。(※この画像ではすでにインストール済になっています)
f:id:rikoubou:20190517135505p:plain

 インストール後に再起動するとメニューなどが日本語化されます。


メモ:入れている拡張機能
 自分が今VSCodeに入れている拡張機能をメモ的に残しておきます。

Python
 最近割とPythonを書く機会が多いのでPythonを入れています。
f:id:rikoubou:20190517140058p:plain

Arduino
 Arduino IDEが入っていること前提ですが、 VSCodeArduinoを扱える拡張機能を入れています。色々と設定する必要があったりはしますが、Arduino IDEはほぼ補完機能がないのでこちらの方が書きやすいかと思います。
f:id:rikoubou:20190517140218p:plain

・Live Share
 ネットを通じてVSCodeの画面を共有したりするための拡張機能です。ネットを通じたペアプロみたいなことができます。いずれこの機能についても記事にしたいです。
f:id:rikoubou:20190517140511p:plain
 

 以上がざっとしたVSCodeのインストールから日本語化とちょっとした拡張機能の紹介です。

 個人的にVSCodeはかなり気に入ってるので、まだ試したことのない人は入れてみると良いかと思います。


・参考資料

【python/OpenCV/dlib】顔を認識して笑い男画像をリアルタイムで貼り付ける

 過去の記事でdlibを使って顔認識をしたり、アルファ画像を合成したりしました。

 これらのやり方がわかれば「笑い男画像を貼り付けることができるのでは?」と思い今回作ってみました。

 では始めます。


1:顔に被せる画像を準備する
 顔に被せたいアイコン画像を準備します。今回は以下のページ様から画像をダウンロードします。


2:ソースコード
 以下のソースコードをコピペして「face_icon.py」として保存します。

・face_icon.py

# -*- coding: utf-8 -*-
import cv2
import dlib
from datetime import datetime

IMAGE_PATH = "./nc73730.png" # 画像パス
CAPTURE_SCALE = 0.5 # カメラの画像サイズ

def main():
    # 顔認証の準備
    detector = dlib.get_frontal_face_detector()
    predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")

    cap = cv2.VideoCapture(0) # カメラの準備

    while True:
        _, frame = cap.read() # カメラ画像を取得
        
        # 画像サイズを変更
        frame = img_resize(frame, CAPTURE_SCALE)
        h, w, _  = frame.shape

        img = frame # img = frame * 0とすると黒画面になる
        dets = detector(frame[:, :, ::-1])

        if len(dets) > 0:
            parts = predictor(frame, dets[0]).parts() # 顔の点を取得
            distance, pos = calc_distance_and_pos(img, parts) # 顔の横幅と顔の中心を取得
            icon, icon_w, icon_h = load_icon(IMAGE_PATH, distance) # アイコン読み込み

            if (pos != None) and (distance > 0.0):
                x = pos.x - int(icon_w/2)
                y = pos.y - int(icon_h/2)
                if (0 <= y) and (y <= (h-int(icon_h))) and (0 <= x) and (x <= (w-int(icon_w))):
                    # 画面の範囲内だったら画像を合成
                    img = merge_images(img, icon, x, y)

        cv2.imshow("camera", img) # 画像を表示

        k = cv2.waitKey(1)&0xff # キー入力を待つ
        if k == ord('q'):
            # 「q」キーが押されたら終了する
            break
        elif k == ord('p'):
            # 「p」キーで画像を保存
            save_image(img)
            cv2.imshow("saved", img) # キャプチャした画像を表示

    # キャプチャをリリースして、ウィンドウをすべて閉じる
    cap.release()
    cv2.destroyAllWindows()

# アイコンを読み込む関数
def load_icon(path, distance):
    icon = cv2.imread(path, -1)
    icon_height, _  = icon.shape[:2]
    icon = img_resize(icon, float(distance * 1.5/icon_height))
    icon_h, icon_w  = icon.shape[:2]

    return icon, icon_w, icon_h

# 画像をリサイズする関数
def img_resize(img, scale):
    h, w  = img.shape[:2]
    img = cv2.resize(img, (int(w*scale), int(h*scale)))
    return img

# 距離と顔の中心座標を計算
def calc_distance_and_pos(img, parts):
    # 確認(33が顔の中心位置)
    cnt = 0
    pos = None
    p1 = None
    distance = 0.0

    for i in parts:
        if (cnt == 0):
            # 顔の幅を測る時の始点
            p1 = i
        if (cnt == 16):
            # 顔の幅を計算
            distance = ((p1.x-i.x)**2 + (p1.y-i.y)**2)**0.5
        if (cnt == 33):
            pos = i # 顔の中心位置
        # 画像に点とテキストをプロット
        cv2.putText(img, str(cnt), (i.x, i.y), cv2.FONT_HERSHEY_SIMPLEX, 0.3, (0, 0, 255), thickness=1, lineType=cv2.LINE_8)
        cv2.circle(img, (i.x, i.y), 1, (255, 0, 0), -1)
        cnt = cnt + 1

    return distance, pos

# 画像を保存する関数
def save_image(img):
    date = datetime.now().strftime("%Y%m%d_%H%M%S")
    path = "./" + date + ".png"
    cv2.imwrite(path, img) # ファイル保存

# 画像を合成する関数
def merge_images(bg, fg_alpha, s_x, s_y):
    alpha = fg_alpha[:,:,3]  # アルファチャンネルだけ抜き出す(要は2値のマスク画像)
    alpha = cv2.cvtColor(alpha, cv2.COLOR_GRAY2BGR) # grayをBGRに
    alpha = alpha / 255.0    # 0.0〜1.0の値に変換

    fg = fg_alpha[:,:,:3]

    f_h, f_w, _ = fg.shape # アルファ画像の高さと幅を取得
    b_h, b_w, _ = bg.shape # 背景画像の高さを幅を取得

    # 画像の大きさと開始座標を表示
    print("f_w:{} f_h:{} b_w:{} b_h:{} s({}, {})".format(f_w, f_h, b_w, b_h, s_x, s_y))

    bg[s_y:f_h+s_y, s_x:f_w+s_x] = (bg[s_y:f_h+s_y, s_x:f_w+s_x] * (1.0 - alpha)).astype('uint8') # アルファ以外の部分を黒で合成
    bg[s_y:f_h+s_y, s_x:f_w+s_x] = (bg[s_y:f_h+s_y, s_x:f_w+s_x] + (fg * alpha)).astype('uint8')  # 合成

    return bg

if __name__ == '__main__':
    main()

 以下のように保存したら以下のように「face_icon.py」と同じ階層に、被せたい画像「icon.png」とdlibの学習済みデータである「shape_predictor_68_face_landmarks.dat」を配置します。
f:id:rikoubou:20190515173226p:plain

 この状態で実行すると以下のような感じで顔に画像を被せた状態で表示されます。
f:id:rikoubou:20190515173140p:plain

 顔を前後に動かすと、その顔の大きさに応じて被せる画像の大きさも変化します。顔が認識されない時や被せる画像が画面サイズからはみ出してしまう時は被せる画像は表示されません。
 また「p」キーを押すと画面キャプチャもできます。


 以上が今回作ってみたものになります。

 攻殻機動隊が好きだったので、割と手軽に笑い男を再現できて面白かったです。顔に被せる画像を色々変更して遊んでみるのもいいですね。


・参考資料

【python/OpenCV】背景画像にアルファ画像を合成する方法

 今回はpythonOpenCVを使って背景画像にアルファ画像を合成する方法の備忘録になります。

 では始めます。


1:背景画像とアルファ画像を用意する
 背景画像と合成用のアルファ画像を用意します。

 今回は以下の画像を使った前提で進めていきますので、必要な方は右クリックから画像を保存してください。

・背景画像(bg.jpg)
f:id:rikoubou:20190514142824j:plain

・合成用アルファ画像(fg.png)
f:id:rikoubou:20190514142856p:plain


2:背景画像にアルファ画像を合成する関数
 結論を先に書くと以下の関数でできます。

import cv2

# 画像を合成する関数(s_xは画像を貼り付けるx座標、s_yは画像を貼り付けるy座標)
def merge_images(bg, fg_alpha, s_x, s_y):
    alpha = fg_alpha[:,:,3]  # ①アルファチャンネルだけ抜き出す(要は2値のマスク画像)
    alpha = cv2.cvtColor(alpha, cv2.COLOR_GRAY2BGR) # grayをBGRに
    alpha = alpha / 255.0    # 0.0〜1.0の値に変換

    fg = fg_alpha[:,:,:3]

    f_h, f_w, _ = fg.shape # アルファ画像の高さと幅を取得
    b_h, b_w, _ = bg.shape # 背景画像の高さを幅を取得

    # 画像の大きさと開始座標を表示
    print("f_w:{} f_h:{} b_w:{} b_h:{} s({}, {})".format(f_w, f_h, b_w, b_h, s_x, s_y))

    bg[s_y:f_h+s_y, s_x:f_w+s_x] = (bg[s_y:f_h+s_y, s_x:f_w+s_x] * (1.0 - alpha)).astype('uint8') # ②アルファ以外の部分を黒で合成
    bg[s_y:f_h+s_y, s_x:f_w+s_x] = (bg[s_y:f_h+s_y, s_x:f_w+s_x] + (fg * alpha)).astype('uint8')  # ③合成

    return bg

 
 ①のアルファ部分を取得した結果は以下のようになります。アルファ部分がマスクとなって取得されています。
f:id:rikoubou:20190514143216p:plain

 ②で合成した結果が以下のようになります。背景に①のマスクの白い部分が黒色で合成されています。
f:id:rikoubou:20190514143329p:plain

 そして最後に③の行で、②の黒い部分にアルファ以外の部分が合成されて完成です。
f:id:rikoubou:20190514143427p:plain


3:サンプルプログラム
 2で背景画像にアルファ画像を合成する方法がわかったので、以下のようなサンプルプログラムを作成しました。

・alpha_add.py

import cv2
from datetime import datetime

BG_PATH    = "./img/bg.jpg"   # 背景画像
ALPHA_PATH = "./img/fg.png" # 合成アルファ画像
ALPHA_SCALE = 1.0

# メイン関数
def main():
    add_img = load_alphaImage(ALPHA_PATH, ALPHA_SCALE)
    bg_img  = cv2.imread(BG_PATH)

    bg_img = merge_images(bg_img, add_img, 0, 0) # 座標を指定してアルファ画像を合成
    save_image(bg_img) # 画像を保存

# アルファ画像を読み込む関数
def load_alphaImage(path, scale):
    add_img = cv2.imread(path, -1) # アルファチャンネルで読み込み
    add_img = img_resize(add_img, scale) # リサイズ
    return add_img

# 画像をリサイズする関数
def img_resize(img, scale):
    h, w  = img.shape[:2]
    img = cv2.resize(img, (int(w*scale), int(h*scale)) )
    return img

# 画像を保存
def save_image(img):
    date = datetime.now().strftime("%Y%m%d_%H%M%S")
    path = "./result/" + date + ".png"
    cv2.imwrite(path, img) # ファイル保存

# 画像を合成する関数(s_xは画像を貼り付けるx座標、s_yは画像を貼り付けるy座標)
def merge_images(bg, fg_alpha, s_x, s_y):
    alpha = fg_alpha[:,:,3]  # アルファチャンネルだけ抜き出す(要は2値のマスク画像)

    alpha = cv2.cvtColor(alpha, cv2.COLOR_GRAY2BGR) # grayをBGRに
    alpha = alpha / 255.0    # 0.0〜1.0の値に変換

    fg = fg_alpha[:,:,:3]

    f_h, f_w, _ = fg.shape # アルファ画像の高さと幅を取得
    b_h, b_w, _ = bg.shape # 背景画像の高さを幅を取得

    # 画像の大きさと開始座標を表示
    print("f_w:{} f_h:{} b_w:{} b_h:{} s({}, {})".format(f_w, f_h, b_w, b_h, s_x, s_y))

    bg[s_y:f_h+s_y, s_x:f_w+s_x] = (bg[s_y:f_h+s_y, s_x:f_w+s_x] * (1.0 - alpha)).astype('uint8') # アルファ以外の部分を黒で合成
    bg[s_y:f_h+s_y, s_x:f_w+s_x] = (bg[s_y:f_h+s_y, s_x:f_w+s_x] + (fg * alpha)).astype('uint8')  # 合成

    return bg

if __name__ == '__main__':
    main()

 上記の内容をコピペして「alpha_add.py」という名前で保存した後、imgとresultフォルダを同階層に作成してimgフォルダ内に背景画像と合成用アルファ画像を配置します。
f:id:rikoubou:20190514144256p:plain

 この状態でalpha_add.pyを実行すると以下のように結果が得られます。
f:id:rikoubou:20190514143427p:plain

 背景画像の画面サイズよりも合成用アルファ画像の画像サイズが大きい場合はエラーとなるため、ALPHA_SCALEの値を0.5(半分のサイズ)などに変更して実行してください。

 またmerge_images関数の第3引数、第4引数の値を変更すれば、貼り付ける位置を変更できます。


 以上が背景画像にアルファ画像を合成する方法です。

 背景画像をWebカメラから取得した画像に変更すれば、装飾フレームのようにすることもできそうです。

 今回作成したサンプルプログラムも一応公開しておきます。


・参考資料

【python】ディレクトリ(フォルダ)の作成と削除

 pythonの記事をいくつか書いていましたが、ディレクトリの作成や削除といったものをやってこなかったので、今回はディレクトリ操作についての備忘録です。

 参考資料として挙げたページ様を簡単にまとめた形になるので、詳しく知りたい方はそちらを参照してください。

 では始めます。


1:ディレクトリの作成
 ディレクトリの作成には以下の2通りの方法があります。それぞれの引数には作成したいディレクトリのパスを設定します。

import os

os.mkdir("path") # 新しいディレクトリを作成
os.makedirs("path") # 深いディレクトリまで作成

 os.mkdir関数は、ディレクトリが存在している場合や存在しないディレクトリ内にディレクトリを作ろうとするとエラーになります。

 os.makedirs関数は、引数を設定すればディレクトリが存在している場合は無視したり、存在しないディレクトリ内にディレクトリを作る場合でも再帰的に作成してくれます。

 なので、基本的にはos.makedirs関数を使った方が便利です。

 これら2つの関数の挙動をまとめたサンプルコードが以下になります。

import os

def main():
    os.mkdir("./test1/")
    os.mkdir("./test1/tt/")
    # os.mkdir("./test1/") # すでにディレクトリが存在する場合はエラー
    # os.mkdir("./test4/tt/") # 存在しないディレクトリ内に作成できないのでエラー

    os.makedirs("./test2/")
    # os.makedirs("./test2/") # エラー
    os.makedirs("./test2/", exist_ok=True) # python3.2以降ではexist_ok=Trueでエラーは吐かなくなる(デフォルトはFalse)
    os.makedirs("./test3/tt/") # 存在しないディレクトリでも再帰的に作成

if __name__ == '__main__':
    main()


2:ディレクトリの削除
 ディレクトリの削除方法もいくつかあります。それぞれの引数には削除したいディレクトリのパスを設定します。

import os
import shutil

os.rmdir("path") # 空のディレクトリのみ削除
shutil.rmtree("path") # 対象のディレクトリを中身ごと削除

 os.rmdir関数は、ディレクトリの中身が空でなかった場合はエラーになります。

 shutil.rmtree関数は、ディレクトリ内のものも全て対象ディレクトリごと削除します。

 どちらの関数も対象ディレクトリが存在しない場合はエラーになります。

 基本的にはshutil.rmtree関数を使ってディレクトリごと削除するのが便利です。
 
 これら2つの関数の挙動をまとめたサンプルコードが以下になります。

import os
import shutil

def main():
    os.mkdir("./test4/")
    os.rmdir("./test4/") # 空ディレクトリなので削除できる

    os.makedirs("./test4/tt/", exist_ok=True)
    # os.rmdir("./test4/") # 空のディレクトリではないのでエラー
    shutil.rmtree("./test4/") # 対象ディレクトリごと全部削除

if __name__ == '__main__':
    main()


 以上がpythonでのディレクトリの作成と削除の方法です。

 色々やり方はありますが、作成なら「os.makedirs関数」、削除なら「shutil.rmtree関数」を使うのが良さそうです。


・参考資料

【映画鑑賞】バースデー・ワンダーランドの感想(ネタバレあり)

www.youtube.com

 原恵一監督最新作のバースデー・ワンダーランドを観てきたので、その感想をつらつらと書いていきたいと思います。

 ネタバレを気にせず書くので、まだ観てない方は注意してください!
 また、これは個人的な感想ということも付け加えておきます。




 では始めます。




1:全体的な感想
 まず観終わった後の率直な感想としては「惜しい作品」だと感じました。決して面白くないわけではないが、色々と気になる部分や入り込めない部分があり、可もなく不可もなくという感じです。もちろん印象的なシーンや作画的な見所もあるのですが、それ以上に「ん?」と思うところや「大人が泣いた!」という宣伝との乖離もあり複雑な気分になりました。
 また作品全体としてあまり派手なシーンがあるわけではないので、若干退屈してしまうというところもあるかもしれません。


2:物語・テーマについて
 お話としては「主人公のアカネが世界の美しさに気づいてちょっと成長する」というのが観てわかるもっとも伝えたいテーマだとは思います。ただ個人的にはそこに少し失敗している印象がありました。

 異世界を旅していく中でアカネは美しいものに出会っていきますが、同行しているチィが率先して「綺麗」を見つけているため画面に美しいものが現れても、観客はチィ目線で見てしまうような気がしました。確かにアカネも同じ「綺麗」を目にしてはいますが、それを見た感想を明確にセリフに出しているわけではないし、その綺麗に「心が動かされている」ことがわかるシーンがなかったように思います。(自分が見落としているだけかもしれませんが…)

 盛り上がりの一つであるアカネがザン・グを説得するところも、道程で「アカネの心に変化があったこと」が明示的でなかったため、見ている自分としては「いつの間にそんな気持ちの変化があったんだ?」と物語に入り込めなかった感じはありました。もうちょっと言葉や仕草などで明確にアカネの心が動いたことがわかるシーンを入れておけばよかったのかなと思います。

 特に砂嵐後の夜空をアカネが車内から見上げるところで、瞳の中に綺麗な夜空が映り込んで瞳が揺らいでいるというようなカットを追加しておけば、アカネの気持ちが動いてたことがわかりやすくなったのではと思います。

 また内容として異世界を旅する話なのですが、作中で出てくる単語の説明があまりないまま進むのもあまりよろしくなかった気がします。

 その世界特有の単語はファンタジーものではよくありますが、物語に関わる単語なのであれば違和感なく説明する必要があります。説明しないのであれば説明しなくても問題ないような作りにすべきだと思います。「しずく切り」という重要な要素について、それがどういう儀式なのか、どれほど重要なものなのかは単語が出された時に全部すべきだった気がします。「しずく切り」に失敗した場合は、本当にしずく切りの儀式が始まる前に初めて出てきたのは遅すぎた感じがあります。

 最初の村でお願いされた「逆さとんがり市への出品」ももう一つの大きな目的でもあったはずですが、その結果が最後あまりにもあっさりした形で片付けられてしまったのも自分としては拍子抜けでした。尺の関係もあるのかもしれませんが、個人的にはここにも何かちょっとした展開があった上での優勝という形にして欲しかったです。

 前のめりのイカリも重要なアイテムとして出てきますが、どこからその効力が切れていたのかもよくわからなかったのも気になりました。あえて明確にしていないということも考えられますが「アカネが主体的に動く」というシーン自体が少なかったのも、このアイテムとお話がイマイチ噛み合ってないところなのかなとも思います。

 明確な一本のテーマはあるもののその他の要素が強く出てきてしまい、結果として判然としない内容になってしまった印象を受けました。


3:よかった点について
 色々と感想を書いてきましたが、よかったところはたくさんあります。

 まず異世界の国を巡る話なので国が変わるごとに様々な景色が楽しめて視覚的に楽しくどれも美しいです。
 クライマックスのしずく切りでは、湧き上がった水を切って鳥になっていくところは映像の美しさと挿入歌が相まってとても素晴らしいシーンになっていました。
 個人的には異世界に行く前、チィが箒を取り出してヒポクラテスと話していたあたりの作画がものすごく上手くて驚きました。


 以上、色々と書いてきましたが、全て個人的な感想です。

 物足りない感じは確かにありますが、見て損をするというわけでもないので気になる方は観た方が良いと思います。


・参考資料