본문 바로가기

기술/AI

오디오와 비디오 데이터 분석

반응형

멀티모달 데이터 분석의 두 번째 글에서는 오디오와 비디오 데이터에서 의미를 추출하고 이를 텍스트로 변환하는 방법, 그리고 다양한 모달리티 데이터를 통합적으로 분석하는 접근법에 대해 살펴보겠습니다.

오디오 데이터: 음성 -> 텍스트 변환(STT) 및 키워드 추출

오디오 데이터(회의 녹음, 팟캐스트, 동영상의 음성 트랙 등)는 음성 신호 형태이므로 바로 언어적 키워드를 추출할 수 없습니다. 따라서 먼저 음성을 텍스트로 변환하는 음성인식(STT, Speech-to-Text) 단계가 필요합니다.

1. Whisper를 이용한 STT

최근에는 OpenAI의 Whisper 모델이 멀티언어 음성 인식에서 뛰어난 성능을 보이며 널리 활용되고 있습니다. Whisper는 사전학습된 엔드투엔드 Transformer 모델로, 다양한 언어의 대용량 음성 데이터를 학습하여 높은 인식 정확도를 가집니다.

HuggingFace의 Transformers 파이프라인을 이용하면 Whisper 모델로 손쉽게 음성 인식을 수행할 수 있습니다:

from transformers import pipeline

stt_pipeline = pipeline("automatic-speech-recognition", model="openai/whisper-medium")
result = stt_pipeline("sample_audio_ko.mp3")  # 또는 PCM WAV 등 지원
print(result['text'])
# 출력 예시: "안녕하세요. 오늘 회의에서는 프로젝트 일정에 대해 논의했습니다."

Whisper 모델은 내부적으로 30초 단위 등으로 긴 오디오를 처리하며, chunk_length_s와 stride_length_s 등의 파라미터로 긴 녹음도 처리할 수 있습니다.

2. 키워드 추출 및 화자 분리

이렇게 얻어진 텍스트에 대해서는 앞서 텍스트 데이터 처리와 동일하게 키워드 추출을 수행하면 됩니다. 예컨대 회의 녹음을 Whisper로 텍스트화한 뒤 KeyBERT를 적용하여 회의의 핵심 주제어를 뽑아낼 수 있습니다.

# 음성 -> 텍스트 -> 키워드 추출 파이프라인
from transformers import pipeline
from keybert import KeyBERT

# 1. 음성을 텍스트로 변환
stt_pipeline = pipeline("automatic-speech-recognition", model="openai/whisper-medium")
transcript = stt_pipeline("meeting_recording.mp3")['text']

# 2. 텍스트에서 키워드 추출
kw_model = KeyBERT()
keywords = kw_model.extract_keywords(transcript, keyphrase_ngram_range=(1, 2), top_n=5)
print(keywords)

만약 오디오 데이터에 여러 화자가 있고 이를 구분해야 한다면, 추가로 화자 분리(diarization)를 수행한 뒤 각 화자별로 STT -> 키워드 추출을 할 수도 있습니다. 화자 분리는 pyannote.audio와 같은 라이브러리를 이용할 수 있습니다:

from pyannote.audio import Pipeline

diarization = Pipeline.from_pretrained("pyannote/speaker-diarization")
diarization_result = diarization("audio.wav")

# 화자별로 발화 구간 확인
for turn, _, speaker in diarization_result.itertracks(yield_label=True):
    print(f"Speaker {speaker}: {turn.start:.1f}s - {turn.end:.1f}s")

 

비디오 데이터: 영상의 장면/자막 기반 텍스트화

비디오 데이터는 시각 정보와 청각 정보가 결합된 형태입니다. 비디오에서 의미를 추출하려면 보통 두 가지 경로를 모두 활용합니다: (1) 영상의 화면 내용, (2) 영상의 오디오(내레이션/대화). 각각을 처리하여 얻은 정보를 결합하면 비디오의 주요 내용을 표현할 수 있습니다.

1. 오디오 활용

비디오에 대사가 있거나 내레이션이 있는 경우, 먼저 해당 비디오의 오디오 스트림을 추출하여 (예: FFmpeg 등을 사용) Whisper와 같은 STT를 적용합니다. 이렇게 하면 비디오에서 말해지는 모든 내용을 텍스트로 얻을 수 있습니다.

import subprocess

# FFmpeg로 비디오에서 오디오 추출
subprocess.call([
    'ffmpeg',
    '-i', 'video.mp4',  # 입력 비디오
    '-q:a', '0',       # 품질 설정
    '-map', 'a',       # 오디오 스트림만 선택
    'audio.mp3'        # 출력 오디오 파일
])

# 이후 추출된 오디오에 STT 적용
# ...

만약 유튜브 동영상처럼 자막 데이터가 이미 있다면, 자막을 활용해도 좋습니다. YouTube-dl이나 youtube-transcript-api와 같은 라이브러리를 사용하여 자막을 직접 다운로드할 수 있습니다:

