一、界面
我做的界面大概思路:点击 上传图片,显示待检验的照片
点击 U-net模型检测 会使用已经训练好的U-net模型进行预测
点击 OpenCV统计 会将预测结果进行轮廓检测,并统计所有轮廓的面积
在 处理结果 这个groupBox中进行显示
大致界面如下:
详细界面布局如下:
二、关联PyCharm
将已经设置好的UI界面保存为yy.ui
将该文件放到自己的项目中,例如我放到的位置如下:
创建一个测试文件my_UI.py,在同级目录下
三、代码块讲解
1,初始化
其中:
self.ui = QUiLoader().load('yy.ui')
,指定Qt页面的路径,因为我这个是同一级目录故之间导入即可yy.ui
这行代码的意思为:将yy.ui
这个UI创建一个名为ui的对象
self.output_size = 480
,规定图像的大小
其他的为加载U-net网络模型
def __init__(self):# 从文件中加载UI定义# 从 UI 定义中动态 创建一个相应的窗口对象# 注意:里面的控件对象也成为窗口对象的属性了# 比如 self.ui.button , self.ui.textEditself.ui = QUiLoader().load('yy.ui') # 这里的参数为ui的路径,对这个ui文件创建对象ui"""对某个控件进行操作会产生一个signal,常通过slot来进行处理signalslot就是通过clicked.connect来绑定某个函数,这个函数用于处理signal"""self.output_size = 480# 加载网络,图片单通道,分类为1。net = UNet(n_channels=1, n_classes=1)# 将网络拷贝到deivce中net.to(device=device)# 加载模型参数net.load_state_dict(torch.load('G:/PyCharm/workspace/my_test/yy_unet/best_model1.pth', map_location=device)) # todo 模型位置# 测试模式net.eval()self.model = net
2,处理signal,绑定slot函数
为每一个button绑定对应的处理函数,其中pushButton、pushButton_2、pushButton_3是yy.ui页面中的button对象,ui中必须有这些名称的button才行
pushButton:打开文件,获取预测图片路径
pushButton_2:通过U-net网络模型预测
pushButton_3:通过OpenCV获取轮廓个数以及面积(像素)
def window_init(self):self.ui.pushButton.clicked.connect(self.file)self.ui.pushButton_2.clicked.connect(self.unet)self.ui.pushButton_3.clicked.connect(self.opencv)
3,file函数讲解
点击pushButton
即可选择图片,通过file函数进行处理
image_file
存放照片的路径
后面为例存放处理的图片,通过os.path.basename(image_file)
单独抽取了图片的名称,方面后续存储照片命名
self.ui.label_5.setPixmap(image_file)
,通过label_5进行展示照片,也就是选择照片,然后展示一下
# 打开图片def file(self):print("已点击file")FileDialog = QFileDialog(self.ui.pushButton)# 设置可以打开任何文件FileDialog.setFileMode(QFileDialog.AnyFile)# 文件过滤Filter = "(*.jpg,*.png,*.jpeg,*.bmp,*.gif)|*.jgp;*.png;*.jpeg;*.bmp;*.gif|All files(*.*)|*.*"image_file, _ = FileDialog.getOpenFileName(self.ui.pushButton, 'open file', './','Image files (*.jpg *.gif *.png *.jpeg)') # 选择目录,返回选中的路径 'Image files (*.jpg *.gif *.png *.jpeg)'# 判断是否正确打开文件if not image_file:QMessageBox.warning(self.ui.pushButton, "警告", "文件错误或打开文件失败!", QMessageBox.Yes)returnprint("读入文件成功")print(image_file)self.image_file = image_fileself.base_image = os.path.basename(image_file)print(self.image_file)print(self.base_image)# 设置标签的图片self.ui.label_5.setPixmap(image_file)self.ui.label_5.setScaledContents(True) # 让图片自适应 label 大小
4,unet函数讲解
点击pushButton_2
即可运行到unet函数进行处理
model = self.model
,加载模型
# 开始检测def unet(self):print("U-net...")model = self.model # 加载模型output_size = self.output_size # 规定输入图片的大小img = cv2.imread(self.image_file)# 读取file函数所打开的图片origin_shape = img.shape# print(origin_shape)# 转为灰度图img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)img = cv2.resize(img, (512, 512))# 转为batch为1,通道为1,大小为512*512的数组img = img.reshape(1, 1, img.shape[0], img.shape[1])# 转为tensorimg_tensor = torch.from_numpy(img)# 将tensor拷贝到device中,只用cpu就是拷贝到cpu中,用cuda就是拷贝到cuda中。img_tensor = img_tensor.to(device=device, dtype=torch.float32)# 预测pred = self.model(img_tensor)# 提取结果pred = np.array(pred.data.cpu()[0])[0]# 处理结果pred[pred >= 0.5] = 255pred[pred < 0.5] = 0# 保存图片cv2.imwrite("E:/change_picture/{}".format(self.base_image), pred)# 将预测的结果进行保存# 将预测的图片通过label_6进行展示self.ui.label_6.setPixmap("E:/change_picture/{}".format(self.base_image))self.ui.label_6.setScaledContents(True)
5,opencv函数讲解
该函数的作用是在unet预测的结果之上,将已经识别出来的起毛起球绘制轮廓并且统计个数以及每个起毛起球的面积(像素)
def opencv(self):print("start")print(self.base_image)print("E:/change_picture/{}".format(self.base_image))img = cv2.imread("E:/change_picture/{}".format(self.base_image))#cv2.imshow('img', img)gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为灰度图ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 二值化 # 对图像进行二值处理,小于127为0,大于127为255contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)draw_img = img.copy() # 注意一定要copy要不然会对原图进行改变!!!res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2) # -1表示显示所有轮廓,(0,0,225)BGR表示红色,2为轮廓粗细#cv2.imshow('-1 is All', res)cv2.imwrite("E:/change_picture/result/{}".format(self.base_image), res) # 保存照片# cv2.waitKey(0)# cv2.destroyAllWindows()self.ui.label_7.setPixmap("E:/change_picture/result/{}".format(self.base_image))self.ui.label_7.setScaledContents(True)self.ui.plainTextEdit.clear()n = len(contours) # 轮廓数量print(n)coutoursImg = [] # 创建绘制轮廓列表sum_all = 0for i in range(n):temp = np.zeros(draw_img.shape, np.uint8) # 把图像转换为矩阵coutoursImg.append(temp) # 向列表追加矩阵信息# 绘制轮廓:drawContours()"""绘制轮廓:drawContours()1、img2:等待绘制轮廓的图像2、coutours:要绘制的轮廓(与findContours()函数输出的coutours参数一致《list类型即:列表类型》)3、contourIdx:要绘制一条轮廓还是全部轮廓:如果参数是一个整数或者0表示绘制对应索引号的轮廓[如果是-1表示绘制全部轮廓]4、color:绘制颜色,用B,G,R格式表示5、thickness:表示绘制时的画笔粗细,如果为-1表示用实心绘制6、lineType:表示绘制线轮廓时的线形7、hierarchy:所输出的层次信息8、maxLevel:控制轮廓层次的深度(如果是0表示绘制地0层轮廓)9、offset:偏移参数使轮廓偏移一定位置用[ x , y ]坐标表示"""if cv2.contourArea(contours[i]) >= 0:#对轮廓面积进行筛选,要是太小的话就可以忽略不计sum_all = sum_all + cv2.contourArea(contours[i])print("第" + str(i + 1) + "个毛球的面积是:" + str(cv2.contourArea(contours[i])) + "个像素点")self.ui.plainTextEdit.appendPlainText("第" + str(i + 1) + "个毛球的面积是:" + str(cv2.contourArea(contours[i])) + "个像素点")#cv2.imshow("chulihou", draw_img)# 键盘输入检测,参数为显示时长,如果参数为0就是键盘按下退出显示cv2.waitKey()# 关闭所有窗口cv2.destroyAllWindows()self.ui.label_8.setText(str(len(contours)))self.ui.label_9.setText(str(sum_all))self.ui.label_10.setText("1级")
6,主函数
调用上面所定义的beyondyanyu类即可
if __name__=="__main__":app = QApplication([])gui = beyondyanyu() #初始化gui.window_init()gui.ui.show() #将窗口控件显示在屏幕上app.exit(app.exec_())# 进行死循环展示
四、完整代码
import shutil
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import torch
from model.unet_model import UNet
import numpy as np
from PySide2.QtWidgets import QApplication, QMessageBox,QFileDialog,QMainWindow
from PySide2.QtUiTools import QUiLoader
import cv2
import os# 窗口主类
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')class beyondyanyu():def __init__(self):# 从文件中加载UI定义# 从 UI 定义中动态 创建一个相应的窗口对象# 注意:里面的控件对象也成为窗口对象的属性了# 比如 self.ui.button , self.ui.textEditself.ui = QUiLoader().load('yy.ui') # 这里的参数为ui的路径,对这个ui文件创建对象ui"""对某个控件进行操作会产生一个signal,常通过slot来进行处理signalslot就是通过clicked.connect来绑定某个函数,这个函数用于处理signal"""self.output_size = 480# 加载网络,图片单通道,分类为1。net = UNet(n_channels=1, n_classes=1)# 将网络拷贝到deivce中net.to(device=device)# 加载模型参数net.load_state_dict(torch.load('G:/PyCharm/workspace/my_test/yy_unet/best_model1.pth', map_location=device)) # todo 模型位置# 测试模式net.eval()self.model = netdef window_init(self):self.ui.pushButton.clicked.connect(self.file)self.ui.pushButton_2.clicked.connect(self.unet)self.ui.pushButton_3.clicked.connect(self.opencv)# 打开图片def file(self):print("已点击file")FileDialog = QFileDialog(self.ui.pushButton)# 设置可以打开任何文件FileDialog.setFileMode(QFileDialog.AnyFile)# 文件过滤Filter = "(*.jpg,*.png,*.jpeg,*.bmp,*.gif)|*.jgp;*.png;*.jpeg;*.bmp;*.gif|All files(*.*)|*.*"image_file, _ = FileDialog.getOpenFileName(self.ui.pushButton, 'open file', './','Image files (*.jpg *.gif *.png *.jpeg)') # 选择目录,返回选中的路径 'Image files (*.jpg *.gif *.png *.jpeg)'# 判断是否正确打开文件if not image_file:QMessageBox.warning(self.ui.pushButton, "警告", "文件错误或打开文件失败!", QMessageBox.Yes)returnprint("读入文件成功")print(image_file) # 'C:\\', 默认C盘打开self.image_file = image_fileself.base_image = os.path.basename(image_file)print(self.image_file)print(self.base_image)# 设置标签的图片self.ui.label_5.setPixmap(image_file) #输入为图片路径,比如当前文件内的logo.png图片# self.label.setFixedSize(600, 400) # 设置显示固定尺寸,可以根据图片的像素长宽来设置self.ui.label_5.setScaledContents(True) # 让图片自适应 label 大小# 开始检测def unet(self):print("U-net...")model = self.modeloutput_size = self.output_sizeimg = cv2.imread(self.image_file)origin_shape = img.shape# print(origin_shape)# 转为灰度图img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)img = cv2.resize(img, (512, 512))# 转为batch为1,通道为1,大小为512*512的数组img = img.reshape(1, 1, img.shape[0], img.shape[1])# 转为tensorimg_tensor = torch.from_numpy(img)# 将tensor拷贝到device中,只用cpu就是拷贝到cpu中,用cuda就是拷贝到cuda中。img_tensor = img_tensor.to(device=device, dtype=torch.float32)# 预测pred = self.model(img_tensor)# 提取结果pred = np.array(pred.data.cpu()[0])[0]# 处理结果pred[pred >= 0.5] = 255pred[pred < 0.5] = 0# 保存图片#im0 = cv2.resize(pred, self.origin_shape)cv2.imwrite("E:/change_picture/{}".format(self.base_image), pred)# 目前的情况来看,应该只是ubuntu下会出问题,但是在windows下是完整的,所以继续#self.right_img.setPixmap(QPixmap("images/tmp/single_result.jpg"))self.ui.label_6.setPixmap("E:/change_picture/{}".format(self.base_image))self.ui.label_6.setScaledContents(True)def opencv(self):print("start")print(self.base_image)print("E:/change_picture/{}".format(self.base_image))img = cv2.imread("E:/change_picture/{}".format(self.base_image))#cv2.imshow('img', img)gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为灰度图ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 二值化 # 对图像进行二值处理,小于127为0,大于127为255contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)draw_img = img.copy() # 注意一定要copy要不然会对原图进行改变!!!res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2) # -1表示显示所有轮廓,(0,0,225)BGR表示红色,2为轮廓粗细#cv2.imshow('-1 is All', res)cv2.imwrite("E:/change_picture/result/{}".format(self.base_image), res) # 保存照片# cv2.waitKey(0)# cv2.destroyAllWindows()self.ui.label_7.setPixmap("E:/change_picture/result/{}".format(self.base_image))self.ui.label_7.setScaledContents(True)self.ui.plainTextEdit.clear()n = len(contours) # 轮廓数量print(n)coutoursImg = [] # 创建绘制轮廓列表sum_all = 0for i in range(n):temp = np.zeros(draw_img.shape, np.uint8) # 把图像转换为矩阵coutoursImg.append(temp) # 向列表追加矩阵信息# 绘制轮廓:drawContours()"""绘制轮廓:drawContours()1、img2:等待绘制轮廓的图像2、coutours:要绘制的轮廓(与findContours()函数输出的coutours参数一致《list类型即:列表类型》)3、contourIdx:要绘制一条轮廓还是全部轮廓:如果参数是一个整数或者0表示绘制对应索引号的轮廓[如果是-1表示绘制全部轮廓]4、color:绘制颜色,用B,G,R格式表示5、thickness:表示绘制时的画笔粗细,如果为-1表示用实心绘制6、lineType:表示绘制线轮廓时的线形7、hierarchy:所输出的层次信息8、maxLevel:控制轮廓层次的深度(如果是0表示绘制地0层轮廓)9、offset:偏移参数使轮廓偏移一定位置用[ x , y ]坐标表示"""if cv2.contourArea(contours[i]) >= 0:sum_all = sum_all + cv2.contourArea(contours[i])print("第" + str(i + 1) + "个毛球的面积是:" + str(cv2.contourArea(contours[i])) + "个像素点")self.ui.plainTextEdit.appendPlainText("第" + str(i + 1) + "个毛球的面积是:" + str(cv2.contourArea(contours[i])) + "个像素点")#cv2.imshow("chulihou", draw_img)# 键盘输入检测,参数为显示时长,如果参数为0就是键盘按下退出显示cv2.waitKey()# 关闭所有窗口cv2.destroyAllWindows()self.ui.label_8.setText(str(len(contours)))self.ui.label_9.setText(str(sum_all))self.ui.label_10.setText("1级")# # 设置标签的图片# self.ui.label_2.setPixmap(self.image_file) ##输入为图片路径,比如当前文件内的logo.png图片# # self.label.setFixedSize(600, 400) # 设置显示固定尺寸,可以根据图片的像素长宽来设置# self.ui.label_2.setScaledContents(True) # 让图片自适应 label 大小if __name__=="__main__":app = QApplication([])gui = beyondyanyu() #初始化gui.window_init()gui.ui.show() #将窗口控件显示在屏幕上app.exit(app.exec_())