Zero2x / Diff.py
Hyphonical's picture
πŸš€ Upload Project From My GitHub
b09e573
raw
history blame
6.9 kB
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)