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

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

【Blender】Blender2.8で輪郭線のみを抽出した画像の作成方法

 色々とBlender2.8をいじってみています。

 今回はタイトル通り、Blender2.8で輪郭線のみを抽出していく方法です。

 では始めます。


1:FreeStyleで輪郭線を出す
 対象オブジェクトはなんでもいいのですが、Blender2.8を起動させてとりあえずスザンヌを用意します。
f:id:rikoubou:20190110162713p:plain

 輪郭線を出したい場合は、以下のように「Render」タブを左クリックで開いて一番下にある「FreeStyle」にチェックを入れます。
f:id:rikoubou:20190110163006p:plain

 この状態で上部メニューの「Render」→「Render Image」を選択して画像としてレンダリングしてみます。
f:id:rikoubou:20190110163400p:plain

 以下のように輪郭線がレンダリング時に出ていることがわかります。
f:id:rikoubou:20190110163440p:plain


2:対象オブジェクトを透過させる
 次に対象のオブジェクトを透過します。

 対象のオブジェクトを選択した状態で「Material」タブを左クリックで開き「New」ボタンを左クリックします。
f:id:rikoubou:20190110164414p:plain

 するとMaterialが追加されます。
f:id:rikoubou:20190110164519p:plain

Surface」の項目の中にある「Surface」を左クリックして「Transparent BSDF」を選択します。
f:id:rikoubou:20190110164731p:plain

 その後「Settings」の項目の中にある「Blend Mode」のプルダウンから「Alpha Blend」を選択します。
f:id:rikoubou:20190110175454p:plain

 その状態で画面上部の「Rendered」のボタンを左クリックすると、以下のようにオブジェクトが透明になります。
f:id:rikoubou:20190110175936p:plain

 ここまでできたら、先ほどと同じように画像としてレンダリングしてみると、以下のように輪郭線のみが出力されます。
f:id:rikoubou:20190110180405p:plain


3:背景を透過させる
 背景を透過させておきたい場合もあるので、次はレンダリング時の背景を透過させます。

「Render」タブの「Film」の項目の中にある「Alpha」のプルダウンから「Transparent」を選択します。
f:id:rikoubou:20190110180842p:plainf:id:rikoubou:20190110180911p:plain

 同じように画像としてレンダリングしてみると、イラストソフトなどを使い慣れている方ならわかると思いますが、以下のように背景が透過された画像出力できます。
f:id:rikoubou:20190110181150p:plain

 あとはレンダリングした画面の上部メニューにある「Image」→「Save As...」で画像として保存できます。
f:id:rikoubou:20190110181440p:plain


 以上がBlender2.8で輪郭線のみを抽出した画像の作成方法です。

 イラストの素材にする場合など、色々使えるかと思います。


・参考資料

【Blender/python】Blender2.8でちょっとした自作アドオンを作ってみた

 まだまだリリースは先なBlender2.8ですが、ベータ版が公開されてからはだいぶ安定して使えるようになってきている感じがあります。

 そんな中、ちょっとBlender2.8でアドオンを作ってみるか、と思い立って作ってみたのでその備忘録です。

 基本的にはBlender2.79でアドオンを作ったことある人向けで書きますが、最後にBlender2.79時代のアドオン作成備忘録の記事も載せておくのでわからない場合はそちらも参照してみてください。

 では始めます。


1:Blender2.8のBlender Python API Documentation
 まだ開発中のBlender2.8ですが、アドオンに必要なPython APIについてはほぼほぼ確定しているようです。

 すでにBlender2.8のPython API Documentationができているので、詳しいAPIの内容は以下を参照してください。


2:Blender2.79とBlender2.8でのアドオン記述の違い
 まず前提としてBlender2.79のアドオンはBlender2.8では使えません。
 Blender2.79のアドオンをBlender2.8で使うには必ずプログラムの修正が必要になります。

・2−1:アドオンの情報部分
 アドオンの情報を記述する「bl_info」にある、対応するBlenderのバージョン部分を2.8に合わせた書き方に修正する必要があります。

