когда мы считаем здесь MAE для всего видеоряда пос...

Tạo vào: 17 tháng 9, 2025

Trả lời bằng GPT-4.5 bởi Chat01

Câu hỏi

когда мы считаем здесь MAE для всего видеоряда после синхронизации, между чем усредняется MAE? нужно чтобы усреднялось между всеми ключевыми точками на всех кадразfrom pathlib import Path

import cv2
import numpy as np
import pandas as pd
from tqdm import tqdm

from detectors.sift_detector import SIFTDetector
from detectors.orb_detector import ORBDetector
from detectors.akaze_detector import AKAZEDetector
from video_sync.metrics import compute_matches_and_inliers
from video_sync.plot_sync import (
plot_distance_histogram,
plot_inliers_heatmap_normalized
)

def get_safe_frame_idxs(video1: Path, video2: Path,
offset_min: int, offset_max: int,
frame_skip: int) -> list[int]:
"""
Возвращает список индексов кадров, которые есть в обоих видео
при любом смещении в [offset_min, offset_max].
"""
cap1 = cv2.VideoCapture(str(video1))
cap2 = cv2.VideoCapture(str(video2))
total1 = int(cap1.get(cv2.CAP_PROP_FRAME_COUNT))
total2 = int(cap2.get(cv2.CAP_PROP_FRAME_COUNT))
cap1.release()
cap2.release()

text
start = abs(offset_min) end = min(total1, total2) - abs(offset_max) return list(range(start, end, frame_skip))

def compute_metrics_for_offset(video1: Path, video2: Path,
offset: int, frame_idxs: list[int],
ransac_thresh: float,
det) -> tuple[float,float,list[float]]:
"""
Для заданного offset и заранее заданного набора frame_idxs
вычисляет MAE, MSE и список всех inlier-расстояний.
"""
cap1 = cv2.VideoCapture(str(video1))
cap2 = cv2.VideoCapture(str(video2))
fps1 = cap1.get(cv2.CAP_PROP_FPS)
fps2 = cap2.get(cv2.CAP_PROP_FPS)

text
mae_list: list[float] = [] mse_list: list[float] = [] all_dists: list[float] = [] for frame_idx in tqdm(frame_idxs, desc=f"[{det.__class__.__name__}] offset={offset}", leave=False): cap1.set(cv2.CAP_PROP_POS_FRAMES, frame_idx) ret1, f1 = cap1.read() if not ret1: continue t2 = frame_idx/fps1 + offset/fps1 idx2 = int(round(t2 * fps2)) cap2.set(cv2.CAP_PROP_POS_FRAMES, idx2) ret2, f2 = cap2.read() if not ret2: continue g1 = cv2.cvtColor(f1, cv2.COLOR_BGR2GRAY) g2 = cv2.cvtColor(f2, cv2.COLOR_BGR2GRAY) # quick inlier check _, inliers = compute_matches_and_inliers(det, g1, g2, ransac_thresh) if inliers < 4: continue # compute distances for inliers kp1, des1 = det.detect_and_compute(g1) kp2, des2 = det.detect_and_compute(g2) matches = det.match_features(des1, des2) pts1 = np.float32([kp1[m.queryIdx].pt for m in matches]).reshape(-1,1,2) pts2 = np.float32([kp2[m.trainIdx].pt for m in matches]).reshape(-1,1,2) _, mask = cv2.findHomography(pts1, pts2, cv2.RANSAC, ransac_thresh) if mask is None: continue mask = mask.ravel().astype(bool) p1 = pts1[mask].reshape(-1,2) p2 = pts2[mask].reshape(-1,2) if p1.size == 0: continue d = np.linalg.norm(p1 - p2, axis=1) mae_list.append(d.mean()) mse_list.append((d**2).mean()) all_dists.extend(d.tolist()) cap1.release() cap2.release() overall_mae = float(np.nanmean(mae_list)) if mae_list else float('nan') overall_mse = float(np.nanmean(mse_list)) if mse_list else float('nan') return overall_mae, overall_mse, all_dists

OFFSET_MIN = -30
OFFSET_MAX = 30

def main():
video1 = Path(r"D:\Downloads\samples\samples\sony\C0430.MP4")
video2 = Path(r"D:\Downloads\samples\samples\huawei\VID_20250715_170534.mp4")
offsets_csv = Path("output/video_sync/offset_scores.csv")
stats_csv = Path("output/video_sync/video_sync_stats.csv")
out_dir = Path("output/video_sync/metrics")
frame_skip = 2
ransac_thresh = 5.0

