Coverage for brainbox/video.py: 96%

28 statements  

« prev     ^ index     » next       coverage.py v7.7.0, created at 2025-03-17 15:25 +0000

1"""Functions for analyzing video frame data""" 

2import numpy as np 

3import cv2 

4 

5 

6def frame_diff(frame1, frame2): 

7 """ 

8 Outputs pythagorean distance between two frames. 

9 

10 :param frame1: A numpy array of pixels with a shape of either (m, n, 3) or (m, n) 

11 :param frame2: A numpy array of pixels with a shape of either (m, n, 3) or (m, n) 

12 :return: An array with a shape equal to the input frames 

13 """ 

14 if frame1.shape != frame2.shape: 1e

15 raise ValueError('Frames must have the same shape') 1e

16 diff32 = np.float32(frame1) - np.float32(frame2) 1e

17 if frame1.ndim == 3: 1e

18 norm32 = np.float32( 1e

19 np.sqrt(diff32[:, :, 0] ** 2 + diff32[:, :, 1] ** 2 + diff32[:, :, 2] ** 2) / 

20 np.sqrt(255 ** 2 * 3) 

21 ) 

22 else: 

23 norm32 = np.float32(np.sqrt(diff32 ** 2 * 3) / np.sqrt(255 ** 2 * 3)) 1e

24 return np.uint8(np.round(norm32 * 255)) 1e

25 

26 

27def frame_diffs(frames, diff=1): 

28 """ 

29 Return the difference between frames. 

30 

31 May also take difference between more than 1 frames. Values are normalized between 0-255. 

32 

33 :param frames: Array or list of frames, where each frame is either (y, x) or (y, x, 3). 

34 :param diff: Take difference between frames N and frames N + diff. 

35 :return: uint8 array with shape (n-diff, y, x). 

36 """ 

37 frames = np.array(frames, dtype=np.float32) 1dabc

38 if frames.shape[0] < diff: 1dabc

39 raise ValueError('Difference must be less than number of frames') 1d

40 diff32 = frames[diff:] - frames[:-diff] 1dabc

41 # Normalize 

42 if frames.ndim == 4: 1dabc

43 norm32 = np.sqrt((diff32 ** 2).sum(axis=3)) / np.sqrt(255 ** 2 * 3).astype(np.float32) 1d

44 else: 

45 norm32 = np.sqrt(diff32 ** 2 * 3) / np.sqrt(255 ** 2 * 3).astype(np.float32) 1dabc

46 return np.uint8(norm32 * 255) 1dabc

47 

48 

49def motion_energy(frames, diff=2, kernel=None, normalize=True): 

50 """ 

51 Returns a min-max normalized vector of motion energy between frames. 

52 :param frames: A list of ndarray of frames. 

53 :param diff: Take difference between frames N and frames N + diff. 

54 :param kernel: An optional Gaussian smoothing to apply with a given kernel size. 

55 :param normalize: If True, motion energy is min-max normalized 

56 :return df_: A vector of length n frames - diff, normalized between 0 and 1. 

57 :return stDev: The standard deviation between the frames (not normalized). 

58 

59 Example 1 - Calculate normalized difference between consecutive frames 

60 df, std = motion_energy(frames, diff=1) 

61 

62 Example 2 - Calculate smoothed difference between every 2nd frame 

63 df, _ = motion_energy(frames, kernel=(9, 9)) 

64 """ 

65 df = frame_diffs(frames, diff) 1abc

66 

67 # Smooth with a Gaussian blur TODO Use median blur instead 

68 if kernel is not None: 1abc

69 df = cv2.GaussianBlur(df, (9, 9), 0) 

70 stDev = np.array([cv2.meanStdDev(x)[1] for x in df]).squeeze() 1abc

71 

72 # Feature scaling 

73 df_ = df.sum(axis=(1, 2)) 1abc

74 if normalize: 1abc

75 df_ = (df_ - df_.min()) / (df_.max() - df_.min()) 1abc

76 return df_, stDev 1abc