├── README.md └── video_stabilization.py /README.md: -------------------------------------------------------------------------------- 1 | # Video-Stabilization 2 | 3 | ## Reference Blog:- 4 | 5 | 1. https://www.learnopencv.com/video-stabilization-using-point-feature-matching-in-opencv/ 6 | 7 | ## Video Stabilization Using Point Feature Matching in OpenCV. 8 | 9 | This mainly involves reducing the effect of motion due to translation or rotation or any movement in camera. 10 | In this, Euclidean Motion Model is used instead of Affine or Homographic transformation, because it is adequate for motion stabilization. 11 | 12 | ## Steps for video stabilization 13 | 14 | 1. Capture two consequent frames of a video. 15 | 2. Find motion between those two frames. 16 | 3. Correct the motion. 17 | 18 | ## Finding motions between frames 19 | 20 | 1. Find good features in the current frame and previous frame (`goodGeaturesToTrack`) and (`calcOpticalFlowPyrLK`). 21 | 2. We use two set of points to find rigid transform that maps previous frame to the current frame(`estimateRigidTranform`). 22 | 3. Once the motion is estimated, we store the rotated, translated values. 23 | 4. We soothe the values, found in step 3 (moving average filter). 24 | 5. Calculate smooth motion between frames (trajectory). 25 | 6. Apply smoothed camera motion to frames. 26 | 27 | ## Important functions used:- 28 | 29 | 1. `calcOpticalFlowPyrLK()` 30 | a) `nextPts` - output vector of 2D points (with single-precision floating-point coordinates) containing the calculated new positions of input features in the second image. 31 | b) `status` – output status vector (of unsigned chars); each element of the vector is set to 1 if the flow for the corresponding features has been found, otherwise, it is set to 0. 32 | c) `err` - outputs vector of errors, if the flow wasn’t found then the error is not defined. 33 | 34 | 2. `estimateRigidTransform()` 35 | a) Computes an optimal affine transformation between two 2D point sets. 36 | 37 | ## Version Requirements 38 | 39 | 1. Python 2.7 or Python 3.x 40 | 2. OpenCV version 3.x.x 41 | 42 | ## Usage 43 | 44 | `python video_stabilization.py` 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /video_stabilization.py: -------------------------------------------------------------------------------- 1 | # source learnopencv.com 2 | 3 | import numpy as np 4 | import cv2 5 | SMOOTHING_RADIUS=50 6 | 7 | def movingAverage(curve, radius): 8 | window_size = 2 * radius + 1 9 | # Define the filter 10 | f = np.ones(window_size)/window_size 11 | # Add padding to the boundaries 12 | curve_pad = np.lib.pad(curve, (radius, radius), 'edge') 13 | # Apply convolution 14 | curve_smoothed = np.convolve(curve_pad, f, mode='same') 15 | # Remove padding 16 | curve_smoothed = curve_smoothed[radius:-radius] 17 | # return smoothed curve 18 | return curve_smoothed 19 | 20 | def smooth(trajectory): 21 | smoothed_trajectory = np.copy(trajectory) 22 | # Filter the x, y and angle curves 23 | for i in range(3): 24 | smoothed_trajectory[:,i] = movingAverage(trajectory[:,i], radius=SMOOTHING_RADIUS) 25 | 26 | return smoothed_trajectory 27 | 28 | def fixBorder(frame): 29 | s = frame.shape 30 | # Scale the image 4% without moving the center 31 | T = cv2.getRotationMatrix2D((s[1]/2, s[0]/2), 0, 1.04) 32 | frame = cv2.warpAffine(frame, T, (s[1], s[0])) 33 | return frame 34 | 35 | # Read input video 36 | 37 | cp = cv2.VideoCapture('path to video file.mp4') 38 | 39 | # To get number of frames 40 | n_frames = int(cp.get(cv2.CAP_PROP_FRAME_COUNT)) 41 | 42 | # To check the number of frames in the video 43 | print(n_frames) 44 | 45 | width = int(cp.get(cv2.CAP_PROP_FRAME_WIDTH)) 46 | height = int(cp.get(cv2.CAP_PROP_FRAME_HEIGHT)) 47 | 48 | print("height", width) 49 | print("height", height) 50 | 51 | # get the number of frames per second 52 | fps = cp.get(cv2.CAP_PROP_FPS) 53 | 54 | fourcc = cv2.VideoWriter_fourcc(*'MJPG') 55 | print(fourcc) 56 | 57 | # Try doing 2*width 58 | out = cv2.VideoWriter('video_out.mp4',0x7634706d, fps, (width, height)) 59 | 60 | # read the first frame 61 | _, prev = cp.read() 62 | 63 | prev_gray = cv2.cvtColor(prev, cv2.COLOR_BGR2GRAY) 64 | transforms = np.zeros((n_frames-1, 3), np.float32) 65 | 66 | for i in range(n_frames-2): 67 | prev_pts = cv2.goodFeaturesToTrack(prev_gray, maxCorners=200, qualityLevel=0.01, minDistance=30, blockSize=3) 68 | 69 | succ, curr = cp.read() 70 | 71 | if not succ: 72 | break 73 | 74 | curr_gray = cv2.cvtColor(curr, cv2.COLOR_BGR2GRAY) 75 | 76 | 77 | # Track feature points 78 | # status = 1. if flow points are found 79 | # err if flow was not find the error is not defined 80 | # curr_pts = calculated new positions of input features in the second image 81 | curr_pts, status, err = cv2.calcOpticalFlowPyrLK(prev_gray, curr_gray, prev_pts, None) 82 | 83 | 84 | idx = np.where(status==1)[0] 85 | prev_pts = prev_pts[idx] 86 | curr_pts = curr_pts[idx] 87 | assert prev_pts.shape == curr_pts.shape 88 | 89 | 90 | # fullAffine= FAlse will set the degree of freedom to only 5 i.e translation, rotation and scaling 91 | # try fullAffine = True 92 | m = cv2.estimateRigidTransform(prev_pts, curr_pts, fullAffine=False) 93 | 94 | dx = m[0,2] 95 | dy = m[1,2] 96 | 97 | # Extract rotation angle 98 | da = np.arctan2(m[1,0], m[0,0]) 99 | 100 | transforms[i] = [dx,dy,da] 101 | 102 | prev_gray = curr_gray 103 | 104 | 105 | #print("Frame: " + str(i) + "/" + str(n_frames) + " - Tracked points : " + str(len(prev_pts))) 106 | 107 | # Find the cumulative sum of tranform matrix for each dx,dy and da 108 | trajectory = np.cumsum(transforms, axis=0) 109 | 110 | smoothed_trajectory = smooth(trajectory) 111 | difference = smoothed_trajectory - trajectory 112 | transforms_smooth = transforms + difference 113 | 114 | # Reset stream to first frame 115 | cp.set(cv2.CAP_PROP_POS_FRAMES, 0) 116 | # Write n_frames-1 transformed frames 117 | for i in range(n_frames-2): 118 | # Read next frame 119 | success, frame = cp.read() 120 | if not success: 121 | break 122 | 123 | # Extract transformations from the new transformation array 124 | dx = transforms_smooth[i,0] 125 | dy = transforms_smooth[i,1] 126 | da = transforms_smooth[i,2] 127 | 128 | # Reconstruct transformation matrix accordingly to new values 129 | m = np.zeros((2,3), np.float32) 130 | m[0,0] = np.cos(da) 131 | m[0,1] = -np.sin(da) 132 | m[1,0] = np.sin(da) 133 | m[1,1] = np.cos(da) 134 | m[0,2] = dx 135 | m[1,2] = dy 136 | 137 | # Apply affine wrapping to the given frame 138 | frame_stabilized = cv2.warpAffine(frame, m, (width,height)) 139 | 140 | # Fix border artifacts 141 | frame_stabilized = fixBorder(frame_stabilized) 142 | 143 | # Write the frame to the file 144 | frame_out = cv2.hconcat([frame, frame_stabilized]) 145 | 146 | # If the image is too big, resize it. 147 | if(frame_out.shape[1] > 1920): 148 | frame_out = cv2.resize(frame_out, (frame_out.shape[1]/2, frame_out.shape[0]/2)); 149 | 150 | cv2.imshow("Before and After", frame_out) 151 | cv2.waitKey(10) 152 | out.write(frame_out) 153 | 154 | # Release video 155 | cp.release() 156 | out.release() 157 | # Close windows 158 | cv2.destroyAllWindows() 159 | 160 | --------------------------------------------------------------------------------