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

news2024/11/15 12:00:09

文章目录

  • 前言
  • 一、设置imshow显示窗口
  • 二、绘制圆形
  • 三、绘制矩形
  • 四、绘制多边形
  • 五、示例完整代码
  • 总结


前言

本文主要讲述了在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<vector<cv::Point>> 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 <QWidget>
#include <QMessageBox>
#include <QFileDialog>
#include <QDebug>

#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<vector<cv::Point>> 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上显示图片并进行线条/矩形框/多边形的绘制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1924742.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

html(抽奖设计)

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>抽奖</title><style type"text/css">* {margin: 0;padding: 0;}.container {width: 800px;height: 800px;border: 1px dashed red;position: absolut…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(四)-无人机系统(UAS)命令与控制(C2)通信用例

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

鸿蒙系统在服装RFID管理中的应用:打造智能零售新时代

​随着物联网技术的迅速发展&#xff0c;服装零售行业正面临着新的变革与挑战。鸿蒙系统作为新一代智能操作系统&#xff0c;结合RFID技术&#xff0c;为服装行业提供了高效、智能的管理解决方案。常达智能物联&#xff0c;作为RFID技术的领先企业&#xff0c;致力于将鸿蒙系统…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(五)-同时支持无人机和eMBB用户数据传输的用例

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

1、BOREDHACKERBLOG:社交网络

靶机&#xff1a;https://www.vulnhub.com/entry/boredhackerblog-social-network,454/ 参考&#xff1a;Vulnhub靶机&#xff1a;BOREDHACKERBLOG: SOCIAL NETWORK_boredhackerblog系列-CSDN博客 需要使用virtualbox。 先去官网下载了最新版的vietualbox&#xff0c;以及把这…

pytorch-LSTM

目录 1. RNN存在的问题2. LSTM的由来3. LSTM门3.1 遗忘门3.2 输入门3.3 输出门 4. LSTM是如何减轻梯度弥散问题 1. RNN存在的问题 如下图&#xff1a;RNN能满足预测下一个单词&#xff0c;但是对于获取更多的上下文信息就做不到了。 2. LSTM的由来 RNN能做到短时记忆即shor…

jenkins系列-04-jenkins参数化构建

使用maven build之前&#xff0c;先checkout 指定分支或标签&#xff1a; 拖拽调整顺序&#xff1a;shell执行在前&#xff0c;构建在后&#xff1a; gitee新建标签tag:

【持续集成_06课_Jenkins高级pipeline应用】

一、创建项目选择pipeline的风格 它主要是以脚本&#xff08;它自己的语言&#xff09;的方式进行运行&#xff0c;一般由运维去做的事情&#xff0c;作为测试而言。了解即可。 --- 体现形式全部通过脚本去实现&#xff1a;执行之前&#xff08;拉取代码&#xff09;执行&…

《Cross-Image Pixel Contrasting for Semantic Segmentation》论文解读

期刊&#xff1a;TPAMI 年份&#xff1a;2024 摘要 研究图像语义分割问题。目前的方法主要集中在通过专门设计的上下文聚合模块(如空洞卷积、神经注意力)或结构感知的优化目标(如iou样损失)挖掘"局部"上下文&#xff0c;即单个图像中像素之间的依赖关系。然而&…

【图解大数据技术】流式计算:Spark Streaming、Flink

【图解大数据技术】流式计算&#xff1a;Spark Streaming、Flink 批处理 VS 流式计算Spark StreamingFlinkFlink简介Flink入门案例Streaming Dataflow Flink架构Flink任务调度与执行task slot 和 task EventTime、Windows、WatermarksEventTimeWindowsWatermarks 批处理 VS 流式…

Lottery 分布式抽奖(个人向记录总结)

1.搭建&#xff08;DDDRPC&#xff09;架构 DDD——微服务架构&#xff08;微服务是对系统拆分的方式&#xff09; &#xff08;Domain-Driven Design 领域驱动设计&#xff09; DDD与MVC同属微服务架构 是由Eric Evans最先提出&#xff0c;目的是对软件所涉及到的领域进行建…

jenkins系列-05-jenkins构建golang程序

下载go1.20.2.linux-arm64.tar.gz 并存放到jenkins home目录&#xff1a; 写一个golang demo程序&#xff1a;静态文件服务器&#xff1a;https://gitee.com/jelex/jenkins_golang package mainimport ("encoding/base64""flag""fmt""lo…

搜索引擎中的相关性模型

一、什么是相关性模型&#xff1f; 相关性模型主要关注的是query和doc的相关性。例如给定query&#xff0c;和1000个doc&#xff0c;找到哪个doc是好query最相关的。 二、为什么需要相关性模型&#xff1f; 熟悉es的应该都熟悉BM25相关性算法。它是一个很简单的相关性算法。我…

【Linux】权限管理与相关指令

文章目录 1.权限、文件权限、用户文件权限的理解以及对应八进制数值表示、设置目录为粘滞位文件类型 2.权限相关的常用指令susudochmodchownchgrpumaskwhoamifile 1.权限、文件权限、用户 通过一定条件&#xff0c;拦住一部分人&#xff0c;给另一部分权利来访问资源&#xff0…

【node-RED 4.0.2】连接 Oracle 数据库踩坑解决,使用模组:node-red-contrib-agur-connector

关于 Oracle Oracle 就好像一张吸满水的面巾纸&#xff0c;你稍一用力它就烂了。 一、发现的问题 1.为什么需要 Oracle Instant Client && 不能使用 rpm 安装的原因 我们在使用 node-red 的 node-red-contrib-agur-connector 插件模组时&#xff0c;需要用到 Oracl…

QML界面控件加载与显示顺序

一、QML界面控件加载顺序 QML在界面加载时的顺序和我们认知的有很大的不同&#xff0c;有时候会对我们获取参数以及界面实现造成很大的困扰 1、加载顺序 import QtQuick 2.12 import QtQml 2.12 import QtQuick.Window 2.12 import QtQuick.VirtualKeyboard 2.4Window {id: …

Oracle使用fetch first子句报错:ORA-00933 SQL命令未正确结束

问题背景 今天在统计终端厂商告警次数Top10的时候使用SQL查询使用到了fetch first子句&#xff0c;结果执行报错&#xff1a;ORA-00933 SQL命令未正确结束。 报错原因 Oracle数据库中&#xff0c;使用 FETCH FIRST 子句需要启用 Oracle 12c 及以上版本。如果在较低版本的 Or…

德迅与DSV携香港蝴蝶效应集团,创半导体与新能源汽车物流新篇章

在全球经济一体化的大背景下,物流行业作为连接生产与消费的重要纽带,正迎来前所未有的发展机遇。特别是在半导体产业和新能源汽车领域,物流服务的专业性和高效性已成为企业竞争力的重要体现。近日,国际物流巨头德迅(Kuehne Nagel International)与全球汽车行业供应链物流专家D…

GitHub+Picgo图片上传

Picgo下载&#xff0c;修改安装路径&#xff0c;其他一路下一步&#xff01; 地址 注册GitHub&#xff0c;注册过程不详细展开&#xff0c;不会的百度一下 地址 新建GitHub仓库存放图片 生成Token令牌 点击头像&#xff0c;点击Settings 滑到最后 过期时间&#xff1a;No expi…

用HTML和CSS实现提示工具(tooltip)及HTML元素的定位

所谓提示工具&#xff0c;是指将鼠标移动到某个HTML元素&#xff08;工具&#xff09;时会显示一些提示内容&#xff08;提示文本&#xff09;&#xff0c;而鼠标移出工具元素的范围时提示文本就消失了。考虑到提示文本元素应当在鼠标进入工具元素时显示&#xff0c;鼠标离开工…