Materiał poświęcony jest sposobom pozyskiwania obrazu i dźwięku u urządzeń zewnętrznych wewnątrz środowiska Python. Jeżeli macie problemy z urządzeniami polecam zajrzeć do FAQ

Przechwytywanie obrazu

Najłatwiejszym dla nas rozwiązaniem jest wykorzystanie w tym celu biblioteki OpenCV. Słów kilka na temat tego procesu. Podstawowa informacja jak otwieramy strumienie wideo, ze pomocą OpenCV. Nie zależnie od tego czy będzie to strumień pochodzący z pliku, adresu internetowego czy kamery internetowej.

import cv2

cap = cv2.VideoCapture(0) #przechwytywanie z kamery o id -> 0
cap = cv2.VideoCapture("nazwa.plik") #przechwytywanie z pliku

Teraz napiszmy mały program służący do wyświetlania obrazu. Poniższy program wyświetli nam obraz z domyślnego źródła obrazu przekształcony do obrazu w skali odcieni szarości. Program można wyłączyć naciskając klawisz q na klawiaturze. Uprzedzając pytania: tak w konsoli będą prawdopodobnie pojawiały się komunikaty i ostrzeżenia.

if not cap.isOpened():
    print("Cannot open camera")
    exit()
while True:
    # Capture frame-by-frame
    ret, frame = cap.read()
    # if frame is read correctly ret is True
    if not ret:
        print("Can't receive frame (stream end?). Exiting ...")
        break
    # Our operations on the frame come here
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # Display the resulting frame
    cv2.imshow('frame', gray)
    if cv2.waitKey(1) == ord('q'):
        break
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()

Domyślny rozmiar klatki możemy odczytać lub zmienić za pomocą odpowiednich poleceń set oraz get:

cap.get(cv2.CAP_PROP_FRAME_WIDTH)
cap.get(cv2.CAP_PROP_FRAME_HEIGHT)

cap.set(cv2.CAP_PROP_FRAME_WIDTH,1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT,720)

W jaki sposób zapisać nasz strumień wideo do pliku? Na początku tworzymy odpowiednią funkcję zapisu wybierając odpowiedni kodek (DIVX, XVID, MJPG, X264) i przypisując go do odpowiedniego strumienia wyjściowego.

fourcc = cv2.VideoWriter_fourcc(*'X264')
out = cv2.VideoWriter('output.avi', fourcc, 20.0, (640,  480))

Potem wewnątrz pętli należy przekazać klatkę do zapisu.

out.write(frame)

A na końcu pamiętać, żeby zamknąć strumień wyjściowy.

out.release()

Przechwytywanie audio

Do pracy z tą częścią materiały wykorzystamy bibliotekę PyAudio. Ty mogą być pewne trudności z instalacja dlatego podaję kilka sposobów na wykonanie tego:

# metoda 1
pip install pipwin
pipwin install pyaudio
# metoda 2
conda install PyAudio

Jeżeli obie nie będą działać to można jeszcze spróbować tej metody

Po instalacji zacznijmy od sprawdzenia dostępnych urządzeń, z których możemy skorzystać.

audio = pyaudio.PyAudio()
numdevices = audio.get_device_count()
for i in range(0, numdevices):
        print(audio.get_device_info_by_index(i))

Jak znamy już urządzenia jakie mamy podłączone urządzenia możemy przygotować się na przechwytywanie. Na początek przygotujmy sobie ustawienia:

FORMAT = pyaudio.paInt16 
CHANNELS = 1
FS = 44100
CHUNK = 1024

Na ich podstawie możemy utworzyć strumień danych do przechwytywania.

stream = audio.open(input_device_index =0,
                    format=FORMAT,
                    channels=CHANNELS,
                    rate=FS,
                    input=True,
                    frames_per_buffer=CHUNK
                    )

Przykład 1

Teraz mamy kilka możliwości jak przechwycić i obsłużyć sygnał zaczniemy od pierwszego przykładu źródło, który na bieżąco wyświetla dane zbierane z mikrofonu i zapisuje je do bufora. Na początek trochę operacji przygotowawczych.

global keep_going

i=0
f,ax = plt.subplots(2)

# Prepare the Plotting Environment with random starting values
x = np.arange(10000)
y = np.random.randn(10000)

# Plot 0 is for raw audio data
li, = ax[0].plot(x, y)
ax[0].set_xlim(0,1000)
ax[0].set_ylim(-5000,5000)
ax[0].set_title("Raw Audio Signal")
# Plot 1 is for the FFT of the audio
li2, = ax[1].plot(x, y)
ax[1].set_xlim(0,5000)
ax[1].set_ylim(-100,100)
ax[1].set_title("Fast Fourier Transform")
# Show the plot, but without blocking updates
plt.pause(0.01)
plt.tight_layout()


