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

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

【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の画面に反映されます。


・参考資料

【アニメ感想】抱かれたい男1位に脅されています。 hug7「好きでいてもいいですか。」の演出について

www.nicovideo.jp

 今まで映画とかの感想は書いてきたのですが、今回は今現在放送中のアニメ「抱かれたい男1位に脅されています。」の7話の演出があまりに素晴らしかったので、それについての感想を書いていこうかと思います。本編はトップにリンクがあるのでそちらから確認してください。

 今回も今まで通り本編のキャプチャを交えつつ自分が感じたことをざっくばらんに書いていくので、ネタバレ前提で進めていきます。

 では始めます。


1:アバンの宇宙
 正直ここはよくわからない。今までの話数で出てきたカットか、もしくは次の話のどこかのカットに繋がるものだと思うのですが確認できていない状態。ただ何か劇的な変化を表現していそう。

2:見上げる鳥
f:id:rikoubou:20181120221505p:plain
 宇宙からのカットの後、過去の話に切り替わるのと同時に画面の色もセピア調に。運転するバイクから准太が見上げるのは1羽の鳥。まだ羽ばたくことのできていない自分自身か、もしくはすでに役者の高みにいる高人さんを表現していると思われる。そして准太はまだその鳥まで届いていない…。

3:信号演出
f:id:rikoubou:20181120222357p:plain
 続く信号のカット。セリフ通りの「順風満帆のオールグリーン」。一直線に並ぶだけの信号は画面としても理路整然としすぎていて、准太と同じくどこかつまらなさを感じるところがある。

4:高人さんとのファーストコンタクト
f:id:rikoubou:20181120222854p:plain
 准太が高人さんと初めて会うシーン。高人さんが画面上手に、准太が下手になっている。また二人の間を照明の足が分断するようにも配置されている。二人の間に分かり合えない壁が存在していることを意味している。以後も二人を分断するような道具の配置が何度も出てくる。
f:id:rikoubou:20181120223347p:plain
 また高人さんの背後に照明も配置されている。これは高人さんが文字通り光り輝くスターであることを視聴者に意識させる。
 上手にいる上に光まで背負っている、手の届かないほどの存在という強烈な印象を与えてくるシーンになっている。

5:三脚越しに視線を送る二人
f:id:rikoubou:20181120223726p:plainf:id:rikoubou:20181120223734p:plain
 三脚越しにお互いに視線を送っているという窮屈な画面。先ほども出た物理的な障壁という表現に加え、画面の窮屈さ、居心地の悪さも表現しているようにも思える。

6:キャラ同ポジのカット切り替え
f:id:rikoubou:20181120224158p:plainf:id:rikoubou:20181120224211p:plain
 痺れる! 最高にクール! まさに准太が高人さんに抱いているような感情を視聴者にもど直球で伝えてくる演出。文句なしにカッコいいと思える切り替え。

7:自販機前での二人の会話
f:id:rikoubou:20181120224641p:plain
 このコーヒーを吹き出すようなギャグっぽいシーンでも、高人さんが上手、准太が下手という関係は保たれている。
f:id:rikoubou:20181120224811p:plain
 そして詰め寄る場面でも、高人さんの背後には太陽からの光が注いでいる。

8:関係性の転換
f:id:rikoubou:20181120224959p:plain
 高人さんが准太の肩を叩くこの場面で初めて上手と下手が入れ替わる。話としてもこれをきっかけに准太に変化が起きるので、まさに転換となるシーン。
f:id:rikoubou:20181120225332p:plain
 この心象風景でも鳥が羽ばたいており、これからの変化を予感させる。

9:タブレットの意味
f:id:rikoubou:20181120225503p:plainf:id:rikoubou:20181120225516p:plain
 不安や戸惑いを紛らわすためにタブレットを乱暴に口へと放り込み、砕く。准太の中にある獣のような感情の一片も効果的に表現している。

10:ドラマの撮影
f:id:rikoubou:20181120231434p:plain
 准太に変化のきっかけはできたものの、ドラマにおいても下手のままでいる。まだ覚醒はしていない。