bl_info = {
    "name": "hogehoge", # アドオン一覧に表示される名前
    "author": "author",  # 作者
    "version": (1, 0), # アドオンのバージョン
    "blender": (2, 80, 0), # 対応するBlenderのバージョン(ここが「(2, 79, 0)」のような記述だとエラーになる)
    "location": "",
    "description": "hogehogehoge", # アドオンの説明
    "warning": "",
    "support": "TESTING", # アドオンの分類
    "wiki_url": "",
    "tracker_url": "",
    "category": "Material" # カテゴリー
}

・2−2:モジュールの登録/解除部分
 Blender2.79までは以下のような書き方でモジュールを登録/解除していました。

・旧モジュール登録/解除

import bpy

# アドオンについての説明
bl_info = {
    # 省略
}

# アドオンのクラス
class Hoge(bpy.types.Operator):
    # 省略

    def execute(self, context):
        # 省略
        return {'FINISHED'}

# メニューを構築する関数
def menu_fn(self, context):
    self.layout.separator()
    self.layout.operator(Hoge.bl_idname)


# アドオン有効化時の処理
def register():
    bpy.utils.register_module(__name__)
    bpy.types.VIEW3D_MT_object.append(menu_fn)
    print("Add-on:ON")


# アドオン無効化時の処理
def unregister():
    bpy.types.VIEW3D_MT_object.remove(menu_fn)
    bpy.utils.unregister_module(__name__)
    print("Add-on:OFF")

# メイン関数
if __name__ == "__main__":
    register()

 アドオンのクラスを記述したあとに「def menu_fn(self, context):」部分で追加するメニュー部分を、「def register():」でアドオン有効化時の処理、「def unregister():」でアドオン無効化時の処理を記述するという書き方でした。

 Blender2.8ではその記述の仕方が一新され、以下のようにより記述しやすくなりました。

・新モジュール登録/解除

import bpy

# アドオンについての説明
bl_info = {
    "name": "sample: add-onTest",
    "author": "ssss",
    "version": (2, 0),
    "blender": (2, 80, 0),
    "location": "location",
    "description": "sample",
    "warning": "warning",
    "support": "TESTING",
    "wiki_url": "http://hogehoge.com",
    "tracker_url": "http://hogehoge2.com",
    "category": "Object"
}

# UI(ボタンを構築するクラス)
class UI(bpy.types.Panel):
  bl_label = "add sphere panel"
  bl_space_type = "VIEW_3D"
  bl_region_type = "UI"
  
  def draw(self, context):
    self.layout.operator("add_sphere_object.button")

# アドオンのクラス
class AddSphere(bpy.types.Operator):
    bl_idname = "add_sphere_object.button" # ID
    bl_label = "Add Sphere" # メニューに追加されるラベル
    bl_description = "Add Sphere" # 関数の説明
    bl_options = {'REGISTER', 'UNDO'} # 処理の属性

    def execute(self, context):
        bpy.ops.mesh.primitive_ico_sphere_add()
        print("Add Sphere End") # コンソールにログ出力
        self.report({'INFO'}, "Add Sphere End")
        return {'FINISHED'}

# クラスをまとめる
classes = (
  UI,
  AddSphere
)

# まとめたクラスを一度に登録
register, unregister = bpy.utils.register_classes_factory(classes)

 クラスをまとめて「classes = (クラス名, クラス名...)」として、それを「register, unregister = bpy.utils.register_classes_factory(classes)」とすることで一気にクラスを登録します。
 この書き方をすることでかなり短い行数で記述することができるようになっています。

 これらの他にもいくつか変更点があるようですが、全体に影響する大きな変更はこの2点ぐらいです。

 他の変更は以下のページにまとめられているので各自で確認するようにしてください。


