OpenCVで画像を処理する際に、同時に音声も処理したい場合があります。OpenCVのCVはComputer Visionですので、音声を処理する機能は持っていません。音声は別のPythonライブラリで処理します。
この記事ではOpenCVで映像を表示しながら音声を処理する際に気をつける点について解説します。
Video frame and Audio frame
以下にmain関数を示します。
def main(video_file, audio_file):
print(f"{video_file=},{audio_file=},{use_pyaudio=}")
cap = cv2.VideoCapture(video_file)
player = WithPyAudio(audio_file)
start_time = time.time()
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
cv2.imshow(video_file, frame)
elapsed = (time.time() - start_time) * 1000 # msec
play_time = int(cap.get(cv2.CAP_PROP_POS_MSEC))
sleep = max(1, int(play_time - elapsed))
if cv2.waitKey(sleep) & 0xFF == ord("q"):
break
player.close()
cap.release()
cv2.destroyAllWindows()
Code language: Python (python)
main関数には映像ファイル(video_file
)と音声ファイル(audio_file
)を別々に渡します。通常の映像ファイルは音声も含まれますが、予め分割して渡す必要があります。映像から音声を分割する方法はffmpeg
を使用し以下を実行します。
ffmpeg -i test.mp4 -vn -f wav test.wav
Code language: Bash (bash)
cap.read()
で映像フレームを取得します。音声データはループの中で特別な処理は不要です。
ret, frame = cap.read()
if not ret:
break
Code language: Python (python)
elapsed
に再生が開始されてからの実時間を設定します。play_time
に現在のビデオの再生位置を表す時間を設定します。cv2.CAP_PROP_POS_MSEC
を使用することで再生位置の時間を取得することができます。このplay_timeをelapsedに一致させるように調整することで、ビデオの再生速度を実時間に合わせることができます。なお、音声の再生速度を実時間に合わせる特別な処理は不要です。
play_time = int(cap.get(cv2.CAP_PROP_POS_MSEC))
sleep = max(1, int(play_time - elapsed))
if cv2.waitKey(sleep) & 0xFF == ord("q"):
break
Code language: Python (python)
最後に、pyaudioの処理を説明します。
class WithPyAudio:
def __init__(self, audio_file):
super().__init__()
wf = wave.open(audio_file, "rb")
p = pyaudio.PyAudio()
self.wf = wf
self.p = p
print(f"{wf.getsampwidth()=},{wf.getnchannels()=},{wf.getframerate()=}")
self.stream = p.open(
format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True,
stream_callback=self._stream_cb,
)
def close(self):
self.stream.stop_stream()
self.stream.close
self.p.terminate()
self.wf.close()
def _stream_cb(self, in_data, frame_count, time_info, status):
data = self.wf.readframes(frame_count)
# process audio here
return (data, pyaudio.paContinue)
Code language: Python (python)
waveファイルオブジェクトwf
、PyAudioオブジェクトp
を作成し、p.openを呼び出すとstream_callback
に渡したコールバック関数_stream_cbが定期的に呼び出されます。
def __init__(self, audio_file):
super().__init__()
wf = wave.open(audio_file, "rb")
p = pyaudio.PyAudio()
self.wf = wf
self.p = p
print(f"{wf.getsampwidth()=},{wf.getnchannels()=},{wf.getframerate()=}")
self.stream = p.open(
format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True,
stream_callback=self._stream_cb,
)
Code language: Python (python)
_stream_cb
ではwaveファイルオブジェクトwf
から音声フレームを読み出し、そのフレームをリターンします。音声ファイルに処理を加えたい場合は、このコールバック関数内で取得した音声フレームを処理します。
def _stream_cb(self, in_data, frame_count, time_info, status):
data = self.wf.readframes(frame_count)
# process audio here
return (data, pyaudio.paContinue)
Code language: Python (python)
ソースコードはhttps://github.com/otamajakusi/opencv_video_with_audioにあります。
応用例
応用例として、映像の物体認識を実行しながら音声認識を実行し、特定の音声を検出したい場合、該当の映像を強調表示される例を考えます。説明がわかりにくいで以下のビデオを参照してくだいさい。
これは将棋の盤面をYOLOv5で認識しながら、解説者の「45歩」、「88玉」、「79玉」などの音声を認識し、将棋盤にその座標の位置を強調表示させる例です。音声認識はwav2vec2を使用しています。
以上です。