2016年11月14日月曜日

【Raspberry pi 2】 USB監視カメラを作ってみた

Raspberry pi zeroという、$5のラズパイが発表されてもう1年ぐらい経つと記憶していますが、海外ではすでに入手可能だけど日本では正規流通品はまだなんですよね.もちろん秋月にもまだありません.理由は知らないけど版元が日本発売にSTOPをかけているんだとかいう噂です.本当に$5だと輸入商社のマージンが薄すぎるので商社がつかないのかな.早く売ってくれ~

さて、今わたしが持っているのは、Raspberry pi 2です.

ラズパイ2とUSBカメラを使って、簡単な監視カメラを作ったのでご報告.

写真のように、Logicoolが昔売ってたUSBカメラを2つ使い、ラズパイ2に挿します.ラズパイ2へのログインはネット経由でtelnetでリモートログインします.KBD・マウスはなし.
こういったコンフィギュレーションで、USBカメラ2個の画像をラズパイ2に処理させます.

監視カメラといっても、ビデオを常時録画するほど立派なものではなく、毎秒ぐらいの頻度で撮影した画像に変化があったらjpg画像として保存するだけです.
処理速度が決して速くはないラズパイ2ですが、MPEG処理するわけでもないので、毎秒数コマ程度の簡単な画像処理なら出来るんじゃないかなぁと思いつつ、作業に突入するのであります.

なお、以下の情報が間違っていて読者がドハマリしてもわたしは知りませんので素直に死んでくださいませ~.

-----
ラズパイ2のOSは、Rasbianです.バージョンはこれを書いてる時点で最新のもの.Jessyとかいうやつです.

画像処理関連のライブラリというとわたしはopenCVばっかしな人なので今回もopenCVを使います.Rasbianをインストールしただけですと、openCVはインストールされていませんでしたので、手動でインストールします.
apt-cache search opencv
これを打つと、opencv関連ライブラリがゾロゾロと表示されまして、どれをインストールするべきなのか、さっぱりわかりません.
これかなぁと予想して、下記をインストールしたら、gppでコンパイル出来るようになりましたのでこれで良いのでしょう.
apt-get install libopencv-dev
もしも参照先が無いというエラーが出るときは、
apt-get update
をやってみましょう.

近頃のopenCVは、C言語APIだけでなく、C++やPythonでも呼び出せるようですね.わたしはPython未体験者なのでPythonは問題外として、経験が浅いC++でやってみることにしました.C++のblack-boxさにいつも苦労するわけですが.

結果を記しますと、USBカメラを2ヶ接続して、毎秒の画像処理およびjpg保存は問題なく動きました.たぶん3ヶ接続でも動く気がします.

ラズパイというと、カメラモジュールが定番商品ですが、値段が高くないですか? USBカメラならクソの様に安価なのが売られているので、ホビー用途には便利かもしれません.


-----
C++ってよくわからんのでグダグダなソースコードかと思いますが、以下のソースで動きました.

まずはコンパイルのコマンドです.
g++ monicam.cpp `pkg-config --cflags opencv` `pkg-config --libs opencv` -o monicam

つぎは動かすコマンドです.
       monicam 0 10 w             USBカメラ0   window表示あり
monicam 0 10 n             USBカメラ0   windowなし
       monicam 1 10 w             USBカメラ1   window表示あり
monicam 1 10 n             USBカメラ1   windowなし
       monicam 2 10 w             USBカメラ2   window表示あり
monicam 2 10 n             USBカメラ2   windowなし
・カメラ0とカメラ1の認識は、USBカメラを2台挿せばどちらかが0でどちらかが1としてopenCVに認識されます.USBカメラを3台挿せば、2が有効になるものと思います.なお、カレントフォルダにjpgファイルが大量発生しますので、カメラ0を起動するフォルダとカメラ1を起動するフォルダを別にしておくのがよいです.
・「windowなし」というのは、撮影画像を逐次モニタに表示しない動かし方です.telnetで遠隔地からログインして監視するときにはX-windowを使わないようにすればよいかと思うためです.
・「10」という数字は、検出感度の設定で、1高感度~255不感症までの範囲です.10ぐらいでいいかと思いました.

以下はソースです.肝のところをチビチビと説明します.

・cap.set(CV_CAP_PROP_FPS, 1);
USBカメラにFPS=1を設定しています.一秒間に1フレームという設定なのですが、1を設定してもFPS=2にしかなってないようです.USBカメラがFPS=1を受け付けないようです.

・cap >> frame;
USBカメラからフレーム画像を読む.この一文だけが孤立しているのには理由があります.while loopで毎回読み込む必要があるためです.さもないと、USBバッファに蓄積された古い画像が取り出されてしまい、最新画像を取り出せないからです.

