<!--親の顔より見た光景-->

日々の発見を残していきます。

ステレオカメラとIRステレオカメラによるヘビの運動量算出【4】

通常ステレオカメラとIRステレオカメラを電気スタンドに固定する。

1. プレートにカメラを固定
1. 土台に穴を空ける

f:id:akagi13213:20190331190500p:plain
図1 カメラの土台に穴をあける

2. ステレオカメラを固定する

f:id:akagi13213:20190331190626p:plain
図2 カメラを固定

3. マイコンを固定する

f:id:akagi13213:20190331190902p:plain
図3 マイコンを固定

4. 電気スタンドに固定
電気スタンドのソケット以降を外し、プレートを固定する。

f:id:akagi13213:20190331191018p:plainf:id:akagi13213:20190331192433p:plain
図4 プレートを電気スタンドに固定

ステレオカメラとIRステレオカメラによるヘビの運動量算出【3】

2つのIR カメラをrasberry pi に繋ぐ。

1. 準備

  • rasberry pi用マルチカメラアダプタ

f:id:akagi13213:20190321224936p:plain
図1 rasberry pi 用マルチカメラアダプタ

  • IR カメラ x 2

f:id:akagi13213:20190318002506p:plain
図2 赤外線カメラ

2. 接続

写真のようにフレキケーブルを接続する。GPIOピンの位置はこの通りにしないと壊れると思う。

f:id:akagi13213:20190321225615p:plainf:id:akagi13213:20190321225737p:plainf:id:akagi13213:20190321225812p:plain


マルチカメラアダプタはABCDの4つ口があり、最大4段まで拡張が可能で、1段積み用の設定、2段積み用・・と設定がある。
1段しか使わない場合は、DIPスイッチの1, 5のみONにする。

f:id:akagi13213:20190322001729p:plain
図3 1段積み用のDIPスイッチ

3. 動作確認

このマルチカメラアダプタにはライブラリはない(たぶん)ので泥臭くコードを書く必要がある。

1. コード

#!/bin/python

import RPi.GPIO as gp
import os
import time

gp.setwarnings(False)
gp.setmode(gp.BOARD)

# Setup the stack layer 1 board
gp.setup(7, gp.OUT)
gp.setup(11, gp.OUT)
gp.setup(12, gp.OUT)

def main():
  gp.output(7, False)
  gp.output(11, False)
  gp.output(12, True)
  capture(1)
  gp.output(7, False)
  gp.output(11, True)
  gp.output(12, False)
  capture(3)

def capture(cam):
  cmd = "raspistill -o capture_%d.jpg" % cam
  os.system(cmd)

if __name__ == "__main__":
  time.sleep(5)
  main()
  gp.output(7, False)
  gp.output(11, False)
  gp.output(12, True)

●コードの解説

  gp.output(7, False)
  gp.output(11, False)
  gp.output(12, True)
  capture(1)
  gp.output(7, False)
  gp.output(11, True)
  gp.output(12, False)
  capture(3)

CAM A をキャプチャする場合、ピン出力を以下にする。

ピン番号
7 OFF
11 OFF
12 ON

CAM C をキャプチャする場合、ピン出力を以下にする。

ピン番号
7 OFF
11 ON
12 OFF

その他、マルチカメラアダプタの詳細設定は以下を参照。
http://www.arducam.com/downloads/RaspCAM/RaspberryPi_Multi_Camera_Adapter_Module_UG.pdf


2. 赤外線ライトの強度調整
添付画像の赤枠箇所をマイナスドライバーで回すと、赤外線ライトの強度を変更できる。

f:id:akagi13213:20190322002634p:plain
図4 可変抵抗でライト強度を調整

3. カメラピント調整
図の赤枠箇所を回すと、ピント調整できる。

f:id:akagi13213:20190322002813p:plain
図5 ピント調整

4. 結果
左右でこんな画が撮れた。

f:id:akagi13213:20190322002942p:plainf:id:akagi13213:20190322003012p:plain
図6 IRカメラ画

ステレオカメラとIRステレオカメラによるヘビの運動量算出【2】

ステレオIR カメラを作成する。

1. 準備

  • 木材

f:id:akagi13213:20190322200336p:plain
図1 木材

  • IR カメラ x 2

f:id:akagi13213:20190318002506p:plain
図2 赤外線カメラ

  • 電動ドリル

f:id:akagi13213:20190322200847p:plain
図3 電動ドリル

  • ノコギリ

f:id:akagi13213:20190322201007p:plain
図4 ノコギリ

2. 作成

電動ドリルでネジ穴を空ける。

f:id:akagi13213:20190318002402p:plain
図5 ネジ穴をあける

カメラを固定する

f:id:akagi13213:20190318003400p:plain
図6 カメラを固定


余分な部分をノコギリで切って、やすりで角を削ったら完成。

