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.
Related
My code works fine but it is not continuously pass the co-ordinates when the left button of mouse is still pressed. It does pass the co-ordinates when mouse is moving.
Sorry i didn't add the mouse_callback function, I now have added that.
Any help would be helpful.
code:
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Point pt(-1, -1);
bool newCoords = false;
void mouse_callback(int event, int x, int y, int flag, void *param)
{
if (event == EVENT_LBUTTONDOWN)
{
pt.x = x;
pt.y = y;
newCoords = true;
}
else if (flag == EVENT_FLAG_LBUTTON && event == EVENT_MOUSEMOVE) {
pt.x = x;
pt.y = y;
newCoords = true;
}
}
int main(int argc, char** argv)
{
String WindowName = "Original Feed";
namedWindow(WindowName, CV_WINDOW_AUTOSIZE);
Mat oriImg;
int Frame_Width = 720;
int Frame_Height = 540;
VideoCapture cap(0);
setMouseCallback(WindowName, mouse_callback);
cap.set(CV_CAP_PROP_FRAME_WIDTH, Frame_Width);
cap.set(CV_CAP_PROP_FRAME_HEIGHT, Frame_Height);
while (waitKey(30) != 27) {
cap.read(oriImg);
if (pt.x != -1 && pt.y != -1)
{
circle(oriImg, pt, 3, Scalar(0, 0, 255));
if (newCoords)
{
std::cout << "Clicked coordinates: " << pt << std::endl;
newCoords = false;
}
}
imshow(WindowName, oriImg);
}
return 0;
}
The callback is called whenever the OS/GUI generates a mouse event. Such events are generated whenever the state of the mouse (button, wheel, position, etc.) change -- if nothing changes, you already know (are can know) the current state.
If you want to somehow respond to a stable state of the mouse (e.g. button held down while stationary) in multiple iterations, you will have to handle it yourself.
The way I understand your problem is as follows: As long as the left button is held, draw the circle at the most recent position of the cursor on every frame displayed.
Since we're running at a decent frame rate, we can afford to keep things simple, and only update the current frame based on the click/position information from the previous frame. The user will not be able to observe this slight delay.
Just for posterity: the image is being displayed, and the mouse callback called only while the cv::waitKey function is running.
The following enumerates the situations we may encounter in each iteration and how we should react to them in the following frame:
LMB was not held at entry and exit of waitKey
LMB was never released (therefore never pressed) -> nothing to do
LMB was held and released at least once (and therefore pressed as many times) -> draw circle at position of last depress
LMB was not held at entry, but held at exit of waitKey
LMB is held down -> draw circle at last reported mouse position
LMB was held at entry, but not held at exit of waitKey
LMB was held and released at least once (and pressed one less time) -> draw circle at position of last depress
LMB was held at entry and exit of waitKey
LMB is held down -> draw circle at last reported mouse position
Based on this, we need to keep track of:
The last position where the LMB was either held down, or released.
Whether the LMB is currently being held down
Whether the LMB was released in the previous iteration (i.e. it was held down, even though it no longer is)
We can use a struct to hold this info (which we let the callback use thanks to the user data parameter):
struct mouse_state
{
mouse_state()
: position(-1, -1)
, left_button_held(false)
, left_button_clicked(false)
{}
void new_iteration()
{
left_button_clicked = false;
}
cv::Point2i position; // Last position where the LMB was down
bool left_button_held; // Is the LMB down right now?
bool left_button_clicked; // Was the LMB down in the last iteration?
};
The mouse callback will be responsible for updating the above struct.
When user presses LMB, record position and set button held
When user releases LMB, record position, set button clicked, and set button not held
When mouse moves and LMB held, record position
Sample implementation of the callback with some primitive debug tracing could look as such:
void mouse_callback(int event, int x, int y, int flag, void* param)
{
mouse_state* state(static_cast<mouse_state*>(param));
if (event == cv::EVENT_LBUTTONDOWN) {
std::cout << "LMB down # (" << x << "," << y << ")\n";
state->position = cv::Point2i(x, y);
state->left_button_held = true;
} else if (event == cv::EVENT_LBUTTONUP) {
std::cout << "LMB up #(" << x << "," << y << ")\n";
state->position = cv::Point2i(x, y);
state->left_button_held = false;
state->left_button_clicked = true;
} else if ((flag == cv::EVENT_FLAG_LBUTTON) && (event == cv::EVENT_MOUSEMOVE)) {
std::cout << "LMB held, mouse moved to (" << x << "," << y << ")\n";
state->position = cv::Point2i(x, y);
}
}
The callback then could be registered in the following manner:
mouse_state ms;
cv::setMouseCallback(WINDOW_NAME, mouse_callback, &ms);
The processing loop would then be quite simple:
Read new frame, exit loop on failure
Check last mouse state. If LMB is held, or was clicked, draw circle at recorded position.
Reset per-iteration mouse state flags
Show image, update mouse state info
This might look as follows:
for (;;) {
// We can have `image` here, since `cap.read` always gives us
// a reference to its internal buffer
cv::Mat image;
if (!cap.read(image)) {
std::cerr << "Failed to read image, exiting...\n";
break; // Failed to read image, nothing else to do
}
if (ms.left_button_clicked || ms.left_button_held) {
std::cout << "Current position: " << ms.position << "\n";
cv::circle(image, ms.position, 3, cv::Scalar(0, 0, 255));
cv::circle(image, ms.position, 10, cv::Scalar(0, 0, 255), 2);
}
ms.new_iteration();
cv::imshow(WINDOW_NAME, image);
if (cv::waitKey(30) == 27) {
std::cout << "Esc pressed, exiting...\n";
break;
}
}
Sample Program
#include <opencv2/opencv.hpp>
#include <iostream>
struct mouse_state
{
mouse_state()
: position(-1, -1)
, left_button_held(false)
, left_button_clicked(false)
{}
void new_iteration()
{
left_button_clicked = false;
}
cv::Point2i position; // Last position where the LMB was down
bool left_button_held; // Is the LMB down right now?
bool left_button_clicked; // Was the LMB down in the last iteration?
};
void mouse_callback(int event, int x, int y, int flag, void* param)
{
mouse_state* state(static_cast<mouse_state*>(param));
if (event == cv::EVENT_LBUTTONDOWN) {
std::cout << "LMB down # (" << x << "," << y << ")\n";
state->position = cv::Point2i(x, y);
state->left_button_held = true;
} else if (event == cv::EVENT_LBUTTONUP) {
std::cout << "LMB up #(" << x << "," << y << ")\n";
state->position = cv::Point2i(x, y);
state->left_button_held = false;
state->left_button_clicked = true;
} else if ((flag == cv::EVENT_FLAG_LBUTTON) && (event == cv::EVENT_MOUSEMOVE)) {
std::cout << "LMB held, mouse moved to (" << x << "," << y << ")\n";
state->position = cv::Point2i(x, y);
}
}
int main(int argc, char** argv)
{
cv::String const WINDOW_NAME("Original Feed");
cv::namedWindow(WINDOW_NAME, CV_WINDOW_AUTOSIZE);
mouse_state ms;
cv::setMouseCallback(WINDOW_NAME, mouse_callback, &ms);
int const FRAME_WIDTH(720);
int const FRAME_HEIGHT(540);
cv::VideoCapture cap(0);
if (!cap.isOpened()) {
std::cerr << "Unable to open camera, exiting...\n";
return -1;
}
cap.set(CV_CAP_PROP_FRAME_WIDTH, FRAME_WIDTH);
cap.set(CV_CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT);
for (;;) {
// We can have `image` here, since `cap.read` always gives us
// a reference to its internal buffer
cv::Mat image;
if (!cap.read(image)) {
std::cerr << "Failed to read image, exiting...\n";
break; // Failed to read image, nothing else to do
}
if (ms.left_button_clicked || ms.left_button_held) {
std::cout << "Current position: " << ms.position << "\n";
cv::circle(image, ms.position, 3, cv::Scalar(0, 0, 255));
cv::circle(image, ms.position, 10, cv::Scalar(0, 0, 255), 2);
}
ms.new_iteration();
cv::imshow(WINDOW_NAME, image);
if (cv::waitKey(30) == 27) {
std::cout << "Esc pressed, exiting...\n";
break;
}
}
return 0;
}
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;
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.
I am using openCV 2.4.9. I open camera using OpenCV run camera in a new window. I want to change colors of camera feed whith a key press. For example, when I click '1' camera feed change to gray scale, '2' -> black and white, '3' -> HSV, and when I press 'ESC' return(0). This what I've came up so far:
#include <iostream>
#include <conio.h>
using namespace std;
#include<opencv\cv.h>
#include<opencv\highgui.h>
#include "opencv2\core\core.hpp"
#include "opencv2\imgproc\imgproc.hpp"
void main(){
CvCapture *capture = cvCaptureFromCAM(CV_CAP_ANY);
IplImage *frame = 0, *image = 0;
int key = 0, last = 0;
cvNamedWindow("WebCamera", CV_WINDOW_AUTOSIZE);
while(key != 27) {
frame = cvQueryFrame(capture);
image = cvCloneImage(frame);
// i try to use swich and case for this but i can't get it work
// when using cvtColor need to use Mat image but when use cvShowImage need IplImage
// switch(last)
// {
// case '1':
// cvtColor(image,HSVimage,CV_BGR2HSV);
// case '2':
// cvtColor(image,HSVimage,CV_BGR2GRAY);
// case '3':
// .
// .
// default: break;
// }
cvShowImage("WebCamera", image);
cvReleaseImage(&image);
key = cvWaitKey(1);
if (key != -1) last = key;
}
cvDestroyWindow("WebCamera");
cvReleaseCapture(&capture);
exit(0);
}
I want change colors again and again in same window or (if it is not possible) open and close windows for each color filter. Thank You. Sorry for bad English
It should work with the code below. Got it from this OpenCV tutorial and from the OpenCV documentation.
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
int key = 0, last = 0;
VideoCapture cap(0); // open the default camera
if(!cap.isOpened()) // check if we camera is opened
{
cout << "Cannot open selected camera" << endl;
return -1;
}
namedWindow("Capture",1);
Mat convertedImage;
for(;;) //Loop until user hit "esc"
{
Mat frame;
cap >> frame; // get a new frame from camera
switch(last)
{
case '1':
{
cvtColor(frame,convertedImage,CV_BGR2GRAY);
break;
}
case '2': //Binarization to generate Black/White image
{
Mat img_gray;
cvtColor(frame,img_gray,CV_BGR2GRAY); //First convert to gray
//Binarization. Use your parameters here or try adaptiveThreshold
threshold(img_gray, convertedIamge, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
}
case '3':
{
cvtColor(frame,convertedImage,CV_BGR2HSV);
break;
}
default: //use to prevent ecxeption at program start or use case '0' to show original image
{
convertedImage = frame;
}
}
imshow("Capture", convertedImage); //show converted image
key = waitKey(1);
if (key != -1)
last = key;
if(key == 27)
break;
// the camera will be deinitialized automatically in VideoCapture destructor
}
return 0;
}
I not checked this(compileted).. But I think it can help you.
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
VideoCapture stream1(0);
if (!stream1.isOpened()) { cout << "cannot open camera"; }
while (true) {
Mat cameraFrame;
stream1.read(cameraFrame);
switch(last)
{
case '1':
cvtColor(image,HSVimage,CV_BGR2HSV);
break;
case '2':
cvtColor(image,HSVimage,CV_BGR2GRAY);
break;
case '3':
...}
imshow("cam", cameraFrame);
key = cvWaitKey(1);
if (key != -1) last = key;
}
return 0;
}
I am using the setMouseCallBack function to extract pixel coordinate. It can work if the for-loop change to while(1).
Now, I would like to run and record the pixel coordinate value only for 24 times. But, the for-loop doesn't work. How should I do by using setMouseCallBack function?
Thanks!
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace cv;
void mouse_call(int event, int x, int y, int flag, void *param)
{
if (event == EVENT_LBUTTONDOWN)
{
Point *p = (Point*)param;
p->x = x;
p->y = y;
}
if (event == EVENT_LBUTTONUP)
{
Point *p = (Point*)param;
p->x = x;
p->y = y;
}
}
int main(int argc, char** argv)
{
static Point p;
int cor[24][2] = {0};
string filename;
cout << "Filename: ";
cin >> filename;
img = imread(filename);
resize(img, img, Size(), 0.5, 0.5, 1);
namedWindow("Image");
imshow("Image", img);
for(int i = 0; i < 24; i++)
{
setMouseCallback("Image", mouse_call);
cor[i][0] = p.x
cor[i][1] = p.y
}
waitKey(0);
return(0);
}
You need to set the callback only once. And you need to tell which variable you're passing as param.
I modified a little your code in order to keep it simple:
the coordinates are stored in a vector<Point>
the vector of coordinates is a global variable. This make a toy program like this easier. For real application, it will probably be a class member.
I pass a status flag as parameter of the callback, and it turns true when I reached the required number of points
I added a loop that waits for the given number of points to be collected
I collect a point only on mouse down event.
Here the code:
#include <opencv2\opencv.hpp>
#include <vector>
#include <iostream>
using namespace cv;
using namespace std;
vector<Point> coords;
int N = 3;
void mouse_call(int event, int x, int y, int flag, void *param)
{
if (event == EVENT_LBUTTONDOWN)
{
coords.push_back(Point(x,y));
// Debug
copy(coords.begin(), coords.end(), ostream_iterator<Point>(cout, " "));
cout << endl;
if (coords.size() == N)
{
bool* exitflag = static_cast<bool*>(param);
*exitflag = true;
}
}
}
int main()
{
bool bExit = false;
string filename;
cout << "Filename: ";
cin >> filename;
Mat3b img = imread(filename);
resize(img, img, Size(), 0.5, 0.5, 1);
namedWindow("Image");
// Set callback
setMouseCallback("Image", mouse_call, static_cast<void*>(&bExit));
imshow("Image", img);
while (!bExit)
{
waitKey(30);
}
cout << "Found " << N << " points... Exit" << endl;
return(0);
}
Altough your question is a bit unspecific I think your actual problem is quite simple.
Your for loop does work, but you have to keep in mind that there are a lot more mouse events.
Your mouse_call handler also gets spammed with movements events which are neglected by your handler. And since the loop only runs 24 times it has no actual chance to capture a button down event before it's finished.