frames = []
keep_going = True

Oraz funkcji którą będziemy wywoływać:

def plot_data(in_data):
     # get and convert the data to float
    audio_data = np.frombuffer(in_data, dtype=np.int16)
    dfft = 10.*np.log10(abs(np.fft.rfft(audio_data)))
    li.set_xdata(np.arange(len(audio_data)))
    li.set_ydata(audio_data)
    li2.set_xdata(np.arange(len(dfft))*10.)
    li2.set_ydata(dfft)
    frames.extend(audio_data.tolist())

    plt.pause(0.01)
    if keep_going:
        return True
    else:
        return False

Następnie uruchamiamy naszą pętlę (wyłączmy ją zamykając okno a następnie naciskając kombinację klawiszy Ctrl+C w konsoli). Uwaga program nie zadziała jeżeli wyświetlamy ploty wewnątrz IDE, należy wymusić wyświetlanie ich w nowym oknie.

stream.start_stream()
print("\n+---------------------------------+" )
print("| Press Ctrl+C to Break Recording |")
print("+---------------------------------+\n")

# Loop so program doesn't end while the stream callback's
# itself for new data
while keep_going:
    try:
        frame=stream.read(CHUNK)
        plot_data(frame)
    except KeyboardInterrupt:
        keep_going=False
    except:
        pass

stream.stop_stream()
stream.close()

audio.terminate()

Zapisywanie danych do pliku

Do zapisania danych na podstawie naszych danych do pliku wave możemy wykorzystać poniższą funkcję. Będzie ona działać dla danych pozyskanych zarówno w pierwszym jak i drugim przykładzie, ale do drugiego przykładu jest też dedykowany sposób.

wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(audio.get_sample_size(FORMAT))
wf.setframerate(FS)
wf.writeframes(b''.join(frames))
wf.close()

Przykład 2

Druga metoda czyli przechwytywanie jako proces działający w tle przy wykorzystaniu funkcji callback. Na początek zadeklarujmy sobie funkcję która zrealizuje nam ten process bezpośredniego przetwarzania danych:

def process_data(in_data, frame_count, time_info, flag):
    global Frame_buffer,frame_idx
    in_audio_data = np.frombuffer(in_data, dtype=np.int16)
    Frame_buffer[frame_idx:(frame_idx+CHUNK),0]=in_audio_data
    ################################
    ## Do something wih data
    out_audio_data = in_audio_data
    ################################
    Frame_buffer[frame_idx:(frame_idx+CHUNK),1]=out_audio_data
    out_data =  out_audio_data.tobytes()
    frame_idx+=CHUNK
    return out_data, pyaudio.paContinue

Mamy tu jak widać miejsce na przechwytywanie danych tak jak w poprzednim przykładzie jak również miejsce na ich przetwarzanie. Dane pobrane z mikrofonu będą w zmiennej in_data a dane przekazywane do waszych głośników będą zwracane w returnie w zmiennej out_data. Kolejnym korkiem jest modyfikacja naszego strumienia i przerobienie naszego strumienia wejściowego na wejściowo-wyjściowe. UWAGA Trzeba ustalić odpowiednia urządzenia wejścia i wyjścia - urządzenie o indeksie \(0\) nie będzie na pewno jednym i drugim.

stream = audio.open(input_device_index =0,
                    output_device_index=0,
                    format=FORMAT,
                    channels=CHANNELS,
                    rate=FS,
                    input=True,
                    output=True,
                    frames_per_buffer=CHUNK,
                    stream_callback=process_data
                    )

Teraz deklaracja potrzebnych zmiennych globalnych oraz nowa pętla działająca przez \(10\) sekund (można sterować parametrem):

global Frame_buffer,frame_idx

N=10
Frame_buffer = np.zeros(((N+1)*FS,2))
frame_idx=0

stream.start_stream()
while stream.is_active():
    time.sleep(N)
    stream.stop_stream()
stream.close()

Sprawdzanie danych i zapis do pliku

Zastosowanie buforu w postaci dwukolumnowej tablicy numpy daje nam lepszą możliwość porównywania wejścia i wyjścia naszego callbacka. To możemy go zarówno zapisać do pliku i sprawdzić wyniki w zewnętrznym programie do obórki dźwięku lub wyświetlić sobie oba sygnały (wywołanie uzupełnić o odpowiednią metodę wygenerowania wartości czasu dla osi OX).

## zapis do pliku
sf.write('nazwa.wav', Frame_buffer.astype(np.int16), FS)

## wyświetlanie niepełne
plt.subplot(2,1,1)
plt.plot(?,Frame_buffer[:,0])
plt.subplot(2,1,2)
plt.plot(?,Frame_buffer[:,1])