【fishing-pan:https://blog.youkuaiyun.com/u013921430转载请注明出处】
前言
前段时间杂事很多,这几天突然觉得自己有段时间没有碰Qt了,手有点生了。心血来潮,花了两个小时(是真的手生了),利用Qt和OpenCV写了个用于图像二值化的程序。由于我个人习惯的原因,我是在VS2013中直接写代码进行编译的,不是在Qt中写的代码。好了话不多说,开始吧!
软件工具:VS2013+ Qt5.5.1 +OpenCV3.0+Win7(X64)
工程配置
在VS2013中首先新建一个Qt项目,然后将OpenCV的包含目录、库目录以及库名添加到项目属性中(这一步,我就不多讲了,怎么配置工程环境,大家应该都清楚)。因为要使用工具栏,所以创建项目时,基类使用的是QMainWindow。
软件界面
为了方便讲解,这里先把软件界面展示给大家,图中详细标注了我所放置的控件。
界面分析
上图左上角的读入、保存按键是用以读入原始图像和保存二值化的结果图的,图中公有6个label。label1和label2用来标注下方图像为原始图与二值化图像,label3和label4 用于显示图像,label5和label6分别标注滑条的最大值与最小值。此外还有一个微调框和滑条用以控制图像二值化阈值。
软件操作
视频转换成gif搞了半天还是不清晰,大家凑合看吧!意思是那个个意思,打开图片,调节阈值,选取合适的阈值,输出二值化后的结果图。
代码
照例先贴出代码;
需要说明,我将软件界面中心的四个label单独在一个类中实现了,这需要右键项目属性,添加类,并且选择Qt5Class,基类选择的是QWidget,类名为ShowWidget,同时自动生成了一个头文件一个源文件,代码如下,十分简单,不做讲解。
showwidgets.h
//--------showwidgets.h
//--------潘正宇 2018.04.02
#pragma once
#include <QWidget>
#include <QLabel>
#include <QImage>
#pragma execution_character_set("utf-8")
class ShowWidget :
public QWidget
{
Q_OBJECT
public:
explicit ShowWidget(QWidget *parent = 0);
QImage img;
QLabel *originLabel; //label1
QLabel *binaryLabel; //label2
QLabel *imageLabel1; //label3
QLabel *imageLabel2; //label4
QWidget *Widget1; //存放label1与label3的widget
QWidget *Widget2; //存放label2与label5的widget
signals:
public slots :
private:
};
showwidgets.cpp
//--------showwidgets.cpp
//--------潘正宇 2018.04.02
#include "showwidgets.h"
#include <QBoxLayout>
#include <QLineEdit>
#include <QPushButton>
#include<opencv2/opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
ShowWidget::ShowWidget(QWidget *parent) :QWidget(parent)
{
originLabel = new QLabel;
originLabel->setText(QStringLiteral("原始图像"));
binaryLabel = new QLabel;
binaryLabel->setText(QStringLiteral("二值化的图像"));
imageLabel1 = new QLabel;
imageLabel1->setFixedSize(400, 400); //固定label的大小;
imageLabel1->setScaledContents(true); //按比例自动调整图像大小以在label中显示
imageLabel2 = new QLabel;
imageLabel2->setFixedSize(400, 400);
imageLabel2->setScaledContents(true);
if (img.load("white.jpg"))
{
imageLabel1->setPixmap(QPixmap::fromImage(img));
imageLabel2->setPixmap(QPixmap::fromImage(img));
}
Widget1 = new QWidget;
Widget2 = new QWidget;
QVBoxLayout *originLayout = new QVBoxLayout(Widget1);
originLayout->addWidget(originLabel);
originLayout->addWidget(imageLabel1);
QVBoxLayout *binaryLayout = new QVBoxLayout(Widget2);
binaryLayout->addWidget(binaryLabel);
binaryLayout->addWidget(imageLabel2);
QHBoxLayout *mainLayout = new QHBoxLayout(this);
mainLayout->addWidget(Widget1);
mainLayout->addWidget(Widget2);
mainLayout->setSpacing(5); //在各个区域之间设置间隔。
}
主窗口实现的头文件及源文件如下;
qt_threshold.h
//--------qt_threshold.h
//--------潘正宇 2018.04.02
#ifndef QT_THRESHOLD_H
#define QT_THRESHOLD_H
#include "showwidgets.h"
#include <QtWidgets/QMainWindow>
#include <QPixmap>
#include <QSlider>
#include <QFileDialog>
#include <QMessageBox>
#include <QAction>
#include <QToolBar>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <QLabel>
#include <QSpinBox>
#include <QImage>
using namespace cv;
class Qt_threshold : public QMainWindow
{
Q_OBJECT
public:
Qt_threshold(QWidget *parent = 0);
~Qt_threshold();
private:
QImage originImg; //存储原始图像
QImage binaryImg; //存储二值化后的图像
ShowWidget *showwidget; //定义一个ShowWidget对象
QWidget *mainWidget; //中心部件窗体
QWidget *Change; //放置滑条、微调框、label5、label6的窗体
QSlider *slider; //滑条
QSpinBox *pSpinBox; //微调框
QLabel *minVauleLabel; //label5
QLabel *maxVauleLabel; //label6
QToolBar *fileTool; //工具栏
QString fileName;
Mat originImage;
Mat binaryImage;
void createAction(); //创建工具栏的函数
void createChanges(); //创建滑条和微调框的函数
signals:
//--------------槽函数
public slots :
void SaveFile(); //保存文件的槽函数
void OpenFile(); //打开文件的槽函数
void SpinBoxChangeImageshow(); //spinBox改变时触发的槽函数
void SliderChangeImageshow(); //slider改变时触发的槽函数
};
#endif // QT_THRESHOLD_H
qt_threshold.cpp
//--------qt_threshold.cpp
//--------潘正宇 2018.04.02
#include "qt_threshold.h"
#include <QToolBar>
#include <QHBoxLayout>
#include <QSpinBox>
#include <QSlider>
#include <string>
#include "showwidgets.h"
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
Qt_threshold::Qt_threshold(QWidget *parent)
: QMainWindow(parent)
{
setWindowTitle(QStringLiteral("自制图像二值化mini程序"));
createAction();
showwidget = new ShowWidget(this); // 创建一个ShowWidget类型的区域,用于展示载入的图片
mainWidget = new QWidget; // 创建中心部件区域
Change = new QWidget;
createChanges();
QVBoxLayout *mainLayout = new QVBoxLayout(mainWidget); //在mainWidget内构件一个垂直的布局,放入
mainLayout->addWidget(showwidget);
mainLayout->addWidget(Change);
setCentralWidget(mainWidget); //将mainWidget置于MainWindow的中心区域
}
Qt_threshold::~Qt_threshold()
{
}
void Qt_threshold::createChanges()
{
slider = new QSlider;
slider->setOrientation(Qt::Horizontal); // 水平方向
slider->setMinimum(0); // 最小值
slider->setMaximum(255); // 最大值
slider->setSingleStep(1); // 步长
slider->setValue(80); //初始值
pSpinBox = new QSpinBox(this);
pSpinBox->setMinimum(0);
pSpinBox->setMaximum(255);
pSpinBox->setSingleStep(1);
pSpinBox->setValue(80);
minVauleLabel = new QLabel;
maxVauleLabel = new QLabel;
minVauleLabel->setText("0"); //滑条两端的label,放置最大值以及最小值;
maxVauleLabel->setText("255");
QHBoxLayout *HLayout = new QHBoxLayout(Change);
HLayout->addWidget(pSpinBox); //水平布局
HLayout->addWidget(minVauleLabel);
HLayout->addWidget(slider);
HLayout->addWidget(maxVauleLabel);
///--------------信号槽连接;
connect(slider, SIGNAL(valueChanged(int)), this, SLOT(SliderChangeImageshow()));
connect(pSpinBox, SIGNAL(valueChanged(int)), this, SLOT(SpinBoxChangeImageshow()));
}
void Qt_threshold::createAction()
{
QAction *openFileAction = new QAction(QIcon("open.png"), tr("open file"), this); //设置打开文件的动作
openFileAction->setShortcut(tr("Ctrl+O"));
openFileAction->setStatusTip(tr("open a tiff picture"));
QAction *saveFileAction = new QAction(QIcon("save.png"), tr("save file"), this); //设置保存文件的动作
saveFileAction->setShortcut(tr("Ctrl+S"));
saveFileAction->setStatusTip(tr("save the result picture"));
fileTool = addToolBar("File"); //利用QMainWindow的addToolBar()函数增加工具条
fileTool->addAction(openFileAction);
fileTool->addAction(saveFileAction);
fileTool->setAllowedAreas(Qt::TopToolBarArea | Qt::LeftToolBarArea); //限定文件工具条出现的位置,并且不能移动
fileTool->setMovable(false);
connect(openFileAction, SIGNAL(triggered()), this, SLOT(OpenFile()));
connect(saveFileAction, SIGNAL(triggered()), this, SLOT(SaveFile()));
}
///----------------------------槽函数
//----打开图像---
void Qt_threshold::OpenFile()
{
fileName = QFileDialog::getOpenFileName(this, tr("Select Tiff picture"), ".", tr("Image Files(*.tif *.jpg *.bmp)"));
if (!fileName.isEmpty())
{
//-------将QString转换成const char[];
string trans = fileName.toLocal8Bit().toStdString();
const char *FileName = trans.c_str();
//------使用OpenCV读图
originImage = imread(FileName);
//-----如果图像是彩色图,转为灰度图
if (originImage.channels()== 3)
{
QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("图像为彩色图,将转换为灰度图像显示"));
cvtColor(originImage, originImage, CV_RGB2GRAY);
}
if (originImage.depth()!=CV_8U)
{
QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("图像不是8bit图像,本软件只接受8bit图像"));
return;
}
//------将Mat格式的图像传递到QImage的图像中;
QImage originImg = QImage((const unsigned char*)(originImage.data),
originImage.cols, originImage.rows, QImage::Format_Grayscale8);
//------传递原始灰度图显示
showwidget->imageLabel1->setPixmap(QPixmap::fromImage(originImg));
//-------初始的二值化阈值为80
threshold(originImage, binaryImage, 80, 255.0, CV_THRESH_BINARY);
QImage binaryImg = QImage((const unsigned char*)(binaryImage.data),
binaryImage.cols, binaryImage.rows, QImage::Format_Grayscale8);
//-------传递二值化图像显示
showwidget->imageLabel2->setPixmap(QPixmap::fromImage(binaryImg));
}
else
{
QMessageBox::information(NULL, tr("Path"), QStringLiteral("未选中任何图像")); //提示未选中任何路径
}
}
//----------保存图像------//
void Qt_threshold::SaveFile()
{
QString savePath = QFileDialog::getSaveFileName(this, tr("Select picture save path"), ".", tr("Image Files(*.bmp)"));
if (savePath.isEmpty())
{
QMessageBox::information(NULL, tr("Path"), tr("You have not select any path."));
}
string trans = savePath.toLocal8Bit().toStdString();
const char *path = trans.c_str();
if (imwrite(path, this->binaryImage))
{
QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("图像写出完毕"));
}
}
//----------spinBox改变时触发的槽函数-----
void Qt_threshold::SpinBoxChangeImageshow()
{
int thres = pSpinBox->value();
slider->setValue(thres); //slider值与spinbox值同步
threshold(originImage, binaryImage, thres, 255.0, CV_THRESH_BINARY);
//------修改imageLabel2中的图像;
QImage binaryImg = QImage((const unsigned char*)(binaryImage.data),
binaryImage.cols, binaryImage.rows, QImage::Format_Grayscale8);
showwidget->imageLabel2->setPixmap(QPixmap::fromImage(binaryImg));
}
//----------slider改变时触发的槽函数-----
void Qt_threshold::SliderChangeImageshow()
{
int thres = slider->value();
pSpinBox->setValue(thres); //slider值与spinbox值同步
threshold(originImage, binaryImage, thres, 255.0, CV_THRESH_BINARY);
//------修改imageLabel2中的图像;
QImage binaryImg = QImage((const unsigned char*)(binaryImage.data),
binaryImage.cols, binaryImage.rows, QImage::Format_Grayscale8);
showwidget->imageLabel2->setPixmap(QPixmap::fromImage(binaryImg));
}
main.cpp
//--------main.cpp
//--------潘正宇 2018.04.02
#include "qt_threshold.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Qt_threshold w;
w.show();
return a.exec();
}
代码分析
因为代码标注的非常清楚,很容易理解,我这里就不多讲解了,主要说一下主窗口实现的构造函数。一行一行的来看;
- 第一行代码,设置了主窗口的标题;
- 第二行代码,调用createAction()函数,在主窗口上添加了工具栏;
- 第三行代码,新建一个ShowWidget类动态对象showwidget,这时候调用ShowWidget类的构造函数。动态对象指向的是一个窗体,这个窗体包含四个label;
- 第四行代码,构建一个动态窗体对象,用于指向主窗口的中心部件。
- 第五行代码,构建一个动态窗体对象,指向包含滑条等部件的窗体。
- 第六行代码,调用createChanges()函数,将滑条、微调框等部件放入Change所指的窗体中。
- 第七至九行代码,利用垂直布局,将Change和showwidget放置到mainWidget。
- 第十行代码,将mainWidget设置为MainWindow的中心部件。
已完;