ふぎのモノづくりにっき

痛車乗りのモノづくり日記。いろいろ試したり、作ったりするのを載せる予定だぞいッ

動画を元にDCGANで高木さんを描いてみた

f:id:nyanpasuAxela:20180603172745j:plain

にゃんぱすー\(๑¯Δ¯๑)

動画ファイルを基にopencvやDCGANなど使って、128×128のカラー画像生成をしてみました。すでにDCGANや各技術は多数の記事が出ていますが、メモとして記載。

これまで、本で機械学習系のことを読んでたので、試しにkerasでDCGANを初実装してみました。機械学習の入門書として、

・ゼロから作るDeep Learning (斎藤 康毅 著)

が分かりやすく、それを読んだ後であれば、

・現場で使えるTensorFlow開発入門 (機械学習の知識ゼロ状態からだと、できても意味わからないと思います)

がkeras機械学習実装入門として分かりやすかったです。

 GANというのは機械学習のネットワークで次の絵のように、ノイズなどを入力として本物に似たデータ(画像など)を生成するGenerator[G]モデルと、本物かGeneratorが作った偽物かを判断するDiscriminator[D]モデル、の2つの別々のモデルから成りたっています。

f:id:nyanpasuAxela:20180603181600p:plain

 GeneratorはDiscriminatorが本物であると判断してしまうような本物に似たデータを作成しようとし、
 Discriminatorは入力されたデータ(画像など)を本物か、Generatorが作った偽物かを正しく判断しようとします。
 GeneratorとDiscriminatorを交互(もしくは制限を加え)に学習していくことで互いに、Generatorはより本物に似たデータを作れるようになり、Discriminatorは本物であるか偽物であるかより正確に判断できる識別器となっていきます。


GANの分かりやすい情報として、既にいろいろネットに上がっています。

今さら聞けないGAN(1) 基本構造の理解 - Qiita
GANについて概念から実装まで ~DCGANによるキルミーベイベー生成~ - Qiita
はじめてのGAN


DCGAN(元論文 https://arxiv.org/abs/1511.06434 )はこのGANのネットワークの部分に画像機械学習で大成功しているCNNを用いたものです。さらに、よりよく収束するために、

・プーリング層をストライド2のConvolution層に変更
・全結合層を使わない
・BatchNormalizationを多用
・GeneratorにReLU, DiscriminatorにLeakyReLUを多用

しています。


 今回行ったことの全体の流れとしては、

動画データ

フリー動画編集ソフトAviutlで間引き&bmp

pythonでjpg化&同一画像削除

openCVで顔認識&顔画像作成

作成した顔画像データを基にDCGANで模擬高木さん作成

をしました。

まずは動画編集ソフトAviutlでbmp化しました。

f:id:nyanpasuAxela:20180603185158j:plain

方法は「Aviutl 連番BMP出力」とかでググればでてくるプラグインを入れてエクスポートすればできます。
Aviutlはフリーでプラグインがたくさんあり、動画の編集や形式変更などいろいろできるのでとても便利です。


次にpythonで同一画像の削除と、bmpのままだと容量が大きすぎるのでjpg化をしました。(↓がコード, 動画画像の順番と画像名の順番ほぼ同じになっているの想定)

import cv2
import glob
import os
import numpy as np
from keras.preprocessing.image import load_img, array_to_img,img_to_array

from PIL import Image
filename=glob.glob('D:\高木さん\新しいフォルダー/test9*.bmp')
num1=1

img = Image.open(filename[0])
imgArray = img_to_array(img)
Abef=imgArray

for F in filename:
    print(F)
    
    img = Image.open(F)
    imgArray = img_to_array(img)
    print(type(imgArray))
    Aaft=imgArray
    print('aft')
    dif = Aaft - Abef
    Abef=imgArray
    print(np.sum(dif,axis=None))
    if abs(np.sum(dif,axis=None)) > 500000:
        imgImage = array_to_img(imgArray)
        #print(img.format)
        imgname=('data/takagisan3/jpg_'+os.path.basename(F)[0:-3]+'jpg')
        print(imgname)
        img.save(imgname)


↓作ったjpgファイルからpython opencvを使って顔抽出&正方形に近い形状のデータだけjpgとして保存。
基本的には(OpenCVでアニメの顔検出 - Qiita)をそのままで若干だけ変更しました。

import os
import cv2
import glob
from keras.preprocessing.image import load_img, array_to_img,img_to_array

# 特徴量ファイルをもとに分類器を作成
classifier = cv2.CascadeClassifier('D:\python\lbpcascade_animeface-master\lbpcascade_animeface.xml')

filename=glob.glob('D:\\python\\jupyter_notebook\\data\\takagisan3\\*')
for F in filename:

    image = cv2.imread(F)
    gray_image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    faces = classifier.detectMultiScale(gray_image)
    if len(faces) > 0:    
        # ディレクトリを作成
        output_dir = 'faces'
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)
        for i, (x,y,w,h) in enumerate(faces):    
            # 一人ずつ顔を切り抜く
            face_image = image[y-int(0.2*h):y+int(1.2*h), x-int(0.2*w):x+int(1.2*w)]
            if (face_image.shape[0]/(face_image.shape[1]+0.1) > 0.7 and face_image.shape[0]/(face_image.shape[1]+0.1) < 1.3): #正方形に近いものだけ保存
                output_path = os.path.join(output_dir,os.path.basename(F)[0:-4]+'{0:03d}.jpg'.format(i))
                cv2.imwrite(output_path,face_image)

