一、介绍
在现实生活中,哈哈镜是指一种表面凹凸不平的镜子,可以反应出人像及物体的扭曲面貌。
本文介绍如何设计变换函数对实时视频(从摄像头读取)进行变形,生成哈哈镜的效果。
具体的要求有:
①采用双线性插值进行图像重采样
②采用cv::VideoCapture读取摄像头视频,并进行实时处理和显示结果。
③优化代码执行效率,改善实时性。(需要打开编译优化,VS下使用release模式编译)。
④将使用哈哈镜过程录制成视频。
二、过程
1、读取摄像头视频并显示
第一次学习OpenCV视频文件读取,有很多没有学习过的函数和方法,参考了该教程。
读取摄像头视频需要使用到以下函数:
①读取视摄像头实时画面数据:
VideoCapture capture(0)
参数0默认是笔记本的摄像头;参数1表示外接摄像头。
②判断摄像头是否打开:
capture.isOpened()
判断视频读取或者摄像头调用是否成功,成功则返回true。
③将画面读取到Mat帧:
capture.read(frame)
如果没有读取帧到Mat对象,那么会返回0。在C++语法下也可以直接使用capture >> frame
④将画面停留、阻塞:
waitKey(20)
“20”表示延时为20ms,相当于1s内显示50帧的画面。
在之前实验中waitKey一般设置为waitKey()或者waitKey(0),意思都是让画面永久停留。
同时waitKey也会等待键盘输入并将当前字符的ASCII码对应的十进制值返回。
在该实验中,如果仍然设置为永久停留,那么函数将一直阻塞,相当于一直阻塞在第一帧数据中,即画面会停留在一个画面的。所以这里需要设置阻塞时间。
阻塞时间越短,那么摄像头读取的实时画面帧率也会越高,视频也会越流畅。但是帧率越高,后续哈哈镜处理的时间成本也越大,导致延迟增加。同时人所能感受到的帧率是有限的,一般动画为24帧/秒,因此这里设置20ms即可。
将上面函数组合起来使用:
Mat frame;
VideoCapture capture(0);//读取视摄像头实时画面数据,0默认是笔记本的摄像头;如果是外接摄像头,这里改为1
while (capture.isOpened())//判断摄像头是否打开
{
//读取当前帧
if (!capture.read(frame))//相当于 capture >> frame
{
cout << "摄像头断开连接或视频读取完成..." << endl;
break;
}
if (!frame.empty())//判断输入的视频帧是否为空的
imshow("window", frame); //在window窗口显示frame摄像头数据画面
char key = waitKey(20);//延时20ms,相当于1s内显示50帧的画面
if (key == 27)//输入"Ese"键则退出
break;
}
capture.release(); //释放摄像头资源
destroyAllWindows(); //释放全部窗口
运行上面函数就可以测试自己的摄像头了,这时候对摄像头读取出来的一帧一帧画面是没有作任何处理直接显示出来的。一般来说此时运行是很流畅的,基本感觉不到延迟。
在该实验中我们设置了key=27(也就是ESE的ASC码)时才会终止。这里需要注意,无论设置成key='q'还是其他的键,都必须使用鼠标点击一下跳出来的视频窗口,激活这个窗口后输入设置的键才能触发(不是点黑框框的终端窗口,是点击有视频的“window”)。
同时因为上面函数有while(1)循环,因此如果直接关掉窗口是不会停止的,会接着再次弹出一个窗口。而如果直接中断终端,那么VS会报异常。正确的关闭方法是使用ESE键正常终止或直接点击VS里的强制终止进程。
所以整个过程思路就是:
①定义一个Mat数据容器frame用来存放摄像头的实时画面数据,使用 VideoCapture函数来获取摄像头的实时画面数据;
②把VideoCapture函数读取的摄像头数据,写到Mat数据容器frame,读取的是当前帧;
③判断frame是否为空,如果不为空,用一个窗口显示摄像头的画面;
④释放资源。
上述摄像头读取出来的画面与我们生活中的场景是相同的,但我们日常习惯了镜像后的画面。因此将原有的画面进行镜像,即上下不变,左右相反,方法如下:
void mirroY(Mat src, Mat& dst)//将src图像镜像变换到dst
{
dst