智能垃圾分类

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很关键

总结

值得回忆的备赛视频

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

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

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

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

本文版权归趣营销www.SEOgUrublog.com 所有,如有转发请注明来出,竞价开户托管,seo优化请联系QQ卍61910465