diff --git a/temporal_correlation.py b/temporal_correlation.py index 1a6fc3f..60d2417 100644 --- a/temporal_correlation.py +++ b/temporal_correlation.py @@ -1,95 +1,104 @@ import subprocess import numpy as np import matplotlib.pyplot as plt -from scipy.interpolate import interp1d +import sys # --- Setup --- video_a = "/home/honney/projects/mkv/Saga of Tanya the Evil/Season 1/Saga Of Tanya The Evil - S01E01 - The Devil Of The Rhine.mkv" video_b = "/home/honney/projects/mkv/Saga of Tanya the Evil (2017-2017) [imdbid-tt6455986]/Season 01/Saga of Tanya the Evil S01E001.mkv" -FPS_NATIVE = 24000/1001 WIDTH = 160 HEIGHT = int((WIDTH/16)*9) -MAX_OFFSET_SECONDS = 1 +FPS = 24000/1001 +MAX_OFFSET_SECONDS = 2 # ±2 Sekunden Suche -# FAKTOR FÜR PRÄZISION: -# 2 = Doppelte FPS für die Rechnung (Sub-Frame) -# 10 = Zehnfache Präzision (reicht für Mikrosekunden-Sync) -UPSAMPLE_FACTOR = 10 -FPS_VIRTUAL = FPS_NATIVE * UPSAMPLE_FACTOR def extract_hist_signal(video): + """Extrahiert pro Frame einen Histogrammvektor unter Nutzung von VA-API (AMD GPU).""" + # Wir nutzen vaapi für Hardware-Dekodierung und Scaling proc = subprocess.Popen([ - "ffmpeg", "-loglevel", "error", "-i", video, - "-vf", f"scale={WIDTH}:{HEIGHT},format=gray", - "-f", "rawvideo", "-" + "ffmpeg", + "-loglevel", "error", + "-hwaccel", "vaapi", # Nutze VA-API Hardware-Beschleunigung + "-hwaccel_device", "/dev/dri/renderD128", # Pfad zur RX 7900 XTX + "-hwaccel_output_format", "vaapi", + "-i", video, + "-vf", ( + f"fps={FPS}," # Framerate Anpassung + f"scale_vaapi=w={WIDTH}:h={HEIGHT}:format=gray8," # GPU-Scaling + Grayscale + "hwdownload,format=gray8" # Zurück in den RAM schieben + ), + "-f", "rawvideo", + "-" ], stdout=subprocess.PIPE) frame_size = WIDTH * HEIGHT hist_signal = [] + + # Der restliche Loop bleibt gleich, da die Histogramm-Berechnung + # (np.histogram) weiterhin auf der CPU via NumPy erfolgt. while True: data = proc.stdout.read(frame_size) - if not data or len(data) < frame_size: break + if not data or len(data) < frame_size: + break frame = np.frombuffer(data, dtype=np.uint8) - hist, _ = np.histogram(frame, bins=64, range=(0, 255)) - hist_signal.append(hist / (np.sum(hist) + 1e-6)) + hist, _ = np.histogram(frame, bins=32, range=(0, 255)) + hist_signal.append(hist / np.sum(hist)) + proc.wait() return np.array(hist_signal) -print(f"Extracting signals (Native FPS: {FPS_NATIVE:.4f})...") -a_raw = extract_hist_signal(video_a) -b_raw = extract_hist_signal(video_b) -# 1. In 1D-Signal umwandeln (Helligkeits-Schnitt) -sig_a_raw = np.mean(a_raw, axis=1) -sig_b_raw = np.mean(b_raw, axis=1) +print("Extracting signal of video A") +a = extract_hist_signal(video_a) -# 2. UPSAMPLING (Die "doppelte FPS" Logik) -# Wir erstellen eine glatte Kurve durch die Frames und tasten sie feiner ab -def upsample_signal(sig, factor): - x = np.arange(len(sig)) - f = interp1d(x, sig, kind='cubic') # Kubische Interpolation für weiche Übergänge - x_new = np.linspace(0, len(sig) - 1, len(sig) * factor) - return f(x_new) +print("Extracting signal of video B") +b = extract_hist_signal(video_b) -print(f"Upsampling signals by factor {UPSAMPLE_FACTOR}...") -sig_a = upsample_signal(sig_a_raw, UPSAMPLE_FACTOR) -sig_b = upsample_signal(sig_b_raw, UPSAMPLE_FACTOR) +print("Calculating cross correlation") -# 3. Zentrieren für bessere Korrelation -sig_a -= np.mean(sig_a) -sig_b -= np.mean(sig_b) +max_lag_frames = int(MAX_OFFSET_SECONDS * FPS) +n_a, n_bins = a.shape +n_b, _ = b.shape -print("Calculating high-precision cross correlation...") -corr = np.correlate(sig_a, sig_b, mode='full') -lags = np.arange(-len(sig_b) + 1, len(sig_a)) +lags = np.arange(-max_lag_frames, max_lag_frames + 1) +corr = [] -# Suchbereich einschränken (jetzt basierend auf virtueller FPS) -max_lag_v = int(MAX_OFFSET_SECONDS * FPS_VIRTUAL) -search_range = (lags >= -max_lag_v) & (lags <= max_lag_v) +for lag in lags: + if lag < 0: + # Offset negativ → Video B vor Video A + sub_a = a[:lag] # bis lag Frames von Video A + sub_b = b[-lag:] # von -lag bis Ende von Video B + else: + sub_a = a[lag:] # ab lag bis Ende von Video A + sub_b = b[:n_b - lag] # bis n_b - lag von Video B -filtered_corr = corr[search_range] -filtered_lags = lags[search_range] + # Frames angleichen + min_len = min(len(sub_a), len(sub_b)) + if min_len == 0: + corr.append(0) + continue + sub_a = sub_a[:min_len] + sub_b = sub_b[:min_len] -best_idx = np.argmax(filtered_corr) -best_lag_v = filtered_lags[best_idx] + corr.append(np.sum(sub_a * sub_b)) -# Der Offset berechnet sich nun aus dem virtuellen Lag und der virtuellen FPS -best_offset_seconds = best_lag_v / FPS_VIRTUAL +corr = np.array(corr) +best_index = np.argmax(corr) +best_lag = lags[best_index] -print("-" * 30) -print(f"Best virtual lag: {best_lag_v} units") -print(f"Resulting frames: {best_lag_v / UPSAMPLE_FACTOR:.3f}") -print(f"Best offset seconds: {best_offset_seconds:.12f}") -print("-" * 30) +print("Best offset frames:", best_lag) +print("Best offset seconds:", best_lag / FPS) + +sys.exit() # Plot -plt.figure(figsize=(12, 5)) -plt.plot(filtered_lags / FPS_VIRTUAL, filtered_corr) -plt.axvline(best_offset_seconds, color='r', linestyle='--', label=f"Peak: {best_offset_seconds:.6f}s") -plt.title(f"High-Precision Sync (Upsampled x{UPSAMPLE_FACTOR})") +plt.figure(figsize=(10, 5)) +plt.plot(lags / FPS, corr, marker='o') +plt.axvline(best_lag / FPS, color='r', linestyle='--', label=f"Best offset = {best_lag / FPS:.3f}s") plt.xlabel("Offset (seconds)") -plt.ylabel("Correlation") +plt.ylabel("Cross correlation") +plt.title("Video cross correlation (Histogram-based)") plt.legend() plt.grid(True) plt.show() \ No newline at end of file