・if(sec==ltime->tm_sec) continue;
USBカメラにFPS=1と設定することで毎秒チェック状態にできるのかと期待したがそうではなかったので、システムタイマで1秒経過をチェックすることにしました.

・画像処理の部分
640x480→320x240にフレームサイズを縮小 =frame2
一つ前のフレームとの差分を計算 =sub
白黒画像に変換 =gray
2値画像に変換 =bin
2値画像を黄色に変換 =red        あとで利用
binの画素平均値を計算 =ave       動きがあれば数字が大きくなる、後で利用
frame2を次回のために保存する =prevframe



============monicam.cppここから===========
#include "cv.h"
#include "highgui.h"
#include <iostream>
#include <ctime>

//USBカメラを640x480で動かします
#define WIDTH 640
#define HEIGHT 480

using namespace cv;

long matave( Mat x ) //フレーム全要素の平均
{
  int i,k;
  long sum=0;
  for(i=0;i<x.rows;i++)
    for(k=0;k<x.cols;k++) sum += (int)x.at<unsigned char>(i,k);
  return sum/x.total();
}

void rgb2red( Mat x ) //カラーフレームを特定の色だけにする
{
  int i,k;
  for(i=0;i<x.rows;i++)
    for(k=0;k<x.cols;k++)
      {
        x.at<Vec3b>(i,k)[0]=0; //黄色にする
      }
  return;
}

int main(int argc, char *argv[])
{
  int cam,th;
  char d;
  if(argc!=4)
    {
      std::cout << "usage: monicam02 0 10 w|n ---> cam0 th=10 [1-255] window or n
o window\n";
      exit(1);
    }
  cam =(int)atof(argv[1]); //USBカメラ番号
  th  =(int)atof(argv[2]); //動き検出感度
  d = argv[3][0]; //windowオンオフ

  if(th<1) exit(1);
  if(d!='w' && d!='n') exit(1);

  VideoCapture cap(cam); //USBカメラopen&設定
  if(!cap.isOpened()) return -1;
  cap.set(CV_CAP_PROP_FRAME_WIDTH, WIDTH);
  cap.set(CV_CAP_PROP_FRAME_HEIGHT, HEIGHT);
  cap.set(CV_CAP_PROP_FPS, 1); //FPS=1にしたいが1にならない
  cap.set(CV_CAP_PROP_BRIGHTNESS, 0.7); //少し明るく

  Mat prevframe;
  int sec;
  if(d=='w') namedWindow("Capture", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO); //window作り

  while(1) {
    Mat frame,frame2,sub,red,gray,bin;
    time_t ctime;
    struct tm *ltime;
    long ave;

    cap >> frame;  //キャプチャ

    time(&ctime);
    ltime = localtime(&ctime);
    if(sec==ltime->tm_sec) continue; //1秒経過なら先へ進む
    sec=ltime->tm_sec;
    string t = asctime(ltime);
    t.erase(t.length()-1,2);  //今の時刻の文字列をつくる

    //画像処理ここから
    resize(frame,frame2,Size(WIDTH/2,HEIGHT/2),INTER_LINEAR);
    sub=abs(frame2 - prevframe);
    cvtColor(sub, gray, CV_RGB2GRAY);
    threshold(gray, bin, 50, 255, THRESH_BINARY);
    cvtColor(gray, red, CV_GRAY2BGR);
    rgb2red(red);
    ave = matave(bin);
    prevframe=frame2.clone();
    //画像処理ここまで

    if(ave>th) frame2 = frame2/3 + red; //動きがあれば画像を暗くして動いた場所の画像マーカーとして黄色を加算する

    //画像に時刻を表示する
    putText(frame,  t, Point(0,HEIGHT-3), FONT_HERSHEY_PLAIN, 1.5, Scalar(0,0,0), 4, CV_AA);
    putText(frame,  t, Point(0,HEIGHT-3), FONT_HERSHEY_PLAIN, 1.5, Scalar(255,128,255), 2, CV_AA);
    putText(frame2, t, Point(0,HEIGHT/2-3), FONT_HERSHEY_PLAIN, 1.0, Scalar(0,0,0), 3, CV_AA);
    putText(frame2, t, Point(0,HEIGHT/2-3), FONT_HERSHEY_PLAIN, 1.0, Scalar(255,128,255), 2, CV_AA);

    if(ave>th) imwrite(t+".jpg", frame); //動きがあれば640x480をjpg保存する
    if(d=='w')
    {
      imshow("Capture", frame2); //320x240をwindow表示する
      waitKey(30); //これ必須
    }
  }
}
============monicam.cppここまで===========

0 件のコメント:

コメントを投稿