九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、このところElectron
のプログラムが面白くなってきて個人的にもいろいろと開発する意欲が湧いてきています。
そして、そんな中で自然と試したいことが頭に浮かんできました。
それは・・・・・・
他の言語とのコラボレーション
です。
つまり、Electron
はNodeJS
(JavaScript)を使っていますが、これ単体だけではなく、例えば、Python
やコマンドラインなども実行してみたくなったんですね。
ということで、今回は以前試してみた(といっても移転前のブログですが)Python
+ dlib
の顔認証コードを使ってモザイク処理をするElectron
アプリをつくってみます。
ぜひ学習の参考にしてみてくださいね。
※ 開発環境: Electron 2.0、Python 2.7
目次
やりたいこと
せっかくElectron
を使うので、単純に検出された全ての顔にモザイクをかけるというアプリではなく、次のようにモザイクをかける顔画像を選択できるようにします。
- 画像を選択する
- 画像に含まれている顔をすべて抽出
- モザイクをかけたい顔をチェック
- 実行(選んだ顔だけモザイク処理をする)
では、実際に開発を進めていきましょう!
Electronのインストール
インストールは、初心者向き!electron で簡単なメモ帳をつくってみようの “electronのインストール” に詳しく書いてあるので、そちらを見てください。
フォルダ名はpixelate_face
です。
Pythonコードをつくる
Pythonコードで必要になるファイルは次の2つです。
- detect_face.py ・・・ 顔を検出する
- pixelate_face.py ・・・ 顔にモザイク処理する
顔検出に必要な環境を整える
まず必要となるパッケージをpip
でインストールします。
pip install opencv-python pip install dlib
※ もしdlib
のインストールでエラーがでるようなら、コード必要なし!2Dの顔画像を3D化するPythonコード(PRNet) の「実行に必要なパッケージをインストール > dlib」を参考にしてインストールしてください。
顔検出コードをつくる
では、まずは画像から顔を検出し、それがどこにあるかを取得するコードをつくりましょう。
想定しているコマンドラインは次のようなものです。
python detect_face.py -i "(画像のパス)"
では、実際のコードです。
#!/usr/bin/python import cv2 import dlib import argparse import json parser = argparse.ArgumentParser() parser.add_argument('-i', help='required', type=str) args = parser.parse_args() path = args.i detector = dlib.get_frontal_face_detector() im = cv2.imread(path, cv2.IMREAD_COLOR) if im is None: exit() rects = detector(im, 1) if len(rects) == 0: exit() faces = [] for rect in rects: faces.append({ 'top': int(rect.top()), 'left': int(rect.left()), 'width': int(rect.width()), 'height': int(rect.height()) }) print(json.dumps(faces))
まず画像を読み込み、顔データをdetector()
で取得します。
あとは、取得された顔データから必要な部分を取り出し、最後のJSONにして表示しているだけです。
モザイク処理をするコードをつくる
次にモザイク処理をするコードです。
モザイク処理の仕方は、opencv の基本的な画像変形: 全12実例!で紹介しているものを使います。
#!/usr/bin/python import cv2 import json import os import argparse PIXEL_RATIO = 0.01 parser = argparse.ArgumentParser() parser.add_argument('-i', help='required', type=str) parser.add_argument('-p', help='required', type=str) args = parser.parse_args() path = args.i faces = json.loads(args.p) im = cv2.imread(path, cv2.IMREAD_COLOR) rows,cols = im.shape[:2] for face in faces: x = face['left'] y = face['top'] width = face['width'] height = face['height'] block_im = im[y:y+height, x:x+width] pixel_x = int(cols*PIXEL_RATIO) pixel_y = int(rows*PIXEL_RATIO) small_im = cv2.resize(block_im, (pixel_x, pixel_y), interpolation=cv2.INTER_NEAREST) mosaic_im = cv2.resize(small_im, (width, height), interpolation=cv2.INTER_NEAREST) im[y:y+height, x:x+width] = mosaic_im save_path = os.path.dirname(path) +'/pixelated-'+ os.path.basename(path) cv2.imwrite(save_path, im) print('pixelated')
まず始めのPIXEL_RATIO
というのは、モザイク処理の度合いを設定する定数で、小さい数字ほどモザイクがきつくなります。
そして、顔のパラメータはElectron
側からJSONデータを取得し、それらをループさせモザイク処理を実行していきます。
なお、モザイク処理された画像は同じフォルダ内に保存されるようになっています。
Electronコードをつくる
アプリの設定をする
今回はウィンドウのサイズと位置、そしてメニューバーを非表示にします。
// main.js mainWindow = new BrowserWindow({ width: 400, height: 400, x: 0, y: 0, autoHideMenuBar: true, webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: true } })
【追記:2020.3.19】Electron v5
以降のためにwebPreferences
を追加しました。
必要なパッケージをインストールする
今回はVue.js
とaxios
、そしてbootstrap
をインストールして使います。
npm i vue --save npm i axios --save npm i bootstrap --save
レイアウトをつくる
では実際にアプリに表示されるレイアウトをつくりましょう。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>モザイク処理アプリ!</title> <link rel="stylesheet" href="./node_modules/bootstrap/dist/css/bootstrap.min.css"> </head> <body> <div id="app" class="container"> <label style="margin-top:15px;"> <span class="btn btn-light"> 画像を選択 <input type="file" style="display:none" accept="image/*"> </span> </label> <hr> <span class="badge badge-pill badge-dark">抽出された顔</span> <div>検出されませんでした。</div> <button type="button" class="btn btn-danger btn-sm" style="position:absolute;right:15px;bottom:15px;">モザイク処理をする</button> </div> <script> require('./renderer.js') </script> <script src="./node_modules/vue/dist/vue.min.js"></script> <script src="./node_modules/axios/dist/axios.min.js"></script> <script> new Vue({ el: '#app', methods: { onImageChanged(e) { const files = e.target.files } } }) </script> </body> </html>
実行したものがこちら。
※ もし選択された画像に顔が含まれていたら、「検出されませんでした」の部分にその顔を表示させて選択できるようにします。
JavaScriptコードをつくる
画像選択部分
では、まずは “画像を選択” ボタンをクリックした時にonImageChanged()
が実行されるようにします。
<span class="btn btn-light"> 画像を選択 <input type="file" style="display:none" accept="image/*" @change="onImageChanged($event)"> </span>
onImageChanged()
本体はこうなります。
const {spawn} = require('child_process');
onImageChanged(e) { this.imagePath = '' const files = e.target.files if(files.length > 0) { this.faces = [] const file = files[0] const path = file.path const target = __dirname +'/python/detect_face.py' const command = '/usr/bin/python "'+ target +'" -i "'+ path +'"' const process = spawn('/bin/sh', ['-c', command]) process.stdout.on('data', (data) => { if(data != '') { this.faces = JSON.parse(data) } }); this.imagePath = path } }
ここで重要なのが、spawn()
の部分です。
ここでコマンドを実行しているのですが、/bin/sh
や/usr/bin/python
は環境に寄って変わってくる可能性があります。
その場合は、コマンドラインでwhich sh
やwhich python
としてチェックするといいでしょう。(もしくはアプリ起動時にこのコマンドを実行して自動取得してもいいかもしれません。ただ、今回は省略します)
また、imagePath
は選択された画像へのパスになります。
検出された顔を表示する部分
では、先ほどのコードで検出された顔データが入る変数faces
、選択された画像パスが入ってくるimagePath
を登録しておきましょう。
data: { faces: [], imagePath: '' },
次にcomputed
に検出された顔データが存在しているかどうかを判別するhasFace
という疑似変数も登録してこれを使ってレイアウトの切り替えを行います。
computed: { hasFace() { return (this.faces.length > 0) } }
<div v-if="!hasFace">検出されませんでした。</div> <div v-else>モザイク処理をする顔を選んでください。</div>
<button type="button" class="btn btn-danger btn-sm" style="position:absolute;right:15px;bottom:15px;" :disabled="!hasFace">モザイク処理をする</button>
これで、顔データが検出された時とそうでない時でレイアウトが自動で変更になります。
(検出されてない時)
(検出された時)
では、続いて取得された顔データの一覧を表示し、選択できるようにしましょう。
<div class="float-left" style="margin:5px;" v-for="(face,index) in faces"> <label class="text-center"> <input type="checkbox"> <div :style="faceStyle(face)"></div> <label> </div>
まずは顔データfaces
をv-for
でループさせ1つずつ取得します。
そして、選択用のcheckbox
と顔画像を表示する<div>
タグを用意し、背景に選択された画像を表示し、位置をずらすことで顔だけを表示しています。
faceStyle(face) { return { width: face.width +'px', height: face.height +'px', border: '1px solid #ccc', backgroundImage: 'url('+ this.imagePath +')', backgroundPositionX: '-'+ face.left +'px', backgroundPositionY: '-'+ face.top +'px' } }
では、この状態で実際に顔データを取得して表示してみましょう。
利用するのはpixabay
でダウンロードしてきた、この(幸せを絵に描いたような ^^)家族の画像です。
結果はこうなりました。
5人全ての顔を検出し表示させることができました。
画像を選択する部分
では、次に表示された顔の中からモザイク処理をする顔を選択する部分です。
まず、data
の中にcheckedIndexes
というチェックされた画像のインデックス番号を格納する変数を用意します。
data: { // 省略 checkedIndexes: [] },
そして、この変数をチェックボックスにバインディングします。
<input type="checkbox" :value="index" v-model="checkedIndexes">
これで、チェックされた画像のインデックス番号がリアルタイムでcheckedIndexes
に反映されるようになりました。
モザイク処理をする
では、最後に “モザイク処理をする” ボタンがクリックされた時のコードをつくっていきます。
まずはボタンがクリックされたときにpixelate()
を実行するようにします。
<button type="button" class="btn btn-danger btn-sm" style="position:absolute;right:15px;bottom:15px;" :disabled="!hasFace" @click="pixelate()">モザイク処理をする</button>
中身はこうなります。
pixelate() { const target = __dirname +'/python/pixelate_face.py' const command = '/usr/bin/python "'+ target +'"' +' -i "'+ this.imagePath +'"' +" -p '"+ JSON.stringify(this.checkedFaces) +"'" const process = spawn('/bin/sh', ['-c', command]) process.stdout.on('data', (data) => { let message = ''; let dataString = data.toString().replace("\n", '') if(dataString == 'pixelated') { message = 'モザイク処理が完了しました!' } else { message = '予期せぬエラーにより失敗しました・・・' } this.checkedIndexes = [] alert(message); }); }
基本的にpythonコマンドを実行しているだけですが、さっきと違うのはパラメータp
をJSONで指定しているところ、そして処理が完了した時に取得するdata
には改行コードが含まれているのでこれを除去しているところです。
テストしてみる
では、同じく次の画像を使って最終テストをしてみましょう!
まず顔が入った画像を選択するとモザイク処理できる顔が検出されます。
では、お子さんだけ残して後は全員モザイク処理をしてみましょう。
“モザイク処理をする” ボタンをクリックします。
結果はどうなったでしょうか??
こうなりました。
成功です!
では、次に真ん中のお母さんだけ残してモザイク処理をしてみましょう。
こちらもうまくいきました!
このアプリを使えば簡単にモザイク処理できますね。
教材ソースコードをダウンロードする
今回実際に開発したソースコード一式を以下からダウンロードすることができます。
※ ただし、npm
やpip
のインストールはご自身で行ってください。