举个例子:
背景:
几年前在某家人脸识别公司工作时,做了几个演示用的demo(FaceView),其中大量用到了绘图方面的Qt方法。
核心就是:在摄像头的视频流上绘制人脸框、性别、年龄等等。好在当时是使用openCV来捕获摄像头,后面在绘图事件中刷新,所以实际写起来起来非常简单。
这次在MMI中也遇到了这个问题,开发时考虑到对相机的要求不高,能看就行,于是采用了QCamera->QCameraViewfinder一系列操作。
就在快要验收的时候,甲方爸爸提出:不要使用一大块取景器,最好像手机识别那样的圆框。
尝试解决:
网上的一些方法,继承QCameraViewfinder之后重写paintEvent()。实际debug时,发现paintEvent只有窗体被show出来的时候调用了一次,没有update,更别说画图了。
解决:
后来在某个issue下看到有人提到QAbstractVideoSurface,于是去大概了解了一下。
QCamera初始化完成后,可以用setViewfinder()方法来设置数据流往哪走。
void setViewfinder(QVideoWidget *viewfinder);
void setViewfinder(QGraphicsVideoItem *viewfinder);
void setViewfinder(QAbstractVideoSurface *surface);
这里提到的就是第三种方法,追到QAbstractVideoSurface类中,结合Qt手册可以看到:
virtual bool present(const QVideoFrame &frame) = 0;
[pure virtual] bool QAbstractVideoSurface::present(const QVideoFrame &frame)
Presents a video frame.Returns true if the frame was presented, and false if an error occurred.
说白了就是把帧丢出来,给我们自己处理。
这样一来,整条线就打通了,从取数据->绘画->渲染。
实现:
要实现上述功能,首先需要对数据进行处理。
1.新建类VideoSurface,继承自QAbstractVideoSurface,可以把它理解成数据源。继承后,supportedPixelFormats(...)、present(...)这两个纯虚函数实现一下,同时别忘记加上信号,以方便把我们的数据扔出去给其他控件使用;
2.新建类ViewFinder,继承自QLabel,可以把它理解成取景器的QWidget。选择用QLabel是因为比较容易把图片放上来(setPixmap),重写它的paintEvent()。注意:重写的绘图事件中,一定要先调用QLabel::paintEvent(event),否则外面触发的update都不会被渲染;
3.在原本的相机类中,用VideoSurface取代QCameraViewfinder(注意:这时候VideoSurface不是QWidget了,要将ViewFinder当做Widget,设定父窗口、父窗口等)。在这里定义一个槽,用来接收步骤1中的信号,获取图片;
4.使用setPixmap方法,给ViewFinder加上相机的背景。调用此方法时,会进入ViewFinder的绘图事件,在步骤2所说的QLabel::paintEvent(event)后,添加画图的工序。
上述步骤完成后,即可大功告成。
我绘制的是一张png图片,把这张图片换成切好的人脸识别框,就可以实现甲方爸爸的需求了。当然,也可以自己用蒙版画出来一个圆。
具体的实现代码,见https://github.com/zhujingran/PaintOnQCamera