去年やった絵柄判定ネタ再び

最近、Neural Network Consoleで遊んでいるのですが、去年イラストを学習させて、誰が描いたのかを判定してみるというのをやってみたところ、わりと悲しい結果となってしまったものに改めて挑戦してみました。

(データについては以前に許可を頂いているデータをそのまま使用しました)

今回のモデル

今回はGUIでモデルを作成していますので、ネットワーク構成とかはりつけてみました。

左図のようなモデルにしてみました。

1,000回学習させたものがこちら。

あんまり芳しい感じではないです。

イラスト100枚を学習させています。

結果

こんな感じになりました。

https://www.mizunagi-works.com/gadgets/nanoillust/20180201/index.html

確率で並び替えが出来るようになっています。

「engo001さんが描いたビールジョッキを持ったはやて」をsan_ponさんが描いたと判定してくれるようになったので、前回よりはそれっぽい判断になってくれ…たかもしれない。

.

Neural Network Libraryで遊んでみる

SONYのNeural Network Libraryを使ってみてのメモとか。

NNLで画像を読み込みたい場合

モデルに画像を読み込ませるには、nnabla.utils.data_source_loaderモジュールで読み込ませる事が出来ます。

# -*- coding: utf-8 -*-
import PIL.Image
import nnabla.utils.data_source_loader as nn_dsl

X_SIZE = 28
Y_SIZE = 28

# shapeに指定したサイズにリサイズされて読み込まれます。
# paddingはされません。
np_img = nn_dsl.load_image(
    "testdata0.png",
    shape=(3, X_SIZE, Y_SIZE),
    normalize=False
)

# 読み込んだ画像のshapeのままだと、Pillowで利用出来ませんので
# Pillowで活用したい場合は以下のようにします。

np_pil = np_img.transpose(1, 2, 0)
imp_pil = PIL.Image.fromarray(np_pil)
img_pil.save("testdata1.png")

Pillowで読み込んだ画像をNNLで扱える構造にしたい場合

numpyモジュールを使います。

# -*- coding: utf-8 -*-

import numpy as np
import PIL.Image

X_SIZE = 28
Y_SIZE = 28

img_pil = PIL.Image.open(pathname)
img_pil.thumbnail((X_SIZE, Y_SIZE))

img_target = PIL.Image.new("RGB", (X_SIZE, Y_SIZE))

x_src, y_src = o_image.size
x_pos = (X_SIZE - x_src) >> 1
y_pos = (Y_SIZE - y_src) >> 1

img_target.paste(img_pil, (x_pos, y_pos))

data = img_target.getdata()

np_img = np.array(data, dtype=np.float32)
np_img = np_img.reshape((X_SIZE, Y_SIZE, 3))
np_img = np_img.transpose(2, 0, 1)

# normalizeする場合は、 np_img /= 255.0

 

ISO-2022-JP-MSからCP932への変換

使用する機械はあまりなさそうでうけど、ISO-2022-JP-MS(JIS)からCP932(SHIFT-JIS)へ変換するコードを、以下のサイトに掲載されているプログラムを移植してみました。

http://www.geocities.jp/hoku_hoshi/TIPPRG/tipprg03.html

プログラム本体

# -*- coding: utf-8 -*-
"""
"""
# ------------------------------------------------------------------ import(s)
import sys
import array

# --------------------------------------------------------------- exception(s)
class CConvertError(Exception):
    """
    想定範囲外のJISコードエスケープを検出
    """
    pass

