神戸のデータ活用塾!KDL Data Blog

KDLが誇るデータ活用のプロフェッショナル達が書き連ねるブログです。

【オフィス人数の遠隔取得①】ソラカメ™とYOLOv8使ってみた編

こんにちは! DataIntelligenceチームの福岡です。

この連載では、以下の要領でオフィスにいる人数の遠隔自動取得に挑戦します!

  1. オフィスの様子を設置したカメラで撮影&ダウンロード
  2. 画像から人だけを検出。人数をカウント
  3. 上記を自動で定期的に実行する
  4. 他サービスと連携して結果をリアルタイムに反映する

連載第一回目となる今回は、クラウドカメラサービス『ソラカメ』と物体検出モデルYOLOv8を使って、1と2を実装します。

取り組みの背景

弊社はハイブリッド勤務を採用しており、社員はリモートワークか出社を自由に選択できます。 そのため、午前中に在宅勤務やお客様のところに訪問して、午後から出社している社員もいます。

人が少ない方が集中できる!という方もいれば、大勢いるときに出社したい方もいます。ハイブリッド勤務になったことで、いつ誰が出社しているか把握するための出社管理システムが導入されました。 ですが、申請し忘れてしまうこともしばしば...

なので、自動で人数をカウントできるシステムがあったらな~と思ったのが、今回の実験のモチベーションです!

作業の流れ

作業は次のような流れです。

  1. ソラカメ(後述)を設置
  2. ソラカメから指定した時間の画像を取得
  3. 画像に写っている人を検出・カウント

順にみていきましょう。

1. ソラカメを設置

ソラカメはソラコム社が提供するクラウドカメラサービスです。インターネットと電源につないでポンと置くだけで24時間のクラウド録画ができます。

クラウド保存期間や料金は下記をご覧ください。

Soracom Cloud Camera Services ソラカメ - IoT プラットフォーム SORACOM

ソラカメは各種APIを提供しているため、他システムとの連携も可能です。 今回は、「指定した時刻の録画画像を取得する」処理に関連するAPIを利用します。

早速設置します。 オフィスの出社人数をカウントしたいので、全体が写るようにいい感じに設置します。

2. ソラカメから指定した時間の画像を取得

APIを使って、指定した時間の録画画像をダウンロードします。 ソラカメのAPIリファレンスを参考にします。

users.soracom.io

事前準備として、先に以下2点を実施します。

  1. 必要なライブラリをインストール
  2. API認証で用いるメールアドレスやパスワードの定義
 #必要なパッケージのインストール
import requests
import json
import time
from pathlib import Path
from datetime import datetime, timezone, timedelta

#メールアドレス等の定義
email ="E_MAIL@ADDRES"
password = "PASSWORD"
X_Cybozu_API_Token= "MY_API_TOKUN"
Content_Type =  "application/json"
 

次に時刻の指定回りの準備を行います。 ソラカメ APIの時間指定はUNIXタイム(エポックミリ秒)で行います。 現在の時刻を取得して、UNIXタイムに変換するクラスを用意します。

#現在時刻を取得し、UNIXタイムに変換するクラス
class TimeConverter:
    def __init__(self):
        # 現在の日時を取得
        now = datetime.now()
        
        # 各部分を取得
        self.year = now.year
        self.month = now.month
        self.day = now.day
        self.hour = now.hour
        self.minute = now.minute
        self.second = int(now.second)  # 小数点以下を切り捨てるためにintにキャスト

        # 現在時刻のUNIXタイムを計算
        self.get_image_time = self.japan_time_to_unix_time_milliseconds()

    def japan_time_to_unix_time_milliseconds(self):
        # 日本時間のタイムゾーンオフセット(UTC+9時間)を作成
        jst_offset = timezone(timedelta(hours=9))

        # 日本時間のdatetimeオブジェクトを作成
        japan_time = datetime(self.year, self.month, self.day, self.hour, self.minute, self.second, tzinfo=jst_offset)

        # UNIXタイム(エポックミリ秒)に変換
        unix_time_ms = int(japan_time.timestamp() * 1000)
        return unix_time_ms

