diff --git a/modules/highgui/CMakeLists.txt b/modules/highgui/CMakeLists.txt index 472d66799a..a0da13ef95 100644 --- a/modules/highgui/CMakeLists.txt +++ b/modules/highgui/CMakeLists.txt @@ -22,6 +22,7 @@ set(highgui_hdrs set(highgui_srcs ${CMAKE_CURRENT_LIST_DIR}/src/window.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/roiSelector.cpp ) file(GLOB highgui_ext_hdrs diff --git a/modules/highgui/include/opencv2/highgui.hpp b/modules/highgui/include/opencv2/highgui.hpp index 16ef8c4a9e..b1f203df03 100644 --- a/modules/highgui/include/opencv2/highgui.hpp +++ b/modules/highgui/include/opencv2/highgui.hpp @@ -468,6 +468,44 @@ Mouse-wheel events are currently supported only on Windows. */ CV_EXPORTS int getMouseWheelDelta(int flags); +/** @brief Selects ROI on the given image. +Function creates a window and allows user to select a ROI using mouse. +Controls: use `space` or `enter` to finish selection, use key `c` to cancel selection (function will return the zero cv::Rect). + +@param windowName name of the window where selection process will be shown. +@param img image to select a ROI. +@param showCrosshair if true crosshair of selection rectangle will be shown. +@param fromCenter if true center of selection will match initial mouse position. In opposite case a corner of +selection rectangle will correspont to the initial mouse position. +@return selected ROI or empty rect if selection canceled. + +@note The function sets it's own mouse callback for specified window using cv::setMouseCallback(windowName, ...). +After finish of work an empty callback will be set for the used window. + */ +CV_EXPORTS_W Rect selectROI(const String& windowName, InputArray img, bool showCrosshair = true, bool fromCenter = false); + +/** @overload + */ +CV_EXPORTS_W Rect selectROI(InputArray img, bool showCrosshair = true, bool fromCenter = false); + +/** @brief Selects ROIs on the given image. +Function creates a window and allows user to select a ROIs using mouse. +Controls: use `space` or `enter` to finish current selection and start a new one, +use `ecs` to terminate multiple ROI selection process. + +@param windowName name of the window where selection process will be shown. +@param img image to select a ROI. +@param boundingBoxes selected ROIs. +@param showCrosshair if true crosshair of selection rectangle will be shown. +@param fromCenter if true center of selection will match initial mouse position. In opposite case a corner of +selection rectangle will correspont to the initial mouse position. + +@note The function sets it's own mouse callback for specified window using cv::setMouseCallback(windowName, ...). +After finish of work an empty callback will be set for the used window. + */ +CV_EXPORTS_W void selectROIs(const String& windowName, InputArray img, + CV_OUT std::vector& boundingBoxes, bool showCrosshair = true, bool fromCenter = false); + /** @brief Creates a trackbar and attaches it to the specified window. The function createTrackbar creates a trackbar (a slider or range control) with the specified name diff --git a/modules/highgui/src/roiSelector.cpp b/modules/highgui/src/roiSelector.cpp new file mode 100644 index 0000000000..8bc6ba620f --- /dev/null +++ b/modules/highgui/src/roiSelector.cpp @@ -0,0 +1,203 @@ +// 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. + +#include "precomp.hpp" +#include +#include + +using namespace cv; + +namespace +{ +class ROISelector +{ + public: + Rect select(const String &windowName, Mat img, bool showCrossair = true, bool fromCenter = true) + { + // show notice to user + printf("Select a ROI and then press SPACE or ENTER button!\n"); + printf("Cancel the selection process by pressing c button!\n"); + + key = 0; + imageSize = img.size(); + + // set the drawing mode + selectorParams.drawFromCenter = fromCenter; + + // show the image and give feedback to user + imshow(windowName, img); + + // copy the data, rectangle should be drawn in the fresh image + selectorParams.image = img.clone(); + + // select the object + setMouseCallback(windowName, mouseHandler, (void*)this); + + // end selection process on SPACE (32) ESC (27) or ENTER (13) + while (!(key == 32 || key == 27 || key == 13)) + { + // draw the selected object + rectangle(selectorParams.image, selectorParams.box, Scalar(255, 0, 0), 2, 1); + + // draw cross air in the middle of bounding box + if (showCrossair) + { + // horizontal line + line(selectorParams.image, + Point((int)selectorParams.box.x, + (int)(selectorParams.box.y + selectorParams.box.height / 2)), + Point((int)(selectorParams.box.x + selectorParams.box.width), + (int)(selectorParams.box.y + selectorParams.box.height / 2)), + Scalar(255, 0, 0), 2, 1); + + // vertical line + line(selectorParams.image, + Point((int)(selectorParams.box.x + selectorParams.box.width / 2), + (int)selectorParams.box.y), + Point((int)(selectorParams.box.x + selectorParams.box.width / 2), + (int)(selectorParams.box.y + selectorParams.box.height)), + Scalar(255, 0, 0), 2, 1); + } + + // show the image bouding box + imshow(windowName, selectorParams.image); + + // reset the image + selectorParams.image = img.clone(); + + // get keyboard event + key = waitKey(30); + + if (key == 'c' || key == 'C')//cancel selection + { + selectorParams.box = Rect(); + break; + } + } + + //cleanup callback + setMouseCallback(windowName, emptyMouseHandler, NULL); + + return selectorParams.box; + } + + void select(const String &windowName, Mat img, std::vector &boundingBoxes, + bool showCrosshair = true, bool fromCenter = true) + { + printf("Finish the selection process by pressing ESC button!\n"); + boundingBoxes.clear(); + key = 0; + + // while key is not ESC (27) + for (;;) + { + Rect temp = select(windowName, img, showCrosshair, fromCenter); + if (key == 27) + break; + if (temp.width > 0 && temp.height > 0) + boundingBoxes.push_back(temp); + } + } + + struct handlerT + { + // basic parameters + bool isDrawing; + Rect2d box; + Mat image; + + // parameters for drawing from the center + bool drawFromCenter; + Point2f center; + + // initializer list + handlerT() : isDrawing(false), drawFromCenter(true){}; + } selectorParams; + + private: + static void emptyMouseHandler(int, int, int, int, void*) + { + } + + static void mouseHandler(int event, int x, int y, int flags, void *param) + { + ROISelector *self = static_cast(param); + self->opencv_mouse_callback(event, x, y, flags); + } + + void opencv_mouse_callback(int event, int x, int y, int) + { + switch (event) + { + // update the selected bounding box + case EVENT_MOUSEMOVE: + if (selectorParams.isDrawing) + { + if (selectorParams.drawFromCenter) + { + selectorParams.box.width = 2 * (x - selectorParams.center.x); + selectorParams.box.height = 2 * (y - selectorParams.center.y); + selectorParams.box.x = std::min( + std::max(selectorParams.center.x - selectorParams.box.width / 2.0, 0.), (double)imageSize.width); + selectorParams.box.y = std::min( + std::max(selectorParams.center.y - selectorParams.box.height / 2.0, 0.), (double)imageSize.height); + } + else + { + selectorParams.box.width = std::max( + std::min(x - selectorParams.box.x, (double)imageSize.width - selectorParams.box.x), - selectorParams.box.x); + selectorParams.box.height = std::max( + std::min(y - selectorParams.box.y, (double)imageSize.height - selectorParams.box.y), - selectorParams.box.y); + } + } + break; + + // start to select the bounding box + case EVENT_LBUTTONDOWN: + selectorParams.isDrawing = true; + selectorParams.box = Rect2d(x, y, 0, 0); + selectorParams.center = Point2f((float)x, (float)y); + break; + + // cleaning up the selected bounding box + case EVENT_LBUTTONUP: + selectorParams.isDrawing = false; + if (selectorParams.box.width < 0) + { + selectorParams.box.x += selectorParams.box.width; + selectorParams.box.width *= -1; + } + if (selectorParams.box.height < 0) + { + selectorParams.box.y += selectorParams.box.height; + selectorParams.box.height *= -1; + } + break; + } + } + + // save the keypressed characted + int key; + Size imageSize; +}; +} + +Rect cv::selectROI(InputArray img, bool showCrosshair, bool fromCenter) +{ + ROISelector selector; + return selector.select("ROI selector", img.getMat(), showCrosshair, fromCenter); +} + +Rect cv::selectROI(const String& windowName, InputArray img, bool showCrosshair, bool fromCenter) +{ + ROISelector selector; + return selector.select(windowName, img.getMat(), showCrosshair, fromCenter); +} + +void cv::selectROIs(const String& windowName, InputArray img, + std::vector& boundingBox, bool showCrosshair, bool fromCenter) +{ + ROISelector selector; + selector.select(windowName, img.getMat(), boundingBox, showCrosshair, fromCenter); +} diff --git a/samples/cpp/tutorial_code/features2D/AKAZE_tracking/planar_tracking.cpp b/samples/cpp/tutorial_code/features2D/AKAZE_tracking/planar_tracking.cpp index 77bd9a6894..6ccb83119a 100755 --- a/samples/cpp/tutorial_code/features2D/AKAZE_tracking/planar_tracking.cpp +++ b/samples/cpp/tutorial_code/features2D/AKAZE_tracking/planar_tracking.cpp @@ -159,7 +159,7 @@ int main(int argc, char **argv) cout << "Please select a bounding box, and press any key to continue." << endl; vector bb; - cv::Rect2d uBox = selectROI(video_name, frame); + cv::Rect uBox = cv::selectROI(video_name, frame); bb.push_back(cv::Point2f(static_cast(uBox.x), static_cast(uBox.y))); bb.push_back(cv::Point2f(static_cast(uBox.x+uBox.width), static_cast(uBox.y))); bb.push_back(cv::Point2f(static_cast(uBox.x+uBox.width), static_cast(uBox.y+uBox.height))); diff --git a/samples/cpp/tutorial_code/features2D/AKAZE_tracking/utils.h b/samples/cpp/tutorial_code/features2D/AKAZE_tracking/utils.h index 2142e988c0..a3e9d7dbde 100644 --- a/samples/cpp/tutorial_code/features2D/AKAZE_tracking/utils.h +++ b/samples/cpp/tutorial_code/features2D/AKAZE_tracking/utils.h @@ -56,59 +56,4 @@ vector Points(vector keypoints) } return res; } - -Rect2d selectROI(const String &video_name, const Mat &frame) -{ - struct Data - { - Point center; - Rect2d box; - - static void mouseHandler(int event, int x, int y, int flags, void *param) - { - Data *data = (Data*)param; - switch( event ) - { - // start to select the bounding box - case EVENT_LBUTTONDOWN: - data->box = cvRect( x, y, 0, 0 ); - data->center = Point2f((float)x,(float)y); - break; - // update the selected bounding box - case EVENT_MOUSEMOVE: - if(flags == 1) - { - data->box.width = 2 * (x - data->center.x); - data->box.height = 2 * (y - data->center.y); - data->box.x = data->center.x - data->box.width / 2.0; - data->box.y = data->center.y - data->box.height / 2.0; - } - break; - // cleaning up the selected bounding box - case EVENT_LBUTTONUP: - if( data->box.width < 0 ) - { - data->box.x += data->box.width; - data->box.width *= -1; - } - if( data->box.height < 0 ) - { - data->box.y += data->box.height; - data->box.height *= -1; - } - break; - } - } - } data; - - setMouseCallback(video_name, Data::mouseHandler, &data); - while(waitKey(1) < 0) - { - Mat draw = frame.clone(); - rectangle(draw, data.box, Scalar(255,0,0), 2, 1); - imshow(video_name, draw); - } - return data.box; -} - #endif // UTILS_H