OpenShot Library | libopenshot 1.1.1
CVStabilization.cpp
Go to the documentation of this file.
1
10// Copyright (c) 2008-2019 OpenShot Studios, LLC
11//
12// SPDX-License-Identifier: LGPL-3.0-or-later
13
14#include <fstream>
15#include <iomanip>
16#include <iostream>
17
18#include "CVStabilization.h"
19#include "Exceptions.h"
20
21#include "stabilizedata.pb.h"
22#include <google/protobuf/util/time_util.h>
23
24#if CV_VERSION_MAJOR >= 5
25#define int64 opencv_broken_int
26#define uint64 opencv_broken_uint
27#include <opencv2/geometry/3d.hpp>
28#undef uint64
29#undef int64
30#else
31#include <opencv2/calib3d.hpp>
32#endif
33
34using namespace std;
35using namespace openshot;
36using google::protobuf::util::TimeUtil;
37
38// Set default smoothing window value to compute stabilization
39CVStabilization::CVStabilization(std::string processInfoJson, ProcessingController &processingController)
40: processingController(&processingController){
41 SetJson(processInfoJson);
42 start = 1;
43 end = 1;
44}
45
46// Process clip and store necessary stabilization data
47void CVStabilization::stabilizeClip(openshot::Clip& video, size_t _start, size_t _end, bool process_interval){
48
49 if(error){
50 return;
51 }
52 processingController->SetError(false, "");
53
54 start = _start; end = _end;
55 // Compute max and average transformation parameters
56 avr_dx=0; avr_dy=0; avr_da=0; max_dx=0; max_dy=0; max_da=0;
57
58 video.Open();
59 // Save original video width and height
60 cv::Size readerDims(video.Reader()->info.width, video.Reader()->info.height);
61
62 size_t frame_number;
63 if(!process_interval || end <= 1 || end-start == 0){
64 // Get total number of frames in video
65 start = (int)(video.Start() * video.Reader()->info.fps.ToFloat()) + 1;
66 end = (int)(video.End() * video.Reader()->info.fps.ToFloat()) + 1;
67 }
68
69 // Extract and track opticalflow features for each frame
70 for (frame_number = start; frame_number <= end; frame_number++)
71 {
72 // Stop the feature tracker process
73 if(processingController->ShouldStop()){
74 return;
75 }
76
77 std::shared_ptr<openshot::Frame> f = video.GetFrame(frame_number);
78
79 // Grab OpenCV Mat image
80 cv::Mat cvimage = f->GetImageCV();
81 // Resize frame to original video width and height if they differ
82 if(cvimage.size().width != readerDims.width || cvimage.size().height != readerDims.height)
83 cv::resize(cvimage, cvimage, cv::Size(readerDims.width, readerDims.height));
84 cv::cvtColor(cvimage, cvimage, cv::COLOR_RGB2GRAY);
85
86 if(!TrackFrameFeatures(cvimage, frame_number)){
87 prev_to_cur_transform.push_back(TransformParam(0, 0, 0));
88 }
89
90 // Update progress
91 processingController->SetProgress(uint(100*(frame_number-start)/(end-start)));
92 }
93
94 // Calculate trajectory data
95 std::vector <CamTrajectory> trajectory = ComputeFramesTrajectory();
96
97 // Calculate and save smoothed trajectory data
98 trajectoryData = SmoothTrajectory(trajectory);
99
100 // Calculate and save transformation data
101 transformationData = GenNewCamPosition(trajectoryData);
102
103 // Normalize smoothed trajectory data
104 for(auto &dataToNormalize : trajectoryData){
105 dataToNormalize.second.x/=readerDims.width;
106 dataToNormalize.second.y/=readerDims.height;
107 }
108 // Normalize transformation data
109 for(auto &dataToNormalize : transformationData){
110 dataToNormalize.second.dx/=readerDims.width;
111 dataToNormalize.second.dy/=readerDims.height;
112 }
113}
114
115// Track current frame features and find the relative transformation
116bool CVStabilization::TrackFrameFeatures(cv::Mat frame, size_t frameNum){
117 // Check if there are black frames
118 if(cv::countNonZero(frame) < 1){
119 return false;
120 }
121
122 // Initialize prev_grey if not
123 if(prev_grey.empty()){
124 prev_grey = frame;
125 return true;
126 }
127
128 // OpticalFlow features vector
129 std::vector <cv::Point2f> prev_corner, cur_corner;
130 std::vector <cv::Point2f> prev_corner2, cur_corner2;
131 std::vector <uchar> status;
132 std::vector <float> err;
133 // Extract new image features
134 cv::goodFeaturesToTrack(prev_grey, prev_corner, 200, 0.01, 30);
135 // Track features
136 cv::calcOpticalFlowPyrLK(prev_grey, frame, prev_corner, cur_corner, status, err);
137 // Remove untracked features
138 for(size_t i=0; i < status.size(); i++) {
139 if(status[i]) {
140 prev_corner2.push_back(prev_corner[i]);
141 cur_corner2.push_back(cur_corner[i]);
142 }
143 }
144 // In case no feature was detected
145 if(prev_corner2.empty() || cur_corner2.empty()){
146 last_T = cv::Mat();
147 // prev_grey = cv::Mat();
148 return false;
149 }
150
151 // Translation + rotation only
152 cv::Mat T = cv::estimateAffinePartial2D(prev_corner2, cur_corner2); // false = rigid transform, no scaling/shearing
153
154 double da, dx, dy;
155 // If T has nothing inside return (probably a segment where there is nothing to stabilize)
156 if(T.size().width == 0 || T.size().height == 0){
157 return false;
158 }
159 else{
160 // If no transformation is found, just use the last known good transform
161 if(T.data == NULL){
162 if(!last_T.empty())
163 last_T.copyTo(T);
164 else
165 return false;
166 }
167 // Decompose T
168 dx = T.at<double>(0,2);
169 dy = T.at<double>(1,2);
170 da = atan2(T.at<double>(1,0), T.at<double>(0,0));
171 }
172
173 // Filter transformations parameters, if they are higher than these: return
174 if(dx > 200 || dy > 200 || da > 0.1){
175 return false;
176 }
177
178 // Keep computing average and max transformation parameters
179 avr_dx+=fabs(dx);
180 avr_dy+=fabs(dy);
181 avr_da+=fabs(da);
182 if(fabs(dx) > max_dx)
183 max_dx = dx;
184 if(fabs(dy) > max_dy)
185 max_dy = dy;
186 if(fabs(da) > max_da)
187 max_da = da;
188
189 T.copyTo(last_T);
190
191 prev_to_cur_transform.push_back(TransformParam(dx, dy, da));
192 frame.copyTo(prev_grey);
193
194 return true;
195}
196
197std::vector<CamTrajectory> CVStabilization::ComputeFramesTrajectory(){
198
199 // Accumulated frame to frame transform
200 double a = 0;
201 double x = 0;
202 double y = 0;
203
204 vector <CamTrajectory> trajectory; // trajectory at all frames
205
206 // Compute global camera trajectory. First frame is the origin
207 for(size_t i=0; i < prev_to_cur_transform.size(); i++) {
208 x += prev_to_cur_transform[i].dx;
209 y += prev_to_cur_transform[i].dy;
210 a += prev_to_cur_transform[i].da;
211
212 // Save trajectory data to vector
213 trajectory.push_back(CamTrajectory(x,y,a));
214 }
215 return trajectory;
216}
217
218std::map<size_t,CamTrajectory> CVStabilization::SmoothTrajectory(std::vector <CamTrajectory> &trajectory){
219
220 std::map <size_t,CamTrajectory> smoothed_trajectory; // trajectory at all frames
221
222 for(size_t i=0; i < trajectory.size(); i++) {
223 double sum_x = 0;
224 double sum_y = 0;
225 double sum_a = 0;
226 int count = 0;
227
228 for(int j=-smoothingWindow; j <= smoothingWindow; j++) {
229 if(i+j < trajectory.size()) {
230 sum_x += trajectory[i+j].x;
231 sum_y += trajectory[i+j].y;
232 sum_a += trajectory[i+j].a;
233
234 count++;
235 }
236 }
237
238 double avg_a = sum_a / count;
239 double avg_x = sum_x / count;
240 double avg_y = sum_y / count;
241
242 // Add smoothed trajectory data to map
243 smoothed_trajectory[i + start] = CamTrajectory(avg_x, avg_y, avg_a);
244 }
245 return smoothed_trajectory;
246}
247
248// Generate new transformations parameters for each frame to follow the smoothed trajectory
249std::map<size_t,TransformParam> CVStabilization::GenNewCamPosition(std::map <size_t,CamTrajectory> &smoothed_trajectory){
250 std::map <size_t,TransformParam> new_prev_to_cur_transform;
251
252 // Accumulated frame to frame transform
253 double a = 0;
254 double x = 0;
255 double y = 0;
256
257 for(size_t i=0; i < prev_to_cur_transform.size(); i++) {
258 x += prev_to_cur_transform[i].dx;
259 y += prev_to_cur_transform[i].dy;
260 a += prev_to_cur_transform[i].da;
261
262 // target - current
263 double diff_x = smoothed_trajectory[i + start].x - x;
264 double diff_y = smoothed_trajectory[i + start].y - y;
265 double diff_a = smoothed_trajectory[i + start].a - a;
266
267 double dx = prev_to_cur_transform[i].dx + diff_x;
268 double dy = prev_to_cur_transform[i].dy + diff_y;
269 double da = prev_to_cur_transform[i].da + diff_a;
270
271 // Add transformation data to map
272 new_prev_to_cur_transform[i + start] = TransformParam(dx, dy, da);
273 }
274 return new_prev_to_cur_transform;
275}
276
277// Save stabilization data to protobuf file
279 using std::ios;
280
281 // Create stabilization message
282 pb_stabilize::Stabilization stabilizationMessage;
283
284 std::map<size_t,CamTrajectory>::iterator trajData = trajectoryData.begin();
285 std::map<size_t,TransformParam>::iterator transData = transformationData.begin();
286
287 // Iterate over all frames data and save in protobuf message
288 for(; trajData != trajectoryData.end(); ++trajData, ++transData){
289 AddFrameDataToProto(stabilizationMessage.add_frame(), trajData->second, transData->second, trajData->first);
290 }
291 // Add timestamp
292 *stabilizationMessage.mutable_last_updated() = TimeUtil::SecondsToTimestamp(time(NULL));
293
294 // Write the new message to disk.
295 std::fstream output(protobuf_data_path, ios::out | ios::trunc | ios::binary);
296 if (!stabilizationMessage.SerializeToOstream(&output)) {
297 std::cerr << "Failed to write protobuf message." << std::endl;
298 return false;
299 }
300
301 // Delete all global objects allocated by libprotobuf.
302 google::protobuf::ShutdownProtobufLibrary();
303
304 return true;
305}
306
307// Add frame stabilization data into protobuf message
308void CVStabilization::AddFrameDataToProto(pb_stabilize::Frame* pbFrameData, CamTrajectory& trajData, TransformParam& transData, size_t frame_number){
309
310 // Save frame number
311 pbFrameData->set_id(frame_number);
312
313 // Save camera trajectory data
314 pbFrameData->set_a(trajData.a);
315 pbFrameData->set_x(trajData.x);
316 pbFrameData->set_y(trajData.y);
317
318 // Save transformation data
319 pbFrameData->set_da(transData.da);
320 pbFrameData->set_dx(transData.dx);
321 pbFrameData->set_dy(transData.dy);
322}
323
325
326 // Check if the stabilizer info for the requested frame exists
327 if ( transformationData.find(frameId) == transformationData.end() ) {
328
329 return TransformParam();
330 } else {
331
332 return transformationData[frameId];
333 }
334}
335
337
338 // Check if the stabilizer info for the requested frame exists
339 if ( trajectoryData.find(frameId) == trajectoryData.end() ) {
340
341 return CamTrajectory();
342 } else {
343
344 return trajectoryData[frameId];
345 }
346}
347
348// Load JSON string into this object
349void CVStabilization::SetJson(const std::string value) {
350 // Parse JSON string into JSON objects
351 try
352 {
353 const Json::Value root = openshot::stringToJson(value);
354 // Set all values that match
355
356 SetJsonValue(root);
357 }
358 catch (const std::exception& e)
359 {
360 // Error parsing JSON (or missing keys)
361 throw openshot::InvalidJSON("JSON is invalid (missing keys or invalid data types)");
362 }
363}
364
365// Load Json::Value into this object
366void CVStabilization::SetJsonValue(const Json::Value root) {
367
368 // Set data from Json (if key is found)
369 if (!root["protobuf_data_path"].isNull()){
370 protobuf_data_path = (root["protobuf_data_path"].asString());
371 }
372 if (!root["smoothing-window"].isNull()){
373 smoothingWindow = (root["smoothing-window"].asInt());
374 }
375}
376
377/*
378||||||||||||||||||||||||||||||||||||||||||||||||||
379 ONLY FOR MAKE TEST
380||||||||||||||||||||||||||||||||||||||||||||||||||
381*/
382
383// Load protobuf data file
385 using std::ios;
386 // Create stabilization message
387 pb_stabilize::Stabilization stabilizationMessage;
388 // Read the existing tracker message.
389 std::fstream input(protobuf_data_path, ios::in | ios::binary);
390 if (!stabilizationMessage.ParseFromIstream(&input)) {
391 std::cerr << "Failed to parse protobuf message." << std::endl;
392 return false;
393 }
394
395 // Make sure the data maps are empty
396 transformationData.clear();
397 trajectoryData.clear();
398
399 // Iterate over all frames of the saved message and assign to the data maps
400 for (size_t i = 0; i < stabilizationMessage.frame_size(); i++) {
401 const pb_stabilize::Frame& pbFrameData = stabilizationMessage.frame(i);
402
403 // Load frame number
404 size_t id = pbFrameData.id();
405
406 // Load camera trajectory data
407 float x = pbFrameData.x();
408 float y = pbFrameData.y();
409 float a = pbFrameData.a();
410
411 // Assign data to trajectory map
412 trajectoryData[id] = CamTrajectory(x,y,a);
413
414 // Load transformation data
415 float dx = pbFrameData.dx();
416 float dy = pbFrameData.dy();
417 float da = pbFrameData.da();
418
419 // Assing data to transformation map
420 transformationData[id] = TransformParam(dx,dy,da);
421 }
422
423 // Delete all global objects allocated by libprotobuf.
424 google::protobuf::ShutdownProtobufLibrary();
425
426 return true;
427}
Header file for CVStabilization class.
Header file for all Exception classes.
TransformParam GetTransformParamData(size_t frameId)
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
CVStabilization(std::string processInfoJson, ProcessingController &processingController)
Set default smoothing window value to compute stabilization.
void AddFrameDataToProto(pb_stabilize::Frame *pbFrameData, CamTrajectory &trajData, TransformParam &transData, size_t frame_number)
Add frame stabilization data into protobuf message.
std::map< size_t, TransformParam > transformationData
void stabilizeClip(openshot::Clip &video, size_t _start=0, size_t _end=0, bool process_interval=false)
Process clip and store necessary stabilization data.
std::map< size_t, CamTrajectory > trajectoryData
CamTrajectory GetCamTrajectoryTrackedData(size_t frameId)
void SetJson(const std::string value)
Load JSON string into this object.
void SetError(bool err, std::string message)
float Start() const
Get start position (in seconds) of clip (trim start of video)
Definition: ClipBase.h:91
This class represents a clip (used to arrange readers on the timeline)
Definition: Clip.h:89
void Open() override
Open the internal reader.
Definition: Clip.cpp:393
float End() const override
Get end position (in seconds) of clip (trim end of video), which can be affected by the time curve.
Definition: Clip.cpp:429
std::shared_ptr< openshot::Frame > GetFrame(int64_t clip_frame_number) override
Get an openshot::Frame object for a specific frame number of this clip. The image size and number of ...
Definition: Clip.cpp:464
void Reader(openshot::ReaderBase *new_reader)
Set the current reader.
Definition: Clip.cpp:346
Exception for invalid JSON.
Definition: Exceptions.h:224
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16