f:id:nyanpasuAxela:20180603193509j:plain

4時間の動画からおよそ33000枚の顔と認識された画像ができました!

内訳を見てみると、顔以外の画像や使えなさそうな荒い画像、目だけの画像などが多数含まれており3時間ほどかけて手作業で除去しました。
除去結果、大体19000枚の学習用顔データができました♪

ということでDCGANの論文に則り、kerasでプログラム作成をしました。
DCGANの論文は事細かに構造が書かれており、再現したつもりが急激に学習が進みパラメータ調整してもうまく動きませんでした。

ということで、何が原因かよくわからなかったので、キルミーベイベー画像を再現していた良記事(
GANについて概念から実装まで ~DCGANによるキルミーベイベー生成~ - Qiita
)のコードをもとにDCGANを作成しました。結局、なんだかんだで元記事のモデル構造を使わさせていただきました。


↓作ったコード

import os
import glob
import math
import random

import numpy as np
import matplotlib.pyplot as plt
from tensorflow.python import keras
from tensorflow.python.keras.optimizers import Adam
from tensorflow.python.keras.models import Model, Sequential
from tensorflow.python.keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array, array_to_img
from tensorflow.python.keras.layers import LeakyReLU,Dropout,ZeroPadding2D
from tensorflow.python.keras.layers import Activation, BatchNormalization, Reshape, Flatten, Add, Input, Conv2D, Conv2DTranspose, Dense, Input, MaxPooling2D, UpSampling2D, Lambda

data_dir = 'D:/python/jupyter_notebook/data/tmp/'
batch_size = 128
img_shape = (128,128,3)

gen = ImageDataGenerator(rescale=1/127.5, samplewise_center=True)
iters = gen.flow_from_directory(
    directory=data_dir,
    classes=['faces'],
    class_mode=None,
    color_mode='rgb',
    target_size=img_shape[:2],
    batch_size=batch_size,
    shuffle=True
)
x_train_batch=next(iters)
for i in range(3):
    display(array_to_img(x_train_batch[i]))
    
def generator_model():
    model = Sequential()
    model.add(Dense(128 * 32 * 32, activation="relu", input_shape=(100,)))
    model.add(Reshape((32, 32, 128)))
    model.add(BatchNormalization(momentum=0.8))
    model.add(UpSampling2D())
    model.add(Conv2D(128, kernel_size=3, padding="same"))
    model.add(Activation("relu"))
    model.add(BatchNormalization(momentum=0.8))
    model.add(UpSampling2D())
    model.add(Conv2D(64, kernel_size=3, padding="same"))
    model.add(Activation("relu"))
    model.add(BatchNormalization(momentum=0.8))
    model.add(Conv2D(3, kernel_size=3, padding="same"))
    model.add(Activation("tanh"))
    
    noise = Input(shape=(100,))
    img = model(noise)
    return Model(noise, img)

