手把手教你如何设计一个方便快捷的小工具,从图像中截取若干个小图像用作图像分类。
最近在做一个图像分类任务,在数据集方面需要从一个图像中截取出所有单个物品为图像形成图像分类使用的数据集,虽然Opencv可以进行截取,但是在,为了方便快捷,就使用pyqt5+opencv完成了一个小工具的制作,这样就大大的简便了数据集的制作,并且移植性很强,配置好环境即可在自己电脑上使用。
这里先上一个界面图,让大家初步了解有哪些元素。
最近有一个项目涉及到把图像中的物品都给裁剪出来成为单个的物品图像数据。
大概类似于这样的图像,提取出图像中所有的单个的硬币图像并且分类存储。
这里使用了 Pyqt来设计界面,通过按钮来完成指定的操作,以及弹窗提示、路径读取等功能;Opencv则是用来作为图像剪切的工具进行使用,该库中函数丰富,可以简单快捷的使用起来截取ROI图像。
本次项目需要如下所示的库文件:
PyQt5 -----设计界面
Opencv ------图像处理工具
sys -----系统库
os -----文件操作库
datetime -----系统时间获取(用于保存图片时候的命名)
通过qt的designer软件进行ui设计,本次设计的界面大致如下。
主要包含了一些按钮(如图深灰色的就是按钮控件),还有若干的label,label可以用来显示图像,也可以用来显示操作信息等等。深白色的linedit用来获取我们输入的信息,这里根据这个信息区分类别存入不同的文件夹。
将设计好的文件保存为****.ui 后缀的文件,然后打开终端,切换到该文件目录下,使用以下命令将ui文件转换成.py文件
pyuic5 -o ****.py *****.ui
为了看起来美观,还可以通过Pyqt设置界面背景。
self.setStyleSheet("#MainWindow{border-image:url(back.png);}")
在界面类中setupUi方法中加入改代码即可,“()”中的内容即为你需要设置的背景图片的路径。效果如前言中展示的那样,当然还可以给界面中的控件添加背景,方法类似。
最后的界面代码如下,命名为:ui_splitImage.py。
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(953, 516)
font = QtGui.QFont()
font.setFamily("Algerian")
MainWindow.setFont(font)
self.setStyleSheet("#MainWindow{border-image:url(back.png);}")
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton_filePath = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_filePath.setGeometry(QtCore.QRect(10, 20, 141, 28))
self.pushButton_filePath.setObjectName("pushButton_filePath")
self.pushButton_savePath = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_savePath.setGeometry(QtCore.QRect(10, 80, 141, 31))
self.pushButton_savePath.setObjectName("pushButton_savePath")
self.label_imagesPath = QtWidgets.QLabel(self.centralwidget)
self.label_imagesPath.setGeometry(QtCore.QRect(180, 20, 351, 31))
self.label_imagesPath.setObjectName("label_imagesPath")
self.label_savePath = QtWidgets.QLabel(self.centralwidget)
self.label_savePath.setGeometry(QtCore.QRect(180, 90, 331, 21))
font = QtGui.QFont()
font.setFamily("Cambria")
self.label_savePath.setFont(font)
self.label_savePath.setObjectName("label_savePath")
self.label_3 = QtWidgets.QLabel(self.centralwidget)
self.label_3.setGeometry(QtCore.QRect(10, 270, 91, 31))
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(12)
self.label_3.setFont(font)
self.label_3.setObjectName("label_3")
self.label_4 = QtWidgets.QLabel(self.centralwidget)
self.label_4.setGeometry(QtCore.QRect(210, 280, 161, 16))
self.label_4.setObjectName("label_4")
self.layoutWidget = QtWidgets.QWidget(self.centralwidget)
self.layoutWidget.setGeometry(QtCore.QRect(20, 400, 471, 91))
self.layoutWidget.setObjectName("layoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.pushButton_last = QtWidgets.QPushButton(self.layoutWidget)
self.pushButton_last.setObjectName("pushButton_last")
self.horizontalLayout.addWidget(self.pushButton_last)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.pushButton_cut = QtWidgets.QPushButton(self.layoutWidget)
self.pushButton_cut.setObjectName("pushButton_cut")
self.horizontalLayout.addWidget(self.pushButton_cut)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem1)
self.pushButton_next = QtWidgets.QPushButton(self.layoutWidget)
self.pushButton_next.setIconSize(QtCore.QSize(20, 20))
self.pushButton_next.setObjectName("pushButton_next")
self.horizontalLayout.addWidget(self.pushButton_next)
self.label_showImage = QtWidgets.QLabel(self.centralwidget)
self.label_showImage.setGeometry(QtCore.QRect(410, 20, 521, 371))
font = QtGui.QFont()
font.setFamily("Cambria")
self.label_showImage.setFont(font)
self.label_showImage.setObjectName("label_showImage")
self.pushButton_saveImage = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_saveImage.setGeometry(QtCore.QRect(640, 420, 101, 51))
self.pushButton_saveImage.setObjectName("pushButton_saveImage")
self.lineEdit_class = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit_class.setGeometry(QtCore.QRect(60, 270, 131, 41))
self.lineEdit_class.setObjectName("lineEdit_class")
self.label_process = QtWidgets.QLabel(self.centralwidget)
self.label_process.setGeometry(QtCore.QRect(510, 410, 91, 61))
font = QtGui.QFont()
font.setFamily("AMGDT")
font.setPointSize(12)
self.label_process.setFont(font)
self.label_process.setObjectName("label_process")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(20, 150, 81, 31))
self.label.setObjectName("label")
self.label_numImage = QtWidgets.QLabel(self.centralwidget)
self.label_numImage.setGeometry(QtCore.QRect(100, 150, 91, 31))
font = QtGui.QFont()
font.setFamily("Cambria")
self.label_numImage.setFont(font)
self.label_numImage.setText("")
self.label_numImage.setObjectName("label_numImage")
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "SplitImageTools"))
self.pushButton_filePath.setText(_translate("MainWindow", "选择读取路径"))
self.pushButton_savePath.setText(_translate("MainWindow", "选择保存路径"))
self.label_imagesPath.setText(_translate("MainWindow", "“图像读取位置”"))
self.label_savePath.setText(_translate("MainWindow", "“图片保存地址”"))
self.label_3.setText(_translate("MainWindow", "类别:"))
self.label_4.setText(_translate("MainWindow", "输入截取图像类别"))
self.pushButton_last.setText(_translate("MainWindow", "上一张"))
self.pushButton_cut.setText(_translate("MainWindow", "截取图像"))
self.pushButton_next.setText(_translate("MainWindow", "下一张"))
self.label_showImage.setText(_translate("MainWindow", " SHOW ROI_Image"))
self.pushButton_saveImage.setText(_translate("MainWindow", "保存图片"))
self.label_process.setText(_translate("MainWindow", "操作信息"))
self.label.setText(_translate("MainWindow", "图片数量:"))
我的习惯是创建一个文件专门用来写信号与槽,实现功能的py文件。
下面我将主要的几个功能说明即可,介绍几个实用的小功能。
为了能够批量处理图像,我们需要读取图像所在的文件夹路径,所以使用了PyQt中的函数实现文件夹路径的获取。
QtWidgets.QFileDialog.getExistingDirectory(self, "选取文件夹", "./")
def chooseImagesPath(self):
# filepath, filetype = QtWidgets.QFileDialog.getOpenFileName(self, "选取文件", "./", "*.*")
# print(filetype, filepath) #读取文件
self.imagesPath = QtWidgets.QFileDialog.getExistingDirectory(self,
"选取文件夹",
"./") # 起始路径、读取文件夹
self.label_imagesPath.setText(self.imagesPath)
#获取路径下的所有图像内容
if (self.imagesPath != ""):
imgDict = [".jpg", ".png", ".jpeg"]
for name in os.listdir(self.imagesPath):
if os.path.splitext(name)[1] in imgDict:
self.images.append(name)
continue
self.label_numImage.setText(str(len(self.images)))
将该代码与按键建立信号与槽即可完成:点击按钮选择路径的功能。
在有些情况下,避免出现错误,或者说误操作的情况下,可以使用弹窗进行提醒,这里使用PyQt5.QtWidgets下的QMessageBox类
QMessageBox.information(self, "弹窗名", "内容",
QMessageBox.Yes)
比如在该项目中,没有获取文件路径的情况下,点击“下一张”按钮就会无效,所以这时候可以添加一条提醒内容。
有了这个功能,不仅可以提醒操作步骤,还可以防止错误、无效操作。
Opencv中有函数可以通过鼠标画框获取Box信息,通过这个方法我们就可以自定义区域图像的截取。
cv2.selectROI("窗口名", img, False)
该函数返回值为列表:[x,y,w,h],表示了box的左上角坐标、长、宽。
-----ROI_select-----
对于一个图像中存在多种物品,截取出来需要存放在不同的文件夹里面,这里采用lineEdit来获取使用者输入的信息,根据输入信息来区分本次截取图像的类别,然后存放在同类文件夹中。
该部分在界面图中“类别”那里输入,深白色的输入框。
至此,咱们就可以根据以上的方法结合设计出这个截取图像数据的小工具。
功能代码(func.py)如下:
from ui_splitImage import *
import os
import cv2
from PyQt5.QtWidgets import QMainWindow, QMessageBox
from PyQt5 import QtGui, Qt, QtCore
import datetime as dt
class uiSplitImage(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.imagesPath = ""
self.savePath = ""
self.images = []
self.imgID = 0
self.bbox = []
self.flag = 0
self.RoiImage = ""
self.img = 0
self.pushButton_filePath.clicked.connect(self.chooseImagesPath)
self.pushButton_savePath.clicked.connect(self.chooseSavePath)
self.pushButton_cut.clicked.connect(self.cv_cutImage)
self.pushButton_saveImage.clicked.connect(self.saveImage)
self.pushButton_next.clicked.connect(self.nextImage)
self.pushButton_last.clicked.connect(self.lastImage)
def chooseImagesPath(self):
# filepath, filetype = QtWidgets.QFileDialog.getOpenFileName(self, "选取文件", "./", "*.*")
# print(filetype, filepath) #读取文件
self.imagesPath = QtWidgets.QFileDialog.getExistingDirectory(self,
"选取文件夹",
"./") # 起始路径、读取文件夹
self.label_imagesPath.setText(self.imagesPath)
#获取路径下的所有图像内容
if (self.imagesPath != ""):
imgDict = [".jpg", ".png", ".jpeg"]
for name in os.listdir(self.imagesPath):
if os.path.splitext(name)[1] in imgDict:
self.images.append(name)
continue
self.label_numImage.setText(str(len(self.images)))
def chooseSavePath(self):
self.savePath = QtWidgets.QFileDialog.getExistingDirectory(self,
"选取文件夹",
"./") # 起始路径、读取文件夹
self.label_savePath.setText(self.savePath)
pass
def cv_cutImage(self):
if self.judgeFirst() == 0:
return 0
if len(self.images) == 0:
QMessageBox.information(self, "提示", "没有图片可以处理",
QMessageBox.Yes)
return 0
self.label_process.setText("截取图像中")
try:
if self.img == 0:
imagePath = os.path.join(self.imagesPath, self.images[0])
self.img = cv2.imread(imagePath)
except:
pass
self.flag = 0
cv2.namedWindow("ROI_select")
self.bbox = cv2.selectROI("ROI_select", self.img, False)
self.RoiImage = self.img[self.bbox[1]:self.bbox[1]+self.bbox[3], self.bbox[0]:self.bbox[0]+self.bbox[2]]
self.show_cv_img(self.RoiImage)
self.flag = 1
self.label_process.setText("截取完毕")
def qt_cutImage(self):
pass
def saveImage(self):
if (self.flag == 0):
QMessageBox.information(self, "提示", "请截取图像!",
QMessageBox.Yes)
return 0
class_id = self.lineEdit_class.text()
if class_id == "":
QMessageBox.information(self, "提示", "请输入图像类别!",
QMessageBox.Yes)
return 0
now_time = dt.datetime.now().strftime("%Y%m%d%H%M%S%f")
# print(now_time)
if not os.path.exists(os.path.join(self.savePath+"/"+class_id+"/")):
os.mkdir(os.path.join(self.savePath+"/"+class_id+"/"))
cv2.imwrite(os.path.join(self.savePath, class_id+"/"+str(now_time)[-7:-1]+".jpg"), self.RoiImage)
self.flag = 0
self.label_process.setText("保存成功!")
def nextImage(self):
cv2.destroyAllWindows()
if self.judgeFirst() == 0:
return 0
self.imgID += 1
if self.imgID >= len(self.images)-1:
QMessageBox.information(self, "提示", "后面已经没有图片了!",
QMessageBox.Yes)
return 0
# if self.flag != 1:
# QMessageBox.information(self, "提示", "请处理当前图像!",
# QMessageBox.Yes)
# return 0
imagePath = os.path.join(self.imagesPath, self.images[self.imgID])
# print(self.imgID)
self.img = cv2.imread(imagePath)
def lastImage(self):
if self.judgeFirst() == 0:
return 0
if (self.imgID == 0):
QMessageBox.information(self, "提示", "上面没有图片了!",
QMessageBox.Yes)
return 0
self.imgID -= 1
imagePath = os.path.join(self.imagesPath, self.images[self.imgID])
# print(self.imgID)
self.img = cv2.imread(imagePath)
#在指定物件上展示图片
def show_cv_img(self, img):
shrink = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
QtImg = QtGui.QImage(shrink.data,
shrink.shape[1],
shrink.shape[0],
shrink.shape[1] * 3,
QtGui.QImage.Format_RGB888)
jpg_out = QtGui.QPixmap(QtImg).scaled(
self.label_showImage.width(), self.label_showImage.height())
self.label_showImage.setPixmap(jpg_out)
#判断是否选择路径
def judgeFirst(self):
if self.imagesPath == "" or self.savePath == "":
QMessageBox.information(self, "提示", "请选择读取文件路径后继续",
QMessageBox.Yes)
return 0
return 1
# 添加一个退出的提示事件
def closeEvent(self, event):
"""我们创建了一个消息框,上面有俩按钮:Yes和No.第一个字符串显示在消息框的标题栏,第二个字符串显示在对话框,
第三个参数是消息框的俩按钮,最后一个参数是默认按钮,这个按钮是默认选中的。返回值在变量reply里。"""
reply = QMessageBox.question(self, 'Message', "Are you sure to quit?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
# 判断返回值,如果点击的是Yes按钮,我们就关闭组件和应用,否则就忽略关闭事件
if reply == QMessageBox.Yes:
cv2.destroyAllWindows()
event.accept()
else:
event.ignore()
主函数运行代码:
import func
import sys
from PyQt5.QtWidgets import QApplication
if __name__ == "__main__":
app = QApplication(sys.argv)
win = func.uiSplitImage()
win.show()
sys.exit(app.exec_())
本文章基于我的使用需求设计而来,虽然可以直接使用opencv的selectRoi完成图像截取,但是在一个图像中存在多个不同种类的物品的时候就无法区分,这时就可以通过这个小工具来人为输入类别信息,以此来进行区分存放。
通过学习本文,可以学到许多PyQt的小功能,也可以将其应用在自己的许多项目当中,希望对大家有帮助。
文章到这里就结束了,感兴趣的小伙伴可以复制代码到本地,有对应的库文件即可开始体验了~~~~