3:自作したアドオン
 今回Blender2.8で自作したアドオンは以下になります。アドオンの内容としては「グリースペンシルでのMaterialのStrokeの色をFillの色にコピーする」というものです。
 コピペして「copyStrokeColorToFillColor.py」のファイル名で保存してからアドオンとしてBlenderにインストールすれば使えます。コピペが面倒な場合は記事の後の方でファイルのダウンロードリンクを貼っておくのでそこからダウンロードしてみてください。

・copyStrokeColorToFillColor.py

#
# グリースペンシルのMaterialのStrokeの色を
# Fillの色にコピーするアドオン
#
import bpy

bl_info = {
    "name": "Copy Stroke color to Fill color", # アドオン一覧に表示される名前
    "author": "whip",  # 作者
    "version": (1, 0), # アドオンのバージョン
    "blender": (2, 80, 0), # 対応するBlenderのバージョン
    "location": "",
    "description": "copy Stroke color to Fill color", # アドオンの説明
    "warning": "",
    "support": "TESTING", # アドオンの分類
    "wiki_url": "",
    "tracker_url": "",
    "category": "Material" # カテゴリー
}

# UIに関するクラス
class CopyColorUI(bpy.types.Panel):
  bl_label = "Copy Stroke color to Fill"
  bl_space_type = "PROPERTIES"
  bl_region_type = "WINDOW"
  bl_context = "material"
  
  def draw(self, context):
    self.layout.operator("copy.button") # ボタンを作成

# ボタンに関するクラス
class CopyColor(bpy.types.Operator):
  bl_idname = "copy.button"
  bl_label = "copy color"
  bl_options = {'REGISTER', 'UNDO'} # オブション
  
  # ボタンが押された時に実行される処理
  def execute(self, context):
    # 色をコピー
    lineColor = bpy.context.object.active_material.grease_pencil.color
    bpy.context.object.active_material.grease_pencil.fill_color = lineColor
    self.report({'INFO'}, "copy end") # Blenderのログに出力
    return{'FINISHED'}

# クラスをまとめる
classes = (
  CopyColorUI,
  CopyColor
)

# まとめたクラスを一度に登録
register, unregister = bpy.utils.register_classes_factory(classes)

 アドオンのインストールが済んだら「Preferences」画面の「Add-ons」→「Testing」タブを開くと「Copy Stroke color to Fill color」というのがあるはずなので、それにチェックを入れます。
f:id:rikoubou:20190109173841p:plain

 これでアドオンが有効になります。

 アドオンが有効な状態だと、2D Animationタブを開いた画面の「Material」の項目の最後に「Copy Stroke color to Fill」という項目が追加され、その中に「copy color」ボタンが表示されます。
f:id:rikoubou:20190109174242p:plain

 使い方としては、例えば以下のようにStrokeの色とFillの色が違っている状態でこの「copy color」ボタンを押すとFillの色がStrokeの色と同じになります。(アルファ値も同じになります)
f:id:rikoubou:20190109174648p:plainf:id:rikoubou:20190109174701p:plain


 以上が今回Blender2.8で作成してみたアドオンになります。
 ボタンの追加方法とかはまた色々と面倒になるかと思うので、後々記事にでもしようと思います。

 今回作成したアドオンのファイルは以下からダウンロードできるので、ご自由にどうぞ。


・参考資料


・Blender2.79時代のアドオン関連記事

【Blender】Blender2.8でのアドオン追加方法

 Blender2.8ではUIも色々変わっています。

 基本的にはBlender2.79の時と変わりませんが、今回はBlender2.8でのアドオン追加方法を説明していきます。


・Blender2.8でのアドオン追加方法
 Blender2.8では「Edit」に「Preferences」が移動しているので、「Edit」→「Preferences」を左クリックします。
f:id:rikoubou:20190109175729p:plain

 あとは今まで通り「Add-on」のところを左クリックすると見慣れた画面になります。
 アドオンを追加するには「Install」ボタンを左クリックします。
f:id:rikoubou:20190109181456p:plain

 するとアドオンファイル選択ができるので、ファイルを選択して「Install Add-on from File」ボタンを左クリックします。