from youtube_transcript_api import YouTubeTranscriptApi

video_id = "VIDEO_ID_HERE"
transcript = YouTubeTranscriptApi.get_transcript(video_id)

# 자막 텍스트 추출
transcript_text = " ".join([item['text'] for item in transcript])
print(transcript_text)

2. 비디오 프레임 활용

음성 정보가 부족하거나, 시각적인 장면 변화가 중요한 영상의 경우 키 프레임(Key frame)을 추출하여 해당 프레임마다 이미지 캡셔닝을 적용할 수 있습니다.

키 프레임 추출은 일정 간격으로 프레임을 추출하거나, 씬(Scene) 전환 검출 알고리즘(PySceneDetect 등)을 사용하여 장면이 바뀌는 지점을 추출하는 방식이 있습니다:

import cv2
from scenedetect import VideoManager, SceneManager, StatsDetector

# 씬 전환 검출로 키 프레임 추출
video_path = "video.mp4"
video_manager = VideoManager([video_path])
scene_manager = SceneManager()
scene_manager.add_detector(StatsDetector())

# 비디오 처리 및 씬 경계 검출
video_manager.start()
scene_manager.detect_scenes(frame_source=video_manager)
scene_list = scene_manager.get_scene_list()

# 각 씬의 첫 프레임을 키 프레임으로 저장
for i, scene in enumerate(scene_list):
    frame_num = scene[0].frame_num
    video_manager.seek(frame_num)
    ret, frame = video_manager.retrieve()
    if ret:
        cv2.imwrite(f'keyframe_{i}.jpg', frame)

추출된 키 프레임에 BLIP-2 등의 이미지 캡셔닝 모델을 적용하면, 비디오의 시각적 내용에 대한 설명을 얻을 수 있습니다:

from transformers import AutoProcessor, Blip2ForConditionalGeneration
from PIL import Image

processor = AutoProcessor.from_pretrained("Salesforce/blip2-opt-2.7b")
model = Blip2ForConditionalGeneration.from_pretrained("Salesforce/blip2-opt-2.7b")

# 키 프레임 이미지 로드 및 캡션 생성
image = Image.open("keyframe_0.jpg")
inputs = processor(images=image, return_tensors="pt")
out = model.generate(**inputs, max_new_tokens=30)
caption = processor.batch_decode(out, skip_special_tokens=True)[0].strip()
print(caption)  # 예: "회의실에서 사람들이 토론하고 있다"

 

3. 정보 통합

이렇게 비디오의 텍스트 자막 정보와 영상 장면 캡션 정보를 모두 얻었으면, 이를 합쳐서 비디오의 설명을 구성합니다. 가장 간단한 방법은 오디오로부터 얻은 텍스트가 있다면 그것을 우선 사용하고, 추가로 시각적 요소 중 오디오에 언급되지 않는 중요한 내용을 캡션으로 보완하는 것입니다.

예를 들어, "사람들이 회의하는 장면의 영상으로, 프로젝트 일정에 대해 논의하고 있음"과 같이 오디오와 비디오 정보를 결합한 요약/설명이 가능합니다. 이 최종 설명 문장에서 핵심 키워드를 추출하거나, 또는 설명 문장 자체를 임베딩에 사용할 수 있습니다.

# 오디오 텍스트와 비디오 캡션을 결합한 설명 생성
def generate_video_description(audio_text, frame_captions):
    # 간단한 요약 예시 (실제로는 더 복잡한 요약 알고리즘 적용 가능)
    key_frame_description = "; ".join(set(frame_captions[:3]))  # 중복 제거 후 처음 3개 캡션만 사용
    
    # 오디오 텍스트 요약 (간단한 예시)
    audio_summary = audio_text[:200] + "..." if len(audio_text) > 200 else audio_text
    
    # 결합
    description = f"영상 내용: {key_frame_description}. 오디오 내용: {audio_summary}"
    return description

실제 서비스에서는 비디오에 대해 짧은 요약문을 생성해 메타데이터로 저장해두는 경우도 많은데, 이 역시 멀티모달 임베딩 분석을 쉽게 해주는 형태라 볼 수 있습니다.

멀티모달 데이터 통합 접근법

각 모달리티에서 키워드나 설명을 추출했다면, 이제 이들을 어떻게 통합하여 의미 있는 분석을 할 수 있을지 살펴보겠습니다.

1. 텍스트 기반 통합

가장 간단하고 권장되는 방법은 모든 데이터를 텍스트로 표현해버리는 것입니다. 앞서 설명한 대로 이미지 캡션, 오디오 전사, 비디오 설명 등을 얻으면, 결국 텍스트 비교 문제로 환원되므로 특별한 통합 없이도 일관된 비교가 가능합니다.