# ---------------------------------------------------------------- function(s)
# ============================================================================
# 0x1B([ESC]), 0x24($), 0x40(@)
# 0x1B([ESC]), 0x24($), 0x42(B)
# 0x1B([ESC]), 0x26(&), 0x40(@)
# 0x1B([ESC]), 0x28((), 0x42(B)
# 0x1B([ESC]), 0x28((), 0x4A(J)
# 0x1B([ESC]), 0x28((), 0x49(I)
def jis_to_sjis(jis_buffer):
    """
    与えられたバイト配列をJISコード文字列とみなして、SHIFT-JISへの変換を行います。

    args:
        jis_buffer (str):
            バイト配列

    returns:
        str: SHIFT-JIS文字列を戻します。
    """

    ary_src = array.array("B", jis_buffer)
    ary_dst = array.array("B")

    str_result = []

    n = 0
    kanji_mode = False
    while n < len(ary_src):
        c0 = ary_src[n]
        n += 1
        if c0 == 0x1B:
            c1 = ary_src[n]
            n += 1
            c2 = ary_src[n]
            n += 1
            if c1 == 0x24 and c2 in (0x40, 0x42):
                kanji_mode = True
            elif c1 == 0x26 and c2 == 0x40:
                kanji_mode = True
            elif c1 == 0x28 and c2 in (0x42, 0x49, 0x4A):
                kanji_mode = False
            else:
                raise CConvertError()
            continue

        if kanji_mode is True:
            c1 = ary_src[n]
            n += 1

            if c0 < 0x5F:
                irow_offset = 0x70
            else:
                irow_offset = 0xB0

            if c0 & 0x01:
                if c1 > 0x5F:
                    icell_offset = 0x20
                else:
                    icell_offset = 0x1F
            else:
                icell_offset = 0x7E

            ary_dst.append(((c0 + 1) >> 1) + irow_offset)
            ary_dst.append(c1 + icell_offset)

        else:
            ary_dst.append(c0)

    return ary_dst.tostring()


if __name__ == "__main__":

    with open("testdata.jis", "rb") as h_reader, open("testdata.conv", "wb") as h_writer:
        code_jis = h_reader.read()
        code_sjis = jis_to_sjis(code_jis)
        h_writer.write(code_sjis)
        print("cp932", code_sjis.decode("cp932"))

# [EOF]

テストデータ

ABCDE
あいうえお
FGHIJ
①②③④⑤
KLMNO
㈱㈲δ㌔㌧

テスト方法

上記の様なテストデータをUTF-8で作成して、iconvやnkfを使用してjis形式のファイルに変換します。

iconvの場合

iconv -f UTF-8 -t ISO-2022-JP-MS testdata.utf8 > testdata.jis
iconv -f UTF-8 -t CP932 > testdata.utf8 > testdata.cp932

nkfの場合

nkf -Wj testdata.utf8 > testdata.jis
nkf -Ws testdata.utf8 > testdata.cp932

.

Neural Network Consoleでキャラ判定

SONYが公開しているNeural Network Console を使用して、キャラクター判定をさせてみました。

NNCはWindows用なのですが、学習させたモデルの利用はMac(Anaconda + Python 3.6)でも動作しました。

ヴィヴィオとアインハルトのみですので適当に答えても50%の確率なので、あまり面白みはないけど…

Neural Network Console

https://dl.sony.com/ja/

SONYが公開しているニューラルネットワークを作成するためのGUI環境です。

今回は以下のようなネットワークを作成してみました。

 ネットワーク構成

 学習状況(2000Epoch)

画像データはBingの画像検索を使用してヴィヴィオとアインハルトの画像を収集しました。

画像については以下のルールで生成しています。

  • 変身前か変身後は関係なくキャラクター毎に30枚程度を用意
  • 描かれているキャラクターは一人だけ(背景はあってもなくてもよい)
  • 画像サイズは64×64に縮小(パディングは0(黒)で行う)

画像はかなり少ないのですが2000Epoch程学習をさせて、評価を行います。

評価方法

NNC上でも評価出来るのですが、生成したモデルをPythonやC++から使用することが出来ます。

今回は以下のようなコードを生成しました。

# -*- coding: utf-8 -*-
import sys

import nnabla as nn
import nnabla.utils.data_source_loader as U_dsl
import nnabla.functions as F
import nnabla.parametric_functions as PF

