aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFranklin Wei <franklin@rockbox.org>2019-11-09 18:29:29 -0500
committerFranklin Wei <franklin@rockbox.org>2019-11-09 18:29:29 -0500
commit3760535d8b9e842ca46e3e1a53b0953032f741b1 (patch)
tree72f17249574cd65cdc7977eb4ccc47f30ac6b488
parentc0d006cffcfd7b2966850a7905533a84123de6fa (diff)
downloadregentester-3760535d8b9e842ca46e3e1a53b0953032f741b1.zip
regentester-3760535d8b9e842ca46e3e1a53b0953032f741b1.tar.gz
regentester-3760535d8b9e842ca46e3e1a53b0953032f741b1.tar.bz2
regentester-3760535d8b9e842ca46e3e1a53b0953032f741b1.tar.xz
Fixup
-rwxr-xr-xtools/tachometer.py117
1 files changed, 83 insertions, 34 deletions
diff --git a/tools/tachometer.py b/tools/tachometer.py
index 9ae5ab5..84a5878 100755
--- a/tools/tachometer.py
+++ b/tools/tachometer.py
@@ -1,8 +1,37 @@
#!/usr/bin/env python
"""
-A video tachometer. Run as ./tachometer.py and select the target
-region on the wheel.
+
+A video tachometer. Point a camera at a rotating shaft with a
+reflective strip, and measure the rotational speed.
+
+Run as ./tachometer.py and select the region of interest on a shaft.
+
+How it works:
+
+ Camera image
+ |
+ V
+ region of interest
+ |
+ V
+ average brightness
+ |
+ v
+ DFT
+ |
+ V
+ bandpass filter
+(for expected RPM range)
+ |
+ V
+ peak finder
+ |
+ V
+ RPM measurement
+
+Cubic interpolation of the FFT results is used to increase resolution.
+
"""
import cv2
@@ -11,33 +40,37 @@ import matplotlib.pyplot as plt
import time
from scipy.interpolate import interp1d
+# adjust to choose camera
CAMERA_IDX=2
-# samples to keep
+# number of frames
WINDOW=100
-WIDTH=320
-HEIGHT=180
+# camera resolution (smaller is better)
+WIDTH=640
+HEIGHT=480
# number of stripes on wheel
-TICS_PER_REV = 6
+TICS_PER_REV = 2
-#desired RPM range
-RPM_LOW = 6
+# desired RPM range
+RPM_LOW = 12
RPM_HIGH = 180
+# RPMs below this get mapped to zero
ZERO_RPM_THRES = 10
+
def update_plot(ax, fig, x, y):
ax.lines[0].set_data( x, y ) # set plot data
ax.relim() # recompute the data limits
ax.set_ylim(ymin=0, ymax=max(y) * 1.5)
ax.autoscale_view() # automatic axis scaling
- fig.canvas.flush_events() # update the plot and take care of window eve
+ fig.canvas.flush_events() # update the plot and take care of window eve
def show_webcam(mirror=False):
cam = cv2.VideoCapture(CAMERA_IDX)
-
+
cam.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT)
cam.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH)
@@ -63,25 +96,26 @@ def getFFT(data, rate):
freq = np.fft.fftfreq(len(fft), 1.0 / rate)
return freq[:int(len(freq) / 2)], fft[:int(len(fft) / 2)]
-def getRPM(x_data, y_data):
+def getRPM(x_data, y_data, ax, fig):
delta_t = x_data[-1] - x_data[0]
nsamples = len(x_data)
- supersample = 2
+ supersample = 3
f_t = interp1d([x-x_data[0] for x in x_data], y_data, kind='cubic')
eval_x = np.linspace(0, delta_t, nsamples*supersample)
eval_y = f_t(eval_x)
rate = nsamples * supersample / delta_t
freq, fft = getFFT(eval_y, rate)
-
# get low and high freq bins
lo = get_first_higher(freq, RPM_LOW / 60 * TICS_PER_REV)
hi = get_first_higher(freq, RPM_HIGH / 60 * TICS_PER_REV)
- #lo_freq = freq[lo]
- #hi_freq = freq[hi - 1]
-
- #print("RPM range:", freq[lo]*60 / TICS_PER_REV, freq[hi - 1]*60 / TICS_PER_REV)
-
+
+ actual_rate = nsamples / delta_t
+ #print("Actual FPS: ", actual_rate, "Nyquist max RPM: ", actual_rate / TICS_PER_REV / 2 * 60)
+ #print("Window length: ", delta_t)
+ #print("Filtered RPM range:", freq[lo]*60 / TICS_PER_REV, freq[hi - 1]*60 / TICS_PER_REV)
+ #print("Maximum RPM range:", freq[0] * 60 / TICS_PER_REV, freq[-1] * 60 / TICS_PER_REV)
+
# bandpass filter
freq = freq[lo:hi]
fft = fft[lo:hi]
@@ -90,22 +124,17 @@ def getRPM(x_data, y_data):
freq = 60*freq / TICS_PER_REV
lo_freq = freq[0]
hi_freq = freq[-1]
-
+
freq_interp = interp1d(freq, fft, kind='cubic')
interp_res = 10
-
+
freqs = np.linspace(lo_freq, hi_freq, len(freq) * interp_res)
-
+
interpolated_freqs = freq_interp(freqs)
-
- #ax.lines[0].set_data( freqs, interpolated_freqs ) # set plot data
-
- #print(freq[2:], fft[2:])
- #ax.relim() # recompute the data limits
- #ax.autoscale_view() # automatic axis scaling
- #fig.canvas.flush_events() # update the plot and take care of window eve
- # print(eval_x, eval_y)
+
+ update_plot(ax, fig, freqs, interpolated_freqs)
+
peakRPM = np.where(interpolated_freqs == np.amax(interpolated_freqs))
peakRPM=freqs[peakRPM[0]][0]
print(peakRPM)
@@ -118,19 +147,37 @@ def main():
cam.read()
cam.read()
- roi=getROI(cam)
- x,y,w,h = roi
+ roi = getROI(cam)
+ x, y, w, h = roi
+
plt.ion()
+
fig = plt.figure()
ax = plt.subplot(1,1,1)
ax.set_xlabel('time')
ax.set_ylabel('measured rpm')
+ fig_fft = plt.figure()
+ ax_fft = plt.subplot(1,1,1)
+ ax_fft.set_xlabel('frequency (rpm)')
+ ax_fft.set_ylabel('amplitude')
+
+ fig_brt = plt.figure()
+ ax_brt = plt.subplot(1,1,1)
+ ax_brt.set_xlabel('time')
+ ax_brt.set_ylabel('region brightness')
+
x_data, y_data = [], []
rpm_times, rpm_data = [], []
- ax.plot(x_data , y_data , 'ko-' , markersize = 10 ) # add an empty line to the plot
+ ax.plot(x_data , y_data , ',-k' , markersize = 10 ) # add an empty line to the plot
fig.show() # show the window (figure will be in foreground, but the user may move it to background)
+ ax_fft.plot(x_data, y_data, ',-k')
+ fig_fft.show()
+
+ ax_brt.plot(x_data, y_data, ',-k')
+ fig_brt.show()
+
while True:
# sample.
ret_val, img = cam.read()
@@ -148,8 +195,10 @@ def main():
x_data.pop(0)
y_data.pop(0)
+ update_plot(ax_brt, fig_brt, x_data, y_data)
+
if len(x_data) >= WINDOW:
- rpm = getRPM(x_data, y_data)
+ rpm = getRPM(x_data, y_data, ax_fft, fig_fft)
rpm_times.append(t)
rpm_data.append(rpm)
if(len(rpm_times) > WINDOW):
@@ -157,7 +206,7 @@ def main():
rpm_data.pop(0)
update_plot(ax, fig, rpm_times, rpm_data)
- if cv2.waitKey(1) == 27:
+ if cv2.waitKey(1) == 27:
break # esc to quit
cv2.destroyAllWindows()