/*
* 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 ();
}
}
}