Spaces:
Running
on
Zero
Running
on
Zero
import numpy as np | |
import cv2 | |
import time | |
import logging | |
# Set up logging | |
logging.basicConfig(level=logging.INFO) | |
Logger = logging.getLogger(__name__) | |
def MergeBoxes(Boxes, Padding=5): | |
if len(Boxes) <= 1: | |
return Boxes | |
MergedOccurred = True | |
while MergedOccurred: | |
MergedOccurred = False | |
NewBoxes = [] | |
Boxes.sort(key=lambda b: b[0]) | |
Used = [False] * len(Boxes) | |
for Index in range(len(Boxes)): | |
if Used[Index]: | |
continue | |
CurrentBox = list(Boxes[Index]) | |
Used[Index] = True | |
for J in range(Index + 1, len(Boxes)): | |
if Used[J]: | |
continue | |
NextBox = Boxes[J] | |
OverlapX = max(CurrentBox[0], NextBox[0]) <= min(CurrentBox[0] + CurrentBox[2], NextBox[0] + NextBox[2]) + Padding | |
OverlapY = max(CurrentBox[1], NextBox[1]) <= min(CurrentBox[1] + CurrentBox[3], NextBox[1] + NextBox[3]) + Padding | |
if OverlapX and OverlapY: | |
NewX = min(CurrentBox[0], NextBox[0]) | |
NewY = min(CurrentBox[1], NextBox[1]) | |
NewW = max(CurrentBox[0] + CurrentBox[2], NextBox[0] + NextBox[2]) - NewX | |
NewH = max(CurrentBox[1] + CurrentBox[3], NextBox[1] + NextBox[3]) - NewY | |
CurrentBox = [NewX, NewY, NewW, NewH] | |
Used[J] = True | |
MergedOccurred = True | |
NewBoxes.append(tuple(CurrentBox)) | |
Boxes = NewBoxes | |
return Boxes | |
def GetChangeMask(Image1, Image2, Threshold=25, MinArea=100): | |
if Image1.shape != Image2.shape: | |
Logger.warning(f'Image shapes differ: {Image1.shape} vs {Image2.shape}. Resizing Image2.') | |
Image2 = cv2.resize(Image2, (Image1.shape[1], Image1.shape[0])) | |
Gray1 = cv2.cvtColor(Image1, cv2.COLOR_BGR2GRAY) | |
Gray2 = cv2.cvtColor(Image2, cv2.COLOR_BGR2GRAY) | |
Blur1 = cv2.GaussianBlur(Gray1, (5, 5), 0) | |
Blur2 = cv2.GaussianBlur(Gray2, (5, 5), 0) | |
DiffFrame = cv2.absdiff(Blur1, Blur2) | |
_, ThresholdCalc = cv2.threshold(DiffFrame, Threshold, 255, cv2.THRESH_BINARY) | |
Kernel = np.ones((5, 5), np.uint8) | |
DilatedThreshold = cv2.dilate(ThresholdCalc, Kernel, iterations=2) | |
Contours, _ = cv2.findContours(DilatedThreshold, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
OutputMask = np.zeros_like(DilatedThreshold) | |
ValidContours = 0 | |
if Contours: | |
for Contour in Contours: | |
if cv2.contourArea(Contour) > MinArea: | |
cv2.drawContours(OutputMask, [Contour], -1, 255, -1) # type: ignore | |
ValidContours +=1 | |
Logger.info(f'GetChangeMask: Found {len(Contours)} raw contours, kept {ValidContours} after MinArea filter ({MinArea}).') | |
return OutputMask | |
def VisualizeDifferences(Image1Path, Image2Path, OutputPath, Threshold=25, MinArea=100, OutlineColor=(0, 255, 0), FillColor=(0, 180, 0), FillAlpha=0.3): | |
Logger.info(f'π¨ Visualizing differences between {Image1Path} and {Image2Path}') | |
Image1 = cv2.imread(Image1Path) | |
Image2 = cv2.imread(Image2Path) | |
if Image1 is None or Image2 is None: | |
Logger.error(f'β Error loading images for visualization: {Image1Path} or {Image2Path}') | |
return | |
if Image1.shape != Image2.shape: | |
Logger.warning(f'β οΈ Image shapes differ: {Image1.shape} vs {Image2.shape}. Resizing Image2 for visualization.') | |
Image2 = cv2.resize(Image2, (Image1.shape[1], Image1.shape[0])) | |
ChangedMask = GetChangeMask(Image1, Image2, Threshold, MinArea) | |
OutputImage = Image2.copy() | |
Overlay = OutputImage.copy() | |
# Apply fill color to changed areas | |
Overlay[ChangedMask == 255] = FillColor | |
cv2.addWeighted(Overlay, FillAlpha, OutputImage, 1 - FillAlpha, 0, OutputImage) | |
# Find contours of the changed areas to draw outlines | |
Contours, _ = cv2.findContours(ChangedMask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
cv2.drawContours(OutputImage, Contours, -1, OutlineColor, 2) | |
Logger.info(f'π¨ Drew {len(Contours)} difference regions.') | |
try: | |
cv2.imwrite(OutputPath, OutputImage) | |
Logger.info(f'πΎ Saved difference visualization to {OutputPath}') | |
except Exception as E: | |
Logger.error(f'β Failed to save visualization {OutputPath}: {E}') | |
# --- Function to be used in App.py for upscaling --- | |
def GetChangedRegions(Image1, Image2, Threshold=25, Padding=10, MinArea=100, MergePadding=5): | |
StartTime = time.time() | |
Logger.info('π Comparing images...') | |
if Image1 is None or Image2 is None: | |
Logger.error('β Cannot compare None images.') | |
return [] | |
if Image1.shape != Image2.shape: | |
Logger.warning(f'β οΈ Image shapes differ: {Image1.shape} vs {Image2.shape}. Resizing Image2.') | |
Image2 = cv2.resize(Image2, (Image1.shape[1], Image1.shape[0])) | |
Gray1 = cv2.cvtColor(Image1, cv2.COLOR_BGR2GRAY) | |
Gray2 = cv2.cvtColor(Image2, cv2.COLOR_BGR2GRAY) | |
Blur1 = cv2.GaussianBlur(Gray1, (5, 5), 0) | |
Blur2 = cv2.GaussianBlur(Gray2, (5, 5), 0) | |
DiffFrame = cv2.absdiff(Blur1, Blur2) | |
_, ThresholdCalc = cv2.threshold(DiffFrame, Threshold, 255, cv2.THRESH_BINARY) | |
Kernel = np.ones((5, 5), np.uint8) | |
DilatedThreshold = cv2.dilate(ThresholdCalc, Kernel, iterations=2) | |
Contours, _ = cv2.findContours(DilatedThreshold, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
Logger.info(f'π Found {len(Contours)} raw contours.') | |
BoundingBoxes = [] | |
if Contours: | |
ValidContours = 0 | |
for Contour in Contours: | |
ContourArea = cv2.contourArea(Contour) | |
if ContourArea > MinArea: | |
ValidContours += 1 | |
X, Y, W, H = cv2.boundingRect(Contour) | |
PaddedX = max(0, X - Padding) | |
PaddedY = max(0, Y - Padding) | |
MaxW = Image1.shape[1] - PaddedX | |
MaxH = Image1.shape[0] - PaddedY | |
PaddedW = min(W + (Padding * 2), MaxW) | |
PaddedH = min(H + (Padding * 2), MaxH) | |
BoundingBoxes.append((PaddedX, PaddedY, PaddedW, PaddedH)) | |
Logger.info(f'π Filtered {ValidContours} contours based on MinArea ({MinArea}).') | |
InitialBoxCount = len(BoundingBoxes) | |
MergedBoundingBoxes = MergeBoxes(BoundingBoxes, MergePadding) | |
EndTime = time.time() | |
if MergedBoundingBoxes: | |
Logger.info(f'π¦ Merged {InitialBoxCount} boxes into {len(MergedBoundingBoxes)} regions.') | |
else: | |
Logger.info('β No significant changed regions found after filtering and merging.') | |
Logger.info(f'β±οΈ Region finding took {EndTime - StartTime:.3f}s') | |
return MergedBoundingBoxes | |
# Example call for the new visualization function | |
VisualizeDifferences(r'C:\Users\joris\Pictures\frame_01660.png', r'C:\Users\joris\Pictures\frame_01661.png', './Diff.png', 25, 100, (0, 255, 0), (0, 180, 0), 0.3) |