11:再度同じように視線を送る二人
f:id:rikoubou:20181120230224p:plainf:id:rikoubou:20181120230232p:plain
 5と同じような感じで視線を送る二人。二人の間にあるのは三脚ではなく、マイク?(照明?)になっている。二人の間の壁がより小さいものになっていることの表現だろう。

12:バスシーン
f:id:rikoubou:20181120230827p:plain
 hug3であったあのシーンと同じシチュエーション。視聴者はドキッとしてしまうが、同じシチュエーションでの二人の関係性の違いとむしろ准太が覚醒するきっかけを与えてくれる重要なシーンになっている。同じ場面を重要なところに持ってくるのが素晴らしい。

13:半覚醒後のドラマの撮影
f:id:rikoubou:20181120231544p:plain
 准太が上手に、高人さんが下手に配置されている。准太は先のバスのシーンから明らかに変わった存在として描かれている。
f:id:rikoubou:20181120231912p:plain
 抽象的なカットを挟みつつ濡れる事故もあって高人さんを襲おうとしてしまう准太。この抽象的な表現も心のごちゃごちゃを上手く表現できている。
f:id:rikoubou:20181120231934p:plain
 続くカットも准太の目は画面に入れず、影に注目させる不穏で恐ろしい印象を与える。准太の中にある獣が目覚めた瞬間を見事に描いている。

14:大量のタブレットとガラスに映る自分
f:id:rikoubou:20181120232702p:plain
 大量のタブレットを消費しており准太の心がかなり乱れていることが見て取れる。
f:id:rikoubou:20181120232831p:plain
 そしてガラスに映った自分を見つめる中で准太は自分の中にいる獣の存在を自覚する。

15:高人さん以外でも上手になる准太
f:id:rikoubou:20181120232951p:plain
 高人さん以外の相手にも、襲ってしまうかのような姿勢で体も大きく見せるようにして上手に位置している。獣を自覚し自分のものとした、まさに完全覚醒状態と言えるだろう。

16:覚醒後の二人の会話
f:id:rikoubou:20181120233245p:plain
 准太は役者として覚醒した。しかしまだ高人さんと准太の間にはスタンドの棒で仕切られた壁がある。
f:id:rikoubou:20181120233349p:plain
 高人さんに叩かれるシーン。ペットボトルから出た飛沫が准太の涙のようにも見える。准太は内心深いショックを受けていることが見て取れる表現。

17:エレベーター
f:id:rikoubou:20181120233512p:plain
 ここでも二人の間を邪魔するようにエレベーターの下へと降る矢印が壁として存在している。
f:id:rikoubou:20181120233654p:plain
 そして准太の「この人は俺との空間から早く出たいと思ってるんだろうな」のセリフの直後にピンポーンと正解の音をエレベーターが知らせる。

18:病院
f:id:rikoubou:20181120233756p:plain
 病院のベッドで寝ている高人さんにキスを迫ろうとする准太。しかし画面にはやはり二人の間を仕切るものが存在し、キスは未遂に終わってしまう。

19:タブレット消失からの羽ばたき
f:id:rikoubou:20181120233915p:plainf:id:rikoubou:20181120233929p:plain
 自分の気持ちに気がつき迷いのなくなった准太の手からタブレットは不要になっていた。そして鳥が羽ばたいて飛び上がるのと同じくして准太自身もより高みを目指すことを決意する。准太が羽を得た瞬間を描いたエモいシーン。

20:決意の別れ
f:id:rikoubou:20181120234213p:plain
 ドラマの撮影も終わりに近づいた時、准太は高人さんの前で手を伸ばそうとするがやめる。照明が今度は准太に当たるようになっていることと、のちに高人さんを見下ろす立場(抱かれたい男1位を取る)になることを暗示しているシーン。

