video_stabilization.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. #include "video_stabilization.h"
  2. #include "image_process.h"
  3. #include "third_party/scope_guard.hpp"
  4. #include <opencv2/calib3d.hpp>
  5. #include <opencv2/cudaoptflow.hpp>
  6. #include <opencv2/cudaimgproc.hpp>
  7. #include <Eigen/Dense>
  8. #include <cmath>
  9. #include <deque>
  10. #include <ranges>
  11. #include <vector>
  12. namespace {
  13. template<typename T>
  14. std::vector<T> download(const cv::cuda::GpuMat &d_mat, int type) {
  15. auto ret = std::vector<T>(d_mat.cols);
  16. auto mat = cv::Mat(1, d_mat.cols, type, ret.data());
  17. d_mat.download(mat);
  18. return ret;
  19. }
  20. struct point_tracker {
  21. cv::Ptr<cv::cuda::CornersDetector> detector;
  22. cv::Ptr<cv::cuda::SparsePyrLKOpticalFlow> tracker;
  23. sp_image prev;
  24. using points_type = std::vector<cv::Point2f>;
  25. struct result_type {
  26. points_type prev_pts;
  27. points_type curr_pts;
  28. };
  29. using create_config = video_stabilization::point_tracker_config;
  30. create_config conf;
  31. explicit point_tracker(const create_config _conf)
  32. : conf(_conf) {
  33. detector = cv::cuda::createGoodFeaturesToTrackDetector(
  34. CV_8UC1, conf.max_feature_points,
  35. 0.3, 7, 7);
  36. tracker = cv::cuda::SparsePyrLKOpticalFlow::create();
  37. }
  38. // Input RGB image
  39. std::optional<result_type> process(const sp_image &img) {
  40. auto closer = sg::make_scope_guard([&] { prev = img; });
  41. if (prev.empty()) return {};
  42. auto stream_guard = cv_stream_guard();
  43. cv::cuda::GpuMat d_prev_pts;
  44. if (true) {
  45. const auto prev_gray = image_rgb_to_gray(prev);
  46. const auto helper = read_access_helper(prev_gray.cuda());
  47. detector->detect(prev_gray.cv_gpu_mat(helper.ptr()), d_prev_pts,
  48. cv::noArray(), stream_guard.cv_stream());
  49. }
  50. cv::cuda::GpuMat d_curr_pts, d_status;
  51. if (true) {
  52. const auto prev_helper = read_access_helper(prev.cuda());
  53. const auto curr_helper = read_access_helper(img.cuda());
  54. tracker->calc(prev.cv_gpu_mat(prev_helper.ptr()),
  55. img.cv_gpu_mat(curr_helper.ptr()),
  56. d_prev_pts, d_curr_pts, d_status);
  57. }
  58. auto status = download<uchar>(d_status, CV_8UC1);
  59. auto valid_indices = std::views::iota(0ull, status.size())
  60. | std::views::filter([&](auto i) { return status[i]; });
  61. auto prev_pts = download<cv::Point2f>(d_prev_pts, CV_32FC2);
  62. auto curr_pts = download<cv::Point2f>(d_curr_pts, CV_32FC2);
  63. auto valid_prev_pts = valid_indices
  64. | std::views::transform([&](auto i) { return prev_pts[i]; });
  65. auto valid_curr_pts = valid_indices
  66. | std::views::transform([&](auto i) { return curr_pts[i]; });
  67. auto ret = result_type();
  68. std::ranges::copy(valid_prev_pts, std::back_inserter(ret.prev_pts));
  69. std::ranges::copy(valid_curr_pts, std::back_inserter(ret.curr_pts));
  70. return ret;
  71. }
  72. };
  73. struct transform_stabilization {
  74. using matrix_type = Eigen::Matrix<float, 2, 3>;
  75. using matrix_param_type = Eigen::Vector4f;
  76. using record_type = Eigen::VectorXf;
  77. using trajectory_type = Eigen::Matrix4Xf;
  78. static record_type optimize_simple(const record_type &traj) {
  79. const auto num = traj.rows();
  80. auto ret = record_type(num);
  81. ret.setConstant(traj.mean());
  82. return ret;
  83. }
  84. static trajectory_type optimize_trajectory(const trajectory_type &traj) {
  85. const auto num_dims = traj.rows();
  86. auto ret = trajectory_type(num_dims, traj.cols());
  87. for (auto i = 0; i < num_dims; ++i) {
  88. ret.row(i) = optimize_simple(traj.row(i));
  89. }
  90. return ret;
  91. }
  92. static matrix_param_type decompose_transform(const matrix_type &mat) {
  93. const auto scale = sqrtf(mat.block<2, 2>(0, 0).determinant());
  94. const auto angle = atan2f(mat(1, 0), mat(0, 0));
  95. const auto tx = mat(0, 2);
  96. const auto ty = mat(1, 2);
  97. return {logf(scale), angle, tx, ty};
  98. }
  99. static matrix_type compose_transform(const matrix_param_type &param) {
  100. const auto scale = expf(param(0)), angle = param(1);
  101. const auto tx = param(2), ty = param(3);
  102. auto ret = matrix_type();
  103. ret << cosf(angle), -sinf(angle), tx,
  104. sinf(angle), cosf(angle), ty;
  105. ret.block<2, 2>(0, 0) *= scale;
  106. return ret;
  107. }
  108. using create_config = video_stabilization::transform_stabilization_config;
  109. create_config conf;
  110. size_t buffer_length = 0;
  111. std::deque<matrix_param_type> transform_buf;
  112. explicit transform_stabilization(const create_config &_conf)
  113. : conf(_conf) {
  114. buffer_length = 2 * conf.future_frames + 1;
  115. }
  116. std::optional<matrix_type> process(const matrix_type &mat) {
  117. const auto param = decompose_transform(mat);
  118. transform_buf.push_back(param);
  119. if (transform_buf.size() <= conf.future_frames) return {};
  120. if (transform_buf.size() > buffer_length) {
  121. transform_buf.pop_front();
  122. }
  123. auto curr_traj = trajectory_type(4, transform_buf.size());
  124. for (int i = 0; i < transform_buf.size(); i++) {
  125. curr_traj.col(i) = transform_buf[i];
  126. if (i > 0) {
  127. curr_traj.col(i) += curr_traj.col(i - 1);
  128. }
  129. }
  130. const auto traj_opt = optimize_trajectory(curr_traj);
  131. const auto curr_pos = conf.future_frames;
  132. const auto diff = traj_opt.col(curr_pos) - curr_traj.col(curr_pos);
  133. const auto curr = transform_buf[curr_pos] + diff;
  134. return compose_transform(curr);
  135. }
  136. };
  137. }
  138. struct video_stabilization::impl {
  139. using matrix_type = transform_stabilization::matrix_type;
  140. static matrix_type combine_transform(const matrix_type &mat1, const matrix_type &mat2) {
  141. auto ret = matrix_type();
  142. const auto r1_mat = mat1.leftCols<2>();
  143. ret.leftCols<2>() = r1_mat * mat2.leftCols<2>();
  144. ret.rightCols<1>() = r1_mat * mat2.rightCols<1>() + mat1.rightCols<1>();
  145. return ret;
  146. }
  147. static matrix_type identity_transform() {
  148. auto ret = matrix_type();
  149. ret.setZero();
  150. ret.leftCols<2>().setIdentity();
  151. return ret;
  152. }
  153. static glm::mat3 to_mat3(const matrix_type &mat) {
  154. auto ret = glm::mat3();
  155. for (auto i = 0; i < 2; ++i)
  156. for (auto j = 0; j < 3; ++j) {
  157. ret[j][i] = mat(i, j);
  158. }
  159. return ret;
  160. }
  161. static matrix_type to_transform(const cv::Mat &mat) {
  162. assert(mat.rows == 2 && mat.cols == 3);
  163. assert(mat.type() == CV_64FC1);
  164. auto ret = matrix_type();
  165. for (auto i = 0; i < 2; ++i)
  166. for (auto j = 0; j < 3; ++j) {
  167. ret(i, j) = mat.at<double>(i, j);
  168. }
  169. return ret;
  170. }
  171. static constexpr auto min_tracked_points = 10;
  172. create_config conf;
  173. std::optional<point_tracker> track_left, track_right;
  174. std::optional<transform_stabilization> transform_stab;
  175. std::deque<sp_image> img_buf;
  176. [[nodiscard]] sp_image transform_image(const sp_image &img, matrix_type mat) const {
  177. const auto center = cv::Point2f(img.width() / 2.f, img.height() / 2.f);
  178. const auto fix_mat = cv::getRotationMatrix2D(center, 0, 1.f + conf.enlarge_factor);
  179. mat = combine_transform(to_transform(fix_mat), mat);
  180. return image_warp_affine(img, to_mat3(mat));
  181. }
  182. std::optional<sp_image> process_mono(const sp_image &img) {
  183. assert(!conf.stereo_mode);
  184. const auto pts = track_left->process(img);
  185. if (!pts) return {};
  186. matrix_type mat;
  187. if (pts->prev_pts.size() < min_tracked_points) [[unlikely]] {
  188. mat = identity_transform();
  189. } else {
  190. const auto cv_mat = cv::estimateAffinePartial2D(
  191. pts->prev_pts, pts->curr_pts);
  192. mat = to_transform(cv_mat);
  193. }
  194. const auto cur_mat = transform_stab->process(mat);
  195. img_buf.push_back(img);
  196. if (!cur_mat) return {};
  197. const auto cur_img = img_buf.front();
  198. img_buf.pop_front();
  199. return transform_image(cur_img, *cur_mat);
  200. }
  201. std::optional<sp_image> process_stereo(const sp_image &img) {
  202. assert(conf.stereo_mode);
  203. auto [img_left, img_right] = image_stereo_split_view(img);
  204. auto pts_left = track_left->process(img_left);
  205. auto pts_right = track_right->process(img_right);
  206. if (!pts_left || !pts_right) return {};
  207. auto num_points = pts_left->prev_pts.size() + pts_right->prev_pts.size();
  208. matrix_type mat;
  209. if (num_points < min_tracked_points) [[unlikely]] {
  210. mat = identity_transform();
  211. } else {
  212. auto prev_pts = std::move(pts_left->prev_pts);
  213. prev_pts.reserve(num_points);
  214. std::ranges::copy(pts_right->prev_pts, std::back_inserter(prev_pts));
  215. auto curr_pts = std::move(pts_left->curr_pts);
  216. curr_pts.reserve(num_points);
  217. std::ranges::copy(pts_right->curr_pts, std::back_inserter(curr_pts));
  218. const auto cv_mat = cv::estimateAffinePartial2D(prev_pts, curr_pts);
  219. mat = to_transform(cv_mat);
  220. }
  221. const auto cur_mat = transform_stab->process(mat);
  222. img_buf.push_back(img);
  223. if (!cur_mat) return {};
  224. const auto cur_img = img_buf.front();
  225. img_buf.pop_front();
  226. auto [cur_left, cur_right] = image_stereo_split_view(cur_img);
  227. cur_left = transform_image(cur_left, *cur_mat);
  228. cur_right = transform_image(cur_right, *cur_mat);
  229. return image_stereo_combine(cur_left, cur_right);
  230. }
  231. auto process(const sp_image &img) {
  232. if (conf.stereo_mode) {
  233. return process_stereo(img);
  234. } else {
  235. return process_mono(img);
  236. }
  237. }
  238. explicit impl(const create_config &_conf) : conf(_conf) {
  239. track_left.emplace(conf.pt_conf);
  240. if (conf.stereo_mode) {
  241. track_right.emplace(conf.pt_conf);
  242. }
  243. transform_stab.emplace(conf.st_conf);
  244. }
  245. };
  246. video_stabilization::video_stabilization(create_config conf)
  247. : pimpl(std::make_unique<impl>(conf)) { (void) 0; }
  248. video_stabilization::~video_stabilization() = default;
  249. std::optional<sp_image> video_stabilization::process(const sp_image &img) const {
  250. return pimpl->process(img);
  251. }
  252. #include "core/imgui_utility.hpp"
  253. struct video_stabilization_ui::impl {
  254. create_config conf;
  255. obj_conn_type conn;
  256. bool passthrough = false;
  257. std::optional<BS::thread_pool> work_tp;
  258. std::optional<video_stabilization> video_stab;
  259. void image_callback_impl() const {
  260. if (!conf.opts.stereo_mode) {
  261. const auto img = OBJ_QUERY(sp_image, conf.in_name);
  262. if (const auto ret = video_stab->process(img)) {
  263. OBJ_SAVE(conf.out_name, *ret);
  264. }
  265. } else {
  266. auto img_left = OBJ_QUERY(sp_image, conf.in_name);
  267. auto img_right = OBJ_QUERY(sp_image, conf.in2_name);
  268. auto img = image_stereo_combine(img_left, img_right);
  269. if (auto ret = video_stab->process(img)) {
  270. auto [img_left, img_right] = image_stereo_split_view(*ret);
  271. OBJ_SAVE(conf.out_name, img_left);
  272. OBJ_SAVE(conf.out2_name, img_right);
  273. }
  274. }
  275. }
  276. void image_callback() {
  277. if (passthrough) {
  278. try {
  279. OBJ_SAVE(conf.out_name, OBJ_QUERY(sp_image, conf.in_name));
  280. if (conf.opts.stereo_mode) {
  281. OBJ_SAVE(conf.out2_name, OBJ_QUERY(sp_image, conf.in2_name));
  282. }
  283. } catch (...) { (void) 0; }
  284. return;
  285. }
  286. if (!video_stab) [[unlikely]] {
  287. video_stab.emplace(conf.opts);
  288. }
  289. auto task = [this] {
  290. try {
  291. image_callback_impl();
  292. } catch (...) { (void) 0; }
  293. };
  294. if (work_tp->get_tasks_queued() <= 1) {
  295. work_tp->detach_task(task);
  296. } else {
  297. SPDLOG_WARN("Too many frames for stabilization, frame dropped.");
  298. }
  299. }
  300. void reset_video_stab() {
  301. MAIN_DETACH([this] {
  302. work_tp->wait();
  303. video_stab.reset();
  304. });
  305. }
  306. void show_ui() {
  307. if (ImGui::Checkbox("Passthrough", &passthrough)) {
  308. if (!passthrough) {
  309. reset_video_stab();
  310. }
  311. }
  312. if (!passthrough) {
  313. ImGui::SameLine();
  314. if (ImGui::Button("Reset")) {
  315. reset_video_stab();
  316. }
  317. ImGui::DragInt("Frame Delay",
  318. &conf.opts.st_conf.future_frames, 1, 1);
  319. }
  320. }
  321. explicit impl(const create_config &_conf) : conf(_conf) {
  322. work_tp.emplace(1);
  323. conn = OBJ_SIG(conf.in_name)->connect(
  324. [this](auto _) { image_callback(); });
  325. }
  326. ~impl() {
  327. conn.disconnect();
  328. }
  329. };
  330. video_stabilization_ui::video_stabilization_ui(create_config conf)
  331. : pimpl(std::make_unique<impl>(conf)) { (void) 0; }
  332. video_stabilization_ui::~video_stabilization_ui() = default;
  333. void video_stabilization_ui::show_ui() const {
  334. pimpl->show_ui();
  335. }