United Statesからアクセスのようです。言語設定をEnglishに切り替えますか?
Switch to English site
Skip to main content

顔認識機能付き監視カメラ

概要

このシステムは、Raspberry Piに接続されたUSBカメラと、Androidタブレットから構成され、USBカメラによって撮影された映像に顔認識処理を行った上でAndroidタブレットに送信する。Raspberry PiとAndroidタブレットはネットワークを介して接続され、映像はあらゆる場所において取得することができる。システムの概略は下図のようになっている。

図1: システム概略図

Raspberry Pi側

Pythonプログラムでは主に以下の3つの処理を行っている。

  • Raspberry Piに接続されたUSBカメラから映像を取得する
  • 取得した映像に顔認識処理を施す
  • 顔認識の結果をHTTPレスポンスとして返す

それぞれについての詳細を以下に示す。

まずRaspberry Piに接続されたUSBカメラからの映像の取得は以下のように行う。

# camera.py

import cv2

class Video(object):
    def __init__(self):
        self.video = cv2.VideoCapture(0)

    def __del__(self):
        self.video.release()

まず、PythonのOpenCVライブラリのVideoCaptureクラスのオブジェクトを生成する。VideoCapureは引数として動画ファイルのパスもしくはカメラIDを受け取る。ここでは、Raspberry Piに接続したUSBカメラの映像を取得するためIDとして0を指定している。そして、このオブジェクトのデストラクタとしてrelease()を実行し、カメラを無効化している。

このようにして映像を取得できるようにしたのちに、実際に顔認識処理を行う。該当する処理を以下に示す。

# camera.py

haar_cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml")
ds_factor = 0.6

class Video(object):
    ...

    def get_frame(self):
        stream, image = self.video.read()
        image = cv2.resize(
            image, None, fx=ds_factor, fy=ds_factor, interpolation=cv2.INTER_AREA
        )
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        rects = haar_cascade.detectMultiScale(gray, 1.3, 5)

        for (x, y, w, h) in rects:
            cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
            break

        ret, jpeg = cv2.imencode(".jpg", image)
        
        return jpeg

本システムにおける顔認識機能の実装にあたっては、Haar-like特徴と呼ばれる、画像内の局所的な明暗差のパターンを集めた特徴量を用いて物体を識別する識別器を用いる。Haar-like分類器は深層学習による物体識別と比較し精度は劣るものの、計算時間が高速であり本システムにのようにリアルタイム認識機能を実装する際には適していると考えられる。OpenCVには複数の分類器が用意されており、本システムでは正面の顔を識別するhaarcascade_frontalface_alt2モデルを用いる。

実際の識別処理は次のような流れで行われる。まず、先ほど述べたように生成したVideoCaptureオブジェクトのreadメソッドにより動画のフレームを切り出す。そして、切り出した画像のリサイズを行い、グレイスケール変換を行う。得られたグレイスケール画像に対してHaar-like分類器のdetectMultiScaleメソッドを適用する。このメソッドは、対象とする物体が識別されると矩形の左上頂点座標および辺長を返す。この情報を利用して画像上に矩形を描画し、JPEGを返す。

このようにして得られる顔認識処理を次のようにWebアプリケーションとして用いる。

# main.py

import cv2
from flask import Flask, render_template, Response
from camera import Video

app = Flask(__name__)

@app.route("/stream")
def stream():
    return render_template("stream.html")

def gen(camera):
    while True:
        frame = camera.get_frame()

        if frame is not None:
            yield (b"--frame\r\n"
                b"Content-Type: image/jpeg\r\n\r\n" + frame.tobytes() + b"\r\n")
        else:
            print("frame is none")

@app.route("/video_feed")
def video_feed():
    return Response(gen(Camera()),
            mimetype="multipart/x-mixed-replace; boundary=frame")


if __name__ == "__main__":
    app.debug = True
    app.run(host="0.0.0.0", port=5000)

PythonのWebフレームワークであるFlaskを用いて、画像が埋め込まれたHTMLを返す。http://localhost:5000/streamにリクエストがあるとstream.htmlをレンダーする。stream.htmlを以下に示す。

<!-- stream.html -->

<html>
  <head>
    <title>Raspberry Pi Camera Streaming</title>
  </head>
  <body>
    <h1>Flask Streaming Test</h1>
    <img style="display: block; margin:25px auto 25px auto; padding-bottom:10px; padding-top:10px;" src="{{ url_for('video_feed') }}">
  </body>
</html>

stream.htmlでは、img要素のsrcにvideo_feedが指定されており、main.pyのvideo_feed()を呼ぶ。video_feed()ではgen()関数がコールバックされ顔認識処理後のフレームを受け取りレスポンスとして返す。

このHTMLをAndroid側に埋め込み利用する。

Android側

Android Studioが提供するWebViewを用いて、上記のWebページをアプリ中に表示し、接続不良が起こった場合、下図にあるように、左下のオレンジ色のRELOADボタンを押せば再接続可能となる。

図2: Androidアプリケーション

以下にAndroidアプリケーションのソースコードを示す。

// MainActivity.java

package com.example.rasp_camera;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.View;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.CompoundButton;
public class MainActivity extends AppCompatActivity {
   private Button reloadButton;
   private WebView webView;
@Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       reloadButton = new Button(this);
       reloadButton = (Button) findViewById(R.id.button_reload);
       reloadButton.setOnClickListener(new View.OnClickListener(){
           public void onClick(View arg0){
               webView.reload();
               Log.d("Log:", "WebView is reloaded.");
} });
       webView = new WebView(this);
       webView = (WebView) findViewById(R.id.webview);
       webView.loadUrl("http://192.168.11.18:5000/stream");
} }
Yutayt さんはまだ自己紹介を入力されていません...