使用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;
    }
}

完整工程项目见:下载地址
本项目已上传github:github

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