知乎上被邀请回答一个问题,关于OpenCV的鼠标操作的问题。我发现回答下来写了不少东西,可以整理为一篇文章发出来,顺便说下不少人关心的如何用操作鼠标,比如如何用鼠标在图像上画一个矩形或者说选择一个矩形的ROI。
知乎上的问题问的是下面这段代码是什么意思。
正好,这段代码我是看过的,而且就在最近两周。所以正好可以说道一下。
这一段代码我最初是在contrib模块里面tracking模块的samples里面看到的,出自roiSelector.hpp。这个文件的作用就是用鼠标在图片中选择一个矩形区域。感兴趣的读者可以到这里知道源代码。为了照顾一部分人,更加直白的说法是这段代码在下面这样的路径下:
opencv3.2\opencv_contrib-master\modules\tracking\samples
注意,这里是opencv contrib模块,不是官网下载的那个OpenCV.
显然,我们知道要用一个鼠标选择一个矩形区域,鼠标的运动可以细分为一下三个动作:
鼠标左键按下鼠标非水平非垂直地滑动鼠标左键抬起。在roiSelector.hpp的代码中,在处理EVENT_LBUTTONUP(鼠标左键抬起事件)之前,还分别先对EVENT_LBUTTONDOWN(鼠标左键按下事件)和EVENT_MOUSEMOVE(鼠标移动事件)进行了处理。 为了说明题主给出的代码的具体含义,必须先明白前两个事件,也即鼠标左键按下和鼠标滑动,都是怎么处理的。为了方便说明,这里不讨论代码中从矩形中心开始画矩形的情况,我把这三个事件的代码简化如下,为了放方便说明,调整了顺序:
PS:加一句也许是废话的话,在OpenCV中,矩形的表示方式是(x,y,width,height),也即是矩形框左上角坐标,外加宽高。而OpenCV的图像坐标系也是以图像左上角为原点,越往右x越大,越往下y越大。
代码经过了简化,但是应该已经足够说明问题。
简化后的代码如下:
switch (event) { // start to select the bounding box case cv::EVENT_LBUTTONDOWN: data->isDrawing = true; data->box = cv::Rect2d(x, y, 0, 0); break; // update the selected bounding box case cv::EVENT_MOUSEMOVE: if (data->isDrawing) { data->box.width = x - data->box.x; data->box.height = y - data->box.y; } break; // cleaning up the selected bounding box case cv::EVENT_LBUTTONUP: data->isDrawing = false; 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; }这里先说第一个case,也即第一个动作,鼠标左键按下事件:EVENT_LBUTTONDOWN。
// 若鼠标左键按下,则矩形初始化为以鼠标当时坐标为左上角坐标,宽高都为0的矩形。 // 且开始画flag为真,左键不按下滑动鼠标则不会开始画。 case cv::EVENT_LBUTTONDOWN: data->isDrawing = true; data->box = cv::Rect2d(x, y, 0, 0); break;第二个case,也即第二个动作,鼠标滑动事件:EVENT_MOUSEMOVE。
// 如果鼠标开始滑动,更新矩形的宽高。 // 用滑动时鼠标所在的坐标x减去初始的x为矩形的宽度。 // 坐标y减去矩形初始的y为矩形的宽。 // 比如鼠标往左水平移动了5个像素,那么宽为5px。 // 鼠标垂直向下移动了10个像素,那么矩形高为10px。 case cv::EVENT_MOUSEMOVE: if (data->isDrawing) { data->box.width = x - data->box.x; data->box.height = y - data->box.y; } break;