代码在最后!
Attention1:关于摄像头的内容,务必要在.pro加上下面两行,不加报错,注释用#哈。
#multimediawidgets 对应<QCameraViewfinder>等
#multimedia 对应<QCamera>, <QCameraImageCapture>,<QAbstractVideoSurface>等
QT += core gui multimedia multimediawidgets
Attention2:为什么要用QAbstractVideoSurface呢?比如一些VideoWidget也可以办到啊。其实一般的widget只适合一般的显示,但你要对每一帧图像进行处理,画人脸框呢?用捕获视频帧,QPainter画出来是一个好办法。
一 :QAbstractVideoSurface抽象基类
1.1 养成好习惯,对于一个新类,加上它的头文件;添加qmake组建;留意它的父类;
1.2 抽象基类,务必显示所有的虚函数
1.2.1 支持的像素格式
QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType type = QAbstractVideoBuffer::NoHandle) const Q_DECL_OVERRIDE;
假如返回值是QAbstractVideoBuffer::NoHandle(其他的是OpenGL,shared memory XVideo image,macOS–帮助文档有),那么只能通过只读的方式来map(其实就是拷贝)这些帧数据了。如果你的帧像素数据格式不在这个这个函数中所提及,会报错。我只定义了几个,你可以全加上,但是Qt的QImage图像格式是RGB32。
//支持的像素格式
QList<QVideoFrame::PixelFormat> myvideosurface::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const
{
if(handleType == QAbstractVideoBuffer::NoHandle){
return QList<QVideoFrame::PixelFormat>() << QVideoFrame::Format_RGB32
<< QVideoFrame::Format_ARGB32
<< QVideoFrame::Format_ARGB32_Premultiplied
<< QVideoFrame::Format_RGB565
<< QVideoFrame::Format_RGB555;
}
else {
return QList<QVideoFrame::PixelFormat>();
}
}
1.2.2 捕获帧中视频格式是否被预设格式所支持
bool isFormatSupported(const QVideoSurfaceFormat &) const Q_DECL_OVERRIDE; //将视频流中像素格式转换成格式对等的图片格式,若无对等的格式,返回QImage::Format_Invalid
这个函数其实是检测视频流的格式是否合法,返回时bool的,
(1):传入QVideoSurfaceFormat & 通过pixelFormat()返回视频流中帧的像素格式
(2):再将视频流中帧的像素格式通过imageFormatFromPixelFormat()弄成格式等效的图像格式,如不匹配,返回QImage::Format_Invalid
//将视频流中像素格式转换成格式对等的图片格式,若无对等的格式,返回QImage::Format_Invalid
bool myvideosurface::isFormatSupported(const QVideoSurfaceFormat &videoformat) const
{
//imageFormatFromPixelFormat()-----返回与视频帧像素格式等效的图像格式
//pixelFormat()-----返回视频流中帧的像素格式
return QVideoFrame::imageFormatFromPixelFormat(videoformat.pixelFormat()) != QImage::Format_Invalid;
}
1.2.3 开始工作–只要摄像头开,就会调用
bool start(const QVideoSurfaceFormat &) Q_DECL_OVERRIDE; //只要摄像头开,就会调用
//这些虚函数,会自动被调用,start检测图像是否可以对等转换,每一帧有没有
bool myvideosurface::start(const QVideoSurfaceFormat &videoformat)
{
//qDebug() << QVideoFrame::imageFormatFromPixelFormat(videoformat.pixelFormat()); //格式是RGB32
if(QVideoFrame::imageFormatFromPixelFormat(videoformat.pixelFormat()) != QImage::Format_Invalid && !videoformat.frameSize().isEmpty()){
QAbstractVideoSurface::start(videoformat);
return true;
}
return false;
}
1.2.4 捕获每一帧视频,每一帧都会到present中
bool present(const QVideoFrame &) Q_DECL_OVERRIDE; //每一帧画面将回到这里处理
通过一个信号直接把获得的视频帧发送出去,出错直接停止,所以绝大部分时间都在present中,你把摄像头关了也就结束了。
bool myvideosurface::present(const QVideoFrame &frame)
{
if (frame.isValid()){
QVideoFrame cloneFrame(frame); //每一帧视频都会进入present中,内部机制
emit frameAvailable(cloneFrame); //直接把视频帧发送出去
return true;
}
stop();
return false;
}
1.2.5 结束
void stop() Q_DECL_OVERRIDE;
void myvideosurface::stop()
{
QAbstractVideoSurface::stop();
}
二 :通过信号槽来将图片画到界面上。
在主窗口中实现相应的槽函数,如:
void rcvFrame(QVideoFrame); //接收图像帧数据
这里任有几个需要注意的:
map数据的时候要用ReadOnly方式,上面已经讲了。
其实视频数据可能存储在显存或者不可访问的内存中,所以在真正的访问视频数据时,需要用map接口把它拷贝出来。
使用map进行拷贝,务必用unmapping释放
在只读模式可以多次map,当然unmapping次数应该对应。
QAbstractVideoBuffer::ReadOnly可以适应大多数视频帧
void opencamera::rcvFrame(QVideoFrame m_currentFrame)
{
m_currentFrame.map(QAbstractVideoBuffer::ReadOnly);
//将视频帧转化成QImage,devicePixelRatio设备像素比,bytesPerLine一行的像素字节(1280*4=5120)
videoImg = QImage(m_currentFrame.bits(),
m_currentFrame.width(),
m_currentFrame.height(),
QVideoFrame::imageFormatFromPixelFormat(m_currentFrame.pixelFormat())).copy(); //这里要做一个copy,因为char* pdata在emit后释放了
videoImg = videoImg.mirrored(false, true); //水平翻转,原始图片是反的
// qDebug() << "image" << videoImg; //可以看看输出啥东西
// QString currentTime = QDateTime::currentDateTime().toString("yyyyMMddhhmmss");
// QString savefile = QString("E:/study/QT/opencamera/opencamera/%1.jpg").arg(currentTime);
// qDebug() << videoImg.save(savefile);
m_currentFrame.unmap(); //释放map拷贝的内存
QWidget::update(); //更新了,就会触发paintEvent画图
}
三 :在构造函数中创建myvideosurface对象并连接信号
//自己用QPainter将每一帧视频画出来
myvideosurface *surface_ = new myvideosurface(this);
m_camera->setViewfinder(surface_); //预览窗口重定向在surface_中
//处理myvideosurface中每一帧视频
connect(surface_, SIGNAL(frameAvailable(QVideoFrame)), this, SLOT(rcvFrame(QVideoFrame)), Qt::DirectConnection);
四 :在paintEvent事件中,发射信号
//QPainter一般情况下只能在paintEvent中使用。
//触发事件(3条,一般使用第二条进行触发):
//窗口部件第一次显示时,系统会自动产生一个绘图事件,从而强制绘制这个窗口部件。---主窗口起来会触发一次
//当重新调整窗口部件的大小时,系统也会产生一个绘制事件---QWidget::update()或者QWidget::repaint()来强制产生一个绘制事件
//当窗口部件被其他窗口部件遮挡,然后又再次显示出来的时候,就会对那些隐藏的区域产生一个绘制事件
void opencamera::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QRect rect(130, 10, 550, 330);
//设置画笔
QPen pen;
pen.setWidth(5); //线宽
pen.setColor(Qt::red);
pen.setStyle(Qt::SolidLine); //实线
pen.setCapStyle(Qt::FlatCap); //线端点样式
pen.setJoinStyle(Qt::BevelJoin); //线连接点样式
painter.setPen(pen);
painter.drawRect(rect);
//qDebug() << "image" << videoImg; //第一次输出的QImage(null)
//只要窗口启动,就会触发paintEvent,所以第一次是null,不可以画图。
if(videoImg != QImage(nullptr)){
painter.drawImage(rect, videoImg);
}
}
五 :结果显示(工程是做人脸识别的,有密钥,整个工程传上来也没用),前提你要自己把摄像头开起来哦!我会再写一篇博客。
1 界面显示是画出来的
2 预览是最大分辨率1280*720,捕获的照片是自适应显示
3 对比是在线程中完成的
示例代码:
链接:https://pan.baidu.com/s/109B_QZ32-gU5-TnBzWP30w
提取码:bk2a