Safie Engineers' Blog!

Safieのエンジニアが書くブログです

Safie APIとDeep Learningで飼い猫の健康管理をしてみた

はじめに

はじめまして!事業戦略部の松本です。ビジネスサイドでSafie API関連のサポートを担当しています。

本記事ではSafie APIとDeep Learningを利用して、飼い猫達を画像分類し給仕DXをしたのでお伝えしていきたいと思います。


やりたいこと

我が家には飼い猫のチャチャ(メス: 2歳7か月)とクロ(オス: 1歳11か月)が暮らしています。

サビ猫のチャチャ
黒猫のクロ

当初はチャチャを1匹でお迎えしていましたが、YouTubeなどで2匹の飼い猫が仲良く暮らしているのを見て憧れ、1年ほど後にクロもお迎えすることにしました。

初めての多頭飼い(複数のペットを飼うこと)だったので、事前に色々予習をしました。生活回りのものは分けた方がいいということだったので、餌やり器や水やり器、トイレなども別々で用意していたのですが、幸か不幸か2匹は同じものを利用しているようでした。

そこで出てきたのが、チャチャとクロが「それぞれどのくらいの餌を食べているのか分からない問題」(以後、猫餌問題)でした。

今までチャチャ1匹の時は、食べている餌の量でその日の体調の良し悪しが分かっていたのですが、2匹が同じ餌やり器を共有することでそれが分からなくなってしまったのです…...。

そこで、Safie APIとディープラーニングを組み合わせて、チャチャとクロのそれぞれがどのくらい餌を食べているのか判別するようにしました!

Safie APIとは

Safieでは、ユーザ様がSafieカメラで撮影した映像や静止画を自社のツールに組み込んだり、AIerなどが提供する映像解析と連携させるために、オープンAPIであるSafie APIを提供しています。

Safie APIでは映像や静止画などのバイナリデータの他、Safie OneなどのエッジAIを搭載したカメラで利用できるAI-App人数カウントのデータも取得できます。その他にも取得できるデータは様々あり、取得方法も併せて開発者向けサイトSafie Developersに纏まっています。

もちろんデータの利用には映像データ所有者のユーザ様の事前許諾が必要で、認可/ 認証にはOAuth2.0/ APIキーを採用しています。

どうするか

今回は猫餌問題解決のため、「画像取得API」を利用することにしました。このAPIでは実行したタイミングの最新の静止画が取得出来る他、指定した過去の任意の時点の静止画を取得することもできます(録画プランの範囲内に限ります)。

我が家では電動で猫の餌が出る餌やり器を利用しています。この餌やり器をSafieカメラで撮影し、そこに映った猫がチャチャなのかクロなのかをTensorFlowを利用し画像分類して判別できるようにしたいと思います。

画像取得APIを実行してみる

まず画像取得APIを利用して、カメラの静止画を取得してみます。Safie APIを利用するにはまずSafie Developersでアプリケーションを作成します。

Safie APIでは認証方式をOAuth2.0とAPIキーの2種類から選択できます。APIキー方式ではトライアル利用もできるため、今回はAPIキー方式を採用します。

APIキーの発行もSafie Develoeprsから実施します。 実際の画像取得APIの実行方法については、APIリファレンスで仕様を確認できます。

リファレンスではAPIリクエスト時のエンドポイントやパラメータ、レスポンスの内容が確認できます。画像取得APIで必要なパラメータはdevice_idとtimestampで、timestampを省略すると直近の静止画がレスポンスで返ります。device_idはSafieカメラごとに固有のIDでシリアルナンバーとは異なり、デバイス一覧取得APIで確認できます。

import requests


api_key = "YOUR_API_KEY_HERE"
device_id = "YOUR_DEVICE_ID_HERE"


url = f"https://openapi.safie.link/v2/devices/{device_id}/image"
headers = {"Safie-Api-Key": api_key}


try:
    res = requests.get(url, headers=headers)
    res.raise_for_status()
except:
    print(f"Error! Status code: {res.status_code}")
else:
    with open("./safie_image.jpg", "wb") as f:
        f.write(res.content)

APIを実行する際はリクエストヘッダにAPIキーを付与します。実行したところ静止画が保存されていることが確認できました。 保存される静止画のイメージ。チャチャが餌を食べているところ。

猫を学習

次に保存した写真を分類するための前準備として「チャチャ」と「クロ」と「猫が映っていない」の3パターンをTensorFlowを使って機械学習させていきます。

具体的には、それぞれの食事中の写真を数百枚ずつほど用意しました。
ここは手動となるので地道にSafie Viewerとにらめっこしながら作業しますが、Safieの多くのカメラには動きのあった時間を検知する「モーション検知機能」が標準で用意されているのでそこまで大変な作業ではありませんでした。

猫は部屋が暗い夜間でも餌を食べます。Safieの多くのカメラには真っ暗な環境でも白黒の映像を撮影できる「IR機能」が備わっているため、今回はIR機能を常時ONにし、学習させる画像データもIRがONの白黒のものにしました。

これらの写真を使ってデータセットの作成・モデルの構築をしていきます。こちらのサイトを参考にしました。

1.データセットの作成。データ拡張のため画像の回転・反転を行っています。

from PIL import Image
import glob
import numpy as np
from PIL import ImageFile


ImageFile.LOAD_TRUNCATED_IMAGES = True