def discriminator_model(img_shape):
    model = Sequential()
    model.add(Conv2D(32, kernel_size=3, strides=2, input_shape=img_shape, padding="same"))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dropout(0.25))
    model.add(Conv2D(64, kernel_size=3, strides=2, padding="same"))
    model.add(ZeroPadding2D(padding=((0, 1), (0, 1))))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dropout(0.25))
    model.add(BatchNormalization(momentum=0.8))
    model.add(Conv2D(128, kernel_size=3, strides=2, padding="same"))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dropout(0.25))
    model.add(BatchNormalization(momentum=0.8))
    model.add(Conv2D(256, kernel_size=3, strides=1, padding="same"))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dropout(0.25))
    model.add(Flatten())
    model.add(Dense(1, activation='sigmoid'))
    
    img = Input(shape=img_shape)
    validity = model(img)
    return Model(img, validity)    
    
optimizer = Adam(lr=0.0002, beta_1=0.5)

generator = generator_model()
discriminator = discriminator_model(img_shape)
discriminator.trainable = True
generator.compile(loss='binary_crossentropy', optimizer=optimizer)
discriminator.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'])

z = Input(shape=(100,))
img = generator(z)

discriminator.trainable = False
valid = discriminator(img)

generator_containing_discriminator = Model(z, valid)
generator_containing_discriminator.compile(loss='binary_crossentropy', optimizer=optimizer)

#saveディレクトリ
model_save_dir = 'dcgan/models'
img_save_dir = 'dcgan/imgs'

#5×5生成画像生成
imgs_shape = (5, 5)
n_img_samples = np.prod(imgs_shape)

sample_seeds = np.random.uniform(-1, 1, (n_img_samples, 100))

hist = []
logs = []

#保存先フォルダ生成
os.makedirs(model_save_dir, exist_ok=True)
os.makedirs(img_save_dir, exist_ok=True)

def save_imgs(path, imgs, rows, cols):
    base_width = imgs.shape[1]
    base_height = imgs.shape[2]
    channels = imgs.shape[3]
    output_shape = (
        base_height*rows,
        base_width*cols,
        channels
    )
    buffer = np.zeros(output_shape)
    for row in range(rows):
        for col in range(cols):
            img = imgs[row*cols + col]
            buffer[
                row*base_height:(row + 1)*base_height,
                col*base_width:(col + 1)*base_width
            ] = img
    array_to_img(buffer).save(path)


Totalstep=500000
for step, batch in enumerate(iters):
    if len(batch) < batch_size:
        continue
    if step > Totalstep:
        break
    half_batch = int(batch_size / 2)
    z_d = np.random.uniform(-1, 1, (half_batch, 100))
    
    g_pred = generator.predict(z_d)
    discriminator.trainable = True
    real_loss=discriminator.train_on_batch(batch[:half_batch], np.ones((half_batch, 1)))
    fake_loss=discriminator.train_on_batch(g_pred, np.zeros((half_batch, 1)))
    d_loss = 0.5 * np.add(real_loss, fake_loss)
    
    
    z_g = np.random.uniform(-1, 1, (batch_size, 100))
    discriminator.trainable = False
    g_loss = generator_containing_discriminator.train_on_batch(z_g, np.ones((batch_size, 1)))
    
    
    logs.append({'step': step, 'd_loss': d_loss[0], 'acc[%]': 100*d_loss[1] , 'g_loss': g_loss})
    print(logs[-1])
    if step%200==0:
        
        
        img_path = '{}/generate_{}.png'.format(img_save_dir, step)
        save_imgs(img_path, generator.predict(sample_seeds), rows=imgs_shape[0], cols=imgs_shape[1])
    if step%5000==0:
        print(step)
        generator.save('{}/generator_{}.hd5'.format(model_save_dir, step))
        discriminator.save('{}/discriminator_{}.hd5'.format(model_save_dir, step))


#↓推測時のみ用
Totalstep=30
generator.load_weights('D:/python/jupyter_notebook/dcgan/models/generator_25000.hd5')
for step, batch in enumerate(iters):
    if len(batch) < batch_size:
        continue
    if step > Totalstep:
        break
    z_d = np.random.uniform(-1, 1, (batch_size, 100))
    g_pred = generator.predict(z_d)
    img_path = '{}/pred_generate_{}.png'.format(img_save_dir, step)
    save_imgs(img_path, generator.predict(z_d), rows=imgs_shape[0], cols=imgs_shape[1])    
              


