// Copyright 2019, Collabora, Ltd. // SPDX-License-Identifier: BSL-1.0 /*! * @file * @brief Calibration code. * @author Pete Black * @author Jakob Bornecrantz */ #include "util/u_sink.h" #include "util/u_misc.h" #include "util/u_debug.h" #include "util/u_format.h" #include "tracking/t_tracking.h" #include DEBUG_GET_ONCE_BOOL_OPTION(hsv_filter, "T_DEBUG_HSV_FILTER", false) DEBUG_GET_ONCE_BOOL_OPTION(hsv_picker, "T_DEBUG_HSV_PICKER", false) DEBUG_GET_ONCE_BOOL_OPTION(hsv_viewer, "T_DEBUG_HSV_VIEWER", false) /* * * Structs * */ class Calibration { public: struct xrt_frame_sink base = {}; struct { cv::Mat rgb = {}; struct xrt_frame_sink *sink = {}; } gui; cv::Mat grey; char text[512]; }; /*! * Holds `cv::Mat`s used during frame processing when processing a yuyv frame. */ struct t_frame_yuyv { public: //! Full frame size, each block is split across two cols. cv::Mat data_full = {}; //! Half horizontal width covering a complete block of two pixels. cv::Mat data_half = {}; }; /* * * Small helpers. * */ static void send_rgb_frame(struct xrt_frame_sink *xsink, cv::Mat &rgb) { struct xrt_frame xf = {}; xf.format = XRT_FORMAT_R8G8B8; xf.width = rgb.cols; xf.height = rgb.rows; xf.data = rgb.data; u_format_size_for_dimensions(xf.format, xf.width, xf.height, &xf.stride, &xf.size); xsink->push_frame(xsink, &xf); } static void ensure_buffers_are_allocated(class Calibration &c, int rows, int cols) { if (c.gui.rgb.cols == cols && c.gui.rgb.rows == rows) { return; } c.grey = cv::Mat(rows, cols, CV_8UC1, cv::Scalar(0)); c.gui.rgb = cv::Mat(rows, cols, CV_8UC3, cv::Scalar(0, 0, 0)); } static void print_txt(cv::Mat &rgb, const char *text, double fontScale) { int fontFace = 0; int thickness = 2; cv::Size textSize = cv::getTextSize(text, fontFace, fontScale, thickness, NULL); cv::Point textOrg((rgb.cols - textSize.width) / 2, textSize.height * 2); cv::putText(rgb, text, textOrg, fontFace, fontScale, cv::Scalar(192, 192, 192), thickness); } static void make_gui_str(class Calibration &c) { auto &rgb = c.gui.rgb; int cols = 800; int rows = 100; ensure_buffers_are_allocated(c, rows, cols); cv::rectangle(rgb, cv::Point2f(0, 0), cv::Point2f(cols, rows), cv::Scalar(0, 0, 0), -1, 0); print_txt(rgb, c.text, 1.0); send_rgb_frame(c.gui.sink, c.gui.rgb); } static void make_calibration_frame(class Calibration &c) { auto &rgb = c.gui.rgb; if (rgb.rows == 0 || rgb.cols == 0) { ensure_buffers_are_allocated(c, 480, 640); cv::rectangle(c.gui.rgb, cv::Point2f(0, 0), cv::Point2f(rgb.cols, rgb.rows), cv::Scalar(0, 0, 0), -1, 0); } /* * Draw text */ print_txt(rgb, "CALIBRATION MODE", 1.5); send_rgb_frame(c.gui.sink, rgb); } /* * * Main functions. * */ static void process_frame_yuv(class Calibration &c, struct xrt_frame *xf) { int w = (int)xf->width; int h = (int)xf->height; cv::Mat data(h, w, CV_8UC3, xf->data, xf->stride); ensure_buffers_are_allocated(c, data.rows, data.cols); cv::cvtColor(data, c.gui.rgb, cv::COLOR_YUV2RGB); cv::cvtColor(c.gui.rgb, c.grey, cv::COLOR_RGB2GRAY); } static void process_frame_yuyv(class Calibration &c, struct xrt_frame *xf) { /* * Cleverly extract the different channels. * Cr/Cb are extracted at half width. */ int w = (int)xf->width; int half_w = w / 2; int h = (int)xf->height; struct t_frame_yuyv f = {}; f.data_half = cv::Mat(h, half_w, CV_8UC4, xf->data, xf->stride); f.data_full = cv::Mat(h, w, CV_8UC2, xf->data, xf->stride); ensure_buffers_are_allocated(c, f.data_full.rows, f.data_full.cols); cv::cvtColor(f.data_full, c.gui.rgb, cv::COLOR_YUV2RGB_YUYV); cv::cvtColor(f.data_full, c.grey, cv::COLOR_YUV2GRAY_YUYV); } /* * * Interface functions. * */ extern "C" void t_calibration_frame(struct xrt_frame_sink *xsink, struct xrt_frame *xf) { auto &c = *(class Calibration *)xsink; #if 0 if (xf->stereo_format != XRT_FS_STEREO_SBS) { snprintf(c.text, sizeof(c.text), "ERROR: Not side by side stereo!"); make_gui_str(c); return; } #endif // Fill both c.gui.rgb and c.grey with the data we got. switch (xf->format) { case XRT_FORMAT_YUV888: process_frame_yuv(c, xf); break; case XRT_FORMAT_YUV422: process_frame_yuyv(c, xf); break; default: snprintf(c.text, sizeof(c.text), "ERROR: Bad format '%s'", u_format_str(xf->format)); make_gui_str(c); return; } make_calibration_frame(c); } /* * * Exported functions. * */ extern "C" int t_calibration_create(struct xrt_frame_sink *gui, struct xrt_frame_sink **out_sink) { auto &c = *(new Calibration()); c.gui.sink = gui; c.base.push_frame = t_calibration_frame; *out_sink = &c.base; snprintf(c.text, sizeof(c.text), "Waiting for camera"); make_gui_str(c); int ret = 0; if (debug_get_bool_option_hsv_filter()) { ret = t_debug_hsv_filter_create(*out_sink, out_sink); } if (debug_get_bool_option_hsv_picker()) { ret = t_debug_hsv_picker_create(*out_sink, out_sink); } if (debug_get_bool_option_hsv_viewer()) { ret = t_debug_hsv_viewer_create(*out_sink, out_sink); } // Ensure we only get yuv or yuyv frames. u_sink_create_to_yuv_or_yuyv(*out_sink, out_sink); return ret; }