21:色づきもう一度出会う
f:id:rikoubou:20181120234528p:plainf:id:rikoubou:20181120234538p:plainf:id:rikoubou:20181120234551p:plain
 セピア調だった画面が一度白くなり、そしてはっきりとした色へと変化する。退屈だと思っていた准太の日々が生まれ変わり、そしてまた高人さんに出会い色づいていくという演出。まさにやられたという感じで最高の盛り上がりを見せた後でラストのセリフが「高人さん」で締めるという大感動で終わる。

 ほぼ全部の流れを書いてしまった感じはありますが、本当に丁寧に演出されていて准太の気持ちと高人さんとの関係性がビシビシと伝わって来る作りでした。この記事執筆時点で2回視聴しているのですが見るたびに新しい発見がある素晴らしい回でした。お話としても素晴らしいです。

 以上が抱かれたい男1位に脅されています。のhug7を見て思ったことです。

 この作品のタイトルにあるように女性向けの作品で男性同士の恋愛をテーマにしていますが、その内容は純粋な人間関係の恋愛を描いています。現在放送中の「やがて君になる。」では女性同士の恋愛を描いているため、対比して鑑賞するとまた違いが楽しめるかと思います。個人的には君になるまでを描くのが「やがて君になる。」で、君になったあとを描いたのが「抱かれたい男1位に脅されています。」だと思い、両方楽しんでいます。

 やがて君になるの方も演出が凝っていて「どのシーンにも演出的意図があるのではないか?」と思いながら見れる素晴らしい作品です。

 未見の方は食わず嫌いをせずに見るのをオススメします!

 抱かれたい男1位に脅されています。はいいぞ!!

【python/OpenCV】画像を手書きっぽく加工するやつを作ってみる

 最近SNSなどでとある手書き風のアプリがリリースされちょっと話題になっています。

 iPhoneを持っていないのでどのようなアプリかはわかっていないのですが、結果の動画や画像を見る限りではOpenCVで再現できそうでした。そこでタイトルにあるように似たようなものを作れないかと思い、自分で色々調べて作ってみたのでその備忘録です。

 ちなみに自作したものは以下のような感じになります。

・加工前
f:id:rikoubou:20181115184750j:plain

・加工後
f:id:rikoubou:20181115184805p:plain

 完全に同じにはなりませんが、割と手書きな感じにはなっているかと思います。

 今回の画像加工の手順としては大体「グレースケール化→輪郭線抽出→影部分の抽出→輪郭線と影部分の合成」という順番でやっています。

 ではそれぞれについて説明していきます。読むのが面倒な場合は最後のソースコードのところまで飛ばしてください。


1:グレースケール化
 輪郭線を抽出する前段階として画像をグレースケール化します。輪郭線を抽出する前段階として一般的なものです。
 グレースケール化するには以下の「cv2.cvtColor」関数でできます。

import cv2

image = cv2.imread('hogehoge.jpg') # 画像取得
grayImage = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # グレースケール


2:輪郭線抽出
 グレースケール化したものから輪郭線を抽出するのは「cv2.Canny」関数でできます。

outlineImage = 255 - cv2.Canny(grayImage, 110, 70)

 cv2.Cannyの第1引数がグレースケール化した画像です。第2引数と第3引数を変化させることで取得する線の調節ができます。
「255」という値から結果を引いているのは、cv2.Cannyの結果として線の部分が白、他が黒になっているためです。255から結果を引くことで色を反転させています。


3:影部分の抽出
 影部分の抽出は以下の手順でやっています。

grayImage = cv2.GaussianBlur(grayImage, (31, 31), 30) # ぼかす
ret, shadowImage = cv2.threshold(grayImage, 40, 220, cv2.THRESH_BINARY) # 閾値で2値化

 GaussianBlur関数でぼかし、そのあと2値化しています。これで影の部分が取得できます。


4:輪郭線と影部分の合成
 2つの画像ができたのであとは合成します。合成には「cv2.bitwise_and」関数を使います。

resultImg = cv2.bitwise_and(outlineImage, shadowImage)


5:ソースコード
 基本的には1〜4の手順で行い、適宜コントラストを減らしたりぼかしを加えたりなどして調節しています。
 今回作成したソースコードは以下の通りです。

 ・2018/11/19追記:画像サイズが奇数ピクセルだとエラーになっていたのでソースコードを修正しました。

・cv2Outline.py