f:id:rikoubou:20190109181402p:plain

 インストールできたら一覧にアドオンが追加されているので、対象のアドオンにチェックを入れて有効化します。
 Blenderを再起動させても有効化させたい場合は「Save Preferences」ボタンを左クリックして設定を保存します。
f:id:rikoubou:20190109181702p:plain

 これでアドオンが使えるようになります。


 以上がBlender2.8でのアドオン追加方法です。基本的にはBlender2.79の時代と変わりませんが、UIが全く違っていて戸惑うかと思ったので備忘録として記事にした次第です。

【Raspberry Pi】Raspberry PiのSDカードバックアップ方法

 あけましておめでとうございます。
 去年の年末は体調が悪かったりちょっと忙しかったりでブログの更新がほとんどできていませんでした(言い訳)。

 今回はよくあるRaspbery PiのSDカードのバックアップの方法です。よくあるのになぜ記事にするかというと、毎回調べるのが面倒なのとコマンドにとあるオプションをつけないとものすごく時間がかかるということがわかったからです(二度とそんな思いはしたくない……)。

 以前書いた記事と被っている部分もあります。

 環境はMacOSなので、他の環境の場合は方法が違ってくるので注意してください。

 ではその方法を解説していきます。


1:SDカードのバックアップを取る
 まずSDカードを接続していない状態で以下のコマンドを実行します。

$ df

 すると「/dev/disk」から始まる一覧が表示されます。

 その後、SDカードリーダーなどにコピーしたいSDカードを差し込んだ状態でターミナルを立ち上げてもう一度以下のコマンドを実行します。

$ df

 この前後のコマンドの結果を比較して増えているものが対象のSDカードとなるのでメモしておきます。
※以後は「/dev/disk2」が該当のSDカードという前提で進めます。

 対象のSDカードがわかったら以下のコマンドを実行してSDカードをアンマウントします(「/dev/disk2」の部分は先ほど確認したものに任意に変更してください)。

$ diskutil umountDisk /dev/disk2

 SDカードをアンマウントしたら以下のコマンドを実行してSDカードからイメージファイルを作成します。

$ sudo dd if=/dev/rdisk2 of=raspberry_pi_backup.img bs=1m

「if=/dev/rdisk2」の部分は任意に変更してください(diskの前のrは必ずつけてください)。
「of=raspberry_pi_backup.img」が保存するイメージファイルのパスとファイル名の指定部分です。
「bs=1m」のオプションは必ず記述してください。これがあるのとないのとでは吸い出す時間が数倍違ってきます。

 コマンド実行中に「Ctrl+Tキー」を押すことで進捗状況を確認することができます。

 またSDカードを丸ごとコピーするため、イメージファイルを作成するPC側にSDカードと同じ容量以上を確保しておく必要もあります。


2:バックアップしたイメージファイルの書き込み
 新品のSDカードを使わない場合は、「SD Card Formatter」を使ってフォーマットします。

 SDカードを差し込んでSD Card Formatterを起動させると以下のような画面になるので、フォーマットボタンを押してフォーマットします。
f:id:rikoubou:20190107162440p:plain

 フォーマットが済んだら以下のコマンドを実行してSDカードに書き込みます。

$ diskutil unmountDisk /dev/disk2
$ sudo dd if=raspberry_pi_backup.img of=/dev/rdisk2 bs=1m conv=sync

 1つ目でSDカードをアンマウントし、2つ目でSDカードに書き込んでいます。
「if=raspberry_pi_backup.img」は書き込むイメージファイルパスです。
「of=/dev/rdisk2」の部分は任意に変更してください(diskの前のrは必ずつけてください)。
「bs=1m」のオプションをつけることで処理が早くなります。
「conv=sync」のオプションをつけることでOSを書き込む際に出る場合があるエラーを抑制することができるそうです。


 以上がRaspberry PiのSDカードのバックアップと書き込み方法です。
 オプションがあるのとないのとで全く違ってくるので本当に注意してください…。