f:id:akagi13213:20190318003455p:plain
図7 完成

ステレオカメラとIRステレオカメラによるヘビの運動量算出【1】

過去に挑戦した、ペットのヘビを監視するシステムを進化させることにした。
参考:ヘビの安否確認システム - <!--親の顔より見た光景-->

前回のシステムは、通常のUSBカメラを用いた撮影なので
夜間の行動を見ることができなかった(夜行性なのに。

そこで今回は、IRカメラを導入 & 複眼カメラにして1日の運動量を計測することにした。

今回は準備編。

---------------------------

●背景
ペットのヘビを監視するシステムを構築し、1日あたりの運動量を測りたい。

●原理
通常のステレオカメラとIRステレオカメラを作成し、被写体の深度を測る。
昼間は通常のカメラ、夜間はIRカメラを使用する。
深度画像と物体検出アルゴリズムを用いることで、ヘビの運動量を計算することが可能なはず。

こんなイメージで観察したい。

f:id:akagi13213:20190317191817p:plain
図1 作りたいもののイメージ

●実験準備
ざっとコレだけ必要

f:id:akagi13213:20190317192507j:plain
図2 システム構築のために用意したもの

マイコン周り

モノ 値段 参考リンク
Rasberry pi 3 B+ ¥5100 Amazon
SDカード 32GB ¥1350 Amazon
ACアダプタ 3.0A ¥1580 Amazon
通常カメラ(CSI インターフェース) ¥1699 Amazon
赤外線カメラ(CSI インターフェース) ¥2899 Amazon
Raspberry Pi用マルチカメラアダプタ(CSI 拡張基盤) ¥6171 スイッチサイエンス


●機構周り(ホームセンターで全て購入)

モノ 値段 参考リンク
アームライト(電球別売り) ¥2894 Amazon
充電式電動ドリルドライバー ¥3218 ビバホームオンラインショップ
精密ドライバー ¥321 ホームセンター
カメラプレート用木材 ¥116 ホームセンター
マイコン土台用木材 ¥354 ホームセンター
のこぎり ¥1002 ホームセンター
紙やすり #240 ¥84 ホームセンター
M3 スペーサ * 4 ¥578 ホームセンター
M3 ねじ+ナット+ワッシャー ¥159 ホームセンター
M2 ねじ+ナット+ワッシャー ¥159 ホームセンター
定規 ¥0 どこでも

電動ドリルが意外と安かったので、
カメラのプレートと、マイコン用の土台を木材で作ることにした。
あと、のこぎりがカッコいい

f:id:akagi13213:20190317200459j:plain
図3 のこぎり

●ソフトウェア

モノ 値段 参考リンク
fusion 360(CAD ソフト)無償体験版 ¥0 ホームページ

図4のような感じで設計し、IR ステレオカメラの完成イメージを掴む。
(手間はかかるが、カメラ位置微調整用のネジ穴数を増やしたほうが良いと気づけた。)

f:id:akagi13213:20190317201542p:plain
図4 CAD ソフトでIRカメラを設計

●合計
¥27684
(めっちゃかかった……)

【C++】離散フーリエ級数展開

理解するためにすべて手書きで書いた。
乱数で作ったグラフを、
三角関数の足し合わせのみでキレイに再現できている。
f:id:akagi13213:20190203015531p:plain

(あと一息で離散フーリエ変換ができそう)

#include "pch.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>

#define M_PI    3.14159265358979323846

int main(void)
{
    std::ifstream ifs( "./source.csv" );
    if ( ifs.fail() ) {
        std::cout << "file read error..." << std::endl;
        return -1;
    }

    std::string str;
    std::vector<double> f;
    while ( getline( ifs, str ) ) {
        f.push_back(std::stod( str ));
    }

    double a0 = 0;
    std::vector<double> an;
    std::vector<double> bn;

    double sum = 0;
    int N = f.size();
    for ( int x = 0; x < N; x++ ) {
        sum += f[x];
    }
    a0 = sum / N;

    for ( int n = 1; n < N; n++ ) {
        double a_sum = 0;
        double b_sum = 0;
        
        for ( int x = 0; x < N; x++ ) {
            a_sum += f[x] * cos( 2 * M_PI * n * x  / N );
            b_sum += f[x] * sin( 2 * M_PI * n * x / N );
        }
        
        if ( n == ( N / 2 - 1 ) ) {
            an.push_back( a_sum / N );
            bn.push_back( b_sum / N );
        }
        else {
            an.push_back( 2 * a_sum / N );
            bn.push_back( 2 * b_sum / N );
        }
    }
    
    std::vector<double> fx_fourier;
    for (int x = 0; x < N; x++) {
        double a_sum = 0;
        double b_sum = 0;
        for ( int n = 1; n < N; n++ ) {
            a_sum += an[n - 1] * cos( 2 * M_PI * n * x / N );
            b_sum += bn[n - 1] * sin( 2 * M_PI * n * x / N );
        }
        fx_fourier.push_back( a0 + a_sum + b_sum );
    }

    std::ofstream dist;
    dist.open("dist.csv", std::ios::out);
    for ( int i = 0; i < fx_fourier.size(); i++ ) {
        dist << fx_fourier[i] << std::endl;
    }
    
    return 0;
}

【画像処理】2次微分フィルタ

二次微分フィルタを自作した。

def edge_filter_2(file):
    image = cv2.imread(file, cv2.IMREAD_GRAYSCALE)
    (height, width) = image.shape[:2]

    edge_width = image.copy()
    edge_height = image.copy()
    edge_lap = image.copy()
    for i in range(1, height-1):
        for j in range(1, width-1):
            left = image[i, j-1]
            right = image[i, j+1]
            top = image[i-1, j]
            bottom = image[i+1, j]
            center = -image[i, j] * 2

            edge_width[i, j] = abs(left + right + center)
            edge_height[i, j] = abs(top + bottom + center)
            edge_lap[i, j] = abs(top + bottom + left + right + center*2)
 
    cv2.imwrite('gray.png', image)
    cv2.imwrite('edge2_width.png', edge_width)
    cv2.imwrite('edge2_height.png', edge_height)
    cv2.imwrite('edge2_lap.png', edge_lap)

参考は以下。
画像処理フィルタの1次微分と2次微分の違い | ぱーくん plus idea

適用フィルタは以下。
横方向

0 0 0
1 -2 1
0 0 0

縦方向

0 1 0
0 -2 0
0 1 0

4近傍

0 1 0
1 -4 1
0 1 0

●横方向二次微分
f:id:akagi13213:20190120184119p:plain

●縦方向二次微分
f:id:akagi13213:20190120184130p:plain

●4近傍(ラプラシアンフィルタ)
f:id:akagi13213:20190120184140p:plain

★なぜ1次微分より2次微分のほうがエッジが目立つ?
f:id:akagi13213:20190120184749p:plain
左から、元画像・1次微分(横方向)・2次微分(横方向)

横方向微分の1次微分と2次微分の出力差を見てみる。
仮に、顔の輪郭にフィルタを適用するとして、
フィルタ適用前画素列を、以下とする

32 32 64 64 64 64 255 255

★★1次微分(横方向)

-1/2 0 1/2

このフィルタを適用すると(左端・右端は元画像のままとする)、

32 32 64 64 64 64 255 255

 ↓↓ 1次微分適用

32 16 16 0 0 95 95 255

★★2次微分(横方向)

1 -2 1

このフィルタを適用すると(左端・右端は元画像のままとする)、
※絶対値を画素値として採用

32 32 64 64 64 64 255 255

 ↓↓ 2次微分適用

32 32 -32 0 0 191 -191 255

1次微分より白成分が2倍になっている。

【画像処理】1次微分フィルタ(横、縦)

自作で微分フィルタを作った。

#!/usr/env/bin python
import cv2
import sys

args = sys.argv

def edge_filter(file):
    image = cv2.imread(file, cv2.IMREAD_GRAYSCALE)
    image = cv2.medianBlur(image, 3)
    (height, width) = image.shape[:2]

    edge_width = image.copy()
    edge_height = image.copy()
    for i in range(1, height-1):
        for j in range(1, width-1):
            left = image[i, j-1] / -2
            right = image[i, j+1] / 2
            top = image[i-1, j] / -2
            bottom = image[i+1, j] / 2

            edge_width[i, j] = abs(left + right)
            edge_height[i, j] = abs(top + bottom)

    cv2.imwrite('gray.png', image)
    cv2.imwrite('edge_width.png', edge_width)
    cv2.imwrite('edge_height.png', edge_height)

if __name__ == '__main__':
     edge_filter(args[1])

フィルタは以下を適用した。
ただし、画像の端には適用していない。(コード上の、range(1, height-1) と range(1, width-1))

●横方向微分フィルタ

0 0 0
-1/2 0 1/2
0 0 0

●縦方向微分フィルタ

0 -1/2 0
0 0 0
0 1/2 0

■元画像
f:id:akagi13213:20190120155743p:plain

■横方向微分画像
f:id:akagi13213:20190120155754p:plain

■縦方向微分画像
f:id:akagi13213:20190120155808p:plain

余計なエッジを消すために、メディアンフィルタを適用してから微分フィルタを適用する。

メディアンフィルタ 方向微分
f:id:akagi13213:20190120155836p:plain

メディアンフィルタ 方向微分
f:id:akagi13213:20190120155843p:plain

★★比較
f:id:akagi13213:20190120161812j:plain

縦方向微分は横線を抽出し、
横方向微分は縦線を抽出している。