【Python】顔画像をPOST 、 顔検出 、 canvasで顔にお絵かき【JS】

### [※こちらに移行しました。 ← 2018/11/24](https://tech-blog.s-yoshiki.com/entry/19/)

# 概要
「顔検出をして、顔に絵を描く」というもの。

具体的には、
顔写真の画像をAPIサーバへアップロードし、顔検出を行う。
サーバは顔座標を返し、それを元にcanvasでお絵かきする。

### 参考
[【Python】顔画像をPOST 、 顔検出 、 canvasで顔にお絵かき【JS】](https://tech-blog.s-yoshiki.com/entry/19)
[【Python】Apache + WSGI + Flask でWebアプリケーション構築](https://tech-blog.s-yoshiki.com/entry/9)

# 環境
### フロント
html + JS + canvas
axios.jsでAPIを叩く

### サーバサイド
Ubutnu 16.04
Python + Flask
OpenCV 3.2
など

# アプリケーション 
![face_detection](https://qiita-image-store.s3.amazonaws.com/0/82419/a99e665d-5cc3-f2cc-482b-bbe1c99d7c2c.png)
こんなイメージ。
とりあえず、顔に枠線を書いている。
画像の描画、顔へのお絵かきは全てJSで実装。
サーバ側は顔検出するだけ。

下記にソース。

# ディレクトリ構成

```sh
./
├── face_detect.py # 顔検出の処理
├── index.py       # リクエスト処理
├── templates      # html(テンプレート)
│   └── index.html 
└── tmp            # アップロードされた画像格納ディレクトリ
    └── sample.jpg
```

# フロント
ファイルが選択されたら、canvasに画像を描画。
ボタン押下時にPOST。
POST処理はJSで。

#### html

```index.html

<html>
    <head>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.js"></script>
    </head>
    <body>
        <div class="container">
            <canvas id="canvas"></canvas>
            <hr>
            <label>
                画像
                <input type="file" class="form-control" id="image">
            </label>
            <button id="run">
                submit
            </button>
        </div>
        <script src="index.js">
        </script>
    </body>
</html>
```

#### js
requestを投げるのにaxiosを使ってみた。

```index.js

(function(){

    document.getElementById("run").addEventListener("click", function(){
        var params = new FormData();
        var file = document.getElementById("image").files[0]

        params.append('image', file)
        axios.post('/api', params).then(function(response) {
            response.data.forEach((e) => {
                var v = e.face
                drawRect(v.x, v.y, v.w, v.h)
                e.eyes.forEach((eye) => {
                    eye.x += v.x
                    eye.y += v.y
                    drawRect(eye.x, eye.y, eye.w, eye.h)
                })
            })
        }).catch(function(error) {
            alert("error")
        });
    })

    document.getElementById("image").addEventListener("change", function(e) {
        var file = e.target.files;
        var reader = new FileReader();
        reader.readAsDataURL(file[0]);
        reader.onload = function() {
            var source = reader.result;
            drawImage(reader.result)
        }
    }, false);

    function drawImage(src) {
        var canvas = document.getElementById("canvas")
        var context = canvas.getContext('2d')
        var image = new Image()
        image.src = src;
        image.onload = function() {
            canvas.width = image.width
            canvas.height = image.height
            context.drawImage(image, 0, 0)
        }
    }

    function drawRect(x, y, w, h) {
        var canvas = document.getElementById("canvas")
        var context = canvas.getContext('2d')
        context.rect(x, y, w, h);
        context.stroke();
    }
})()
```

##### メモ
drawRectが顔画像の描画処理を行なっている。


# サーバ
サーバサイドはPython + Flaskで実装。
顔画像処理はOpenCV、numpyなどで実装。

<a href="https://docs.opencv.org/3.4/d7/d8b/tutorial_py_face_detection.html">顔検出のサンプルソース - OpenCV</a>
#### index.py

```index.py
import os,sys
from flask import Flask, render_template, request, jsonify
from jinja2 import FileSystemLoader
import base64

from face_detect import get_facepos

app = Flask(__name__)

app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 * 1024
app.config['UPLOAD_FOLDER'] = './tmp'

@app.route("/", methods=['GET'])
def get_index():
    return render_template('index.html')

@app.route("/api", methods=['POST'])
def face_detect():
    img = request.files['image']
    name = img.filename
    path = os.path.join(app.config['UPLOAD_FOLDER'], name)
    img.save(path)
    face_pos = get_facepos(path)
    return jsonify(face_pos)
```

#### face_detect.py

```face_detect.py
import numpy as np
import cv2

def get_facepos(img_path):
    face_cascade = cv2.CascadeClassifier('/path/to/haarcascade_frontalface_default.xml')
    eye_cascade = cv2.CascadeClassifier('/path/to/haarcascades/haarcascade_eye_tree_eyeglasses.xml')

    img = cv2.imread(img_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    result = []
    for (x,y,w,h) in faces:
        face_dst = {
            "x": np.asscalar(x),
            "y": np.asscalar(y),
            "w": np.asscalar(w),
            "h": np.asscalar(h)
        }
        eyes_dst = []
        roi_gray = gray[y:y+h, x:x+w]
        eyes = eye_cascade.detectMultiScale(roi_gray)
        for (ex,ey,ew,eh) in eyes:
            eye_pos = {
                "x": np.asscalar(ex),
                "y": np.asscalar(ey),
                "w": np.asscalar(ew),
                "h": np.asscalar(eh)
            }
            eyes_dst.append(eye_pos)
        result.append({
            "face": face_dst,
            "eyes": eyes_dst
        })
    return result
```
##### メモ1

カスケードファイルであるhaarcascade_frontalface_default.xmlなどは、
フルパスではなくファイル名だけで読み込めるはずですが、
特殊な環境など、読めない環境では、パスを記載する。

##### メモ2

face_detect.pyで宣言している顔検出ご座標`(x,y,w,h)`をそのままListに格納し、
JSONに変換して出力しようとすると、エラーが発生する。(int32がなんたら...って言ってた)
とりあえず、`np.asscalar()`を使って回避することができた。






# 参考
[【Python】顔画像をPOST 、 顔検出 、 canvasで顔にお絵かき【JS】](https://tech-blog.s-yoshiki.com/2018/07/210/)
[【Python】Apache + WSGI + Flask でWebアプリケーション構築](https://tech-blog.s-yoshiki.com/2018/05/104/)