Selaa lähdekoodia

Broken Video Stabilization.

jcsyshc 1 vuosi sitten
vanhempi
commit
570346f59a

+ 17 - 11
CMakeLists.txt

@@ -10,6 +10,7 @@ add_executable(${PROJECT_NAME} src/main.cpp
         src/image_process/impl/image_process_ui.cpp
         src/image_process/impl/process_funcs.cpp
         src/image_process/impl/versatile_convertor.cpp
+        src/image_process_v5/video_stabilization.cpp
         src/impl/main_impl.cpp
         src/impl/apps/app_selector/app_selector.cpp
         src/impl/apps/debug/app_debug.cpp
@@ -128,7 +129,7 @@ endif ()
 if (WIN32)
     set(GLAD_DIR C:/BuildEssentials/Library/glad)
 else ()
-    set(GLAD_DIR /home/tpx/src/glad)
+    set(GLAD_DIR /home/tpx/ext/src/glad)
 endif ()
 target_include_directories(${PROJECT_NAME} PRIVATE ${GLAD_DIR}/include)
 target_sources(${PROJECT_NAME} PRIVATE ${GLAD_DIR}/src/gl.c)
@@ -142,7 +143,7 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE GLM_ENABLE_EXPERIMENTAL)
 if (WIN32)
     set(IMGUI_DIR C:/BuildEssentials/Library/imgui-1.89.5)
 else ()
-    set(IMGUI_DIR /home/tpx/src/imgui-1.90.9)
+    set(IMGUI_DIR /home/tpx/ext/src/imgui-1.91.0)
 endif ()
 set(IMGUI_BACKENDS_DIR ${IMGUI_DIR}/backends)
 target_include_directories(${PROJECT_NAME} PRIVATE ${IMGUI_DIR} ${IMGUI_BACKENDS_DIR})
@@ -160,13 +161,13 @@ foreach(source_file ${IMGUI_IMPL_FILES})
 endforeach()
 
 # ImGuiFileDialog config
-set(ImGuiFileDialog_DIR /home/tpx/src/ImGuiFileDialog-0.6.7)
+set(ImGuiFileDialog_DIR /home/tpx/ext/src/ImGuiFileDialog-0.6.7)
 add_subdirectory(${ImGuiFileDialog_DIR} third_party/imgui_file_dialog)
 target_include_directories(ImGuiFileDialog PRIVATE ${IMGUI_DIR})
 target_link_libraries(${PROJECT_NAME} ImGuiFileDialog)
 
 # imGuIZMO config
-set(IMGUIZMO_DIR /home/tpx/src/imGuIZMO.quat-3.0/imGuIZMO.quat)
+set(IMGUIZMO_DIR /home/tpx/ext/src/imGuIZMO.quat-3.0/imGuIZMO.quat)
 target_include_directories(${PROJECT_NAME} PRIVATE ${IMGUIZMO_DIR})
 target_sources(${PROJECT_NAME} PRIVATE
         ${IMGUIZMO_DIR}/imGuIZMOquat.cpp)
@@ -174,7 +175,7 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE
         IMGUIZMO_IMGUI_FOLDER=${IMGUI_DIR}/)
 
 # NanoVG config
-set(NANOVG_DIR /home/tpx/src/nanovg)
+set(NANOVG_DIR /home/tpx/ext/src/nanovg-f93799c)
 target_include_directories(${PROJECT_NAME} PRIVATE ${NANOVG_DIR}/src)
 target_sources(${PROJECT_NAME} PRIVATE ${NANOVG_DIR}/src/nanovg.c)
 
@@ -213,9 +214,12 @@ find_package(Eigen3 REQUIRED)
 target_link_libraries(${PROJECT_NAME} Eigen3::Eigen)
 
 # Orbbec config