# Neural Network Console から出力した Python コード
# ---- ここから ----
def network(x, y, test=False):
    # Input -> 3,64,64
    # Convolution -> 16,31,31
    with nn.parameter_scope('Convolution'):
        h = PF.convolution(x, 16, (3,3), (0,0), (2,2))
    # ReLU
    h = F.relu(h, True)
    # MaxPooling -> 16,16,16
    h = F.max_pooling(h, (2,2), (2,2))
    # Dropout_2
    if not test:
        h = F.dropout(h, 0.2)
    # Convolution_2 -> 27,7,7
    with nn.parameter_scope('Convolution_2'):
        h = PF.convolution(h, 27, (3,3), (0,0), (2,2))
    # ReLU_5
    h = F.relu(h, True)
    # Convolution_3 -> 16,3,3
    with nn.parameter_scope('Convolution_3'):
        h = PF.convolution(h, 16, (3,3), (0,0), (2,2))
    # ReLU_3
    h = F.relu(h, True)
    # Dropout
    if not test:
        h = F.dropout(h)
    # Affine -> 20
    with nn.parameter_scope('Affine'):
        h = PF.affine(h, (20,))
    # ReLU_4
    h = F.relu(h, True)
    # Affine_2 -> 10
    with nn.parameter_scope('Affine_2'):
        h = PF.affine(h, (10,))
    # ReLU_2
    h = F.relu(h, True)
    # Affine_4 -> 1
    with nn.parameter_scope('Affine_4'):
        h = PF.affine(h, (1,))
    # SquaredError
    # ここはコメントアウト
    # h = F.squared_error(h, y)

    return h
# ---- ここまで ---- 

def main():

    nn.clear_parameters()
    nn.load_parameters("parameters.h5")

    x = nn.Variable(shape=(1, 3, 64, 64))
    y = network(x, None)

    x.d = U_dsl.load_image(sys.argv[1], shape=(3, 64, 64), normalize=True)

    y.forward()

    print(y.d)

if __name__ == "__main__":
    main()

# [EOF]

学習結果はフォルダにHDF5形式で格納されていますので、そのまま使用することが出来ます。

ソースコードもNNCが生成したものをそのまま使用可能となっています。

評価に使用するデータ

画像は自分が描いた2枚の画像を使用してみました。

 ヴィヴィオ( vivio = 1 )

 アインハルト( einhald = 0 )

実行結果

 0.978

 0.042

0に近いほどアインハルト、1に近いほどヴィヴィオという判定が出るように学習させましたので、どうやら正しく判定してくれたようです。

自分以外の絵でも試してみましたが、正答率は80%ぐらいでした。

.

文書の類似度判定(3)

データベースに文書を保存するまでが出来たので、やっと本題の類似度判定を。

データベースから文書を取り出して学習と判定を行う

# -*- coding: utf-8 -*-
# ------------------------------------------------------------------ import(s)
import sys
import os
import sqlite3
import gensim.models.doc2vec
import gensim.models.doc2vec


# ------------------------------------------------------------------- const(s)
# ------------------------------------------------------------------- class(s)
# ---------------------------------------------------------------- function(s)
def main():

    list_document = []

    o_conn = sqlite3.connect("dccol.db")
    o_cursor = o_conn.cursor()

    o_cursor.execute(
        """
        SELECT
            id_doc
        ,   word_pos
        ,   word_surface
        ,   word_feature
        FROM
            doc_collection
        WHERE
            LENGTH(word_surface) > 1
            AND
            word_feature = '名詞'
        ORDER BY
            id_doc, word_pos
        """
    )

    dict_document = {}

    for r in o_cursor.fetchall():
        id_doc = r[0]
        word_pos = r[1]
        word_surface = r[2]
        word_feature = r[3]

        if id_doc not in dict_document:
            dict_document[id_doc] = []
        dict_document[id_doc].append(word_surface.encode("utf-8"))

    list_tagged_document = []

    for id_doc, list_word in dict_document.items():

        with open(id_doc + ".wakati", "w") as h_writer:
            h_writer.write(" ".join(list_word))

        o_doc = gensim.models.doc2vec.TaggedDocument(
            words=list_word,
            tags=[id_doc]
        )

        list_tagged_document.append(o_doc)

    dvec_model = gensim.models.doc2vec.Doc2Vec(
        documents=list_tagged_document,
        min_count=1
    )

    list_word = ["ヴィヴィオ", "アインハルト","コロナ","リオ"]
    for similar_word in list_word:
        print u"指定した単語に関連するもの", similar_word.decode("utf-8")
        for r in dvec_model.wv.most_similar(positive=[similar_word]):
            print "\t", r[0].decode("utf-8"), r[1]
        print

    similar_doc = os.path.join("doc_source", "doc1.txt")
    print u"指定した文書に近いもの", similar_doc
    for r in dvec_model.docvecs.most_similar(positive=[similar_doc]):
        print "\t", r[0], r[1]
    print