실무에서도 이미지 자체의 픽셀 임베딩보다는 이미지 설명문을 생성한 뒤 텍스트 임베딩하는 방식을 택해, 텍스트 검색 시스템에 편입시키는 경우가 많습니다. 예컨대 전사된 오디오 내용과 문서 텍스트를 같은 검색 인덱스에 넣어두면, 사용자가 텍스트로 질의했을 때 팟캐스트 내용과 문서가 함께 검색될 수 있습니다.

2. 벡터 연산으로 통합 표현 생성

하나의 아이템(예: 하나의 비디오)에 대해 여러 개의 임베딩 벡터가 존재하는 경우, 이를 하나의 대표 벡터로 합치는 방법도 있습니다. 예를 들어 비디오의 오디오 임베딩과 이미지 장면 임베딩 여러 개가 있을 때, 벡터 평균을 구하여 비디오 전체의 임베딩으로 사용할 수 있습니다.

import numpy as np

# 여러 모달리티 임베딩을 하나의 벡터로 통합
def combine_embeddings(image_embs, audio_embs, weights=None):
    """
    image_embs: 이미지 임베딩 배열 [n_images, dim]
    audio_embs: 오디오 임베딩 [dim]
    weights: 각 모달리티의 가중치 (선택 사항)
    """
    # 이미지 임베딩들의 평균
    avg_image_emb = np.mean(image_embs, axis=0)
    
    # 가중 평균으로 통합 (가중치가 제공된 경우)
    if weights:
        image_weight, audio_weight = weights
        combined = (image_weight * avg_image_emb + audio_weight * audio_embs) / (image_weight + audio_weight)
    else:
        # 단순 평균
        combined = (avg_image_emb + audio_embs) / 2
    
    # 정규화 (선택 사항)
    combined = combined / np.linalg.norm(combined)
    
    return combined

이렇게 하면 멀티모달 정보를 단일 벡터에 융합할 수 있는데, 단순 평균은 구현이 쉽지만 경우에 따라 중요한 모달리티의 가중치를 높이는 등의 기법을 쓸 수도 있습니다.

또는 이미지와 텍스트 벡터를 이어붙인(concatenate) 벡터로 만들어 한 아이템을 표현하기도 합니다. 이 경우 차원은 두 배가 되지만, 임베딩 DB에서 동일 아이템에 대해 멀티필드 검색을 하는 효과를 얻을 수 있습니다.

3. 공통 임베딩 공간 사용

앞서 언급한 CLIP처럼, 애초에 여러 모달의 데이터를 같은 모델로 임베딩하면 비교가 바로 가능합니다. 이미지와 텍스트를 동시에 다루는 CLIP 모델의 임베딩 결과는 동일한 차원의 같은 공간에 존재하므로, 별도의 조정 없이도 유사도를 계산할 수 있습니다.

from sentence_transformers import SentenceTransformer, util
import torch
from PIL import Image

# CLIP 모델 로드
model = SentenceTransformer('clip-ViT-B-32')

# 이미지와 텍스트 임베딩
image = Image.open('cat.jpg')
image_emb = model.encode(image)

texts = ["A cat sitting on a sofa", "A dog running in the park"]
text_embs = model.encode(texts)

# 이미지-텍스트 유사도 계산
similarities = util.cos_sim(torch.tensor(image_emb), torch.tensor(text_embs))
print(similarities)

 

실제 응용 사례

이러한 멀티모달 데이터 분석 기법은 다양한 분야에서 활용되고 있습니다:

  1. 크로스모달 검색: 이미지로 관련 문서를 찾거나, 텍스트 질의로 관련 이미지/비디오를 검색하는 시스템
  2. 콘텐츠 추천: 사용자가 본 이미지나 동영상과 텍스트 내용이 유사한 다른 형태의 콘텐츠 추천
  3. 자동 태깅 및 카탈로깅: 이미지, 오디오, 비디오 등 다양한 미디어 자산에 자동으로 관련 태그를 부여
  4. 미디어 모니터링: 소셜 미디어나 뉴스에서 특정 주제와 관련된 모든 형태의 콘텐츠를 실시간 감지

예를 들어, Pinterest는 이미지 캡셔닝을 통해 이미지에 대한 텍스트 설명을 생성하고, 이를 텍스트 검색 인덱스에 통합하여 이미지와 텍스트를 동시에 검색할 수 있는 시스템을 구축했습니다. Netflix는 영화 내용, 대사, 시각적 요소를 모두 분석하여 사용자에게 더 정확한 콘텐츠를 추천하는 데 멀티모달 접근법을 활용합니다.

다음 편에서는...

이번 글에서는 오디오와 비디오 데이터에서 의미 있는 텍스트 표현을 추출하는 방법과 다양한 모달리티의 데이터를 통합하는 접근법에 대해 알아보았습니다. 다음 편에서는 이렇게 준비된 데이터를 임베딩하는 다양한 기법과 임베딩 벡터의 의미 통합에 대해 더 자세히 알아보겠습니다.

반응형