結果
f:id:nyanpasuAxela:20180603172745j:plain

gifで
f:id:nyanpasuAxela:20180603195330g:plain

ほにゃほにゃですが、雰囲気だけでもポくなりました(*´ω`*)

今回はお借りしまくった感じで、まだ前処理とかちゃんとしてないので、そのうちもっと精度上げたいところです(`・ω・´)ゞ

3DF Zephyr Freeを使って愛車を3Dモデル化してみた

f:id:nyanpasuAxela:20180514222226p:plainhttps://www.3dflow.net/3df-zephyr-pro-3d-models-from-photos/

前回、イチからメッシュを張り、高木さんモデルをblenderで手作業で時間をかけて作成しました。次は愛車を3Dモデル化したくて複数枚の写真から3Dモデルを生成してくれるソフトを試してみました。

 

まず、複数枚の写真→3Dモデル変換ツールを調べて最初によく出てきたのは「Autodesk Remake」。

しかし、つい最近Autodesk Recapに変更されて、なかなか期限付き体験版と有料版以外見つかりませんでした。(体験版後無料で使えるかも?)

 

もう少し調べると3DF Zephyr Freeが出てきました。英語サイトだったので、若干不安になりつつも、さらに調べていくと、

 

写真→3Dモデル変換に関するとても良い記事が!

Photogrammetryのススメ ~ソフト比較と質感設定~ - 人柱系CGモデラーのTipsブログ

 

ここで、初めて今回やりたいことがphotogrammetryと呼ばれていることや撮影のコツを知りました。これを見る限り、フリー(for personal)でいうと3DF Zephyrが良さげかな、ということで3DF Zephyr Freeをインストール。日本の代理店のサイトもありましたが、代理店では有料版しか扱っていなかったため、元の英語サイトでインストール。

 

Free版では写真の枚数が50枚までに限られますが、チャレンジ!

 

まずは、今回の被写体である車を撮影!

先ほどのphotogrammetryのブログや他サイトに、

  • 5~10°毎ぐらいには写真があったほうが良い
  • 影はないほうが良い
  • 画像はくっきりしていたほうが良い

とあったので、

 

一眼カメラの設定を、

  • 車体ボディの反射具合が角度によって変わりにくいようにコントラスト低め
  • クッキリするように明瞭度高め、

で撮影しました。

 

また、photogrammetryは複数写真間の対応点を基に3Dモデルを再構成しているようなので、撮影対象が常に同じものであるという前提だと考え、写真によってボヤけ度があったり、光の具合が大きく変わらないようにし、且つ、できるだけ設定を同じようにしました。

具体的には一眼カメラは、

  • マニュアルモード
  • F値大きめ(F10以上に絞る)で固定[写真ごとでできるだけ距離の違いによってボケけ度が変わらないように]
  • ISO感度シャッタースピード固定[F値と合わせて、光の強度変わらないように]
  • 焦点距離固定(測定する距離も同じぐらいにした)

にしました。さらに、環境としても暗すぎない感じに車全体が影に入っており、撮影する角度によって反射が変わりにくいようにしました。

 

念のため50枚以上撮影し後で、選択しました。

 

f:id:nyanpasuAxela:20180514225539j:plain

 

ソフト自体はインストールすると日本語を選ぶことができます。

使い方は3Dモデル化するだけなら、とても操作は単純で、チュートリアルの動画を見れば英語がわからない人でもできると思います。

 

まずは、50枚選んで、低密度ポイント数で点群データ化。

f:id:nyanpasuAxela:20180514225912j:plain

