文章目录
- 前言
- 一、在QLabel上显示图片并绘制矩形框
- 二、保存矩形框数据为CSV文件
- 三、保存截取图像
- 四、将截取图像填充到表格
- 五、图形视图框架显示图像
- 六、示例完整代码
- 总结
前言
本文主要讲述了在Qt下使用OpenCV截取绘制的矩形框图像,并将矩形框数据保存为CSV文件,以及在QtableWidget表格上显示截取的图像,其中也使用到了Qt的图形视图框架,下面是示例的详细内容展示,以便大家学习,如有错误之处,欢迎大家批评指正。
项目效果
提示:以下是本篇文章正文内容,下面案例可供参考
一、在QLabel上显示图片并绘制矩形框
本文中使用事件过滤器(eventFilter)来处理界面上QLabel部件的绘图事件,使用QPainter在其上显示图片以及绘制矩形框,矩形框的绘制重写了一些QMouseEvent,具体实现见下文示例完整代码。在我之前的文章中还有直线、多边形等绘制的实现,可以查看文章Qt实现在QLabel上显示图片并进行线条/矩形框/多边形的绘制
//更新界面绘图
void MainWindow::updateShowPaint()
{
//绘制图像
QPainter painter(ui->lb_showImage);
if(m_isImageFlag)
{
painter.drawPixmap(0,0,m_fitPixmap);
}
painter.setFont(QFont(font().family(),12));
//红色绘制区域矩形框
if(m_drawRectFlag)
{
QPen pen(Qt::red); //默认实线画笔
for(int i=0;i<m_rectList.size();i++)
{
const QRect &rect = m_rectList.at(i);
if(i == m_selectedRectIndex)
{
pen.setStyle(Qt::DashLine); //虚线画笔
painter.setPen(pen);
}
else
{
pen.setStyle(Qt::SolidLine); //实线画笔
painter.setPen(pen);
}
painter.drawRect(rect);
}
if(m_isPressedFlag)
{
pen.setStyle(Qt::DashLine); //虚线画笔
painter.setPen(pen);
painter.drawRect(m_currentRect);
}
}
}
二、保存矩形框数据为CSV文件
示例中使用了QList容器来存储绘制的矩形,所以要遍历容器中的矩形数据并保存为CSV文件(逗号分隔符文件),下面是读写CSV文件的实现:
//保存矩形框数据为CSV文件
bool MainWindow::saveRectListToCSV(const QList<QRect> &rectList, const QString &fileName)
{
QFile file(fileName);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
LOGDEBUG<<fileName<<"打开失败!";
return false;
}
QTextStream out(&file);
out<<"x,y,width,height"<<endl; //写入CSV文件的标题行
for(const auto &rect : rectList)
{
out<<rect.x()<<","<<rect.y()<<","<<rect.width()<<","<<rect.height()<<endl;
}
file.close();
return true;
}
//读取CSV文件数据转换为矩形框
QList<QRect> MainWindow::loadRectListFromCSV(const QString &fileName)
{
QList<QRect> rectList;
QFile file(fileName);
if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
LOGDEBUG<<fileName<<"打开失败!";
return rectList;
}
QTextStream in(&file);
QString line = in.readLine(); //跳过标题行
while(!in.atEnd())
{
line = in.readLine();
if(!line.isEmpty())
{
QStringList fields = line.split(",");
if(fields.size() == 4)
{
bool ok;
int x = fields[0].toInt(&ok);
int y = fields[1].toInt(&ok);
int width = fields[2].toInt(&ok);
int height = fields[3].toInt(&ok);
if(ok)
{
rectList.append(QRect(x,y,width,height));
}
else
{
LOGDEBUG<<"矩形转换失败!";
}
}
else
{
LOGDEBUG<<"字段数量不正确!";
}
}
}
file.close();
return rectList;
}
三、保存截取图像
要保存截取图像,首先在程序运行目录创建了一个保存截取图像的文件夹,然后每次保存时会先删除文件夹内原先的图像文件,再建新的截取文件图像。使用OpenCV来进行截取图像的保存,所以需要将绘制的QRect转换为OpenCV的Rect:
//保存截取的图像
void MainWindow::saveCroppedImage()
{
//创建保存截取图像文件夹
QString imageDir = QCoreApplication::applicationDirPath() + "/saveImage/";
QDir dir(imageDir);
if(!dir.exists())
{
LOGDEBUG<<"保存截取图像文件夹不存在!";
if(!dir.mkpath(imageDir))
{
LOGDEBUG<<imageDir + "文件夹创建失败!";
return;
}
}
//判断当前显示图像是否正确
int rows = m_showMat.rows;
int cols = m_showMat.cols;
LOGDEBUG<<"图像高:"<<rows<<" 图像宽:"<<cols;
if((rows == 648) && (cols == 820)) //测试图像分辨率
{
//获取目录下所有图像文件的列表
QStringList filters;
filters<<"*.jpg";
QStringList imageFiles = dir.entryList(filters,QDir::Files);
//遍历列表并删除文件
for(const QString &file : imageFiles)
{
QString filePath = dir.filePath(file);
QFile::remove(filePath);
}
//遍历矩形列表
for(const QRect &rect : m_rectList)
{
//将QRect转换为OpenCV的Rect
cv::Rect roi(rect.x()*m_scaleNum,rect.y()*m_scaleNum,rect.width()*m_scaleNum,rect.height()*m_scaleNum);
//截取图像
cv::Mat croppedImage = m_showMat(roi);
//保存图像到文件
QString saveName = QString("saveImage_%1.jpg").arg(m_rectList.indexOf(rect)+1);
if(!saveName.isEmpty())
{
QString saveImage = imageDir + saveName;
bool success = cv::imwrite(saveImage.toStdString(),croppedImage);
if(success)
{
LOGDEBUG<<saveName<<"保存成功!";
}
else
{
LOGDEBUG<<saveName<<"保存失败!";
}
}
}
QMessageBox::information(this,"提示","截取图像保存成功!");
}
else
{
LOGDEBUG<<"保存失败,当前显示图像错误!";
QMessageBox::information(this,"提示","截取图像保存失败!");
}
}
四、将截取图像填充到表格
获取保存截取图像的文件夹内的图像,创建一个QLabel来显示图像并将其设置为单元格的小部件,这样就实现了表格显示图像的效果:
//将截取图像填充到表格
void ShowImage::fillTableWidgetWithImages(QTableWidget *tableWidget)
{
//清除表格内容
tableWidget->clear();
tableWidget->setRowCount(0);
//设置表头和列宽
tableWidget->setColumnCount(2);
tableWidget->setHorizontalHeaderLabels(QStringList() << "截取图像" << "时间");
tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); //设置列宽拉伸模式
tableWidget->resizeColumnsToContents(); //列宽自适应内容
//获取文件夹内文件
QString imageDir = QCoreApplication::applicationDirPath() + "/saveImage/";
QDir dir(imageDir);
QStringList filters;
filters<<"*.jpg";
QStringList imageFiles = dir.entryList(filters,QDir::Files);
for(const QString &fileName : imageFiles)
{
//获取最后修改时间
QString filePath = dir.filePath(fileName);
QFileInfo fileInfo(filePath);
QDateTime fileTime = fileInfo.lastModified();
//获取图像
cv::Mat image = cv::imread(filePath.toStdString());
if(!image.empty())
{
//插入新行
int rowCount = tableWidget->rowCount();
tableWidget->insertRow(rowCount);
//创建一个QLabel来显示图像
QLabel *imageLabel = new QLabel(tableWidget);
QPixmap pixmap = cvMatToQPixmap(image);
imageLabel->setPixmap(pixmap);
imageLabel->setAlignment(Qt::AlignCenter);
tableWidget->setCellWidget(rowCount,0,imageLabel); //将QLabel设置为单元格的小部件
//设置时间文本
QTableWidgetItem *timeItem = new QTableWidgetItem(fileTime.toString("yyyy-MM-dd HH:mm:ss"));
timeItem->setTextAlignment(Qt::AlignCenter);
tableWidget->setItem(rowCount,1,timeItem); //将时间添加到表格的下一列
//设置图像文件路径到表格项的data角色,方便后续删除操作
QTableWidgetItem *pathItem = new QTableWidgetItem();
pathItem->setData(Qt::UserRole,filePath);
tableWidget->setItem(rowCount,0,pathItem);
}
}
}
五、图形视图框架显示图像
双击表格某行实现跳转,可以对截取图像进行缩放查看,在这里有场景、视图和图形项的使用,关于图形视图框架的更多内容可以查看文章Qt学习:图形视图框架的使用
//双击表格跳转
void ShowImage::on_tw_image_cellDoubleClicked(int row,int column)
{
//检查列数,确保不是点击的表头或其他非数据列
if(column < ui->tw_image->columnCount())
{
//从表格项中获取图像文件路径
QVariant imagePathVariant = ui->tw_image->item(row,0)->data(Qt::UserRole);
QString imagePath = imagePathVariant.toString();
LOGDEBUG<<"选中的图像文件名:"<<imagePath;
//加载图像
QPixmap pixmap(imagePath);
if(!pixmap.isNull())
{
//清除场景中的旧内容
scene->clear();
//创建QGraphicsPixmapItem并添加到场景中
QGraphicsPixmapItem *pixmapItem = scene->addPixmap(pixmap);
//调整场景大小以匹配图像
scene->setSceneRect(pixmapItem->boundingRect());
//确保视图适应新内容
ui->gw_image->fitInView(scene->sceneRect(),Qt::KeepAspectRatio);
}
else
{
//处理图像加载失败的情况
LOGDEBUG<<"加载图像失败!";
}
//切换页面
ui->sw_tableImage->setCurrentIndex(1);
}
}
六、示例完整代码
1.mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "showimage.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void initWidget();
void saveCroppedImage();
bool saveRectListToCSV(const QList<QRect> &rectList, const QString &fileName);
QList<QRect> loadRectListFromCSV(const QString &fileName);
void setShowImage();
void setWidgetEnabled(bool flag);
void setCurPoint(QPoint *getPoint);
void updateShowPaint();
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
bool eventFilter(QObject *watched, QEvent *event);
private slots:
void on_pb_drawRect_clicked();
void on_pb_resetRect_clicked();
void on_pb_set_clicked();
void on_pb_open_clicked();
void on_pb_view_clicked();
void on_pb_save_clicked();
private:
Ui::MainWindow *ui;
bool m_isImageFlag; //图片是否存在
bool m_isRegionFlag; //是否绘图区域
bool m_isPressedFlag; //鼠标是否按压
bool m_isMoveFlag; //鼠标是否移动
bool m_drawRectFlag; //绘制区域标志
double m_scaleNum; //缩放比例
int m_differNumX; //x偏移值
int m_differNumY; //y偏移值
int m_getNumX; //x最大值
int m_getNumY; //y最大值
cv::Mat m_showMat; //界面显示图像
QPixmap m_fitPixmap; //界面绘制图像
QPoint m_startPoint; //矩形绘制的起点
QRect m_currentRect; //当前正在绘制的矩形
int m_selectedRectIndex; //表示选中的矩形序号
QList<QRect> m_rectList; //存储绘制区域的矩形
ShowImage *m_showImage; //截取图像列表界面
};
#endif // MAINWINDOW_H
2.mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->initWidget();
}
MainWindow::~MainWindow()
{
delete ui;
delete m_showImage;
}
//初始化界面
void MainWindow::initWidget()
{
//初始化
m_isImageFlag = false;
m_isRegionFlag = false;
m_isPressedFlag = false;
m_isMoveFlag = false;
m_drawRectFlag = false;
m_scaleNum = 2; //根据图像与显示界面的大小变化,显示界面是410*324,测试图像大小是820*648
m_differNumX = 20;
m_differNumY = 20;
m_getNumX = 410;
m_getNumY = 324;
m_showMat = NULL;
m_startPoint = QPoint(0,0);
m_currentRect = QRect();
m_selectedRectIndex = -1;
m_rectList.clear();
//设置界面尺寸
this->setFixedSize(this->width(),this->height());
//添加事件过滤器
ui->lb_showImage->installEventFilter(this);
//截取图像列表界面
m_showImage = new ShowImage();
//设置界面显示图像
setShowImage();
}
//保存截取的图像
void MainWindow::saveCroppedImage()
{
//创建保存截取图像文件夹
QString imageDir = QCoreApplication::applicationDirPath() + "/saveImage/";
QDir dir(imageDir);
if(!dir.exists())
{
LOGDEBUG<<"保存截取图像文件夹不存在!";
if(!dir.mkpath(imageDir))
{
LOGDEBUG<<imageDir + "文件夹创建失败!";
return;
}
}
//判断当前显示图像是否正确
int rows = m_showMat.rows;
int cols = m_showMat.cols;
LOGDEBUG<<"图像高:"<<rows<<" 图像宽:"<<cols;
if((rows == 648) && (cols == 820)) //测试图像分辨率
{
//获取目录下所有图像文件的列表
QStringList filters;
filters<<"*.jpg";
QStringList imageFiles = dir.entryList(filters,QDir::Files);
//遍历列表并删除文件
for(const QString &file : imageFiles)
{
QString filePath = dir.filePath(file);
QFile::remove(filePath);
}
//遍历矩形列表
for(const QRect &rect : m_rectList)
{
//将QRect转换为OpenCV的Rect
cv::Rect roi(rect.x()*m_scaleNum,rect.y()*m_scaleNum,rect.width()*m_scaleNum,rect.height()*m_scaleNum);
//截取图像
cv::Mat croppedImage = m_showMat(roi);
//保存图像到文件
QString saveName = QString("saveImage_%1.jpg").arg(m_rectList.indexOf(rect)+1);
if(!saveName.isEmpty())
{
QString saveImage = imageDir + saveName;
bool success = cv::imwrite(saveImage.toStdString(),croppedImage);
if(success)
{
LOGDEBUG<<saveName<<"保存成功!";
}
else
{
LOGDEBUG<<saveName<<"保存失败!";
}
}
}
QMessageBox::information(this,"提示","截取图像保存成功!");
}
else
{
LOGDEBUG<<"保存失败,当前显示图像错误!";
QMessageBox::information(this,"提示","截取图像保存失败!");
}
}
//保存矩形框数据为CSV文件
bool MainWindow::saveRectListToCSV(const QList<QRect> &rectList, const QString &fileName)
{
QFile file(fileName);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
LOGDEBUG<<fileName<<"打开失败!";
return false;
}
QTextStream out(&file);
out<<"x,y,width,height"<<endl; //写入CSV文件的标题行
for(const auto &rect : rectList)
{
out<<rect.x()<<","<<rect.y()<<","<<rect.width()<<","<<rect.height()<<endl;
}
file.close();
return true;
}
//读取CSV文件数据转换为矩形框
QList<QRect> MainWindow::loadRectListFromCSV(const QString &fileName)
{
QList<QRect> rectList;
QFile file(fileName);
if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
LOGDEBUG<<fileName<<"打开失败!";
return rectList;
}
QTextStream in(&file);
QString line = in.readLine(); //跳过标题行
while(!in.atEnd())
{
line = in.readLine();
if(!line.isEmpty())
{
QStringList fields = line.split(",");
if(fields.size() == 4)
{
bool ok;
int x = fields[0].toInt(&ok);
int y = fields[1].toInt(&ok);
int width = fields[2].toInt(&ok);
int height = fields[3].toInt(&ok);
if(ok)
{
rectList.append(QRect(x,y,width,height));
}
else
{
LOGDEBUG<<"矩形转换失败!";
}
}
else
{
LOGDEBUG<<"字段数量不正确!";
}
}
}
file.close();
return rectList;
}
//设置界面显示图像
void MainWindow::setShowImage()
{
//读取CSV文件数据转换为矩形框
QString csvFile = QCoreApplication::applicationDirPath() + "/regionRect.csv";
m_rectList.clear();
m_rectList = loadRectListFromCSV(csvFile);
//判断图像文件是否存在
QImage showImage;
QString imageName = QCoreApplication::applicationDirPath() + "/testShow.jpg";
LOGDEBUG<<"图像文件名:"<<imageName;
QFileInfo fileInfo(imageName);
if(!fileInfo.isFile())
{
LOGDEBUG<<"图像文件不存在";
return;
}
m_showMat = cv::imread(imageName.toStdString());
showImage.load(imageName);
QPixmap showPixmap = QPixmap::fromImage(showImage);
if(showPixmap.isNull())
{
LOGDEBUG<<"界面显示图像为空";
m_isImageFlag = false;
return;
}
else
{
m_isImageFlag = true;
m_fitPixmap = showPixmap.scaled(QSize(m_getNumX,m_getNumY),Qt::KeepAspectRatio,Qt::SmoothTransformation); //按比例缩放
}
//更新界面
this->update();
}
//设置界面按钮使能
void MainWindow::setWidgetEnabled(bool flag)
{
m_selectedRectIndex = -1;
m_drawRectFlag = !flag;
ui->pb_drawRect->setEnabled(flag);
ui->pb_resetRect->setEnabled(flag);
ui->pb_set->setEnabled(flag);
ui->pb_open->setEnabled(flag);
ui->pb_view->setEnabled(flag);
ui->pb_save->setEnabled(flag);
}
//设置绘图超限点
void MainWindow::setCurPoint(QPoint *getPoint)
{
if(getPoint->x() < 0)
{
getPoint->setX(0);
}
else if(getPoint->x() > m_getNumX)
{
getPoint->setX(m_getNumX);
}
if(getPoint->y() < 0)
{
getPoint->setY(0);
}
else if(getPoint->y() > m_getNumY)
{
getPoint->setY(m_getNumY);
}
}
//更新界面绘图
void MainWindow::updateShowPaint()
{
//绘制图像
QPainter painter(ui->lb_showImage);
if(m_isImageFlag)
{
painter.drawPixmap(0,0,m_fitPixmap);
}
painter.setFont(QFont(font().family(),12));
//红色绘制区域矩形框
if(m_drawRectFlag)
{
QPen pen(Qt::red); //默认实线画笔
for(int i=0;i<m_rectList.size();i++)
{
const QRect &rect = m_rectList.at(i);
if(i == m_selectedRectIndex)
{
pen.setStyle(Qt::DashLine); //虚线画笔
painter.setPen(pen);
}
else
{
pen.setStyle(Qt::SolidLine); //实线画笔
painter.setPen(pen);
}
painter.drawRect(rect);
}
if(m_isPressedFlag)
{
pen.setStyle(Qt::DashLine); //虚线画笔
painter.setPen(pen);
painter.drawRect(m_currentRect);
}
}
}
//鼠标按压,开始绘制
void MainWindow::mousePressEvent(QMouseEvent *event)
{
//判断是否开始绘制
if(!m_drawRectFlag)
{
return;
}
//判断鼠标是否在绘图区域内
int xMax = m_getNumX + m_differNumX;
int yMax = m_getNumY + m_differNumY;
if((event->pos().x() < 30) || (event->pos().x() > xMax) ||
(event->pos().y() < 30) || (event->pos().y() > yMax))
{
m_isRegionFlag = false;
return;
}
else
{
m_isRegionFlag = true;
}
//鼠标左键按压开始绘制
if(event->button() == Qt::LeftButton)
{
m_isPressedFlag = true;
m_isMoveFlag = false;
//获取起点
m_startPoint = QPoint(event->pos().x()-m_differNumX,event->pos().y()-m_differNumY);
m_currentRect = QRect(m_startPoint,QSize());
this->update();
}
}
//鼠标移动
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
//判断是否开始绘制
if(!m_drawRectFlag)
{
return;
}
//判断是否在绘图区域内
if(!m_isRegionFlag)
{
return;
}
if(m_isPressedFlag)
{
m_isMoveFlag = true;
QPoint movePoint = QPoint(event->pos().x()-m_differNumX,event->pos().y()-m_differNumY);
setCurPoint(&movePoint);
int w = movePoint.x() - m_startPoint.x();
int h = movePoint.y() - m_startPoint.y();
m_currentRect.setSize(QSize(w,h));
this->update();
}
}
//鼠标松开,绘制完成
void MainWindow::mouseReleaseEvent(QMouseEvent *event)
{
//判断是否开始绘制
if(!m_drawRectFlag)
{
return;
}
//判断是否在绘图区域内
if(!m_isRegionFlag)
{
return;
}
//鼠标左键松开
if(event->button() == Qt::LeftButton)
{
m_isPressedFlag = false;
if(!m_isMoveFlag)
{
//判断是否选中矩形
QVector<int> v_index;
QPoint m_endPoint = QPoint(event->pos().x()-m_differNumX,event->pos().y()-m_differNumY);
QList<QRect> curRectList;
if(m_drawRectFlag)
{
curRectList = m_rectList;
}
for(int i=0;i<curRectList.size();i++)
{
//需要考虑大矩形包含小矩形的情况
if(curRectList.at(i).contains(m_endPoint))
{
//将矩形号保存起来
v_index.push_back(i);
}
}
//通过面积来判断选中的矩形
int vSize = v_index.size();
if(vSize == 0)
{
m_selectedRectIndex = -1;
}
else
{
if(vSize == 1)
{
m_selectedRectIndex = v_index[0];
}
else
{
m_selectedRectIndex = v_index[0];
int smallArea = curRectList[v_index[0]].width() * curRectList[v_index[0]].width();
for(int i=0;i<vSize;i++)
{
int area = curRectList[v_index[i]].width() * curRectList[v_index[i]].width();
if(area < smallArea)
{
m_selectedRectIndex = v_index[i];
}
}
}
}
}
//判断矩形大小是否正常
int width = qAbs(m_currentRect.width());
int height = qAbs(m_currentRect.height());
//LOGDEBUG<<"width:"<<width<<" height:"<<height;
if((width < 10) && (height < 10))
{
this->update();
return;
}
if((width == 0) || (height == 0))
{
this->update();
return;
}
//矩形
if(m_drawRectFlag)
{
m_rectList.append(m_currentRect);
}
this->update();
}
else if(event->button() == Qt::RightButton)
{
//界面使能并更新
setWidgetEnabled(true);
this->update();
}
}
//事件过滤
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if((watched == ui->lb_showImage) && (event->type() == QEvent::Paint))
{
updateShowPaint();
}
return QWidget::eventFilter(watched,event);
}
//绘制矩形
void MainWindow::on_pb_drawRect_clicked()
{
//界面使能
setWidgetEnabled(false);
ui->pb_resetRect->setEnabled(true);
this->update();
}
//重置矩形
void MainWindow::on_pb_resetRect_clicked()
{
if(m_selectedRectIndex >= 0)
{
if(m_drawRectFlag)
{
m_rectList.removeAt(m_selectedRectIndex);
}
m_selectedRectIndex = -1;
this->update();
}
else
{
QMessageBox::information(this,"提示","未选择绘制的矩形!");
}
}
//设置截取图像
void MainWindow::on_pb_set_clicked()
{
//保存截取的图像
saveCroppedImage();
}
//打开截取图像列表
void MainWindow::on_pb_open_clicked()
{
m_showImage->updateWidget();
m_showImage->show();
}
//查看绘制好的矩形
void MainWindow::on_pb_view_clicked()
{
m_selectedRectIndex = -1;
m_drawRectFlag = true;
this->update();
}
//保存
void MainWindow::on_pb_save_clicked()
{
//保存矩形框数据为CSV文件
QString csvFile = QCoreApplication::applicationDirPath() + "/regionRect.csv";
LOGDEBUG<<"矩形框数据文件名:"<<csvFile;
if(saveRectListToCSV(m_rectList,csvFile))
{
QMessageBox::information(this,"提示","文件保存成功!");
}
}
3.mainwindow.ui
4.showimage.h
#ifndef SHOWIMAGE_H
#define SHOWIMAGE_H
#include <QWidget>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QMessageBox>
#include <QTime>
#include <QDebug>
#include <QLabel>
#include <QPainter>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QTableWidget>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include "opencv2/opencv.hpp"
#define LOGDEBUG qDebug()<<QTime::currentTime().toString("[hh:mm:ss:zzz]")
namespace Ui {
class ShowImage;
}
class ShowImage : public QWidget
{
Q_OBJECT
public:
explicit ShowImage(QWidget *parent = nullptr);
~ShowImage();
void initWidget();
void updateWidget();
QPixmap cvMatToQPixmap(const cv::Mat &mat);
void fillTableWidgetWithImages(QTableWidget *tableWidget);
void deleteSelectedImage(QTableWidget *tableWidget);
protected:
void wheelEvent(QWheelEvent *event);
private slots:
void on_pb_delete_clicked();
void on_tw_image_cellDoubleClicked(int row, int column);
void on_pb_retuen_clicked();
private:
Ui::ShowImage *ui;
QGraphicsScene *scene; //场景
};
#endif // SHOWIMAGE_H
5.showimage.cpp
#include "showimage.h"
#include "ui_showimage.h"
ShowImage::ShowImage(QWidget *parent) :
QWidget(parent),
ui(new Ui::ShowImage)
{
ui->setupUi(this);
this->initWidget();
}
ShowImage::~ShowImage()
{
delete ui;
}
//初始化界面
void ShowImage::initWidget()
{
this->setWindowTitle("图像列表");
this->setWindowFlags(Qt::WindowCloseButtonHint | Qt::Dialog); //Qt::WindowStaysOnTopHint始终保持在顶层界面
this->setWindowModality(Qt::ApplicationModal);
this->setAttribute(Qt::WA_QuitOnClose,false);
this->setFixedSize(this->width(),this->height());
//创建场景
scene = new QGraphicsScene(this);
ui->gw_image->setScene(scene);
ui->gw_image->setRenderHint(QPainter::Antialiasing);
ui->gw_image->setDragMode(QGraphicsView::ScrollHandDrag);
}
//更新界面显示
void ShowImage::updateWidget()
{
ui->sw_tableImage->setCurrentIndex(0);
fillTableWidgetWithImages(ui->tw_image);
}
//将OpenCV图像转换为QPixmap
QPixmap ShowImage::cvMatToQPixmap(const cv::Mat &mat)
{
QImage showImage;
if(mat.channels() > 1)
{
showImage = QImage((const unsigned char*)(mat.data),mat.cols,mat.rows,mat.step,QImage::Format_RGB888); //彩色图
}
else
{
showImage = QImage((const unsigned char*)(mat.data),mat.cols,mat.rows,mat.step,QImage::Format_Indexed8); //灰度图
}
//OpenCV使用BGR顺序,而Qt使用RGB顺序,因此需要交换颜色通道
return QPixmap::fromImage(showImage.rgbSwapped().scaled(100,100,Qt::KeepAspectRatio,Qt::SmoothTransformation));
}
//将截取图像填充到表格
void ShowImage::fillTableWidgetWithImages(QTableWidget *tableWidget)
{
//清除表格内容
tableWidget->clear();
tableWidget->setRowCount(0);
//设置表头和列宽
tableWidget->setColumnCount(2);
tableWidget->setHorizontalHeaderLabels(QStringList() << "截取图像" << "时间");
tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); //设置列宽拉伸模式
tableWidget->resizeColumnsToContents(); //列宽自适应内容
//获取文件夹内文件
QString imageDir = QCoreApplication::applicationDirPath() + "/saveImage/";
QDir dir(imageDir);
QStringList filters;
filters<<"*.jpg";
QStringList imageFiles = dir.entryList(filters,QDir::Files);
for(const QString &fileName : imageFiles)
{
//获取最后修改时间
QString filePath = dir.filePath(fileName);
QFileInfo fileInfo(filePath);
QDateTime fileTime = fileInfo.lastModified();
//获取图像
cv::Mat image = cv::imread(filePath.toStdString());
if(!image.empty())
{
//插入新行
int rowCount = tableWidget->rowCount();
tableWidget->insertRow(rowCount);
//创建一个QLabel来显示图像
QLabel *imageLabel = new QLabel(tableWidget);
QPixmap pixmap = cvMatToQPixmap(image);
imageLabel->setPixmap(pixmap);
imageLabel->setAlignment(Qt::AlignCenter);
tableWidget->setCellWidget(rowCount,0,imageLabel); //将QLabel设置为单元格的小部件
//设置时间文本
QTableWidgetItem *timeItem = new QTableWidgetItem(fileTime.toString("yyyy-MM-dd HH:mm:ss"));
timeItem->setTextAlignment(Qt::AlignCenter);
tableWidget->setItem(rowCount,1,timeItem); //将时间添加到表格的下一列
//设置图像文件路径到表格项的data角色,方便后续删除操作
QTableWidgetItem *pathItem = new QTableWidgetItem();
pathItem->setData(Qt::UserRole,filePath);
tableWidget->setItem(rowCount,0,pathItem);
}
}
}
//删除选中的截取图像
void ShowImage::deleteSelectedImage(QTableWidget *tableWidget)
{
QList<QTableWidgetItem*> selectedItems = tableWidget->selectedItems();
if(selectedItems.isEmpty())
{
QMessageBox::information(this,"提示","请选择要删除的图像!");
return;
}
//获取选中的行索引
int row = tableWidget->row(selectedItems.first());
//从表格项中获取图像文件路径
QVariant imagePathVariant = tableWidget->item(row,0)->data(Qt::UserRole);
QString imagePath = imagePathVariant.toString();
//删除图像文件
QFile::remove(imagePath);
//删除表格行
tableWidget->removeRow(row);
}
//滚轮事件
void ShowImage::wheelEvent(QWheelEvent *event)
{
if(event->angleDelta().y() > 0)
{
//滚轮向上滚动,放大
ui->gw_image->scale(1.1, 1.1);
}
else
{
//滚轮向下滚动,缩小
ui->gw_image->scale(1 / 1.1, 1 / 1.1);
}
}
//删除
void ShowImage::on_pb_delete_clicked()
{
deleteSelectedImage(ui->tw_image);
}
//双击表格跳转
void ShowImage::on_tw_image_cellDoubleClicked(int row,int column)
{
//检查列数,确保不是点击的表头或其他非数据列
if(column < ui->tw_image->columnCount())
{
//从表格项中获取图像文件路径
QVariant imagePathVariant = ui->tw_image->item(row,0)->data(Qt::UserRole);
QString imagePath = imagePathVariant.toString();
LOGDEBUG<<"选中的图像文件名:"<<imagePath;
//加载图像
QPixmap pixmap(imagePath);
if(!pixmap.isNull())
{
//清除场景中的旧内容
scene->clear();
//创建QGraphicsPixmapItem并添加到场景中
QGraphicsPixmapItem *pixmapItem = scene->addPixmap(pixmap);
//调整场景大小以匹配图像
scene->setSceneRect(pixmapItem->boundingRect());
//确保视图适应新内容
ui->gw_image->fitInView(scene->sceneRect(),Qt::KeepAspectRatio);
}
else
{
//处理图像加载失败的情况
LOGDEBUG<<"加载图像失败!";
}
//切换页面
ui->sw_tableImage->setCurrentIndex(1);
}
}
//返回
void ShowImage::on_pb_retuen_clicked()
{
ui->sw_tableImage->setCurrentIndex(0);
}
6.showimage.ui
总结
在这个示例中,我们可以看到其中使用了事件过滤器来刷新绘图界面,使用了OPenCV来实现对图像的截取,使用图形视图框架来对图像进行扩大缩小等,其实有些功能在我之前的文章中就有实现过,但在这里发现了之前实现的需求存在一些不足,并对其进行了优化。在学习的过程中不仅可以发现过去的不足,而且能解锁新的知识,让我们一起加油努力呀~
hello:
共同学习,共同进步,如果还有相关问题,可在评论区留言进行讨论。