需求
在视频窗口上进行绘图,包括圆,矩形,扇形等
效果:
思路:
自己取图然后转成QImage ,再向QWidget 进行渲染,根据以往的经验,无法达到很高的帧率。因此决定使用相机SDK自带的渲染功能,也就是传一个句柄给到sdk。但是这样视频渲染出来了,向上绘制图案,会被视频遮挡住,因此这里采用了两个窗口叠加,然后上层窗口设置透明背景的方式来实现。
底层取图窗口代码:
#ifndef CAMERAWIDGET_H
#define CAMERAWIDGET_H
#include <QWidget>
#include <windows.h>
#include <MvCameraControl.h>
#include "./util/PSEventController.h"
#include "graphwidget.h"
class CameraWidget : public QWidget
{
Q_OBJECT
public:
explicit CameraWidget(QWidget *parent = nullptr);
~CameraWidget();
void updatePos(int x,int y);
private:
bool findDevice();
bool printDeviceInfo(MV_CC_DEVICE_INFO* pstMVDevInfo);
void getImage();
bool openDevice();
bool closeDevice();
private:
int nRet = MV_OK;
void* handle = NULL;
MV_CC_DEVICE_INFO_LIST stDeviceList = {0};
std::atomic_bool bExit = true;
HWND hwnd = NULL;
GraphWidget * graphWidget=nullptr;
QWidget *videoWidget =nullptr;
std::atomic_bool isCapture{false};
private:
void initData();
void mvToQImage(MV_DISPLAY_FRAME_INFO &stDisplayInfo);
public slots:
void on_psEvent_capture(bool isChecked);
void on_psEvent_adjustImage(bool isChecked);
void on_psEvent_fixImage(bool isChecked);
signals:
void capture(QImage image);
};
#endif // CAMERAWIDGET_H
#include "camerawidget.h"
#include <iostream>
#include <mutex>
#include <thread>
#include <QWidget>
#include <QPainter>
#include <QGridLayout>
#include <QStackedLayout>
#include <QDebug>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsItem>
CameraWidget::CameraWidget(QWidget *parent)
: QWidget{parent}
{
graphWidget = new GraphWidget(this);
graphWidget->setWindowFlags(Qt::FramelessWindowHint|Qt::Dialog|Qt::WindowStaysOnTopHint|Qt::SubWindow);
graphWidget->setAttribute(Qt::WA_TranslucentBackground, true);
QPalette pal;
pal.setColor(QPalette::Window,QColor(0,0,0,0));
graphWidget->setAutoFillBackground(true);
graphWidget->setPalette(pal);
graphWidget->show();
hwnd = (HWND)this->winId();
openDevice();
this->setFixedSize(816,683);
graphWidget->setFixedSize(this->size());
setUpdatesEnabled(false);
setAttribute(Qt::WA_OpaquePaintEvent);
initData();
}
CameraWidget::~CameraWidget()
{
closeDevice();
}
bool CameraWidget::findDevice()
{
//枚举设备
nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);
if (MV_OK != nRet)
{
printf("Enum Devices fail! nRet [0x%x]\n", nRet);
return false;
}
if (stDeviceList.nDeviceNum > 0)
{
for (unsigned int i = 0; i < stDeviceList.nDeviceNum; i++)
{
printf("[device %d]:\n", i);
MV_CC_DEVICE_INFO* pDeviceInfo = stDeviceList.pDeviceInfo[i];
if (NULL == pDeviceInfo)
{
break;
}
printDeviceInfo(pDeviceInfo);
}
}
else
{
printf("Find No Devices!\n");
return false;
}
return true;
}
bool CameraWidget::openDevice()
{
if(!findDevice()){
return false;
}
unsigned int nIndex = 0;
if (nIndex >= stDeviceList.nDeviceNum)
{
printf("Invalid device index!\n");
return false;
}
do
{
//选择设备并创建句柄
nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[nIndex]);
if (MV_OK != nRet)
{
printf("Create Handle fail! nRet [0x%x]\n", nRet);
break;
}
// ch:打开设备
nRet = MV_CC_OpenDevice(handle);
if (MV_OK != nRet)
{
printf("Open Device fail! nRet [0x%x]\n", nRet);
break;
}
// ch:探测网络最佳包大小(只对GigE相机有效)
if (stDeviceList.pDeviceInfo[nIndex]->nTLayerType == MV_GIGE_DEVICE)
{
int nPacketSize = MV_CC_GetOptimalPacketSize(handle);
if (nPacketSize > 0)
{
nRet = MV_CC_SetIntValue(handle,"GevSCPSPacketSize",nPacketSize);
if(nRet != MV_OK)
{
printf("Warning: Set Packet Size fail nRet [0x%x]!", nRet);
}
}
else
{
printf("Warning: Get Packet Size fail nRet [0x%x]!", nPacketSize);
}
}
//设置触发模式为off
nRet = MV_CC_SetEnumValue(handle, "TriggerMode", 0);
if (MV_OK != nRet)
{
printf("Set Trigger Mode fail! nRet [0x%x]\n", nRet);
break;
}
// ch:开始取流 | en:Start grab image
nRet = MV_CC_StartGrabbing(handle);
if (MV_OK != nRet)
{
printf("Start Grabbing fail! nRet [0x%x]\n", nRet);
break;
}
bExit=false;
std::thread t([&](){
getImage();
});
t.detach();
return true;
}while (0);
if (nRet != MV_OK)
{
if (handle != NULL)
{
MV_CC_DestroyHandle(handle);
handle = NULL;
}
}
return false;
}
bool CameraWidget::closeDevice()
{
bExit=true;
if (handle == NULL)
return true;
do{
// ch:停止取流 | en:Stop grab image
nRet = MV_CC_StopGrabbing(handle);
if (MV_OK != nRet)
{
printf("Stop Grabbing fail! nRet [0x%x]\n", nRet);
break;
}
// ch:关闭设备 | Close device
nRet = MV_CC_CloseDevice(handle);
if (MV_OK != nRet)
{
printf("ClosDevice fail! nRet [0x%x]\n", nRet);
break;
}
// ch:销毁句柄 | Destroy handle
nRet = MV_CC_DestroyHandle(handle);
if (MV_OK != nRet)
{
printf("Destroy Handle fail! nRet [0x%x]\n", nRet);
break;
}
handle = NULL;
return true;
}while (0);
if (nRet != MV_OK)
{
if (handle != NULL)
{
MV_CC_DestroyHandle(handle);
handle = NULL;
}
}
return false;
}
void CameraWidget::initData()
{
PSEventController::subscribe(this,"capture");
PSEventController::subscribe(this,"adjustImage");
PSEventController::subscribe(this,"fixImage");
connect(this,&CameraWidget::capture,this,[&](QImage image){
graphWidget->setBackgroundImage(QPixmap::fromImage(image),true);
},Qt::UniqueConnection);
}
void CameraWidget::on_psEvent_capture(bool isChecked)
{
if(!isChecked){
QPixmap backgroundImage;
graphWidget->setBackgroundImage(backgroundImage,false);
}else{
isCapture=true;
}
}
void CameraWidget::on_psEvent_adjustImage(bool isChecked)
{
graphWidget->setIsScale(true);
}
void CameraWidget::on_psEvent_fixImage(bool isChecked)
{
graphWidget->setIsScale(false);
}
void CameraWidget::updatePos(int x, int y)
{
graphWidget->move(x,y);
}
bool CameraWidget::printDeviceInfo(MV_CC_DEVICE_INFO *pstMVDevInfo)
{
if (NULL == pstMVDevInfo)
{
printf("The Pointer of pstMVDevInfo is NULL!\n");
return false;
}
if (pstMVDevInfo->nTLayerType == MV_GIGE_DEVICE)
{
int nIp1 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24);
int nIp2 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16);
int nIp3 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8);
int nIp4 = (pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff);
// ch:打印当前相机ip和用户自定义名字 | en:print current ip and user defined name
printf("CurrentIp: %d.%d.%d.%d\n" , nIp1, nIp2, nIp3, nIp4);
printf("UserDefinedName: %s\n\n" , pstMVDevInfo->SpecialInfo.stGigEInfo.chUserDefinedName);
}
else if (pstMVDevInfo->nTLayerType == MV_USB_DEVICE)
{
printf("UserDefinedName: %s\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chUserDefinedName);
printf("Serial Number: %s\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chSerialNumber);
printf("Device Number: %d\n\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.nDeviceNumber);
}
else
{
printf("Not support.\n");
}
return true;
}
//std::once_flag flag;
void CameraWidget::getImage()
{
int nRet = MV_OK;
MV_FRAME_OUT stImageInfo = {0};
MV_DISPLAY_FRAME_INFO stDisplayInfo = {0};
while(1)
{
nRet = MV_CC_GetImageBuffer(handle, &stImageInfo, 1000);
if (nRet == MV_OK)
{
// printf("Get Image Buffer: Width[%d], Height[%d], FrameNum[%d]\n",
// stImageInfo.stFrameInfo.nWidth, stImageInfo.stFrameInfo.nHeight, stImageInfo.stFrameInfo.nFrameNum);
if (hwnd)
{
stDisplayInfo.hWnd = hwnd;
stDisplayInfo.pData = stImageInfo.pBufAddr;
stDisplayInfo.nDataLen = stImageInfo.stFrameInfo.nFrameLen;
stDisplayInfo.nWidth = stImageInfo.stFrameInfo.nWidth;
stDisplayInfo.nHeight = stImageInfo.stFrameInfo.nHeight;
stDisplayInfo.enPixelType = stImageInfo.stFrameInfo.enPixelType;
//转qt QImage 给到绘图窗口
if(isCapture){
mvToQImage(stDisplayInfo);
}
//调整窗口尺寸
// std::call_once(flag, [&](){
// int cW = stImageInfo.stFrameInfo.nWidth;
// int cH = stImageInfo.stFrameInfo.nHeight;
// });
MV_CC_DisplayOneFrame(handle, &stDisplayInfo);
}
nRet = MV_CC_FreeImageBuffer(handle, &stImageInfo);
if(nRet != MV_OK)
{
printf("Free Image Buffer fail! nRet [0x%x]\n", nRet);
}
}
else
{
printf("Get Image fail! nRet [0x%x]\n", nRet);
}
if(bExit)
{
break;
}
}
}
void CameraWidget::mvToQImage(MV_DISPLAY_FRAME_INFO &stDisplayInfo)
{
QImage image;
if(stDisplayInfo.enPixelType==PixelType_Gvsp_Mono8){
image= QImage(stDisplayInfo.pData, stDisplayInfo.nWidth, stDisplayInfo.nHeight, QImage::Format_Indexed8);
}else if(stDisplayInfo.enPixelType==PixelType_Gvsp_RGB8_Packed){
image= QImage(stDisplayInfo.pData, stDisplayInfo.nWidth, stDisplayInfo.nHeight, QImage::Format_RGB888);
}
emit capture(image);
isCapture=false;
}
上层绘图窗口代码:
#ifndef CAMERAWIDGET_H
#define CAMERAWIDGET_H
#include <QWidget>
#include <windows.h>
#include <MvCameraControl.h>
#include "./util/PSEventController.h"
#include "graphwidget.h"
class CameraWidget : public QWidget
{
Q_OBJECT
public:
explicit CameraWidget(QWidget *parent = nullptr);
~CameraWidget();
void updatePos(int x,int y);
private:
bool findDevice();
bool printDeviceInfo(MV_CC_DEVICE_INFO* pstMVDevInfo);
void getImage();
bool openDevice();
bool closeDevice();
private:
int nRet = MV_OK;
void* handle = NULL;
MV_CC_DEVICE_INFO_LIST stDeviceList = {0};
std::atomic_bool bExit = true;
HWND hwnd = NULL;
GraphWidget * graphWidget=nullptr;
QWidget *videoWidget =nullptr;
std::atomic_bool isCapture{false};
private:
void initData();
void mvToQImage(MV_DISPLAY_FRAME_INFO &stDisplayInfo);
public slots:
void on_psEvent_capture(bool isChecked);
void on_psEvent_adjustImage(bool isChecked);
void on_psEvent_fixImage(bool isChecked);
signals:
void capture(QImage image);
};
#endif // CAMERAWIDGET_H
#include "graphwidget.h"
#include <QGridLayout>
#include <QPainter>
#include <QMouseEvent>
#include <QDebug>
#include "../graphics/bqgraphicsitem.h"
#define VIEW_CENTER viewport()->rect().center()
#define VIEW_WIDTH viewport()->rect().width()
#define VIEW_HEIGHT viewport()->rect().height()
GraphWidget::GraphWidget(QWidget *parent)
: QGraphicsView{parent}
{
setAttribute(Qt::WA_TranslucentBackground);
setStyleSheet("background: transparent;border:0px");
setWindowFlags(Qt::FramelessWindowHint);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setRenderHint(QPainter::Antialiasing);
scene = new QGraphicsScene(this);
this->setScene(scene);
setDragMode(ScrollHandDrag);
this->setSceneRect(this->geometry());
centerOn(0, 0);
initData();
}
void GraphWidget::setIsScale(bool newIsScale)
{
isScale = newIsScale;
if(!isScale){
revertSize();
}
}
void GraphWidget::setBackgroundImage(const QPixmap &newBackgroundImage,bool newIsSetBackground)
{
qDebug()<<"setBackgroundImage "<<newIsSetBackground;
isSetBackground=newIsSetBackground;
if(backgroundItem){
scene->removeItem(backgroundItem);
backgroundItem=nullptr;
}
if(isSetBackground){
setAttribute(Qt::WA_TranslucentBackground,false);
backgroundImage = newBackgroundImage;
if(!backgroundImage.isNull()){
backgroundItem = scene->addPixmap(backgroundImage);
backgroundItem->setPos(-this->width()/2,-this->height()/2);
backgroundItem->setZValue(-1000);
backgroundItem->setFlags(QGraphicsItem::ItemIsSelectable |
QGraphicsItem::ItemIsMovable |
QGraphicsItem::ItemIsFocusable);
update();
}
}else{
setAttribute(Qt::WA_TranslucentBackground,true);
revertSize();
}
}
void GraphWidget::clearItem()
{
for(int i=0;i<scene->items().size();i++){
QGraphicsItem *item = scene->items().at(i);
if(item->zValue()==-1000){
continue;
}else{
scene->removeItem(scene->items().at(i));
}
}
}
void GraphWidget::on_psEvent_addLine(bool isChecked)
{
clearItem();
BRectangle *m_rectangle = new BRectangle(0, 0, 100, 50, BGraphicsItem::ItemType::Rectangle);
scene->addItem(m_rectangle);
}
void GraphWidget::on_psEvent_addCircle(bool isChecked)
{
clearItem();
BConcentricCircle *m_conCircle = new BConcentricCircle(0, 0, 50, 80, BGraphicsItem::ItemType::Concentric_Circle);
scene->addItem(m_conCircle);
}
void GraphWidget::on_psEvent_addEllipse(bool isChecked)
{
clearItem();
BEllipse *m_ellipse = new BEllipse(0, 0, 100, 50, BGraphicsItem::ItemType::Ellipse);
scene->addItem(m_ellipse);
}
void GraphWidget::on_psEvent_addArc(bool isChecked)
{
clearItem();
BPie *m_pie = new BPie(0, 0, 80, 30, BGraphicsItem::ItemType::Pie);
scene->addItem(m_pie);
}
void GraphWidget::wheelEvent(QWheelEvent *event)
{
if(isScale){
static float scale = 1.1;
auto angle = event->angleDelta();
if(angle.y() > 0)
{
this->scale(scale, scale);
}
else
{
this->scale(1/scale, 1/scale);
}
}
}
void GraphWidget::initData()
{
PSEventController::subscribe(this,"addLine");
PSEventController::subscribe(this,"addCircle");
PSEventController::subscribe(this,"addEllipse");
PSEventController::subscribe(this,"addArc");
}
void GraphWidget::revertSize()
{
setTransformationAnchor(QGraphicsView::AnchorViewCenter);
QMatrix q;
q.setMatrix(1,this->matrix().m12(),this->matrix().m21(),1,this->matrix().dx(),this->matrix().dy());
setMatrix(q,false);
}
主窗口移动和缩放时更新视频窗口位置:
void MainWindow::updateGraphWidgetPos()
{
if(cameraWidget){
QPoint p = mapToGlobal(cameraWidget->parentWidget()->pos());
cameraWidget->updatePos(p.x(),p.y()+22);
}
}
void MainWindow::resizeEvent(QResizeEvent *e)
{
updateGraphWidgetPos();
return QWidget::resizeEvent(e);
}
void MainWindow::moveEvent(QMoveEvent *e)
{
updateGraphWidgetPos();
return QWidget::moveEvent(e);
}