周りの写真が中二病っぽくてかっこいいですね(*´ω`*)

ですが、まだ点はまばらです。

 

ここからさらに点群を高密度化します。

f:id:nyanpasuAxela:20180514230013j:plain

形状が分かるレベルになりました。

 

次に、点群データをメッシュ化。

f:id:nyanpasuAxela:20180514230215j:plain

おおぉぉぉ~~~( ゚Д゚)

本物っぽくなってきました。

 

さらに、テクスチャ(絵)メッシュを生成して完了。

f:id:nyanpasuAxela:20180514232233j:plain

f:id:nyanpasuAxela:20180514232941j:plain

f:id:nyanpasuAxela:20180514233002j:plain

f:id:nyanpasuAxela:20180514233032j:plain

 

動画だと下のような感じです。思ったより実物に近い感じになりました!


nyanpasuAxela_3D

 

計算時間は設定に依存し、基本的には精度や精細さが高くなるような設定は時間がかかるようになってました。デフォルト設定で割とよく時間的にも精度的に良さそうですが、私はデフォルトより少し精度高い設定にしてi7-8700K CPU 6core 3.7GHz 3.7GHz, GTX1080TiのPCで低密度点群化、高密度化、メッシュ化、テクスチャメッシュ化それぞれ5~10分ぐらいかかりました。3DF Zephyr Freeは少しだけGPU使える部分あると書いてありましたが、何も考えずとりあえず回していたら今回の作業中でタスクマネージャーのパフォーマンスを眺めてたら、CPUほぼ100%、GPU10数%ぐらいの使用率になってました。

 

とりあえず3Dモデル化する!といったところまでだけで言えば、インストール含め3時間かからないぐらいで形になりました。詳細機能は使ってないのでわかりませんが、単純な今回行ったことだけで言えば作業は非常に簡単で、思った以上の精度が出ました。

 

出力としてobjファイルを出せるのもありがたいところです。

 

せっかく、objファイルになったので、blenderにエクスポートして高木さんと共演↓ww

 

 

Blenderで3Dキャラモデル作ってみた

 

f:id:nyanpasuAxela:20180508234924p:plain

 

配布されていないキャラクターでも自分で作ってみたい!モデルつくりって実際なにやってるのか知りたい!ということで、3Dモデル作成に初挑戦し動かしてみました。

 

3DCG作成について調べてみると、CG化までには、

 

モデリング(メッシュデータ作成)

テクスチャ設定(質感、カラーなどの設定)

ライティング設定

レンダリング(3Dから2D画像に落とし込む)

 

という流れをたどるようです。

 

3DCG作成ツールとしてはmayaなどのソフトウェアが有名なようですが、値段がウン十万単位だったので即却下。

どれも高額ではありましたが、その中でBlender」は無料だったため今回blenderでモデル作成することにしました。Blenderは上述のモデリングからレンダリングまで一通りはできます。

 

3DCGに関わったことのない私、さっそく書店に行って本を購入!ネット上にも情報はたくさんありましたが、紙の良さに惹かれて。

f:id:nyanpasuAxela:20180508220501j:plain

Blender 3Dキャラクターメイキング・テクニック Benjamin著

この本、amazonだと酷評だったのですが、初心者の私でも普通に難なくモデル作成でき動かすことができました。「顔を作るのが難しくて説明が少ない」とamazonではレビュられてましたが、本に前から&横から見た下絵と頂点の絵が複数あるのでそれを参考にどんどん頂点を打ってけば、できました。

ついでに、ゲームエンジンUnreal Engine 4の本も購入。こちらのソフトも販売無し目的に使用する場合は無料です。

 

 

まずはblenderのソフトをインストールし、本に記載してあるデータ(私は下絵[前面から見た絵、90度側面から見た絵]のみ使用)もダウンロードしました。

 

その後、およそ下記の通りの流れでキャラ作成しました。

 

モデリング(メッシュ作成)

マテリアル・テクスチャ設定(色、質感設定)

アニメーション設定

 

 メッシュ作成

前面から見た下絵と側面から見た下絵をもとにどんどん頂点を配置し、線でつないでいきます。

まず、前面から見たxz平面で下絵と本に載っている絵の点を参考に、頂点の位置を決めます。都合により下絵は↓の画像には載せてませんが下絵をメッシュの後ろに重ねながら作業しました。

f:id:nyanpasuAxela:20180508232110p:plain

ある程度区切りのいいところまで頂点を売ったら、視点を90度回し側面から見たyz平面で基本的にはy方向だけを動かして下絵、本の絵を参考に頂点の位置を決めます。

f:id:nyanpasuAxela:20180508235714p:plain

これで、メッシュの頂点の位置が決まるので、基本的にはこれを多数繰り返していけばほぼメッシュ完成です。ある頂点から線を伸ばして新たな頂点を作成するショートカットEと接続のショートカットFを使いまくりです。

その他にも便利なショートカットやツールを使ってさらに効率アップです。

f:id:nyanpasuAxela:20180508223117j:plain

下絵を参考にはしましたが、違う画像やフィギュアも見つつ感覚で補正して違うキャラクターを作成しました。

今回行ったのは主に、前面から見て位置合わせして、側面から位置合わせして、拡大縮小してと単純な作業なので、違うキャラクターを難なく作ることができました(クオリティーを気にしなければですが)。

 

思ったほど、ここまで困難はなかったのですが、想像以上に時間がかかりました。GW中だったこともあり2,3日もあれば簡単なものが動かせると思ってましたが、メッシュ作成で既に3日経過( ゚Д゚)

 

「けど、メッシュ完成したし、もうすぐ動くぞいっ!」

と思っていましたが、ここからさらに時間が、、、

マテリアル・テクスチャ設定

完成したメッシュデータに区切り線を入れてメッシュ領域を分けて色や反射、透過などの設定をしていきます。絵を貼り付けるテクスチャ設定も行います。

テクスチャ設定ではメッシュを区切り線で分けた領域で平面化出力し、お絵かきソフトで色塗りしていきました。

f:id:nyanpasuAxela:20180508224711j:plain

3Dメッシュの対応位置を見つつ塗っていきました。

3Dに直接塗るのではなく、平面に切り開いて塗るんですね、知らなかったです。

 

塗り終わって、

 

前から、

f:id:nyanpasuAxela:20180508225021j:plain

横から

 

f:id:nyanpasuAxela:20180508225036j:plain

高木さん風ですが、中二病の六花ちゃんのフィギュアを見ながら作ってたらちょっとセクシーになりましたw

随時ライティング(光の強度、光源位置など)、レンダリングも設定

アニメーション設定

動きについては、下記①、②などの方法でキーとなる恰好を決め、後述のようにそれらを時間的に配置することでそのキーを補間する形で動作します。

 

①メッシュを少し変えてほしい形(ウインクなど)にして、位置記録

f:id:nyanpasuAxela:20180508230248j:plain

 

②骨組み(アーマチュア)を作ってメッシュとの連動具合(ウェイト)を決め、ほしい恰好に動かして記録

f:id:nyanpasuAxela:20180508230330j:plain

f:id:nyanpasuAxela:20180508230352j:plain

 

 

うん可愛い(*´ω`*)

