深度学习图像分拣小工具

深度学习图像分拣是个比较繁琐的工作,windwos文件夹预览图像放大倍数有限,而单独地查看图像来回切换各个文件夹很容易乱。因此在空闲尝试做了个小软件。

一、功能需求

  1. 具备类似windows图像查看软件的缩放,拖拽,快捷键切换功能;
  2. 具备一键分拣,即快捷键分拣到对应文件夹功能;
  3. 后续测试中发现快捷键分拣很容易手滑,因此加入了后撤和重做。

二、软件平台

QT5.12.0/C++/OPENCV3.4.0

三、UI界面

主界面包括三个菜单,一图像窗口,一下拉页,一日志窗口。
在这里插入图片描述

3.1 菜单UI

在这里插入图片描述

3.1.1 打开图像路径

功能:

  1. 打开包含图像的文件夹,读取所有图片后缀文件列表;
  2. 显示第一张。

代码:

QString openFile = QFileDialog::getExistingDirectory(this, "choose src Directory", "/");
openFile.toLocal8Bit().constData();
QStringList imageList;  QString ImgPath; QDir imgDir ;
if (openFile != NULL)
{
	ImgPath = openFile; 	
	imgDir.setPath(ImgPath);
	imageList << "*.bmp" << "*.jpg" << "*.png" << "*.tif";
	imgDir.setNameFilters(imageList);
	imgCount = imgDir.count();
    if (imgCount == 0)
   	{
	   return;		
   	}
    else
    {
		QString imageName = ImgPath + "/" + imgDir[0];
		matOpen = imread(imageName.toLocal8Bit().toStdString());
	}
else
{
	return;
}

3.1.2 打开图像路径

功能: 选择/创建主路径文件夹。
代码:

QString openFile = QFileDialog::getExistingDirectory(this, "choose src Directory", "/");
openFile.toLocal8Bit().constData();
if (openFile != NULL)
{
   MainPath = openFile;
}
else
{
   reutn;
}

3.1.3 创建子类路径

界面:
在这里插入图片描述
功能:输入需要分拣的图像类别,’,'分隔,然后根据类别名称新建子类文件夹。
代码:

//输入类以','分割  
//vector <string> classes   类别文件夹名
string tmpClass = ui>lineEdit_classes>text().toLocal8Bit().toStdString();
char *input = (char *)tmpClass.c_str();
char *token = std::strtok(input, ",");
classes.clear();
while (token != NULL)
{
   classes.push_back(token);
   token = std::strtok(NULL, ",");
}
//在主路径下创建以子类名称命名的文件夹
if (MainPath != NULL)
{
   for (int i = 0; i < classes.size(); i++)
   {
   	QString tmpClassPath = MainPath + '/' + QString::fromStdString(classes[i]);
   	QDir *folder = new QDir;
   	folder->mkdir(tmpClassPath);
   }
}

3.2 编辑UI

在这里插入图片描述

3.2.1 撤销/重做

功能:

  1. 撤销即取消图像移动操作;
  2. 重做即取消撤销操作;
  3. 清空日志窗口。

代码:

//当前图像源路径和目标路径添加到hisProcess,撤销即删除已经移动的图像,
//his_count+1,重做即做反操作,his_count-1;
//清空日志不做赘述。

//撤销操作
//int his_count;  //历史操作计数
//vector <QStringList> hisProcess;  //历史记录
if (hisProcess.size() != his_count)
{
   QFile::remove(hisProcess.at(hisProcess.size() - his_count - 1).last());
   his_count += 1;
}
else
{
   return;
}

//重做操作
if (his_count != 0)
{
   QFile::copy(hisProcess.at(hisProcess.size() - his_count).first(), hisProcess.at(hisProcess.size() - his_count).last());
   his_count -= 1;
}
else
{
   return;
}

3.3 提示menu

提示功能不做赘述,按需自写。
在这里插入图片描述

3.4 图像显示UI

在这里插入图片描述
图像显示部分起初只是一个单纯的QLabel来显示图像,后来在使用中发现还是有很多不方便,包括显示分拣classes信息(在textbrowser中无法置顶),单独的翻页按钮比较丑等。于是 对QLabel进行了修改。
主要包括以下几个功能:

  1. 显示图像;
  2. 点击左右边界附近切换上下张图;
  3. 图像的拖拽,缩放;
  4. 右键菜单实现翻页、保存。

3.4.1 显示图像

这个就不做赘述了。

3.4.2 点击左右边界附近切换上下张图

在这里插入图片描述
在这里插入图片描述

从类视图可以看到,imgShow 包含了两个按钮和一个显示ClassFile的QLabel。QT是不允许控件的叠加布局的,查阅了很多,都是建议重写布局方案,感觉太过繁琐。https://www.cnblogs.com/ybqjymy/p/13578255.html 提供了一种通过QWidgets布局的叠加控件方案,我的方案也是在此基础上进行。

  1. 新建一个QWidgets,添加一个QLabel和两个PushButton。两个PushButton先做水平布局,然后再跟QLabel做垂直布局,最后选择QWidgets的栅格布局,效果如图;
    在这里插入图片描述

  2. 调整垂直布局和水平布局比例;
    在这里插入图片描述
    在这里插入图片描述

  3. 按钮删去所有文字,尺寸水平和垂直方向选择perferred(不删文字按钮水平方向无法最小化),背景透明设置 background:transparent,两个按钮中间加入若干spacer布局;
    在这里插入图片描述

  4. 右键打开*.ui文件,找到新建的QWidget,class修改为QLabel,回到VS后,点击*.ui,重新编译(若使用QT自带的开发,回到软件后会提示已修改,是否重新加载,确认即可),到此widget修改的label已经可以显示图像了。
    在这里插入图片描述

3.4.3 图像的拖拽,缩放

功能:

  1. 鼠标点击拖动图像;
  2. ‘ctrl’+滚轮缩放图像,右键还原;
  3. 代码参考了很多类似例子,方法可以参考:https://blog.csdn.net/Viciower/article/details/97648437.

代码:
zoomLabel.h

#include <QObject>
#include <QLabel>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QPainter>
#include <QDebug>
#include <QPixmap>
#include <iostream>

class my_zoomLabel :public QLabel
{
public:
	my_zoomLabel(QWidget *parent = nullptr);
	~my_zoomLabel();

	void initPos();
	void recievePix(QPixmap *pixmap);

protected:
	void paintEvent(QPaintEvent *) override;
	void mouseMoveEvent(QMouseEvent *ev) override;
	void mousePressEvent(QMouseEvent *ev) override;
	void mouseReleaseEvent(QMouseEvent *ev) override;
	void wheelEvent(QWheelEvent *event) override;
	void changeWheelValue(QPoint event, int value);

private:
	double m_scaleValue;					 //图片缩放倍数
	QPointF m_drawPoint;					 //绘图起点
	QPointF m_mousePoint;					 //鼠标当前位置点
	QRect m_rectPixmap;						 //被绘图片的矩形范围
	bool m_isMousePress;				     //鼠标是否按下
	QPixmap pix;							 //作为图元显示的图片

	const double EPS = 1e-6;				 //双精度浮点数比较
	const double SCALE_VALUE = 0.1;
	const double SCALE_MAX_VALUE = 10.0;
	const double SCALE_MIN_VALUE = 0.2;
};

zoomLabel.cpp

#include "my_zoomLabel.h"

my_zoomLabel::my_zoomLabel(QWidget *parent) :QLabel(parent)
{
	m_scaleValue = 1.0;
	m_mousePoint = QPointF(0, 0);
	m_drawPoint = QPointF(0, 0);
	m_rectPixmap = QRect(0, 0, 0, 0);
	m_isMousePress = 0;
}

my_zoomLabel::~my_zoomLabel()
{}

void my_zoomLabel::paintEvent(QPaintEvent *)
{
	QPainter painter(this);
	double width = this->width()*m_scaleValue;
	double height = this->height()*m_scaleValue;
	if (!pix.isNull())
	{
		QPixmap scalePixmap = pix.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); // 饱满缩放
		m_rectPixmap = QRect(m_drawPoint.x(), m_drawPoint.y(), width, height);  // 图片区域
		painter.drawPixmap(m_rectPixmap, scalePixmap);
	}
}