text
out_dir.mkdir(parents=True, exist_ok=True) df_off = pd.read_csv(offsets_csv) df_stats = pd.read_csv(stats_csv) # выбираем лучший offset по max inliers best_offsets = { alg: int(sub.sort_values("sum_inliers", ascending=False) .iloc[0]["offset"]) for alg, sub in df_off.groupby("alg") } print("Best offsets:", best_offsets) # формируем безопасные индексы кадров frame_idxs = get_safe_frame_idxs( video1, video2, OFFSET_MIN, OFFSET_MAX, frame_skip ) # детекторы detectors = { "SIFT": SIFTDetector(nfeatures=300), "ORB": ORBDetector(nfeatures=300), "AKAZE": AKAZEDetector(), } # подсчёт MAE/MSE по фиксированным кадрам records = [] distances_map = {} for alg, offset in best_offsets.items(): det = detectors[alg] mae, mse, dists = compute_metrics_for_offset( video1, video2, offset, frame_idxs, ransac_thresh, det ) records.append({ "alg": alg, "offset": offset, "MAE": mae, "MSE": mse }) distances_map[alg] = dists df_metrics = pd.DataFrame(records) df_metrics.to_csv(out_dir/"fixed_offset_metrics.csv", index=False, encoding="utf-8-sig") print("Saved fixed_offset_metrics.csv:\n", df_metrics) # строим heatmap inliers over time plot_inliers_heatmap_normalized(df_stats, out_dir) print("Saved heatmap → inliers_heatmap.png") # строим гистограммы расстояний for alg, dists in distances_map.items(): plot_distance_histogram(np.array(dists), out_dir, alg) print(f"Saved {alg}_distance_histogram.png") print("Готово. Результаты в", out_dir)

if name == "main":
main()

Trả lời

В данном коде MAE усредняется следующим образом:

Пошагово объясню, между чем именно усредняется MAE:

  1. Сначала для каждого кадра, который есть в обоих видеорядах (с учетом лучшего вычисленного offset), вычисляются координаты всех ключевых точек, совпавших по алгоритму (SIFT, ORB, AKAZE).

  2. Затем для этих пар совпавших точек из кадров двух видео рассчитывается расстояние (евклидово расстояние между точками на кадре 1 и точками на кадре 2).

  3. Затем рассчитывается среднее расстояние (MAE) для всех ключевых точек в этом конкретном кадре.

  4. После этого происходит усреднение всех MAE, полученных на каждом кадре, чтобы получить общий итоговый MAE для всего видеоряда.


Таким образом:

  • Для одного кадра MAE = среднее евклидово расстояние между всеми совпавшими ключевыми точками.
  • Для всего видеоряда MAE = среднее значение всех кадровых MAE, то есть усреднение по всем ключевым точкам на всех кадрах.

Если требуется строгое усреднение между всеми ключевыми точками на всех кадрах (без промежуточного усреднения по кадрам), код нужно изменить так:

Изменение:

python
# Вместо этого: overall_mae = float(np.nanmean(mae_list)) if mae_list else float('nan') # Используйте это: overall_mae = float(np.nanmean(all_dists)) if all_dists else float('nan')

Тогда итоговое MAE будет точно отражать среднее расстояние всех ключевых точек за все кадры.

Câu hỏi

poetry run python -m scripts.metrics
Best offsets: {'AKAZE': 12, 'ORB': 4, 'SIFT': 6}
Saved fixed_offset_metrics.csv:
alg offset MAE MSE
0 AKAZE 12 NaN NaN
1 ORB 4 NaN NaN
2 SIFT 6 NaN NaN
Saved heatmap → inliers_heatmap.png
Saved AKAZE_distance_histogram.png
Saved ORB_distance_histogram.png
Saved SIFT_distance_histogram.png
Готово. Результаты в output\video_sync(frame_skip=5)\metrics
PS C:\Users\01\Documents\GitHub\temporal_alignment>

Trả lời

Причина того, что у тебя получился NaN в MAE и MSE:

Что именно происходит?
В коде есть проверка, когда недостаточно inliers (совпавших точек) на кадре (меньше 4), кадр пропускается:

python
_, inliers = compute_matches_and_inliers(det, g1, g2, ransac_thresh) if inliers < 4: continue

Также есть дополнительная проверка:

python
mask = mask.ravel().astype(bool) p1 = pts1[mask].reshape(-1,2) p2 = pts2[mask].reshape(-1,2) if p1.size == 0: continue

Если по всем кадрам не набирается ни одного валидного случая с количеством точек >4, то список расстояний остаётся пустым (mae_list и mse_list), что приводит к NaN.


🔧 Как это исправить:

1. Убедись, что ты используешь достаточное количество кадров для анализа:

  • Попробуй уменьшить шаг пропуска кадров (frame_skip) с 5 до 1 или 2.
python
frame_skip = 1 # или 2

2. Проверь порог RANSAC:

  • Возможно, порог RANSAC слишком строгий. Попробуй увеличить его:
python
ransac_thresh = 10.0

3. Проверь, что offset действительно верный:

  • Если выбранный offset ({'AKAZE': 12, 'ORB': 4, 'SIFT': 6}) слишком большой, кадры могут быть просто не похожи друг на друга.
  • Попробуй вручную задать небольшой offset, например offset=0 или offset=1, и проверить.

4. Убедись в правильности путей к видеофайлам:

  • Возможно, видео открываются, но кадры некорректно читаются (например, кодеки). Выведи проверку:
python
ret1, f1 = cap1.read() ret2, f2 = cap2.read() if not (ret1 and ret2): print(f"Failed to read frames: {frame_idx}, {idx2}")

🧪 Что сейчас сделать?

Измени в scripts.metrics эти строки и перезапусти:

python
frame_skip = 1 # <–– важно уменьшить ransac_thresh = 10.0 # <–– увеличить порог

Затем снова запусти:

bash
poetry run python -m scripts.metrics

После этого проверь, исчезли ли NaN.

Chia sẻ Q&A này