diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2019-03-18 15:20:09 +0100 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2019-03-18 15:20:09 +0100 |
commit | 5023b54ba76325bb0b5598d59714bdad2d55d81e (patch) | |
tree | 15cc639eff7dbfa12eeccaa52d7fd251f18969e6 /video-opencv | |
parent | 3aababf6fd53a7b0312c2c1492bab6b43584b613 (diff) |
video: add support for camera modules
Issue: #910
Diffstat (limited to 'video-opencv')
-rw-r--r-- | video-opencv/CMakeLists.txt | 6 | ||||
-rw-r--r-- | video-opencv/camera-impl.cpp | 173 | ||||
-rw-r--r-- | video-opencv/camera-names.cpp | 106 | ||||
-rw-r--r-- | video-opencv/camera-names.hpp | 18 | ||||
-rw-r--r-- | video-opencv/export.hpp | 11 | ||||
-rw-r--r-- | video-opencv/lang/nl_NL.ts | 4 | ||||
-rw-r--r-- | video-opencv/lang/ru_RU.ts | 4 | ||||
-rw-r--r-- | video-opencv/lang/stub.ts | 4 | ||||
-rw-r--r-- | video-opencv/lang/zh_CN.ts | 4 | ||||
-rw-r--r-- | video-opencv/video-property-page.cpp | 165 | ||||
-rw-r--r-- | video-opencv/video-property-page.hpp | 13 |
11 files changed, 508 insertions, 0 deletions
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 <sthalik@misaki.pl> + * + * 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 <optional> + +#include <opencv2/core.hpp> +#include <opencv2/videoio.hpp> + +using namespace video::impl; + +struct cam; + +struct metadata : camera_ +{ + metadata(); + std::vector<QString> camera_names() const override; + std::unique_ptr<camera> 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<const frame&, bool> get_frame() override; + bool show_dialog() override; + + bool get_frame_(); + + std::optional<cv::VideoCapture> cap; + cv::Mat mat; + frame frame_; + int idx = -1; +}; + +metadata::metadata() = default; + +std::unique_ptr<camera> metadata::make_camera(const QString& name) +{ + int idx = camera_name_to_index(name); + if (idx != -1) + return std::make_unique<cam>(idx); + else + return nullptr; +} + +std::vector<QString> 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<const frame&, bool> 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 <algorithm> +#include <iterator> + +#ifdef _WIN32 +# include <cwchar> +# define NO_DSHOW_STRSAFE +# include <dshow.h> +#elif defined(__unix) || defined(__linux) || defined(__APPLE__) +# include <unistd.h> +#endif + +#ifdef __linux +# include <fcntl.h> +# include <sys/ioctl.h> +# include <linux/videodev2.h> +# include <cerrno> +# include <cstring> +#endif + +#include <QDebug> + +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<QString> get_camera_names() +{ + std::vector<QString> 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 <sthalik@misaki.pl> + + * 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 <vector> +#include <QString> + +#include "export.hpp" + +std::vector<QString> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +</TS> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +</TS> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +</TS> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +</TS> 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 <cstring> + +#include <opencv2/videoio.hpp> + +#include <QApplication> +#include <QProcess> +#include <QThread> +#include <QMessageBox> + +#include <QDebug> + +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 <QProcess> +# include "compat/camera-names.hpp" + +bool video_property_page::show(int idx) +{ + const QList<QString> 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 <QString> +#include <opencv2/videoio.hpp> + +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: +}; + |