・参考資料

【OpenCV】Webカメラから取得したキャプチャが過去のものになっている時の対処方法

 今回はタイトルにあるようにOpenCVで時々ある問題についての解決方法です。

 このタイトルにあるような現象は、例えばRaspberry PiなどでOpenCVを使ってWebカメラからの画像を取得するといった場合に起こることがあります。また1台のPCで大量にWebカメラの画像を取得する場合などにも発生しうるかと思います。

 解決方法と共に原因も合わせて説明していきます。


OpenCVWebカメラから取得したキャプチャが過去のものになっている時の対処方法
 ・原因1:電力不足
 対処方法は「電源付きUSBハブを使用する」というものです。

 例に出したRaspberry Pi本体のUSBコネクタからの給電でWebカメラを動かす場合、Raspberry Piからの電力供給でWebカメラが動くことになります。一般的にPCについているUSBケーブルは5V-0.5A程度の給電能力ですが、Raspberry Piの電源自体がせいぜい5V-3A程度なのでWebカメラにかなり電力を取られてしまってRaspberry Piの処理能力が低下し、結果OpenCVのキャプチャのバッファが溜まってしまうということから過去の画像取得になっているということのようです。

 ・原因2:バッファが溜まってしまっている
 対処方法は「バッファに溜まっているものを読み飛ばす」というものです。

import cv2

cap = cv2.VideoCapture(0)

while (True):
    ret, frame = cap.read()

    # 何か重い処理

    cv2.imshow("camera", frame)
    key = cv2.waitKey(1) & 0xff
    if key == 27:
        break

 上記のようなプログラムがあった場合、重い処理の部分で時間がかかってしまってVideoCaptureが保持しているバッファが溜まり続けてしまうことがあります。

 そのような場合は以下のようにバッファをまとめて読み飛ばす処理を追加します。

import cv2

cap = cv2.VideoCapture(0)

while (True):
    ret, frame = cap.read()

    # 何か重い処理

    cv2.imshow("camera", frame)


    ### 読み飛ばし処理を追加 ###
    for i in range (5) :
        img = cap.read()
    ### 読み飛ばし処理を追加ここまで ###

    key = cv2.waitKey(1) & 0xff
    if key == 27:
        break


 以上がWebカメラからのキャプチャが過去の画像になっている時の対処方法です。これで必ず解決するという訳ではありませんが、調べてもなかなか出てこない情報だったりするので、メモとして残しておいた次第です。


・参考文献

【Blender/python】OpenCVの瞳認識の値を使ってBlenderのオブジェクトの座標を変更する

rikoubou.hatenablog.com

 以前の記事でBlenderOpenCVを使えるようにする方法を書きました。

 今回はOpenCVで瞳の認識をして、その値を使ってBlenderのオブジェクトを動かすというのをやっていきます。また今回のプログラムを実行するにはWebカメラが必要になります。


1:瞳認識を行うxmlファイルの場所を確認する
 最初に瞳認識を行うためのxmlファイルの場所を確認します。

 OpenCVのインストール先にdataというフォルダがあります。そのフォルダの中に「haarcascade_lefteye_2splits.xml」があるのでそこまでのパスをコピーしておきます。
f:id:rikoubou:20181127174235p:plain


2:瞳認識のソースコード
 Blenderで動かす瞳を認識するプログラムは以下のようになります。

・blenderPythonTest.py

# ---openCVを使えるようにする---
import sys
sys.path.append("/hogehoge/cv2/") # OpenCVのパス。適宜書き換える

# ---以下プログラム部分---
import bpy         # BlenderのpythonAPI
import numpy as np # 配列関係のライブラリ
import cv2         # OpenCV のインポート

# 左目認識部分のxml読み込み
left_eye_cascade = cv2.CascadeClassifier('/hogehogehogehoge/cv2/data/haarcascade_lefteye_2splits.xml') # 瞳認識用のパス。適宜書き換える

