using mouse as paint brush - c++

So this is my attempt at setting MouseEvent in openCV, my point is to draw some thing at given point according to mouse event. Any idea what's wrong?
I followed this tutorials: https://docs.opencv.org/3.3.0/db/d5b/tutorial_py_mouse_handling.html
#include<opencv2\core\core.hpp>
#include<opencv2\imgproc\imgproc.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\ml\ml.hpp>
#include<iostream>
#include<conio.h>
using namespace std;
using namespace cv;
int radius = 5;
Mat img;
int ix = 1;
int iy = 1;
//mouse callback function
void drawCircle(int event, int x, int y, int, void* param) {
if (event == CV_EVENT_LBUTTONDOWN)
{
cout << "x = " << x
<< "\t y = " << y
<< endl;
circle(img, Point(x, y), 10, Scalar(255,255,255), 10);
}
//line(img, Point(10, 10), Point(x, y), Scalar(0, 0, 255), 3);
}
int main() {
img = imread("image.png");
if (img.empty()) {
cout << "\nerror reading image" << endl;
_getch();
return -1;
}
namedWindow("Image",1);
imshow("Image", img);
setMouseCallback("Image", drawCircle);
waitKey(0);
return 0;
}
Why doesn't it draw circle or line on my image? Thanks!

You probably need to force a redraw of the window to reflect the changes. I.e. replace your waitKey(0) by something like:
while (waitKey(20) != 27) // wait until ESC is pressed
{
imshow("Image", img);
}
Alternatively, you may be able to just add a call to imshow to the drawCircle callback.

Related

Why doesn't OpenCV setMouseCallBack need a loop?

I am new to C++ and OpenCV. Currently I am studying the chapter about User Interface which includes waitKey function and setMouseCallBack function. I am curious why setMouseCallBack does not need a loop for multiple mouse events, while waitKey needs a loop for multiple keyboard inputs. The codes written below are the sample codes from the book.
If the first code does not have the while loop, then only the first keyboard input will be read and the code finishes.
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main(void)
{
Mat img = imread("D://alpaca.jpg");
if (img.empty()) {
printf("Image load failed");
return -1;
}
namedWindow("Alpaca", WINDOW_NORMAL);
imshow("Alpaca", img);
while (1) {
int keycode = waitKey();
if (keycode == 'i' || keycode == 'I') {
img = ~img;
imshow("Alpaca", img);
}
else if (keycode == 'q' || keycode == 'Q' || keycode == 27) {
break;
}
}
return 0;
}
However, in the second code, each mouse event calls on_mouse function without any loop.
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace cv;
using namespace std;
Mat img;
Point pt0ld;
void on_mouse(int event, int x, int y, int flags, void*)
{
switch (event) {
case EVENT_LBUTTONDOWN:
pt0ld = Point(x, y);
cout << "Event_LBUTTONDOWN: " << x << "," << y << endl;
break;
case EVENT_LBUTTONUP:
cout << "Event_LBUTTONUP: " << x << "," << y << endl;
break;
case EVENT_MOUSEMOVE:
if (flags & EVENT_FLAG_LBUTTON) {
line(img, pt0ld, Point(x, y), Scalar(0, 255, 255), 2);
imshow("img", img);
pt0ld = Point(x, y);
}
break;
default:
break;
}
}
int main(void)
{
img = imread("D://alpaca.jpg");
if (img.empty()) {
printf("Image Load Failed!");
return -1;
}
namedWindow("img");
setMouseCallback("img", on_mouse);
imshow("img", img);
waitKey();
return 0;
}
The difference is that waitKey is a function. You have a direct call to that. In this case the function will block until a key is pressed.
setMouseCallback as the name suggest is used to register a callback function, in this case on_mouse. Someone else will call on_mouse when a mouse event will occur. So you don't need to call the function on_mouse on your own.
The first approach is called polling because you check continuity if something happen.
The second one is event based because when an event will occur, the function will be called.