-set(OrbbecSDK_DIR /home/tpx/src/OrbbecSDK-1.9.5)
-find_package(OrbbecSDK REQUIRED)
-target_link_libraries(${PROJECT_NAME} OrbbecSDK::OrbbecSDK)
+set(OrbbecSDK_ROOT_DIR /home/tpx/ext/src/OrbbecSDK_v1.10.12/SDK)
+set(OrbbecSDK_LIBRARY_DIRS ${OrbbecSDK_ROOT_DIR}/lib)
+set(OrbbecSDK_INCLUDE_DIR ${OrbbecSDK_ROOT_DIR}/include)
+target_include_directories(${PROJECT_NAME} PRIVATE ${OrbbecSDK_INCLUDE_DIR})
+target_link_directories(${PROJECT_NAME} PRIVATE ${OrbbecSDK_LIBRARY_DIRS})
+target_link_libraries(${PROJECT_NAME} OrbbecSDK)
 target_sources(${PROJECT_NAME} PRIVATE
         src/device/impl/orb_camera.cpp
         src/device/impl/orb_camera_ui.cpp)
@@ -299,15 +303,17 @@ find_package(azmq REQUIRED)
 target_link_libraries(${PROJECT_NAME} Azmq::azmq)
 
 # BS::thread_pool config
-set(BSTP_DIR /home/tpx/src/thread-pool-4.1.0)
+set(BSTP_DIR /home/tpx/ext/src/thread-pool-4.1.0)
 target_include_directories(${PROJECT_NAME} PRIVATE ${BSTP_DIR}/include)
 
 # Sophiar2 config
 if (WIN32)
     set(Sophiar2DIR D:/Program/Robot/Sophiar2)
 else ()
-    set(Sophiar2DIR /home/tpx/project/Sophiar2)
+    set(Sophiar2DIR /home/tpx/ext/project/Sophiar2)
 endif ()
 add_subdirectory(${Sophiar2DIR}/src Sophiar2Lib)
 target_include_directories(${PROJECT_NAME} PRIVATE ${Sophiar2DIR}/src)
-target_link_libraries(${PROJECT_NAME} Sophiar2Lib)
+target_link_libraries(${PROJECT_NAME} Sophiar2Lib)
+add_compile_definitions(EIGEN_DONT_VECTORIZE)
+add_compile_definitions(EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT)

+ 17 - 17
data/config_endo_guide_oldhead.yaml

@@ -1,6 +1,6 @@
 app_name: endo_guide
 
-sophiar_config: /home/tpx/project/DepthGuide/data/sophiar_config_endo_guide_oldhead.json
+sophiar_config: ./sophiar_config_endo_guide_oldhead.json
 sophiar_start_var: tracker_all
 
 camera_ref_transform_var: camera_ref_in_tracker
@@ -21,19 +21,19 @@ monitor:
 augment_list:
   - name: Head
     transform_var: head_in_tracker_denoised
-    model: /home/tpx/data/OldHead/OldBone.stl
+    model: /home/tpx/ext/data/models/old_head/bone.stl
   - name: ExtraA
     transform_var: head_in_tracker_denoised
-    model: /home/tpx/data/OldHead/Nerve.stl
+    model: /home/tpx/ext/data/models/old_head/nerve.stl
 #  - name: ExtraB
 #    transform_var: head_in_tracker_denoised
 #    model: /home/tpx/data/OldHead/model2.stl
 
-probe_model: /home/tpx/data/Probe.stl
+probe_model: /home/tpx/data/models/Probe.stl
 
 registration_list:
   - name: Head
-    model_file: /home/tpx/data/OldHead/OldBone.stl
+    model_file: /home/tpx/ext/data/models/old_head/bone.stl
     collect_obj: point_picker_in_head_ref
     collect_var: picked_point_in_head_ref
     target_var: head_in_head_ref
@@ -43,22 +43,22 @@ left_camera_name: "LeftEye"
 right_camera_name: "RightEye"
 
 left_camera_info:
-  fx: 3566.07514575013
-  fy: 3565.09801365950
-  cx: 1230.14290684677
-  cy: 1026.56369172491
-  k0: -0.0668340443448111
-  k1: 0.0831050102080411
+  fx: 3569.14196423103
+  fy: 3568.02459571424
+  cx: 1229.37096752839
+  cy: 1026.81619824569
+  k0: -0.0681166229505913
+  k1: 0.0751335815980414
   width: 2448
   height: 2048
 
 right_camera_info:
-  fx: 3579.11217391698
-  fy: 3578.22682676712
-  cx: 1219.97484179738
-  cy: 1036.82186898493
-  k0: -0.0802083588903196
-  k1: 0.158880530651155
+  fx: 3581.91081422450
+  fy: 3580.97918934816
+  cx: 1218.86459178897
+  cy: 1034.61633754506
+  k0: -0.0753067946100483
+  k1: 0.106748846894580
   width: 2448
   height: 2048
 

+ 28 - 28
data/sophiar_config_endo_guide_oldhead.json

@@ -16,13 +16,13 @@
       "name": "head_in_head_ref",
       "type": "transform_obj",
       "value": [
-        -158.57010140440562,
-        109.08408048026303,
-        311.7159241940251,
-        0.6426166606045021,
-        -0.3019245048766848,
-        -0.18681286301124206,
-        0.6789597742738753
+        233.96470027218044,
+        42.187788316685904,
+        306.58865752584546,
+        0.8190555328954703,
+        -0.26342165437712295,
+        0.24510986086006864,
+        -0.4468536921048027
       ]
     },
     {
@@ -116,9 +116,9 @@
             "name": "probe",
             "parent": "probe_ref",
             "transform": [
-              -0.69,
-              -13.95,
-              -186.84,
+              0,
+              0,
+              0,
               1,
               0,
               0,
@@ -154,26 +154,26 @@
             "name": "left_camera",
             "parent": "mvs_ref",
             "transform": [
-              33.2854,
-              -37.7917,
-              -24.4682,
-              0.6318,
-              0.3547,
-              0.4719,
-              -0.5023
+              1.69529294997093,
+              -40.8504461910221,
+              -38.4356249156025,
+              0.328217190586743,
+              0.623382258094322,
+              0.318037852337519,
+              -0.634444607965983
             ]
           },
           {
             "name": "right_camera",
             "parent": "mvs_ref",
             "transform": [
-              37.3992,
-              -56.7108,
-              -85.8847,
-              0.6304,
-              0.3605,
-              0.4738,
-              -0.4982
+              0.0941597771490770,
+              -41.0431819166041,
+              -105.682270133671,
+              0.329203554783163,
+              0.626106195513828,
+              0.316605284270741,
+              -0.631962930423275
             ]
           }
         ]