if __name__ == "__main__":
    main()

# [EOF]

やっていることは、データベースから二文字以上の名詞を取り出して学習用データを生成して、与えたキーワード、文書に似ている(近いベクトルのもの)を取得しています。

実行すると以下のような結果が表示されます。

指定した単語に関連するもの ヴィヴィオ
        スタイル 0.404163241386
        状態 0.396886348724
        ため 0.391772806644
        アインハルト 0.378925800323
        リンネ・ベルリネッタ 0.374079525471
        結果 0.3656296134
        意味合い 0.356572329998
        デバイス 0.350234866142
        雪辱 0.344531387091
        兵器 0.343862652779

学習させた文書は、Pixiv百科事典からキャラクター名の項目をテキスト化したものを与えています。

ヴィヴィオに関連するものとして、

  • アインハルト
  • リンネ・ベルリネッタ

といったキーワードが出力されています。

与えた文書量が少なく、文書自体も短いためなんとなくうまくいっているようないないような。

文書の類似度判定(2)

まずはjanomeを使用して形態素解析を行います。

カスタム辞書を指定していますが、指定しなくても動作します。

janomeによる形態素解析

# -*- coding: utf-8 -*-
# http://www.mizunagi-works.com
# ------------------------------------------------------------------ import(s)
import sys
import os

import janome.tokenizer

# ------------------------------------------------------------------- const(s)
DB_FILENAME = "dccol.db"
CUSTOM_DIC = "./dic/nanoha_keyword.dic"
RAW_TEXT_DIR = "doc_source"


# ------------------------------------------------------------------- class(s)
# ---------------------------------------------------------------- function(s)
def main():

    o_tagger = janome.tokenizer.Tokenizer(CUSTOM_DIC)

    # 指定したフォルダ内の .txt 拡張子のファイルを読み込み
    for dirname, _, list_filename in os.walk(RAW_TEXT_DIR):
        for filename in list_filename:
            if os.path.splitext(filename)[1].lower() not in (".txt",):
                continue

            pathname = os.path.join(dirname, filename)

            with open(pathname, "r") as h_reader:

                # utf-8 to unicode
                raw_u_text = h_reader.read().decode("utf-8")

                for pos, r in enumerate(o_tagger.tokenize(raw_u_text)):
                    word_surface = r.surface
                    word_feature = r.part_of_speech.split(",")[0]

if __name__ == "__main__":
    main()

# [EOF]

word2vecを使用するには形態素解析したデータをもとに分かち書きを作成する必要があります。

毎回形態素解析をして生成しても良いですが、ここでは形態素解析をした結果をデータベースに一旦保存した後、必要な情報を取得できるようにしてみます。

janomeによる形態素解析とデータベースへの保存

前述のコードを修正してデータベース(ここではsqlite3)へ保存するようにしたもの。

# -*- coding: utf-8 -*-
# http://www.mizunagi-works.com
# ------------------------------------------------------------------ import(s)
import sys
import os
import sqlite3

import janome.tokenizer


# ------------------------------------------------------------------- const(s)
DB_FILENAME = "dccol.db"
CUSTOM_DIC = "./dic/nanoha_keyword.dic"
RAW_TEXT_DIR = "doc_source"


# ------------------------------------------------------------------- class(s)
# ---------------------------------------------------------------- function(s)
def main():

    o_tagger = janome.tokenizer.Tokenizer(CUSTOM_DIC)

    o_conn = sqlite3.connect(DB_FILENAME)
    o_cursor = o_conn.cursor()

    # テーブル生成
    o_cursor.execute(
        """
        CREATE TABLE IF NOT EXISTS doc_collection
        (
            id_doc text
        ,   word_pos int
        ,   word_surface text
        ,   word_feature text
        );
        """
    )

    # 指定したフォルダ内の .txt 拡張子のファイルを読み込んでDB化
    for dirname, _, list_filename in os.walk(RAW_TEXT_DIR):
        for filename in list_filename:
            if os.path.splitext(filename)[1].lower() not in (".txt",):
                continue

            pathname = os.path.join(dirname, filename)

            with open(pathname, "r") as h_reader:

                # utf-8 to unicode
                raw_u_text = h_reader.read().decode("utf-8")

                # 指定したドキュメントを削除
                o_cursor.execute(
                    """
                    DELETE FROM
                        doc_collection
                    WHERE
                        id_doc = ?;
                    """,
                    (pathname,)
                )

                for pos, r in enumerate(o_tagger.tokenize(raw_u_text)):

                    word_surface = r.surface
                    word_feature = r.part_of_speech.split(",")[0]

                    # 指定したドキュメントに単語を追加
                    o_cursor.execute(
                        """
                        INSERT INTO doc_collection
                        (
                            id_doc, word_pos, word_surface, word_feature
                        ) VALUES (?, ?, ?, ?);
                        """,
                        (pathname, pos, word_surface, word_feature,)
                    )

    o_conn.commit()

