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
|