+-
第七届工程训练大赛 --垃圾分类
首页 专栏 机器学习 文章详情
1
头图

第七届工程训练大赛 --垃圾分类

Al_tair 发布于 4 月 19 日

第七届工程训练大赛

2021.4.9,浙江省举办了第七届工程训练大赛,我们组参加的是垃圾分类的项目,我们组顺利挺进决赛,但是我们看决赛规则并没有标注多种垃圾分类,我们没有完全的准备好应对多种垃圾分类,所以与国赛是无缘了!

前言

随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文通过竞赛就介绍了机器学习的基础内容,和参加竞赛需要的界面设计</font>

我们预赛的规则如下,但是我对显示垃圾名称有点想不明白,分四种垃圾不就好了,还要把菜叶橘子皮分辨出来作甚?

我先自我介绍一下,我是来自计算机科学与技术一名大二本科生,我和我的两名队友参加第七届工程训练大赛,我在队伍中担任得任务是进行图像处理,模型训练和,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很关键

总结

值得回忆的备赛视频

第七届工程训练大赛最终虽无缘国赛,但是我们组员在实验室一起奋斗的场景历历在目,我觉得这段记忆是非常珍贵,我们有一起努力过!奋斗过!我觉得就值得了,竞赛之外的友谊是非常难得的!
首先分享一下在比赛前一个晚上,护着我们的宝贝垃圾桶进实验楼(两位队友)

接下来我来分享一下我们宁波杭州湾之旅行的照片

宁波工程学院的鸟巢型书吧,我是真的喜欢!

这是比赛前,风特别大的时候,队友在进行拍照,我在重新训练模型,献上我的垃圾桶,充满神秘感!!
最后留下一张参赛证,这就是回忆!!!

机器学习 深度学习 神经网络 图像识别 tensorflow
阅读 195 发布于 4 月 19 日
赞1 收藏
分享
本作品系原创, 采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议
avatar
Al_tair
1 声望
0 粉丝
关注作者
0 条评论
得票数 最新
提交评论
你知道吗?

注册登录
avatar
Al_tair
1 声望
0 粉丝
关注作者
宣传栏
目录

第七届工程训练大赛

2021.4.9,浙江省举办了第七届工程训练大赛,我们组参加的是垃圾分类的项目,我们组顺利挺进决赛,但是我们看决赛规则并没有标注多种垃圾分类,我们没有完全的准备好应对多种垃圾分类,所以与国赛是无缘了!

前言

随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文通过竞赛就介绍了机器学习的基础内容,和参加竞赛需要的界面设计</font>

我们预赛的规则如下,但是我对显示垃圾名称有点想不明白,分四种垃圾不就好了,还要把菜叶橘子皮分辨出来作甚?

我先自我介绍一下,我是来自计算机科学与技术一名大二本科生,我和我的两名队友参加第七届工程训练大赛,我在队伍中担任得任务是进行图像处理,模型训练和,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很关键

总结

值得回忆的备赛视频

第七届工程训练大赛最终虽无缘国赛,但是我们组员在实验室一起奋斗的场景历历在目,我觉得这段记忆是非常珍贵,我们有一起努力过!奋斗过!我觉得就值得了,竞赛之外的友谊是非常难得的!
首先分享一下在比赛前一个晚上,护着我们的宝贝垃圾桶进实验楼(两位队友)

接下来我来分享一下我们宁波杭州湾之旅行的照片

宁波工程学院的鸟巢型书吧,我是真的喜欢!

这是比赛前,风特别大的时候,队友在进行拍照,我在重新训练模型,献上我的垃圾桶,充满神秘感!!
最后留下一张参赛证,这就是回忆!!!