Spaces:
Running
Running
Commit
·
cc5a426
1
Parent(s):
5a7f927
✨ Add Motion Detection Functionality with ORB Algorithm
Browse files- App.py +52 -4
- Scripts/ORB.py +28 -0
App.py
CHANGED
|
@@ -13,6 +13,7 @@ from rich.console import Console
|
|
| 13 |
from rich.logging import RichHandler
|
| 14 |
|
| 15 |
from Scripts.SAD import GetDifferenceRectangles
|
|
|
|
| 16 |
|
| 17 |
# ============================== #
|
| 18 |
# Core Settings #
|
|
@@ -176,7 +177,7 @@ class Upscaler:
|
|
| 176 |
return OutputFrame, SimilarityPercentage, Rectangles, RegionLog, UseRegions
|
| 177 |
|
| 178 |
@spaces.GPU
|
| 179 |
-
def Process(self, InputVideo, InputModel, InputUseRegions, InputThreshold, InputMinPercentage, InputMaxRectangles, InputPadding, InputSegmentRows, InputSegmentColumns, InputFullFrameInterval, Progress=App.Progress()):
|
| 180 |
if not InputVideo:
|
| 181 |
Logger.warning('❌ No Video Provided')
|
| 182 |
App.Warning('❌ No Video Provided')
|
|
@@ -214,6 +215,7 @@ class Upscaler:
|
|
| 214 |
CurrentFrameIndex += 1
|
| 215 |
|
| 216 |
ForceFull = False
|
|
|
|
| 217 |
if CurrentFrameIndex == 1 or not InputUseRegions:
|
| 218 |
ForceFull = True
|
| 219 |
PartialUpscaleCount = 0
|
|
@@ -221,6 +223,39 @@ class Upscaler:
|
|
| 221 |
ForceFull = True
|
| 222 |
PartialUpscaleCount = 0
|
| 223 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
if ForceFull:
|
| 225 |
OutputFrame = self.UpscaleFullFrame(Model, Frame)
|
| 226 |
SimilarityPercentage = 0
|
|
@@ -333,7 +368,9 @@ with App.Blocks(
|
|
| 333 |
- **Padding:** Adds extra pixels around detected regions to include out of bounds pixels.
|
| 334 |
- **Min Percentage:** If the similarity between frames is above this value, only regions are upscaled; otherwise, the full frame is upscaled.
|
| 335 |
- **Max Rectangles:** Limits the number of regions to process per frame for performance.
|
| 336 |
-
- **Segment Rows/Columns:** Controls the grid size for region detection. More segments allow finer detection but may increase processing time.
|
|
|
|
|
|
|
| 337 |
''')
|
| 338 |
with App.Group():
|
| 339 |
InputUseRegions = App.Checkbox(
|
|
@@ -406,6 +443,15 @@ with App.Blocks(
|
|
| 406 |
info='Force a full-frame upscale every N frames (set to 1 to always upscale full frame)',
|
| 407 |
interactive=False
|
| 408 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
SubmitButton = App.Button('🚀 Upscale Video')
|
| 410 |
|
| 411 |
with App.Column(show_progress=True):
|
|
@@ -425,13 +471,14 @@ with App.Blocks(
|
|
| 425 |
App.update(interactive=UseRegions),
|
| 426 |
App.update(interactive=UseRegions),
|
| 427 |
App.update(interactive=UseRegions),
|
|
|
|
| 428 |
App.update(interactive=UseRegions)
|
| 429 |
)
|
| 430 |
|
| 431 |
InputUseRegions.change(
|
| 432 |
fn=ToggleRegionInputs,
|
| 433 |
inputs=[InputUseRegions],
|
| 434 |
-
outputs=[InputThreshold, InputMinPercentage, InputMaxRectangles, InputPadding, InputSegmentRows, InputSegmentColumns, InputFullFrameInterval],
|
| 435 |
)
|
| 436 |
|
| 437 |
SubmitButton.click(
|
|
@@ -446,7 +493,8 @@ with App.Blocks(
|
|
| 446 |
InputPadding,
|
| 447 |
InputSegmentRows,
|
| 448 |
InputSegmentColumns,
|
| 449 |
-
InputFullFrameInterval
|
|
|
|
| 450 |
],
|
| 451 |
outputs=[OutputVideo, OutputDownload],
|
| 452 |
)
|
|
|
|
| 13 |
from rich.logging import RichHandler
|
| 14 |
|
| 15 |
from Scripts.SAD import GetDifferenceRectangles
|
| 16 |
+
from Scripts.ORB import DetectMotionWithOrb
|
| 17 |
|
| 18 |
# ============================== #
|
| 19 |
# Core Settings #
|
|
|
|
| 177 |
return OutputFrame, SimilarityPercentage, Rectangles, RegionLog, UseRegions
|
| 178 |
|
| 179 |
@spaces.GPU
|
| 180 |
+
def Process(self, InputVideo, InputModel, InputUseRegions, InputThreshold, InputMinPercentage, InputMaxRectangles, InputPadding, InputSegmentRows, InputSegmentColumns, InputFullFrameInterval, InputMotionThreshold, Progress=App.Progress()):
|
| 181 |
if not InputVideo:
|
| 182 |
Logger.warning('❌ No Video Provided')
|
| 183 |
App.Warning('❌ No Video Provided')
|
|
|
|
| 215 |
CurrentFrameIndex += 1
|
| 216 |
|
| 217 |
ForceFull = False
|
| 218 |
+
CopyPrevUpscaled = False
|
| 219 |
if CurrentFrameIndex == 1 or not InputUseRegions:
|
| 220 |
ForceFull = True
|
| 221 |
PartialUpscaleCount = 0
|
|
|
|
| 223 |
ForceFull = True
|
| 224 |
PartialUpscaleCount = 0
|
| 225 |
|
| 226 |
+
if PrevFrame is not None:
|
| 227 |
+
IsMotion, TotalMagnitude, DirectionAngle = DetectMotionWithOrb(PrevFrame, Frame, InputMotionThreshold)
|
| 228 |
+
if IsMotion:
|
| 229 |
+
ForceFull = True
|
| 230 |
+
PartialUpscaleCount = 0
|
| 231 |
+
Logger.info(f'🟨 Frame {CurrentFrameIndex}: Motion Detected - Upscaling Full Frame')
|
| 232 |
+
|
| 233 |
+
if not ForceFull and PrevFrame is not None and UpscaledPrevFrame is not None:
|
| 234 |
+
DiffResult = GetDifferenceRectangles(
|
| 235 |
+
PrevFrame,
|
| 236 |
+
Frame,
|
| 237 |
+
Threshold=InputThreshold,
|
| 238 |
+
Rows=InputSegmentRows,
|
| 239 |
+
Columns=InputSegmentColumns,
|
| 240 |
+
Padding=InputPadding
|
| 241 |
+
)
|
| 242 |
+
SimilarityPercentage = DiffResult['SimilarPercentage']
|
| 243 |
+
if SimilarityPercentage == 100:
|
| 244 |
+
OutputFrame = UpscaledPrevFrame.copy()
|
| 245 |
+
RegionLog = '🟦'
|
| 246 |
+
UseRegions = False
|
| 247 |
+
Rectangles = []
|
| 248 |
+
Logger.info(f'{RegionLog} Frame {CurrentFrameIndex}: 100% Similar - Copied Previous Upscaled Frame')
|
| 249 |
+
FrameProgress += PerFrameProgress
|
| 250 |
+
Progress(FrameProgress, desc=f'📦 Processed Frame {CurrentFrameIndex}/{FrameCount}')
|
| 251 |
+
cv2.imwrite(f'{TempDir}/Upscaled_Frame_{CurrentFrameIndex:05d}.png', OutputFrame)
|
| 252 |
+
PrevFrame = Frame.copy()
|
| 253 |
+
UpscaledPrevFrame = OutputFrame.copy()
|
| 254 |
+
DeltaTime = time.time() - StartTime
|
| 255 |
+
Times.append(DeltaTime)
|
| 256 |
+
StartTime = time.time()
|
| 257 |
+
continue
|
| 258 |
+
|
| 259 |
if ForceFull:
|
| 260 |
OutputFrame = self.UpscaleFullFrame(Model, Frame)
|
| 261 |
SimilarityPercentage = 0
|
|
|
|
| 368 |
- **Padding:** Adds extra pixels around detected regions to include out of bounds pixels.
|
| 369 |
- **Min Percentage:** If the similarity between frames is above this value, only regions are upscaled; otherwise, the full frame is upscaled.
|
| 370 |
- **Max Rectangles:** Limits the number of regions to process per frame for performance.
|
| 371 |
+
- **Segment Rows/Columns:** Controls the grid size for region detection. More segments allow finer detection but may increase processing time. Uses less Vram when used.
|
| 372 |
+
- **Full Frame Interval:** Forces a full-frame upscale every N frames. Set to 1 to always upscale the full frame. This is to prevent regions from glitching out.
|
| 373 |
+
- **Motion Threshold:** Controls how sensitive the motion detection is. Upscaling motion frames increases faulty regions. Lower = More strict
|
| 374 |
''')
|
| 375 |
with App.Group():
|
| 376 |
InputUseRegions = App.Checkbox(
|
|
|
|
| 443 |
info='Force a full-frame upscale every N frames (set to 1 to always upscale full frame)',
|
| 444 |
interactive=False
|
| 445 |
)
|
| 446 |
+
InputMotionThreshold = App.Slider(
|
| 447 |
+
label='Motion Threshold',
|
| 448 |
+
value=1,
|
| 449 |
+
minimum=0,
|
| 450 |
+
maximum=10,
|
| 451 |
+
step=0.5,
|
| 452 |
+
info='Threshold for the motion detection algorithm to consider a frame as different',
|
| 453 |
+
interactive=False
|
| 454 |
+
)
|
| 455 |
SubmitButton = App.Button('🚀 Upscale Video')
|
| 456 |
|
| 457 |
with App.Column(show_progress=True):
|
|
|
|
| 471 |
App.update(interactive=UseRegions),
|
| 472 |
App.update(interactive=UseRegions),
|
| 473 |
App.update(interactive=UseRegions),
|
| 474 |
+
App.update(interactive=UseRegions),
|
| 475 |
App.update(interactive=UseRegions)
|
| 476 |
)
|
| 477 |
|
| 478 |
InputUseRegions.change(
|
| 479 |
fn=ToggleRegionInputs,
|
| 480 |
inputs=[InputUseRegions],
|
| 481 |
+
outputs=[InputThreshold, InputMinPercentage, InputMaxRectangles, InputPadding, InputSegmentRows, InputSegmentColumns, InputFullFrameInterval, InputMotionThreshold],
|
| 482 |
)
|
| 483 |
|
| 484 |
SubmitButton.click(
|
|
|
|
| 493 |
InputPadding,
|
| 494 |
InputSegmentRows,
|
| 495 |
InputSegmentColumns,
|
| 496 |
+
InputFullFrameInterval,
|
| 497 |
+
InputMotionThreshold
|
| 498 |
],
|
| 499 |
outputs=[OutputVideo, OutputDownload],
|
| 500 |
)
|
Scripts/ORB.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
def DetectMotionWithOrb(FrameOne, FrameTwo, MotionThreshold=1.0):
|
| 5 |
+
FrameOneGray = cv2.cvtColor(FrameOne, cv2.COLOR_BGR2GRAY)
|
| 6 |
+
FrameTwoGray = cv2.cvtColor(FrameTwo, cv2.COLOR_BGR2GRAY)
|
| 7 |
+
OrbDetector = cv2.ORB_create() #type: ignore
|
| 8 |
+
Keypoints1, Descriptors1 = OrbDetector.detectAndCompute(FrameOneGray, None)
|
| 9 |
+
Keypoints2, Descriptors2 = OrbDetector.detectAndCompute(FrameTwoGray, None)
|
| 10 |
+
if Descriptors1 is None or Descriptors2 is None:
|
| 11 |
+
return False, 0, 0
|
| 12 |
+
BfMatcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
|
| 13 |
+
Matches = BfMatcher.match(Descriptors1, Descriptors2)
|
| 14 |
+
Vectors = []
|
| 15 |
+
for Match in Matches:
|
| 16 |
+
Pt1 = Keypoints1[Match.queryIdx].pt
|
| 17 |
+
Pt2 = Keypoints2[Match.trainIdx].pt
|
| 18 |
+
Vector = (Pt2[0] - Pt1[0], Pt2[1] - Pt1[1])
|
| 19 |
+
Vectors.append(Vector)
|
| 20 |
+
if not Vectors:
|
| 21 |
+
return False, 0, 0
|
| 22 |
+
Vectors = np.array(Vectors)
|
| 23 |
+
AvgVector = np.mean(Vectors, axis=0)
|
| 24 |
+
AvgX, AvgY = AvgVector
|
| 25 |
+
TotalMagnitude = np.hypot(AvgX, AvgY)
|
| 26 |
+
DirectionAngle = np.degrees(np.arctan2(AvgY, AvgX))
|
| 27 |
+
IsMotion = TotalMagnitude >= MotionThreshold
|
| 28 |
+
return IsMotion, TotalMagnitude, DirectionAngle
|