import numpy as np import cv2 # Segmented Absolute Difference (SAD) # Compares two frames, highlights differences in segments, and returns rectangles of changed areas def HighlightDifferences(BaseFrame: np.ndarray, NextFrame: np.ndarray, Columns: int = 20, Rows: int = 12, Threshold: float = 10, Padding: int = 1): FrameHeight, FrameWidth = BaseFrame.shape[:2] SegmentWidth = FrameWidth // Columns SegmentHeight = FrameHeight // Rows HighlightedFrame = BaseFrame.copy() TotalSegments = 0 SimilarSegments = 0 DifferentSegments = 0 DifferentSegmentMask = np.zeros((Rows, Columns), dtype=bool) for Row in range(Rows): for Col in range(Columns): Y = Row * SegmentHeight X = Col * SegmentWidth Y2 = FrameHeight if Row == Rows - 1 else Y + SegmentHeight X2 = FrameWidth if Col == Columns - 1 else X + SegmentWidth TotalSegments += 1 SegmentBase = BaseFrame[Y:Y2, X:X2] SegmentNext = NextFrame[Y:Y2, X:X2] GreyBase = cv2.cvtColor(SegmentBase, cv2.COLOR_BGR2GRAY) GreyNext = cv2.cvtColor(SegmentNext, cv2.COLOR_BGR2GRAY) BlurredBase = cv2.GaussianBlur(GreyBase, (5, 5), 0) BlurredNext = cv2.GaussianBlur(GreyNext, (5, 5), 0) AbsDiff = cv2.absdiff(BlurredBase, BlurredNext) MeanDiff = np.mean(AbsDiff) # type: ignore if MeanDiff > Threshold: DifferentSegments += 1 DifferentSegmentMask[Row, Col] = True else: SimilarSegments += 1 PaddedMask = DifferentSegmentMask.copy() for Row in range(Rows): for Col in range(Columns): if DifferentSegmentMask[Row, Col]: for PR in range(max(0, Row - Padding), min(Rows, Row + Padding + 1)): for PC in range(max(0, Col - Padding), min(Columns, Col + Padding + 1)): PaddedMask[PR, PC] = True for Row in range(Rows): for Col in range(Columns): Y = Row * SegmentHeight X = Col * SegmentWidth Y2 = FrameHeight if Row == Rows - 1 else Y + SegmentHeight X2 = FrameWidth if Col == Columns - 1 else X + SegmentWidth SegmentBase = BaseFrame[Y:Y2, X:X2] if PaddedMask[Row, Col]: HighlightedFrame[Y:Y2, X:X2] = cv2.addWeighted( HighlightedFrame[Y:Y2, X:X2], 0.5, np.full_like(SegmentBase, (0, 0, 255)), 0.2, 0 ) else: HighlightedFrame[Y:Y2, X:X2] = cv2.addWeighted( HighlightedFrame[Y:Y2, X:X2], 0.5, np.full_like(SegmentBase, (0, 255, 0)), 0.2, 0 ) SimilarityPercentage = (SimilarSegments / TotalSegments) * 100 TileCoords = [] for Row in range(Rows): for Col in range(Columns): if PaddedMask[Row, Col]: TileCoords.append((Col, Row)) return HighlightedFrame, DifferentSegments, SimilarityPercentage, TileCoords def GetRectanglesFromTiles(TileMask: np.ndarray, MinDifferentRatio: float = 0.8): Height, Width = TileMask.shape Visited = np.zeros_like(TileMask, dtype=bool) Rectangles = [] for Y in range(Height): for X in range(Width): if TileMask[Y, X] and not Visited[Y, X]: W = 1 H = 1 Expand = True while Expand: Expand = False if X + W < Width: NewCol = TileMask[Y:Y+H, X+W] & ~Visited[Y:Y+H, X+W] if np.any(NewCol): NewRect = TileMask[Y:Y+H, X:X+W+1] & ~Visited[Y:Y+H, X:X+W+1] Total = NewRect.size Diff = np.count_nonzero(NewRect) Ratio = Diff / Total if Ratio >= MinDifferentRatio and not np.any(Visited[Y:Y+H, X+W]): W += 1 Expand = True if Y + H < Height: NewRow = TileMask[Y+H, X:X+W] & ~Visited[Y+H, X:X+W] if np.any(NewRow): NewRect = TileMask[Y:Y+H+1, X:X+W] & ~Visited[Y:Y+H+1, X:X+W] Total = NewRect.size Diff = np.count_nonzero(NewRect) Ratio = Diff / Total if Ratio >= MinDifferentRatio and not np.any(Visited[Y+H, X:X+W]): H += 1 Expand = True Visited[Y:Y+H, X:X+W] = True Rectangles.append((X, Y, W, H)) return Rectangles def GetDifferenceRectangles(BaseFrame, NextFrame, Columns=20, Rows=12, Threshold=5, Padding=1): HighlightedFrame, DifferentSegments, SimilarPercentage, TileCoords = HighlightDifferences( BaseFrame, NextFrame, Columns=Columns, Rows=Rows, Threshold=Threshold, Padding=Padding ) TileMask = np.zeros((Rows, Columns), dtype=bool) for Col, Row in TileCoords: if Row < TileMask.shape[0] and Col < TileMask.shape[1]: TileMask[Row, Col] = True Rectangles = GetRectanglesFromTiles(TileMask, MinDifferentRatio=0.7) return { 'HighlightedFrame': HighlightedFrame, 'Rectangles': Rectangles, 'SimilarPercentage': SimilarPercentage, 'TileCoords': TileCoords, 'Columns': Columns, 'Rows': Rows }