void my_zoomLabel::mouseMoveEvent(QMouseEvent *event)
{
	if (m_isMousePress)
	{
		int x = event->pos().x() - m_mousePoint.x();
		int y = event->pos().y() - m_mousePoint.y();
		m_mousePoint = event->pos();
		m_drawPoint = QPointF(m_drawPoint.x() + x, m_drawPoint.y() + y);
		update();
	}
}

void my_zoomLabel::mousePressEvent(QMouseEvent *event)
{
	if (event->button() == Qt::LeftButton)
	{
		m_isMousePress = true;
		m_mousePoint = event->pos();
	}
}

void my_zoomLabel::mouseReleaseEvent(QMouseEvent *event)
{
	if (event->button() == Qt::RightButton)
	{
		m_drawPoint = QPointF(0, 0);
		m_scaleValue = 1.0;
		update();
	}
	if (event->button() == Qt::LeftButton) m_isMousePress = false;
}

void my_zoomLabel::wheelEvent(QWheelEvent *event)
{
	if (event->modifiers() == Qt::ControlModifier)  //ctrl+滚轮缩放
	{
		int numDegrees = event->delta() / 8; // 滚动角度 - *8就是鼠标滚动的距离
		int numSteps = numDegrees / 15;     // 滚动步数 - *15就是鼠标滚动的角度
		changeWheelValue(event->pos(), numSteps);
		event->accept();
	}
}