ここまでで準備完了。お疲れさまでした。

ではソラカメのAPIを使っていきましょう! 指定した時間の録画画像の取得が目的なので、ダウンロード用URLを出力するクラスを用意します。

#指定した時刻の画像を取得するためのクラス
class SoracomAPI:
 def __init__(self, email, password):
        self.base_url = "https://api.soracom.io/v1/"
        self.UserInfo_url = "auth/"
        self.email = email
        self.password = password
        self.headers = {'Content-Type': 'application/json'}
        self.authenticate()

 #API実行には認証が必要
    def authenticate(self):
        data = {
            'email': self.email,
            'password': self.password
        }
        resp_UserInfo = requests.post(self.base_url + self.UserInfo_url, headers=self.headers, data=json.dumps(data))
        self.api_key = resp_UserInfo.json()["apiKey"]
        self.token = resp_UserInfo.json()["token"]
        self.headers.update({
            'X-Soracom-API-Key': self.api_key,
            'X-Soracom-Token': self.token
        })
 
 #画像エクスポートに必要なデバイスIDを返すAPI
    def get_device_id(self):
        url = "sora_cam/devices"
        url = self.base_url + url
        resp = requests.get(url, headers=self.headers)
        resp = json.loads(resp.content)
        device_Id = resp[0]['deviceId']
        return device_Id
  
 #エクスポートを開始するAPI
    def start_image_export(self,get_image_time):
      device_id = self.get_device_id()
      url = 'sora_cam/devices/{}/images/exports'.format(device_id)
      url = self.base_url + url
      data = {
          'time': get_image_time
          }

      resp = requests.post(url, headers=self.headers, data=json.dumps(data))
      return resp

 #ダウンロード用URLを取得するAPI
    def get_image_URL(self):
      url = 'sora_cam/devices/images/exports'
      url = self.base_url + url

      params  = {'device_id': self.get_device_id() ,
          "limit":1, #最新の一枚を取得
          "sort":"desc"
          }
      resp = requests.get(url, headers=self.headers, params=params)
      return resp

試してみましょう。

#現在時刻をUNIXタイムに変換
test = TimeConverter()
test.get_image_time

#1690881969000
#2023-08-01 18:26:09 のUNIXタイム表記

この時間の録画画像をダウンロードしましょう。

soracom = SoracomAPI(email,password)
img = soracom.start_image_export(1690881969000)

これで、

print(img.json()[0]["url"])

とすれば画像のURLを取得でき、そこからダウンロードが可能です(注:一定時間が経過するとダウンロード不可になります)。

こんな感じで、ダウンロードできます。

取得した画像

※情報保護のため一部加工しています。

3. 取得した画像から、映っている人を検出・カウント

ここまでで画像の用意ができました。 この画像に何人写っているかを検出・カウントしましょう!

今回は個人的な興味から、Ultralytics社が提供する物体検出モデルYOLOの最新版 YOLOv8を使用します。 数ある物体検出モデルの中でも、少ないコードで高精度のモデルを簡単に実装できます。贅沢ですね。

早速試してみましょう。 まずは環境構築です。

!git clone https://github.com/ultralytics/ultralytics
%cd ultralytics
!pip install -r requirements.txt

これだけで準備OK。簡単です。

YOLOv8が使えるようになったか確かめてみましょう。 デモ画像で実際に物体検出ができるか試してみます。

from ultralytics import YOLO

#v8シリーズの中でも一番性能の良い8xを使用
model = YOLO("yolov8x.pt") 
result = model("https://ultralytics.com/images/bus.jpg", save=True) 

save=Trueにすることで、推論結果をultralytics/runs/detect/predictに保存します。

デモ画像の推論結果

いい感じですね。

では、先ほどダウンロードした画像に適用してみましょう!!

ただし、今回はオフィス内の人数だけに興味があるので、人だけを検出できるように引数を調整します。 また、閾値も変えてみましょう。

Configuration - Ultralytics YOLOv8 Docs

上記ドキュメントを参照すると、

