Vor ungefähr fünf Jahren (ungefähr 2015), als ich mit einem tauben Bekannten zum Abendessen ging ** Manchmal kann ich das Berührungsgeräusch von Suica (der Transport-IC-Karte) nicht hören und mache mir Sorgen, ob ich es berühren könnte ** Ich habe die Geschichte gehört. Zu dieser Zeit wollte ich gerade einen Raspberry Pi in Akihabara kaufen und fragte mich, ob ich etwas dagegen tun könnte. ** Kann ich eine Maschine bauen, die die LED aufleuchtet, wenn ich den Touch-Sound von Suica (PASMO oder ICOCA, was auch immer) aufnehme? ** ** ** Ich dachte darüber nach und versuchte es zu schaffen.
Es ist gut, sich daran zu erinnern, aber da die Ausrüstung (Verkabelung) bereits zerlegt wurde, frage ich mich, wie ich es erklärt habe. Übrigens, obwohl ich mich zum Zeitpunkt des Schreibens dieses Artikels in einer solchen Situation befand, habe ich ihn für weitere zwei Jahre verlassen, bis ich ihn veröffentlicht habe ...
In der Abbildung oben ist auch ein USB-Strom- / Spannungsprüfer angeschlossen. Ich denke, dies war im Standby-Modus (als das Erkennungsprogramm nicht ausgeführt wurde), aber da die USB-Stromversorgung etwa 5 V beträgt, arbeitet sie mit etwa 1,4 W.
Wenn Sie sich das Bild der Platine früher genau ansehen, gibt es einen Widerstand, der jedoch den Strom der 7-Segment-LED begrenzt. Mit Blick auf das Datenblatt der 7-Segment-LED,
Also habe ich es über den 220Ω Widerstand, den ich zufällig hatte, mit dem GPIO von RPi verbunden. Informationen zu GPIO finden Sie beispielsweise auf der folgenden Seite. EIN / AUS kann durch Befehle und Programme gesteuert werden.
[^ 1]: Es gibt eine 16-mA-Grenze für den Strom, der durch den GPIO-Pin des Raspberry Pi geleitet werden kann. Daher müssen diese Bedingung sowie die Teile erfüllt sein.
Der GPIO von RPi1 beträgt übrigens 3,3 V. Wenn Sie also einen 220 Ω-Widerstand verwenden, fließt dieser etwa 6 mA zur LED [^ 2]?
[^ 2]: Es gibt eine Grenze für den Gesamtstrom, der durch den GPIO-Pin des Raspberry Pi geleitet werden kann, bis zu 50 mA. In diesem Fall sollte dies jedoch in Ordnung sein, da er 42 mA beträgt, selbst wenn alle 7 Segmente leuchten.
Wenn Sie sich ein (beleuchtetes) Segment ansehen, sollten Sie eine solche Schaltung sehen. Tatsächlich scheinen die sieben Segmente parallel geschaltet zu sein, wie das Experiment der Miniaturbirne der Wissenschaft. Es sollte jedoch beachtet werden, dass die + Seite tatsächlich nur im beleuchteten Segment 3,3 V beträgt und das beleuchtete Segment auf der + Seite 0 V beträgt. Das Aus-Segment hat sowohl auf der + als auch auf der Seite 0 V, sodass kein Strom fließt.
Danach schreiben Sie ein Programm, das auf Python auf Raspbian läuft. Breit geteilt
Sie müssen einen Treffer machen.
Ein über einen USB-Audiokonverter angeschlossenes Mikrofon kann als ALSA-Gerät behandelt werden. Daher habe ich es mit dem Modul alsaaudio geschrieben. Ich wollte auch zum Debuggen aus der WAV-Datei lesen, also habe ich sie so geschrieben, dass beide unterstützt werden.
Eine Sache, die man bei Raspberry Pi beachten sollte, ist, dass das Schreiben eines schlechten Programms schwer ist und nicht geholfen werden kann. Die Verarbeitung, die auf einem PC sofort abgeschlossen werden kann, ist eine schwierige Aufgabe für RPi (insbesondere, wenn Sie wie diese in Echtzeit arbeiten möchten). Während FFT eine feste Anzahl von Bildern (2 Quadrate) verarbeiten muss, kann der Mikrofoneingang nicht immer eine feste Anzahl von Bildern lesen. Aus diesem Grund gibt es einen Prozess wie das Erstellen eines Ringpuffers, das Kombinieren der gelesenen Daten und das Ausschneiden auf eine bestimmte Länge für FFT. Wenn ich es geschrieben hätte, indem ich Listen kombiniert und geschnitten hätte, ohne an irgendetwas zu denken, wäre es zu schwer gewesen, um in Echtzeit zu arbeiten ...
pcmmod.py
# -*- coding: utf-8 -*-
import numpy
import alsaaudio
import wave
import collections
FrameArrayTuple = collections.namedtuple(
"FrameArrayTuple",
["array", "nframes_read"])
# Based on https://scimusing.wordpress.com/2013/10/25/ring-buffers-in-pythonnumpy/
class RingBuffer:
"A 1D ring buffer using numpy arrays"
def __init__(self, length):
pow2 = int(numpy.ceil(numpy.log2(length)))
self.length = 2 ** pow2
self.data = numpy.zeros(self.length, dtype='float64')
self.index_top = 0
self.index_bottom = 0
def extend(self, x):
"adds array x to ring buffer"
if x.size > 0:
x_index = (self.index_bottom + numpy.arange(x.size)) & (self.length-1)
self.data[x_index] = x
self.index_bottom = x_index[-1] + 1
def get(self, n=None):
"Returns the first-in-first-out data in the ring buffer"
idx = (self.index_top + numpy.arange(min(n, self.count()) if n is not None else self.count())) & (self.length-1)
self.index_top = idx[-1] + 1
return self.data[idx]
def count(self):
c = (self.index_bottom - self.index_top + self.length) & (self.length-1)
return c
class PCMInputStream:
def __init__(self, maxNumFrame):
#Kann nicht unabhängig instanziiert werden, kann aber vom Vererbungsziel aus aufgerufen werden
self.maxNumFrame = maxNumFrame
self.op_array = self.getFramesToArrayOperator()
def readFrames(self):
raise NotImplementedError("readFrames() must be implemented")
def readFrameArray(self):
frames = self.readFrames()
return self.op_array(frames)
def getNumChannels(self):
raise NotImplementedError("getNumChannels() must be implemented")
def getFrameRate(self):
raise NotImplementedError("getFrameRate() must be implemented")
def getSampleWidthInBytes(self):
raise NotImplementedError("getSampleWidthInBytes() must be implemented")
def getFramesToArrayOperator(self):
sw = self.getSampleWidthInBytes()
if sw == 1:
fmt = "uint8"
shift_amp = 128.0
max_amp = 128.0
elif sw == 2:
fmt = "int16"
shift_amp = 0.0
max_amp = 32768.0
else:
raise ValueError("getSampleWidthInBytes() must be return 1 or 2")
return
return (lambda frames: (numpy.frombuffer(frames, dtype=fmt) - shift_amp) / max_amp)
def getFrameArrayIterator(self):
#Versuchen Sie immer, numMaxFrame gleichzeitig zurückzugeben.
nframes_read = 0
num_channels = self.getNumChannels()
arr_buffer = RingBuffer(self.maxNumFrame * 10)
l = -1
while l != 0:
if arr_buffer.count() >= self.maxNumFrame * num_channels:
nframes_read += self.maxNumFrame
#In Kanäle teilen
arr_channels = arr_buffer.get(self.maxNumFrame * num_channels).reshape(self.maxNumFrame, num_channels).T
yield FrameArrayTuple(arr_channels, nframes_read)
else:
arr = self.readFrameArray()
l = arr.shape[0]
assert l % num_channels == 0
#Kombinieren Sie den gelesenen Inhalt
arr_buffer.extend(arr)
#Gibt die letzten Daten zurück
arr = arr_buffer.get()
nframes_read += arr.shape[0] / num_channels
arr_channels = arr.reshape(arr.shape[0] / num_channels, num_channels).T
yield FrameArrayTuple(arr_channels, nframes_read)
def close(self):
pass
class PCMInputStreamFromWave(PCMInputStream):
def __init__(self, filename, maxNumFrame):
self.wv = wave.open(filename, "r")
self.ch = self.wv.getnchannels()
self.rate = self.wv.getframerate()
self.sw = self.wv.getsampwidth()
self.maxNumFrame = maxNumFrame
PCMInputStream.__init__(self, maxNumFrame)
def readFrames(self):
return self.wv.readframes(self.maxNumFrame)
def getNumChannels(self):
return self.ch
def getFrameRate(self):
return self.rate
def getSampleWidthInBytes(self):
return self.sw
def close(self):
self.wv.close()
class PCMInputStreamFromMic(PCMInputStream):
def __init__(self, rate, sampleWidth, maxNumFrame):
self.ch = 1
self.rate = rate
self.sw = sampleWidth
self.maxNumFrame = maxNumFrame
#Initialisierung des Aufnahmegeräts
self.pcm = alsaaudio.PCM(alsaaudio.PCM_CAPTURE)
self.pcm.setchannels(self.ch)
self.pcm.setrate(self.rate)
#Lesen Sie zweimal gleichzeitig, um die Verarbeitung zu beschleunigen
print self.pcm.setperiodsize(self.maxNumFrame * 4)
if self.sw == 1:
self.pcm.setformat(alsaaudio.PCM_FORMAT_U8)
elif self.sw == 2:
self.pcm.setformat(alsaaudio.PCM_FORMAT_S16_LE)
else:
raise ValueError("sampleWidth must be 1 or 2")
PCMInputStream.__init__(self, maxNumFrame)
def readFrames(self):
length, frames = self.pcm.read()
return frames
def getNumChannels(self):
return self.ch
def getFrameRate(self):
return self.rate
def getSampleWidthInBytes(self):
return self.sw
Dies ist auch eine ziemlich schwierige Erinnerung. Grundsätzlich wurde der Schall durch FFT frequenzanalysiert, und wenn die gegebene Frequenz (des Berührungsschalls) mehr Leistung als die anderen Frequenzen hätte, wäre sie als "berührt" beurteilt worden ... Wenn Sie die for-Schleife jedoch einfach verwenden, als würden Sie ein Programm auf einem PC schreiben, ist sie zu schwer, um in Echtzeit zu arbeiten, wenn Sie beispielsweise die Leistung einer bestimmten Frequenz aus den Komponenten des Frequenzbereichs berechnen (wenn Sie nicht gut darin sind, die Verarbeitung der FFT selbst). Ich erinnere mich, dass ich verschiedene Dinge getan habe, z. B. das Umschreiben der Teile, die mit integrierten Modulen, NumPy und SciPy geschrieben werden können.
suicadetection.py
# -*- coding: utf-8 -*-
import numpy
import scipy.fftpack
import time
import bisect
import collections
DetectionHistoryTuple = collections.namedtuple(
"DetectionHistoryTuple",
["cond_energy", "energy_peak", "freq_center_detected"])
#Konstante, die den Erkennungsstatus darstellt
DETECTION_ON = "on"
DETECTION_OFF = "off"
class SuicaDetection:
#Frequenzradius der Energieberechnung[Hz]
FREQ_TOLERANCE = 50
#Anzahl der gespeicherten Historien
NUM_HIST_SAVED = 3
#Energieverhältnisschwelle zum Zeitpunkt der Anstiegsbeurteilung
THRES_ENERGY_RATIO = 0.25
#Mindestwert von float64
EPS_FLOAT64 = numpy.finfo(numpy.float64).eps
def freq_filter_vector(self, freq_center, freq_tolerance):
freq_delta = self.freq_axis[1] - self.freq_axis[0]
# freq_center +/- freq_Die in Toleranz enthaltene Energie
#Gibt einen zu berechnenden Gewichtsvektor zurück.
energy_weight = numpy.array(
[(lambda freq_min, freq_max:
(1.0 if freq_min <= f and f + freq_delta <= freq_max
else (freq_max - f) / freq_delta if freq_min <= f <= freq_max <= f + freq_delta
else (f + freq_delta - freq_min) / freq_delta if f <= freq_min <= f + freq_delta <= freq_max
else (freq_tolerance * 2 / freq_delta) if f <= freq_min and freq_max <= f + freq_delta
else 0.0))
(freq_center - freq_tolerance, freq_center + freq_tolerance)
for f in self.freq_axis])
return energy_weight
def __init__(self, center_freqs, freq_axis):
self.ts = time.time()
self.hist = []
self.time_axis = []
self.nframes_read = 0
self.detected_freq = None
self.init_energy = None
self.freq_axis = freq_axis
#Energiegewicht für die Mittenfrequenz, die Sie erfassen möchten
self.center_freqs = center_freqs
self.energy_weight_array = numpy.array([
self.freq_filter_vector(center_freq, self.FREQ_TOLERANCE)
for center_freq in center_freqs
]).T
def input_array(self, arr):
assert len(arr.shape) == 1
num_frames = arr.shape[0]
self.detect(arr)
status = None
if self.detected_freq:
#Wenn ein Ton ertönt, ist der Ton dieses Frequenzbandes unmittelbar zuvor dreimal zu hören-Beenden Sie, wenn 5 dB fallen
if all((t.energy_peak is None) or (t.energy_peak - self.init_energy) < -5 for t in self.hist[-3:]):
self.detected_freq = None
status = DETECTION_OFF
else:
#Wenn kein Ton zu hören ist, ist es in Ordnung, wenn die Energiebedingung in den letzten drei Fällen zweimal erfüllt ist.
if len([t for t in self.hist[-3:] if t.cond_energy]) >= 2:
self.detected_freq = self.hist[-1].freq_center_detected
self.init_energy = self.hist[-1].energy_peak
status = DETECTION_ON
self.nframes_read += num_frames
return (self.nframes_read, status)
def detect(self, arr):
#print "start", time.time()
assert len(arr.shape) == 1
num_frames = arr.shape[0]
# FFT
f = scipy.fftpack.fft(arr)
e = numpy.square(numpy.absolute(f))
#Fehlervermeidung
e = numpy.maximum(e, self.EPS_FLOAT64)
#Gesamtenergie (konstante Zeiten)
energy_total = e.sum()
#Energie für jede Frequenz
# +/-Kombiniere (verdopple) die Energie der Frequenz von
energy_axis = 2 * e[0:num_frames/2]
log_energy_axis = 10 * numpy.log10(energy_axis)
#Berechnen Sie das Energieverhältnis in der Nähe der angegebenen Frequenz
energy_weighted = energy_axis.dot(self.energy_weight_array)
energy_ratio_max, freq_center_detected = \
max(zip(list(energy_weighted / energy_total), self.center_freqs),
key=lambda t: t[0])
#Energiebedingungen
#Konzentrieren Sie sich auf die stärkste Frequenz
cond_energy = (energy_ratio_max >= self.THRES_ENERGY_RATIO)
# +/-Basierend auf der maximalen Leistung innerhalb von 100Hz
idx_low = bisect.bisect_left(self.freq_axis, freq_center_detected - 100)
idx_high = bisect.bisect_right(self.freq_axis, freq_center_detected + 100)
energy_peak = log_energy_axis[idx_low:idx_high+1].max()
#Zur Geschichte hinzufügen
self.hist.append(DetectionHistoryTuple(cond_energy=cond_energy,
energy_peak=energy_peak,
freq_center_detected=freq_center_detected))
#Alte Geschichte löschen
if len(self.hist) > self.NUM_HIST_SAVED:
self.hist.pop(0)
Steuern Sie das EIN / AUS des entsprechenden GPIO entsprechend der Nummer, die Sie leuchten möchten. Welches GPIO verwendet werden soll, wird vom aufrufenden Programm festgelegt. Für diese EIN / AUS-Steuerung wurde das Modul RPi.GPIO verwendet.
Es scheint, dass GPIO nur gesteuert werden kann, wenn es als Root ausgeführt wird. Seien Sie also dort vorsichtig.
sevenseg.py
# -*- coding: utf-8 -*-
import sys
import RPi.GPIO as GPIO
import time
class GPIO7seg:
sevenseg_on = [[0, 2, 3, 5, 6, 7, 8, 9],
[0, 1, 2, 3, 4, 7, 8, 9],
[0, 1, 3, 4, 5, 6, 7, 8, 9],
[0, 2, 3, 5, 6, 8, 9],
[0, 2, 6, 8],
[0, 4, 5, 6, 8, 9],
[2, 3, 4, 5, 6, 8, 9]]
def __init__(self, id_pin_seg):
self.id_pin_seg = id_pin_seg
GPIO.setmode(GPIO.BCM)
for i in id_pin_seg:
GPIO.setup(i, GPIO.OUT)
def digit(self, n):
for i in xrange(7):
GPIO.output(self.id_pin_seg[i], n not in self.sevenseg_on[i])
Beim Aufrufen jedes Moduls aus dem Hauptprogramm leuchtet die LED entsprechend dem Beurteilungsergebnis auf.
Beispielsweise wird 1 angezeigt, wenn ein Piepton ertönt, und 2, wenn ein Piepton ertönt. Beim Passieren des Ticketschalters ist die Bedeutung ein- und zweimal unterschiedlich. Versuchen wir also, sie zu verstehen Es ist ein Programm namens.
Es ist ein GPIO zum Beleuchten einer 7-Segment-LED, aber diesmal entsprechen die Pins 15 bis 21 den Segmenten A bis G (siehe Datenblatt) der 7-Segment-LED.
Wenn Sie dieses Hauptprogramm als Root ausführen (zur GPIO-Steuerung), ändern sich die Zahlen hoffentlich als Reaktion auf den vom Mikrofon aufgenommenen Touch-Sound.
suica_main.py
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import sys
import numpy
import itertools
import pcmmod
import suicadetection
import sevenseg
# ================================
def main():
#Signallänge (Anzahl der Abtastwerte)
MAX_NUM_FRAMES = 512
#Frequenz, die Sie erkennen möchten
FREQS_CENTER = [2550, 2700, 3000]
#Fensterfunktion
ham_window = numpy.hamming(MAX_NUM_FRAMES)
#WAVE-Lesung oder Mikrofoneingang
if len(sys.argv) == 1:
#Mikrofon
rate = 44100
sw = 2
sound = pcmmod.PCMInputStreamFromMic(rate, sw, MAX_NUM_FRAMES)
else:
# WAVE
sound = pcmmod.PCMInputStreamFromWave(sys.argv[1], MAX_NUM_FRAMES)
rate = sound.getFrameRate()
#Frequenzachse
freq_axis = numpy.fft.fftfreq(MAX_NUM_FRAMES, 1.0/rate)[0:MAX_NUM_FRAMES/2]
sd = suicadetection.SuicaDetection(FREQS_CENTER, freq_axis)
#Zähler(7SEG LED)
counter_ring = 0
id_pin_seg = [15, 16, 17, 18, 19, 20, 21]
gpio7 = sevenseg.GPIO7seg(id_pin_seg)
gpio7.digit(counter_ring)
#Siehe die zuletzt gezählte Zeit
counted_last = None
#Lesen Sie die Wellenform
#Verwenden Sie bis zum vollständigen und gelesenen Teil
for arr, nframes_read in itertools.takewhile(lambda t: t.array.shape[1] == MAX_NUM_FRAMES,
sound.getFrameArrayIterator()):
#Wird zur Beurteilung verwendet:Nimm L für 2ch
time_frames, status = sd.input_array(arr[0] * ham_window)
if status == suicadetection.DETECTION_ON:
print float(time_frames) / rate, "ON"
# 0.Zählen Sie nicht, bis 1 Sekunde vergangen ist
counted_last = time_frames
pending = True
elif status == suicadetection.DETECTION_OFF:
print float(time_frames) / rate, "OFF"
print
pending = False
if counted_last is not None:
if time_frames > counted_last + rate * 2.0:
#Setzen Sie die LED nach 2 Sekunden zurück
gpio7.digit(counter_ring)
counted_last = None
elif time_frames > counted_last + rate * 1.0:
#Keine Combo-Beurteilung nach 1 Sekunde (Zähler nur intern zurücksetzen)
counter_ring = 0
elif pending and time_frames > counted_last + rate * 0.1:
counter_ring += 1
gpio7.digit(counter_ring)
pending = False
if __name__ == "__main__":
main()
Ich glaube, ich habe viele Anpassungen vorgenommen, als ich den Touch-Sound beurteilt habe. Wenn Sie es einfach mit Strom betrachten, reagiert es in einem lauten Bahnhofshof.
Danach stellte sich heraus, dass die Frequenz des Berührungsgeräuschs unerwartet nicht einheitlich zu sein scheint. ** Selbst wenn Sie den Fall des Ticketschalters und den Fall der Kassiererin im Supermarkt vergleichen, ist die Tonhöhe (Frequenz) des Touch-Sounds tatsächlich unterschiedlich **, sodass es immer komplizierter wird, wenn Sie darüber nachdenken, eine Erkennung zu ermöglichen. ….
Übrigens hatte ich mir vorher noch keine Gedanken über die Frequenz des Touch-Sounds gemacht. Als ich dieses Programm schrieb, habe ich zum ersten Mal in Audacity nach bestimmten Zahlen gesucht. Als ich meinem Bekannten zu Beginn berichtete, dass "der Touch-Sound 2550 Hz oder 2700 Hz zu sein scheint", war ich überzeugt, dass "ich ihn nicht hören kann".
Es ist lange her, dass die Leute sagten, es sei barrierefreies oder universelles Design. ** Betrachten Sie nur sichtbare Stufen? Entwerfen Sie den Sound unter Berücksichtigung des Frequenzbandes? ** ** ** Ich glaube, das ist mir aufgefallen. Wenn Sie jedoch den Ton leise machen, kann es aufgrund der Menschenmenge schwierig sein, ihn zu hören. es ist schwierig.