if __name__ == "__main__":
    main()

# [EOF]

.

文書の類似度判定(1)

nanoha_keyword知り合いとの会話にword2vecが話題にあがったので、例によってリリカルなのは的なネタに絡めた記事を作成する事にしました。

やることは、

  1. 文書を形態素解析
  2. データベースに保存
  3. doc2vec(word2vec)向けのデータを生成

といったものです。

文書の形態素解析

文書を取り扱う為に、まずやるべきことは形態素解析です。

形態素解析を行うライブラリには様々な物がありますが、今回はjanomeを使用しました。janomeはIPA辞書を内蔵していますが、目的に特化した単語を判定するには専用の辞書を使用する必要があります。

カスタム辞書の追加方法

janomeのカスタム辞書は以下の方法で生成する事が出来ます。janomeのカスタム辞書はMeCabと同じ形式のものを使用する事が出来ますが、単語だけの簡単な構造の形式を使用する事も出来ます。

アイナ・トライトン,名詞,アイナ・トライトン
アインハルト・ストラトス,名詞,アインハルト・ストラトス
アインハルト,名詞,アインハルト

サンプルとして以下のファイルを用意してみました。

nanoha_keyword.csv

# -*- coding: utf-8 -*-
import sys

import janome.dic
import sysdic


def main():
    o_dic = janome.dic.UserDictionary(
        "nanoha_keyword.csv",
        "utf8",
        "simpledic",
        sysdic.connections
    )
    o_dic.save("nanoha_keyword.dic")


if __name__ == "__main__":
    main()



# [EOF]

.

 

Pawooのアカウントについて

Pixivアカウントを所有していればPowooと連携する事が出来ます。ただ、アカウントの扱いについて具体例があまりなかったので、自分なりに調べた情報をまとめてみました。

自分のアカウントはPixivIDにあわせて@mizunagi_kbにしてみました。

基本的な情報源について

重要な情報の多くは@pawoo_supportで閲覧する事が出来ます。閲覧できる情報にはわりと重要な内容が含まれていたりするため、出来れば静的なページとしてPixiv側か専用のサイトに用意しておいて欲しい所なのですが、現時点では前述したページに固定されているトゥート(ツイート)だけだったりします。

少し使ってみた感じだと、ウェブ版のユーザーインターフェースがやや判りづらい感じでしたが、アカウント管理に関する説明不足の方が気になりましました。また、アカウント周りのユーザーインターフェースがやや雑な挙動をするため、利用者に無駄な不安を抱かせてしまうのではないかと思われます。

 雑な挙動の例

上図の場合、パスワード変更メールが古いためにトークンが無効になっているために生じているのですが、テキストを読むだけでは利用者には何が起きているか判らず日本語としてもおかしい。

PixivIDとの連携方法

Pawooは他のマストドンと同様、アカウントとメールアドレスを登録する事で利用可能になりますが、PawooはPixivと連動するのを前提としていますので、Pawooを使用するのであれば、PixivIDを取得してから連携する事をお薦めします。

PixivIDからの連携は難しいことはなく、Pawooのトップページからpixivアカウントを使って連携(ログイン)を選択するだけです。初回利用時にはアカウント名とメールアドレスの登録が促され、一度連携すると次回からはログインが自動で行われるようになります。この振る舞いはTwitter連携等と同じOAuthにより実現されています。

アカウント名

初回利用時にPixivID名称が候補として指定されます。Pixivと連携をするつもりであれば、変更せずにそのままそのまま確定するだけで完了します。

