基本思路:
(1)画线操作与视频读取工作空间分离,否则的话画线不连续
(2)读入视频的一帧,在这帧上触发鼠标事件记录当前点到单向链表中
(3)每次读入视频都画出链表中的所有点和线
(4)键盘事件,空格开始暂停录制视频,以及ESC退出程序保存视频到磁盘
废话少说,上代码,代码注释已经画了一点时间整理完善过了
代码:
//***************************************************************************
//***************************************************************************
录制视频及用户对其鼠标的画线操作,保存RecordVideo.avi
//***************************************************************************
//****************************************************************************
#include<iostream>
#include<time.h>
#include <opencv2/opencv.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp> //图像处理的库,circle()要用到
#include<opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;
struct myPoint //点p, 该点是否是一条线的起始点
{
Point p;
bool start; //true是一条线的起始点,false不是起始点
};
struct pointNode{ //链表数据节点,数据value,指针next
myPoint value;
pointNode *next;
};
struct startNode{ //链表起始节点,指针next指向下一个数据节点
pointNode *next;
};
int point_num = 0; //record_point()函数中用于确定是否是当前连续线的起始点
int heapPoint_num = 0; //保存的所有点的数量
clock_t previous = clock(), current; //record_point()函数中用于记录上一个时间和现在的时间的变量
double duration; //画两个点之间的时间差
Mat frame; //摄像头读入的一帧图像
startNode *heapPoint = new startNode; //整个链表的起始节点
pointNode *last_node = new pointNode; //链表中最后一个节点
//*********************************************
//画点函数,从链表中连续读取各点,然后画出来
//********************************************
static void draw_point()
{
pointNode *node = new pointNode; //new出当前要画的节点
Point previous_p; //保存的画直线的上一个点
node = heapPoint->next; //node初始节点被赋值为第一个有数据的链表节点
for (int i = 0; i < heapPoint_num; i++)
{
if (node->value.start == true) //如果当前点是一条连续线的起始点
{
circle(frame, node->value.p, 1, Scalar(0, 0, 255)); //画这条线的第一个点
}
else //如果当前点不是一条连续线的起始点
{
line(frame, previous_p, node->value.p, Scalar(0, 0, 255)); //在当前点与上一个点之间画出一条直线
}
previous_p = node->value.p; //将上一个点更新为当前点
node = node->next; //链表指针下移,指向下一个节点
}
}
//***********************************************
//响应函数,记录鼠标点信息
//传递过来的参数:鼠标事件,鼠标事件发生处的坐标,FLAG
//***********************************************
static void record_point(int event, int x, int y, int flags, void *)
{
if ((event == CV_EVENT_MOUSEMOVE) && (flags&CV_EVENT_FLAG_LBUTTON)) //鼠标左键按下并且光标移动
{
cout << "Mouse Event" << endl;
pointNode *node = new pointNode; //new出当前节点
if (heapPoint_num == 0) //如果这是要记录的第一个点
{
heapPoint->next = node; //就将起始指针指向当前节点
}
else //如果这不是要记录的第一个点
{
last_node->next = node; //将上一个节点的指针指向当前节点
}
Point p = Point(x, y); //当前要记录的点
current = clock(); //clock返回ms, time则返回s
duration = (double)(current - previous); //返回的是double类型的s
if (duration > 200) //如果停顿时间很长,则重新起点画线,经验阈值200
{
point_num = 0;
}
if (point_num != 0) //判断当前点是否是起点,写入当前节点标志位
{
node->value.start = false;
}
else
{
node->value.start = true;
}
node->value.p = p; //将当前点写入当前节点
node->next = NULL; //当前节点的next指针赋值为NULL,代表终节点
last_node = node; //将当前节点的地址保存一份到last_node中
previous = current; //将先前时间更新为当前时间
heapPoint_num++; //节点总数量+1
point_num++; //当前连续节点总数量+1
}
}
void main()
{
VideoCapture cap(0); //打开电脑摄像头
if (!cap.isOpened())
{
cout << "error" << endl;
waitKey(0);
return;
}
int w = static_cast<int>(cap.get(CV_CAP_PROP_FRAME_WIDTH)); //获得cap的分辨率
int h = static_cast<int>(cap.get(CV_CAP_PROP_FRAME_HEIGHT));
Size videoSize(w, h);
VideoWriter writer("RecordVideo.avi", CV_FOURCC('M', 'J', 'P', 'G'), 25, videoSize);
int key; //记录键盘按键
char startOrStop = 1; //0 开始录制视频; 1 结束录制视频
char flag = 0; //正在录制标志 0-不在录制; 1-正在录制
namedWindow("image"); //打开一个窗口,名字叫image
setMouseCallback("image", record_point); //回调record_point(),记录鼠标点的信息
while (1)
{
cap >> frame; //从摄像头读入一帧
key = waitKey(100); //读入键盘按键
draw_point(); //画点
if (key == 32) //按下空格开始录制、暂停录制 可以来回切换
{
startOrStop = 1 - startOrStop; //录制开始、录制暂停的切换标志
if (startOrStop == 0) //如果录制开始,置flag=1,表明正在录制
{
flag = 1;
}
}
if (key == 27) //按下ESC退出整个程序,保存视频文件到磁盘
{
break;
}
if (startOrStop == 0 && flag == 1) //如果接收到录制命令并且录制已经开始
{
writer << frame; //将当前帧继续写入视频
cout << "recording" << endl;
}
else if (startOrStop == 1) //如果接收到录制结束命令
{
flag = 0; //将正在录制标志flag置0,停止录制
cout << "end recording" << endl;
}
imshow("image", frame); //不断在窗口显示画线和摄像头图像
}
cap.release(); //摄像头释放
writer.release(); //视频写入器释放
destroyAllWindows(); //销毁所有打开的windows
}
*/
Tips
视频获取同时对帧操作,希望后一帧的图像不覆盖前一帧画的点和线,只有与把所有点的数据先存储起来,等读入每一帧再重新从头到尾画上去
BY LEO ON DEC8,2018, AT YUQUAN CAMPUS ZJU