f:id:nyanpasuAxela:20180508230731j:plain

 

上記の恰好や色などを時間的に下画像のように配置すると、後はそれを補間するかたちでアニメーションします。

f:id:nyanpasuAxela:20180508231031j:plain

 

 

 

完成!

f:id:nyanpasuAxela:20180508233820g:plain

動いた~~~~~!

 

 

最終的に力尽きて単純な動きにw

 

なんだかんだで、1日中やっていたわけではありませんが、3DCGツール使ったことない状態からトータルで6日間近くかかりました。

 

これはハイクオリティー、動きも多彩にしたら、とてつもなく時間かかりそうですね。

 

とりあえず、っぽくなりましたし、動いてよかったです。

はてなブログはじめました

 

はじめまして!

ふぎと申します。

 

のんのんびよりの宮内れんげ、通称れんちょん仕様のアクセラに乗っている痛車乗りです。

 

f:id:nyanpasuAxela:20180428125334j:plain

車のコミュニティ「みんカラ」でブログや整備手帳を書いていますが、車以外のこと、ものづくりの日記を書きたくて、はてなブログを始めることにしました。車・キャンプ関連はみんカラへ https://minkara.carview.co.jp/userid/2454945/profile/

 

はてなブログを書く目的としては、、、

 ・ものづくり、技術習得のモチベ上げ

 ・技術的、ツール的なことの記録

 ・ものつくった!みて!みて!ンゴッ

 

ハード、ソフト、機械学習AIやラズパイからアナログ的なものまでいろいろやってみたり、作ったりしたいと思ってます。はてなブログの内容は主にそのモノづくりやソフト触りの日記になる予定です。

 

よろしくお願いします\(๑¯Δ¯๑)

 f:id:nyanpasuAxela:20180428134222g:plain