import cv2 import numpy as np from typing import List, Tuple, Optional class TemporalTracker: def __init__(self, diff_threshold: float = 0.05): self.diff_threshold = diff_threshold self.last_frame = None self.current_page_frames = [] self.unique_pages = [] self.frame_count = 0 self.stable_frame_count = 0 def _extract_print_channel(self, bgr: np.ndarray) -> np.ndarray: # 흑백으로 변환하여 악보의 선, 숫자 등 고정 텍스트 기호 영역 픽셀을 명확히 함 gray = np.max(bgr, axis=2) _, binary = cv2.threshold(gray, 230, 255, cv2.THRESH_BINARY) return binary def process_frame(self, frame: np.ndarray, tracking_channel: Optional[np.ndarray] = None) -> None: if tracking_channel is not None: frame_gray = tracking_channel.copy() else: frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) self.frame_count += 1 if self.last_frame is None: self.last_frame = frame_gray.copy() self.current_page_frames.append(frame.copy()) self.stable_frame_count = 1 return diff = cv2.absdiff(self.last_frame, frame_gray) _, thresh = cv2.threshold(diff, 50, 255, cv2.THRESH_BINARY) # 커서 이동 vs 페이지 전환을 명확히 구분하기 위한 혁신적 지표 도입 # 커서는 세로로 길기 때문에 픽셀 면적(area)으로는 비중이 크지만 가로 폭(column)으로는 매우 좁음(전체 폭의 <5%). # 반면 실제 페이지 전환은 가로 전체에 걸쳐 악보가 바뀌므로(>15%). col_sums = np.sum(thresh > 0, axis=0) h, w = thresh.shape # 한 열에서 높이의 3% 이상 픽셀이 변한 경우 "유의미하게 변한 열"로 간주 changed_cols = np.sum(col_sums > (h * 0.03)) diff_ratio = changed_cols / w if diff_ratio > 0.15: # 가로폭의 15% 이상이 완전히 바뀌면 페이지 전환 self.stable_frame_count = 0 if len(self.current_page_frames) > 0: print(f"[Tracker] Page Flip Detected! (Col Change: {diff_ratio*100:.1f}%) -> Saving Median Page {len(self.unique_pages)+1}") median_page = np.median(self.current_page_frames, axis=0).astype(np.uint8) self.unique_pages.append(median_page) self.current_page_frames = [] self.last_frame = frame_gray.copy() else: self.stable_frame_count += 1 if self.stable_frame_count % 3 == 0: self.current_page_frames.append(frame.copy()) def get_final_panorama(self) -> Optional[np.ndarray]: # 시스템 호환성을 위해 이름만 panorama 유지 (실제로는 불필요해진 로직) return None def get_unique_pages(self) -> List[np.ndarray]: if len(self.current_page_frames) > 0: median_page = np.median(self.current_page_frames, axis=0).astype(np.uint8) self.unique_pages.append(median_page) print(f"[Tracker] Saving Final Median Page {len(self.unique_pages)}") return self.unique_pages