Electron + Python で選んだ顔にモザイクをかけるアプリ(ダウンロード可)

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、このところElectronのプログラムが面白くなってきて個人的にもいろいろと開発する意欲が湧いてきています。

そして、そんな中で自然と試したいことが頭に浮かんできました。
それは・・・・・・

他の言語とのコラボレーション

です。

つまり、ElectronNodeJS(JavaScript)を使っていますが、これ単体だけではなく、例えば、Pythonやコマンドラインなども実行してみたくなったんですね。

ということで、今回は以前試してみた(といっても移転前のブログですが)Python + dlibの顔認証コードを使ってモザイク処理をするElectronアプリをつくってみます。

ぜひ学習の参考にしてみてくださいね。

※ 開発環境: Electron 2.0、Python 2.7

やりたいこと

せっかくElectronを使うので、単純に検出された全ての顔にモザイクをかけるというアプリではなく、次のようにモザイクをかける顔画像を選択できるようにします。

  1. 画像を選択する
  2. 画像に含まれている顔をすべて抽出
  3. モザイクをかけたい顔をチェック
  4. 実行(選んだ顔だけモザイク処理をする)

では、実際に開発を進めていきましょう!

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.jsaxios、そして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 shwhich 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>

まずは顔データfacesv-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には改行コードが含まれているのでこれを除去しているところです。

テストしてみる

では、同じく次の画像を使って最終テストをしてみましょう!

まず顔が入った画像を選択するとモザイク処理できる顔が検出されます。

では、お子さんだけ残して後は全員モザイク処理をしてみましょう。

“モザイク処理をする” ボタンをクリックします。

結果はどうなったでしょうか??

こうなりました。
成功です!

では、次に真ん中のお母さんだけ残してモザイク処理をしてみましょう。

こちらもうまくいきました!
このアプリを使えば簡単にモザイク処理できますね。

教材ソースコードをダウンロードする

今回実際に開発したソースコード一式を以下からダウンロードすることができます。

※ ただし、npmpipのインストールはご自身で行ってください。

Electron + Python で顔にモザイク

 

このエントリーをはてなブックマークに追加       follow us in feedly  
開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