未確認ですが、アカウント名はPawoo内でユニークでなければならないため、登録時点で既に誰かがアカウント名を使用していた場合は別の名称に変更する必要があります。なお、一度設定したアカウント名(@からはじまる名称)は変更する事が出来ません。

メールアドレス

おそらく無くても登録可能かもしれませんが、メールアドレス設定されていないとパスワード設定が行えません。そしてパスワード登録がされていないアカウントは削除する事が出来ません。

アカウントの削除方法

設定画面のログイン情報からアカウントの削除が行えます。

アカウントの削除を行うには、Pixivアカウント連携を解除しておく必要があります。

アカウントの削除を実行すると以下の処理が行われます。

  • 自分の投稿やメディアが全て削除される。
  • 自分のアカウント名称が永続的に使用不可能になる。

アカウント名称自体は消滅しない為、削除した後にもう一度同じアカウント名で作成することは出来ません。

パスワードの設定方法

Pixiv連携でアカウントを登録すると、初期状態ではパスワードが未設定の状態となります。

この状態のままだと、

  • Pixivアカウントの連携解除
  • アカウントとパスワードによるログイン
  • アカウント削除

といった機能を利用する事が出来ません。

新しいパスワードを設定する際に気を付けること

新しいパスワードを設定する歳にパスワード変更用のアドレスが記載されたメールが届くのですが、リクエストしてからメールを受け取るまでにかなり時間がかかります。場合によっては届かない場合があります。この際、新たなにパスワードの設定要求行うと、届くメールの内容と現在アクティブなパスワード変更アドレスが一致しないような挙動をする場合があり、この状況に陥るとどうしようもなくなります。

正攻法では、最後に送信したメールが届くまでひたすら待つ事なのですが、あまりお薦めできない手っ取り早い方法として、3回程度高速にパスワード変更要求をかけることで、サーバー側のキューをむりやりフラッシュさせる方法があります。

複数メールが届きますが、どれかのアドレスがアクティブになっているはずです。

アカウントと表示名称について

アカウント名称は一度作成すると変更出来ませんが、表示名称は設定画面から変更可能です。

PixivIDとPawooアカウントの同一性について

PixivIDとPawooアカウントはそれぞれ別の管理がされており、無関係の名称を関連付ける事が可能な様です。

そのため、他人のPixivIDを本人よりも先んじて取得してしまえるのではないかと推測出来ます。これを悪用することで、他人の使いそうなアカウント名称を登録+破棄を繰り返すことで、容易に妨害行為を行うことが出来そうに感じました。

実害はほとんどないのですが、PixivIDと連携するのであれば、PixivID取得時に何らかの方法でリザーブを行うか、PawooのID管理はUUIDで行うといった実装の方が良いような気がしました。(マストドンとの互換性を保持するために、あまりアカウント管理まわりは独自の実装に出来ないのかもしれませんが)

Pawooの良いと感じたところ

誰もが気にするであろうアカウント周りの扱いが雑に見えてしまい、今のところ積極的に乗り換える気はないのですが、マイクロブログという側面から観た場合はなかなか面白い機能が実装されています。

  • 入力文字数が500文字まである。
  • 一定時間後に削除されるメッセージを作成出来る。
  • ネタバレ等をうっかり読まれないように表示前に警告文を設定可能になっている。
  • 投稿の公開レベルをある程度制御出来る。

投稿単位で閲覧対象を変更できるというのは、表現内容につきまとう昨今の問題を考えた場合はかなり重要な機能に感じました。

Handyを買いました

手を描くときは自分の手を参考にしたりするのですけど、向きによっては見ながら描くのが難しい…

というわけで、前から気になっていたHandyというアプリを買ってみました。

 

体全体だとArtPoseというのがあり愛用しているのですが、ArtPoseは名前の通りポーズ人形ですので、手の表情を付けることが出来ません。(決められた種類の中から選ぶことは出来る)

こちらは手に特化しているだけあって、かなり使いやすくなっています。

欠点は標準状態だと血色がやや悪い様に見えることでしょうか…

ARDUBOY用の画像変換ツール

Make TokyoでARDUBOYを入手したので変換ツールを作成してみました。

