Webアプリ~リア充と非リア~

前書き

この記事は,SLP KBIT Advent Calendar 2021 の5日目の記事です。

adventar.org

前回の記事で男女を区別するmodelを作成しました。 今回は、作成したmodelとFlaskを使用し、リア充と非リアの判断を行うWebアプリを製作していきます。

目次

全体の概要

  • ファイル構造
.
├── F_M.h5
├── demo_app.py
├── haarcascade_frontalface_default.xml
├── resize
├── stock
└── templates
        ├── main_page.html
        └── uploads_page.html

demo_app.pyがバックエンドのメイン処理を行っています。F_M.h5が前回したmodel,haarcascade_frontalface_default.xmlは顔検知の際に使用するもの、resizestockは画像の処理を行う際に使用するディレクトリーです。 templatesは、フロントエンドのコードです。

バックエンド

処理の流れが以下のようになってます。

1. 画像から顔部分を抜き取る
1. 抜き取った画像サイズを変更する
1. 加工した画像から、写真に写ってる人が男性か女性か判断
1. 写真に写ってる人数や男女比からリア充か判断

今回は、LGBTも尊重しているため、ホモっプルかレズップルかも判断することにしました。これで、桜Trickのようなカップルを見つけれるかもしれません。

demo_app.py

from flask import Flask, request, render_template
import os
import cv2
import numpy as np
from keras.models import  load_model
from PIL import Image
import glob
from werkzeug.utils import secure_filename


# 画像をアップロードするフォルダー
upload_folder = './uploads'
# 画像の拡張子の制限
# set()で重複した要素を取り除く
allowed_extenstions = set(["png","jpg","jpeg"])

# お決まり
app = Flask(__name__)
app.secret_key = "hogehoge"

# 設定の保存
# upload_folderの設定を保存
UPLOAD_FOLDER = './uploads/'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

# configの読み込み
app.config.from_object(__name__)

face_cascade_path = './haarcascade_frontalface_default.xml'
face_cascade = cv2.CascadeClassifier(face_cascade_path)

#バウンディングボックス座標データを取得
def Get_Bounding_Box(image):
    #画像を読み込む
    img_b = cv2.imread(image)
    if img_b is None:
        print("No No Object")
        return -1
    #ここでオブジェクトを検出
    #バウンディングボックスもろもろ検出してる
    img_b_gray = cv2.cvtColor(img_b, cv2.COLOR_BGR2GRAY)
    #バウンディングボックスの座標情報をゲット
    face = face_cascade.detectMultiScale(img_b_gray)
    bbox = []
    for x, y, w, h in face:
        bbox.append([x,y,x+w,y+h])
    #print(face)
    #バウンディングボックス座標データを返却
    #print(bbox)
    return bbox

#画像を抜き取り、リサイズ
def detect_face(img, bbox_Coordinate):
    #一時保存先を指定
    save_path = "./stock/stock.jpg"
    #リサイズしたものの出力先
    out_dir = "./resize/stock_"
    i = 0
    #配列の中が空かどうか判断
    #空だったら、実行しない
    if len(bbox_Coordinate) == 0:
        #print("no object")
        return -1
    else:
        #画像読み込み
        img_o = Image.open(img)
        #画像を切り取る
        #座標を一個ずつ読み取る
        for item_position in bbox_Coordinate:
            #取得した座標のところだけを抜き取る
            img_crop = img_o.crop(item_position)
            #画像出力
            img_crop.save(save_path)
            #座標リストを空にする
            #item_position.clear()
            #サイズ変更
            img =Image.open(save_path)
            #64×64サイズにしてる
            img_rot = img.resize((64,64))
            img_rot.save(out_dir + str(i) + ".jpg")
            i += 1
        return 0

def detect_who(img_url):
    model = load_model('./F_M.h5')
    img = cv2.imread(img_url)
    img_set = np.expand_dims(img,axis=0)
    #予測
    name=""
    #print("predict:",model.predict(img_set))
    nameNumLabel=np.argmax(model.predict(img_set))
    if nameNumLabel== 0: 
        name="男性"
    elif nameNumLabel==1:
        name="女性"
    print(name)
    return name

