Video Stabilizer
Sources:
Contents
Description of parameters:
- model=(Translation|TranslationAndScale|Rigid|Similarity|Affine|Homography) Set motion model. The default is 'Affine'.
- lin_prog_motion_est=(true|false) Turn on/off LP based motion estimation. Requires CLP library. The default is false.
- subset=(int|auto) Number of random samples per one motion hypothesis. The default is 'auto'.
- thresh=(float|auto) Maximum error to classify match as inlier. The default is 'auto'.
- outlier_ratio=<float> Motion estimation outlier ratio hypothesis. The default is 0.5.
- min_inlier_ratio=<float> Minimum inlier ratio to decide if estimated motion is OK. The default is 0.1.
- nkps=<int> Number of keypoints to find in each frame. The default is 1000.
- extra_kps=<int> Default is 0.
- local_outlier_rejection=(true|false) Perform local outlier rejection. The default is false.
- save_motions=<file_path> Save estimated motions into file. The default is ''.
- load_motions=<file_path> Load motions from file. The default is ''.
- radius=<int> Set sliding window radius. The default is 15.
- stdev=(float|auto) Set smoothing weights standard deviation. The default is 'auto' (i.e. sqrt(radius)).
- lin_prog_stab=(true|false) Turn on/off linear programming based stabilization method. Requires CLP library. Default false
- lps_trim_ratio=(float|auto) Trimming ratio used in linear programming based method.
- lps_w1=<float> 1st derivative weight. The default is 1.
- lps_w2=<float> 2nd derivative weight. The default is 10.
- lps_w3=<float> 3rd derivative weight. The default is 100.
- lps_w4=<float> Non-translation motion components weight. The default is 100.
- deblur=(true|false) Do deblurring.
- deblur_sens=<float> Set deblurring sensitivity (from 0 to +inf). The default is 0.1.
- trim_ratio=<float> Set trimming ratio (from 0 to 0.5). The default is 0.1.
- est_trim=(true|false) Estimate trim ratio automatically. The default is true.
- incl_constr=(true|false) Ensure the inclusion constraint is always satisfied. The default is false.
- border_mode=(Replicate|Reflect|Constant) Set border extrapolation mode. The default is 'Replicate'.
- mosaic=(true|false) Do consistent mosaicing. The default is false.
- mosaic_stdev=<float> Consistent mosaicing stdev threshold. The default is 10.0.
- motion_inpaint=(true|false) Do motion inpainting (requires CUDA support). The default is false.
- mi_dist_thresh=<float> Estimated flow distance threshold for motion inpainting. The default is 5.0.
- color_inpaint=(no|average|ns|telea) Do color inpainting. The defailt is 'no'.
- ci_radius=<float> Set color inpainting radius (for 'ns' and 'telea' options only). The default is 2.0
- wobble_suppress=(true|false) Perform wobble suppression. The default is false.
- ws_lin_prog_motion_est=(true|false) Turn on/off LP based motion estimation. Requires CLP library. The default is false.
- ws_period=<int> Set wobble suppression period. The default is 30.
- ws_model=(Translation|TranslationAndScale|Rigid|Similarity|Affine|Homography) Set wobble suppression motion model (must have more DOF than motion estimation model). The default is 'Homography'.
- ws_subset=(int|auto) Number of random samples per one motion hypothesis. The default is 'auto'.
- ws_thresh=(float|auto) Maximum error to classify match as inlier. The default is 'auto'.
- ws_outlier_ratio=<float> Motion estimation outlier ratio hypothesis. The default is 0.5.
- ws_min_inlier_ratio=<float> Minimum inlier ratio to decide if estimated motion is OK. The default is 0.1.
- ws_nkps=<int> Number of keypoints to find in each frame. The default is 1000.
- ws_extra_kps=<int> Default is 0.
- ws_local_outlier_rejection=(true|false) Perform local outlier rejection. The default is false.
- save_motions2=<file_path> Save motions estimated for wobble suppression. The default is ''.
- load_motions2=<file_path> Load motions for wobble suppression from file. The default is ''.
- gpu=(true|false) Use CUDA optimization whenever possible. The default is false.
- output=<file_path> Set output file path explicitely. The default is 'stabilized.avi'.
- fps=(float|auto) Set output video FPS explicitely. By default the source FPS is used (auto).
- quiet Don't show output video frames.
- logger=(LogToMATLAB|NullLog) Log message to specified sink. Default is 'NullLog'.
Note: some configurations lead to two passes, some to single pass.
Options
default values
opts = struct(); opts.inputPath = ''; opts.model = 'Affine'; opts.lin_prog_motion_est = false; opts.subset = 'auto'; opts.thresh = 'auto'; opts.outlier_ratio = 0.5; opts.min_inlier_ratio = 0.1; opts.nkps = 1000; opts.extra_kps = 0; opts.local_outlier_rejection = false; opts.save_motions = ''; opts.load_motions = ''; opts.radius = 15; opts.stdev = 'auto'; opts.lin_prog_stab = false; opts.lps_trim_ratio = 'auto'; opts.lps_w1 = 1; opts.lps_w2 = 10; opts.lps_w3 = 100; opts.lps_w4 = 100; opts.deblur = false; opts.deblur_sens = 0.1; opts.est_trim = true; opts.trim_ratio = 0.1; opts.incl_constr = false; opts.border_mode = 'Replicate'; opts.mosaic = false; opts.mosaic_stdev = 10.0; opts.motion_inpaint = false; opts.mi_dist_thresh = 5.0; opts.color_inpaint = 'no'; opts.ci_radius = 2.0; opts.wobble_suppress = false; opts.ws_lin_prog_motion_est = false; opts.ws_period = 30; opts.ws_model = 'Homography'; opts.ws_subset = 'auto'; opts.ws_thresh = 'auto'; opts.ws_outlier_ratio = 0.5; opts.ws_min_inlier_ratio = 0.1; opts.ws_nkps = 1000; opts.ws_extra_kps = 0; opts.ws_local_outlier_rejection = false; opts.save_motions2 = ''; opts.load_motions2 = ''; opts.gpu = false; opts.output = ''; opts.fps = 'auto'; opts.quiet = false; opts.logger = 'NullLog';
override options
if mexopencv.require('vision') opts.inputPath = which('shaky_car.avi'); end %opts.model = 'Translation'; %opts.min_inlier_ratio = 0.01; %opts.nkps = 500; %opts.local_outlier_rejection = true; %opts.radius = 150; %opts.deblur = true; opts.est_trim = false; %HACK: otherwise corrupts frames! opts.trim_ratio = 0; %HACK: otherwise corrupts frames! %opts.border_mode = 'Constant'; %opts.mosaic = true; %opts.color_inpaint = 'telea'; %opts.wobble_suppress = true; %opts.ws_local_outlier_rejection = true; opts.output = fullfile(tempdir(), 'stabilized.avi'); opts.gpu = false; %TODO: not yet implemented display(opts)
opts = struct with fields: inputPath: 'C:\Program Files\MATLAB\R2017a\toolbox\vision\visiondata\shaky_car.avi' model: 'Affine' lin_prog_motion_est: 0 subset: 'auto' thresh: 'auto' outlier_ratio: 0.5000 min_inlier_ratio: 0.1000 nkps: 1000 extra_kps: 0 local_outlier_rejection: 0 save_motions: '' load_motions: '' radius: 15 stdev: 'auto' lin_prog_stab: 0 lps_trim_ratio: 'auto' lps_w1: 1 lps_w2: 10 lps_w3: 100 lps_w4: 100 deblur: 0 deblur_sens: 0.1000 est_trim: 0 trim_ratio: 0 incl_constr: 0 border_mode: 'Replicate' mosaic: 0 mosaic_stdev: 10 motion_inpaint: 0 mi_dist_thresh: 5 color_inpaint: 'no' ci_radius: 2 wobble_suppress: 0 ws_lin_prog_motion_est: 0 ws_period: 30 ws_model: 'Homography' ws_subset: 'auto' ws_thresh: 'auto' ws_outlier_ratio: 0.5000 ws_min_inlier_ratio: 0.1000 ws_nkps: 1000 ws_extra_kps: 0 ws_local_outlier_rejection: 0 save_motions2: '' load_motions2: '' gpu: 0 output: 'C:\Users\Amro\AppData\Local\Temp\stabilized.avi' fps: 'auto' quiet: 0 logger: 'NullLog'
One vs. Two pass stabilizer
determine whether we must use one pass or two pass stabilizer
isTwoPass = opts.est_trim || opts.wobble_suppress || opts.lin_prog_stab; if isTwoPass stab = cv.TwoPassStabilizer(); else stab = cv.OnePassStabilizer(); end display(stab)
stab = OnePassStabilizer with properties: id: 1 Radius: 15 TrimRatio: 0 CorrectionForInclusion: 0 BorderMode: 'Replicate'
source video
assert(exist(opts.inputPath, 'file') == 2, 'specify valid video file'); stab.setFrameSource('VideoFileSource', opts.inputPath); vid = stab.getFrameSource(); fprintf('Frame Count (rough): %d\n', vid.Count); if strcmp(opts.fps, 'auto') outputFps = vid.FPS; else outputFps = opts.fps; end display(vid)
Frame Count (rough): 132 vid = struct with fields: TypeId: 'class cv::videostab::VideoFileSource' Width: 320 Height: 240 FPS: 30.0000 Count: 132
prepare motion estimation builders
for stabilizer (prefix="") and wobble suppressor (prefix="ws_")
motionEstBuilder = {}; wsMotionEstBuilder = {}; for prefix = {'', 'ws_'} getopts = @(name) opts.([prefix{1} name]); if getopts('lin_prog_motion_est') est = {'MotionEstimatorL1', 'MotionModel',getopts('model')}; else ransac = stab.RansacParamsDefault2dMotion(getopts('model')); if ~strcmp(getopts('subset'), 'auto') ransac.Size = getopts('subset'); end if ~strcmp(getopts('thresh'), 'auto') ransac.Thresh = getopts('thresh'); end ransac.Eps = getopts('outlier_ratio'); est = {'MotionEstimatorRansacL2', 'MotionModel',getopts('model'), ... 'RansacParams',ransac, 'MinInlierRatio',getopts('min_inlier_ratio')}; end if getopts('local_outlier_rejection') ransacParams = stab.RansacParamsDefault2dMotion('Translation'); if ~strcmp(getopts('thresh'), 'auto') ransacParams.Thresh = getopts('thresh'); end outlierRejector = {'TranslationBasedLocalOutlierRejector', ... 'RansacParams',ransacParams}; else outlierRejector = {'NullOutlierRejector'}; end if opts.gpu kbest = {'KeypointBasedMotionEstimatorGpu', est, ... 'OutlierRejector',outlierRejector, 'MotionModel',getopts('model')}; else kbest = {'KeypointBasedMotionEstimator', est, ... 'Detector',{'GFTTDetector', 'MaxFeatures',getopts('nkps')}, ... 'OutlierRejector',outlierRejector, 'MotionModel',getopts('model')}; end if isempty(prefix{1}) motionEstBuilder = kbest; else wsMotionEstBuilder = kbest; end end celldisp(motionEstBuilder) celldisp(wsMotionEstBuilder)
motionEstBuilder{1} = KeypointBasedMotionEstimator motionEstBuilder{2}{1} = MotionEstimatorRansacL2 motionEstBuilder{2}{2} = MotionModel motionEstBuilder{2}{3} = Affine motionEstBuilder{2}{4} = RansacParams motionEstBuilder{2}{5} = Size: 3 Thresh: 0.5000 Eps: 0.5000 Prob: 0.9900 motionEstBuilder{2}{6} = MinInlierRatio motionEstBuilder{2}{7} = 0.1000 motionEstBuilder{3} = Detector motionEstBuilder{4}{1} = GFTTDetector motionEstBuilder{4}{2} = MaxFeatures motionEstBuilder{4}{3} = 1000 motionEstBuilder{5} = OutlierRejector motionEstBuilder{6}{1} = NullOutlierRejector motionEstBuilder{7} = MotionModel motionEstBuilder{8} = Affine wsMotionEstBuilder{1} = KeypointBasedMotionEstimator wsMotionEstBuilder{2}{1} = MotionEstimatorRansacL2 wsMotionEstBuilder{2}{2} = MotionModel wsMotionEstBuilder{2}{3} = Homography wsMotionEstBuilder{2}{4} = RansacParams wsMotionEstBuilder{2}{5} = Size: 4 Thresh: 0.5000 Eps: 0.5000 Prob: 0.9900 wsMotionEstBuilder{2}{6} = MinInlierRatio wsMotionEstBuilder{2}{7} = 0.1000 wsMotionEstBuilder{3} = Detector wsMotionEstBuilder{4}{1} = GFTTDetector wsMotionEstBuilder{4}{2} = MaxFeatures wsMotionEstBuilder{4}{3} = 1000 wsMotionEstBuilder{5} = OutlierRejector wsMotionEstBuilder{6}{1} = NullOutlierRejector wsMotionEstBuilder{7} = MotionModel wsMotionEstBuilder{8} = Homography
set properties and algorithms of stabilizer
if isTwoPass % we must use two pass stabilizer stab.EstimateTrimRatio = opts.est_trim; % determine stabilization technique if opts.lin_prog_stab if strcmp(opts.lps_trim_ratio, 'auto') trimRatio = opts.trim_ratio; else trimRatio = opts.lps_trim_ratio; end stab.setMotionStabilizer('LpMotionStabilizer', ... 'FrameSize',[vid.Width vid.Height], 'TrimRatio',trimRatio, ... 'Weight1',opts.lps_w1, 'Weight2',opts.lps_w2, ... 'Weight3',opts.lps_w3, 'Weight4',opts.lps_w4); else if strcmp(opts.stdev, 'auto') stdev = -1; else stdev = opts.stdev; end stab.setMotionStabilizer('GaussianMotionFilter', ... 'Radius',opts.radius, 'Stdev',stdev); end % init wobble suppressor if necessary if opts.wobble_suppress if opts.gpu ws = 'MoreAccurateMotionWobbleSuppressorGpu'; else ws = 'MoreAccurateMotionWobbleSuppressor'; end motionEstimator = wsMotionEstBuilder; if ~isempty(opts.load_motions2) motionEstimator = {'FromFileMotionReader', opts.load_motions2, ... 'MotionModel',opts.ws_model}; end if ~isempty(opts.save_motions2) motionEstimator = {'ToFileMotionWriter', opts.save_motions2, ... wsMotionEstBuilder, 'MotionModel',opts.ws_model}; end stab.setWobbleSuppressor(ws, 'Period',opts.ws_period, ... 'MotionEstimator',motionEstimator); end else % we must use one pass stabilizer if strcmp(opts.stdev, 'auto') stdev = -1; else stdev = opts.stdev; end stab.setMotionFilter('GaussianMotionFilter', ... 'Radius',opts.radius, 'Stdev',stdev); end
init motion estimator
motionEstimator = motionEstBuilder; if ~isempty(opts.load_motions) motionEstimator = {'FromFileMotionReader', opts.load_motions, ... 'MotionModel',opts.model}; end if ~isempty(opts.save_motions) motionEstimator = {'ToFileMotionWriter', opts.save_motions, ... motionEstBuilder, 'MotionModel',opts.model}; end stab.setMotionEstimator(motionEstimator{:});
stab.Radius = opts.radius; % set up trimming parameters stab.TrimRatio = opts.trim_ratio; stab.CorrectionForInclusion = opts.incl_constr; % border extrapolation mode stab.BorderMode = opts.border_mode; % logger stab.setLog(opts.logger);
init deblurer
if opts.deblur stab.setDeblurer('WeightingDeblurer', 'Radius',opts.radius, ... 'Sensitivity',opts.deblur_sens); end
init inpainter
inpainters = {}; if opts.mosaic inpainters{end+1} = {'ConsistentMosaicInpainter', ... 'StdevThresh',opts.mosaic_stdev}; end if opts.motion_inpaint inpainters{end+1} = {'MotionInpainter', ... 'DistThreshold',opts.mi_dist_thresh}; end if strcmp(opts.color_inpaint, 'average') inpainters{end+1} = {'ColorAverageInpainter'}; elseif strcmp(opts.color_inpaint, 'ns') inpainters{end+1} = {'ColorInpainter', 'Method','NS', ... 'Radius2',opts.ci_radius}; elseif strcmp(opts.color_inpaint, 'telea') inpainters{end+1} = {'ColorInpainter', 'Method','Telea', ... 'Radius2',opts.ci_radius}; elseif ~strcmp(opts.color_inpaint, 'no') error('unknown color inpainting method: %s', opts.color_inpaint); end if ~isempty(inpainters) stab.setInpainter('InpaintingPipeline',inpainters, 'Radius',opts.radius); end
run
writer = cv.VideoWriter(); nframes = 0; if ~opts.quiet hImg = imshow(zeros([vid.Height vid.Width 3], 'uint8')); title('stabilizedFrame') end while true % for each stabilized frame frame = stab.nextFrame('FlipChannels',true); if isempty(frame), break; end nframes = nframes + 1; % init writer (once) and save stabilized frame if ~isempty(opts.output) if ~writer.isOpened() sz = size(frame); writer.open(opts.output, [sz(2) sz(1)], ... 'FourCC','XVID', 'FPS',outputFps); end writer.write(frame); end % show stabilized frame if ~opts.quiet set(hImg, 'CData',frame) drawnow end if true %HACK: break early, processing of few last frames is slow! if nframes > (vid.Count - ceil(opts.radius)) break; end end end clear writer fprintf('processed frames: %d\n', nframes);
processed frames: 118