classes = ["chacha", "kuro", "na"]
num_classes = len(classes)
image_size = 64
num_testdata = 25


X_train = []
X_test  = []
y_train = []
y_test  = []


for index, classlabel in enumerate(classes):
    photos_dir = "./" + classlabel
    files = glob.glob(photos_dir + "/*.jpg")
    for i, file in enumerate(files):
        image = Image.open(file)
        image = image.convert("RGB")
        image = image.resize((image_size, image_size))
        data = np.asarray(image)
        if i < num_testdata:
            X_test.append(data)
            y_test.append(index)
        else:
            for angle in range(-20, 20, 5):
                img_r = image.rotate(angle)
                data = np.asarray(img_r)
                X_train.append(data)
                y_train.append(index)
                img_trains = img_r.transpose(Image.FLIP_LEFT_RIGHT)
                data = np.asarray(img_trains)
                X_train.append(data)
                y_train.append(index)


X_train = np.array(X_train)
X_test  = np.array(X_test)
y_train = np.array(y_train)
y_test  = np.array(y_test)


xy = (X_train, X_test, y_train, y_test)
np.save("./chacha_kuro_datasets.npy", xy)

2.モデルの作成(一部)。作成したデータセットから猫の判別に利用するKerasモデルを作成しています。

# データを読み込む関数
def load_data():
    X_train, X_test, y_train, y_test = np.load("./chacha_kuro_datasets.npy", allow_pickle=True)
    X_train = X_train.astype("float") / 255
    X_test  = X_test.astype("float") / 255
    y_train = np_utils.to_categorical(y_train, num_classes)
    y_test  = np_utils.to_categorical(y_test, num_classes)


    return X_train, y_train, X_test, y_test

モデルを学習する関数

def train(X, y, X_test, y_test):
    model = Sequential()


    model.add(Conv2D(32,(3,3), padding='same',input_shape=X.shape[1:]))
    model.add(Activation('relu'))
    model.add(Conv2D(32,(3,3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2,2)))
    model.add(Dropout(0.1))


    model.add(Conv2D(64,(3,3), padding='same'))
    model.add(Activation('relu'))
    model.add(Conv2D(64,(3,3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2,2)))
    model.add(Dropout(0.25))


    model.add(Flatten())
    model.add(Dense(512))
    model.add(Activation('relu'))
    model.add(Dropout(0.45))
    model.add(Dense(num_classes))
    model.add(Activation('softmax'))


    opt = RMSprop(lr=0.00005, decay=1e-6)


    model.compile(loss='categorical_crossentropy',optimizer=opt,metrics=['accuracy'])
    model.fit(X, y, batch_size=28, epochs=40)


    model.save('./cnn.h5')
  1. 動作の確認。作成したモデルをロードしテスト用の写真を判別できるかやってみます!
import keras
import numpy as np
from PIL import Image
from keras.models import load_model


model = load_model("./cnn.h5")


img = Image.open("./chacha_pic_for_test.jpg")
img = img.convert('RGB')
img = img.resize((64, 64))
img = np.asarray(img)
img = img / 255.0
prd = model.predict(np.array([img]))
prelabel = np.argmax(prd, axis=1)
if prelabel == 0:
    print("チャチャの写真です")
elif prelabel == 1:
    print("クロの写真です")
elif prelabel == 2:
    print("どちらでもないです")

実行するタイミングを決める

餌やり器から餌がなくなった状態を検出し、そのタイミングから60秒さかのぼって上記のコードを実行するようにします。

def is_esa_empty(image_from_safie: bytes) -> bool:
    img_binarystream = io.BytesIO(image_from_safie)
    img_pil = Image.open(img_binarystream)
    img_numpy = np.asarray(img_pil)


    img = cv2.cvtColor(img_numpy, cv2.IMREAD_COLOR)


    # 餌の皿部分以外をマスクする
    h, w = img.shape[:2]
    mask = np.zeros((h, w), dtype=np.uint8)
    cv2.circle(mask, center=(645, 340), radius=170, color=255, thickness=-1)
    img[mask==0] = [255, 255, 255]  # mask の値が 0 の画素は黒で塗りつぶす。


    # 白黒に変更
    img_bw  = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)


    # 二値化
    _, thresh = cv2.threshold(img_bw, 106,255, cv2.THRESH_BINARY_INV)


    # 輪郭検出
    contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)


    # 輪郭描画
    cv2.drawContours(img, contours, -1, (0, 255, 0), 2)
   
    # 餌ひとかけらの面積を400として餌の数を算出
    esa_count = int(sum([cv2.contourArea(contour, False) for contour in contours]) / 400)
   
    # 餌の数が10を切ったらemptyとする
    return True if esa_count < 10 else False

いつでもどこでも見られるようにする

最後に、Safieのカメラと同じようにいつでもどこでも猫の餌の状況を確認できるように、上記のコードで取得できたデータをデータベースに保存し、Webサーバを立ててアクセスできるようにしました。

どちらの猫がいつどのくらい食べているのか簡単に分かり健康管理に役立てています。
また、在宅していないタイミングでも元気に餌を食べてるか確認できるのでとても便利です。

以上、SafieカメラとAPIを利用して、飼い猫の給仕DXをした話でした。皆さんも是非Safie APIを使って日々の生活や業務の効率化に役立ててみてください!

参考文献: AI Academy - Deep Learningで犬・猫を分類してみよう

© Safie Inc.