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++
Related
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?
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.
So, after following the advise from the stackexchange users about mouse event, I was able to understand and implement some simple task using mouse clicks. So, the next goal was to draw a simple line using the mouse left click and mouse right click. Unfortunately, I can't see any line after I implemented my program.
int x,y;
Point p(0,0);
Point q(0,0);
Mat xal;
void drawimage()
{
a = q.x - p.x; //i am just checking out the values of a and b to see if the drawimagefunction is being called in the rightmouse click event
b = q.y - p.y;
cout<<" a is :"<<a<<endl;
cout<<"b is:"<<b<<endl;
line(xal,Point(p.x,p.y),Point(q.x,q.y),Scalar(0,0,255),2,8);
}
void onMouse( int event, int x, int y, int f, void* )
{
switch (event)
{
case EVENT_LBUTTONDOWN:
cout<<"Left button was pressed"<<x<<" "<<y<<" "<<endl;
{
p.x = x;
p.y = y;
cout<<"p is:"<<p.x<<p.y<<endl;
}
break;
case EVENT_RBUTTONDOWN:
cout<<"Right button was pressed at :"<<x <<" "<<y<<endl;
{
q.x = x;
q.y = y;
drawimage();//no line is being drawn though i can see that i get the values of a and b in the drawimage function.
}
break;
default:
break;
}
}
int main()
{
xal = imread("pic.JPG);
namedWindow("Picture",1);
setMouseCallback("Picture",onMouse,NULL);
imshow("Picture",xal);
cvwaitkey(0);
}
Add the following after your "line(..)" call in your drawLine() function:
imshow("Picture", xal);
The problem is that you are writing the line to the xal matrix, but you have not updated the image on the screen, which is what the imshow(..) call will do.
Try this one code. It is useful for you.
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace std;
using namespace cv;
void drawimage()
{
line(xal,Point(p->x,p->y),Point(q->x,q->y),Scalar(0,0,255),2,8);
}
void CallBackFunc(int event, int x, int y, int flags, void *ptr )
{
if ( event == EVENT_LBUTTONDOWN )
{
Point*p = (Point*)ptr;
p->x = x;
p->y = y;
drawimage();
}
else if ( event == EVENT_RBUTTONDOWN )
{
Point*q = (Point*)ptr;
q->x = x;
q->y = y;
drawimage();
}
}
int main(int argc, char** argv)
{
// Read image from file
Point p;
Point q;
Mat xal = imread("MyPic.JPG");
//if fail to read the image
if ( xal.empty() )
{
cout << "Error loading the image" << endl;
return -1;
}
//Create a window
namedWindow("My Window", 1);
//set the callback function for any mouse event
setMouseCallback("My Window", CallBackFunc,&p);
setMouseCallback("My Window", CallBackFunc,&q);
//show the image
imshow("My Window", xal);
// Wait until user press some key
waitKey(0);
return 0;
}
The following program would give the location of left mouse button click.
void onMouse(int evt, int x, int y, int flags, void* param) {
if(evt == CV_EVENT_LBUTTONDOWN) {
cv::Point* ptPtr = (cv::Point*)param;
ptPtr->x = x;
ptPtr->y = y;
}
}
int main() {
cv::Point2i pt(-1,-1);//assume initial point
cv::namedWindow("Output Window");
Mat frame = cv::imread("chhhha.png");
cv::setMouseCallback("Output Window", onMouse, (void*)&pt);
int X, Y;
while(1)
{
cv::imshow("Output Window", frame);
X=pt.x;
Y=pt.y;
cout<<"X and Y coordinates are given below"<<endl;
cout<<X<<'\t'<<Y<<endl;
waitKey(10);
}
getch();
}
I want to draw a line connecting the two points user clicks. I know line can be drawn by the function:
C++: void line(Mat& img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType=8, int shift=0)
But the problem is I have to provide two points to this function, but my previous point is lost as can be seen in the following code:
void onMouse(int evt, int x, int y, int flags, void* param) {
if(evt == CV_EVENT_LBUTTONDOWN) {
cv::Point* ptPtr = (cv::Point*)param;
ptPtr->x = x;
ptPtr->y = y;
}
}
int main() {
cv::Point2i pt(-1,-1);
cv::namedWindow("Output Window");
Mat frame = cv::imread("chhhha.png");
cv::setMouseCallback("Output Window", onMouse, (void*)&pt);
int X, Y;
while(1)
{
cv::imshow("Output Window", frame);
X=pt.x;
Y=pt.y;
cout<<"X and Y coordinates are given below"<<endl;
cout<<X<<'\t'<<Y<<endl;
line(frame, pt1, pt2, 'r', 1, 8, 0); //here I am having only one point. This is the issue
waitKey(10);
}
getch();
}
Edit
So is there anyway to store the coordinates of the point clicked by user. So let's say user clicks two points on the image, and we would store x coordinates of the two clicks in X[0] and X[1], and similarly Y[0] and Y[1] for y coordinates.
Then I can easily use the function for drawing a line. Kindly help me proceed in this direction.
Thanks in advance for your suggestions.
My latest code
using namespace cv;
using namespace std;
void onMouse(int evt, int x, int y, int flags, void* param) {
if(evt == CV_EVENT_LBUTTONDOWN) {
std::vector<cv::Point>* ptPtr = (std::vector<cv::Point>*)param;
ptPtr->push_back(cv::Point(x,y));
}
}
int main()
{
std::vector<Point> points;
cv::namedWindow("Output Window");
Mat frame = cv::imread("chhha.png");
cv::setMouseCallback("Output Window", onMouse, (void*)&points);
int X=0, Y=0;
while(1)
{
cv::imshow("Output Window", frame);
X=points[0].x;
Y=points[0].y;
cout<<"First X and Y coordinates are given below"<<endl;
cout<<X<<'\t'<<Y<<endl;
waitKey(10);
}
getch();
}
This has two major issues:
1- This would compile well, but during run time it gives Debug Assertion Failed! error,
at the following lines when I debugged by putting breakpoints:
X=points[0].x;
Y=points[0].y;
It further says:
Expression: Vector subscript out of range
2- How do I come out of the while loop?
In other similar programs I noticed that it remains in while loop forever.
I suggest that instead of passing to a cv::Point*, pass a std::vector<cv::Point>*. Since cv::Point has a copy constructor, you can store points in there by push_back.
The code as I mean it:
std::vector<cv:Point> points;
cv::namedWindow("Output Window");
Mat frame = cv::imread("chhhha.png");
cv::setMouseCallback("Output Window", onMouse, (void*)&points);
int X, Y;
while(1)
{
cv::imshow("Output Window", frame);
if (points.size() > 2) //we have 2 points
{
for (auto it = points.begin(); it != points.end(); ++it)
{
cout<<"X and Y coordinates are given below"<<endl;
cout<<(*it).x<<'\t'<<(*it).y<<endl;
}
//draw points
}
...
and in the callback:
void onMouse(int evt, int x, int y, int flags, void* param) {
if(evt == CV_EVENT_LBUTTONDOWN) {
std::vector<cv::Point>* ptPtr = (std::vector<cv::Point>*)param;
ptPtr->push_back(cv::Point(x,y));
}
}
Later on, you can access the first 2 points with points[0] and points[1].
EDIT: updated the code with a check of the vector size. You should note that this method might require locking, if the mouse callback is executed in a different thread.
I have this code that basically does a "dumb" background subtraction on two frames.
void FrameDifferenceBGS::operator()(cv::InputArray _image, cv::OutputArray _fgmask, double learningRate)
{
cv::Mat img_input = _image.getMat();
if(img_input.empty())
return;
_fgmask.create(img_input.size(), CV_8U);
cv::Mat img_foreground = _fgmask.getMat();
if(img_input_prev.empty())
{
img_input.copyTo(img_input_prev);
return;
}
cv::absdiff(img_input_prev, img_input, img_foreground);
if(img_foreground.channels() == 3)
cv::cvtColor(img_foreground, img_foreground, CV_BGR2GRAY);
if(enableThreshold)
cv::threshold(img_foreground, img_foreground, threshold, 255, cv::THRESH_BINARY);
if(showOutput)
cv::imshow("Frame Difference", img_foreground);
img_input.copyTo(img_input_prev);
img_foreground.copyTo(_fgmask);
firstTime = false;
}
If I don't add img_foreground.copyTo(_fgmask) in the end, the output array isn't updated with the result of img_foreground, resulting on a black image when this is called.
What am I doing wrong / should be doing here?
I reviewed your code again. It looks like you are creating new object for _fgmask.
_fgmask.create(img_input.size(), CV_8U);
I think this is why you have the problem. Because of this reference in the argument is different from the one after this statement. Why don't you call the line before calling the function.
fix
change _fgmask.create(img_input.size(), CV_8U); to _fgmask.create(img_input.size(), CV_8UC3); or _fgmask.create(img_input.size(), img_input.type());
why
this is because cv::absdiff(img_input_prev, img_input, img_foreground); recreate a new array everytime internally. and it does update the img_foreground structure but after the allocation, the memory address data inside _fgmask fail to change since the headers are passed by value.
you can seemlingly fix this(but still incurs creation cost) by doing cv::Mat& img_foreground = _fgmask.getMatRef();
and that is because CV_8U is not the same as CV_8UC3 and therefore the check # Mat::create() in mat.hpp always end up allocating a new array due to type difference
opinion
i think...maybe use Mat instead?
#include "opencv2/opencv.hpp"
using namespace cv;
class FrameDifferenceBGS
{
public:
Mat prev;
Mat diff;
bool enableThreshold;
bool showOutput;
bool firstTime;
uchar threshold;
FrameDifferenceBGS():firstTime(false),enableThreshold(false),showOutput(false),threshold(0)
{
}
void FrameDifferenceBGS::operator()(cv::Mat& _in, cv::Mat &_fg, double _lr)
{
if(_in.empty())
return;
if(prev.empty())
{
prev=_in.clone();
_fg=cv::Mat::zeros(_in.size(),CV_8UC1);
return;
}
cv::absdiff(prev, _in, diff);
if(diff.channels() == 3)
cv::cvtColor(diff, _fg, CV_BGR2GRAY);
else
_fg=diff;
if(enableThreshold)
cv::threshold(_fg, _fg, threshold, 255, cv::THRESH_BINARY);
if(showOutput)
cv::imshow("Frame Difference", _fg);
prev=_in.clone();
firstTime = false;
}
};
int main()
{
VideoCapture cap(0);
FrameDifferenceBGS bgs;
Mat frame,fg;
for(;;)
{
cap >> frame;
bgs(frame,fg,0);
imshow("frame", frame);
imshow("fg", fg);
if(waitKey(1) ==27) exit(0);
}
return 0;
}
edit 2(modified original)
#include "opencv2/opencv.hpp"
class FrameDifferenceBGS
{
public:
cv::Mat img_input_prev;
cv::Mat diff;
cv::Mat img_foreground;//put this in class in stead of inside the function
bool enableThreshold;
bool showOutput;
bool firstTime;
uchar threshold;
FrameDifferenceBGS():firstTime(false),enableThreshold(false),showOutput(false),threshold(0)
{
}
void FrameDifferenceBGS::operator()(cv::InputArray _image, cv::OutputArray _fgmask, double learningRate)
{
cv::Mat img_input = _image.getMat();
if(img_input.empty())
return;
if(_fgmask.empty())
_fgmask.create(img_input.size(), CV_8UC1);
if(img_input_prev.empty())
{
img_input.copyTo(img_input_prev);
return;
}
cv::absdiff(img_input_prev, img_input, img_foreground);
if(img_foreground.channels() == 3)
cv::cvtColor(img_foreground, _fgmask, CV_BGR2GRAY);
if(enableThreshold)
cv::threshold(img_foreground, img_foreground, threshold, 255, cv::THRESH_BINARY);
if(showOutput)
cv::imshow("Frame Difference", img_foreground);
img_input.copyTo(img_input_prev);
//img_foreground.copyTo(_fgmask);
firstTime = false;
}
};
int main()
{
cv::VideoCapture cap(0);
FrameDifferenceBGS bgs;
cv::Mat frame,fg;
for(;;)
{
cap >> frame;
bgs(frame,fg,0);
cv::imshow("frame", frame);
cv::imshow("fg", fg);
if(cv::waitKey(1) ==27) exit(0);
}
return 0;
}