From 5023b54ba76325bb0b5598d59714bdad2d55d81e Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Mon, 18 Mar 2019 15:20:09 +0100 Subject: video: add support for camera modules Issue: #910 --- api/plugin-support.hpp | 36 ++++--- compat/camera-names.cpp | 102 ------------------- compat/camera-names.hpp | 18 ---- cv/video-property-page.cpp | 165 ------------------------------ cv/video-property-page.hpp | 13 --- tracker-aruco/ftnoir_tracker_aruco.cpp | 43 +++++--- tracker-aruco/ftnoir_tracker_aruco.h | 4 +- tracker-pt/CMakeLists.txt | 2 +- tracker-pt/ftnoir_tracker_pt.cpp | 3 +- tracker-pt/ftnoir_tracker_pt_dialog.cpp | 13 +-- tracker-pt/module/camera.cpp | 101 +++++++------------ tracker-pt/module/camera.h | 17 +--- tracker-pt/module/point_extractor.cpp | 2 - tracker-pt/pt-api.hpp | 5 +- tracker-wii/wii_camera.cpp | 7 +- tracker-wii/wii_camera.h | 3 +- tracker-wii/wii_point_extractor.cpp | 2 - variant/default/_variant.cmake | 1 + variant/default/main-window.cpp | 6 +- video-opencv/CMakeLists.txt | 6 ++ video-opencv/camera-impl.cpp | 173 ++++++++++++++++++++++++++++++++ video-opencv/camera-names.cpp | 106 +++++++++++++++++++ video-opencv/camera-names.hpp | 18 ++++ video-opencv/export.hpp | 11 ++ video-opencv/lang/nl_NL.ts | 4 + video-opencv/lang/ru_RU.ts | 4 + video-opencv/lang/stub.ts | 4 + video-opencv/lang/zh_CN.ts | 4 + video-opencv/video-property-page.cpp | 165 ++++++++++++++++++++++++++++++ video-opencv/video-property-page.hpp | 13 +++ video/camera.cpp | 66 ++++++++++++ video/camera.hpp | 95 ++++++++++++++++++ 32 files changed, 783 insertions(+), 429 deletions(-) delete mode 100644 compat/camera-names.cpp delete mode 100644 compat/camera-names.hpp delete mode 100644 cv/video-property-page.cpp delete mode 100644 cv/video-property-page.hpp create mode 100644 video-opencv/CMakeLists.txt create mode 100644 video-opencv/camera-impl.cpp create mode 100644 video-opencv/camera-names.cpp create mode 100644 video-opencv/camera-names.hpp create mode 100644 video-opencv/export.hpp create mode 100644 video-opencv/lang/nl_NL.ts create mode 100644 video-opencv/lang/ru_RU.ts create mode 100644 video-opencv/lang/stub.ts create mode 100644 video-opencv/lang/zh_CN.ts create mode 100644 video-opencv/video-property-page.cpp create mode 100644 video-opencv/video-property-page.hpp create mode 100644 video/camera.cpp create mode 100644 video/camera.hpp diff --git a/api/plugin-support.hpp b/api/plugin-support.hpp index df1344bf..65b8e10d 100644 --- a/api/plugin-support.hpp +++ b/api/plugin-support.hpp @@ -35,10 +35,11 @@ struct dylib final Tracker = 0xcafebeef, Protocol = 0xdeadf00d, Extension = 0xcafebabe, + Video = 0xbadf00d, Invalid = (unsigned)-1, }; - dylib(const QString& filename_, Type t) : + dylib(const QString& filename_, Type t, bool load = true) : full_filename(filename_), module_name(trim_filename(filename_)) { @@ -57,19 +58,25 @@ struct dylib final if (check(!handle.load())) return; - if (check((Dialog = (module_ctor_t) handle.resolve("GetDialog"), !Dialog))) - return; + if (load) + { + if (check((Dialog = (module_ctor_t) handle.resolve("GetDialog"), !Dialog))) + return; - if (check((Constructor = (module_ctor_t) handle.resolve("GetConstructor"), !Constructor))) - return; + if (check((Constructor = (module_ctor_t) handle.resolve("GetConstructor"), !Constructor))) + return; - if (check((Meta = (module_metadata_t) handle.resolve("GetMetadata"), !Meta))) - return; + if (check((Meta = (module_metadata_t) handle.resolve("GetMetadata"), !Meta))) + return; + + std::unique_ptr m{Meta()}; - std::unique_ptr m{Meta()}; + if (check(!m)) + return; - icon = m->icon(); - name = m->name(); + icon = m->icon(); + name = m->name(); + } type = t; #ifdef __clang__ @@ -88,18 +95,20 @@ struct dylib final const struct filter_ { Type type{Invalid}; QString glob; + bool load = true; } filters[] = { { Filter, QStringLiteral(OPENTRACK_LIBRARY_PREFIX "opentrack-filter-*." OPENTRACK_LIBRARY_EXTENSION), }, { Tracker, QStringLiteral(OPENTRACK_LIBRARY_PREFIX "opentrack-tracker-*." OPENTRACK_LIBRARY_EXTENSION), }, { Protocol, QStringLiteral(OPENTRACK_LIBRARY_PREFIX "opentrack-proto-*." OPENTRACK_LIBRARY_EXTENSION), }, { Extension, QStringLiteral(OPENTRACK_LIBRARY_PREFIX "opentrack-ext-*." OPENTRACK_LIBRARY_EXTENSION), }, + { Video, QStringLiteral(OPENTRACK_LIBRARY_PREFIX "opentrack-video-*." OPENTRACK_LIBRARY_EXTENSION), false, }, }; for (const filter_& filter : filters) { for (const QString& filename : module_directory.entryList({ filter.glob }, QDir::Files, QDir::Name)) { - auto lib = std::make_shared(QStringLiteral("%1/%2").arg(library_path, filename), filter.type); + auto lib = std::make_shared(QStringLiteral("%1/%2").arg(library_path, filename), filter.type, filter.load); if (lib->type == Invalid) continue; @@ -159,6 +168,7 @@ private: OPENTRACK_LIBRARY_PREFIX "opentrack-proto-", OPENTRACK_LIBRARY_PREFIX "opentrack-filter-", OPENTRACK_LIBRARY_PREFIX "opentrack-ext-", + OPENTRACK_LIBRARY_PREFIX "opentrack-video-", }; for (auto name : names) @@ -198,7 +208,8 @@ struct Modules final filter_modules(filter(dylib::Filter)), tracker_modules(filter(dylib::Tracker)), protocol_modules(filter(dylib::Protocol)), - extension_modules(filter(dylib::Extension)) + extension_modules(filter(dylib::Extension)), + video_modules(filter(dylib::Video)) {} dylib_list& filters() { return filter_modules; } dylib_list& trackers() { return tracker_modules; } @@ -211,6 +222,7 @@ private: dylib_list tracker_modules; dylib_list protocol_modules; dylib_list extension_modules; + dylib_list video_modules; static dylib_list& sorted(dylib_list& xs) { diff --git a/compat/camera-names.cpp b/compat/camera-names.cpp deleted file mode 100644 index 246d76ee..00000000 --- a/compat/camera-names.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "camera-names.hpp" - -#ifdef _WIN32 -# include -# define NO_DSHOW_STRSAFE -# include -#elif defined(__unix) || defined(__linux) || defined(__APPLE__) -# include -#endif - -#ifdef __linux -# include -# include -# include -# include -# include -#endif - -#include - -int camera_name_to_index(const QString &name) -{ - auto list = get_camera_names(); - int ret = list.indexOf(name); - if (ret < 0) - ret = 0; - return ret; -} - -QList get_camera_names() -{ - QList ret; -#ifdef _WIN32 - // Create the System Device Enumerator. - HRESULT hr; - CoInitialize(nullptr); - ICreateDevEnum *pSysDevEnum = nullptr; - hr = CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum); - if (FAILED(hr)) - { - qDebug() << "failed CLSID_SystemDeviceEnum" << hr; - return ret; - } - // Obtain a class enumerator for the video compressor category. - IEnumMoniker *pEnumCat = nullptr; - hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumCat, 0); - - if (hr == S_OK) { - // Enumerate the monikers. - IMoniker *pMoniker = nullptr; - ULONG cFetched; - while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) - { - IPropertyBag *pPropBag; - hr = pMoniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, (void **)&pPropBag); - if (SUCCEEDED(hr)) { - // To retrieve the filter's friendly name, do the following: - VARIANT var; - VariantInit(&var); - hr = pPropBag->Read(L"FriendlyName", &var, nullptr); - if (SUCCEEDED(hr)) - { - // Display the name in your UI somehow. - QString str((QChar*)var.bstrVal, int(std::wcslen(var.bstrVal))); - ret.append(str); - } - VariantClear(&var); - pPropBag->Release(); - } - pMoniker->Release(); - } - pEnumCat->Release(); - } - else - qDebug() << "failed CLSID_VideoInputDeviceCategory" << hr; - - pSysDevEnum->Release(); -#endif - -#ifdef __linux - for (int i = 0; i < 16; i++) { - char buf[32]; - snprintf(buf, sizeof(buf), "/dev/video%d", i); - - if (access(buf, R_OK | W_OK) == 0) { - int fd = open(buf, O_RDONLY); - if (fd == -1) - continue; - struct v4l2_capability video_cap; - if(ioctl(fd, VIDIOC_QUERYCAP, &video_cap) == -1) - { - qDebug() << "VIDIOC_QUERYCAP" << errno; - close(fd); - continue; - } - ret.append(QString{(const char*)video_cap.card}); - close(fd); - } - } -#endif - return ret; -} diff --git a/compat/camera-names.hpp b/compat/camera-names.hpp deleted file mode 100644 index 97184c8c..00000000 --- a/compat/camera-names.hpp +++ /dev/null @@ -1,18 +0,0 @@ -/* Copyright (c) 2014-2015, Stanislaw Halik - - * Permission to use, copy, modify, and/or distribute this - * software for any purpose with or without fee is hereby granted, - * provided that the above copyright notice and this permission - * notice appear in all copies. - */ - -#pragma once - -#include -#include - -#include "export.hpp" - -OTR_COMPAT_EXPORT QList get_camera_names(); -OTR_COMPAT_EXPORT int camera_name_to_index(const QString &name); - diff --git a/cv/video-property-page.cpp b/cv/video-property-page.cpp deleted file mode 100644 index bae5e8b3..00000000 --- a/cv/video-property-page.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* Copyright (c) 2016 Stanislaw Halik - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ - -#include "video-property-page.hpp" - -#ifdef _WIN32 - -#include "compat/camera-names.hpp" -#include "compat/sleep.hpp" -#include "compat/run-in-thread.hpp" -#include "compat/library-path.hpp" - -#include - -#include - -#include -#include -#include -#include - -#include - -bool video_property_page::show_from_capture(cv::VideoCapture& cap, int /*index */) -{ - return cap.set(cv::CAP_PROP_SETTINGS, 0); -} - -struct prop_settings_worker final : QThread -{ - explicit prop_settings_worker(int idx); - ~prop_settings_worker() override; - -private: - void open_prop_page(); - void run() override; - - cv::VideoCapture cap; - int idx = -1; -}; - -prop_settings_worker::prop_settings_worker(int idx_) -{ - int ret = (int)cap.get(cv::CAP_PROP_SETTINGS); - - if (ret != 0) - run_in_thread_async(qApp, [] { - QMessageBox::warning(nullptr, - "Camera properties", - "Camera dialog already opened", - QMessageBox::Cancel, - QMessageBox::NoButton); - }); - else - { - idx = idx_; - // DON'T MOVE IT - // ps3 eye will reset to default settings if done from another thread - open_prop_page(); - } -} - -void prop_settings_worker::open_prop_page() -{ - cap.open(idx); - - if (cap.isOpened()) - { - cv::Mat tmp; - - for (unsigned k = 0; k < 2000/50; k++) - { - if (cap.read(tmp)) - { - qDebug() << "got frame" << tmp.rows << tmp.cols; - goto ok; - } - portable::sleep(50); - } - } - - qDebug() << "property-page: can't open camera"; - idx = -1; - - return; - -ok: - portable::sleep(100); - - qDebug() << "property-page: opening for" << idx; - - if (!cap.set(cv::CAP_PROP_SETTINGS, 0)) - { - run_in_thread_async(qApp, [] { - QMessageBox::warning(nullptr, - "Camera properties", - "Can't open camera dialog", - QMessageBox::Cancel, - QMessageBox::NoButton); - }); - } -} - -prop_settings_worker::~prop_settings_worker() -{ - if (idx != -1) - { - // ax filter is race condition-prone - portable::sleep(250); - cap.release(); - // idem - portable::sleep(250); - - qDebug() << "property-page: closed" << idx; - } -} - -void prop_settings_worker::run() -{ - if (idx != -1) - { - while (cap.get(cv::CAP_PROP_SETTINGS) > 0) - portable::sleep(1000); - } -} - -bool video_property_page::show(int idx) -{ - auto thread = new prop_settings_worker(idx); - - // XXX is this a race condition? - thread->moveToThread(qApp->thread()); - QObject::connect(thread, &QThread::finished, qApp, [thread] { thread->deleteLater(); }, Qt::DirectConnection); - - thread->start(); - - return true; -} - -#elif defined(__linux) -# include -# include "compat/camera-names.hpp" - -bool video_property_page::show(int idx) -{ - const QList camera_names(get_camera_names()); - - if (idx >= 0 && idx < camera_names.size()) - return QProcess::startDetached("qv4l2", QStringList { "-d", QString("/dev/video%1").arg(idx) }); - else - return false; -} - -bool video_property_page::show_from_capture(cv::VideoCapture&, int idx) -{ - return show(idx); -} -#else -bool video_property_page::show(int) { return false; } -bool video_property_page::show_from_capture(cv::VideoCapture&, int) { return false; } -#endif diff --git a/cv/video-property-page.hpp b/cv/video-property-page.hpp deleted file mode 100644 index c2b9525d..00000000 --- a/cv/video-property-page.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include -#include - -struct video_property_page final -{ - video_property_page() = delete; - static bool show(int id); - static bool show_from_capture(cv::VideoCapture& cap, int index); -private: -}; - diff --git a/tracker-aruco/ftnoir_tracker_aruco.cpp b/tracker-aruco/ftnoir_tracker_aruco.cpp index 8928566f..d9674755 100644 --- a/tracker-aruco/ftnoir_tracker_aruco.cpp +++ b/tracker-aruco/ftnoir_tracker_aruco.cpp @@ -6,8 +6,6 @@ */ #include "ftnoir_tracker_aruco.h" -#include "cv/video-property-page.hpp" -#include "compat/camera-names.hpp" #include "compat/sleep.hpp" #include "compat/math-imports.hpp" @@ -75,7 +73,6 @@ aruco_tracker::~aruco_tracker() wait(); // fast start/stop causes breakage portable::sleep(1000); - camera.release(); } module_status aruco_tracker::start_tracker(QFrame* videoframe) @@ -166,16 +163,18 @@ bool aruco_tracker::open_camera() QMutexLocker l(&camera_mtx); - camera = cv::VideoCapture(camera_name_to_index(s.camera_name)); + camera = video::make_camera(s.camera_name); + video::impl::camera::info args {}; + if (res.width) { - camera.set(cv::CAP_PROP_FRAME_WIDTH, res.width); - camera.set(cv::CAP_PROP_FRAME_HEIGHT, res.height); + args.width = res.width; + args.height = res.height; } if (fps) - camera.set(cv::CAP_PROP_FPS, fps); + args.fps = fps; - if (!camera.isOpened()) + if (!camera->start(args)) { qDebug() << "aruco tracker: can't open camera"; return false; @@ -372,14 +371,28 @@ void aruco_tracker::run() { QMutexLocker l(&camera_mtx); - if (!camera.read(color)) + auto [ img, res ] = camera->get_frame(); + + if (!res) { portable::sleep(100); continue; } - } - cv::cvtColor(color, grayscale, cv::COLOR_BGR2GRAY); + color = cv::Mat(img.height, img.width, CV_8UC(img.channels), (void*)img.data, img.stride); + + switch (img.channels) + { + case 1: + grayscale.setTo(color); break; + case 3: + cv::cvtColor(color, grayscale, cv::COLOR_BGR2GRAY); + break; + default: + qDebug() << "aruco: can't handle" << img.channels << "color channels"; + return; + } + } #ifdef DEBUG_UNSHARP_MASKING { @@ -496,7 +509,9 @@ aruco_dialog::aruco_dialog() : tracker = nullptr; calib_timer.setInterval(100); - ui.cameraName->addItems(get_camera_names()); + + for (const auto& str : video::camera_names()) + ui.cameraName->addItem(str); tie_setting(s.camera_name, ui.cameraName); tie_setting(s.resolution, ui.resolution); @@ -572,10 +587,10 @@ void aruco_dialog::camera_settings() if (tracker) { QMutexLocker l(&tracker->camera_mtx); - video_property_page::show_from_capture(tracker->camera, camera_name_to_index(s.camera_name)); + (void)tracker->camera->show_dialog(); } else - video_property_page::show(camera_name_to_index(s.camera_name)); + (void)video::show_dialog(s.camera_name); } void aruco_dialog::update_camera_settings_state(const QString& name) diff --git a/tracker-aruco/ftnoir_tracker_aruco.h b/tracker-aruco/ftnoir_tracker_aruco.h index 1d6fd107..0a33f02b 100644 --- a/tracker-aruco/ftnoir_tracker_aruco.h +++ b/tracker-aruco/ftnoir_tracker_aruco.h @@ -13,6 +13,7 @@ #include "api/plugin-api.hpp" #include "cv/video-widget.hpp" #include "compat/timer.hpp" +#include "video/camera.hpp" #include "aruco/markerdetector.h" @@ -27,7 +28,6 @@ #include #include -#include // value 0->1 //#define DEBUG_UNSHARP_MASKING .75 @@ -77,7 +77,7 @@ public: void getRT(cv::Matx33d &r, cv::Vec3d &t); QMutex camera_mtx; - cv::VideoCapture camera; + std::unique_ptr camera; private: bool detect_with_roi(); diff --git a/tracker-pt/CMakeLists.txt b/tracker-pt/CMakeLists.txt index f12f530b..304a6b3d 100644 --- a/tracker-pt/CMakeLists.txt +++ b/tracker-pt/CMakeLists.txt @@ -2,7 +2,7 @@ find_package(OpenCV QUIET) if(OpenCV_FOUND) otr_module(tracker-pt-base STATIC) target_include_directories(${self} SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS}) - target_link_libraries(${self} opencv_imgproc opentrack-cv opencv_core) + target_link_libraries(${self} opencv_imgproc opentrack-cv opencv_core opentrack-video) set_property(TARGET ${self} PROPERTY OUTPUT_NAME "pt-base") endif() add_subdirectory(module) diff --git a/tracker-pt/ftnoir_tracker_pt.cpp b/tracker-pt/ftnoir_tracker_pt.cpp index 3854e531..4b796af7 100644 --- a/tracker-pt/ftnoir_tracker_pt.cpp +++ b/tracker-pt/ftnoir_tracker_pt.cpp @@ -8,7 +8,6 @@ #include "ftnoir_tracker_pt.h" #include "video/video-widget.hpp" -#include "compat/camera-names.hpp" #include "compat/math-imports.hpp" #include "pt-api.hpp" @@ -121,7 +120,7 @@ bool Tracker_PT::maybe_reopen_camera() { QMutexLocker l(&camera_mtx); - return camera->start(camera_name_to_index(s.camera_name), + return camera->start(s.camera_name, s.cam_fps, s.cam_res_x, s.cam_res_y); } diff --git a/tracker-pt/ftnoir_tracker_pt_dialog.cpp b/tracker-pt/ftnoir_tracker_pt_dialog.cpp index 2b06c823..edf689a9 100644 --- a/tracker-pt/ftnoir_tracker_pt_dialog.cpp +++ b/tracker-pt/ftnoir_tracker_pt_dialog.cpp @@ -7,10 +7,9 @@ */ #include "ftnoir_tracker_pt_dialog.h" - #include "compat/math.hpp" -#include "compat/camera-names.hpp" -#include "cv/video-property-page.hpp" +#include "video/camera.hpp" + #include #include @@ -33,7 +32,8 @@ TrackerDialog_PT::TrackerDialog_PT(const QString& module_name) : ui.setupUi(this); - ui.camdevice_combo->addItems(get_camera_names()); + for (const QString& str : video::camera_names()) + ui.camdevice_combo->addItem(str); tie_setting(s.camera_name, ui.camdevice_combo); tie_setting(s.cam_res_x, ui.res_x_spin); @@ -231,10 +231,7 @@ void TrackerDialog_PT::show_camera_settings() tracker->camera->show_camera_settings(); } else - { - const int idx = camera_name_to_index(s.camera_name); - video_property_page::show(idx); - } + (void)video::show_dialog(s.camera_name); } void TrackerDialog_PT::trans_calib_step() diff --git a/tracker-pt/module/camera.cpp b/tracker-pt/module/camera.cpp index 1afecc92..687f5bff 100644 --- a/tracker-pt/module/camera.cpp +++ b/tracker-pt/module/camera.cpp @@ -8,15 +8,9 @@ #include "camera.h" #include "frame.hpp" -#include "compat/sleep.hpp" -#include "compat/camera-names.hpp" #include "compat/math-imports.hpp" -#include - -#include "cv/video-property-page.hpp" - -#include +#include namespace pt_module { @@ -26,22 +20,18 @@ Camera::Camera(const QString& module_name) : s { module_name } QString Camera::get_desired_name() const { - return desired_name; + return cam_desired.name; } QString Camera::get_active_name() const { - return active_name; + return cam_info.name; } void Camera::show_camera_settings() { - const int idx = camera_name_to_index(s.camera_name); - - if (cap && cap->isOpened()) - video_property_page::show_from_capture(*cap, idx); - else - video_property_page::show(idx); + if (cap) + (void)cap->show_dialog(); } Camera::result Camera::get_info() const @@ -83,59 +73,53 @@ Camera::result Camera::get_frame(pt_frame& frame_) return { false, {} }; } -bool Camera::start(int idx, int fps, int res_x, int res_y) +bool Camera::start(const QString& name, int fps, int res_x, int res_y) { - if (idx >= 0 && fps >= 0 && res_x >= 0 && res_y >= 0) + if (fps >= 0 && res_x >= 0 && res_y >= 0) { - if (cam_desired.idx != idx || + if (cam_desired.name != name || (int)cam_desired.fps != fps || cam_desired.res_x != res_x || cam_desired.res_y != res_y || - !cap || !cap->isOpened() || !cap->grab()) + !cap || !cap->is_open()) { stop(); - desired_name = get_camera_names().value(idx); - cam_desired.idx = idx; + cam_desired.name = name; cam_desired.fps = fps; cam_desired.res_x = res_x; cam_desired.res_y = res_y; cam_desired.fov = fov; - cap = camera_ptr(new cv::VideoCapture(idx)); + cap = video::make_camera(name); - if (cam_desired.res_x > 0 && cam_desired.res_y > 0) - { - cap->set(cv::CAP_PROP_FRAME_WIDTH, res_x); - cap->set(cv::CAP_PROP_FRAME_HEIGHT, res_y); - } + if (!cap) + goto fail; - if (fps > 0) - cap->set(cv::CAP_PROP_FPS, fps); + camera::info info {}; + info.fps = fps; + info.width = res_x; + info.height = res_y; - if (cap->isOpened()) - { - cam_info = pt_camera_info(); - cam_info.idx = idx; - dt_mean = 0; - active_name = desired_name; + if (!cap->start(info)) + goto fail; - cv::Mat tmp; + cam_info = pt_camera_info(); + cam_info.name = name; + dt_mean = 0; - if (get_frame_(tmp)) - { - t.start(); - return true; - } - } + cv::Mat tmp; - cap = nullptr; - return false; - } + if (!get_frame_(tmp)) + goto fail; - return true; + t.start(); + } } + return true; + +fail: stop(); return false; } @@ -143,34 +127,23 @@ bool Camera::start(int idx, int fps, int res_x, int res_y) void Camera::stop() { cap = nullptr; - desired_name = QString{}; - active_name = QString{}; cam_info = {}; cam_desired = {}; } -bool Camera::get_frame_(cv::Mat& frame) +bool Camera::get_frame_(cv::Mat& img) { - if (cap && cap->isOpened()) + if (cap && cap->is_open()) { - for (unsigned i = 0; i < 10; i++) + auto [ frame, ret ] = cap->get_frame(); + if (ret) { - if (cap->read(frame)) - return true; - portable::sleep(50); + img = cv::Mat(frame.height, frame.width, CV_8UC(frame.channels), (void*)frame.data, frame.stride); + return true; } } - return false; -} -void Camera::camera_deleter::operator()(cv::VideoCapture* cap) -{ - if (cap) - { - if (cap->isOpened()) - cap->release(); - delete cap; - } + return false; } } // ns pt_module diff --git a/tracker-pt/module/camera.h b/tracker-pt/module/camera.h index 2ea633d0..02e2fe4d 100644 --- a/tracker-pt/module/camera.h +++ b/tracker-pt/module/camera.h @@ -9,11 +9,11 @@ #include "pt-api.hpp" #include "compat/timer.hpp" +#include "video/camera.hpp" #include #include -#include #include @@ -23,7 +23,7 @@ struct Camera final : pt_camera { Camera(const QString& module_name); - bool start(int idx, int fps, int res_x, int res_y) override; + bool start(const QString& name, int fps, int res_x, int res_y) override; void stop() override; result get_frame(pt_frame& Frame) override; @@ -37,23 +37,16 @@ struct Camera final : pt_camera void show_camera_settings() override; private: + using camera = typename video::impl::camera; + [[nodiscard]] bool get_frame_(cv::Mat& frame); f dt_mean = 0, fov = 30; Timer t; pt_camera_info cam_info; pt_camera_info cam_desired; - QString desired_name, active_name; - - struct camera_deleter final - { - void operator()(cv::VideoCapture* cap); - }; - - using camera_ptr = std::unique_ptr; - - camera_ptr cap; + std::unique_ptr cap; pt_settings s; static constexpr f dt_eps = f{1}/256; diff --git a/tracker-pt/module/point_extractor.cpp b/tracker-pt/module/point_extractor.cpp index 298d8752..2cb5db97 100644 --- a/tracker-pt/module/point_extractor.cpp +++ b/tracker-pt/module/point_extractor.cpp @@ -13,8 +13,6 @@ #include "cv/numeric.hpp" #include "compat/math.hpp" -#include - #undef PREVIEW //#define PREVIEW diff --git a/tracker-pt/pt-api.hpp b/tracker-pt/pt-api.hpp index b44cfea2..741576a1 100644 --- a/tracker-pt/pt-api.hpp +++ b/tracker-pt/pt-api.hpp @@ -12,6 +12,7 @@ #include #include +#include #ifdef __clang__ # pragma clang diagnostic push @@ -30,7 +31,7 @@ struct pt_camera_info final int res_x = 0; int res_y = 0; - int idx = -1; + QString name; }; struct pt_pixel_pos_mixin @@ -74,7 +75,7 @@ struct pt_camera pt_camera(); virtual ~pt_camera(); - [[nodiscard]] virtual bool start(int idx, int fps, int res_x, int res_y) = 0; + [[nodiscard]] virtual bool start(const QString& name, int fps, int res_x, int res_y) = 0; virtual void stop() = 0; virtual result get_frame(pt_frame& frame) = 0; diff --git a/tracker-wii/wii_camera.cpp b/tracker-wii/wii_camera.cpp index 90ad6385..97a32b9f 100644 --- a/tracker-wii/wii_camera.cpp +++ b/tracker-wii/wii_camera.cpp @@ -16,13 +16,10 @@ #include "wii_frame.hpp" #include "compat/sleep.hpp" -#include "compat/camera-names.hpp" #include "compat/math-imports.hpp" #include -#include "cv/video-property-page.hpp" - #include using namespace pt_module; @@ -33,7 +30,7 @@ WIICamera::WIICamera(const QString& module_name) : s { module_name } cam_info.res_x = 1024; cam_info.res_y = 768; cam_info.fov = 42.0f; - cam_info.idx = 0; + cam_info.name = "Wii"; } WIICamera::~WIICamera() @@ -86,7 +83,7 @@ WIICamera::result WIICamera::get_frame(pt_frame& frame_) return result(true, cam_info); } -bool WIICamera::start(int idx, int fps, int res_x, int res_y) +bool WIICamera::start(const QString& name, int fps, int res_x, int res_y) { m_pDev = std::make_unique(); m_pDev->ChangedCallback = on_state_change; diff --git a/tracker-wii/wii_camera.h b/tracker-wii/wii_camera.h index 05f5436c..7bc74559 100644 --- a/tracker-wii/wii_camera.h +++ b/tracker-wii/wii_camera.h @@ -17,7 +17,6 @@ #include #include -#include #include @@ -31,7 +30,7 @@ struct WIICamera final : pt_camera WIICamera(const QString& module_name); ~WIICamera() override; - bool start(int idx, int fps, int res_x, int res_y) override; + bool start(const QString& name, int fps, int res_x, int res_y) override; void stop() override; result get_frame(pt_frame& Frame) override; diff --git a/tracker-wii/wii_point_extractor.cpp b/tracker-wii/wii_point_extractor.cpp index a23e0e5b..89e4b41b 100644 --- a/tracker-wii/wii_point_extractor.cpp +++ b/tracker-wii/wii_point_extractor.cpp @@ -14,8 +14,6 @@ #include "cv/numeric.hpp" #include "compat/math.hpp" -#include - #undef PREVIEW //#define PREVIEW diff --git a/variant/default/_variant.cmake b/variant/default/_variant.cmake index 161fefda..7501b0a4 100644 --- a/variant/default/_variant.cmake +++ b/variant/default/_variant.cmake @@ -24,6 +24,7 @@ function(otr_init_variant) "migration" "main-window" "video" + "video-*" ) set_property(GLOBAL PROPERTY opentrack-subprojects "${subprojects}") diff --git a/variant/default/main-window.cpp b/variant/default/main-window.cpp index a223c32f..334695cb 100644 --- a/variant/default/main-window.cpp +++ b/variant/default/main-window.cpp @@ -448,7 +448,7 @@ void main_window::stop_tracker_() with_tracker_teardown sentinel; pose_update_timer.stop(); - ui.pose_display->rotate_sync(0,0,0, 0,0,0); + ui.pose_display->present(0,0,0, 0,0,0); if (pTrackerDialog) pTrackerDialog->unregister_tracker(); @@ -473,8 +473,8 @@ void main_window::stop_tracker_() void main_window::show_pose_(const double* mapped, const double* raw) { - ui.pose_display->rotate_async(mapped[Yaw], mapped[Pitch], -mapped[Roll], - mapped[TX], mapped[TY], mapped[TZ]); + ui.pose_display->present(mapped[Yaw], mapped[Pitch], -mapped[Roll], + mapped[TX], mapped[TY], mapped[TZ]); QLCDNumber* raw_[] = { ui.raw_x, ui.raw_y, ui.raw_z, diff --git a/video-opencv/CMakeLists.txt b/video-opencv/CMakeLists.txt new file mode 100644 index 00000000..d8b9b896 --- /dev/null +++ b/video-opencv/CMakeLists.txt @@ -0,0 +1,6 @@ +find_package(OpenCV QUIET) + +if(OpenCV_FOUND) + otr_module(video-opencv) + target_link_libraries(${self} opencv_core opencv_videoio opentrack-video) +endif() diff --git a/video-opencv/camera-impl.cpp b/video-opencv/camera-impl.cpp new file mode 100644 index 00000000..ca18fd4b --- /dev/null +++ b/video-opencv/camera-impl.cpp @@ -0,0 +1,173 @@ +/* Copyright (c) 2019 Stanislaw Halik + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#include "compat/sleep.hpp" +#include "video/camera.hpp" + +#include "camera-names.hpp" +#include "video-property-page.hpp" + +#include + +#include +#include + +using namespace video::impl; + +struct cam; + +struct metadata : camera_ +{ + metadata(); + std::vector camera_names() const override; + std::unique_ptr make_camera(const QString& name) override; + bool can_show_dialog(const QString& camera_name) override; + bool show_dialog(const QString& camera_name) override; +}; + +struct cam final : camera +{ + cam(int idx); + ~cam() override; + + bool start(const info& args) override; + void stop() override; + bool is_open() override; + std::tuple get_frame() override; + bool show_dialog() override; + + bool get_frame_(); + + std::optional cap; + cv::Mat mat; + frame frame_; + int idx = -1; +}; + +metadata::metadata() = default; + +std::unique_ptr metadata::make_camera(const QString& name) +{ + int idx = camera_name_to_index(name); + if (idx != -1) + return std::make_unique(idx); + else + return nullptr; +} + +std::vector metadata::camera_names() const +{ + return get_camera_names(); +} + +bool metadata::can_show_dialog(const QString& camera_name) +{ + return camera_name_to_index(camera_name) != -1; +} + +bool metadata::show_dialog(const QString& camera_name) +{ + int idx = camera_name_to_index(camera_name); + if (idx != -1) + { + video_property_page::show(idx); + return true; + } + else + return false; +} + +cam::cam(int idx) : idx(idx) +{ +} + +cam::~cam() +{ + stop(); +} + +void cam::stop() +{ + if (cap) + { + if (cap->isOpened()) + cap->release(); + cap = std::nullopt; + } + mat = cv::Mat(); + frame_ = { {}, false }; +} + +bool cam::is_open() +{ + return !!cap; +} + +bool cam::start(const info& args) +{ + stop(); + cap.emplace(idx); + + if (args.width > 0 && args.height > 0) + { + cap->set(cv::CAP_PROP_FRAME_WIDTH, args.width); + cap->set(cv::CAP_PROP_FRAME_HEIGHT, args.height); + } + if (args.fps > 0) + cap->set(cv::CAP_PROP_FPS, args.fps); + + if (!cap->isOpened()) + goto fail; + + if (!get_frame_()) + goto fail; + + return true; + +fail: + stop(); + return false; +} + +bool cam::get_frame_() +{ + if (!is_open()) + return false; + + for (unsigned i = 0; i < 10; i++) + { + if (cap->read(mat)) + { + frame_.data = mat.data; + frame_.width = mat.cols; + frame_.height = mat.rows; + frame_.stride = mat.step.p[0]; + frame_.channels = mat.channels(); + + return true; + } + portable::sleep(50); + } + + return false; +} + +std::tuple cam::get_frame() +{ + bool ret = get_frame_(); + return { frame_, ret }; +} + +bool cam::show_dialog() +{ + if (is_open()) + return video_property_page::show_from_capture(*cap, idx); + else + return video_property_page::show(idx); +} + +OTR_REGISTER_CAMERA(metadata) diff --git a/video-opencv/camera-names.cpp b/video-opencv/camera-names.cpp new file mode 100644 index 00000000..69926e5a --- /dev/null +++ b/video-opencv/camera-names.cpp @@ -0,0 +1,106 @@ +#include "camera-names.hpp" + +#include +#include + +#ifdef _WIN32 +# include +# define NO_DSHOW_STRSAFE +# include +#elif defined(__unix) || defined(__linux) || defined(__APPLE__) +# include +#endif + +#ifdef __linux +# include +# include +# include +# include +# include +#endif + +#include + +int camera_name_to_index(const QString &name) +{ + auto list = get_camera_names(); + auto it = std::find(list.cbegin(), list.cend(), name); + if (it != list.cend()) + return std::distance(list.cbegin(), it); + + return -1; +} + +std::vector get_camera_names() +{ + std::vector ret; +#ifdef _WIN32 + // Create the System Device Enumerator. + HRESULT hr; + CoInitialize(nullptr); + ICreateDevEnum *pSysDevEnum = nullptr; + hr = CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum); + if (FAILED(hr)) + { + qDebug() << "failed CLSID_SystemDeviceEnum" << hr; + return ret; + } + // Obtain a class enumerator for the video compressor category. + IEnumMoniker *pEnumCat = nullptr; + hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumCat, 0); + + if (hr == S_OK) { + // Enumerate the monikers. + IMoniker *pMoniker = nullptr; + ULONG cFetched; + while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) + { + IPropertyBag *pPropBag; + hr = pMoniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, (void **)&pPropBag); + if (SUCCEEDED(hr)) { + // To retrieve the filter's friendly name, do the following: + VARIANT var; + VariantInit(&var); + hr = pPropBag->Read(L"FriendlyName", &var, nullptr); + if (SUCCEEDED(hr)) + { + // Display the name in your UI somehow. + QString str((QChar*)var.bstrVal, int(std::wcslen(var.bstrVal))); + ret.push_back(str); + } + VariantClear(&var); + pPropBag->Release(); + } + pMoniker->Release(); + } + pEnumCat->Release(); + } + else + qDebug() << "failed CLSID_VideoInputDeviceCategory" << hr; + + pSysDevEnum->Release(); +#endif + +#ifdef __linux + for (int i = 0; i < 16; i++) { + char buf[32]; + snprintf(buf, sizeof(buf), "/dev/video%d", i); + + if (access(buf, R_OK | W_OK) == 0) { + int fd = open(buf, O_RDONLY); + if (fd == -1) + continue; + struct v4l2_capability video_cap; + if(ioctl(fd, VIDIOC_QUERYCAP, &video_cap) == -1) + { + qDebug() << "VIDIOC_QUERYCAP" << errno; + close(fd); + continue; + } + ret.push_back(QString((const char*)video_cap.card)); + close(fd); + } + } +#endif + return ret; +} diff --git a/video-opencv/camera-names.hpp b/video-opencv/camera-names.hpp new file mode 100644 index 00000000..9f0883f5 --- /dev/null +++ b/video-opencv/camera-names.hpp @@ -0,0 +1,18 @@ +/* Copyright (c) 2014-2015, Stanislaw Halik + + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted, + * provided that the above copyright notice and this permission + * notice appear in all copies. + */ + +#pragma once + +#include +#include + +#include "export.hpp" + +std::vector get_camera_names(); +int camera_name_to_index(const QString &name); + diff --git a/video-opencv/export.hpp b/video-opencv/export.hpp new file mode 100644 index 00000000..1d43a9f1 --- /dev/null +++ b/video-opencv/export.hpp @@ -0,0 +1,11 @@ +// generates export.hpp for each module from compat/linkage.hpp + +#pragma once + +#include "compat/linkage-macros.hpp" + +#ifdef BUILD_VIDEO_OPENCV +# define OTR_VIDEO_OPENCV_EXPORT OTR_GENERIC_EXPORT +#else +# define OTR_VIDEO_OPENCV_EXPORT OTR_GENERIC_IMPORT +#endif diff --git a/video-opencv/lang/nl_NL.ts b/video-opencv/lang/nl_NL.ts new file mode 100644 index 00000000..6401616d --- /dev/null +++ b/video-opencv/lang/nl_NL.ts @@ -0,0 +1,4 @@ + + + + diff --git a/video-opencv/lang/ru_RU.ts b/video-opencv/lang/ru_RU.ts new file mode 100644 index 00000000..6401616d --- /dev/null +++ b/video-opencv/lang/ru_RU.ts @@ -0,0 +1,4 @@ + + + + diff --git a/video-opencv/lang/stub.ts b/video-opencv/lang/stub.ts new file mode 100644 index 00000000..6401616d --- /dev/null +++ b/video-opencv/lang/stub.ts @@ -0,0 +1,4 @@ + + + + diff --git a/video-opencv/lang/zh_CN.ts b/video-opencv/lang/zh_CN.ts new file mode 100644 index 00000000..6401616d --- /dev/null +++ b/video-opencv/lang/zh_CN.ts @@ -0,0 +1,4 @@ + + + + diff --git a/video-opencv/video-property-page.cpp b/video-opencv/video-property-page.cpp new file mode 100644 index 00000000..92abd887 --- /dev/null +++ b/video-opencv/video-property-page.cpp @@ -0,0 +1,165 @@ +/* Copyright (c) 2016 Stanislaw Halik + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#include "video-property-page.hpp" + +#ifdef _WIN32 + +#include "camera-names.hpp" +#include "compat/sleep.hpp" +#include "compat/run-in-thread.hpp" +#include "compat/library-path.hpp" + +#include + +#include + +#include +#include +#include +#include + +#include + +bool video_property_page::show_from_capture(cv::VideoCapture& cap, int /*index */) +{ + return cap.set(cv::CAP_PROP_SETTINGS, 0); +} + +struct prop_settings_worker final : QThread +{ + explicit prop_settings_worker(int idx); + ~prop_settings_worker() override; + +private: + void open_prop_page(); + void run() override; + + cv::VideoCapture cap; + int idx = -1; +}; + +prop_settings_worker::prop_settings_worker(int idx_) +{ + int ret = (int)cap.get(cv::CAP_PROP_SETTINGS); + + if (ret != 0) + run_in_thread_async(qApp, [] { + QMessageBox::warning(nullptr, + "Camera properties", + "Camera dialog already opened", + QMessageBox::Cancel, + QMessageBox::NoButton); + }); + else + { + idx = idx_; + // DON'T MOVE IT + // ps3 eye will reset to default settings if done from another thread + open_prop_page(); + } +} + +void prop_settings_worker::open_prop_page() +{ + cap.open(idx); + + if (cap.isOpened()) + { + cv::Mat tmp; + + for (unsigned k = 0; k < 2000/50; k++) + { + if (cap.read(tmp)) + { + qDebug() << "got frame" << tmp.rows << tmp.cols; + goto ok; + } + portable::sleep(50); + } + } + + qDebug() << "property-page: can't open camera"; + idx = -1; + + return; + +ok: + portable::sleep(100); + + qDebug() << "property-page: opening for" << idx; + + if (!cap.set(cv::CAP_PROP_SETTINGS, 0)) + { + run_in_thread_async(qApp, [] { + QMessageBox::warning(nullptr, + "Camera properties", + "Can't open camera dialog", + QMessageBox::Cancel, + QMessageBox::NoButton); + }); + } +} + +prop_settings_worker::~prop_settings_worker() +{ + if (idx != -1) + { + // ax filter is race condition-prone + portable::sleep(250); + cap.release(); + // idem + portable::sleep(250); + + qDebug() << "property-page: closed" << idx; + } +} + +void prop_settings_worker::run() +{ + if (idx != -1) + { + while (cap.get(cv::CAP_PROP_SETTINGS) > 0) + portable::sleep(1000); + } +} + +bool video_property_page::show(int idx) +{ + auto thread = new prop_settings_worker(idx); + + // XXX is this a race condition? + thread->moveToThread(qApp->thread()); + QObject::connect(thread, &QThread::finished, qApp, [thread] { thread->deleteLater(); }, Qt::DirectConnection); + + thread->start(); + + return true; +} + +#elif defined(__linux) +# include +# include "compat/camera-names.hpp" + +bool video_property_page::show(int idx) +{ + const QList camera_names(get_camera_names()); + + if (idx >= 0 && idx < camera_names.size()) + return QProcess::startDetached("qv4l2", QStringList { "-d", QString("/dev/video%1").arg(idx) }); + else + return false; +} + +bool video_property_page::show_from_capture(cv::VideoCapture&, int idx) +{ + return show(idx); +} +#else +bool video_property_page::show(int) { return false; } +bool video_property_page::show_from_capture(cv::VideoCapture&, int) { return false; } +#endif diff --git a/video-opencv/video-property-page.hpp b/video-opencv/video-property-page.hpp new file mode 100644 index 00000000..c2b9525d --- /dev/null +++ b/video-opencv/video-property-page.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +struct video_property_page final +{ + video_property_page() = delete; + static bool show(int id); + static bool show_from_capture(cv::VideoCapture& cap, int index); +private: +}; + diff --git a/video/camera.cpp b/video/camera.cpp new file mode 100644 index 00000000..c33ab13a --- /dev/null +++ b/video/camera.cpp @@ -0,0 +1,66 @@ +#include "camera.hpp" + +#include +#include +#include + +static std::vector> metadata; +static QMutex mtx; + +namespace video::impl { + +camera_::camera_() = default; +camera_::~camera_() = default; + +camera::camera() = default; +camera::~camera() = default; + +void register_camera(std::unique_ptr camera) +{ + QMutexLocker l(&mtx); + metadata.push_back(std::move(camera)); +} + +} // ns video::impl + +namespace video { + +bool show_dialog(const QString& camera_name) +{ + QMutexLocker l(&mtx); + + for (auto& camera : metadata) + for (const QString& name : camera->camera_names()) + if (name == camera_name) + return camera->show_dialog(camera_name); + + return false; +} + +std::unique_ptr make_camera(const QString& name) +{ + QMutexLocker l(&mtx); + + for (auto& camera : metadata) + for (const QString& name_ : camera->camera_names()) + if (name_ == name) + return camera->make_camera(name); + + return nullptr; +} + +std::vector camera_names() +{ + QMutexLocker l(&mtx); + std::vector names; names.reserve(32); + + for (auto& camera : metadata) + for (const QString& name : camera->camera_names()) + if (std::find(names.cbegin(), names.cend(), name) == names.cend()) + names.push_back(name); + + std::sort(names.begin(), names.end()); + return names; +} + +} // ns video diff --git a/video/camera.hpp b/video/camera.hpp new file mode 100644 index 00000000..c9577933 --- /dev/null +++ b/video/camera.hpp @@ -0,0 +1,95 @@ +/* Copyright (c) 2019 Stanislaw Halik + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#pragma once + +#include "export.hpp" + +#include +#include + +#include + +namespace video +{ + +struct frame final +{ + unsigned char* data = nullptr; + int width = 0, height = 0, stride = 0, channels = 0; +}; + +} // ns video + +namespace video::impl { + +using namespace video; + +struct camera; + +struct OTR_VIDEO_EXPORT camera_ +{ + camera_(); + virtual ~camera_(); + + virtual std::vector camera_names() const = 0; + virtual std::unique_ptr make_camera(const QString& name) = 0; + [[nodiscard]] virtual bool show_dialog(const QString& camera_name) = 0; + virtual bool can_show_dialog(const QString& camera_name) = 0; +}; + +struct OTR_VIDEO_EXPORT camera +{ + struct info final + { + int width = 0, height = 0, fps = 0; + }; + + camera(); + virtual ~camera(); + + [[nodiscard]] virtual bool start(const info& args) = 0; + virtual void stop() = 0; + virtual bool is_open() = 0; + + virtual std::tuple get_frame() = 0; + [[nodiscard]] virtual bool show_dialog() = 0; +}; + +OTR_VIDEO_EXPORT +void register_camera(std::unique_ptr metadata); + +} // ns video::impl + +#define OTR_REGISTER_CAMERA2(type, ctr) \ + namespace { \ + struct init_##ctr \ + { \ + static char fuzz; \ + }; \ + char init_##ctr :: fuzz = \ + (::video::impl::register_camera(std::make_unique()), 0); \ + } // anon ns + +#define OTR_REGISTER_CAMERA(type) \ + OTR_REGISTER_CAMERA2(type, __COUNTER__) + +namespace video +{ +using camera_impl = typename impl::camera; + +OTR_VIDEO_EXPORT +std::unique_ptr make_camera(const QString& name); + +OTR_VIDEO_EXPORT +std::vector camera_names(); + +[[nodiscard]] +OTR_VIDEO_EXPORT +bool show_dialog(const QString& camera_name); + +} // ns video -- cgit v1.2.3