@@ -222,13 +222,13 @@
       "type": "ndi_interface",
       "name": "ndi",
       "init_config": {
-        "address_type": "serial",
-        "ip": "10.0.0.5",
+        "address_type": "ethernet",
+        "ip": "192.168.1.202",
         "tcp_port": 8765,
         "com_port": "/dev/ttyUSB0",
         "tool_list": [
           {
-            "rom_path": "/home/tpx/data/roms/GlassProbe_4Ball_4_SHC.rom",
+            "rom_path": "/home/tpx/data/roms/Probe_Small_4Ball.rom",
             "serial_number": "3DD50000",
             "outputs": {
               "transform": "probe_ref_in_tracker"
@@ -242,7 +242,7 @@
             }
           },
           {
-            "rom_path": "/home/tpx/data/roms/Glass_4Ball_1_Camera_20240312.rom",
+            "rom_path": "/home/tpx/data/roms/NR_1_STCAM_20241115.rom",
             "outputs": {
               "transform": "mvs_ref_in_tracker"
             }

+ 367 - 0
src/image_process_v5/video_stabilization.cpp

@@ -0,0 +1,367 @@
+#include "video_stabilization.h"
+#include "third_party/scope_guard.hpp"
+
+#include <opencv2/calib3d.hpp>
+#include <opencv2/cudaoptflow.hpp>
+#include <opencv2/cudaimgproc.hpp>
+
+#include <Eigen/Dense>
+
+#include <cmath>
+#include <deque>
+#include <ranges>
+#include <vector>
+
+namespace {
+    template<typename T>
+    std::vector<T> download(const cv::cuda::GpuMat &d_mat, int type) {
+        auto ret = std::vector<T>(d_mat.cols);
+        auto mat = cv::Mat(1, d_mat.cols, type, ret.data());
+        d_mat.download(mat);
+        return ret;
+    }
+
+    struct point_tracker {
+        cv::Ptr<cv::cuda::CornersDetector> detector;
+        cv::Ptr<cv::cuda::SparsePyrLKOpticalFlow> tracker;
+        image_ptr prev;
+
+        using points_type = std::vector<cv::Point2f>;
+
+        struct result_type {
+            points_type prev_pts;
+            points_type curr_pts;
+        };
+
+        using create_config = video_stabilization::point_tracker_config;
+        create_config conf;
+
+        explicit point_tracker(const create_config _conf)
+            : conf(_conf) {
+            detector = cv::cuda::createGoodFeaturesToTrackDetector(
+                CV_8UC1, conf.max_feature_points,
+                0.3, 7, 7);
+            tracker = cv::cuda::SparsePyrLKOpticalFlow::create();
+        }
+
+        // Input RGB image
+        std::optional<result_type> process(const image_ptr &img) {
+            auto closer = sg::make_scope_guard([&] { prev = img; });
+            if (prev == nullptr) return {};
+
+            cv::cuda::GpuMat d_prev_pts;
+            if (true) {
+                const auto prev_gray = image_rgb_to_gray(prev);
+                const auto helper = read_access_helper(prev_gray.cuda());
+                detector->detect(prev_gray.cv_gpu_mat(helper.ptr()), d_prev_pts,
+                                 cv::noArray(), stream_guard.cv_stream());
+            }
+
+            cv::cuda::GpuMat d_curr_pts, d_status;
+            if (true) {
+                const auto prev_helper = read_access_helper(prev.cuda());
+                const auto curr_helper = read_access_helper(img.cuda());
+                tracker->calc(prev.cv_gpu_mat(prev_helper.ptr()),
+                              img.cv_gpu_mat(curr_helper.ptr()),
+                              d_prev_pts, d_curr_pts, d_status);
+            }
+
+            auto status = download<uchar>(d_status, CV_8UC1);
+            auto valid_indices = std::views::iota(0ull, status.size())
+                                 | std::views::filter([&](auto i) { return status[i]; });
+            auto prev_pts = download<cv::Point2f>(d_prev_pts, CV_32FC2);
+            auto curr_pts = download<cv::Point2f>(d_curr_pts, CV_32FC2);
+            auto valid_prev_pts = valid_indices
+                                  | std::views::transform([&](auto i) { return prev_pts[i]; });
+            auto valid_curr_pts = valid_indices
+                                  | std::views::transform([&](auto i) { return curr_pts[i]; });
+            auto ret = result_type();
+            std::ranges::copy(valid_prev_pts, std::back_inserter(ret.prev_pts));
+            std::ranges::copy(valid_curr_pts, std::back_inserter(ret.curr_pts));
+            return ret;
+        }
+    };
+
+    struct transform_stabilization {
+        using matrix_type = Eigen::Matrix<float, 2, 3>;
+        using matrix_param_type = Eigen::Vector4f;
+        using record_type = Eigen::VectorXf;
+        using trajectory_type = Eigen::Matrix4Xf;
+
+        static record_type optimize_simple(const record_type &traj) {
+            const auto num = traj.rows();
+            auto ret = record_type(num);
+            ret.setConstant(traj.mean());
+            return ret;
+        }
+
+        static trajectory_type optimize_trajectory(const trajectory_type &traj) {
+            const auto num_dims = traj.rows();
+            auto ret = trajectory_type(num_dims, traj.cols());
+            for (auto i = 0; i < num_dims; ++i) {
+                ret.row(i) = optimize_simple(traj.row(i));
+            }
+            return ret;
+        }
+
+        static matrix_param_type decompose_transform(const matrix_type &mat) {
+            const auto scale = sqrtf(mat.block<2, 2>(0, 0).determinant());
+            const auto angle = atan2f(mat(1, 0), mat(0, 0));
+            const auto tx = mat(0, 2);
+            const auto ty = mat(1, 2);
+            return {logf(scale), angle, tx, ty};
+        }
+
+        static matrix_type compose_transform(const matrix_param_type &param) {
+            const auto scale = expf(param(0)), angle = param(1);
+            const auto tx = param(2), ty = param(3);
+            auto ret = matrix_type();
+            ret << cosf(angle), -sinf(angle), tx,
+                    sinf(angle), cosf(angle), ty;
+            ret.block<2, 2>(0, 0) *= scale;
+            return ret;
+        }
+
+        using create_config = video_stabilization::transform_stabilization_config;
+        create_config conf;
+
+        size_t buffer_length = 0;
+        std::deque<matrix_param_type> transform_buf;
+
+        explicit transform_stabilization(const create_config &_conf)
+            : conf(_conf) {
+            buffer_length = 2 * conf.future_frames + 1;
+        }
+
+        std::optional<matrix_type> process(const matrix_type &mat) {
+            const auto param = decompose_transform(mat);
+            transform_buf.push_back(param);
+            if (transform_buf.size() <= conf.future_frames) return {};
+            if (transform_buf.size() > buffer_length) {
+                transform_buf.pop_front();
+            }
+
+            auto curr_traj = trajectory_type(4, transform_buf.size());
+            for (int i = 0; i < transform_buf.size(); i++) {
+                curr_traj.col(i) = transform_buf[i];
+                if (i > 0) {
+                    curr_traj.col(i) += curr_traj.col(i - 1);
+                }
+            }
+
+            const auto traj_opt = optimize_trajectory(curr_traj);
+            const auto curr_pos = conf.future_frames;
+            const auto diff = traj_opt.col(curr_pos) - curr_traj.col(curr_pos);
+            const auto curr = transform_buf[curr_pos] + diff;
+            return compose_transform(curr);
+        }
+    };
+}
+
+struct video_stabilization::impl {
+    using matrix_type = transform_stabilization::matrix_type;
+
+    static matrix_type combine_transform(const matrix_type &mat1, const matrix_type &mat2) {
+        auto ret = matrix_type();
+        const auto r1_mat = mat1.leftCols<2>();
+        ret.leftCols<2>() = r1_mat * mat2.leftCols<2>();
+        ret.rightCols<1>() = r1_mat * mat2.rightCols<1>() + mat1.rightCols<1>();
+        return ret;
+    }
+
+    static matrix_type identity_transform() {
+        auto ret = matrix_type();
+        ret.setZero();
+        ret.leftCols<2>().setIdentity();
+        return ret;
+    }
+
+    static glm::mat3 to_mat3(const matrix_type &mat) {
+        auto ret = glm::mat3();
+        for (auto i = 0; i < 2; ++i)
+            for (auto j = 0; j < 3; ++j) {
+                ret[j][i] = mat(i, j);
+            }
+        return ret;
+    }
+
+    static matrix_type to_transform(const cv::Mat &mat) {
+        assert(mat.rows == 2 && mat.cols == 3);
+        assert(mat.type() == CV_64FC1);
+        auto ret = matrix_type();
+        for (auto i = 0; i < 2; ++i)
+            for (auto j = 0; j < 3; ++j) {
+                ret(i, j) = mat.at<double>(i, j);
+            }
+        return ret;
+    }
+
+    static constexpr auto min_tracked_points = 10;
+
+    create_config conf;
+    std::optional<point_tracker> track_left, track_right;
+    std::optional<transform_stabilization> transform_stab;
+    std::deque<sp_image> img_buf;
+
+    [[nodiscard]] sp_image transform_image(const sp_image &img, matrix_type mat) const {
+        const auto center = cv::Point2f(img.width() / 2.f, img.height() / 2.f);
+        const auto fix_mat = cv::getRotationMatrix2D(center, 0, 1.f + conf.enlarge_factor);
+        mat = combine_transform(to_transform(fix_mat), mat);
+        return image_warp_affine(img, to_mat3(mat));
+    }
+
+    std::optional<sp_image> process_mono(const sp_image &img) {
+        assert(!conf.stereo_mode);
+        const auto pts = track_left->process(img);
+        if (!pts) return {};
+        matrix_type mat;
+        if (pts->prev_pts.size() < min_tracked_points) [[unlikely]] {
+            mat = identity_transform();
+        } else {
+            const auto cv_mat = cv::estimateAffinePartial2D(
+                pts->prev_pts, pts->curr_pts);
+            mat = to_transform(cv_mat);
+        }
+
+        const auto cur_mat = transform_stab->process(mat);
+        img_buf.push_back(img);
+        if (!cur_mat) return {};
+
+        const auto cur_img = img_buf.front();
+        img_buf.pop_front();
+        return transform_image(cur_img, *cur_mat);
+    }
+
+    std::optional<sp_image> process_stereo(const sp_image &img) {
+        assert(conf.stereo_mode);
+        auto [img_left, img_right] = image_stereo_split_view(img);
+        auto pts_left = track_left->process(img_left);
+        auto pts_right = track_right->process(img_right);
+        if (!pts_left || !pts_right) return {};
+
+        auto num_points = pts_left->prev_pts.size() + pts_right->prev_pts.size();
+        matrix_type mat;
+        if (num_points < min_tracked_points) [[unlikely]] {
+            mat = identity_transform();
+        } else {
+            auto prev_pts = std::move(pts_left->prev_pts);
+            prev_pts.reserve(num_points);
+            std::ranges::copy(pts_right->prev_pts, std::back_inserter(prev_pts));
+            auto curr_pts = std::move(pts_left->curr_pts);
+            curr_pts.reserve(num_points);
+            std::ranges::copy(pts_right->curr_pts, std::back_inserter(curr_pts));
+            const auto cv_mat = cv::estimateAffinePartial2D(prev_pts, curr_pts);
+            mat = to_transform(cv_mat);
+        }
+
+        const auto cur_mat = transform_stab->process(mat);
+        img_buf.push_back(img);
+        if (!cur_mat) return {};
+
+        const auto cur_img = img_buf.front();
+        img_buf.pop_front();
+        auto [cur_left, cur_right] = image_stereo_split_view(cur_img);
+        cur_left = transform_image(cur_left, *cur_mat);
+        cur_right = transform_image(cur_right, *cur_mat);
+        return image_stereo_combine(cur_left, cur_right);
+    }
+
+    auto process(const sp_image &img) {
+        if (conf.stereo_mode) {
+            return process_stereo(img);
+        } else {
+            return process_mono(img);
+        }
+    }
+
+    explicit impl(const create_config &_conf) : conf(_conf) {
+        track_left.emplace(conf.pt_conf);
+        if (conf.stereo_mode) {
+            track_right.emplace(conf.pt_conf);
+        }
+        transform_stab.emplace(conf.st_conf);
+    }
+};
+
+video_stabilization::video_stabilization(create_config conf)
+    : pimpl(std::make_unique<impl>(conf)) { (void) 0; }
+
+video_stabilization::~video_stabilization() = default;
+
+std::optional<sp_image> video_stabilization::process(const sp_image &img) const {
+    return pimpl->process(img);
+}
+
+#include "core/imgui_utility.hpp"
+
+struct video_stabilization_ui::impl {
+    create_config conf;
+    obj_conn_type conn;
+
+    bool passthrough = false;
+    std::optional<BS::thread_pool> work_tp;
+    std::optional<video_stabilization> video_stab;
+
+    void image_callback_impl() const {
+        const auto img = OBJ_QUERY(sp_image, conf.in_name);
+        if (const auto ret = video_stab->process(img)) {
+            OBJ_SAVE(conf.out_name, *ret);
+        }
+    }
+
+    void image_callback() {
+        if (passthrough) {
+            OBJ_SAVE(conf.out_name, OBJ_QUERY(sp_image, conf.in_name));
+            return;
+        }
+
+        if (!video_stab) [[unlikely]] {
+            video_stab.emplace(conf.opts);
+        }
+        auto task = [this] {
+            try {
+                image_callback_impl();
+            } catch (...) { (void) 0; }
+        };
+        if (work_tp->get_tasks_queued() <= 1) {
+            work_tp->detach_task(task);
+        } else {
+            SPDLOG_WARN("Too many frames for stabilization, frame dropped.");
+        }
+    }
+
+    void show_ui() {
+        if (ImGui::Checkbox("Passthrough", &passthrough)) {
+            if (!passthrough) {
+                video_stab.reset();
+            }
+        }
+        if (!passthrough) {
+            ImGui::SameLine();
+            if (ImGui::Button("Reset")) {
+                video_stab.reset();
+            }
+            ImGui::DragInt("Frame Delay",
+                           &conf.opts.st_conf.future_frames, 1, 1);
+        }
+    }
+
+    explicit impl(const create_config &_conf) : conf(_conf) {
+        work_tp.emplace(1);
+        conn = OBJ_SIG(conf.in_name)->connect(
+            [this](auto _) { image_callback(); });
+    }
+
+    ~impl() {
+        conn.disconnect();
+    }
+};
+
+video_stabilization_ui::video_stabilization_ui(create_config conf)
+    : pimpl(std::make_unique<impl>(conf)) { (void) 0; }
+
+video_stabilization_ui::~video_stabilization_ui() = default;
+
+void video_stabilization_ui::show_ui() const {
+    pimpl->show_ui();
+}

+ 52 - 0
src/image_process_v5/video_stabilization.h

@@ -0,0 +1,52 @@
+#ifndef VIDEO_STABILIZATION_H
+#define VIDEO_STABILIZATION_H
+
+#include "core/image_utility_v2.h"
+
+class video_stabilization {
+public:
+    struct point_tracker_config {
+        size_t max_feature_points = 200;
+    };
+
+    struct transform_stabilization_config {
+        int future_frames = 50;
+    };
+
+    struct create_config {
+        bool stereo_mode = false;
+        float enlarge_factor = 0.1f;
+        point_tracker_config pt_conf = {};
+        transform_stabilization_config st_conf = {};
+    };
+
+    explicit video_stabilization(create_config conf);
+
+    ~video_stabilization();
+
+    [[nodiscard]] std::optional<image_ptr> process(const image_ptr &img) const;
+
+private:
+    struct impl;
+    std::unique_ptr<impl> pimpl;
+};
+
+class video_stabilization_ui {
+public:
+    struct create_config {
+        obj_name_type in_name = 0, out_name = 0;
+        video_stabilization::create_config opts = {};
+    };
+
+    explicit video_stabilization_ui(create_config conf);
+
+    ~video_stabilization_ui();
+
+    void show_ui() const;
+
+private:
+    struct impl;
+    std::unique_ptr<impl> pimpl;
+};
+
+#endif //VIDEO_STABILIZATION_H

+ 1 - 1
src/impl/apps/app_selector/app_selector.cpp

@@ -22,7 +22,7 @@ app_selector::app_selector(const create_config &_conf) {
     dialog_conf.flags |= ImGuiFileDialogFlags_HideColumnType;
     dialog_conf.flags |= ImGuiFileDialogFlags_ReadOnlyFileNameField;
     dialog_conf.flags |= ImGuiFileDialogFlags_CaseInsensitiveExtention;
-    dialog_conf.path = "/home/tpx/project/DepthGuide/data"; // TODO: remember last value
+    dialog_conf.path = "/home/tpx/ext/project/DepthGuide/data"; // TODO: remember last value
     dialog->OpenDialog(dialog_name, "Choose YAML file",
                        "YAML files{.yaml,.yml}", dialog_conf);
 }

+ 1 - 1
src/render/impl/render_utility.cpp

@@ -9,7 +9,7 @@
 using boost::iostreams::mapped_file;
 
 std::filesystem::path shader_folder
-        = "/home/tpx/project/DepthGuide/src/render/impl/shader"; // TODO: config shader path
+        = "/home/tpx/ext/project/DepthGuide/src/render/impl/shader"; // TODO: config shader path
 
 cv::Size query_viewport_size() {
     struct {