Problem with getting pixel information using SetMouseCallback, c++, OpenCV

I've been trying to get information about pixels that I click on. I've been using function SetMouceCallback and honestly I don't know exactly how it works.
void startGUI()
{
VideoCapture video;
Mat frame1;
video.open("/Users/Dominik/Desktop/Legia22.mov");
video.read(frame1);
Mat img = frame1.clone();
namedWindow("My Window", 1);
setMouseCallback("My Window", onMouse, &img);
imshow("My Window", img);
//********* I want to use leftClicks[] here
waitKey(0);
}
static void onMouse(int event, int x, int y, int, void* param)
{
Mat &img = *((Mat*)param);
static int counter=0;
Point leftClicks[4];
if ( event == EVENT_LBUTTONDOWN && counter<4)
{
Vec3b val = img.at< Vec3b >(y,x);
cout << "left button clicked pos - (" << x << ", " << y << ")" <<"\t colour bgr: "<< val << endl;
leftClicks[counter].x = x;
leftClicks[counter].y = y;
circle(img, Point(x,y), 4, SCALAR_BLUE, -1);
imshow("My Window", img);
counter++;
}
}
The following code works fine. Window is shown and on the first frame I can draw 4 circles. The points are printed in the terminal.
So basically what I'm trying to do is to get the array leftClicks[4] out of the function for further processing in other functions, eg. where the ****** are.
The problem is that the type of function onMouse has to be void and the arguments can't be changed.
I noticed that the function setMouseCallback has pretty specified what onMouse function has to be and no changes to the function onMouse could be made...
So how can I get the array leftClicks[] out of the function onMouse?

How to draw a line on frame from live video feed in opencv c++