void my_zoomLabel::changeWheelValue(QPoint event, int numSteps)
{
	m_scaleValue += numSteps * SCALE_VALUE;
	if (m_scaleValue > (SCALE_MAX_VALUE + EPS))
	{
		m_scaleValue = SCALE_MAX_VALUE;
		return;
	}
	if (m_scaleValue < (SCALE_MIN_VALUE - EPS))
	{
		m_scaleValue = SCALE_MIN_VALUE;
		return;
	}

	if (m_rectPixmap.contains(event))
	{
		double x = m_drawPoint.x() - (event.x() - m_drawPoint.x()) / m_rectPixmap.width()*(this->width()*SCALE_VALUE)*numSteps;
		double y = m_drawPoint.y() - (event.y() - m_drawPoint.y()) / m_rectPixmap.height()*(this->height()*SCALE_VALUE)*numSteps;
		m_drawPoint = QPointF(x, y);
	}
	else
	{
		double x = m_drawPoint.x() - (this->width()*SCALE_VALUE)*numSteps / 2;
		double y = m_drawPoint.y() - (this->height()*SCALE_VALUE)*numSteps / 2;
		m_drawPoint = QPointF(x, y);
	}
	update();
}

void my_zoomLabel::initPos()
{
	//初始化定位点
	m_scaleValue = 1.0;
	m_drawPoint = QPointF(0, 0);
}

void my_zoomLabel::recievePix(QPixmap * pixmap)
{
	pix = *pixmap;
}

3.4.4 右键菜单实现翻页、保存

右键菜单通过代码添加,所以没法截图…
功能:包括上一页、下一页、保存图像。
代码:

//声明
QMenu*   imgShow_Menu;							//显示窗口右键菜单
QAction* imgShow_Action01;
QAction* imgShow_Action02;
QAction* imgShow_Action03;