識別項目はclasses = [学習に用いられたデータセット内でのラベル]、 閾値はconf =で設定できるようです。

personのラベルが何番か確認します。 ひとまずprint(result) で推論結果の中身を見てみましょう。

[ultralytics.engine.results.Results object with attributes:
 
 boxes: ultralytics.engine.results.Boxes object
 keypoints: None
 masks: None
 names: {0: 'person', 1: 'bicycle', 2: 'car', 3: 'motorcycle', 4: 'airplane', 5: 'bus', 6: 'train', 7: 'truck', 8: 'boat', 9: 'traffic light', 10: 'fire hydrant', 11: 'stop sign', 12: 'parking meter', 13: 'bench', 14: 'bird', 15: 'cat', 16: 'dog', 17: 'horse', 18: 'sheep', 19: 'cow', 20: 'elephant', 21: 'bear', 22: 'zebra', 23: 'giraffe', 24: 'backpack', 25: 'umbrella', 26: 'handbag', 27: 'tie', 28: 'suitcase', 29: 'frisbee', 30: 'skis', 31: 'snowboard', 32: 'sports ball', 33: 'kite', 34: 'baseball bat', 35: 'baseball glove', 36: 'skateboard', 37: 'surfboard', 38: 'tennis racket', 39: 'bottle', 40: 'wine glass', 41: 'cup', 42: 'fork', 43: 'knife', 44: 'spoon', 45: 'bowl', 46: 'banana', 47: 'apple', 48: 'sandwich', 49: 'orange', 50: 'broccoli', 51: 'carrot', 52: 'hot dog', 53: 'pizza', 54: 'donut', 55: 'cake', 56: 'chair', 57: 'couch', 58: 'potted plant', 59: 'bed', 60: 'dining table', 61: 'toilet', 62: 'tv', 63: 'laptop', 64: 'mouse', 65: 'remote', 66: 'keyboard', 67: 'cell phone', 68: 'microwave', 69: 'oven', 70: 'toaster', 71: 'sink', 72: 'refrigerator', 73: 'book', 74: 'clock', 75: 'vase', 76: 'scissors', 77: 'teddy bear', 78: 'hair drier', 79: 'toothbrush'}
 orig_img: array([[[79, 89, 86],
         [79, 89, 86],
         [80, 90, 87],
         ...,
         [75, 89, 87],
         [75, 89, 87],
         [75, 89, 87]],
 
        [[79, 89, 86],
         [79, 89, 86],
         [80, 90, 87],
         ...,
         [75, 89, 87],
         [75, 89, 87],
         [75, 89, 87]],
 
        [[80, 90, 87],
         [80, 90, 87],
         [80, 90, 87],
...

nemes: をみると、どうやら'person' は0番になっているようです。

result[0].names[0]

#'person'

引数に classes=[0] を追加します。

さらに閾値も変更します。 今回はconf= 0.3にしてみます。

#imageフォルダを作成して、推論したい画像を入れておく
result = model("image/target1.png", save=True, classes=[0], conf=0.3)

推論の結果です。

推論の結果

すごい精度です! ほぼ正確に人だけ検出できています。

※画像加工は推論の後からしています。

最後に、検出した人数を取得しましょう。 resultの中身を参考にして、

result[0].boxes.data.shape[0]

#7

とすればOKです。

いかがでしたか? 今回は、

・ソラカメを使って遠隔で画像を取得

・画像から人だけを検出、人数をカウント

を実施しました!

オフィスの人口密度を計算したり、ある施設の時間ごとの来場者数の推移を見たり、なんていう応用もできそうで楽しいですね。

今回はURLから手動で画像を取得し、モデルに渡しています。 画像の取得と推論は定期的に自動で行えたらより便利ですね。

また、業務にkintoneやLINEなどを使っている方もいらっしゃると思います(弊社はkintoneです)。 普段使っているツールと連携してリアルタイムで結果を表示する、といった使い方も考えられます!

続編ではこれらを踏まえ、さらに使い勝手を良くしてきます! 乞うご期待!