Hyphonical commited on
Commit
cc5a426
·
1 Parent(s): 5a7f927

✨ Add Motion Detection Functionality with ORB Algorithm

Browse files
Files changed (2) hide show
  1. App.py +52 -4
  2. 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