第七届工程训练大赛
2021.4.9,浙江省举办了第七届工程训练大赛,我们组参加的是垃圾分类的项目,我们组顺利挺进决赛,但是我们看决赛规则并没有标注多种垃圾分类,我们没有完全的准备好应对多种垃圾分类,所以与国赛是无缘了!
前言
随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文通过竞赛就介绍了机器学习的基础内容,和参加竞赛需要的界面设计
我们预赛的规则如下,但是我对显示垃圾名称有点想不明白,分四种垃圾不就好了,还要把菜叶橘子皮分辨出来作甚?
我先自我介绍一下,我是来自计算机科学与技术一名大二本科生,我和我的两名队友参加第七届工程训练大赛,我在队伍中担任得任务是进行图像处理,模型训练和,stm322F4串口通讯与界面设计,所以我接下来主要讲述我实现得代码,讲的不好,望谅解!
(1)软件安装准备
软件管家中都有这些软件下载,大家不妨去微信公众号关注一下,下载操作步骤是真的详细!!
我安装了Anaconda pycharm python3.6(版本不要太高) Qt界面设计软件
通过python3.6进行tensflow的下载
其他的各种库的下载就需要你先搭建好环境,然后再pycharm中如下的位置进行下载
格式: pip install numpy (以numpy为例)
我们用的笔记本电脑进行训练,然后将模型训练好放在微机win10中进行运行界面显示(注意:训练的机子一定要好!!!条件允许直接上台式电脑)
(2)垃圾分类的训练模型
首先,我展示我参加比赛的最终代码的文件如下
然后我们对于垃圾分类这件事本身来看,好像很好理解,就是区分垃圾,可是怎么实现的?可能一点方向都没有。那么我们直接先从上面的文件开始着手会快很多。
就好比我们是怎么进行分类物品,是不是一个反复学习的过程。但是最基础是什么?是我们具备学习的能力,这就是程序模型的框架
第一个.py文件中是训练模型,框架是使用 keras 中的 resnet 模型,然后我们通过训练,将这个模型训练成具有针对性的模型,专门处理图像识别。
这个是训练集,就是在dataset1文件夹中放入图片(我当时是分类成10种,每种420张图片)来进行训练,不过硬件条件允许的话,照片数量越多越好。
不过要特别注意放入图片不要有中文路径,并且每种文件图像数量尽量相同
# 处理好的224*224文件夹放在本程序同目录下...
train_path_A = './dataset1/train/A/'
train_path_B = './dataset1/train/B/'
train_path_C = './dataset1/train/C/'
train_path_D = './dataset1/train/D/'
train_path_E = './dataset1/train/E/'
train_path_F = './dataset1/train/F/'
train_path_G = './dataset1/train/G/'
train_path_H = './dataset1/train/H/'
train_path_I = './dataset1/train/I/'
train_path_J = './dataset1/train/J/'
mglist_train_A = os.listdir(train_path_A) #导入训练列表
imglist_train_B = os.listdir(train_path_B)
imglist_train_C = os.listdir(train_path_C)
imglist_train_D = os.listdir(train_path_D)
imglist_train_E = os.listdir(train_path_E)
imglist_train_F = os.listdir(train_path_F)
imglist_train_G = os.listdir(train_path_G)
imglist_train_H = os.listdir(train_path_H)
imglist_train_I = os.listdir(train_path_I)
imglist_train_J = os.listdir(train_path_J)
这里定义两个 numpy 对象,X_test输入数组 和 Y_test标签数组,np.empty为创建一个空的多维数组。
3 是图片的通道数(RGB三色)
因为一共有十种图片,所以Y_train() 第二项设置为 10
X_train = np.empty((len(imglist_train_A) + len(imglist_train_B) + len(imglist_train_C) + len(imglist_train_D) + len(imglist_train_E)
+ len(imglist_train_F) + len(imglist_train_G) + len(imglist_train_H) + len(imglist_train_I) + len(imglist_train_J), 224, 224, 3))
Y_train = np.empty((len(imglist_train_A) + len(imglist_train_B) + len(imglist_train_C) + len(imglist_train_D) + len(imglist_train_E)
+ len(imglist_train_F) + len(imglist_train_G) + len(imglist_train_H) + len(imglist_train_I) + len(imglist_train_J), 10))
训练好的模型保存在以下的模型上
#保存变量训练文件.h5
model.save('my_resnet_model_ABCD.h5')
model.save_weights('my_resnet_weights_model_ABCD.h5') #保存变量训练文件
#载入变量训练文件.h5
model = tf.keras.models.load_model('my_resnet_model_ABCD.h5')
model.load_weights('my_resnet_weights_model_ABCD.h5')
如果更换数据的训练种类,这些参数需要对应更改 箭头所指向的数据需要重点去看看,注释上面都很详细,就是batch_size最好是偶数
训练模型有了,但是我们想让它给予我们一个反馈准确值,这个时候就需要测试集,训练集和测试集图片的比例是10:3左右就可以
# 预测:predict(img)
for i in range(10):
img=X_test[i] #在数据集上取得一个样本
print('某个测试图片X_test[i]的形态:', img.shape)
img=(np.expand_dims(img,0)) #表示在0位置添加一个维度数据[[[...],[...],[...],...,[...]]]
# tf.keras 模型输入形态要增加一个维度(1,28,28)
print('某个测试图片数据形态:',img.shape)
predictions=model.predict(img)
#看下第0项图片样本的预测结果是什么
print('模型预测结果predictions[i]:',predictions)
#第0项图片预测最大的概率项是哪一项,就是说最可能是哪种衣服
print('模型预测最大的概率项:',np.argmax(predictions))
#再查询第0项图片真正的标签是什么样的
print('查询第i项图片真正的标签是:',Y_test[i])
#显示一个图片:
plt.figure()
plt.imshow(X_test[i])
plt.colorbar()
plt.grid(False)
plt.show()
(3)Qt界面设计
我当时做界面设计的时候,就是尽量跟着显示屏大小去布置内容的,所以内容比较紧凑,背景是绿色是不难想的,和这个主题有关。
如何在textedit上显示文字,如何触发按钮?
# 在各个write_ui...的界面textEdit...中写入str
self.ms.text_print1.connect(self.write_ui1)
self.ms.text_print2.connect(self.write_ui2)
self.ms.text_print3.connect(self.write_ui3)
self.ms.text_print4.connect(self.write_ui4)
self.ms.text_print5.connect(self.write_ui5)
# 初始化线程参数
self.ui.pushButton.clicked.connect(self.handlePlay) # 播放
self.ui.pushButton_2.clicked.connect(self.handleCircle) # 循环播放
self.ui.pushButton_7.clicked.connect(self.handleStopPlay) # 停止播放
self.ui.pushButton_3.clicked.connect(self.handleStart) # 检测开始
self.ui.pushButton_4.clicked.connect(self.handleStop2) # 检测停止
self.ui.pushButton_5.clicked.connect(self.handleShow) # 显示图像
self.ui.pushButton_6.clicked.connect(self.handleQuit) # 关闭图像
# 在各个textEdit...控件上写入字符
def write_ui1(self, str1):
self.ui.textEdit.append(str1 + '\n') # 在textEdit写入str
def write_ui2(self, str2):
self.ui.textEdit_2.append(str2 + '\n') # 在textEdit_2写入str
def write_ui3(self, str3):
self.ui.textEdit_3.append(str3 + '\n') # 在textEdit_3写入str
def write_ui4(self, str4):
self.ui.textEdit_4.append(str4 + '\n') # 在textEdit_4写入str
def write_ui5(self, str5):
self.ui.textEdit_5.append(str5 + '\n') # 在textEdit_5写入str
那么循环播放和停止播放又是怎么实现的呢?我是通过触发按钮来进行循环播放和关闭标志位来进行停止播放,代码如下
# 循环播放
def handleCircle(self):
global ThreadFlag1 # 全局变量
ThreadFlag1 = 0
for i in range(20):
j = 0
cap = cv2.VideoCapture('./refuse classification video.mp4')
while (cap.isOpened()): # cap.grab()下一帧是否为空
info = ''
info += f'\t-- 垃圾回收宣传片循环播放 --\n'
self.ms.text_print1.emit(info) # 在textEdit写入str1
ret, frame = cap.read()
cv2.imshow('refuse classification video.mp4', frame)
j += 1
if (j == 835): # 防止视频最后的空帧报错
break
# 停止宣传片
if (ThreadFlag1 == 1):
self.ms.text_print1.emit(f'\t-- 垃圾回收宣传片停止播放 --\n')
break
k = cv2.waitKey(20)
# 关闭窗口
cap.release()
cv2.destroyAllWindows() # 删除视频窗口
# 停止播放
def handleStopPlay(self):
global ThreadFlag1 # 全局变量
if(ThreadFlag1==0&cap.isOpened()):
ThreadFlag1 = 1
当时在比赛前几周的时候,我就想开关摄像头去看垃圾桶内的环境,因为我们的垃圾筒的上半部分是黑箱,不好直接观看
# 显示图像
def handleShow(self):
global ThreadFlag3 # 全局变量
ThreadFlag3 = 0
self.ms.text_print3.emit(f' -- 摄像头打开,请投放垃圾! --\n')
while 1:
# get a frame
ret, frame = cap.read()
# show a frame
cv2.imshow("capture", frame)
if ThreadFlag3 == 1:
# 关闭窗口
cv2.destroyAllWindows() # 删除视频窗口
self.ms.text_print3.emit(f' -- 摄像头已经关闭,开始识别! --\n')
break
cv2.waitKey(1)
# 关闭图像
def handleQuit(self):
global ThreadFlag3 # 全局变量
if ThreadFlag3 == 0:
ThreadFlag3 = 1
然后我们通过接受串口的发送去完成什么时候开始图像识别?什么时候开始向下位机传输信息,转动舵机和垃圾筒
# 接收串口数据
def handleRecv(self):
global final
global no
global ThreadFlag2 # 全局变量
ThreadFlag2 = 0
ser.flushInput() # 先清除一下缓冲区
ser.flushInput()
def download():
while 1:
self.ms.text_print2.emit(f'\t-- 接收到串口数据 --\n')
mcu = ser.read(1) ## 读取1个数据
mcu = ser.read(1) ## 读取1个数据
mcu = ser.read(1) ## 读取1个数据
mcu = ser.read(1) ## 读取1个数据
mcu = ser.read(1) ## 读取1个数据
print("接收到第一个数据:", mcu)
self.ms.text_print2.emit(f"\t-- 接收到数据:" + str(mcu))
if mcu == b'5': # 若收到下位机发送的数据/字符,
self.ms.text_print2.emit(f'\t-- 开始拍照 --')
mcu = '' # 清空数据
mcu = '' # 清空数据
mcu = '' # 清空数据
frameone,frame = cap.read() # 读取摄像头
# cv2.imshow("capture", frame) # 显示照片
cv2.waitKey(1) # 等0.1秒
cv2.imwrite("D:\\project_garbage\\picture\\0.jpg", frame) # 保存图片,自己新建个picture文件夹
self.ms.text_print2.emit(f'\t-- 保存照片 --')
self.predict() # 进入预测函数
最后我们开始预测实现图像识别。
在我做这个界面设计的时候遇到了很多的问题,我在这里讲述一下
第一个问题就是如图的0 或者 1 的区别
0 指的是电脑显示屏本身没有摄像头,而是通过外设摄像头来进行拍照
1 指的是比如笔记本电脑,本身就有摄像头,可以自身摄像头和外设摄像头相互切换
在微机调试始中终没有发现这个问题,耽搁了我一会时间
第二个问题是图像导入的路径非常值得注意 / \的区别
导入图片,读出图片
cv2.imwrite("D:\\project_garbage\\picture\\0.jpg", frame) # 保存图片,自己新建个picture文件夹
img_path = "D:/project_garbage/picture/0.jpg"
第三个问题就是如图后串口传输数据的时候接受不到,这个问题种类很多,可以多通过百度解决,我们是python 与单片机32 下位机进行串口通讯。 末尾上加 \r\n很关键
总结
第七届工程训练大赛最终虽无缘国赛,但是我们组员在实验室一起奋斗的场景历历在目,我觉得这段记忆是非常珍贵,我们有一起努力过!奋斗过!我觉得就值得了,竞赛之外的友谊是非常难得的!
首先分享一下在比赛前一个晚上,护着我们的宝贝垃圾桶进实验楼(两位队友)
接下来我来分享一下我们宁波杭州湾之旅行的照片
宁波工程学院的鸟巢型书吧,我是真的喜欢!
这是比赛前,风特别大的时候,队友在进行拍照,我在重新训练模型,献上我的垃圾桶,充满神秘感!!
最后留下一张参赛证,这就是回忆!!!