エージェントにおける動画理解
フレーム抽出、動画要約、クリップに対する時間的推論を扱います。
「エージェントにおける動画理解」はCoddyKit上の無料AI Agentsレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Agents学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Agentsコースには全4レッスンが含まれています。
エージェント向けの動画理解
多くのLLMは動画ファイルを直接処理できませんが、エージェントは代表的なフレームを抽出して画像として送信することで動画を分析できます。その後、エージェントは視覚的なシーケンスを推論し、時間の経過に伴うイベント、変化、パターンを検出します。
フレーム抽出用のOpenCVのインストール
OpenCV(cv2)は、Pythonで動画処理を行うための標準的なライブラリです。動画ファイルを開き、メタデータ(フレームレート、解像度、フレーム数)を読み取り、個々のフレームをNumPy配列として抽出できます。
# pip install opencv-python-headless
import cv2
def get_video_metadata(video_path: str) -> dict:
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise IOError(f'Cannot open video: {video_path}')
fps = cap.get(cv2.CAP_PROP_FPS)
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
duration_s = frame_count / fps if fps > 0 else 0
cap.release()
return {
'fps': round(fps, 2),
'frame_count': frame_count,
'width': width,
'height': height,
'duration_seconds': round(duration_s, 2)
}
meta = get_video_metadata('recording.mp4')
print(meta)N秒ごとのフレーム抽出
ほとんどの動画理解タスクでは、すべてのフレームは必要ありません。N秒ごとに1フレームをサンプリングすると、動画内容の代表的な要約が得られます。1秒あたり1フレームの場合、60秒の動画から60フレームが生成されます。
import cv2
import os
def extract_frames(
video_path: str,
output_dir: str,
every_n_seconds: float = 5.0
) -> list:
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
frame_interval = int(fps * every_n_seconds)
os.makedirs(output_dir, exist_ok=True)
saved_frames = []
frame_idx = 0
while True:
ret, frame = cap.read()
if not ret:
break
if frame_idx % frame_interval == 0:
timestamp = round(frame_idx / fps, 2)
fname = f'{output_dir}/frame_{frame_idx:06d}_{timestamp}s.jpg'
cv2.imwrite(fname, frame)
saved_frames.append({'path': fname, 'timestamp': timestamp})
frame_idx += 1
cap.release()
print(f'Extracted {len(saved_frames)} frames')
return saved_framesシーン変化の検出
均一にサンプリングする代わりに、シーンの境界、つまり視覚的な内容が大きく変化する時点でフレームを抽出します。これにより、API呼び出し1回あたりの情報量を増やせます。フレーム差分を使用し、連続するフレーム間の平均絶対差分がしきい値を超えた場合に、シーンが変化したと判断します。
import cv2
import numpy as np
def extract_scene_change_frames(
video_path: str,
output_dir: str,
diff_threshold: float = 30.0
) -> list:
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
os.makedirs(output_dir, exist_ok=True)
prev_gray = None
saved = []
frame_idx = 0
while True:
ret, frame = cap.read()
if not ret:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
if prev_gray is not None:
diff = np.mean(np.abs(gray.astype(float) - prev_gray.astype(float)))
if diff > diff_threshold:
ts = round(frame_idx / fps, 2)
fname = f'{output_dir}/scene_{frame_idx:06d}_{ts}s.jpg'
cv2.imwrite(fname, frame)
saved.append({'path': fname, 'timestamp': ts, 'diff': round(diff, 2)})
prev_gray = gray
frame_idx += 1
cap.release()
return savedフレームシーケンスのVision APIへの送信
抽出した複数のフレームを、1つのメッセージでVision APIに送信します。モデルが時間的な順序や継続時間を推論できるように、各フレームのタイムスタンプを含めてください。プロンプトでは、時間的推論を明示的に求めます。
import base64
import anthropic
def describe_video_frames(frame_info_list: list, query: str) -> str:
client = anthropic.Anthropic(api_key='YOUR_API_KEY')
content = []
for fi in frame_info_list:
with open(fi['path'], 'rb') as f:
b64 = base64.standard_b64encode(f.read()).decode('utf-8')
content.append({
'type': 'text',
'text': f'Frame at {fi["timestamp"]}s:'
})
content.append({
'type': 'image',
'source': {'type': 'base64', 'media_type': 'image/jpeg', 'data': b64}
})
content.append({'type': 'text', 'text': query})
response = client.messages.create(
model='claude-opus-4-5',
max_tokens=1024,
messages=[{'role': 'user', 'content': content}]
)
return response.content[0].text時間的推論のプロンプト
時間的推論には、具体的なプロンプト構成が必要です。フレームを明示的に比較し、何が変化したかを記録し、活動の継続時間を推定し、フレーム間の因果関係を特定するようモデルに指示してください。
TEMPORAL_QUERY = (
'You are analysing a video sequence. The frames above are in chronological order '
'with their timestamps shown. Please:\n'
'1. Describe what changed between the first and last frame\n'
'2. Identify any notable events and when they occurred (timestamp)\n'
'3. Summarise the overall activity shown in the video\n'
'4. Estimate the total duration of the main activity\n'
'Be specific about timestamps.'
)
# More targeted temporal query:
CHANGE_DETECTION_QUERY = (
'Compare frame at {start_ts}s and frame at {end_ts}s. '
'List all visible differences in bullet points. '
'Categorise each change as: object_moved, object_added, '
'object_removed, lighting_change, or camera_move.'
)
# Usage:
query = CHANGE_DETECTION_QUERY.format(start_ts=0.0, end_ts=20.0)
if __name__ == '__main__':
print('Temporal reasoning query:')
print(TEMPORAL_QUERY)
print()
print('Change detection query:')
print(query)
動画要約パイプライン
動画要約パイプライン全体の流れは、フレームを抽出 → ビジョンモデルにバッチ単位で送信 → バッチごとの要約を収集 → 最終要約を統合、です。バッチ処理により、コンテキストウィンドウの上限を超えることを防げます。
def summarise_video(
video_path: str,
every_n_seconds: float = 10.0,
batch_size: int = 5
) -> str:
import anthropic
client = anthropic.Anthropic(api_key='YOUR_API_KEY')
# Extract frames
frames = extract_frames(video_path, '/tmp/video_frames', every_n_seconds)
# Process in batches
batch_summaries = []
for i in range(0, len(frames), batch_size):
batch = frames[i:i + batch_size]
ts_range = f'{batch[0]["timestamp"]}s - {batch[-1]["timestamp"]}s'
query = f'Summarise what happens in this video segment ({ts_range}).'
summary = describe_video_frames(batch, query)
batch_summaries.append(f'[{ts_range}] {summary}')
# Synthesise
all_summaries = '\n\n'.join(batch_summaries)
result = client.messages.create(
model='claude-opus-4-5',
max_tokens=512,
messages=[{'role': 'user', 'content':
f'Here are segment-level video summaries:\n\n{all_summaries}\n\n'
'Write a single coherent summary of the entire video.'
}]
)
return result.content[0].textフレーム間の物体追跡
ビジョンモデルに、フレームシーケンス全体で特定の物体を追跡するよう依頼します。例:「各フレームで、赤い車の位置を説明してください:左上、右上、中央、左下、右下。」これにより、コンピュータービジョンの追跡アルゴリズムを使わずに、大まかな移動軌跡を作成できます。
def track_object_across_frames(
frames: list,
object_description: str
) -> list:
import anthropic, base64
client = anthropic.Anthropic(api_key='YOUR_API_KEY')
trajectory = []
for fi in frames:
with open(fi['path'], 'rb') as f:
b64 = base64.standard_b64encode(f.read()).decode('utf-8')
response = client.messages.create(
model='claude-opus-4-5',
max_tokens=128,
messages=[{
'role': 'user',
'content': [
{'type': 'image', 'source': {'type': 'base64',
'media_type': 'image/jpeg', 'data': b64}},
{'type': 'text', 'text':
f'Where is the {object_description} in this frame? '
'Answer with one of: top-left, top-right, centre, '
'bottom-left, bottom-right, not-visible.'}
]
}]
)
trajectory.append({
'timestamp': fi['timestamp'],
'position': response.content[0].text.strip()
})
return trajectoryフレーム数の上限への対処
Vision APIのコンテキストウィンドウには、1回のリクエストで送信できる画像数の上限があります(通常は5~20枚)。長い動画には階層的なアプローチを使用し、セグメントを個別に分析してから、2回目のLLM呼び出しでセグメントの要約を最終要約に統合します。
MAX_FRAMES_PER_REQUEST = 8
def hierarchical_video_analysis(frames: list, final_query: str) -> str:
import anthropic
client = anthropic.Anthropic(api_key='YOUR_API_KEY')
# Level 1: analyse each chunk
chunk_summaries = []
for i in range(0, len(frames), MAX_FRAMES_PER_REQUEST):
chunk = frames[i:i + MAX_FRAMES_PER_REQUEST]
ts = f'{chunk[0]["timestamp"]}s-{chunk[-1]["timestamp"]}s'
summary = describe_video_frames(
chunk, f'What happens in this segment ({ts})?'
)
chunk_summaries.append(f'Segment {ts}: {summary}')
# Level 2: synthesise all chunk summaries
combined = '\n'.join(chunk_summaries)
result = client.messages.create(
model='claude-opus-4-5',
max_tokens=512,
messages=[{
'role': 'user',
'content': f'Video segments:\n{combined}\n\n{final_query}'
}]
)
return result.content[0].text音声分析と動画分析の組み合わせ
より深く理解するには、フレーム単位の視覚分析とWhisperによる音声の文字起こしを組み合わせます。エージェントは各タイムスタンプで見えているものと話されている内容を関連付けられるため、会議の分析やチュートリアルのインデックス作成などの高度なタスクが可能になります。
def full_video_analysis(video_path: str) -> dict:
import subprocess
# Step 1: Extract audio track
audio_path = '/tmp/video_audio.mp3'
subprocess.run([
'ffmpeg', '-i', video_path, '-q:a', '0', '-map', 'a',
audio_path, '-y'
], capture_output=True)
# Step 2: Transcribe audio
transcript_data = transcribe_with_timestamps(audio_path)
# Step 3: Extract key frames
frames = extract_frames(video_path, '/tmp/key_frames', every_n_seconds=15.0)
# Step 4: Visual summary
visual_summary = hierarchical_video_analysis(
frames, 'Summarise the visual content.'
)
return {
'transcript': transcript_data['full_text'],
'transcript_segments': transcript_data['segments'],
'visual_summary': visual_summary,
'frame_count': len(frames)
}クリーンアップとフレーム管理
抽出したフレームはすぐに増えていきます。処理後は必ず一時フレームファイルを削除し、同じ動画を繰り返し分析する際の再抽出を避けるため、(video_path, frame_rate)のハッシュをキーとするフレームキャッシュを実装してください。
import os
import shutil
import hashlib
FRAME_CACHE_DIR = '/tmp/frame_cache'
def get_cache_key(video_path: str, every_n_seconds: float) -> str:
content = f'{video_path}:{every_n_seconds}'
return hashlib.md5(content.encode()).hexdigest()[:12]
def get_or_extract_frames(video_path: str, every_n_seconds: float) -> list:
key = get_cache_key(video_path, every_n_seconds)
cache_dir = os.path.join(FRAME_CACHE_DIR, key)
if os.path.exists(cache_dir):
print(f'Cache hit: {key}')
files = sorted(os.listdir(cache_dir))
return [
{'path': os.path.join(cache_dir, f),
'timestamp': float(f.split('_')[-1].replace('s.jpg', ''))}
for f in files if f.endswith('.jpg')
]
return extract_frames(video_path, cache_dir, every_n_seconds)
def clear_frame_cache():
if os.path.exists(FRAME_CACHE_DIR):
shutil.rmtree(FRAME_CACHE_DIR)
print('Frame cache cleared')知識チェック
動画分析では、均一なフレームサンプリングよりもシーン変化の検出が適しているのはなぜですか?
振り返り:エージェントにおける動画理解
よくできました!このレッスンの主なポイント:
- OpenCV:
cap.read()でフレームを抽出し、N秒ごとまたはシーン変化のタイミングでサンプリングする - フレームシーケンス:時間的なコンテキストを保持するため、タイムスタンプ付きで時系列順に送信する
- 階層的分析:バッチに分割 → セグメント要約 → 最終統合
- 複合分析:ffmpegで音声を抽出し、Whisperで文字起こしして、視覚フレームと照合する
- フレームキャッシュ:ハッシュをキーとするキャッシュディレクトリで重複した抽出を避ける
次は、クロスモーダル推論です。テキストが述べていることと画像が示していることが異なる場合を扱います。
AI チューターと学ぶ AI Agents — 無料
ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。
- コース
- 60
- レッスン
- 239
よくある質問
「エージェントにおける動画理解」レッスンは無料ですか?
はい。「エージェントにおける動画理解」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Agentsコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Agentsコースには全4レッスンが含まれています。
「エージェントにおける動画理解」で何を学びますか?
フレーム抽出、動画要約、クリップに対する時間的推論を扱います。 ブラウザで直接実行するハンズオンコードでAI Agentsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
AI Agentsを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAI Agentsは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「エージェントにおける動画理解」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAI Agentsレッスンでコードを書いて実行できますか?
はい。すべてのAI Agentsレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。