I want to draw a line on opencv frame in c++. For this I have below code where I am using setMouseCallback(winName, onMouse, NULL);. In below code I am using image:
Mat src;
const char* winName = "Image";
int start_x = 0;
int start_y = 0;
bool run_once = false;
void onMouse(int event, int x, int y, int f, void*)
{
if (cv::EVENT_LBUTTONDOWN)
{
if (f == 1)
{
if (run_once == false)
{
start_x = x;
start_y = y;
cout << "start x,y is : " << x << y << endl;
run_once = true;
}
}
if (f == 3)
{
cout << "start x,y is : " << start_x << start_y << endl;
int end_x = x;
int end_y = y;
cout << "end x,y is : " << end_x << end_y << endl;
cv::line(src, cv::Point(start_x, start_y), cv::Point(end_x, end_y), Scalar(255), 2, 8, 0);
imshow(winName, src);
run_once = false;
}
}
}
int main()
{
src = imread(<img path>, 1);
namedWindow(winName, WINDOW_NORMAL);
setMouseCallback(winName, onMouse, NULL);
imshow(winName, src);
while(1)
{
}
}
Using above code, if I left click using mouse on frame, it records start_x start_y. I drag my mouse to left/right and then right click and it records end_x end_y and simply draws a line and display it. This works fine but I want to achieve this functionality in live video frame.
The issue which I am facing in live video frame is that, in live video feed we are constantly displaying the frame in while(1) loop thus the line which is drawn is removed in next frame
void onMouse(int event, int x, int y, int f, void*)
{
/*
* SAME AS ABOVE
*/
}
int main(int, char**)
{
VideoCapture cap(1); // open the default camera
if (!cap.isOpened()) // check if we succeeded
return -1;
namedWindow(winName, WINDOW_NORMAL);
setMouseCallback(winName, onMouse, NULL);
for (;;)
{
cap >> src; // get a new frame from camera
imshow(winName, src);
if (waitKey(30) >= 0) break;
}
// the camera will be deinitialized automatically in VideoCapture destructor
return 0;
}
In above code, we have onMouse function where we are using imshow to show the line drawn on frame but we also have imshow in while(1) loop thus the drawn line is not shown.
Is there anyway I can draw line on live video feed frame. Please help. Thanks
As #Sunreef suggested, you can create separate cv::Mat to keep picture with lines only and display src combined with this picture
// #0 NEW - add declaration of lines here so this Mat is visible in both onMouse and main scope
cv::Mat src, lines;
void onMouse(int event, int x, int y, int f, void*)
{
if (f == 3)
{
cout << "start x,y is : " << start_x << start_y << endl;
int end_x = x;
int end_y = y;
cout << "end x,y is : " << end_x << end_y << endl;
// #1 NEW - draw line into lines instead of src
cv::line(lines, cv::Point(start_x, start_y), cv::Point(end_x, end_y), Scalar(255), 2, 8, 0);
// #2 NEW - remove unnecessary imshow here
run_once = false;
}
}
int main(int, char**)
{
for (;;)
{
cap >> src;
// #3 NEW - init lines once, to be the same size, same type as src filled with zeros
if(lines.empty()) lines = cv::Mat::zeros(src.size(), src.type());
// #4 NEW - show lines combined with lines
imshow(winName, lines + src);
if (waitKey(30) >= 0) break;
}
return 0;
}
This way only lines image is updated in onMouse (#1). No need to show it in onMouse event (#2) since it will be displayed anyway in the main loop (#4). But before you actualy display lines you overlay (add) them to the src image. The only tricky part is to initialize lines as soon as you know the size and type of src (#3). And remember to declare the lines first (#0) so that the image is visible globally, just like src.
I also suggest getting familiar with:
cv::add - this is basically what operator+ does
cv::Mat::type
cv::Mat::zeros
cv::Mat::empty
cv::line

Create multiple ROI's using mouse click

My goal is to create a vector<Rect>, right now I'm starting with only a single Rect, containing any number of ROI's that the user creates within a frame, initially captured from the videocamera. The ROI is created by grabbing points when the mouse is pressed and lifted up.
I know that the code to populate the ROI is working in onMouse using commented out cout. If I try to print out the ROI in the while loop of the setup function it is empty. Did I mess up the pointers or is it something else?
using namespace std;
using namespace cv;
//Global Variables
Point P1(0,0);
Point P2(0,0);
bool complete = false;
struct MouseParams
{
Mat img;
Point pt;
int lucky;
};
void onMouse ( int event, int x, int y, int d, void *ptr ){
switch(event){
//mouse pressed (get coordinate of first point)
case CV_EVENT_LBUTTONDOWN:
cout << "press" << endl;
P1.x=x;
P1.y=y;
break;
//mouse lifted (get coordinate of final point)
case CV_EVENT_LBUTTONUP:
cout << "lift" << endl;
P2.x=x;
P2.y=y;
complete = true;
break;
default:
break;
}
//If lifted mosue and have two valid points()
if(complete && P1 != P2){
cout << "Building ROI" << endl;
Rect*ROI = (Rect*)ptr;
int w,h;
h= P2.y-P1.y;
w = P2.x-P1.x;
ROI->x=x;
ROI->y=y;
ROI->width=w;
ROI->height=h;
cout << ROI << endl;
complete = false;
}
}
Rect setup(Mat frame){
Rect ROI;
while(true){
namedWindow("ROI");
setMouseCallback("ROI",onMouse, &ROI);
cout << ROI << endl;
//draw ROI grabbed in onMouse
rectangle( frame, ROI, Scalar(0,255,0), 1, 8, 0 );
imshow("ROI",frame);
moveWindow("ROI", 0,50);
waitKey(0);
return ROI;
}
}
UPDATE: Modify the waitkey() as it was holding up the while loop waiting for an input to move forward. Now code works for retaining the latest ROI (can draw many in setup frame) but need to adapt to retain vector but feel like that is hard to do using pointers.
key = (char)waitKey(0); // explicit cast
if (key == 27) break; // break if `esc' key was pressed.
if (key == ' ') continue;;
}
destroyAllWindows();
return ROI;

Invalid use of non-static member function in header file

