video_stabilization.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. /*
  2. Copyright (c) 2014, Nghia Ho
  3. All rights reserved.
  4. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
  5. 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  6. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  7. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
  8. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  9. Modified by Satya Mallick, Big Vision LLC (Jan 2019)
  10. */
  11. #include <opencv2/opencv.hpp>
  12. #include <iostream>
  13. #include <cassert>
  14. #include <cmath>
  15. #include <fstream>
  16. using namespace std;
  17. using namespace cv;
  18. const int SMOOTHING_RADIUS = 50; // In frames. The larger the more stable the video, but less reactive to sudden panning
  19. struct TransformParam
  20. {
  21. TransformParam() {}
  22. TransformParam(double _dx, double _dy, double _da)
  23. {
  24. dx = _dx;
  25. dy = _dy;
  26. da = _da;
  27. }
  28. double dx;
  29. double dy;
  30. double da; // angle
  31. void getTransform(Mat &T)
  32. {
  33. // Reconstruct transformation matrix accordingly to new values
  34. T.at<double>(0,0) = cos(da);
  35. T.at<double>(0,1) = -sin(da);
  36. T.at<double>(1,0) = sin(da);
  37. T.at<double>(1,1) = cos(da);
  38. T.at<double>(0,2) = dx;
  39. T.at<double>(1,2) = dy;
  40. }
  41. };
  42. struct Trajectory
  43. {
  44. Trajectory() {}
  45. Trajectory(double _x, double _y, double _a) {
  46. x = _x;
  47. y = _y;
  48. a = _a;
  49. }
  50. double x;
  51. double y;
  52. double a; // angle
  53. };
  54. vector<Trajectory> cumsum(vector<TransformParam> &transforms)
  55. {
  56. vector <Trajectory> trajectory; // trajectory at all frames
  57. // Accumulated frame to frame transform
  58. double a = 0;
  59. double x = 0;
  60. double y = 0;
  61. for(size_t i=0; i < transforms.size(); i++)
  62. {
  63. x += transforms[i].dx;
  64. y += transforms[i].dy;
  65. a += transforms[i].da;
  66. trajectory.push_back(Trajectory(x,y,a));
  67. }
  68. return trajectory;
  69. }
  70. vector <Trajectory> smooth(vector <Trajectory>& trajectory, int radius)
  71. {
  72. vector <Trajectory> smoothed_trajectory;
  73. for(size_t i=0; i < trajectory.size(); i++) {
  74. double sum_x = 0;
  75. double sum_y = 0;
  76. double sum_a = 0;
  77. int count = 0;
  78. for(int j=-radius; j <= radius; j++) {
  79. if(i+j >= 0 && i+j < trajectory.size()) {
  80. sum_x += trajectory[i+j].x;
  81. sum_y += trajectory[i+j].y;
  82. sum_a += trajectory[i+j].a;
  83. count++;
  84. }
  85. }
  86. double avg_a = sum_a / count;
  87. double avg_x = sum_x / count;
  88. double avg_y = sum_y / count;
  89. smoothed_trajectory.push_back(Trajectory(avg_x, avg_y, avg_a));
  90. }
  91. return smoothed_trajectory;
  92. }
  93. void fixBorder(Mat &frame_stabilized)
  94. {
  95. Mat T = getRotationMatrix2D(Point2f(frame_stabilized.cols/2, frame_stabilized.rows/2), 0, 1.04);
  96. warpAffine(frame_stabilized, frame_stabilized, T, frame_stabilized.size());
  97. }
  98. int main(int argc, char **argv)
  99. {
  100. // Read input video
  101. VideoCapture cap("video.mp4");
  102. // Get frame count
  103. int n_frames = int(cap.get(CAP_PROP_FRAME_COUNT));
  104. // Get width and height of video stream
  105. int w = int(cap.get(CAP_PROP_FRAME_WIDTH));
  106. int h = int(cap.get(CAP_PROP_FRAME_HEIGHT));
  107. // Get frames per second (fps)
  108. double fps = cap.get(cv::CAP_PROP_FPS);
  109. // Set up output video
  110. VideoWriter out("video_out.avi", cv::VideoWriter::fourcc('M','J','P','G'), fps, Size(2 * w, h));
  111. // Define variable for storing frames
  112. Mat curr, curr_gray;
  113. Mat prev, prev_gray;
  114. // Read first frame
  115. cap >> prev;
  116. // Convert frame to grayscale
  117. cvtColor(prev, prev_gray, COLOR_BGR2GRAY);
  118. // Pre-define transformation-store array
  119. vector <TransformParam> transforms;
  120. //
  121. Mat last_T;
  122. for(int i = 1; i < n_frames-1; i++)
  123. {
  124. // Vector from previous and current feature points
  125. vector <Point2f> prev_pts, curr_pts;
  126. // Detect features in previous frame
  127. goodFeaturesToTrack(prev_gray, prev_pts, 200, 0.01, 30);
  128. // Read next frame
  129. bool success = cap.read(curr);
  130. if(!success) break;
  131. // Convert to grayscale
  132. cvtColor(curr, curr_gray, COLOR_BGR2GRAY);
  133. // Calculate optical flow (i.e. track feature points)
  134. vector <uchar> status;
  135. vector <float> err;
  136. calcOpticalFlowPyrLK(prev_gray, curr_gray, prev_pts, curr_pts, status, err);
  137. // Filter only valid points
  138. auto prev_it = prev_pts.begin();
  139. auto curr_it = curr_pts.begin();
  140. for(size_t k = 0; k < status.size(); k++)
  141. {
  142. if(status[k])
  143. {
  144. prev_it++;
  145. curr_it++;
  146. }
  147. else
  148. {
  149. prev_it = prev_pts.erase(prev_it);
  150. curr_it = curr_pts.erase(curr_it);
  151. }
  152. }
  153. // Find transformation matrix
  154. Mat T = estimateRigidTransform(prev_pts, curr_pts, false);
  155. // In rare cases no transform is found.
  156. // We'll just use the last known good transform.
  157. if(T.data == NULL) last_T.copyTo(T);
  158. T.copyTo(last_T);
  159. // Extract traslation
  160. double dx = T.at<double>(0,2);
  161. double dy = T.at<double>(1,2);
  162. // Extract rotation angle
  163. double da = atan2(T.at<double>(1,0), T.at<double>(0,0));
  164. // Store transformation
  165. transforms.push_back(TransformParam(dx, dy, da));
  166. // Move to next frame
  167. curr_gray.copyTo(prev_gray);
  168. cout << "Frame: " << i << "/" << n_frames << " - Tracked points : " << prev_pts.size() << endl;
  169. }
  170. // Compute trajectory using cumulative sum of transformations
  171. vector <Trajectory> trajectory = cumsum(transforms);
  172. // Smooth trajectory using moving average filter
  173. vector <Trajectory> smoothed_trajectory = smooth(trajectory, SMOOTHING_RADIUS);
  174. vector <TransformParam> transforms_smooth;
  175. for(size_t i=0; i < transforms.size(); i++)
  176. {
  177. // Calculate difference in smoothed_trajectory and trajectory
  178. double diff_x = smoothed_trajectory[i].x - trajectory[i].x;
  179. double diff_y = smoothed_trajectory[i].y - trajectory[i].y;
  180. double diff_a = smoothed_trajectory[i].a - trajectory[i].a;
  181. // Calculate newer transformation array
  182. double dx = transforms[i].dx + diff_x;
  183. double dy = transforms[i].dy + diff_y;
  184. double da = transforms[i].da + diff_a;
  185. transforms_smooth.push_back(TransformParam(dx, dy, da));
  186. }
  187. cap.set(cv::CAP_PROP_POS_FRAMES, 0);
  188. Mat T(2,3,CV_64F);
  189. Mat frame, frame_stabilized, frame_out;
  190. for( int i = 0; i < n_frames-1; i++)
  191. {
  192. bool success = cap.read(frame);
  193. if(!success) break;
  194. // Extract transform from translation and rotation angle.
  195. transforms_smooth[i].getTransform(T);
  196. // Apply affine wrapping to the given frame
  197. warpAffine(frame, frame_stabilized, T, frame.size());
  198. // Scale image to remove black border artifact
  199. fixBorder(frame_stabilized);
  200. // Now draw the original and stablised side by side for coolness
  201. hconcat(frame, frame_stabilized, frame_out);
  202. // If the image is too big, resize it.
  203. if(frame_out.cols > 1920)
  204. {
  205. resize(frame_out, frame_out, Size(frame_out.cols/2, frame_out.rows/2));
  206. }
  207. imshow("Before and After", frame_out);
  208. out.write(frame_out);
  209. waitKey(10);
  210. }
  211. // Release video
  212. cap.release();
  213. out.release();
  214. // Close windows
  215. destroyAllWindows();
  216. return 0;
  217. }