# ボッチかやその顔が男性か女性など判断
def predict_Type():
    fm_dic = {"男性":0, "女性":0}
    img_list = glob.glob("./resize/*.jpg")
    if len(img_list) == 1:
        return "ボッチですね!悲しい人"
    # 各顔写真がどっちか判断
    for img in img_list:
        if detect_who(img) == "男性":
            fm_dic["男性"] += 1
        elif detect_who(img) == "女性":
            fm_dic["女性"] += 1
    #リア充か非リアかホモっプルか判断
    if fm_dic["男性"] == 2 and fm_dic["女性"] == 0:
        return "ホモップル。やりますねー"
    if fm_dic["男性"] == 0 and fm_dic["女性"] == 2:
        return "レズップル!最高!!"
    if fm_dic["男性"] == 0 or fm_dic["女性"] == 0:
        return "リア充に見せかけた非リア"
    if fm_dic["男性"] == 1 and fm_dic["女性"] == 1:
        return "素晴らしいリア充"
    return "お前らは何や?"
    
# resizeのファルダーを削除
def remove_image():
    img_list = glob.glob("./resize/*.jpg")
    for img in img_list:
        os.remove(img)

# リア充か非リアか判断
def riajuu_or_hiria(image_url):
    remove_image()
    image=cv2.imread(image_url)
    if image is None:
        return "Not open"
    # b,g,r = cv2.split(image)
    # image = cv2.merge([r,g,b])
    bbox_Coordinate = Get_Bounding_Box(image_url)
    detect_face(image_url,bbox_Coordinate)
    return predict_Type()

# 拡張子の確認
def allwed_file(filename):
    # .があるかどうかのチェックと、拡張子の確認
    # OKなら1、だめなら0
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extenstions

# main画面
@app.route("/main_page",methods = ["GET", "POST"])
def main_page():
    text = "アップロードテストです"
    return render_template("main_page.html",text = text)

# アップロードしたファイルの処理
@app.route('/uploads_page',methods = ["GET", "POST"])
# ファイルを表示する
def uploaded_file():
    text = "アップしました"
    #データが届いたら
    if request.method == "POST":
        # ファイルを読み込む
        img_file = request.files['img_file']

        # ファイル名を取得する
        filename = secure_filename(img_file.filename)

        # 画像のアップロード先URLを生成する
        img_url = os.path.join(app.config['UPLOAD_FOLDER'], filename)

        # 画像をアップロード先に保存する
        img_file.save(img_url)
        
        # 結果を表示
        result = riajuu_or_hiria(img_url)

        return render_template('uploads_page.html', text=text, result=result)

## おまじない
if __name__ == "__main__":
    app.run(debug=True)

フロントエンド

とりあえず、画像をアップロードするページと結果を表示するページの作成しました。

main_page.html

<!doctype html>
<html lang="ja">
    <head>
        <!-- 文字をutf-8に設定 -->
        <meta charset="utf-8">
        <!-- どのデバイスで見ても見え方を一緒にする = レスポンシブデザイン -->
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title>アップロードテスト</title>
        <!-- cssの読み込み -->
        <!-- link rel="stylesheet" href="static/css/style.css" -->
    </head>

    <body class="text-center">
        <h1>{{text}}</h1>
        <form action="/uploads_page" method=post enctype="multipart/form-data">
            <p><input type=file id="img_file" name=img_file>
                <input type = submit value = Upload>
            </p>
        </form>
    </body>
</html>

f:id:Drink_15:20211205200631p:plain

uploads_page.html

<!doctype html>
<html lang="ja">
    <head>
        <!-- 文字をutf-8に設定 -->
        <meta charset="utf-8">
        <!-- どのデバイスで見ても見え方を一緒にする = レスポンシブデザイン -->
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title>アップロードテスト</title>
        <!-- cssの読み込み -->
        <!-- link rel="stylesheet" href="static/css/style.css" -->
    </head>

    <body class="text-center">
        <h1>{{text}}</h1><br>
        {% if result %}
            <!--img src={{result_img}}-->
            <p>{{result}}</p>
        {% endif %}
    </body>
</html>

f:id:Drink_15:20211205200737p:plain

私はサボっていますが、cssとかもっとしかっり書けば、GUIは豪華になります。

カップル検証

実際にうまくいってるか試してみます。 ネットで無作為にこの画像を選びました。 f:id:Drink_15:20211205202821j:plain 結果がこちらです。 f:id:Drink_15:20211205202934p:plain あれ...?
なぜか、男の娘カップルと判断されましたね('ω')
おそらく、画像解析部分がうまくいってません。頑張って、この部分を改良しようと思います。