ARDUBOY向けの画像変換ツールを探すと、Arduboy Image Converterというのが見つかりますが、こちらは単純二値化しかサポートしていない為、イラストを変換するにはちょっと厳しいです。

というわけで、二値化する際に変換結果を確認しながら、変換パラメータ(白黒の閾値)を調整可能にしてみました。

 画像として保存できるようにしてみました。

ツールを作成していて気が付いたのですが、ARDUBOYは1バイト=縦8ドットなんですね。

何気にこの構造だと画像全体を1ライン分を横方向に1ドットだけ動かすというのが難しそうな気が。もしかして自然な向きはARDUBOYを左に90度回した向きだったり?

.

Image Converter for ARDUBOY

https://www.mizunagi-works.com/gadgets/arduboy/texconv/index.html

Tree Itによる樹木生成

Tree Itというアプリを知ったので、マンガの背景に使えないかな…と思って試してみました。

Tree It

http://www.evolved-software.com/treeit/treeit

.

 こんな画面です。

このソフトウェアの本来の用途は木のポリゴンモデルを生成して、それをゲーム等に読み込んで使用するためのものなのですが、割と汎用的なWavefront obj形式や、透明度付きのPNG画像を生成する事が出来ます。

早速作成

自分で樹木を生成する事も出来るのですが、今回はプリセットを使用してみました。

 木の影絵を生成

 ちょっとわざとらしい?

生成された樹木を黒で塗りつぶして配置してみました。

バリエーションを増やしたら、多少はそれっぽく見える…かも。

Shadeによる背景出力

題名の通りにShadeによる背景出力を試してみました。

 

ボリュームレンダリングによる雲の生成。狙った場所に狙った量の雲を生成するのはかなりの難易度です。上のレンダリングは太陽光源のみを設定しているのですが、それだけだと、やや重たい色合いの雲になってしまうみたい。

背景領域に上と下に雲と海を配置。明るさを調整して霧を薄くかけないと水平線がぱっきりと見える背景になってしまうため、霧を薄くかけています。

作画にShadeを実験導入

今まで難しい立体は、パースガイドやCheeta3Dを利用していたのですが、もうすこし作画用の線画を得られる物はないかと探していたところ、Shade3Dを試してみることにしました。

丁度描くのが難しい場所があったので、早速作図してみたり。

 線画出力

 線画出力(陰線あり)

 トゥーン出力

 こんな感じの画面

Shade3Dは大昔に触ったことがあったのですが、当時はベジエ?自由曲線?といった感じで、マニュアルを読んでも理解出来ませんでした。

それからだいぶ経ちましたが、改めて触った感じは(ソフトウェア自体がバージョンアップしているというのもありそうですが)思った以上に使いやすい印象を受けました。

これなら小物や背景を描くのに使えそう。

GodotEngineのデモ(1)

ゲームのデモと言えば、シューティング…というのは昔の話かもしれませんが。

練習としてちょっとしたデモを作成してみました。

GodotEngineの機能としては、

  • 自弾と敵本体のInstance生成
  • RigidBodyによる接触判定
  • Particleによる爆発表現
  • Singletonによるスコア値計算

といったものを使用しています。

モデルデータの作成にはMagicaVoxelを使用しました。

.

ソースコードはGitHubに置いておきました。

.

Magica Voxelで3Dドット絵

実験がてら使用していたのですが、ATOKのインストール作業中にちょっと作成してみました。

仕組み上任意のところにドットが打てないので、盛り付けてから削り出すというのを繰り返しながらの作業となります。

 なんだかずんぐりむっくり…

一応アインハルトさん…のつもり。

Godot用のツールを更新しました

GodotEngine向けにCOLLADAから直接tscnファイルを生成するツールを更新しました。

collada2tres https://github.com/MizunagiKB/collada2tres

以前のものは単にMeshをtres形式で出力するだけだったのですが、個別に読み込ませるのが手間だったのでシーン全体を読み込ませる様に変更しました。

GodotEngineはCOLLADAファイル経由でデータを読み込む仕組みになっていますが、変換処理部をすっとばして直接tscnファイルを生成します。

tscnファイルはGodotEngineのscnファイルをテキストで表現したものですので、少しでも内部仕様に沿っていない情報が含まれると、GodotEngine本体が異常終了する場合がある為、あまり利用する機会はないかも。