花了几天功夫做了一个YOLOv5的PyQT可视化程序,主要针对多幅图片训练、自动标注和检测展示。涉及正在进行的项目,暂时不开源。在开发过程中,踩了不少坑,这里简单做一些记录。
项目使用到的开源代码:
YOLOv5(5.0+6.0):https://github.com/ultralytics/yolov5
自动标注程序:https://github.com/cnyvfang/labelGo-Yolov5AutoLabelImg
整体效果演示如下:
交互式目标检测软件演示
使用QtDesigner设计的ui文件,可以通过PyUIC自动生成对应的py文件。

生成的文件仅包含窗体对象,浏览时,可以添加下方的执行程序:
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Window = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(Window)
Window.show()
sys.exit(app.exec_())
在开发完之后,用pyinstaller打包应用程序,调用子线程训练时,卡在一半不动,然后程序崩溃自动重启。
查阅相关资料,在windows上Pyinstaller打包多进程程序需要添加代码使其支持多线程操作,添加代码如下:
import multiprocessing
if __name__ == "__main__":
multiprocessing.freeze_support() # 支持pyinstaller,防止打包闪退
pyqt样式美化有很多种开源css文件,本次开发中尝试了三种方案。
安装qdarkstyle:
pip install qdarkstyle
qdarkstyle包括了深色和浅色两种主题,使用方式和效果如下:
深色主题:
import qdarkstyle
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) # Qdark深色样例
Window = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(Window)
Window.show()
sys.exit(app.exec_())

浅色主题
from qdarkstyle.light.palette import LightPalette
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet(qdarkstyle.load_stylesheet(qt_api='pyqt5', palette=LightPalette())) # Qdark浅色样例
Window = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(Window)
Window.show()
sys.exit(app.exec_())

QCandyUi安装:
pip install QCandyUi
QCandyUi使用:
from qt_material import apply_stylesheet
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
apply_stylesheet(app, theme='light_teal.xml')
Window = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(Window)
Window.show()
sys.exit(app.exec_())

这里使用了light_teal.xml这个主题,还有其它主题可供选择:
'dark_amber.xml',
'dark_blue.xml',
'dark_cyan.xml',
'dark_lightgreen.xml',
'dark_pink.xml',
'dark_purple.xml',
'dark_red.xml',
'dark_teal.xml',
'dark_yellow.xml',
'light_amber.xml',
'light_blue.xml',
'light_cyan.xml',
'light_cyan_500.xml',
'light_lightgreen.xml',
'light_pink.xml',
'light_purple.xml',
'light_red.xml',
'light_teal.xml',
'light_yellow.xml'
这些主题都比较简陋,基本上是换个颜色而已
下面三套QSS样式取自知乎刘典武
为了方便下载,我上传到了资源中:https://download.csdn.net/download/qq1198768105/86775044
总共有三套样式,对应黑色、白色、蓝色,本项目使用的是lightblue这套样式。

