アート作品の鑑賞状況をOpenCVで可視化する

## 概要
* アート作品の価値を可視化するシステムを作りたいと、福岡のアーティストの方より依頼があった。
* アート作品をどれだけの人が、どのくらいの時間鑑賞したかをできるだけ安価なシステムで実現できないか検討した。
* RaspberryPiをアート作品の前に設置し、OpenCVで顔検出した時間を累積することとした。
* データ可視化サービスとしてAmbient(https://ambidata.io) を使った。
* データのアップロードはRaspberryPiのWifi経由とした。
* インターネットにカメラ映像は流れず、送信する数値データのみとすることにした。

![スクリーンショット 2020-10-01 9.18.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701445/40769a45-c73e-1b91-cef1-396f22b0cb77.png)
福岡市のスタートアップ支援施設 FGN(https://growth-next.com) に設置し運用してみた様子。
![スクリーンショット 2020-10-01 9.24.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701445/c8ad0b6c-bebd-2aa0-f4ba-6fca42730eff.png)
![IMG_4946.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701445/fcd7be98-1fa6-3da7-1951-7796e55e37fa.jpeg)
![IMG_4945.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701445/fe51ae20-ca4b-be82-f844-5f074d42b22b.jpeg)

## 用意するもの

* Raspberry Pi3 Model B (4でもおそらく大丈夫)
* Raspberry Pi用のケース
* Raspberry Pi用のヒートシンク
* Raspberry Pi3 Model B B+ 対応 電源セット(5V 3.0A)
* aspberry Pi用のカメラ
* HDMI入力が付いたモニターまたはTV
* 100円ショップの三脚

## 準備

* RaspberryPiのセットアップをしWifi接続できるようにしておく。  
  https://www.raspberrypi.org/downloads/
* OpenCVとAmbient(IoTの可視化サービス)のライブラリが使用できるようにしておく。  
* Ambientにてダッシュボードを作成し、チャネルIDとライトキーを取得しておく。  
  https://ambidata.io/refs/python/

## プログラミング
* OpenCVとAmbient(IoTの可視化サービス)のライブラリを使用  
* 顔検出のフレーム、5分毎の訪問数、総滞在時間をカメラ映像にオーバーレイ表示
* 5分毎の訪問数、総滞在時間Ambientに送信し可視化
* 訪問数のカウントは顔検出された時間よりおおまかに推定
* およそ3週間の展示だったため、CPU温度も取得しAmbientに送信

```python:detect_face_camera.py
import sys

sys.path.append('/home/pi/.local/lib/python2.7/site-packages')

import numpy as np

import cv2

import time

import datetime

import ambient

args = sys.argv

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

eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')

cap = cv2.VideoCapture(0)

flg = 0

stime =  0

am = ambient.Ambient(xxxx, 'xxxxxxxxxxxxxxxx')  #AmbientのチャネルID/ライトキー(訪問数・滞在時間)

am2 = ambient.Ambient(xxxx, 'xxxxxxxxxxxxxxxx')  #AmbientのチャネルID/ライトキー(CPU温度)

acnt = 0

while(True):

    t3 = time.time()

    cnt = 0

    ret, frame = cap.read()

    # 読み込んだ画像の高さと幅を取得

    height = frame.shape[0]

    width = frame.shape[1]

    import sys

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    for (x,y,w,h) in faces:

        t1 = time.time()

        cv2.rectangle(frame,(x,y),(x+w,y+h),(152,145,234),2)

        roi_gray = gray[y:y+h, x:x+w]

        roi_color = frame[y:y+h, x:x+w]

        eyes = eye_cascade.detectMultiScale(roi_gray)

        cnt += 1

        for (ex,ey,ew,eh) in eyes:

            cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(108,97,232),2)

        t2 = time.time()

        stime += (t2 - t1) * 1000 / 60

        acnt = acnt + cnt

    cv2.putText(frame, 'count: ' + str(acnt / 100), (10, 420), cv2.FONT_HERSHEY_DUPLEX, 1.0, (0,255,0), thickness=2)

    cv2.putText(frame, 'stay[sec]: ' + str('{:.1f}'.format(stime)), (10, 460), cv2.FONT_HERSHEY_DUPLEX, 1.0, (0,255,0), thickness=2)

    cv2.putText(frame, 'senseart v.0.0.1b', (410, 20), cv2.FONT_HERSHEY_PLAIN, 1.5, (0,255,0), thickness=2)

    resized_img = cv2.resize(frame,(width*2, height*2))

    cv2.imshow('senceart (v.0.0.1 beta)', resized_img)

    if cv2.waitKey(1) & 0xFF == ord('q'):

        break

    t4 = time.time()

    f = open("/sys/class/thermal/thermal_zone0/temp","r")  #CPU温度

    for t in f:

        tmp = t[:2]+"."+t[2:5]

    f.close()

    fps = cap.get(cv2.CAP_PROP_FPS)

    now = datetime.datetime.now() 

    minute = '{0:%M}'.format(now)

    second = '{0:%S}'.format(now)

    print('count', acnt / 100)

    print('stay', stime)

    print("minute", minute)

    print("second", second)

    print("temp", tmp)

    print("\n")

    if int(minute) % 5 == 0 and int(second) == 0:

        r = am.send({'d1': acnt / 100, 'd2': stime})  #訪問数と滞在時間をAmbientに送る

        r = am2.send({'d1': tmp})  #CPU温度をAmbientに送る

        r.close()
        acnt = 0

        cnt = 0

        print("******************sended*****************\n")

        continue

    if int(minute) % 10 == 0 and int(second) == 0:

        r = am.send({'d1': acnt / 100, 'd2': stime})  #訪問数と滞在時間をAmbientに送る

        r = am2.send({'d1': tmp})  #CPU温度をAmbientに送る

        r.close()

        acnt = 0

        cnt = 0

        print("******************sended*****************\n")

        continue

cap.release()
```

* RaspberryPiのデスクトップにシェルスクリプトを設置し、Pythonのプログラムが起動するようにしておく。
* 現場のオペレーションとして、アイコンのダブルクリックで起動するようにしておく。

```sh:senceart.sh
#! /bin/bash
cd /home/pi/opencv_programs/
sudo python3 detect_face_camera.py
```

## 注意点
* 当初、福岡市の公衆Free Wifi(時間制限なし)で接続をしていたが、数日で切断されてしまったのでWifiルーター経由に切り替えた。（原因不明…）
* リモートメンテナンス（今回は広島→福岡）ができるようVNC等リモート操作ソフトが必要。  
* トラブル発生時に現地の施設スタッフがある程度対応できるようマウス・キーボード・マニュアルが必要。

## 使ってみて
* 3週間のうち1度だけダウンしたが、概ね安定して動いた。
* 実験的なものだったので、訪問数のカウントロジックが詰めきれていない。何度も実地検証をしてみる必要がある。
* 動作は軽いとは言えなかったので、RaspberryPi4でどの程度パフォーマンスが上がるのか興味がある。