import numpy as np
import cv2
import glob
from datetime import datetime
from time import sleep

RESIZE_SIZE = 4 # 画像を処理する時に使うサイズ(1/n)
CAMERA_SIZE = 2 # カメラ画像の縮小サイズ(1/n)

# カメラフラグでTrueにするとWebカメラの変換になる
CAMERA_FLG = False

IMG_FOLDER_PATH = "./img/*"     # 画像フォルダ
SAVE_FOLDER_PATH = "./result/"  # 出力保存フォルダ


# メイン関数
def main():
    print("--- start ---")

    # カメラの場合との場合分け
    if (CAMERA_FLG):
        # VideoCaptureのインスタンスを作成(引数でカメラを選択できる)
        cap = cv2.VideoCapture(0)
        changeCameraImage(cap)
        closeWindows(cap) # ウインドウを全て閉じる
    else:
        changeLoadImages(IMG_FOLDER_PATH, SAVE_FOLDER_PATH)

    print("--- end ---")


# カメラの映像を変換する関数
def changeCameraImage(cap):
    while True:
        ret, frame = cap.read() # 戻り値のframeがimg
        resultImg = changeImage(frame) # 画像変換

        # 結果をリサイズ
        fx = int(resultImg.shape[1]/CAMERA_SIZE)
        fy = int(resultImg.shape[0]/CAMERA_SIZE)
        resultImg = cv2.resize(resultImg, (fx, fy))

        # 文字を追加
        text = 'Exit is [Q] key'
        cv2.putText(resultImg, text, (0,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255,0), 3, cv2.LINE_AA)

        # 加工した画像を表示
        cv2.imshow('resultImg', resultImg)

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


# 画像を読み込んで変換する関数
def changeLoadImages(imgFolderPath, saveFolderPath):
    images = glob.glob(imgFolderPath)

    for fname in images:
        frame = cv2.imread(fname) # 画像取得
        resultImg = changeImage(frame) # 画像変換

        # 画像保存
        saveImgByTime(saveFolderPath, resultImg)
        sleep(1)


# 画像を変換する関数
def changeImage(colorImg):
    gray = cv2.cvtColor(colorImg, cv2.COLOR_BGR2GRAY) # グレースケール
    gray = cv2.GaussianBlur(gray, (15, 15), 15) # ぼかす

    # 輪郭線処理
    img_diff = outine(gray) # 輪郭線抽出
    img_diff = cv2.GaussianBlur(img_diff, (11, 11), 8) # ぼかす
    ret, img_diff = cv2.threshold(img_diff, 170, 240, cv2.THRESH_BINARY) # 閾値で2値化

    # 影部分の処理
    gray = cv2.GaussianBlur(gray, (31, 31), 30) # ぼかす
    ret, gray = cv2.threshold(gray, 40, 220, cv2.THRESH_BINARY) # 閾値で2値化
    gray = lowContrast(gray) # コントラストを落とす
    
    # 輪郭線と影部分の画像を合成
    resultImg = cv2.bitwise_and(img_diff, gray)

    return resultImg


# 画像の輪郭線を抽出する関数
def outine(grayImg):
    # リサイズ
    fx = int(grayImg.shape[1]/RESIZE_SIZE)
    fy = int(grayImg.shape[0]/RESIZE_SIZE)

    grayChangeImg = cv2.resize(grayImg, (fx, fy))
    # 輪郭線抽出
    result = 255 - cv2.Canny(grayChangeImg, 110, 70)
    # リサイズして元に戻す
    result = cv2.resize(result, (grayImg.shape[1], grayImg.shape[0]))
    return result


# コントラストを落とす関数
def lowContrast(img):
    # ルックアップテーブルの生成
    min_table = 50
    max_table = 230
    diff_table = max_table - min_table
    look_up_table = np.arange(256, dtype = 'uint8' )
 
    for i in range(0, 255):
        look_up_table[i] = min_table + i * (diff_table) / 255
 
    # コントラストを低減
    result = cv2.LUT(img, look_up_table)
    return result


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


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


