Pythonで集合写真から人物、感情を検出しパワポにまとめてみた

## 目次
[0.結論](#0-結論)
[1.はじめに](#1-はじめに)
[2.前準備](#2-前準備)
[3.集合写真から人物、顔領域、感情を検出](#3-集合写真から人物顔領域感情を検出)
[4.パワーポイントの作成](#4-パワーポイントの作成)
[5.おわりに](#5-おわりに)

### 0. 結論
Pythonを用い、集合写真から写真内の人数の判定や顔領域の判定、および感情検出が可能。
また、上記結果についてあらかじめ設定しておいたパワーポイントのフォーマットに従ったまとめ資料の自動作成が可能。

▼作成したスクリプト
[Make_pptx_from_Image_For_Qiita.ipynb](https://colab.research.google.com/drive/1C4nhU8EFk8Z4ICsvGILT22hMvI78EePO?usp=sharing)

▼作成されるまとめ資料
<iframe src="https://docs.google.com/presentation/d/e/2PACX-1vS5Tplw2qnkZEXGnocettyspbeQEOR8vwv5YW4tu7lW-rcquVCpI4NZNA6Ae_nzL6JxFysIQeNPM_n3/embed?start=true&loop=true&delayms=3000" frameborder="0" width="960" height="749" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>

### 1. はじめに
研修等の際には集合写真を撮影する機会があると思います。

今回は、その集合写真から写真内の人数の判定や顔領域の判定、および感情検出を行なっていきます。

また、上記結果についてパワーポイントにまとめる処理についても実行します。

なお開発環境ですが**Google Colaboratory**を利用しています。

▼参考サイト
[【Google Colab】GPUが無料で使える!? 基本的な使い方](https://sci-by-py.com/hpc/google-colab/)

### 2. 前準備
ここでは、前準備としてGoogleドライブのマウントとテストする集合画像のDLについて記載しております。

```Python:
#Googleドライブのマウント（画像を自分で準備するときはGoogleドライブ使うとよい）
from google.colab import drive
drive.mount('/content/drive')
```

```Python:
#テストする画像のダウンロード
!wget https://static.eyecity.jp/pc/images/eye_psychology/48/img_01.jpg
```

### 3. 集合写真から人物顔領域感情を検出
ここでは、作成したスクリプトのメインの部分となる集合写真から人物、顔領域、感情を検出する処理について紹介していきたいと思います。

まずは画像処理用のライブラリである**OpenCV**と顔認識用のライブラリである**dlib**と感情分析用のライブラリである**FER**をインストールします。

```Python:
#OpenCV（画像処理ライブラリ）のインストール
!pip install opencv-python
import cv2
from google.colab.patches import cv2_imshow
```

```Python:
#dlib（顔認識ライブラリ）のインストール
!pip install dlib
import dlib
detector = dlib.get_frontal_face_detector()
```
```Python:
#FER（感情分析ライブラリ）のインストール（5分くらい時間かかる）
!pip install FER
from fer import FER
emo_detector = FER(mtcnn=True)
```
その他必要なライブラリをインストールします。
```Python:
#その他諸々インストール
import numpy as np
```
色々とPathの設定を記載します。

自身の実行環境において適切に書き換えてください。
```Python:
#上記ダウンロードした画像用のpath、テストしたい画像を準備する場合は自分で記載
#pathの設定
path = ''#初期設定：colab起動中のランタイム内に保存（ランタイム終わったら削除される）
#path = '/content/drive/MyDrive/'#例：Googleドライブを利用する場合
img_name = 'img_01'
extension = '.jpg'
#詳細pathの設定
path_img = path + img_name + extension
path_img_rect = path + 'Rect_' + img_name + extension
path_img_rect_emo = path + 'Rect_Emo_' + img_name + extension
path_img_rect_mosaic = path + 'Rect_Mosaic_' + img_name + extension
```

以下にて顔領域を検出する関数の定義を行なっています。
```Python:
#顔の検出
def face_recognition(img):
  #faceには検出された顔の数 * 座標が格納されている
  #detectorはdlibのインストール時に定義している、(顔検出する元画像, 検出精度)を入力
  face = detector(img,2)
  return face
```

以下にて検出された顔領域を画像内に矩形表示する関数の定義を行なっています。

なお、矩形表示の際には、感情によって矩形の色や大きさを変更しております。

```Python:
#検出された顔の感情検出
def face_rect_emo(faces,img,best_emotion):
  #検出された顔毎、一番高い感情毎の処理
  for face,emo in zip(faces, best_emotion):
    x, y, w, h = face.left(), face.top(), face.width(), face.height()

    #検出した顔を矩形描画（happyなら赤色大矩形、それ以外なら黒色小矩形）
    if emo == 'happy' : img_rect = cv2.rectangle(img,(x-int(w/20),y-int(h/15)),(x+w+int(w/20),y+h+int(h/15)),(0,0,255),5)
    else : img_rect = cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,0),3)
  return img_rect
```

以下にて検出された顔領域を画像として保存する関数の定義を行なっています。
```Python:
#検出された顔の切り抜き
def face_cutting(faces,img, cutting_num, path, img_name, extension):
  for face in faces:
    x, y, w, h = face.left(), face.top(), face.width(), face.height()
    #cut_imgに検出した顔の領域を格納
    cut_img = img[y-int(h/20) : y+h+int(h/15), x-int(w/20): x+w+int(w/15)]
    path_img_cutting = path + 'Cutting_' + img_name + '_' + str(cutting_num) + extension
    #cut_imgを保存
    cv2.imwrite(path_img_cutting, cut_img)
    cutting_num += 1
  return cutting_num
```

以下にて検出された顔領域から感情を判定する関数の定義を行なっています。
```Python:
#顔画像から感情を検出（以下参考にしたサイト）
#https://qiita.com/MMsk0914/items/838a7685a984461c8cb8

def emo_from_face(captured_emotions, best_emotion, cutting_num, path, img_name, extension):
  for i in range(1, cutting_num):
    path_img_cutting = path + 'Cutting_' + img_name + '_' + str(i) + extension
    img = cv2.imread(path_img_cutting)
    #captured_emotionsに検出した感情（辞書形式）を格納
    captured_emotions.append(emo_detector.detect_emotions(img))
    #best_emotionに検出した一番高い感情（辞書形式）を格納
    best_emotion.append(emo_detector.top_emotion(img)[0])
  return captured_emotions,best_emotion
```

以下にて指定した領域をモザイク化する関数の定義を行なっています。
```Python:
#モザイク化
def mosaic(img, ratio=0.1):
  #入力画像を収縮、拡大させることでモザイク化処理実行
  small = cv2.resize(img, None, fx=ratio, fy=ratio, interpolation=cv2.INTER_NEAREST)
  return cv2.resize(small, img.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)
```

以下にて上記関数を利用して顔領域をモザイク化する関数の定義を行なっています。
```Python:
#検出した顔の領域をモザイク化
def face_mosaic(faces, img, path_img_rect_mosaic, ratio=0.1):
    dst = img.copy()
    for face in faces:
      x, y, w, h = face.left(), face.top(), face.width(), face.height()
      #検出した顔の領域をモザイク化
      dst[y : y+h, x : x+w] = mosaic(dst[y : y+h, x : x+w], ratio)
    cv2.imwrite(path_img_rect_mosaic, dst)
```

以下にて作成した関数を実行し、集合写真から人物、顔領域、感情を検出しています。
```Python:
cutting_num = 1
captured_emotions = []
best_emotion = []

#画像の前処理(パワポでの表示がいい感じになるようにする）
img = cv2.imread(path_img)
img = cv2.resize(img, (400,300))
cv2.imwrite(path_img, img)

#画像の読み込み
img = cv2.imread(path_img)
img_for_rect = cv2.imread(path_img)
#顔画像領域の認識
faces = face_recognition(img)
#顔画像領域のモザイク
face_mosaic(faces, img, path_img_rect_mosaic)
#顔画像領域の切り取り
cutting_num = face_cutting(faces, img, cutting_num, path, img_name, extension)
#顔画像から感情を検出
captured_emotions, best_emotion = emo_from_face(captured_emotions, best_emotion, cutting_num, path, img_name, extension)
#顔画像領域の矩形書き込み
img_rect = face_rect_emo(faces, img_for_rect, best_emotion)
cv2.imwrite(path_img_rect_emo, cv2.resize(img_rect, (400,300)))
```

上記によってメインの部分となる集合写真から人物、顔領域、感情を検出する処理が可能となります。

### 4. パワーポイントの作成
ここでは、作成したスクリプトのサブの部分となる、あらかじめ設定しておいたパワーポイントのフォーマットに従ったまとめ資料の自動作成処理について紹介していきたいと思います。

まずはパワーポイント関係用のライブラリである**python-pptx**をインストールします。

```Python:
#python-pptx（パワーポイント関係ライブラリ）のインストール（5分くらい時間かかる）
!pip install python-pptx
from pptx import Presentation
from pptx.util import Inches, Pt, Cm
import datetime
```

以下にてあらかじめ設定しておいたパワーポイントのフォーマットに基づいたまとめ資料の作成処理を行なっています。
```Python:
now = datetime.datetime.now() #現在時刻の取得
today = now.strftime('%Y年%m月%d日') #現在時刻を年月曜日で表示

save_name = path + '集合写真サンプルパワーポイント.pptx' #保存用のパワポのファイル名
prs = Presentation()

# 1枚目
title_slide_layout = prs.slide_layouts[0]
slide = prs.slides.add_slide(title_slide_layout)
title = slide.shapes.title
subtitle = slide.placeholders[1]
title.text = '集合写真サンプルパワーポイント'
subtitle.text = today

# 2枚目
title_slide_layout = prs.slide_layouts[1]
slide = prs.slides.add_slide(title_slide_layout)
title = slide.shapes.title
content = slide.placeholders[1]
title.text = '本日の集合写真'
# 画像をスライドに追加
slide.shapes.add_picture(path_img, Inches(0.5), Inches(3))

# ３枚目
title_slide_layout = prs.slide_layouts[1]
slide = prs.slides.add_slide(title_slide_layout)
title = slide.shapes.title
content = slide.placeholders[1]
title.text = '本日の集合写真（モザイク）'
# 画像をスライドに追加
slide.shapes.add_picture(path_img_rect_mosaic, Inches(0.5), Inches(3))

# ４枚目
title_slide_layout = prs.slide_layouts[1]
slide = prs.slides.add_slide(title_slide_layout)
title = slide.shapes.title
content = slide.placeholders[1]
title.text = '本日の集合写真'
content.text = f'参加した人は{len(best_emotion)}人で、' + f'満足した人は{best_emotion.count("happy")}人です'
# 画像をスライドに追加
slide.shapes.add_picture(path_img_rect_emo, Inches(0.5), Inches(3))

# ５枚目(白紙)
title_slide_layout = prs.slide_layouts[6]
slide = prs.slides.add_slide(title_slide_layout)

# ６枚目以降
for i in range(1, cutting_num):
  title_slide_layout = prs.slide_layouts[7]
  slide = prs.slides.add_slide(title_slide_layout)
  title = slide.shapes.title
  content = slide.placeholders[1]
  title.text = 'XXXさん'
  content.text = '感情を数値化します' + '\n'
  for emo,num in captured_emotions[i-1][0]['emotions'].items():
    content.text += emo + f' : {num}\n'
  # 画像をスライドに追加
  path_img_cutting = path + 'Cutting_' + img_name + '_' + str(i) + extension
  slide.shapes.add_picture(path_img_cutting, Inches(1), Inches(3.5))

prs.save(save_name)
print('ファイルの書き出し完了しました')
```

### 5. おわりに
集合写真から写真内の人数の判定や顔領域の判定、および感情検出ができるスクリプトを作成しました。

また、上記結果についてあらかじめ設定しておいたパワーポイントのフォーマットに従ったまとめ資料の自動作成するスクリプトを作成しました。

人物の判定は入退室の管理等に使えると思います。

またまとめ資料の作成については、大量の画像をパワーポイントに掲載する資料の作成時の時短につながると思っております。

ぜひ、本スクリプトを活用してみてください！

長い文章を最後までお読みいただき、ありがとうございました！！