| /* |
| * 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 <[email protected]> |
| */ |
| |
| #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 (); |
| } |
| } |
| |
| } |