今天明白看一件事情,在编程的学习过程中,每一行的代码最好都是自己敲,遇见一个不懂的地方就去查一下。怪不得那些大牛都说想学编程,总是复制粘贴是绝对不行的。因为你在敲每一个函数,每一个变量的时候都能发现新的问题。学习的其实不是某一个具体的函数,学的是在敲代码的过程中找到很多新的东西。
好了那先说一下这次想要完成的任务:在一个窗口中,点击鼠标左键,确定一个起始位置;拖动鼠标到适当的位置;释放鼠标,随之画出一个随机颜色的矩形框。
先把结果和代码贴出来吧。
#include <opencv2/opencv.hpp>
using namespace cv;
#define WINDOW_NAME "【程序窗口】"
//-------------------全局函数声明部分-------------------
void on_MouseHandle(int event, int x, int y, int flags, void* param);//这就是鼠标操作的回调函数
void DrawRectangle(Mat& img, Rect box);//自定义的用来话矩形,并且颜色随机的函数
void ShowHelpText();//额。。。这个暂时没用到,后续会说到的
//-------------------全局变量声明部分-------------------
Rect g_rectangle;//Rect类和Mat类相似,是opencv定义好的类
bool g_bDrawingBox = false;//是否进行绘制
RNG g_rng(12345);//一个伪随机序列产生器(种子是12345),这个随机序列产生器的名字是g_rng
//-------------------main()函数--------------------------
int main(int argc, char*argv)
{
//【1】准备参数
g_rectangle = Rect(-1, -1, 0, 0);
Mat srcImage(600, 800, CV_8UC3), tempImage;
srcImage.copyTo(tempImage);
g_rectangle = Rect(-1, -1, 0, 0);
srcImage = Scalar::all(0);
//【2】设置鼠标操作回调函数
namedWindow(WINDOW_NAME);
setMouseCallback(WINDOW_NAME, on_MouseHandle, (void*)&srcImage);
//【3】程序主循环,当绘制的标识符为真时,进行绘制
while (1)
{
srcImage.copyTo(tempImage);//复制源图到临时变量
if (g_bDrawingBox)
DrawRectangle(tempImage, g_rectangle);//当进行绘制的标识符为真时进行绘制
imshow(WINDOW_NAME, tempImage);
if (waitKey(10) == 27)
break;//按下ESC键,程序退出
}
return 0;
}
//------------------------【on_MouseHandle()函数】-----------------------
// 描述:鼠标回调函数,根据不同的鼠标事件进行不同的操作
//-----------------------------------------------------------------------
void on_MouseHandle(int event, int x, int y, int flags, void* param)
{
Mat& image = *(cv::Mat*) param;
switch (event)
{
//左键按下消息
case EVENT_LBUTTONDOWN:
{
g_bDrawingBox = true;
g_rectangle = Rect(x, y, 0, 0);//记录起始点
}
break;
//鼠标移动消息
case EVENT_MOUSEMOVE:
{
if (g_bDrawingBox)//如果是否进行绘制的标识符为真,则记录下长和宽到RECT型变量中
{
g_rectangle.width = x - g_rectangle.x;
g_rectangle.height = y - g_rectangle.y;
}
}
break;
case EVENT_LBUTTONUP:
{
g_bDrawingBox = false;
//对宽和高小于0的处理
if (g_rectangle.width < 0)
{
g_rectangle.x += g_rectangle.width;
g_rectangle.width *= -1;
}
if (g_rectangle.height < 0)
{
g_rectangle.y += g_rectangle.height;
g_rectangle.height *= -1;
}
//调用函数进行绘制
DrawRectangle(image, g_rectangle);
}
break;
}
}
//-------------------------【DrawRectangle()函数】-----------------------
// 描述:自定义的矩形绘制函数
//-----------------------------------------------------------------------
void DrawRectangle(cv::Mat& img, cv::Rect box)
{
rectangle(img, box.tl(), box.br(), Scalar(g_rng.uniform(0,255), g_rng.uniform(0,255), g_rng.uniform(0,255)));
}
然后说一下自己在“敲代码”的过程中学到的东西吧。
首先,鼠标的交互式使用和上一篇讲过的轨迹条一样,它们的使用都是借助一个“中介”函数创建,然后再“中介”函数的参数里提供一个回调函数,用来说明创建一个轨迹条或者一个鼠标交互操作是想具体干什么。
在上一篇轨迹条的使用那篇文字里说了,想要“搞”一个轨迹条需要先用一个createTrackbar()函数来创建滑动条并指定回调函数。而指定鼠标操作消息的回调函数的“中介”函数是SetMouseCallback()。
SetMouseCallback()函数的原型如下:
void setMouseCallback(conststring$ winname, MouseCallback onMouse, void *userdata=0)
第一个参数,const string&类型的winname,窗口的名字。
第二个参数,MouseCallback类型的onMouse,指定窗口里每次鼠标事件发生时,被调用的(回调函数)函数的指针。这个回调函数的原型大概形式为void(int event,int x,int y, int flags, void* param )。其中event是EVENT_+变量之一,x,和y是鼠标指针在图像坐标系(不是窗口坐标系)中的坐标值。flags,是EVENT_FLAG的组合(没用到),param是用户定义的传递到SetMouseCallback()函数调用的参数。
第三个参数,void*类型的userdata,用户定义的传递到回调函数的参数,默认值为0。
上面这就是这次学到的最核心的东西了。剩下的就是我个人在敲代码的过程中遇见的一些自己不熟悉的知识内容。
这次实战练习实际上是第一个接触到的涉及坐标系问题的代码。那么就需要简单的了解一下opencv中的坐标系的关系。如果有兴趣可以看一下tornadomeet大神写的opencv坐标系的初步认识点击打开链接。我的收获就是明白了opencv的坐标系原点是建在左上角的,然后最上面的横着的线是x轴,贴着左边的竖线是y轴。另外,大牛还写了很多细节的地方,大家可以看一下。
除了坐标系问题还有就是Rect类。我个人理解这个和Mat类相似,Mat类是用来存矩阵(图片)的。那Rect类就是用来存一个矩形的。里面包含了一个矩形几乎所有的元素。而且最有趣的是这样的Rect类型的变量还可以进行运算(交、并运算)。这个类的资料可以参考CAUC康辉写的OpenCV的Rect矩形类用法点击打开链接。
补充:忘记了一个知识点:RNG。千万不要望文生义,这个可不是Royal Never Giveup。这个是随机序列生成器。(实际上是伪随机序列)。它的作用说的简单点就是由软件开发者给出一些“种子”(别想歪)。然后把这些种子给到计算机,计算机由一个固定的算法产生一些随机数。如果“种子”是固定的那么在同一个平台下产生的随机数也是一样的(所以说它是伪随机嘛!)。在这个程序里就是用给的种子(12345)来生成0-255之间的一些随机数,用来表征一些不同的颜色。