AI Agents · Lekcja

Rozumienie wideo przez agentów

Ekstrakcja klatek, podsumowywanie wideo i rozumowanie czasowe na podstawie klipów.

Lekcja 3 z 413 kroki

Rozumienie wideo przez agentów to bezpłatna lekcja AI Agents na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Agents, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Agents zawiera 4 lekcji w sumie.

Rozumienie wideo przez agentów

Większość modeli LLM nie potrafi bezpośrednio przetwarzać plików wideo, ale agenci mogą analizować wideo, wyodrębniając reprezentatywne klatki i wysyłając je jako obrazy. Następnie agent analizuje sekwencję obrazów, aby wykrywać zdarzenia, zmiany i wzorce zachodzące w czasie.

Instalowanie OpenCV do ekstrakcji klatek

OpenCV (cv2) to standardowa biblioteka do przetwarzania wideo w Pythonie. Umożliwia otwieranie plików wideo, odczytywanie ich metadanych (liczby klatek na sekundę, rozdzielczości i liczby klatek) oraz wyodrębnianie pojedynczych klatek jako tablic 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)

Ekstrakcja klatek co N sekund

W przypadku większości zadań związanych z rozumieniem wideo nie trzeba przetwarzać każdej klatki. Pobieranie jednej klatki co N sekund zapewnia reprezentatywne podsumowanie zawartości wideo. Przy częstotliwości 1 klatki na sekundę 60-sekundowe wideo daje 60 klatek.

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

Wykrywanie zmian sceny

Zamiast równomiernego próbkowania można wyodrębniać klatki na granicach scen — w momentach, gdy zawartość obrazu znacząco się zmienia. Dzięki temu każde wywołanie API dostarcza bardziej przydatne klatki. Należy użyć różnicowania klatek: jeśli średnia różnica bezwzględna między kolejnymi klatkami przekroczy ustalony próg, oznacza to zmianę sceny.

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

Wysyłanie sekwencji klatek do Vision API

Wiele wyodrębnionych klatek należy wysłać w jednej wiadomości do Vision API. Przy każdej klatce należy uwzględnić jej znacznik czasu, aby model mógł analizować kolejność temporalną i czas trwania. W promptcie należy wyraźnie poprosić o rozumowanie temporalne.

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

Prompty do rozumowania temporalnego

Rozumowanie temporalne wymaga odpowiednio sformułowanego promptu. Należy poprosić model o bezpośrednie porównanie klatek, wskazanie zmian, oszacowanie czasu trwania działań oraz określenie związków przyczynowych między klatkami.

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)

Potok podsumowywania wideo

Kompletny potok podsumowywania wideo wygląda następująco: wyodrębnianie klatek → wysyłanie ich partiami do modelu wizyjnego → zebranie podsumowań poszczególnych partii → synteza końcowego podsumowania. Przetwarzanie partiami zapobiega przekroczeniu limitu okna kontekstowego.

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

Śledzenie obiektów między klatkami

Należy poprosić model wizyjny o śledzenie określonych obiektów w sekwencji klatek. Przykład: „W każdej klatce opisz położenie czerwonego samochodu: lewy górny róg, prawy górny róg, środek, lewy dolny róg, prawy dolny róg”. Umożliwia to utworzenie przybliżonej trajektorii ruchu bez używania algorytmów śledzenia z zakresu widzenia komputerowego.

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

Obsługa limitów liczby klatek

Okna kontekstowe Vision API ograniczają liczbę obrazów, które można wysłać w jednym żądaniu (zwykle od 5 do 20 obrazów). W przypadku długich nagrań należy zastosować podejście hierarchiczne: niezależnie analizować segmenty, a następnie połączyć ich podsumowania w końcowe podsumowanie za pomocą drugiego wywołania 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

Łączenie analizy audio i wideo

Aby uzyskać najpełniejsze rozumienie, należy połączyć analizę obrazu na poziomie klatek z transkrypcją audio za pomocą Whisper. Agent może korelować to, co widać, z tym, co zostało powiedziane, dla każdego znacznika czasu, co umożliwia realizację zaawansowanych zadań, takich jak analiza spotkań czy indeksowanie samouczków.

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)
    }

Czyszczenie i zarządzanie klatkami

Wyodrębnione klatki mogą szybko zajmować dużo miejsca. Po przetworzeniu należy zawsze usuwać tymczasowe pliki klatek oraz zaimplementować pamięć podręczną klatek opartą na hashu klucza (video_path, frame_rate), aby uniknąć ponownej ekstrakcji podczas kolejnej analizy tego samego wideo.

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')

Sprawdzenie wiedzy

Dlaczego wykrywanie zmian sceny jest lepsze od równomiernego próbkowania klatek podczas analizy wideo?

Podsumowanie: rozumienie wideo przez agentów

Świetna praca! Najważniejsze informacje z tej lekcji:

  • OpenCV: klatki należy wyodrębniać za pomocą cap.read(); można je próbkować co N sekund lub przy zmianach sceny
  • Sekwencje klatek: należy wysyłać je w kolejności chronologicznej wraz ze znacznikami czasu, aby zachować kontekst temporalny
  • Analiza hierarchiczna: podział na partie → podsumowania segmentów → synteza końcowa
  • Analiza połączona: ffmpeg wyodrębnia audio, Whisper wykonuje transkrypcję, a wyniki należy powiązać z klatkami obrazu
  • Pamięć podręczna klatek: należy unikać zbędnej ekstrakcji za pomocą katalogu pamięci podręcznej identyfikowanego hashem

Następny temat: rozumowanie multimodalne — gdy tekst mówi jedno, a obraz pokazuje coś innego.

Bezpłatny start

Ucz się AI Agents dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
60
Lekcje
239

Często zadawane pytania

Czy lekcja „Rozumienie wideo przez agentów” jest bezpłatna?

Tak — pełny tekst „Rozumienie wideo przez agentów” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Agents, przejdź na CoddyKit PRO. Kurs AI Agents zawiera 4 lekcji w sumie.

Co nauczysz się w „Rozumienie wideo przez agentów”?

Ekstrakcja klatek, podsumowywanie wideo i rozumowanie czasowe na podstawie klipów. Ćwiczysz AI Agents z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Agents?

Nie wymagamy żadnego doświadczenia. AI Agents w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Rozumienie wideo przez agentów”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Agents?

Tak. Każda lekcja AI Agents zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Agenci obrazu i tekstu z Claude Vision i GPT-4V
  2. Przepływy pracy agentów audio i tekstu
  3. Rozumienie wideo przez agentów
  4. Wzorce rozumowania między modalnościami
← Powrót do AI Agents