/*
|
* cl_video_stabilizer.cpp - Digital Video Stabilization using IMU (Gyroscope, Accelerometer)
|
*
|
* Copyright (c) 2017 Intel Corporation
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*
|
* Author: Zong Wei <wei.zong@intel.com>
|
*/
|
|
#include "cl_video_stabilizer.h"
|
#include "cl_utils.h"
|
|
namespace XCam {
|
|
static const XCamKernelInfo kernel_video_stab_warp_info [] = {
|
{
|
"kernel_image_warp_8_pixel",
|
#include "kernel_image_warp.clx"
|
, 0,
|
},
|
{
|
"kernel_image_warp_1_pixel",
|
#include "kernel_image_warp.clx"
|
, 0,
|
}
|
};
|
|
CLVideoStabilizerKernel::CLVideoStabilizerKernel (
|
const SmartPtr<CLContext> &context,
|
const char *name,
|
uint32_t channel,
|
SmartPtr<CLImageHandler> &handler)
|
: CLImageWarpKernel (context, name, channel, handler)
|
{
|
_handler = handler.dynamic_cast_ptr<CLVideoStabilizer> ();
|
}
|
|
CLVideoStabilizer::CLVideoStabilizer (const SmartPtr<CLContext> &context, const char *name)
|
: CLImageWarpHandler (context, name)
|
{
|
_projector = new ImageProjector ();
|
_filter_radius = 15;
|
_motion_filter = new MotionFilter (_filter_radius, 10);
|
|
CoordinateSystemConv world_to_device (AXIS_X, AXIS_MINUS_Z, AXIS_NONE);
|
CoordinateSystemConv device_to_image (AXIS_X, AXIS_Y, AXIS_Y);
|
|
align_coordinate_system (world_to_device, device_to_image);
|
|
_input_frame_id = -1;
|
_frame_ts[0] = 0;
|
_frame_ts[1] = 0;
|
_stabilized_frame_id = -1;
|
}
|
|
SmartPtr<VideoBuffer>
|
CLVideoStabilizer::get_warp_input_buf ()
|
{
|
XCAM_ASSERT (_input_buf_list.size () >= 1);
|
|
SmartPtr<VideoBuffer> buf = (*_input_buf_list.begin ());
|
return buf;
|
}
|
|
bool
|
CLVideoStabilizer::is_ready ()
|
{
|
return CLImageHandler::is_ready ();
|
}
|
|
void
|
CLVideoStabilizer::reset_counter ()
|
{
|
XCAM_LOG_DEBUG ("reset video stabilizer counter");
|
|
_input_frame_id = -1;
|
_stabilized_frame_id = -1;
|
xcam_mem_clear (_frame_ts);
|
_device_pose[0].clear ();
|
_device_pose[1].clear ();
|
_input_buf_list.clear ();
|
}
|
|
XCamReturn
|
CLVideoStabilizer::execute_done (SmartPtr<VideoBuffer> &output)
|
{
|
if (!_input_buf_list.empty ()) {
|
_input_buf_list.pop_front ();
|
}
|
|
CLImageWarpHandler::execute_done (output);
|
|
return XCAM_RETURN_NO_ERROR;
|
}
|
|
XCamReturn
|
CLVideoStabilizer::prepare_parameters (SmartPtr<VideoBuffer> &input, SmartPtr<VideoBuffer> &output)
|
{
|
XCamReturn ret = XCAM_RETURN_NO_ERROR;
|
XCAM_ASSERT (input.ptr () && output.ptr ());
|
XCAM_UNUSED (output);
|
|
if (_input_buf_list.size () >= 2 * _filter_radius + 1) {
|
_input_buf_list.pop_front ();
|
}
|
_input_buf_list.push_back (input);
|
_input_frame_id++;
|
|
const VideoBufferInfo & video_info_in = input->get_video_info ();
|
|
_frame_ts[_input_frame_id % 2] = input->get_timestamp ();
|
|
SmartPtr<DevicePose> data = input->find_typed_metadata<DevicePose> ();
|
while (data.ptr ()) {
|
_device_pose[_input_frame_id % 2].push_back (data);
|
|
input->remove_metadata (data);
|
|
data = input->find_typed_metadata<DevicePose> ();
|
}
|
|
Mat3d homography;
|
if (_input_frame_id > 0) {
|
homography = analyze_motion (
|
_frame_ts[(_input_frame_id - 1) % 2],
|
_device_pose[(_input_frame_id - 1) % 2],
|
_frame_ts[_input_frame_id % 2],
|
_device_pose[_input_frame_id % 2]);
|
|
if (_motions.size () >= 2 * _filter_radius + 1) {
|
_motions.pop_front ();
|
}
|
_motions.push_back (homography);
|
|
_device_pose[(_input_frame_id - 1) % 2].clear ();
|
}
|
|
Mat3d proj_mat;
|
XCamDVSResult warp_config;
|
if (_input_frame_id >= _filter_radius)
|
{
|
_stabilized_frame_id = _input_frame_id - _filter_radius;
|
int32_t cur_stabilized_pos = XCAM_MIN (_stabilized_frame_id, _filter_radius + 1);
|
|
XCAM_LOG_DEBUG ("input id(%ld), stab id(%ld), cur stab pos(%d), filter r(%d)",
|
_input_frame_id,
|
_stabilized_frame_id,
|
cur_stabilized_pos,
|
_filter_radius);
|
|
proj_mat = stabilize_motion (cur_stabilized_pos, _motions);
|
|
Mat3d proj_inv_mat = proj_mat.inverse ();
|
warp_config.frame_id = _stabilized_frame_id;
|
warp_config.frame_width = video_info_in.width;
|
warp_config.frame_height = video_info_in.height;
|
|
for( int i = 0; i < 3; i++ ) {
|
for (int j = 0; j < 3; j++) {
|
warp_config.proj_mat[i * 3 + j] = proj_inv_mat(i, j);
|
}
|
}
|
|
set_warp_config (warp_config);
|
} else {
|
ret = XCAM_RETURN_BYPASS;
|
}
|
|
return ret;
|
}
|
|
XCamReturn
|
CLVideoStabilizer::set_sensor_calibration (CalibrationParams ¶ms)
|
{
|
XCamReturn ret = XCAM_RETURN_NO_ERROR;
|
|
if (_projector.ptr ()) {
|
_projector->set_sensor_calibration (params);
|
} else {
|
ret = XCAM_RETURN_ERROR_PARAM;
|
}
|
|
return ret;
|
}
|
|
XCamReturn
|
CLVideoStabilizer::set_camera_intrinsics (
|
double focal_x,
|
double focal_y,
|
double offset_x,
|
double offset_y,
|
double skew)
|
{
|
XCamReturn ret = XCAM_RETURN_NO_ERROR;
|
|
if (_projector.ptr ()) {
|
_projector->set_camera_intrinsics(
|
focal_x,
|
focal_y,
|
offset_x,
|
offset_y,
|
skew);
|
} else {
|
ret = XCAM_RETURN_ERROR_PARAM;
|
}
|
|
return ret;
|
}
|
|
XCamReturn
|
CLVideoStabilizer::align_coordinate_system (
|
CoordinateSystemConv &world_to_device,
|
CoordinateSystemConv &device_to_image)
|
{
|
XCamReturn ret = XCAM_RETURN_NO_ERROR;
|
|
_world_to_device = world_to_device;
|
_device_to_image = device_to_image;
|
|
return ret;
|
}
|
|
XCamReturn
|
CLVideoStabilizer::set_motion_filter (uint32_t radius, float stdev)
|
{
|
XCamReturn ret = XCAM_RETURN_NO_ERROR;
|
|
_filter_radius = radius;
|
|
if (_motion_filter.ptr ()) {
|
_motion_filter->set_filters (radius, stdev);
|
} else {
|
ret = XCAM_RETURN_ERROR_PARAM;
|
}
|
|
return ret;
|
}
|
|
Mat3d
|
CLVideoStabilizer::analyze_motion (
|
int64_t frame0_ts,
|
DevicePoseList pose0_list,
|
int64_t frame1_ts,
|
DevicePoseList pose1_list)
|
{
|
if (pose0_list.empty () || pose1_list.empty () || !_projector.ptr ()) {
|
return Mat3d ();
|
}
|
XCAM_ASSERT (frame0_ts < frame1_ts);
|
|
Mat3d ext0 = _projector->calc_camera_extrinsics (frame0_ts, pose0_list);
|
|
Mat3d ext1 = _projector->calc_camera_extrinsics (frame1_ts, pose1_list);
|
|
Mat3d extrinsic0 = _projector->align_coordinate_system (
|
_world_to_device,
|
ext0,
|
_device_to_image);
|
|
Mat3d extrinsic1 = _projector->align_coordinate_system (
|
_world_to_device,
|
ext1,
|
_device_to_image);
|
|
return _projector->calc_projective (extrinsic0, extrinsic1);
|
}
|
|
Mat3d
|
CLVideoStabilizer::stabilize_motion (int32_t stab_frame_id, std::list<Mat3d> &motions)
|
{
|
if (_motion_filter.ptr ()) {
|
return _motion_filter->stabilize (stab_frame_id, motions, _input_frame_id);
|
} else {
|
return Mat3d ();
|
}
|
}
|
|
static SmartPtr<CLVideoStabilizerKernel>
|
create_kernel_video_stab (
|
const SmartPtr<CLContext> &context,
|
uint32_t channel,
|
SmartPtr<CLImageHandler> handler)
|
{
|
SmartPtr<CLVideoStabilizerKernel> stab_kernel;
|
|
const char *name = (channel == CL_IMAGE_CHANNEL_Y ? "kernel_image_warp_y" : "kernel_image_warp_uv");
|
char build_options[1024];
|
xcam_mem_clear (build_options);
|
|
snprintf (build_options, sizeof (build_options),
|
" -DWARP_Y=%d ",
|
(channel == CL_IMAGE_CHANNEL_Y ? 1 : 0));
|
|
stab_kernel = new CLVideoStabilizerKernel (context, name, channel, handler);
|
XCAM_ASSERT (stab_kernel.ptr ());
|
XCAM_FAIL_RETURN (
|
ERROR, stab_kernel->build_kernel (kernel_video_stab_warp_info[KernelImageWarp], build_options) == XCAM_RETURN_NO_ERROR,
|
NULL, "build video stab kernel failed");
|
XCAM_ASSERT (stab_kernel->is_valid ());
|
|
return stab_kernel;
|
}
|
|
SmartPtr<CLImageHandler>
|
create_cl_video_stab_handler (const SmartPtr<CLContext> &context)
|
{
|
SmartPtr<CLImageHandler> video_stab;
|
SmartPtr<CLImageKernel> stab_kernel;
|
|
video_stab = new CLVideoStabilizer (context);
|
XCAM_ASSERT (video_stab.ptr ());
|
|
stab_kernel = create_kernel_video_stab (context, CL_IMAGE_CHANNEL_Y, video_stab);
|
XCAM_ASSERT (stab_kernel.ptr ());
|
video_stab->add_kernel (stab_kernel);
|
|
stab_kernel = create_kernel_video_stab (context, CL_IMAGE_CHANNEL_UV, video_stab);
|
XCAM_ASSERT (stab_kernel.ptr ());
|
video_stab->add_kernel (stab_kernel);
|
|
return video_stab;
|
}
|
|
MotionFilter::MotionFilter (uint32_t radius, float stdev)
|
: _radius (radius),
|
_stdev (stdev)
|
{
|
set_filters (radius, stdev);
|
}
|
|
MotionFilter::~MotionFilter ()
|
{
|
_weight.clear ();
|
}
|
|
void
|
MotionFilter::set_filters (uint32_t radius, float stdev)
|
{
|
_radius = radius;
|
_stdev = stdev > 0.f ? stdev : std::sqrt (static_cast<float>(radius));
|
|
int scale = 2 * _radius + 1;
|
float dis = 0.0f;
|
float sum = 0.0f;
|
|
_weight.resize (2 * _radius + 1);
|
|
for (int i = 0; i < scale; i++) {
|
dis = ((float)i - radius) * ((float)i - radius);
|
_weight[i] = exp(-dis / (_stdev * _stdev));
|
sum += _weight[i];
|
}
|
|
for (int i = 0; i <= scale; i++) {
|
_weight[i] /= sum;
|
}
|
|
}
|
|
Mat3d
|
MotionFilter::cumulate_motion (uint32_t index, uint32_t from, std::list<Mat3d> &motions)
|
{
|
Mat3d motion;
|
motion.eye ();
|
|
uint32_t id = 0;
|
std::list<Mat3d>::iterator it;
|
|
if (from < index) {
|
for (id = 0, it = motions.begin (); it != motions.end (); id++, ++it) {
|
if (from <= id && id < index) {
|
motion = (*it) * motion;
|
}
|
}
|
motion = motion.inverse ();
|
} else if (from > index) {
|
for (id = 0, it = motions.begin (); it != motions.end (); id++, ++it) {
|
if (index <= id && id < from) {
|
motion = (*it) * motion;
|
}
|
}
|
}
|
|
return motion;
|
}
|
|
Mat3d
|
MotionFilter::stabilize (int32_t index,
|
std::list<Mat3d> &motions,
|
int32_t max)
|
{
|
Mat3d res;
|
res.zeros ();
|
|
double sum = 0.0f;
|
int32_t idx_min = XCAM_MAX ((index - _radius), 0);
|
int32_t idx_max = XCAM_MIN ((index + _radius), max);
|
|
for (int32_t i = idx_min; i <= idx_max; ++i)
|
{
|
res = res + cumulate_motion (index, i, motions) * _weight[i];
|
sum += _weight[i];
|
}
|
if (sum > 0.0f) {
|
return res * (1 / sum);
|
}
|
else {
|
return Mat3d ();
|
}
|
}
|
|
}
|