文章目录
- 前言
- 一、设置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上显示图片并进行线条/矩形框/多边形的绘制