Обратный вызов вызывается всякий раз, когда OS / GUI генерирует событие мыши. Такие события генерируются всякий раз, когда изменяется состояние мыши (кнопки, колеса, положения и т. Д.) - если ничего не меняется, вы уже знаете (можете знать) текущее состояние.
Если вы хотите как-то реагировать на стабильное состояние мыши (например, кнопка удерживается в неподвижном состоянии) в нескольких итерациях, вам придется справиться с этим самостоятельно.
Я понимаю вашу проблему следующим образом: Пока левая кнопка удерживается, нарисуйте кружок в самой последней позиции курсора на каждом отображаемом кадре.
Поскольку мы работаем с приемлемой частотой кадров, мы можем позволить себе упростить ситуацию и обновлять только текущий кадр на основе информации о щелчке / позиции из предыдущего кадра. Пользователь не сможет наблюдать эту небольшую задержку.
Только для потомков: изображение отображается, и обратный вызов мыши вызывается только во время работы функции cv::waitKey
.
Ниже перечислены ситуации, с которыми мы можем столкнуться на каждой итерации, и то, как мы должны реагировать на них в следующем кадре:
- ЛКМ не удерживался на входе и выходе из
waitKey
- ЛКМ никогда не отпускался (поэтому никогда не нажимался) -> ничего не делать
- ЛКМ удерживался и отпускался как минимум один раз (и поэтому нажимался столько раз) -> нарисуйте круг в положении последней нажатия
- ЛКМ не был задержан на входе, но удерживался на выходе
waitKey
- ЛКМ удерживается нажатой -> нарисуйте круг в последней сообщенной позиции мыши
- ЛКМ был задержан на входе, но не удерживался на выходе
waitKey
- ЛКМ удерживался и отпускался как минимум один раз (и нажимался на один раз меньше) -> нарисуйте круг в положении последней нажатия
- ЛКМ был задержан на входе и выходе из
waitKey
- ЛКМ удерживается -> нарисуйте круг в последней сообщенной позиции мыши
Исходя из этого, нам нужно отслеживать:
- Последняя позиция, где ЛКМ был либо удержан, либо отпущен.
- Будет ли удерживаться ЛКМ в данный момент
- Был ли LMB выпущен на предыдущей итерации (т.е. он удерживался, даже если его больше нет)
Мы можем использовать struct
для хранения этой информации (которую мы разрешаем использовать для обратного вызова благодаря параметру пользовательских данных):
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?
};
Обратный вызов мыши будет отвечать за обновление выше struct
.
- Когда пользователь нажимает ЛКМ, позиция записи и кнопка установки удерживаются
- Когда пользователь отпускает ЛКМ, позиция записи, кнопка установки нажата, и кнопка установки не удерживается
- Когда мышь перемещается и удерживается ЛКМ, позиция записи
Пример реализации обратного вызова с некоторой примитивной трассировкой отладки может выглядеть так:
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);
}
}
Обратный вызов может быть зарегистрирован следующим образом:
mouse_state ms;
cv::setMouseCallback(WINDOW_NAME, mouse_callback, &ms);
Тогда цикл обработки будет довольно простым:
- Считать новый кадр, выйти из цикла при ошибке
- Проверить последнее состояние мыши. Если удерживать ЛКМ или щелкнуть, нарисуйте круг в записанном положении.
- Сброс флагов состояния мыши для каждой итерации
- Показать изображение, обновить информацию о состоянии мыши
Это может выглядеть следующим образом:
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;
}
}
Пример программы
#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;
}