mirror of https://github.com/opencv/opencv.git
Merge pull request #20785 from smirnov-alexey:as/oak_backend
GAPI: Add OAK backend * Initial tests and cmake integration * Add a public header and change tests * Stub initial empty template for the OAK backend * WIP * WIP * WIP * WIP * Runtime dai hang debug * Refactoring * Fix hang and debug frame data * Fix frame size * Fix data size issue * Move test code to sample * tmp refactoring * WIP: Code refactoring except for the backend * WIP: Add non-camera sample * Fix samples * Backend refactoring wip * Backend rework wip * Backend rework wip * Remove mat encoder * Fix namespace * Minor backend fixes * Fix hetero sample and refactor backend * Change linking logic in the backend * Fix oak sample * Fix working with ins/outs in OAK island * Trying to fix nv12 problem * Make both samples work * Small refactoring * Remove meta args * WIP refactoring kernel API * Change in/out args API for kernels * Fix build * Fix cmake warning * Partially address review comments * Partially address review comments * Address remaining comments * Add memory ownership * Change pointer-to-pointer to reference-to-pointer * Remove unnecessary reference wrappers * Apply review comments * Check that graph contains only one OAK island * Minor refactoring * Address review commentspull/21475/head
parent
a1ec4ea3a9
commit
f2d5d6d24e
10 changed files with 1155 additions and 2 deletions
@ -0,0 +1,131 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
//
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
|
||||
#ifndef OPENCV_GAPI_OAK_HPP |
||||
#define OPENCV_GAPI_OAK_HPP |
||||
|
||||
#include <opencv2/gapi/garg.hpp> // IStreamSource |
||||
#include <opencv2/gapi/gkernel.hpp> // GKernelPackage |
||||
#include <opencv2/gapi/gstreaming.hpp> // GOptRunArgsP |
||||
|
||||
namespace cv { |
||||
namespace gapi { |
||||
namespace oak { |
||||
|
||||
// FIXME: copypasted from dai library
|
||||
struct EncoderConfig { |
||||
/**
|
||||
* Rate control mode specifies if constant or variable bitrate should be used (H264 / H265) |
||||
*/ |
||||
enum class RateControlMode: int { CBR, VBR }; |
||||
|
||||
/**
|
||||
* Encoding profile, H264, H265 or MJPEG |
||||
*/ |
||||
enum class Profile: int { H264_BASELINE, H264_HIGH, H264_MAIN, H265_MAIN, MJPEG }; |
||||
/**
|
||||
* Specifies prefered bitrate (kb) of compressed output bitstream |
||||
*/ |
||||
std::int32_t bitrate = 8000; |
||||
/**
|
||||
* Every x number of frames a keyframe will be inserted |
||||
*/ |
||||
std::int32_t keyframeFrequency = 30; |
||||
/**
|
||||
* Specifies maximum bitrate (kb) of compressed output bitstream |
||||
*/ |
||||
std::int32_t maxBitrate = 8000; |
||||
/**
|
||||
* Specifies number of B frames to be inserted |
||||
*/ |
||||
std::int32_t numBFrames = 0; |
||||
/**
|
||||
* This options specifies how many frames are available in this nodes pool (can help if |
||||
* receiver node is slow at consuming |
||||
*/ |
||||
std::uint32_t numFramesPool = 4; |
||||
/**
|
||||
* Encoding profile, H264, H265 or MJPEG |
||||
*/ |
||||
Profile profile = Profile::H265_MAIN; |
||||
/**
|
||||
* Value between 0-100% (approximates quality) |
||||
*/ |
||||
std::int32_t quality = 80; |
||||
/**
|
||||
* Lossless mode ([M]JPEG only) |
||||
*/ |
||||
bool lossless = false; |
||||
/**
|
||||
* Rate control mode specifies if constant or variable bitrate should be used (H264 / H265) |
||||
*/ |
||||
RateControlMode rateCtrlMode = RateControlMode::CBR; |
||||
/**
|
||||
* Input and compressed output frame width |
||||
*/ |
||||
std::int32_t width = 1920; |
||||
/**
|
||||
* Input and compressed output frame height |
||||
*/ |
||||
std::int32_t height = 1080; |
||||
/**
|
||||
* Frame rate |
||||
*/ |
||||
float frameRate = 30.0f; |
||||
}; |
||||
|
||||
G_API_OP(GEncFrame, <GArray<uint8_t>(GFrame, EncoderConfig)>, "org.opencv.oak.enc_frame") { |
||||
static GArrayDesc outMeta(const GFrameDesc&, const EncoderConfig&) { |
||||
return cv::empty_array_desc(); |
||||
} |
||||
}; |
||||
|
||||
G_API_OP(GSobelXY, <GFrame(GFrame, const cv::Mat&, const cv::Mat&)>, "org.opencv.oak.sobelxy") { |
||||
static GFrameDesc outMeta(const GFrameDesc& in, const cv::Mat&, const cv::Mat&) { |
||||
return in; |
||||
} |
||||
}; |
||||
|
||||
GAPI_EXPORTS GArray<uint8_t> encode(const GFrame& in, const EncoderConfig&); |
||||
|
||||
GAPI_EXPORTS GFrame sobelXY(const GFrame& in, |
||||
const cv::Mat& hk, |
||||
const cv::Mat& vk); |
||||
|
||||
// OAK backend & kernels ////////////////////////////////////////////////////////
|
||||
GAPI_EXPORTS cv::gapi::GBackend backend(); |
||||
GAPI_EXPORTS cv::gapi::GKernelPackage kernels(); |
||||
|
||||
// Camera object ///////////////////////////////////////////////////////////////
|
||||
|
||||
struct GAPI_EXPORTS ColorCameraParams {}; |
||||
|
||||
class GAPI_EXPORTS ColorCamera: public cv::gapi::wip::IStreamSource { |
||||
cv::MediaFrame m_dummy; |
||||
|
||||
virtual bool pull(cv::gapi::wip::Data &data) override; |
||||
virtual GMetaArg descr_of() const override; |
||||
|
||||
public: |
||||
ColorCamera(); |
||||
}; |
||||
|
||||
} // namespace oak
|
||||
} // namespace gapi
|
||||
|
||||
namespace detail { |
||||
template<> struct CompileArgTag<gapi::oak::ColorCameraParams> { |
||||
static const char* tag() { return "gapi.oak.colorCameraParams"; } |
||||
}; |
||||
|
||||
template<> struct CompileArgTag<gapi::oak::EncoderConfig> { |
||||
static const char* tag() { return "gapi.oak.encoderConfig"; } |
||||
}; |
||||
} // namespace detail
|
||||
|
||||
} // namespace cv
|
||||
|
||||
#endif // OPENCV_GAPI_OAK_HPP
|
@ -0,0 +1,70 @@ |
||||
#include <fstream> |
||||
|
||||
#include <opencv2/gapi.hpp> |
||||
#include <opencv2/gapi/core.hpp> |
||||
#include <opencv2/gapi/gframe.hpp> |
||||
|
||||
#include <opencv2/gapi/oak/oak.hpp> |
||||
#include <opencv2/gapi/streaming/format.hpp> // BGR accessor |
||||
|
||||
#include <opencv2/highgui.hpp> // CommandLineParser |
||||
|
||||
const std::string keys = |
||||
"{ h help | | Print this help message }" |
||||
"{ output | output.h265 | Path to the output .h265 video file }"; |
||||
|
||||
#ifdef HAVE_OAK |
||||
|
||||
int main(int argc, char *argv[]) { |
||||
cv::CommandLineParser cmd(argc, argv, keys); |
||||
if (cmd.has("help")) { |
||||
cmd.printMessage(); |
||||
return 0; |
||||
} |
||||
|
||||
const std::string output_name = cmd.get<std::string>("output"); |
||||
|
||||
cv::gapi::oak::EncoderConfig cfg; |
||||
cfg.profile = cv::gapi::oak::EncoderConfig::Profile::H265_MAIN; |
||||
|
||||
cv::GFrame in; |
||||
cv::GArray<uint8_t> encoded = cv::gapi::oak::encode(in, cfg); |
||||
|
||||
auto args = cv::compile_args(cv::gapi::oak::ColorCameraParams{}, cv::gapi::oak::kernels()); |
||||
|
||||
auto pipeline = cv::GComputation(cv::GIn(in), cv::GOut(encoded)).compileStreaming(std::move(args)); |
||||
|
||||
// Graph execution /////////////////////////////////////////////////////////
|
||||
pipeline.setSource(cv::gapi::wip::make_src<cv::gapi::oak::ColorCamera>()); |
||||
pipeline.start(); |
||||
|
||||
std::vector<uint8_t> out_h265_data; |
||||
|
||||
std::ofstream out_h265_file; |
||||
out_h265_file.open(output_name, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); |
||||
|
||||
// Pull 300 frames from the camera
|
||||
uint32_t frames = 300; |
||||
uint32_t pulled = 0; |
||||
|
||||
while (pipeline.pull(cv::gout(out_h265_data))) { |
||||
if (out_h265_file.is_open()) { |
||||
out_h265_file.write(reinterpret_cast<const char*>(out_h265_data.data()), |
||||
out_h265_data.size()); |
||||
} |
||||
if (pulled++ == frames) { |
||||
pipeline.stop(); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
std::cout << "Pipeline finished: " << output_name << " file has been written." << std::endl; |
||||
} |
||||
#else // HAVE_OAK
|
||||
|
||||
int main() { |
||||
GAPI_Assert(false && "Built without OAK support"); |
||||
return -1; |
||||
} |
||||
|
||||
#endif // HAVE_OAK
|
@ -0,0 +1,69 @@ |
||||
#include <opencv2/gapi.hpp> |
||||
#include <opencv2/gapi/core.hpp> |
||||
#include <opencv2/gapi/cpu/core.hpp> |
||||
#include <opencv2/gapi/gframe.hpp> |
||||
#include <opencv2/gapi/media.hpp> |
||||
|
||||
#include <opencv2/gapi/oak/oak.hpp> |
||||
#include <opencv2/gapi/streaming/format.hpp> // BGR accessor |
||||
|
||||
#include <opencv2/highgui.hpp> // CommandLineParser |
||||
|
||||
const std::string keys = |
||||
"{ h help | | Print this help message }" |
||||
"{ output | output.png | Path to the output file }"; |
||||
|
||||
#ifdef HAVE_OAK |
||||
|
||||
int main(int argc, char *argv[]) { |
||||
cv::CommandLineParser cmd(argc, argv, keys); |
||||
if (cmd.has("help")) { |
||||
cmd.printMessage(); |
||||
return 0; |
||||
} |
||||
|
||||
const std::string output_name = cmd.get<std::string>("output"); |
||||
|
||||
std::vector<int> h = {1, 0, -1, |
||||
2, 0, -2, |
||||
1, 0, -1}; |
||||
std::vector<int> v = { 1, 2, 1, |
||||
0, 0, 0, |
||||
-1, -2, -1}; |
||||
cv::Mat hk(3, 3, CV_32SC1, h.data()); |
||||
cv::Mat vk(3, 3, CV_32SC1, v.data()); |
||||
|
||||
// Heterogeneous pipeline:
|
||||
// OAK camera -> Sobel -> streaming accessor (CPU)
|
||||
cv::GFrame in; |
||||
cv::GFrame sobel = cv::gapi::oak::sobelXY(in, hk, vk); |
||||
// Default camera and then sobel work only with nv12 format
|
||||
cv::GMat out = cv::gapi::streaming::Y(sobel); |
||||
|
||||
auto args = cv::compile_args(cv::gapi::oak::ColorCameraParams{}, |
||||
cv::gapi::oak::kernels()); |
||||
|
||||
auto pipeline = cv::GComputation(cv::GIn(in), cv::GOut(out)).compileStreaming(std::move(args)); |
||||
|
||||
// Graph execution /////////////////////////////////////////////////////////
|
||||
cv::Mat out_mat(1920, 1080, CV_8UC1); |
||||
|
||||
pipeline.setSource(cv::gapi::wip::make_src<cv::gapi::oak::ColorCamera>()); |
||||
pipeline.start(); |
||||
|
||||
// pull 1 frame
|
||||
pipeline.pull(cv::gout(out_mat)); |
||||
|
||||
cv::imwrite(output_name, out_mat); |
||||
|
||||
std::cout << "Pipeline finished: " << output_name << " file has been written." << std::endl; |
||||
} |
||||
|
||||
#else // HAVE_OAK
|
||||
|
||||
int main() { |
||||
GAPI_Assert(false && "Built without OAK support"); |
||||
return -1; |
||||
} |
||||
|
||||
#endif // HAVE_OAK
|
@ -0,0 +1,47 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
//
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
|
||||
#include <opencv2/gapi/oak/oak.hpp> |
||||
#include <opencv2/gapi/cpu/gcpukernel.hpp> |
||||
|
||||
#include "oak_media_adapter.hpp" |
||||
|
||||
#include <thread> |
||||
#include <chrono> |
||||
|
||||
namespace cv { |
||||
namespace gapi { |
||||
namespace oak { |
||||
|
||||
GArray<uint8_t> encode(const GFrame& in, const EncoderConfig& cfg) { |
||||
return GEncFrame::on(in, cfg); |
||||
} |
||||
|
||||
GFrame sobelXY(const GFrame& in, const cv::Mat& hk, const cv::Mat& vk) { |
||||
return GSobelXY::on(in, hk, vk); |
||||
} |
||||
|
||||
// This is a dummy oak::ColorCamera class that just makes our pipelining
|
||||
// machinery work. The real data comes from the physical camera which
|
||||
// is handled by DepthAI library.
|
||||
ColorCamera::ColorCamera() |
||||
: m_dummy(cv::MediaFrame::Create<cv::gapi::oak::OAKMediaAdapter>()) { |
||||
} |
||||
|
||||
bool ColorCamera::pull(cv::gapi::wip::Data &data) { |
||||
// FIXME: Avoid passing this formal frame to the pipeline
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10)); |
||||
data = m_dummy; |
||||
return true; |
||||
} |
||||
|
||||
cv::GMetaArg ColorCamera::descr_of() const { |
||||
return cv::GMetaArg{cv::descr_of(m_dummy)}; |
||||
} |
||||
|
||||
} // namespace oak
|
||||
} // namespace gapi
|
||||
} // namespace cv
|
@ -0,0 +1,32 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
//
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
|
||||
#include "oak_media_adapter.hpp" |
||||
|
||||
namespace cv { |
||||
namespace gapi { |
||||
namespace oak { |
||||
|
||||
OAKMediaAdapter::OAKMediaAdapter(cv::Size sz, cv::MediaFormat fmt, std::vector<uint8_t>&& buffer) { |
||||
GAPI_Assert(fmt == cv::MediaFormat::NV12 && "OAKMediaAdapter only supports NV12 format for now"); |
||||
m_sz = sz; |
||||
m_fmt = fmt; |
||||
m_buffer = buffer; |
||||
} |
||||
|
||||
MediaFrame::View OAKMediaAdapter::OAKMediaAdapter::access(MediaFrame::Access) { |
||||
uint8_t* y_ptr = m_buffer.data(); |
||||
uint8_t* uv_ptr = m_buffer.data() + static_cast<long>(m_buffer.size() / 3 * 2); |
||||
return MediaFrame::View{cv::MediaFrame::View::Ptrs{y_ptr, uv_ptr}, |
||||
cv::MediaFrame::View::Strides{static_cast<long unsigned int>(m_sz.width), |
||||
static_cast<long unsigned int>(m_sz.width)}}; |
||||
} |
||||
|
||||
cv::GFrameDesc OAKMediaAdapter::OAKMediaAdapter::meta() const { return {m_fmt, m_sz}; } |
||||
|
||||
} // namespace oak
|
||||
} // namespace gapi
|
||||
} // namespace cv
|
@ -0,0 +1,711 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
//
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
|
||||
#include <opencv2/gapi/gkernel.hpp> // GKernelPackage |
||||
|
||||
#ifdef HAVE_OAK |
||||
|
||||
#include <cstring> |
||||
#include <unordered_set> |
||||
#include <algorithm> // any_of |
||||
#include <functional> // reference_wrapper |
||||
|
||||
#include <ade/util/zip_range.hpp> |
||||
|
||||
#include <api/gbackend_priv.hpp> |
||||
#include <backends/common/gbackend.hpp> |
||||
|
||||
#include <opencv2/gapi/streaming/meta.hpp> // streaming::meta_tag |
||||
|
||||
#include "depthai/depthai.hpp" |
||||
|
||||
#include <opencv2/gapi/oak/oak.hpp> |
||||
#include "oak_media_adapter.hpp" |
||||
|
||||
namespace cv { namespace gimpl { |
||||
|
||||
// Forward declaration
|
||||
class GOAKContext; |
||||
struct OAKNodeInfo; |
||||
|
||||
class GOAKExecutable final: public GIslandExecutable { |
||||
friend class GOAKContext; |
||||
virtual void run(std::vector<InObj>&&, |
||||
std::vector<OutObj>&&) override { |
||||
GAPI_Assert(false && "Not implemented"); |
||||
} |
||||
|
||||
virtual void run(GIslandExecutable::IInput &in, |
||||
GIslandExecutable::IOutput &out) override; |
||||
|
||||
void LinkToParents(ade::NodeHandle handle); |
||||
|
||||
class ExtractTypeHelper : protected dai::Node { |
||||
public: |
||||
using Input = dai::Node::Input; |
||||
using Output = dai::Node::Output; |
||||
using InputPtr = dai::Node::Input*; |
||||
using OutputPtr = dai::Node::Output*; |
||||
}; |
||||
|
||||
struct OAKNodeInfo { |
||||
std::shared_ptr<dai::Node> node = nullptr; |
||||
std::vector<ExtractTypeHelper::InputPtr> inputs = {}; |
||||
std::vector<ExtractTypeHelper::OutputPtr> outputs = {}; |
||||
}; |
||||
|
||||
struct OAKOutQueueInfo { |
||||
std::shared_ptr<dai::node::XLinkOut> xlink_output; |
||||
std::shared_ptr<dai::DataOutputQueue> out_queue; |
||||
std::string out_queue_name; |
||||
}; |
||||
|
||||
cv::GArg packInArg(const GArg &arg, std::vector<ExtractTypeHelper::InputPtr>& oak_ins); |
||||
void packOutArg(const RcDesc &rc, std::vector<ExtractTypeHelper::OutputPtr>& oak_outs); |
||||
|
||||
const ade::Graph& m_g; |
||||
GModel::ConstGraph m_gm; |
||||
cv::GCompileArgs m_args; |
||||
|
||||
std::unordered_map<ade::NodeHandle, |
||||
OAKNodeInfo, |
||||
ade::HandleHasher<ade::Node>> m_oak_nodes; |
||||
|
||||
// Will be reworked later when XLinkIn will be introduced as input
|
||||
std::shared_ptr<dai::node::ColorCamera> m_camera_input; |
||||
cv::Size m_camera_size; |
||||
|
||||
// Backend outputs
|
||||
std::vector<OAKOutQueueInfo> m_out_queues; |
||||
|
||||
// Backend inputs
|
||||
std::vector<std::pair<std::string, dai::Buffer>> m_in_queues; |
||||
|
||||
// Note: dai::Pipeline should be the only one for the whole pipeline,
|
||||
// so there is no way to insert any non-OAK node in graph between other OAK nodes.
|
||||
// The only heterogeneous case possible is if we insert other backends after or before
|
||||
// OAK island.
|
||||
std::unique_ptr<dai::Device> m_device; |
||||
std::unique_ptr<dai::Pipeline> m_pipeline; |
||||
|
||||
public: |
||||
GOAKExecutable(const ade::Graph& g, |
||||
const cv::GCompileArgs& args, |
||||
const std::vector<ade::NodeHandle>& nodes, |
||||
const std::vector<cv::gimpl::Data>& ins_data, |
||||
const std::vector<cv::gimpl::Data>& outs_data); |
||||
~GOAKExecutable() = default; |
||||
|
||||
// FIXME: could it reshape?
|
||||
virtual bool canReshape() const override { return false; } |
||||
virtual void reshape(ade::Graph&, const GCompileArgs&) override { |
||||
GAPI_Assert(false && "GOAKExecutable::reshape() is not supported"); |
||||
} |
||||
|
||||
virtual void handleNewStream() override; |
||||
virtual void handleStopStream() override; |
||||
}; |
||||
|
||||
class GOAKContext { |
||||
public: |
||||
// FIXME: make private?
|
||||
using Input = GOAKExecutable::ExtractTypeHelper::Input; |
||||
using Output = GOAKExecutable::ExtractTypeHelper::Output; |
||||
using InputPtr = GOAKExecutable::ExtractTypeHelper::Input*; |
||||
using OutputPtr = GOAKExecutable::ExtractTypeHelper::Output*; |
||||
|
||||
GOAKContext(const std::unique_ptr<dai::Pipeline>& pipeline, |
||||
const cv::Size& camera_size, |
||||
std::vector<cv::GArg>& args, |
||||
std::vector<OutputPtr>& results); |
||||
|
||||
// Generic accessor API
|
||||
template<typename T> |
||||
T& inArg(int input) { return m_args.at(input).get<T>(); } |
||||
|
||||
// FIXME: consider not using raw pointers
|
||||
InputPtr& in(int input); |
||||
OutputPtr& out(int output); |
||||
|
||||
const std::unique_ptr<dai::Pipeline>& pipeline(); |
||||
const cv::Size& camera_size() const; |
||||
|
||||
private: |
||||
const std::unique_ptr<dai::Pipeline>& m_pipeline; |
||||
const cv::Size& m_camera_size; |
||||
std::vector<cv::GArg>& m_args; |
||||
std::vector<OutputPtr>& m_outputs; |
||||
}; |
||||
|
||||
GOAKContext::GOAKContext(const std::unique_ptr<dai::Pipeline>& pipeline, |
||||
const cv::Size& camera_size, |
||||
std::vector<cv::GArg>& args, |
||||
std::vector<OutputPtr>& results) |
||||
: m_pipeline(pipeline), m_camera_size(camera_size), m_args(args), m_outputs(results) {} |
||||
|
||||
const std::unique_ptr<dai::Pipeline>& GOAKContext::pipeline() { |
||||
return m_pipeline; |
||||
} |
||||
|
||||
const cv::Size& GOAKContext::camera_size() const { |
||||
return m_camera_size; |
||||
} |
||||
|
||||
GOAKContext::InputPtr& GOAKContext::in(int input) { |
||||
return inArg<std::reference_wrapper<GOAKContext::InputPtr>>(input).get(); |
||||
} |
||||
|
||||
GOAKContext::OutputPtr& GOAKContext::out(int output) { |
||||
return m_outputs.at(output); |
||||
} |
||||
|
||||
namespace detail { |
||||
template<class T> struct get_in; |
||||
template<> struct get_in<cv::GFrame> { |
||||
static GOAKContext::InputPtr& get(GOAKContext &ctx, int idx) { return ctx.in(idx); } |
||||
}; |
||||
template<class T> struct get_in { |
||||
static T get(GOAKContext &ctx, int idx) { return ctx.inArg<T>(idx); } |
||||
}; |
||||
// FIXME: add support of other types
|
||||
|
||||
template<class T> struct get_out; |
||||
template<> struct get_out<cv::GFrame> { |
||||
static GOAKContext::OutputPtr& get(GOAKContext &ctx, int idx) { return ctx.out(idx); } |
||||
}; |
||||
template<typename U> struct get_out<cv::GArray<U>> { |
||||
static GOAKContext::OutputPtr& get(GOAKContext &ctx, int idx) { return ctx.out(idx); } |
||||
}; |
||||
// FIXME: add support of other types
|
||||
|
||||
struct OAKKernelParams { |
||||
const std::unique_ptr<dai::Pipeline>& pipeline; |
||||
const cv::Size& camera_size; |
||||
std::vector<std::pair<std::string, dai::Buffer>>& m_in_queues; |
||||
}; |
||||
|
||||
template<typename, typename, typename> |
||||
struct OAKCallHelper; |
||||
|
||||
template<typename Impl, typename... Ins, typename... Outs> |
||||
struct OAKCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...> > { |
||||
template<int... IIs, int... OIs> |
||||
static std::shared_ptr<dai::Node> construct_impl( GOAKContext &ctx |
||||
, std::vector<std::pair<std::string, |
||||
dai::Buffer>>& in_queues_params |
||||
, cv::detail::Seq<IIs...> |
||||
, cv::detail::Seq<OIs...>) { |
||||
return Impl::put(OAKKernelParams{ctx.pipeline(), |
||||
ctx.camera_size(), |
||||
in_queues_params}, |
||||
get_in<Ins>::get(ctx, IIs)..., |
||||
get_out<Outs>::get(ctx, OIs)...); |
||||
} |
||||
|
||||
static std::shared_ptr<dai::Node> construct(GOAKContext &ctx, |
||||
std::vector<std::pair<std::string, |
||||
dai::Buffer>>& in_queues_params) { |
||||
return construct_impl(ctx, |
||||
in_queues_params, |
||||
typename cv::detail::MkSeq<sizeof...(Ins)>::type(), |
||||
typename cv::detail::MkSeq<sizeof...(Outs)>::type()); |
||||
} |
||||
}; |
||||
|
||||
} // namespace detail
|
||||
|
||||
struct GOAKKernel { |
||||
using F = std::function<std::shared_ptr<dai::Node>(GOAKContext&, |
||||
std::vector<std::pair<std::string, dai::Buffer>>&)>; |
||||
explicit GOAKKernel(const F& f) : m_put_f(f) {} |
||||
const F m_put_f; |
||||
}; |
||||
|
||||
struct OAKComponent |
||||
{ |
||||
static const char *name() { return "OAK Component"; } |
||||
GOAKKernel k; |
||||
}; |
||||
|
||||
}} // namespace gimpl // namespace cv
|
||||
|
||||
using OAKGraph = ade::TypedGraph |
||||
< cv::gimpl::OAKComponent |
||||
// FIXME: extend
|
||||
>; |
||||
|
||||
using ConstOAKGraph = ade::ConstTypedGraph |
||||
< cv::gimpl::OAKComponent |
||||
// FIXME: extend
|
||||
>; |
||||
|
||||
// This function links dai operation nodes - parent's output to child's input.
|
||||
// It utilizes G-API graph to search for operation's node it's previous operation in graph
|
||||
// when links them in dai graph.
|
||||
void cv::gimpl::GOAKExecutable::LinkToParents(ade::NodeHandle handle) |
||||
{ |
||||
ade::NodeHandle parent; |
||||
for (const auto& data_nh : handle.get()->inNodes()) { |
||||
// Data node has only 1 input
|
||||
GAPI_Assert(data_nh.get()->inNodes().size() == 1); |
||||
parent = data_nh.get()->inNodes().front(); |
||||
|
||||
// Assuming that OAK nodes are aligned for linking.
|
||||
// FIXME: potential rework might be needed then
|
||||
// counterexample is found.
|
||||
GAPI_Assert(m_oak_nodes.at(handle).inputs.size() == |
||||
m_oak_nodes.at(parent).outputs.size() && |
||||
"Internal OAK nodes are not aligned for linking"); |
||||
for (auto && it : ade::util::zip(ade::util::toRange(m_oak_nodes.at(parent).outputs), |
||||
ade::util::toRange(m_oak_nodes.at(handle).inputs))) |
||||
{ |
||||
auto &out = std::get<0>(it); |
||||
auto &in = std::get<1>(it); |
||||
out->link(*in); |
||||
} |
||||
} |
||||
} |
||||
|
||||
cv::GArg |
||||
cv::gimpl::GOAKExecutable::packInArg(const GArg &arg, |
||||
std::vector<ExtractTypeHelper::InputPtr>& oak_ins) { |
||||
if (arg.kind != cv::detail::ArgKind::GOBJREF) { |
||||
GAPI_Assert( arg.kind != cv::detail::ArgKind::GMAT |
||||
&& arg.kind != cv::detail::ArgKind::GSCALAR |
||||
&& arg.kind != cv::detail::ArgKind::GARRAY |
||||
&& arg.kind != cv::detail::ArgKind::GOPAQUE |
||||
&& arg.kind != cv::detail::ArgKind::GFRAME); |
||||
// All other cases - pass as-is, with no transformations to
|
||||
// GArg contents.
|
||||
return const_cast<cv::GArg&>(arg); |
||||
} |
||||
const cv::gimpl::RcDesc &ref = arg.get<cv::gimpl::RcDesc>(); |
||||
switch (ref.shape) { |
||||
case GShape::GFRAME: |
||||
oak_ins.push_back(nullptr); |
||||
return GArg(std::reference_wrapper<ExtractTypeHelper::InputPtr>(oak_ins.back())); |
||||
break; |
||||
default: |
||||
util::throw_error(std::logic_error("Unsupported GShape type in OAK backend")); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
void cv::gimpl::GOAKExecutable::packOutArg(const RcDesc &rc, |
||||
std::vector<ExtractTypeHelper::OutputPtr>& oak_outs) { |
||||
switch (rc.shape) { |
||||
case GShape::GFRAME: |
||||
oak_outs.push_back(nullptr); |
||||
break; |
||||
case GShape::GARRAY: |
||||
oak_outs.push_back(nullptr); |
||||
break; |
||||
default: |
||||
util::throw_error(std::logic_error("Unsupported GShape type in OAK backend")); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
cv::gimpl::GOAKExecutable::GOAKExecutable(const ade::Graph& g, |
||||
const cv::GCompileArgs &args, |
||||
const std::vector<ade::NodeHandle>& nodes, |
||||
const std::vector<cv::gimpl::Data>& ins_data, |
||||
const std::vector<cv::gimpl::Data>& outs_data) |
||||
: m_g(g), m_gm(m_g), m_args(args), |
||||
m_device(nullptr), m_pipeline(new dai::Pipeline) |
||||
{ |
||||
// FIXME: currently OAK backend only works with camera as input,
|
||||
// so it must be a single object
|
||||
GAPI_Assert(ins_data.size() == 1); |
||||
|
||||
// Check that there is only one OAK island in graph since there
|
||||
// can only be one instance of dai::Pipeline in the application
|
||||
auto isl_graph = m_gm.metadata().get<IslandModel>().model; |
||||
GIslandModel::Graph gim(*isl_graph); |
||||
size_t oak_islands = 0; |
||||
|
||||
for (const auto& nh : gim.nodes()) |
||||
{ |
||||
if (gim.metadata(nh).get<NodeKind>().k == NodeKind::ISLAND) |
||||
{ |
||||
const auto isl = gim.metadata(nh).get<FusedIsland>().object; |
||||
if (isl->backend() == cv::gapi::oak::backend()) |
||||
{ |
||||
++oak_islands; |
||||
} |
||||
if (oak_islands > 1) { |
||||
util::throw_error |
||||
(std::logic_error |
||||
("There can only be one OAK island in graph")); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// FIXME: change the hard-coded behavior (XLinkIn path)
|
||||
auto camRgb = m_pipeline->create<dai::node::ColorCamera>(); |
||||
// FIXME: extract camera compile arguments here and properly convert them for dai
|
||||
camRgb->setBoardSocket(dai::CameraBoardSocket::RGB); |
||||
camRgb->setResolution(dai::ColorCameraProperties::SensorResolution::THE_1080_P); |
||||
|
||||
// Set camera output. Fixme: consider working with other camera outputs
|
||||
m_camera_input = camRgb; |
||||
// FIXME: change when other camera censors are introduced
|
||||
std::tuple<int, int> video_size = camRgb->getVideoSize(); |
||||
m_camera_size = cv::Size{std::get<0>(video_size), std::get<1>(video_size)}; |
||||
|
||||
// Prepare XLinkOut nodes for each output object in graph
|
||||
for (size_t i = 0; i < outs_data.size(); ++i) { |
||||
auto xout = m_pipeline->create<dai::node::XLinkOut>(); |
||||
std::string xout_name = "xout" + std::to_string(i); |
||||
xout->setStreamName(xout_name); |
||||
m_out_queues.push_back({xout, nullptr, xout_name}); |
||||
} |
||||
|
||||
// Create OAK node for each node in this backend
|
||||
for (const auto& nh : nodes) { |
||||
if (m_gm.metadata(nh).get<NodeType>().t == NodeType::OP) { |
||||
const auto& op = m_gm.metadata(nh).get<Op>(); |
||||
const auto &u = ConstOAKGraph(m_g).metadata(nh).get<OAKComponent>(); |
||||
// pass kernel input args and compile args to prepare OAK node and
|
||||
// store it to link later
|
||||
m_oak_nodes[nh] = {}; |
||||
m_oak_nodes.at(nh).inputs.reserve(op.args.size()); |
||||
m_oak_nodes.at(nh).outputs.reserve(op.outs.size()); |
||||
|
||||
std::vector<cv::GArg> in_ctx_args; |
||||
in_ctx_args.reserve(op.args.size()); |
||||
for (auto &op_arg : op.args) in_ctx_args.push_back(packInArg(op_arg, |
||||
m_oak_nodes.at(nh).inputs)); |
||||
for (auto &&op_out : op.outs) packOutArg(op_out, m_oak_nodes.at(nh).outputs); |
||||
GAPI_Assert(!m_oak_nodes.at(nh).inputs.empty()); |
||||
GAPI_Assert(!m_oak_nodes.at(nh).outputs.empty()); |
||||
|
||||
GOAKContext ctx(m_pipeline, m_camera_size, in_ctx_args, m_oak_nodes.at(nh).outputs); |
||||
m_oak_nodes.at(nh).node = u.k.m_put_f(ctx, m_in_queues); |
||||
GAPI_Assert(m_oak_nodes.at(nh).node != nullptr); |
||||
|
||||
// Check that all inputs and outputs are properly filled after constructing kernels
|
||||
// to then link it together
|
||||
// FIXME: add more logging
|
||||
const auto& node = m_oak_nodes.at(nh); |
||||
if (std::any_of(node.inputs.cbegin(), node.inputs.cend(), |
||||
[](ExtractTypeHelper::InputPtr ptr) { |
||||
return ptr == nullptr; |
||||
})) { |
||||
GAPI_Assert(false && "DAI input are not set"); |
||||
} |
||||
if (std::any_of(node.outputs.cbegin(), node.outputs.cend(), |
||||
[](ExtractTypeHelper::OutputPtr ptr) { |
||||
return ptr == nullptr; |
||||
})) { |
||||
GAPI_Assert(false && "DAI outputs are not set"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Prepare nodes for linking
|
||||
std::unordered_set<ade::NodeHandle, |
||||
ade::HandleHasher<ade::Node>> in_nodes; |
||||
std::unordered_set<ade::NodeHandle, |
||||
ade::HandleHasher<ade::Node>> out_nodes; |
||||
std::unordered_set<ade::NodeHandle, |
||||
ade::HandleHasher<ade::Node>> inter_nodes; |
||||
|
||||
// TODO: optimize this loop
|
||||
for (const auto& node : m_oak_nodes) { |
||||
auto nh = node.first; |
||||
// Fill input op nodes
|
||||
for (const auto& d : ins_data) { |
||||
for (const auto& indata : nh.get()->inNodes()) { |
||||
auto rc = m_gm.metadata(indata).get<cv::gimpl::Data>().rc; |
||||
if (rc == d.rc) { |
||||
in_nodes.insert(nh); |
||||
} |
||||
} |
||||
} |
||||
// Fill output op nodes
|
||||
for (const auto& d : outs_data) { |
||||
for (const auto& outdata : nh.get()->outNodes()) { |
||||
auto rc = m_gm.metadata(outdata).get<cv::gimpl::Data>().rc; |
||||
if (rc == d.rc) { |
||||
out_nodes.insert(nh); |
||||
} |
||||
} |
||||
} |
||||
// Fill internal op nodes
|
||||
if (in_nodes.find(nh) == in_nodes.end() && |
||||
out_nodes.find(nh) == in_nodes.end()) { |
||||
inter_nodes.insert(nh); |
||||
} |
||||
} |
||||
|
||||
// Properly link all nodes
|
||||
// 1. Link input nodes to camera
|
||||
for (const auto& nh : in_nodes) { |
||||
GAPI_Assert(m_oak_nodes.at(nh).inputs.size() == 1); |
||||
// FIXME: covert other camera outputs
|
||||
m_camera_input->video.link(*(m_oak_nodes.at(nh).inputs[0])); |
||||
} |
||||
|
||||
// 2. Link output nodes to XLinkOut nodes
|
||||
size_t out_counter = 0; |
||||
for (const auto& nh : out_nodes) { |
||||
GAPI_Assert(out_counter + m_oak_nodes.at(nh).outputs.size() <= m_out_queues.size()); |
||||
for (const auto& out : m_oak_nodes.at(nh).outputs) { |
||||
out->link(m_out_queues[out_counter++].xlink_output->input); |
||||
} |
||||
// Input nodes in OAK doesn't have parent operation - just camera (for now)
|
||||
if (in_nodes.find(nh) == in_nodes.end()) { |
||||
LinkToParents(nh); |
||||
} |
||||
} |
||||
|
||||
// 3. Link internal nodes to their parents
|
||||
for (const auto& nh : inter_nodes) { |
||||
// Input nodes in OAK doesn't have parent operation - just camera (for now)
|
||||
if (in_nodes.find(nh) == in_nodes.end()) { |
||||
LinkToParents(nh); |
||||
} |
||||
} |
||||
|
||||
m_device = std::unique_ptr<dai::Device>(new dai::Device(*m_pipeline)); |
||||
|
||||
// Prepare OAK output queues
|
||||
GAPI_Assert(m_out_queues.size() == outs_data.size()); |
||||
for (const auto out_it : ade::util::indexed(outs_data)) |
||||
{ |
||||
auto& q = m_out_queues[ade::util::index(out_it)]; |
||||
GAPI_Assert(q.out_queue == nullptr); // shouldn't be not filled till this point
|
||||
// FIXME: add queue parameters
|
||||
// Currently: 30 - max DAI queue capacity, true - blocking queue
|
||||
q.out_queue = m_device->getOutputQueue(q.out_queue_name, 30, true); |
||||
} |
||||
} |
||||
|
||||
void cv::gimpl::GOAKExecutable::handleNewStream() { |
||||
// do nothing
|
||||
} |
||||
|
||||
void cv::gimpl::GOAKExecutable::handleStopStream() { |
||||
// do nothing
|
||||
} |
||||
|
||||
void cv::gimpl::GOAKExecutable::run(GIslandExecutable::IInput &in, |
||||
GIslandExecutable::IOutput &out) { |
||||
const auto in_msg = in.get(); |
||||
|
||||
if (cv::util::holds_alternative<cv::gimpl::EndOfStream>(in_msg)) { |
||||
out.post(cv::gimpl::EndOfStream{}); |
||||
return; |
||||
} |
||||
|
||||
for (const auto& in_q : m_in_queues) { |
||||
auto q = m_device->getInputQueue(in_q.first); |
||||
q->send(in_q.second); |
||||
} |
||||
|
||||
for (size_t i = 0; i < m_out_queues.size(); ++i) { |
||||
auto q = m_out_queues[i].out_queue; |
||||
// TODO: support other DAI types if needed
|
||||
// Note: we utilize getData() method that returns std::vector of data
|
||||
// on which we gain ownership
|
||||
auto oak_frame = q->get<dai::ImgFrame>(); |
||||
|
||||
auto out_arg = out.get(i); |
||||
|
||||
switch(out_arg.index()) { |
||||
case cv::GRunArgP::index_of<cv::MediaFrame*>(): |
||||
// FIXME: hard-coded NV12
|
||||
*cv::util::get<cv::MediaFrame*>(out_arg) = |
||||
cv::MediaFrame::Create<cv::gapi::oak::OAKMediaAdapter>( |
||||
cv::Size(static_cast<int>(oak_frame->getWidth()), |
||||
static_cast<int>(oak_frame->getHeight())), |
||||
cv::MediaFormat::NV12, |
||||
std::move(oak_frame->getData())); |
||||
break; |
||||
case cv::GRunArgP::index_of<cv::detail::VectorRef>(): |
||||
cv::util::get<cv::detail::VectorRef>(out_arg).wref<uint8_t>() = std::move(oak_frame->getData()); |
||||
break; |
||||
// FIXME: Add support for remaining types
|
||||
default: |
||||
GAPI_Assert(false && "Unsupported type in OAK backend"); |
||||
} |
||||
|
||||
using namespace cv::gapi::streaming::meta_tag; |
||||
cv::GRunArg::Meta meta; |
||||
meta[timestamp] = oak_frame->getTimestamp(); |
||||
meta[seq_id] = oak_frame->getSequenceNum(); |
||||
|
||||
out.meta(out_arg, meta); |
||||
out.post(std::move(out_arg)); |
||||
} |
||||
} |
||||
|
||||
// Built-in kernels for OAK /////////////////////////////////////////////////////
|
||||
|
||||
class GOAKBackendImpl final : public cv::gapi::GBackend::Priv { |
||||
virtual void unpackKernel(ade::Graph &graph, |
||||
const ade::NodeHandle &op_node, |
||||
const cv::GKernelImpl &impl) override { |
||||
OAKGraph gm(graph); |
||||
|
||||
const auto &kimpl = cv::util::any_cast<cv::gimpl::GOAKKernel>(impl.opaque); |
||||
gm.metadata(op_node).set(cv::gimpl::OAKComponent{kimpl}); |
||||
} |
||||
|
||||
virtual EPtr compile(const ade::Graph &graph, |
||||
const cv::GCompileArgs &args, |
||||
const std::vector<ade::NodeHandle> &nodes, |
||||
const std::vector<cv::gimpl::Data>& ins_data, |
||||
const std::vector<cv::gimpl::Data>& outs_data) const override { |
||||
cv::gimpl::GModel::ConstGraph gm(graph); |
||||
// FIXME: pass streaming/non-streaming option to support non-camera case
|
||||
// NB: how could we have non-OAK source in streaming mode, then OAK backend in
|
||||
// streaming mode but without camera input?
|
||||
if (!gm.metadata().contains<cv::gimpl::Streaming>()) { |
||||
GAPI_Assert(false && "OAK backend only supports Streaming mode for now"); |
||||
} |
||||
return EPtr{new cv::gimpl::GOAKExecutable(graph, args, nodes, ins_data, outs_data)}; |
||||
} |
||||
}; |
||||
|
||||
cv::gapi::GBackend cv::gapi::oak::backend() { |
||||
static cv::gapi::GBackend this_backend(std::make_shared<GOAKBackendImpl>()); |
||||
return this_backend; |
||||
} |
||||
|
||||
namespace cv { |
||||
namespace gimpl { |
||||
namespace oak { |
||||
|
||||
namespace { |
||||
static dai::VideoEncoderProperties::Profile convertEncProfile(cv::gapi::oak::EncoderConfig::Profile pf) { |
||||
switch (pf) { |
||||
case cv::gapi::oak::EncoderConfig::Profile::H264_BASELINE: |
||||
return dai::VideoEncoderProperties::Profile::H264_BASELINE; |
||||
case cv::gapi::oak::EncoderConfig::Profile::H264_HIGH: |
||||
return dai::VideoEncoderProperties::Profile::H264_HIGH; |
||||
case cv::gapi::oak::EncoderConfig::Profile::H264_MAIN: |
||||
return dai::VideoEncoderProperties::Profile::H264_MAIN; |
||||
case cv::gapi::oak::EncoderConfig::Profile::H265_MAIN: |
||||
return dai::VideoEncoderProperties::Profile::H265_MAIN; |
||||
case cv::gapi::oak::EncoderConfig::Profile::MJPEG: |
||||
return dai::VideoEncoderProperties::Profile::MJPEG; |
||||
default: |
||||
// basically unreachable
|
||||
GAPI_Assert("Unsupported encoder profile"); |
||||
return {}; |
||||
} |
||||
} |
||||
} // anonymous namespace
|
||||
|
||||
// Kernels ///////////////////////////////////////////////////////////////
|
||||
|
||||
template<class Impl, class K> |
||||
class GOAKKernelImpl: public detail::OAKCallHelper<Impl, typename K::InArgs, typename K::OutArgs> |
||||
, public cv::detail::KernelTag { |
||||
using P = detail::OAKCallHelper<Impl, typename K::InArgs, typename K::OutArgs>; |
||||
public: |
||||
using API = K; |
||||
static cv::gapi::GBackend backend() { return cv::gapi::oak::backend(); } |
||||
static GOAKKernel kernel() { return GOAKKernel(&P::construct); } |
||||
}; |
||||
|
||||
#define GAPI_OAK_KERNEL(Name, API) \ |
||||
struct Name: public cv::gimpl::oak::GOAKKernelImpl<Name, API> |
||||
|
||||
namespace { |
||||
GAPI_OAK_KERNEL(GOAKEncFrame, cv::gapi::oak::GEncFrame) { |
||||
static std::shared_ptr<dai::Node> put(const cv::gimpl::detail::OAKKernelParams& params, |
||||
GOAKContext::InputPtr& in, |
||||
const cv::gapi::oak::EncoderConfig& cfg, |
||||
GOAKContext::OutputPtr& out) { |
||||
auto videoEnc = params.pipeline->create<dai::node::VideoEncoder>(); |
||||
|
||||
// FIXME: convert all the parameters to dai
|
||||
videoEnc->setDefaultProfilePreset(cfg.width, cfg.height, |
||||
cfg.frameRate, |
||||
convertEncProfile(cfg.profile)); |
||||
|
||||
in = &(videoEnc->input); |
||||
out = &(videoEnc->bitstream); |
||||
|
||||
return videoEnc; |
||||
} |
||||
}; |
||||
|
||||
GAPI_OAK_KERNEL(GOAKSobelXY, cv::gapi::oak::GSobelXY) { |
||||
static std::shared_ptr<dai::Node> put(const cv::gimpl::detail::OAKKernelParams& params, |
||||
GOAKContext::InputPtr& in, |
||||
const cv::Mat& hk, |
||||
const cv::Mat& vk, |
||||
GOAKContext::OutputPtr& out) { |
||||
auto edgeDetector = params.pipeline->create<dai::node::EdgeDetector>(); |
||||
|
||||
edgeDetector->setMaxOutputFrameSize(params.camera_size.width * params.camera_size.height); |
||||
|
||||
auto xinEdgeCfg = params.pipeline->create<dai::node::XLinkIn>(); |
||||
xinEdgeCfg->setStreamName("sobel_cfg"); |
||||
|
||||
auto mat2vec = [&](cv::Mat m) { |
||||
std::vector<std::vector<int>> v(m.rows); |
||||
for (int i = 0; i < m.rows; ++i) |
||||
{ |
||||
m.row(i).reshape(1,1).copyTo(v[i]); |
||||
} |
||||
return v; |
||||
}; |
||||
|
||||
dai::EdgeDetectorConfig cfg; |
||||
cfg.setSobelFilterKernels(mat2vec(hk), mat2vec(vk)); |
||||
|
||||
xinEdgeCfg->out.link(edgeDetector->inputConfig); |
||||
|
||||
params.m_in_queues.push_back({"sobel_cfg", cfg}); |
||||
|
||||
in = &(edgeDetector->inputImage); |
||||
out = &(edgeDetector->outputImage); |
||||
|
||||
return edgeDetector; |
||||
} |
||||
}; |
||||
} // anonymous namespace
|
||||
} // namespace oak
|
||||
} // namespace gimpl
|
||||
} // namespace cv
|
||||
|
||||
namespace cv { |
||||
namespace gapi { |
||||
namespace oak { |
||||
|
||||
cv::gapi::GKernelPackage kernels() { |
||||
return cv::gapi::kernels< cv::gimpl::oak::GOAKEncFrame |
||||
, cv::gimpl::oak::GOAKSobelXY |
||||
>(); |
||||
} |
||||
|
||||
} // namespace oak
|
||||
} // namespace gapi
|
||||
} // namespace cv
|
||||
|
||||
#else |
||||
|
||||
namespace cv { |
||||
namespace gapi { |
||||
namespace oak { |
||||
|
||||
cv::gapi::GKernelPackage kernels(); |
||||
|
||||
cv::gapi::GKernelPackage kernels() { |
||||
GAPI_Assert(false && "Built without OAK support"); |
||||
return {}; |
||||
} |
||||
|
||||
} // namespace oak
|
||||
} // namespace gapi
|
||||
} // namespace cv
|
||||
|
||||
#endif // HAVE_OAK
|
@ -0,0 +1,35 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
//
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
|
||||
#ifndef OPENCV_GAPI_OAK_MEDIA_ADAPTER_HPP |
||||
#define OPENCV_GAPI_OAK_MEDIA_ADAPTER_HPP |
||||
|
||||
#include <memory> |
||||
|
||||
#include <opencv2/gapi/media.hpp> |
||||
|
||||
namespace cv { |
||||
namespace gapi { |
||||
namespace oak { |
||||
|
||||
class GAPI_EXPORTS OAKMediaAdapter final : public cv::MediaFrame::IAdapter { |
||||
public: |
||||
OAKMediaAdapter() = default; |
||||
OAKMediaAdapter(cv::Size sz, cv::MediaFormat fmt, std::vector<uint8_t>&& buffer); |
||||
cv::GFrameDesc meta() const override; |
||||
cv::MediaFrame::View access(cv::MediaFrame::Access) override; |
||||
~OAKMediaAdapter() = default; |
||||
private: |
||||
cv::Size m_sz; |
||||
cv::MediaFormat m_fmt; |
||||
std::vector<uint8_t> m_buffer; |
||||
}; |
||||
|
||||
} // namespace oak
|
||||
} // namespace gapi
|
||||
} // namespace cv
|
||||
|
||||
#endif // OPENCV_GAPI_OAK_MEDIA_ADAPTER_HPP
|
@ -0,0 +1,26 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
//
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
|
||||
#include "../test_precomp.hpp" |
||||
|
||||
#ifdef HAVE_OAK |
||||
|
||||
#include <opencv2/gapi/oak/oak.hpp> |
||||
|
||||
namespace opencv_test |
||||
{ |
||||
|
||||
// FIXME: consider a better solution
|
||||
TEST(OAK, Available) |
||||
{ |
||||
cv::GFrame in; |
||||
auto out = cv::gapi::oak::encode(in, {}); |
||||
auto args = cv::compile_args(cv::gapi::oak::ColorCameraParams{}, cv::gapi::oak::kernels()); |
||||
auto pipeline = cv::GComputation(cv::GIn(in), cv::GOut(out)).compileStreaming(std::move(args)); |
||||
} |
||||
} // opencv_test
|
||||
|
||||
#endif // HAVE_OAK
|
Loading…
Reference in new issue