Loading [MathJax]/extensions/tex2jax.js

2016年2月2日火曜日

振り子の動きを画像認識で検出してみる[Python]

前やった特定の色の物体の座標を検出の続きみたいなもんです.
振り子を作ってWebカメラを使って振り子の位置を検出して,グラフにしてみます.

まぁ今日のテストがイミフで,水曜日の第二外国語の勉強したくないだけですけど.

1.振り子

こんなの

文句はなしで行きましょう(笑).
暇つぶしクオリティなので重りはキャップを2つくっつけただけです.
糸も細くて変形しにくい針金などではなく,裁縫道具に入ってたやつです.

2.検出プログラム

こんな感じ.output.csvに時間,x座標,y座標が保存されます.

検出アルゴリズムとしては,HSV色空間にWebカメラの画像を変換し,緑色の成分のみをOpenCVのinRange関数を使って取り出しています.
なので以下のプログラムは「緑色の物体」の座標を認識しているので緑色のものがないところでやるようにしてください.

座標は緑色が一番たくさんあった行と列の要素番号をx,yの座標としてます.
まぁそのへんはプログラムを参照してください.
# -*- coding: utf-8 -*-
import numpy as np
import cv2
import time
cap = cv2.VideoCapture(0)
f = open("output.csv","w")
f.write("t,x,y\n")
start = time.time()
while(True):
# フレームをキャプチャする
ret, frame = cap.read()
# hsv色空間に変換
hsv = cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)
# 0 <= h <= 179 (色相) OpenCVではmax=179なのでR:0(180),G:60,B:120となる
# 0 <= s <= 255 (彩度) 黒や白の値が抽出されるときはこの閾値を大きくする
# 0 <= v <= 255 (明度) これが大きいと明るく,小さいと暗い
# ここでは緑色を抽出するので60±20を閾値とした
low_color = np.array([40, 75, 75])
upper_color = np.array([80, 255, 255])
# 色を抽出する
ex_img = cv2.inRange(hsv,low_color,upper_color)
# 縦,横方向の和の中で最も大きいインデックスを抽出
x = np.argmax(np.sum(ex_img,axis=0))
y = np.argmax(np.sum(ex_img,axis=1))
# 時間と座標の保存
t = time.time() - start
f.write(str(t)+","+str(x)+","+str(y)+"\n")
print(str(t)+","+str(x)+","+str(y)+"\n")
# 抽出した座標に丸を描く
cv2.circle(frame,(x,y),10,(0,255,0))
# 画面に表示する
cv2.imshow('frame',frame)
if t > 120:
break
key = cv2.waitKey(1) & 0xFF
# qが押された場合は終了する
if key == ord('q'):
break
# 後始末
cap.release()
cv2.destroyAllWindows()
f.close()
view raw DetectGreen.py hosted with ❤ by GitHub

3.プロット

データがoutput.csvに入ったら次はmatplotlibで可視化してみます.可視化プログラムはこんな感じです.
#coding:utf-8
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
# 最初のデータはWebカメラの調子が悪いため無視
data = pd.read_csv("output.csv")
data = data[50:]
# 計算しやすいように取り出す
t = data['t']
x = data['x']
y = data['y']
# x,yは平均(振動の中心)を0に持ってこさせる
x = x - np.average(x)
y = y - np.average(y)
# 時間はt[0]が0になるようにしとく
# (pandasを使うとt[0]という表現ができないのでこうしました)
t = t - np.min(t)
# 凡例とともに表示
plt.plot(t,x,label="x")
plt.plot(t,y,label="y")
plt.legend()
plt.show()
view raw PendulumPlot.py hosted with ❤ by GitHub
グラフはこんなんが出てくる.これ横からではなく,下から撮影したのでこうなってます.

x,y座標の振り幅の極大極小はそれなりに取れてますね.振り幅が小さくなるとノイズが目立ちますが.(まぁあとカメラの傾きとか重力の方向とかどうなってんのみたいな話は置いといて……)


今日は以上です.
振り子の長さは大体37cmだったので,T=2*pi*sqrt(l/g)で,周波数はおおよそ0.8[Hz]になっているはずで,本当はここでフーリエ変換で周波数を求めようと思ったのですが……これ一定間隔でサンプリングしてないのです.

numpyやscipyのフーリエ変換の関数はたぶんサンプリング間隔が一定という条件でやってる気がする……時間のデータを与えられないし.
複素数をPythonで扱ったりとかはできず,現状実装できる能力がないので近いうちにフーリエ変換を実装しなければ…….