# VideoCaptureのインスタンスを作成する。
# 引数でカメラを選択
cap = cv2.VideoCapture(0)

OBJ_NAME = "Cube"

# メイン関数
def main():
    obj = bpy.data.objects[OBJ_NAME] # オブジェクトを取得

    while True:
        # VideoCaptureから1フレーム読み込む
        ret, frame = cap.read() # このframeがimg

        # リサイズしてグレースケール化
        frame = cv2.resize(frame, (int(frame.shape[1]/2), int(frame.shape[0]/2))) # リサイズ
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # グレースケール

        # 左目を検出
        left = left_eye_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=2, minSize=(10,10))

        # 左目四角形で複数検出される
        for (x,y,w,h) in left:
            obj.location[0] = (x - (frame.shape[1]/2)) * 0.05
            # obj.location[1] = (y - (frame.shape[0]/2)) * 0.05
            bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) # Blender画面を更新

            cv2.rectangle(frame,(x,y),(x+w,y+h),(255,0,0),2) # 四角形を描写
            break # 1個分しか使わないのでbreakする

        cv2.imshow('frame', frame)

        # キー入力を1ms待って、keyが「q」だったらbreak
        key = cv2.waitKey(1)&0xff
        if key == ord('q'):
            break

    # 座標を0に戻す
    obj.location[0] = 0.0
    obj.location[1] = 0.0

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


if __name__ == '__main__':
    main()

 上記のプログラムを簡単に説明すると、Webカメラに映った瞳を四角形で検出してその四角の左上X座標の値を取得してBlenderのCubeオブジェクトのX座標に設定しているという内容です。Webカメラの画面上で「q」キーを押すと終了します。

 OpenCVまでのパスと読み込むxmlファイルのパスは適宜書き換えてください。


3:Blender上で動かす
 2のプログラムをBlender上で動かします。

 Blenderを起動させたら以下のようにプルダウンから「Scripting」を選択してスクリプト画面に切り替えます。
f:id:rikoubou:20181127175246p:plain

 以下のような画面に切り替わるので「New」ボタンを左クリックします。
f:id:rikoubou:20181127175432p:plain

 するとpythonスクリプトが記述できるようになるので、2で作成したプログラムをコピペします。
f:id:rikoubou:20181127175707p:plain

 コピペできたら「Run Script」ボタンを左クリックして実行します。
f:id:rikoubou:20181127181208p:plain

 実行すると以下のように目の部分が四角で覆われたWebカメラの映像が新しく立ち上がったウインドウに表示されます。新しく立ち上がったウインドウにフォーカスを合わせて「q」キーを押すとプログラムを終了できます。
f:id:rikoubou:20181127180402p:plain

 実際にWebカメラに瞳が映った状態で顔を左右に動かしてみると、以下のようにその動きに合わせてCubeオブジェクトも動きます。片目を隠した状態で顔を動かすとスムーズに動くようになります。
f:id:rikoubou:20181127180515g:plain


 以上がOpenCVの瞳認識の値を使ってBlenderのオブジェクト座標を変更してみた内容です。

 OpenCVBlenderで使えるようにしてからまだちゃんと連携させたことがなかったので、今回のようにうまく連携できることがわかってよかったです。
 もうちょっと色々わかればBlenderを使って擬似的なFaceRigも自作できそうなので挑戦してみたいです。


・参考資料

【Blender/python】pythonからBlenderの画面を更新する方法

 以前Blenderでアドオンを自作とかしていたのですが、その時に「アドオンを実行したあとBlenderの画面で何かアクションをしないと反映されない」という現象がありました。

 今回調べていたらBlenderの画面を更新する書き方がわかったので、その備忘録です。


Blenderの画面を更新する方法
 以下の記述でできます。

bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) # Blender画面更新

 何かスクリプトを実行した後に画面に反映させたい場合は、処理の最後に上記の一行を加えてやるとBlenderの画面に反映されます。


・参考資料