I am fairly new to C/C++ and have the following problem. As part of an exectuable, I want to draw some rectangles on a picture using OpenCV. For this, I have defined a separate header file to keep the .cpp executable as short as possible. It looks like this:
typedef struct Rectangle {
cv::Point startPoint;
cv::Point endPoint;
};
class drawSpaces {
private:
Mat img;
int ix = 1;
int iy = 1;
std::list<Rectangle> rectList;
public:
//mouse callback function
void drawRect(int event, int x, int y, int, void *param) {
if (event == CV_EVENT_LBUTTONDOWN) {
//Save first point of rect
ix = x;
iy = y;
} else if (event == CV_EVENT_LBUTTONUP) {
//Save 2nd point of rect
cv::rectangle(img, Point(ix, iy), Point(x, y), cv::Scalar(0, 255, 0));
Rectangle rect;
rect.startPoint = Point(ix, iy);
rect.endPoint = Point(x, y);
rectList.push_back(rect);
}
}
}
int draw(Mat image) {
img = image;
if (img.empty()) {
cout << "\nerror reading image" << endl;
return -1;
}
namedWindow("Image", 1);
imshow("Image", img);
setMouseCallback("Image", drawRect);
while (waitKey(20) != 27) // wait until ESC is pressed
{
imshow("Image", img);
}
//save image with rectangles
imwrite( "../pics/new_Image.jpg", img );
return 0;
}
};
I now want to create an object of class drawSpaces and a Mat image in my main and run draw on it to get the new image. However, upon building, I get the Error Message
error: invalid use of non-static member function setMouseCallback("Image", drawRect);
with the compiler pointing at the drawRect funtion.
I have looked at other answers at this question and the majority seem to suggest to change drawRect to static. But I don't want my drawRect function to have the functionality of a static function, i.e. being able to be called without an actual drawSpaces object being present.
Any help, also on the style of coding, is appreciated!
EDIT: Using
setMouseCallback("Image", drawSpaces::drawRect);
does not help either.
Env: g++5.4(c++11) + OpenCV 3.3
The callback function of setMouseCallback("Image", callback); should always be static. So you should change the function to:
static void drawRect(int event, int x, int y, int, void *param)
Then you also should modify you class definition (some knowledge related to C++, you should learn by yourself) to compile it successfully.
My result:
//! 2017.12.22 23:13:58 CST
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
struct Rectangle {
cv::Point startPoint;
cv::Point endPoint;
};
class drawSpaces {
private:
static Mat img;
static int ix;
static int iy;
static std::list<Rectangle> rectList;
public:
static void drawRect(int event, int x, int y, int, void *param) {
if (event == CV_EVENT_LBUTTONDOWN) {
//Save first point of rect
ix = x;
iy = y;
} else if (event == CV_EVENT_LBUTTONUP) {
//Save 2nd point of rect
cv::rectangle(img, Point(ix, iy), Point(x, y), cv::Scalar(0, 255, 0), 1, LINE_AA);
Rectangle rect;
rect.startPoint = Point(ix, iy);
rect.endPoint = Point(x, y);
rectList.push_back(rect);
}
}
int draw(Mat image) {
img = image;
if (img.empty()) {
cout << "\nerror reading image" << endl;
return -1;
}
namedWindow("Image", 1);
imshow("Image", img);
setMouseCallback("Image", drawRect);
while (waitKey(20) != 27) {
imshow("Image", img);
}
//save image with rectangles
imwrite( "new_Image.jpg", img );
return 0;
}
};
int drawSpaces::ix =0;
int drawSpaces::iy =0;
Mat drawSpaces::img = Mat() ;
std::list<Rectangle> drawSpaces::rectList;
int main(){
Mat img = imread("test.png");
drawSpaces obj;
obj.draw(img);
}
You are trying to pass a pointer to a member function as an argument. Non-static member functions need a instance to be called on or another signature.
This has already been discussed here:
Passing a member function as an argument in C++