if __name__ == '__main__':
    main()

 少し説明すると「cv2Outline.py」として保存したファイルと同じ場所に「img」、「result」のフォルダをそれぞれ作成します。そして「img」フォルダに加工前の画像を入れて「python3 cv2Outline.py」を実行すると「result」フォルダに結果が保存されます。

 また以下の部分の「False」を「True」に書き換えて実行するとWebカメラの表示に切り替わります。

# カメラフラグでTrueにするとWebカメラの変換になる
CAMERA_FLG = False

 2018/11/19追記:また画像が小さいといい感じに輪郭線の抽出ができていないようだったため、画像サイズに合わせてある程度計算で適切に処理するようなバージョンも作りました。

・alt_cv2Outline.py

import numpy as np
import cv2
import glob
from datetime import datetime
from time import sleep

CAMERA_SIZE = 2 # カメラ画像の縮小サイズ(1/n)

BLUR_VALUE = 25000 # ブラーをかけるための定数
PIXEL_VALUE = 300000 # 拡大/縮小の基準となるピクセル

# カメラフラグでTrueにするとWebカメラの変換になる
CAMERA_FLG = False

IMG_FOLDER_PATH = "./img/*"     # 画像フォルダ
SAVE_FOLDER_PATH = "./result/"  # 出力保存フォルダ

# メイン関数
def main():
    print("--- start ---")

    # カメラの場合との場合分け
    if (CAMERA_FLG):
        # VideoCaptureのインスタンスを作成(引数でカメラを選択できる)
        cap = cv2.VideoCapture(0)
        changeCameraImage(cap)
        closeWindows(cap) # ウインドウを全て閉じる
    else:
        changeLoadImages(IMG_FOLDER_PATH, SAVE_FOLDER_PATH)

    print("--- end ---")


# カメラの映像を変換する関数
def changeCameraImage(cap):
    while True:
        ret, frame = cap.read() # 戻り値のframeがimg
        resultImg = changeImage(frame) # 画像変換

        # 結果をリサイズ
        fx = int(resultImg.shape[1]/CAMERA_SIZE)
        fy = int(resultImg.shape[0]/CAMERA_SIZE)
        resultImg = cv2.resize(resultImg, (fx, fy))

        # 文字を追加
        text = 'Exit is [Q] key'
        cv2.putText(resultImg, text, (0,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255,0), 3, cv2.LINE_AA)

        # 加工した画像を表示
        cv2.imshow('resultImg', resultImg)

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


# 画像を読み込んで変換する関数
def changeLoadImages(imgFolderPath, saveFolderPath):
    images = glob.glob(imgFolderPath)

    for fname in images:
        frame = cv2.imread(fname) # 画像取得
        resultImg = changeImage(frame) # 画像変換

        # 画像保存
        saveImgByTime(saveFolderPath, resultImg)
        sleep(1)


# 画像を変換する関数
def changeImage(colorImg):
    gray = cv2.cvtColor(colorImg, cv2.COLOR_BGR2GRAY) # グレースケール

    # ピクセル数からぼかす値を計算
    allPixel = colorImg.shape[1] * colorImg.shape[0]
    bokashi = calcBlurValue(allPixel)
    gray = cv2.GaussianBlur(gray, (bokashi, bokashi), bokashi) # ぼかす

    # 輪郭線処理
    img_diff = outine(gray, allPixel) # 輪郭線抽出

    # 輪郭線用のぼかし計算
    bokashiOutline = bokashi
    if bokashi > 4:
        bokashiOutline = bokashi - 4

    img_diff = cv2.GaussianBlur(img_diff, (bokashiOutline, bokashiOutline), bokashiOutline) # ぼかす
    ret, img_diff = cv2.threshold(img_diff, 170, 240, cv2.THRESH_BINARY) # 閾値で2値化

    # 影部分の処理
    gray = cv2.GaussianBlur(gray, (bokashi, bokashi), bokashi) # ぼかす
    ret, gray = cv2.threshold(gray, 40, 220, cv2.THRESH_BINARY) # 閾値で2値化
    gray = lowContrast(gray) # コントラストを落とす
    
    # 輪郭線と影部分の画像を合成
    resultImg = cv2.bitwise_and(img_diff, gray)

    return resultImg


