使用QLabel显示摄像头得到的图像(视频流),把QLabel提升为MyLabel,通过在MyLabel类中添加新的方法可以实现在图像中动态选取ROI。要想在视频流中动态选取ROI,思路是创建一张全白的图片,使用以上方法先实现在图像中选取ROI,再把摄像头得到的图像(视频流)使用原QLabel的方法添加到QLabel上去即可。
//mylabel.h
#include <QLabel>
#include <QMenu>
/* 缩放方向 */
enum EmDirection
{
DIR_TOP = 0,
DIR_BOTTOM,
DIR_MIDDLE,
DIR_NONE
};
#define EDGPADDING 5 //四周边缘可拉伸宽度
#define CORPADDING 5 //四角可拉伸宽度
#define EDGE_WIDTH 1 //边框宽度
class MyLabel : public QLabel
{
Q_OBJECT
public:
MyLabel(QWidget *parent = nullptr);
~MyLabel();
QRect getRoiRect() const; //获取已经圈选的框 外部调用
protected:
void paintEvent(QPaintEvent *event);
void mousePressEvent(QMouseEvent *ev);
void mouseMoveEvent(QMouseEvent *ev);
void mouseReleaseEvent(QMouseEvent *ev);
void keyPressEvent(QKeyEvent *ev);
void contextMenuEvent(QContextMenuEvent *ev);
private:
void initViewer(); //初始化
EmDirection region(const QPoint &point); //设置鼠标形状
void moveRect(const QPoint &mousePoint); //移动矩形
void scaleRect(const QPoint &mousePoint); //缩放矩形
private:
bool m_bPainterPressed; //是否正在绘制
bool m_bMovedPressed; //是否正在拖动
bool m_bScalePressed; //是否正在缩放大小
QPoint m_paintStartPoint; //绘制的初始位置
QPoint m_moveStartPoint; //拖动的初始位置
QRect m_roiRect; //绘制的ROI
EmDirection m_emCurDir; //拖动的方向
QImage m_backImage; //背景图
QMenu *m_pOptMenu; //菜单
QAction *m_pDelAction; //删除动作
};
//mylabel.cpp
#include "mylabel.h"
#include <QPainter>
#include <QMouseEvent>
#include <QMenu>
#include <QAction>
#include <QStandardPaths>
#include <QFileDialog>
#include <QDebug>
MyLabel::MyLabel(QWidget *parent)
: QLabel(parent)
{
m_backImage = QImage(640,480, QImage::Format_Grayscale8);
m_backImage.fill(QColor(255, 255, 255));
this->initViewer();
}
MyLabel::~MyLabel()
{
}
void MyLabel::initViewer()
{
m_bPainterPressed = false;
m_bMovedPressed = false;
m_bScalePressed = false;
m_roiRect = QRect(0, 100, 640-1, 200);
m_emCurDir = EmDirection::DIR_NONE;
this->setMouseTracking(true);
this->setFocusPolicy(Qt::StrongFocus);
m_pOptMenu = new QMenu(this);
m_pDelAction = new QAction(QStringLiteral("删除"), this);
connect(m_pDelAction, &QAction::triggered, this, [&]() { m_roiRect = QRect(0, 0, 0, 0); });
m_pOptMenu->addAction(m_pDelAction);
}
/**
* @brief 获取绘制的矩形
* @return 绘制的矩形
*/
QRect MyLabel::getRoiRect() const
{
return m_roiRect;
}
/**
* @brief 绘制矩形
* @param event 绘图事件
* @return void
*/
void MyLabel::paintEvent(QPaintEvent * event)
{
QLabel::paintEvent(event);
QPen pen;
pen.setColor(Qt::blue);
pen.setWidth(EDGE_WIDTH);
QPainter painter;
painter.begin(this);
painter.setPen(pen);
painter.drawRect(m_roiRect);
qDebug() << m_roiRect;
}
/**
* @brief 鼠标按下事件 用于拖动绘制缩放起始
* @param ev 鼠标事件
* @return void
*/
void MyLabel::mousePressEvent(QMouseEvent * ev)
{
if (ev->buttons() & Qt::LeftButton)
{
EmDirection dir = region(ev->pos()); //获取鼠标当前的位置
if (dir == DIR_MIDDLE)
{
//鼠标在矩形中心位置
this->setCursor(Qt::ClosedHandCursor);
m_moveStartPoint.setX(ev->pos().x());
m_moveStartPoint.setY(ev->pos().y());
m_bMovedPressed = true;
}
else if (dir == DIR_NONE)
{
//鼠标在矩形外部
this->setCursor(Qt::ArrowCursor);
m_bPainterPressed = true;
m_paintStartPoint.setX(ev->pos().x());
m_paintStartPoint.setY(ev->pos().y());
}
else
{
//矩形在矩形边缘
m_moveStartPoint.setX(ev->pos().x());
m_moveStartPoint.setY(ev->pos().y());
m_bScalePressed = true;
m_emCurDir = dir;
}
}
}
/**
* @brief 鼠标移动事件
* @param ev 鼠标事件
* @return void
*/
void MyLabel::mouseMoveEvent(QMouseEvent * ev)
{
if (ev->buttons() & Qt::LeftButton)
{
if (m_bMovedPressed) //正在移动状态
moveRect(ev->pos());
else if (m_bScalePressed) //正在缩放大小状态
scaleRect(ev->pos());
update(); //更新界面
return;
}
//根据鼠标的位置设置当前的鼠标形状
EmDirection dir = region(ev->pos());
if (dir == DIR_NONE)
setCursor(Qt::ArrowCursor);
else if (dir == DIR_MIDDLE)
setCursor(Qt::OpenHandCursor);
}
/**
* @brief 鼠标松开事件
* @param ev 鼠标事件
* @return void
*/
void MyLabel::mouseReleaseEvent(QMouseEvent * ev)
{
//判断鼠标是否在矩形中
if (m_roiRect.contains(ev->pos()))
{
//松开鼠标前是否正在拖放
if (m_bMovedPressed)
this->setCursor(Qt::OpenHandCursor);
else
this->setCursor(Qt::ArrowCursor);
}
m_paintStartPoint = QPoint();
m_bMovedPressed = false;
m_bPainterPressed = false;
m_bScalePressed = false;
}
/**
* @brief 键盘按下事件
* @param ev 键盘事件
* @return void
*/
void MyLabel::keyPressEvent(QKeyEvent * ev)
{
if (ev->key() == Qt::Key_Delete)
{
m_roiRect = QRect(0,0,0,0);
update();
}
}
/**
* @brief 右键菜单
* @param ev 菜单事件
* @return void
*/
void MyLabel::contextMenuEvent(QContextMenuEvent * ev)
{
QPoint mousePos = ev->pos();
if (m_roiRect.contains(mousePos))
m_pOptMenu->exec(QCursor::pos());
ev->accept();
}
/**
* @brief 判断鼠标的位置
* @param point 鼠标的位置
* @return EmDirection 方向
*/
EmDirection MyLabel::region(const QPoint & point)
{
int mouseX = point.x();
int mouseY = point.y();
QPoint roiTopLeft = m_roiRect.topLeft();
QPoint roiBottomRight = m_roiRect.bottomRight();
EmDirection dir = DIR_NONE;
if (mouseY <= roiTopLeft.y() + EDGPADDING && mouseY >= roiTopLeft.y() && mouseX >= roiTopLeft.x() && mouseX <= roiBottomRight.x())
{
//上边
this->setCursor(Qt::SizeVerCursor);
dir = DIR_TOP;
}
else if (mouseY >= roiBottomRight.y() - EDGPADDING && mouseY <= roiBottomRight.y() && mouseX >= roiTopLeft.x() && mouseX <= roiBottomRight.x())
{
//下边
this->setCursor(Qt::SizeVerCursor);
dir = DIR_BOTTOM;
}
else if(m_roiRect.contains(point))
{
//中间
dir = DIR_MIDDLE;
}
else
{
dir = DIR_NONE;
}
return dir;
}
/**
* @brief 绘制矩形
* @param mousePoint 鼠标的位置
* @return void
*/
void MyLabel::moveRect(const QPoint & mousePoint)
{
this->setCursor(Qt::ClosedHandCursor);
int width = mousePoint.x() - m_moveStartPoint.x();
int height = mousePoint.y() - m_moveStartPoint.y();
QRect rt;
rt.setRect(0,0,this->width(),this->height());
QRect ret;
ret.setX(m_roiRect.x());
ret.setY(m_roiRect.y() + height);
ret.setSize(m_roiRect.size());
if(rt.contains(QPoint(m_roiRect.x(),m_roiRect.y() + height))
&&rt.contains(QPoint(m_roiRect.x() + m_roiRect.width(),m_roiRect.y() + height +m_roiRect.height())))
{
m_roiRect = ret;
m_moveStartPoint = mousePoint;
}
}
/**
* @brief 缩放矩形
* @param mousePoint 鼠标的位置
* @return void
*/
void MyLabel::scaleRect(const QPoint & mousePoint)
{
QRect newRect(m_roiRect.topLeft(), m_roiRect.bottomRight());
QRect rt;
rt.setRect(0,0,this->width(),this->height());
//根据当前的缩放状态来改变矩形的位置大小信息
switch (m_emCurDir)
{
case DIR_TOP:
newRect.setTop(mousePoint.y());
if(rt.contains(QPoint(newRect.x() + newRect.width(),newRect.y() - newRect.height() + m_roiRect.height())))
{
m_roiRect = newRect;
m_moveStartPoint = mousePoint;
}
break;
case DIR_BOTTOM:
newRect.setBottom(mousePoint.y());
if(rt.contains(QPoint(newRect.x() + newRect.width(),newRect.y() + newRect.height())))
{
m_roiRect = newRect;
m_moveStartPoint = mousePoint;
}
break;
}
}