//图像窗口右键菜单
imgShow_Action01 = new QAction(QString::fromLocal8Bit("上一张"), this);
imgShow_Action02 = new QAction(QString::fromLocal8Bit("下一张"), this);
imgShow_Action03 = new QAction(QString::fromLocal8Bit("保存图像"), this);
imgShow_Menu = new QMenu(this);
imgShow_Menu->addAction(imgShow_Action01);
imgShow_Menu->addAction(imgShow_Action02);
imgShow_Menu->addAction(imgShow_Action03);
connect(ui.label_imgShow, &QLabel::customContextMenuRequested, this, &imgQuickView::imgShow_Menu_Action01);
connect(imgShow_Action01, &QAction::triggered, this, &imgQuickView::action_preImg);
connect(imgShow_Action02, &QAction::triggered, this, &imgQuickView::action_nextImg);
connect(imgShow_Action03, &QAction::triggered, this, &imgQuickView::action_saveImg);
//上一张
if (imgIndex > 0 && imgIndex <= imgCount && imgDir[0] != NULL)
{
   imgIndex -= 1;
   QString preImageName = ImgPath + "/" + imgDir[imgIndex];
   matOpen = imread(preImageName.toLocal8Bit().toStdString());
   imgShow(&matOpen);
}
else
{
   return;
}
//下一张
if (imgIndex < imgCount - 1 && imgDir[0] != NULL)
{
	imgIndex += 1;
	QString nextImageName = ImgPath + "/" + imgDir[imgIndex];
	matOpen = imread(nextImageName.toLocal8Bit().toStdString());
	imgShow(&matOpen);
}
else
{
	return;
}
//保存图像
if (matOpen.empty())
{
   return;
}
else
{
   QString tmpPath = QFileDialog::getExistingDirectory(this, "choose src Directory", "/");
   QString imgSavePath = tmpPath + "/" + QDateTime::currentDateTime().toString("yy_MM_dd_hh_mm_ss") + ".bmp";
   cv::imwrite(imgSavePath.toStdString(), matOpen);
}

3.4 日志窗口

日志窗口采用的textBrowser控件,textBrowser.append()添加所需即可。

四、快捷键

功能:根据输入的class数量,绑定1-9数字键和指定文件夹。
代码:

//移动图像到指定文件夹
void moveImg(int i)
{
	//复制原图到项目文件夹
	sourceImg = ImgPath + "/" + imgDir[imgIndex];
	tagImg =  MainPath + '/' + QString::fromStdString(classes[i]) + "/" + imgDir[imgIndex];
	QFile::copy(sourceImg, tagImg);

	//历史记录 
	//QStringList.first放源路径,QStringList.last放目标路径
	QStringList tmpQList;
	tmpQList.append(sourceImg);
	tmpQList.append(tagImg);
	hisProcess.push_back(tmpQList);	
	
	his_count = 0;
}
void keyPressEvent(QKeyEvent * ev)
{	
	//撤销操作
	if (ev->key() == Qt::Key_Z && ev->modifiers() == Qt::ControlModifier)
	{
		action_cancel();
	}
	else if (ev->key() == Qt::Key_Y && ev->modifiers() == Qt::ControlModifier)
	{
		action_redo();
	}
	
	//对应1-9移动图像到指定文件夹操作
	if (!classes.empty())
	{
		//根据classes数量,将bool数组前n位置1,后续捕捉键盘事件加入bool标志位判断,
		//即根据输入class的数量激活对应键盘快捷键
		int classNum = classes.size();
		for (int i = 0; i < classNum; i++) { keyBool[i] = 1; };

		if (ev->key() == Qt::Key_1)
		{
			if (keyBool[0]) { moveImg(0); }
			else { return; }
		}
		else if (ev->key() == Qt::Key_2)
		{
			if (keyBool[1]) { moveImg(1); }
			else { return; }
		}
		else if (ev->key() == Qt::Key_3)
		{
			if (keyBool[2]) { moveImg(2); }
			else { return; }
		}
		else if (ev->key() == Qt::Key_4)
		{
			if (keyBool[3]) { moveImg(3); }
			else { return; }
		}
		else if (ev->key() == Qt::Key_5)
		{
			if (keyBool[4]) { moveImg(4); }
			else { return; }
		}
		else if (ev->key() == Qt::Key_6)
		{
			if (keyBool[5]) { moveImg(5); }
			else { return; }
		}
		else if (ev->key() == Qt::Key_7)
		{
			if (keyBool[6]) { moveImg(6); }
			else { return; }
		}
		else if (ev->key() == Qt::Key_8)
		{
			if (keyBool[7]) { moveImg(7); }
			else { return; }
		}
		else if (ev->key() == Qt::Key_9)
		{
			if (keyBool[8]) { moveImg(8); }
			else { return; }
		}
	}
} 

第一次发帖,大佬们轻拍。源码编辑过加密了,so~。 不过关键的思路应该都有了。然后我重写的QLabel 缩放、拖拽都一卡一卡的,不知道哪块有问题,有优化思路的可以留言沟通下。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值