# ブラーをかける値を計算する関数
def calcBlurValue(allPixel):
    result = int(np.sqrt(allPixel/BLUR_VALUE))
    if (result%2 == 0):
        result = result + 1
    return result


# 画像の輪郭線を抽出する関数
def outine(grayImg, allPixel):
    # リサイズ
    z =  np.sqrt(PIXEL_VALUE / (grayImg.shape[1] * grayImg.shape[0]))
    if (z > 1):
        z = 1

    fx = int(grayImg.shape[1] * z)
    fy = int(grayImg.shape[0] * z)

    grayChangeImg = cv2.resize(grayImg, (fx, fy))
    # 輪郭線抽出
    result = 255 - cv2.Canny(grayChangeImg, 100, 50)
    # リサイズして元に戻す
    result = cv2.resize(result, (grayImg.shape[1], grayImg.shape[0]))
    return result


# コントラストを落とす関数
def lowContrast(img):
    # ルックアップテーブルの生成
    min_table = 50
    max_table = 230
    diff_table = max_table - min_table
    look_up_table = np.arange(256, dtype = 'uint8' )
 
    for i in range(0, 255):
        look_up_table[i] = min_table + i * (diff_table) / 255
 
    # コントラストを低減
    result = cv2.LUT(img, look_up_table)
    return result


# 画像を時刻で保存する関数
def saveImgByTime(dirPath, img):
    # 時刻を取得
    date = datetime.now().strftime("%Y%m%d_%H%M%S")
    path = dirPath + date + ".png"
    cv2.imwrite(path, img) # ファイル保存
    print("saved: " + date + ".png")


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


if __name__ == '__main__':
    main()

 画像の大きさに合わせてそれぞれのソースコードを使い分ければいいかなと思っています。(どうしても画像のサイズや単純なものが写っているかどうかに影響してくるので…)


 以上が今回作ってみたものです。OpenCVpythonがあれば手軽に色々な加工ができて面白いです。

 また今回作ったソースコードも一応公開しておきます。


・参考資料

【python/OpenCV】OpenCVでWebカメラの画像を取得する

 OpenCVを入れたのでちょっとWebカメラで遊んでみるというタイトル通りの内容です。

 基本的には参考にした内容ほぼ丸パクリです。


1:OpenCVWebカメラの画像を取得する
 以下の記述でできます。

import cv2 # OpenCV のインポート

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

# VideoCaptureから1フレーム読み込む
ret, frame = cap.read() # 戻り値のframeが画像

「cap = cv2.VideoCapture(0)」で接続しているWebカメラインスタンスを作成しています。ノートPCなどに内臓されているカメラの場合は、引数に「0」を指定してあげれば良いです。
「ret, frame = cap.read()」でカメラの画像を1フレーム読み込みます。この戻り値のframeが読み込んだ画像になります。


2:サンプルコード
 1の内容でWebカメラの画像が取得できました。これを応用してカメラの画像に現在時刻を表示させるサンプルを作りました。

 そのサンプルが以下になります。

from datetime import datetime # 時刻関係のライブラリ
import cv2 # OpenCV のインポート

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

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

    # 1/4にリサイズ
    frame = cv2.resize(frame, (int(frame.shape[1]/4), int(frame.shape[0]/4)))
    # 加工なし画像を表示する
    # cv2.imshow('Raw Frame', frame)

    # 現在時刻の文字列を画像に追加
    date = datetime.now().strftime("%H:%M.%S")
    edframe = frame
    cv2.putText(edframe, date, (0,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255,0), 3, cv2.LINE_AA)

    # 加工した画像を表示
    cv2.imshow('Edited Frame', edframe)

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

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

 このサンプルを実行すると、Webカメラの元画像を1/4した大きさのものに現在時刻が左上に表示されます。
 終了させる時は「q」キーを押すと終了します。


 以上がWebカメラの画像を取得する方法です。割と簡単に実装できたのでOpenCVを使ってもっと色々なことをやっていきたいです。


・参考資料