使用方式:
if __name__ == "__main__":
multiprocessing.freeze_support() # 支持pyinstaller,防止打包闪退
app = QtWidgets.QApplication(sys.argv)
styleFile = 'ui/lightblue.css'
with open(styleFile, 'r') as f:
qssStyle = f.read()
app.setStyleSheet(qssStyle)
Window = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi()
ui.show()
sys.exit(app.exec_())
下面是项目中反复使用的一些基础函数和命令。
app.setWindowIcon(QIcon('ui/icon.png'))
self.pushButton.setEnabled(True)
self.setFixedSize(self.width(), self.height())
self.setStyleSheet('''
#mainWindow{background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,stop:0 rgb(255,255,255),stop:.99 rgb(151, 201, 242));}
''')
fill_bg = '''
QWidget {
background: transparent;
}
'''
self.scroll.setStyleSheet(fill_bg)
def open_train_window(self):
self.train_window = train_window.train_Window()
self.train_window.show()
def open_output(self):
path = os.getcwd() + '\\' + 'outputs'
os.system(f"start explorer {path}")
子线程通过信号与槽函数机制来传递参数
self.thread = DetectionThread(self)
self.thread.start()
self.thread.progressBarValue.connect(self.callback)
def callback(self, i):
self.progressBar.setValue(i)
class DetectionThread(QThread):
progressBarValue = pyqtSignal(int)
self.progressBarValue.emit(100)
self.lineEdit.textChanged[str].connect(self.get_epoch)
def get_epoch(self, epoch):
self.epoch = epoch
调用子目录中的程序,在import之前需添加根路径
sys.path.append("yolov5")
Root = os.path.split(os.path.abspath(__file__))[0]
本项目开发之中,遇到的一个重点问题是在ListView中动态添加缩略图,其中分解成两个问题,一个是ListView的使用,另一个是动态缩略图的添加。
ListView在pyqt中有个对应的控件是QScrollArea,找到了一个使用例程:
参考自:https://blog.csdn.net/Yibaomeimei/article/details/124694955
import sys
from ui_test import *
from PyQt5.QtWidgets import *
import random
class test_ui(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
#生成随机的列表作为我们要显示的输出结果
self.results = [[random.randint(1, 4) for j in range(1, 5)] for i in range(1, 15)]
self.topFiller = QWidget()
self.topFiller.setMinimumSize(500, 1000)
for i in range(len(self.results)):
label = QLabel(self.topFiller)
label.resize(420, 30)
label.setText(str("{:" "<13}".format(self.results[i][0]) + ("{:" "<15}".format(self.results[i][1]))
+ "{:" "<13}".format(self.results[i][2])
+ "{:" "<13}".format(self.results[i][3])))
label.move(10, 30 * i)
self.vbox = QVBoxLayout()
self.scroll = QScrollArea()
self.scroll.setWidget(self.topFiller)
self.vbox.addWidget(self.scroll)
self.frame.setLayout(self.vbox)
if __name__ == "__main__":
app = QApplication(sys.argv)
mainwindow = test_ui()
mainwindow.show()
sys.exit(app.exec_())
缩略图加载显示找到了一个例程:
参考自:https://blog.csdn.net/weixin_42512684/article/details/103414691
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import os
import sys
class img_viewed(QWidget):
def __init__(self,parent =None):
super(img_viewed,self).__init__(parent)
self.parent = parent
self.width = 960
self.height = 500
self.scroll_ares_images = QScrollArea(self)
self.scroll_ares_images.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget(self)
self.scrollAreaWidgetContents.setObjectName('scrollAreaWidgetContends')
# 进行网络布局
self.gridLayout = QGridLayout(self.scrollAreaWidgetContents)
self.scroll_ares_images.setWidget(self.scrollAreaWidgetContents)
self.scroll_ares_images.setGeometry(20, 50, self.width, self.height)
self.vertocal1 = QVBoxLayout()
# self.meanbar = QMenu(self)
# self.meanbar.addMenu('&菜单')
# self.openAct = self.meanbar.addAction('&Open',self.open)
# self.startAct =self.meanbar.addAction('&start',self.start_img_viewer)
self.open_file_pushbutton =QPushButton(self)
self.open_file_pushbutton.setGeometry(150,10,100,30)
self.open_file_pushbutton.setObjectName('open_pushbutton')
self.open_file_pushbutton.setText('打开文件夹...')
self.open_file_pushbutton.clicked.connect(self.open)
self.start_file_pushbutton = QPushButton(self)
self.start_file_pushbutton.setGeometry(750, 10, 100, 30)
self.start_file_pushbutton.setObjectName('start_pushbutton')
self.start_file_pushbutton.setText('开始')
self.start_file_pushbutton.clicked.connect(self.start_img_viewer)
self.vertocal1.addWidget(self.scroll_ares_images)
self.show()
#设置图片的预览尺寸;
self.displayed_image_size = 100
self.col = 0
self.row =0
self.initial_path =None
def open(self):
file_path = QFileDialog.getExistingDirectory(self, '选择文文件夹', '/')
if file_path ==None:
QMessageBox.information(self,'提示','文件为空,请重新操作')
else:
self.initial_path =file_path
def start_img_viewer(self):
if self.initial_path:
file_path = self.initial_path
print('file_path为{}'.format(file_path))
print(file_path)
img_type = 'jpg'
if file_path and img_type:
png_list = list(i for i in os.listdir(file_path) if str(i).endswith('.{}'.format(img_type)))
print(png_list)
num = len(png_list)
if num !=0:
for i in range(num):
image_id = str(file_path + '/' + png_list[i])
print(image_id)
pixmap = QPixmap(image_id)
self.addImage(pixmap, image_id)
print(pixmap)
QApplication.processEvents()
else:
QMessageBox.warning(self,'错误','生成图片文件为空')
self.event(exit())
else:
QMessageBox.warning(self,'错误','文件为空,请稍后')
else:
QMessageBox.warning(self, '错误', '文件为空,请稍后')
def loc_fil(self,stre):
print('存放地址为{}'.format(stre))
self.initial_path = stre
def geng_path(self,loc):
print('路径为,,,,,,{}'.format(loc))
def gen_type(self,type):
print('图片类型为:,,,,{}'.format(type))
def addImage(self, pixmap, image_id):
#图像法列数
nr_of_columns = self.get_nr_of_image_columns()
#这个布局内的数量
nr_of_widgets = self.gridLayout.count()
self.max_columns =nr_of_columns
if self.col < self.max_columns:
self.col =self.col +1
else:
self.col =0
self.row +=1
print('行数为{}'.format(self.row))
print('此时布局内不含有的元素数为{}'.format(nr_of_widgets))
print('列数为{}'.format(self.col))
clickable_image = QClickableImage(self.displayed_image_size, self.displayed_image_size, pixmap, image_id)
clickable_image.clicked.connect(self.on_left_clicked)
clickable_image.rightClicked.connect(self.on_right_clicked)
self.gridLayout.addWidget(clickable_image, self.row, self.col)
def on_left_clicked(self,image_id):
print('left clicked - image id = '+image_id)
def on_right_clicked(self,image_id):
print('right clicked - image id = ' + image_id)
def get_nr_of_image_columns(self):
#展示图片的区域
scroll_area_images_width = self.width
if scroll_area_images_width > self.displayed_image_size:
pic_of_columns = scroll_area_images_width // self.displayed_image_size #计算出一行几列;
else:
pic_of_columns = 1
return pic_of_columns
def setDisplayedImageSize(self,image_size):
self.displayed_image_size =image_size
class QClickableImage(QWidget):
image_id =''
def __init__(self,width =0,height =0,pixmap =None,image_id = ''):
QWidget.__init__(self)
self.layout =QVBoxLayout(self)
self.label1 = QLabel()
self.label1.setObjectName('label1')
self.lable2 =QLabel()
self.lable2.setObjectName('label2')
self.width =width
self.height = height
self.pixmap =pixmap
if self.width and self.height:
self.resize(self.width,self.height)
if self.pixmap:
pixmap = self.pixmap.scaled(QSize(self.width,self.height),Qt.KeepAspectRatio,Qt.SmoothTransformation)
self.label1.setPixmap(pixmap)
self.label1.setAlignment(Qt.AlignCenter)
self.layout.addWidget(self.label1)
if image_id:
self.image_id =image_id
self.lable2.setText(image_id)
self.lable2.setAlignment(Qt.AlignCenter)
###让文字自适应大小
self.lable2.adjustSize()
self.layout.addWidget(self.lable2)
self.setLayout(self.layout)
clicked = pyqtSignal(object)
rightClicked = pyqtSignal(object)
def mousePressEvent(self,ev):
print('55555555555555555')
if ev.button() == Qt.RightButton:
print('dasdasd')
#鼠标右击
self.rightClicked.emit(self.image_id)
else:
self.clicked.emit(self.image_id)
def imageId(self):
return self.image_id
if __name__ =='__main__':
app =QApplication(sys.argv)
windo = img_viewed()
windo.show()
sys.exit(app.exec_())
开发中基本沿用了这套加载方案,将原始的网格布局改成垂直盒布局,然后每读取一张图片将其实例化,先使用pixmap.scaled缩放到合适尺寸,再使用addWidget添加到布局中。然后对每一张图片设置了mousePressEvent点击事件监听,点击之后替换主页面图片。
项目中,需要获取子线程中检测进度,将其实时传递到主线程中,进行进度条更新。然而,子线程运行的是另一个子文件夹中的py程序,使用了全局变量、公共对象等方法均没成功。最后想到一个文件读写的方式,单独通过一个cfg文件来传递参数。
下面提供一个示例,包括cfg文件的读取和写入操作:
import configparser
from configparser import ConfigParser
CONFIG_FILE = "glovar.cfg"
process = "1"
if __name__ == "__main__":
# 将conf对象中的数据写入到文件中
conf = configparser.ConfigParser()
cfg_file = open("glovar.cfg", 'w')
conf.add_section("default") # 在配置文件中增加一个段
# 第一个参数是段名,第二个参数是选项名,第三个参数是选项对应的值
conf.set("default", "process", process)
conf.write(cfg_file)
cfg_file.close()
# 读取cfg数据
config_parser = ConfigParser()
config_parser.read('glovar.cfg')
config = config_parser['default']
print(config['process'])
视频演示中,使用了Coco数据集中的图片进行演示,代码中需要写出其80个类别:
英文类别:
names: [ 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light',
'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard',
'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear',
'hair drier', 'toothbrush' ]
中文类别:
names = ["人", "自行车", "汽车", "摩托车", "飞机", "公交车",
"火车", "卡车", "船", "交通灯", "消防栓",
"停止标志", "停车计时器", "长凳", "鸟", "猫", "狗",
"马", "羊", "牛", "大象", "熊", "斑马", "长颈鹿",
"背包", "雨伞", "手提包", "领带", "手提箱", "飞盘", "滑雪",
"滑雪板", "体育用球", "风筝", "棒球棒", "棒球手套",
"滑板", "冲浪板", "网球拍", "瓶子", "红酒杯", "杯子",
"叉子", "小刀", "勺子", "碗", "香蕉", "苹果", "三明治",
"橘子", "西兰花", "胡萝卜", "热狗", "披萨", "甜甜圈", "蛋糕",
"椅子", "沙发", "盆栽", "床", "餐桌", "厕所", "显示器",
"笔记本", "鼠标", "遥控器", "键盘", "手机", "微波炉", "烤箱",
"吐司机", "水槽", "冰箱", "书", "闹钟", "花瓶", "剪刀",
"玩具熊", "吹风机", "牙刷"]
txt格式:
person
bicycle
car
motorbike
aeroplane
bus
train
truck
boat
traffic light
fire hydrant
stop sign
parking meter
bench
bird
cat
dog
horse
sheep
cow
elephant
bear
zebra
giraffe
backpack
umbrella
handbag
tie
suitcase
frisbee
skis
snowboard
sports ball
kite
baseball bat
baseball glove
skateboard
surfboard
tennis racket
bottle
wine glass
cup
fork
knife
spoon
bowl
banana
apple
sandwich
orange
broccoli
carrot
hot dog
pizza
donut
cake
chair
sofa
pottedplant
bed
diningtable
toilet
tvmonitor
laptop
mouse
remote
keyboard
cell phone
microwave
oven
toaster
sink
refrigerator
book
clock
vase
scissors
teddy bear
hair drier
toothbrush
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我即将开始一个将录制和编辑音频文件的项目,我正在寻找一个好的库(最好是Ruby,但会考虑Java或.NET以外的任何库)以进行实时可视化波形。有人知道我应该从哪里开始搜索吗? 最佳答案 要流入浏览器的数据量很大。Flash或Flex图表可能是唯一能提高内存效率的解决方案。Javascript图表往往会因大型数据集而崩溃。 关于ruby-Ruby中的波形可视化,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.c
我想知道我的代码是否在rspec下运行。这可能吗?原因是我正在加载一些错误记录器,这些记录器在测试期间会被故意错误(expect{x}.toraise_error)弄得乱七八糟。我查看了我的ENV变量,没有(明显的)测试环境变量的迹象。 最佳答案 在spec_helper.rb的开头添加:ENV['RACK_ENV']='test'现在您可以在代码中检查RACK_ENV是否经过测试。 关于ruby-检测由RSpec、Ruby运行的代码,我们在StackOverflow上找到一个类似的问题
我正在使用rubydaemongem。想知道如何向停止操作添加一些额外的步骤?希望我能检测到停止被调用,并向其添加一些额外的代码。任何人都知道我如何才能做到这一点? 最佳答案 查看守护程序gem代码,它似乎没有用于此目的的明显扩展点。但是,我想知道(在守护进程中)您是否可以捕获守护进程在发生“停止”时发送的KILL/TERM信号...?trap("TERM")do#executeyourextracodehereend或者你可以安装一个at_exit钩子(Hook):-at_exitdo#executeyourextracodehe
我有一个定义类的Ruby脚本。我希望脚本执行语句BoolParser.generate:file_base=>'bool_parser'仅当脚本作为可执行文件被调用时,而不是当它被irbrequire(或通过-r在命令行上传递)时。我可以用什么来包装上面的语句,以防止它在我的Ruby文件加载时执行? 最佳答案 条件$0==__FILE__...!/usr/bin/ruby1.8classBoolParserdefself.generate(args)p['BoolParser.generate',args]endendif$0==_
我有以下字符串,我想检测那里的换行符。但是Ruby的字符串方法include?检测不到它。我正在运行Ruby1.9.2p290。我哪里出错了?"/'ædres/\nYour".include?('\n')=>false 最佳答案 \n需要在双引号内,否则无法转义。>>"\n".include?'\n'=>false>>"\n".include?"\n"=>true 关于Ruby无法检测字符串中的换行符,我们在StackOverflow上找到一个类似的问题: h
文章目录1.自动驾驶实战:基于Paddle3D的点云障碍物检测1.1环境信息1.2准备点云数据1.3安装Paddle3D1.4模型训练1.5模型评估1.6模型导出1.7模型部署效果附录show_lidar_pred_on_image.py1.自动驾驶实战:基于Paddle3D的点云障碍物检测项目地址——自动驾驶实战:基于Paddle3D的点云障碍物检测课程地址——自动驾驶感知系统揭秘1.1环境信息硬件信息CPU:2核AI加速卡:v100总显存:16GB总内存:16GB总硬盘:100GB环境配置Python:3.7.4框架信息框架版本:PaddlePaddle2.4.0(项目默认框架版本为2.3
我有一个连接到服务器的rubytcpsocket客户端。在发送数据之前如何检查套接字是否已连接?我是否尝试“拯救”断开连接的tcpsocket,重新连接然后重新发送?如果是这样,有没有人有一个简单的代码示例,因为我不知道从哪里开始:(我很自豪我设法在rails中获得了一个持久连接的客户端tcpsocket。然后服务器决定杀死客户端,一切都崩溃了;)编辑我已经使用此代码解决了一些问题-如果未连接,它将尝试重新连接,但如果服务器已关闭则不会处理这种情况(它将继续重试)。这是正确方法的开始吗?谢谢defself.write(data)begin@@my_connection.write(
我在一台Windows764位机器上使用Sass和Ruby(最新版本),我正在我的家庭服务器上处理一个共享文件夹。(但是,我不得不承认问题本身也出现在服务器上,因为我试图安装Ruby并直接-watch服务器上的文件)。问题如下:如果我第一次保存,检测到变化,我的style.css被直接覆盖。之后,我总是需要保存多达7次才能覆盖style.css。每次都会检测到更改,但不会编译任何内容。这是一个屏幕:>>>Sassiswatchingforchanges.PressCtrl-Ctostop.overwritestyle.css>>>Changedetectedto:E:/Websites
我所在的团队负责管理公司面向公众的云平台。我们拥有大量运行面向互联网的VM的用户群。我想对我们的地址空间进行自动扫描,看看是否有人在运行Rails应用程序,这样我就可以通知他们升级他们的Rails版本,以避免本周出现的严重安全漏洞。我注意到在某些Apache部署中,有一个有用的PassengerHeader:X-Powered-By:PhusionPassenger(mod_rails/mod_rack)2.0.3然而,这并不可靠。我想知道是否有一种可靠的方法来检测在Web服务器后面运行的Rails,无论是使用响应header还是某种可以确定的GET/POST。谢谢!