• Qt下使用OpenCV的鼠标回调函数进行圆形/矩形/多边形的绘制



    前言

    本文主要讲述了在Qt下使用OpenCV的鼠标回调在OpenCV的namedWindow和imshow函数显示出来的界面上进行一些图形的绘制,并最终将绘制好的图形显示在QLabel上。示例代码见文章内容,大家可以参考学习,如有错误之处,欢迎大家批评指正。

    项目效果
    请添加图片描述


    提示:以下是本篇文章正文内容,下面案例可供参考

    一、设置imshow显示窗口

    这里对imshow出来的窗口进行了初始化,在namedWindow函数中设置了WINDOW_NORMAL标志,用来允许用户调整窗口大小。

    //开始绘制区域
    void Widget::startDrawArea(Mat imageMat)
    {
        if(imageMat.empty())
        {
            QMessageBox::warning(this,"警告","请先选择显示图像!");
            return;
        }
    
        //显示绘图窗口
        QString title = "绘制区域";
        string strTitle = title.toLocal8Bit().toStdString();
        namedWindow(strTitle,WINDOW_NORMAL);
    
        //设置窗口的初始位置和大小
        moveWindow(strTitle,690,290);
        resizeWindow(strTitle,400,400);
    
        //设置鼠标回调函数
        m_drawMat = getDrawAreaMat();
        setMouseCallback(strTitle,mouseHandler,&imageMat);
    
        //显示图像并等待用户操作
        imshow(strTitle,m_drawMat);
        waitKey(0);
    }
    

    二、绘制圆形

    绘制圆形的时候,鼠标左键按下记录当前点位为圆心,按下并移动时记录移动的距离为半径,并作判断确保绘制的圆不会超出图形边界,这里会实时显示绘制的圆形,左键松开时结束圆的绘制。

    //绘制圆形
    if(m_drawCircleFlag)
    {
        m_drawCircleFlag = false;
        Point pt = Point(x,y);
        m_radius = norm(pt - m_center);
        int radiusMax = std::min(m_center.x,resultMat.cols - m_center.x);
        radiusMax = std::min(radiusMax,m_center.y);
        radiusMax = std::min(radiusMax,resultMat.rows - m_center.y);
        if(m_radius > radiusMax)
        {
            m_radius = radiusMax;
        }
        circle(resultMat, m_center, m_radius, Scalar(0, 0, 255), 1, LINE_AA);
        m_drawMat = resultMat.clone();
        imshow(strTitle, m_drawMat);
    }
    

    三、绘制矩形

    绘制矩形的时候,鼠标左键按下记录当前点位为矩形的起点,按下并移动时记录当前点位为矩形的终点,在移动过程中也会实时显示绘制出来的矩形,左键松开时结束矩形的绘制。

    //绘制矩形
    if(m_drawRectFlag)
    {
        m_drawRectFlag = false;
        m_endPoint = Point(x, y);
        rectangle(resultMat, Rect(m_startPoint,m_endPoint), Scalar(0, 255, 0), 1, LINE_AA);
        m_drawMat = resultMat.clone();
        imshow(strTitle, m_drawMat);
    }
    

    四、绘制多边形

    多边形需要获取各个顶点,所以这里在每次鼠标左键松开时将当前点保存到多边形顶点容器中,并在获取下一个顶点时将其与前一点相连起来,直到鼠标右键按下,将最后一点与第一点相连,结束多边形的绘制。这里每两点之间的连线使用了line函数,在结束绘制时还可以使用polylines函数,下面代码中有编写。

    //绘制多边形
    m_drawPolygonFlag = false;
    if(m_vPolygon.size() > 2)
    {
        //如果已经绘制了多个点,将最后一个点与第一个点连接起来
        line(resultMat, m_vPolygon[0], m_vPolygon.back(), Scalar(255, 0, 0), 1, LINE_AA);
    
        //或者使用polylines绘制
        //vector> polygons;
        //polygons.push_back(m_vPolygon);
        //polylines(resultMat, polygons, 1, Scalar(255, 0, 0), 1, LINE_AA, 0);
    
        imshow(strTitle, resultMat);
    }
    

    五、示例完整代码

    1.MyTest.pro
    在这里我将OpenCV库的头文件和库文件打包到了OpenCV文件夹,并放在项目源程序的目录下,然后在pro文件中包含这个路径,代码如下:

    #OpenCV
    INCLUDEPATH += $$PWD/OpenCV/Includes
    DEPENDPATH += $$PWD/OpenCV/Includes
    LIBS += -L$$PWD/OpenCV/Lib/ -lopencv_world455
    

    2.widget.h

    #ifndef WIDGET_H
    #define WIDGET_H
    
    #include 
    #include 
    #include 
    #include 
    
    #include "opencv2/opencv.hpp"
    
    using namespace cv;
    using namespace std;
    
    QT_BEGIN_NAMESPACE
    namespace Ui { class Widget; }
    QT_END_NAMESPACE
    
    class Widget : public QWidget
    {
        Q_OBJECT
    
    public:
        Widget(QWidget *parent = nullptr);
        ~Widget();
    
        Mat getDrawAreaMat();
        QPixmap cvMatToPixmap(const Mat imageMat);
    
        void startDrawArea(Mat imageMat);
    
    private:
        static void mouseHandler(int event, int x, int y, int flags, void* param);
    
    private slots:
        void on_pb_selectMat_clicked();
        void on_pb_drawCircle_clicked();
        void on_pb_drawRect_clicked();
        void on_pb_drawPolygon_clicked();
    
    private:
        Ui::Widget *ui;
    
        Mat m_showMat;   //界面显示图像
    
    };
    #endif // WIDGET_H
    

    3.widget.cpp

    #include "widget.h"
    #include "ui_widget.h"
    
    //全局变量
    Mat m_drawMat;   //绘制的图像
    
    bool m_drawCircleFlag = false;    //绘制圆形标志
    bool m_drawRectFlag = false;      //绘制矩形标志
    bool m_drawPolygonFlag = false;   //绘制多边形标志
    
    int m_radius = 0;               //绘制圆的半径
    Point m_center = Point();       //绘制圆的圆心
    Point m_startPoint = Point();   //绘制矩形的起点
    Point m_endPoint = Point();     //绘制矩形的终点
    vector<Point> m_vPolygon;       //多边形的顶点容器
    
    Widget::Widget(QWidget *parent)
        : QWidget(parent)
        , ui(new Ui::Widget)
    {
        ui->setupUi(this);
        this->setFixedSize(650,420);
    }
    
    Widget::~Widget()
    {
        delete ui;
    }
    
    //显示绘制的区域
    Mat Widget::getDrawAreaMat()
    {
        //绘制圆
        Mat showMat = m_showMat.clone();
        if((m_radius != 0) && (m_center != Point(0,0)))
        {
            circle(showMat, m_center, m_radius, Scalar(0, 0, 255), 1, LINE_AA);
        }
    
        //绘制矩形
        if((m_startPoint != Point(0,0)) && (m_endPoint != Point(0,0)))
        {
            rectangle(showMat, Rect(m_startPoint,m_endPoint), Scalar(0, 255, 0), 1, LINE_AA);
        }
    
        //绘制多边形
        int vNum = m_vPolygon.size();
        for(int i=1;i<vNum;i++)
        {
            line(showMat, m_vPolygon[i-1], m_vPolygon[i], Scalar(255, 0, 0), 1, LINE_AA);
            if(i == vNum-1)
            {
                line(showMat, m_vPolygon[0], m_vPolygon.back(), Scalar(255, 0, 0), 1, LINE_AA);
            }
        }
        return showMat;
    }
    
    //Mat转QPixmap
    QPixmap Widget::cvMatToPixmap(const Mat imageMat)
    {
        QImage showImage;
        if(imageMat.channels() > 1)
        {
            showImage = QImage((const unsigned char*)(imageMat.data),imageMat.cols,imageMat.rows,imageMat.step,QImage::Format_RGB888);   //彩色图
        }
        else
        {
            showImage = QImage((const unsigned char*)(imageMat.data),imageMat.cols,imageMat.rows,imageMat.step,QImage::Format_Indexed8);   //灰度图
        }
    
        //OpenCV使用BGR顺序,而Qt使用RGB顺序,因此需要交换颜色通道
        return QPixmap::fromImage(showImage.rgbSwapped());
    }
    
    //开始绘制区域
    void Widget::startDrawArea(Mat imageMat)
    {
        if(imageMat.empty())
        {
            QMessageBox::warning(this,"警告","请先选择显示图像!");
            return;
        }
    
        //显示绘图窗口
        QString title = "绘制区域";
        string strTitle = title.toLocal8Bit().toStdString();
        namedWindow(strTitle,WINDOW_NORMAL);
    
        //设置窗口的初始位置和大小
        moveWindow(strTitle,690,290);
        resizeWindow(strTitle,400,400);
    
        //设置鼠标回调函数
        m_drawMat = getDrawAreaMat();
        setMouseCallback(strTitle,mouseHandler,&imageMat);
    
        //显示图像并等待用户操作
        imshow(strTitle,m_drawMat);
        waitKey(0);
    }
    
    //鼠标回调函数
    void Widget::mouseHandler(int event, int x, int y, int flags, void* param)
    {
        QString title = "绘制区域";
        string strTitle = title.toLocal8Bit().toStdString();
        Mat resultMat = *(Mat*)param;
        if(event == EVENT_LBUTTONDOWN)   //鼠标左键按下
        {
            //绘制圆,确定圆心
            if(m_drawCircleFlag)
            {
                m_center = Point(x,y);
            }
    
            //绘制矩形,确定起点
            if(m_drawRectFlag)
            {
                m_startPoint = Point(x,y);
            }
        }
        else if(event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))   //左键按下并移动
        {
            //实时显示绘制的圆
            if(m_drawCircleFlag)
            {
                //计算半径
                Point pt = Point(x,y);
                m_radius = norm(pt - m_center);
    
                //确保圆不会超出图像边界
                int radiusMax = std::min(m_center.x,resultMat.cols - m_center.x);
                radiusMax = std::min(radiusMax,m_center.y);
                radiusMax = std::min(radiusMax,resultMat.rows - m_center.y);
                if(m_radius > radiusMax)
                {
                    m_radius = radiusMax;
                }
                m_drawMat = resultMat.clone();
                circle(m_drawMat, m_center, m_radius, Scalar(0, 0, 255), 1, LINE_AA);
                imshow(strTitle, m_drawMat);
            }
    
            //实时显示绘制的矩形
            if(m_drawRectFlag)
            {
                m_endPoint = Point(x, y);
                m_drawMat = resultMat.clone();
                rectangle(m_drawMat, Rect(m_startPoint,m_endPoint), Scalar(0, 255, 0), 1, LINE_AA);
                imshow(strTitle, m_drawMat);
            }
        }
        else if(event == EVENT_LBUTTONUP)   //鼠标左键抬起
        {
            //结束绘制圆
            if(m_drawCircleFlag)
            {
                m_drawCircleFlag = false;
                Point pt = Point(x,y);
                m_radius = norm(pt - m_center);
                int radiusMax = std::min(m_center.x,resultMat.cols - m_center.x);
                radiusMax = std::min(radiusMax,m_center.y);
                radiusMax = std::min(radiusMax,resultMat.rows - m_center.y);
                if(m_radius > radiusMax)
                {
                    m_radius = radiusMax;
                }
                circle(resultMat, m_center, m_radius, Scalar(0, 0, 255), 1, LINE_AA);
                m_drawMat = resultMat.clone();
                imshow(strTitle, m_drawMat);
            }
    
            //结束绘制矩形
            if(m_drawRectFlag)
            {
                m_drawRectFlag = false;
                m_endPoint = Point(x, y);
                rectangle(resultMat, Rect(m_startPoint,m_endPoint), Scalar(0, 255, 0), 1, LINE_AA);
                m_drawMat = resultMat.clone();
                imshow(strTitle, m_drawMat);
            }
    
            //绘制多边形的各顶点
            if(m_drawPolygonFlag)
            {
                m_vPolygon.push_back(Point(x, y));
                for(size_t i = 1; i < m_vPolygon.size(); i++)
                {
                    line(resultMat, m_vPolygon[i-1], m_vPolygon[i], Scalar(255, 0, 0), 1, LINE_AA);
                }
                imshow(strTitle, resultMat);
            }
        }
        else if(event == EVENT_RBUTTONDOWN)   //鼠标右键按下
        {
            //结束绘制多边形
            m_drawPolygonFlag = false;
            if(m_vPolygon.size() > 2)
            {
                //如果已经绘制了多个点,将最后一个点与第一个点连接起来
                line(resultMat, m_vPolygon[0], m_vPolygon.back(), Scalar(255, 0, 0), 1, LINE_AA);
    
                //或者使用polylines绘制
                //vector> polygons;
                //polygons.push_back(m_vPolygon);
                //polylines(resultMat, polygons, 1, Scalar(255, 0, 0), 1, LINE_AA, 0);
    
                imshow(strTitle, resultMat);
            }
        }
    }
    
    //选择图像
    void Widget::on_pb_selectMat_clicked()
    {
        QString fileName = QFileDialog::getOpenFileName(this,"选择图像文件","E:/PhotoTest/myPhoto","Image Files(*.png *.jpg *.bmp)");
        if(!fileName.isEmpty())
        {
            m_showMat = imread(fileName.toLocal8Bit().toStdString(),1);
    
            //更新界面显示
            QPixmap showPixmap = cvMatToPixmap(m_showMat);
            if(!showPixmap.isNull())
            {
                ui->lb_showMat->setPixmap(showPixmap.scaled(ui->lb_showMat->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));   //保持比例
            }
            else
            {
                QMessageBox::warning(this,"警告","图像文件打开失败!");
            }
        }
    }
    
    //绘制圆形
    void Widget::on_pb_drawCircle_clicked()
    {
        setEnabled(false);
        m_drawCircleFlag = true;
        m_drawRectFlag = false;
        m_drawPolygonFlag = false;
    
        m_radius = 0;
        m_center = Point(0,0);
        startDrawArea(m_showMat.clone());
    
        //更新界面显示
        QPixmap showPixmap = cvMatToPixmap(getDrawAreaMat());
        if(!showPixmap.isNull())
        {
            ui->lb_showMat->setPixmap(showPixmap.scaled(ui->lb_showMat->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
        }
        setEnabled(true);
    }
    
    //绘制矩形
    void Widget::on_pb_drawRect_clicked()
    {
        //点击后禁用窗口交互
        setEnabled(false);
        m_drawCircleFlag = false;
        m_drawRectFlag = true;
        m_drawPolygonFlag = false;
    
        m_startPoint = Point(0,0);
        m_endPoint = Point(0,0);
        startDrawArea(m_showMat.clone());
        QPixmap showPixmap = cvMatToPixmap(getDrawAreaMat());
        if(!showPixmap.isNull())
        {
            ui->lb_showMat->setPixmap(showPixmap.scaled(ui->lb_showMat->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
        }
    
        //绘制结束后开启交互
        setEnabled(true);
    }
    
    //绘制多边形
    void Widget::on_pb_drawPolygon_clicked()
    {
        setEnabled(false);
        m_drawCircleFlag = false;
        m_drawRectFlag = false;
        m_drawPolygonFlag = true;
    
        m_vPolygon.clear();
        startDrawArea(m_showMat.clone());
        QPixmap showPixmap = cvMatToPixmap(getDrawAreaMat());
        if(!showPixmap.isNull())
        {
            ui->lb_showMat->setPixmap(showPixmap.scaled(ui->lb_showMat->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
        }
        setEnabled(true);
    }
    

    4.widget.ui
    请添加图片描述

    总结

    在Qt下使用OpenCV务必要配置好环境,这样才能正常使用OpenCV的函数。可以看到在这个示例中使用的都是一些基本的函数,但是很好的实现了本文标题所写的功能。在本人之前的文章中,也有使用Qt的事件过滤器来实现这个功能,见下文参考博客。


    hello:
    共同学习,共同进步,如果还有相关问题,可在评论区留言进行讨论。

    参考博客:Qt实现在QLabel上显示图片并进行线条/矩形框/多边形的绘制

  • 相关阅读:
    apache和nginx的TLS1.0和TLS1.1禁用处理方案
    【ACWing 算法基础】模拟散列表
    JavaWeb---HTTP与Request
    leetcode 1004.最大连续1的个数 III 滑动窗口
    使用 JavaScript 和 CSS 的简单图像放大镜
    关于处理第三方jar包的maven攻略
    AMD、request.js,生词太多,傻傻搞不清
    C++语法——详解运算符重载
    【论文笔记】基于 DDPG 算法的双轮腿机器人运动控制研究
    Java代码沙箱
  • 原文地址:https://blog.csdn.net/XCJandLL/article/details/140370322