博客简单介绍了我本科毕业设计作品——智能分类垃圾桶。使用Raspberry Pi 3B+、舵机、摄像头、亚克力板等零件材料制作智能分类垃圾桶硬件平台;然后使用收集的垃圾图片数据集样本训练MobileNetV1轻量化网络和XGBoost分类器,配合TensorFlow Lite对MobileNetV1在Raspberry Pi上做进一步优化。在资源受限的Raspberry Pi 3B+开发板上实现垃圾分类投放。
垃圾桶硬件装置应实现对行人投入垃圾的四分类投放(可回收垃圾、有害垃圾、厨余垃圾、其他垃圾)。垃圾桶硬件装置主要包含以下六点结构设计。
(1) 四分类垃圾回收桶箱
为实现对垃圾的四分类投放,智能垃圾桶装置设置四分类垃圾回收桶箱。将整个长方体桶箱平均分成四份,分别对应“可回收垃圾”、“有害垃圾”、“厨余垃圾”、“其他垃圾”四个垃圾大类类别。
(2) 托盘
托盘用来暂存待投放垃圾。待垃圾识别完成后,根据具体的识别结果进行分类投放。
(3) 超声波
为满足系统对垃圾投入有较快的响应速度要求,设置超声波传感器。该传感器检测托盘上是否有垃圾放置。若检测到垃圾,则启动分类程序,对待投放垃圾进行识别投放。
(4) 摄像头
摄像头作为系统中的垃圾图像采集装置。使用摄像头拍摄托盘上的垃圾图像,系统根据拍摄图像对垃圾进行分类识别。
(5) 舵机
舵机为垃圾桶的投放驱动模块,该模块共有两个舵机。当得到待投放垃圾的所属大类类别后,两舵机带动托盘旋转,实现对垃圾识别后的投放步骤。
(6) 树莓派
树莓派作为智能垃圾桶的计算设备,负责连接超声波、摄像头等传感器零件,并运行编写完成的软件程序。
软件功能模块所使用的主要依赖库如表2-1所示。
名称 | 版本 | 作用 |
---|---|---|
操作系统 | Raspbian Buster with desktop(September 2019) | 为程序运行提供平台 |
编程语言 | Python3.6 | 运行软件程序 |
深度学习框架 | TensorFlow 1.15.0 | 使用MobileNetV1网络 |
机器学习框架 | XGBoost 1.4.1 | 使用XGBoost分类器 |
系统软件功能模块分为超声波检测、UI可视化、图像预处理、MobileNetV1图像分类、舵机驱动共五个子模块。
(1) 超声波检测
超声波检测软件模块依托超声波传感器硬件,基于GPIO框架编写超声波硬件的使用程序,判断当前是否有垃圾待投放。
(2) UI可视化
可视化垃圾桶当前工作状态,以更直观的方式展示垃圾桶工作是否正常。
(3) 图像预处理
使用摄像头拍摄得到垃圾图像后,执行图像预处理算法对图片进行裁剪,去除无用的背景,便于后续的MobileNetV1图像分类。
(4) MobileNetV1图像分类
为满足系统的垃圾图像分类算法应具有较高的精度和实时性要求,垃圾图像分类软件模块基于MobileNetV1网络和XGBoost分类算法实现,输入为图像预处理模块得到的垃圾图像,输出垃圾类别。MobileNetV1与XGBoost结合,能够得到较好的分类精度。而MobileNetV1作为轻量化网络,再配合模型量化技术,能够满足实时性要求。
(5) 舵机驱动
舵机驱动软件模块依托舵机硬件,基于GPIO框架编写双舵机硬件的使用程序,使之能够根据垃圾图像分类模块的输出驱动舵机转动合适角度,从而将垃圾投入至正确的回收桶箱之中。
零件 | 型号 | 作用 |
---|---|---|
超声波传感器 | HC-SR04 | 检测托盘上有无垃圾放置 |
摄像头 | Epcbook 1080P免驱USB摄像头 | 采集垃圾图像 |
舵机-0 | DS3115 可控角度90° 15KG扭矩 | 带动托盘转动 |
舵机-1 | DS3218 可控角度360° 20KG扭矩 | 带动旋转圆盘转动 |
开发板 | Raspberry Pi 3B+ | 核心计算设备 |
拓展板 | 小R科技 PWR.A53机器人驱动拓展版 | 拓展Raspberry引脚与供电接口 |
电源 | 12V 2200mAh 锂电池 | 供电 |
显示屏 | RaspberryPi 3.5寸电容USB触摸显示屏 | 显示UI界面 |
装置实现垃圾四分类投放(可回收垃圾、有害垃圾、厨余垃圾、其他垃圾),其主视图、俯视图以及双层舵机结构分别如图3-1、3-2、3-3所示。
主视图展示了装置的整体结构以及摄像头、托盘、回收桶箱的位置,如图3-1所示。
装置俯视图中清晰展示了四分类垃圾回收桶箱与“可回收垃圾”、“有害垃圾”、“厨余垃圾”、“其他垃圾”四个垃圾类别的对应关系,并从俯视角度展示了超声波和托盘的位置。智能垃圾桶装置俯视图如图3-2所示。
双层舵机结构中舵机-1与旋转圆盘连接,用以驱动旋转圆盘转动。舵机-0与托盘连接,用来驱动托盘转动。超声波传感器置于托盘侧面,检测托盘上是否有垃圾放置。双层舵机结构如图3-3所示。
超声波模块固定在托盘旁边,使用超声波传感器可测量放置于托盘表面的垃圾距超声波传感器的距离 D 1 D_1 D1。 D 2 D_2 D2为无垃圾放置时超声波传感器测得的距离数据, D 2 = 14 c m D_2=14cm D2=14cm。若满足 0 < D 1 < D 2 0
超声波传感器距离测量函数代码如下。
# -*- coding: utf-8 -*-
"""
Created on Sat Dec 12 21:33:42 2020
@author: qiqi
此文件用于实现四分类垃圾桶的5个超声波测距的控制
"""
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
def getDistance(TRIG=None, ECHO=None):
'''函数功能:获取ECHO与TRIG指定的超声波传感器测定的距离'''
'''TRIG为超声波发射脚位,ECHO为超声波接收脚位'''
if TRIG is None or ECHO is None:
print('引脚未接好')
return 0
GPIO.output(TRIG,GPIO.HIGH)
time.sleep(0.1)
GPIO.output(TRIG,GPIO.LOW)
while not GPIO.input(ECHO):
pass
t1 = time.time()
while GPIO.input(ECHO):
pass
t2 = time.time()
Distence = (t2-t1)*340/2*100
return Distence
def ultrasonic(pin):
dis = pin.copy()
key = list(pin.keys())
for k in key:
dis[k] = getDistance(TRIG=pin[k][0], ECHO=pin[k][1])
return dis
可视化UI界面可以直观输出智能垃圾桶工作状态功能,主要包括:展示摄像头拍摄图片经过预处理后得到的需要进行分类的图片、输出分类识别的结果(该垃圾属于哪个大类)。
图形化界面依靠智能垃圾桶终端搭载的电容显示屏显示,使用PyQt5工具编程实现。图形界面如图3-4所示。
图形界面完整代码如下。
# -*- coding: utf-8 -*-
"""
Created on Fri Dec 11 20:59:15 2020
@author: qiqi
此文件用于实现四分类垃圾桶两个UI界面的显示工作
"""
import sys
import warnings
import threading
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QTimer,QDateTime
warnings.filterwarnings('ignore')
information = {'垃圾类别':'电池', '任务是否完成':'任务完成',
'满载情况':'未满载', '摄像头画面帧':None, '窗口':None,
'宣传视频帧':None}
class showTrash(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
desktop = QApplication.desktop()
self.label_0 = QtWidgets.QLabel(self) #用来显示垃圾类别
self.label_0.resize(desktop.width()*0.4, desktop.height()*0.2)
self.label_0.move(desktop.width()*0.1, desktop.height()*0.15)
self.label_1 = QtWidgets.QLabel(self) #用来显示任务是否完成
self.label_1.resize(desktop.width()*0.4, desktop.height()*0.2)
self.label_1.move(desktop.width()*0.1, desktop.height()*0.35)
self.label_2 = QtWidgets.QLabel(self) #用来显示满载情况
self.label_2.resize(desktop.width()*0.4, desktop.height()*0.2)
self.label_2.move(desktop.width()*0.1, desktop.height()*0.55)
self.label_3 = QtWidgets.QLabel(self) #用来摄像头画面
self.label_3.resize(desktop.width()*0.5, desktop.height()*0.5)
self.label_3.move(desktop.width()*0.5, desktop.height()*0.15)
self.setWindowTitle('垃圾桶工作状态')
self.setGeometry(600, 600, 1000, 500)
self.showMaximized()
def setupUi(self):
self.Timer=QTimer() #自定义QTimer
self.Timer.start(100) #每0.1秒运行一次
self.Timer.timeout.connect(self.update) #连接update
self.Timer.timeout.connect(self.showImage)
QtCore.QMetaObject.connectSlotsByName(self)
def update(self):
self.label_0.setText(information['垃圾类别'])
self.label_0.setStyleSheet('color:rgb(10,10,10,255);font-size:20px;font-weight:bold;font-family:Roman times;')
self.label_1.setText(information['任务是否完成'])
self.label_1.setStyleSheet('color:rgb(10,10,10,255);font-size:20px;font-weight:bold;font-family:Roman times;')
self.label_2.setText(information['满载情况'])
self.label_2.setStyleSheet('color:rgb(10,10,10,255);font-size:20px;font-weight:bold;font-family:Roman times;')
def showImage(self):
frame = information['摄像头画面帧']
heigt, width, _ = frame.shape
pixmap = QImage(frame, width, heigt, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(pixmap)
self.label_3.setPixmap(pixmap)
class showVideo(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
desktop = QApplication.desktop()
self.label = QtWidgets.QLabel(self)
self.label.resize(desktop.width()*0.99, desktop.height()*0.79)
self.label.move(desktop.width()*0.01, desktop.height()*0.01)
self.setWindowTitle('宣传视频')
self.showMaximized()
def setupUi(self):
self.Timer=QTimer() #自定义QTimer
self.Timer.start(100) #每0.1秒运行一次
self.Timer.timeout.connect(self.showImage)
QtCore.QMetaObject.connectSlotsByName(self)
def showImage(self):
frame = information['宣传视频帧']
heigt, width, _ = frame.shape
pixmap = QImage(frame, width, heigt, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(pixmap)
self.label.setPixmap(pixmap)
if information['窗口'] == '垃圾分类':
self.close()
class Thread(threading.Thread):
def __init__(self, window, *args, **kwargs):
super(Thread, self).__init__(*args, **kwargs)
threading.Thread.__init__(self)
self._stop_event = threading.Event()
self.window = window
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
def run(self):
app = QApplication(sys.argv)
if self.window == '视频播放':
ui = showVideo()
else:
ui = showTrash()
ui.setupUi()
app.exec_()
def showUI():
thread = Thread('垃圾分类')
thread.start()
information['窗口'] = '垃圾分类'
thread.join()
(1) 去除托盘外部无用背景
智能垃圾桶摄像头拍摄的图片如图3-5( a a a)所示,由于智能垃圾桶摄像头与托盘位置相对固定,可先将托盘外的无用图像进行去除,将源图像3-5( a a a)与mask图像进行对应位置像素按位与运算,得到去除结果。mask图像如图3-5( b b b)所示,托盘外部无用背景去除结果如图3-5( c c c)所示。
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = np.bitwise_and(img, mask)
(2) 削减图像局部过亮区域的光照强度
由于实际环境的复杂性,拍摄的图像可能出现部分区域过亮的情况,边缘检测时容易将过亮区域误判为边缘,从而对图像预处理造成不利影响。因此,使用一种不均匀光照补偿算法来削减图像局部过亮区域的光照强度,降低其造成的不利影响。
算法主要步骤:
i . i. i. 将RGB三通道的步骤(1)结果图像灰度化,得到单通道图像 I I I,记图像 I I I的宽为 W 0 W_0 W0,高为 H 0 H_0 H0;
i i . ii. ii. 求得灰度图 I I I的平均值,记为 u u u;
i i i . iii. iii. 指定超参数 b l o c k S i z e = 16 blockSize=16 blockSize=16,若 W 0 W_0 W0和 H 0 H_0 H0不是 b l o c k S i z e blockSize blockSize正整数倍,则先使用双立方插值法将图像宽和高转化为 W 0 ′ W_0' W0′和 H 0 ′ H_0' H0′,其中 W 0 ’ = ( ⌊ W 0 / b l o c k S i z e ⌋ + 1 ) ∗ b l o c k S i z e , H 0 ′ = ( ⌊ H 0 / b l o c k S i z e ⌋ + 1 ) ∗ b l o c k S i z e W_0’=(⌊W_0/blockSize⌋+1)*blockSize,H_0'=(⌊H_0/blockSize⌋+1)*blockSize W0’=(⌊W0/blockSize⌋+1)∗blockSize,H0′=(⌊H0/blockSize⌋+1)∗blockSize。 将整幅图片划分成 M ∗ N M*N M∗N个边长为 b l o c k S i z e blockSize blockSize的正方形,其中 M = W 0 ′ / b l o c k S i z e M=W_0'/blockSize M=W0′/blockSize, N = H 0 ′ / b l o c k S i z e N=H_0'/blockSize N=H0′/blockSize。求出每块的平均值,得到子块的亮度矩阵 D D D(维度为 M ∗ N M*N M∗N);
i v . iv. iv. 用矩阵 D D D的每个元素减去灰度图 I I I的平均值 u u u,得到子块的亮度差值矩阵 E E E;
v . v. v. 用双立方插值法,将矩阵 E E E调整成宽为 W 0 W_0 W0,高为 H 0 H_0 H0的亮度分布矩阵 R R R;
v i . vi. vi. 得到矫正后的图像 r e s u l t = I − R result=I-R result=I−R;
v i i . vii. vii. 使用 7 ∗ 7 7*7 7∗7高斯滤波对图像 r e s u l t result result进行平滑处理,平滑处理后的 r e s u l t result result即为算法输出。
算法效果如图3-5( d d d)所示。
def unevenLightCompensate(img, blockSize):
'''去除图像中的光照不均匀'''
if len(img.shape) == 2:
gray = img
else:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
average = np.mean(gray)
rows_new = int(np.ceil(gray.shape[0] / blockSize))
cols_new = int(np.ceil(gray.shape[1] / blockSize))
blockImage = np.zeros((rows_new, cols_new), dtype=np.float32)
for r in range(rows_new):
for c in range(cols_new):
rowmin = r * blockSize
rowmax = (r + 1) * blockSize
if (rowmax > gray.shape[0]):
rowmax = gray.shape[0]
colmin = c * blockSize
colmax = (c + 1) * blockSize
if (colmax > gray.shape[1]):
colmax = gray.shape[1]
imageROI = gray[rowmin:rowmax, colmin:colmax]
temaver = np.mean(imageROI)
blockImage[r, c] = temaver
blockImage = blockImage - average
blockImage2 = cv2.resize(blockImage, (gray.shape[1], gray.shape[0]), interpolation=cv2.INTER_CUBIC)
gray2 = gray.astype(np.float32)
dst = gray2 - blockImage2
dst[dst > 255] = 255
dst = dst.astype(np.uint8)
dst = cv2.GaussianBlur(dst, (7, 7), 0)
return dst
(3) 边缘检测
本环节使用Canny算法实现边缘检测,对步骤(2)得到不均匀光照补偿后的图像执行Canny算法,算法的两个阈值参数设置为:低阈值=50,高阈值=100。图3-5( e e e)为边缘检测后的效果图。
def edge_demo(image):
'''使用Canny算法对image进行边缘检测,输入image为彩色图像'''
edge_output = cv2.Canny(image, 50, 100)
return edge_output
(4) 消除图像细小噪声并平滑边界
观察图3-5( e e e),发现电池的边缘被成功检测出来。但仅含边缘的图像并不适合做分割操作。因此,对边缘检测的输出图像执行腐蚀和膨胀操作。腐蚀和膨胀操作结合具备消除噪声和连接极大值区域的作用。
图3-5( f f f)为腐蚀膨胀后的效果图。通过效果图可以发现,虽然腐蚀膨胀后仍含有少量噪声区域,但电池所在的区域已经成为最主要的连通区域,方便被分割出来。
def smooth(image):
'''将image图像中的垃圾使用矩阵框圈出,返回矩形框的四个顶点的坐标(box)'''
''''以图片左下角为原点,box[0]为左下角顶点,box[1]为左上角'''
'''box[2]右下角,box[3]右上角,closed为边缘检测经过腐蚀膨胀后的图像'''
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (35, 35))
closed = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)
closed = cv2.erode(closed, None, iterations=4)
closed = cv2.dilate(closed, None, iterations=4)
_, cnts, _ = cv2.findContours(closed.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if len(cnts) == 0:
return None, None
c = sorted(cnts, key=cv2.contourArea, reverse=True)[0]
# compute the rotated bounding box of the largest contour
rect = cv2.minAreaRect(c)
box = np.int0(cv2.boxPoints(rect))
# draw a bounding box arounded the detected barcode and display the image
return box, closed
(5) 裁剪目标区域
i . i. i. 上一步骤结束后,先对腐蚀膨胀输出图像进行轮廓检测,得到轮廓数组 c o n t o u r s contours contours, c o n t o u r s contours contours每个元素是一个像素点集合,存储组成该轮廓的所有像素点;
i i . ii. ii. 原始图像经步骤(1)、(2)、(3)、(4)后,电池所在的目标区域为腐蚀膨胀后图像的最大连通区域。因此,在寻找到的所有轮廓中,构成电池所在目标区域的轮廓包含的像素点数量最多。在轮廓数组 c o n t o u r s contours contours中找出像素点最多的轮廓,记为 m a x C o n t o u r maxContour maxContour;
i i i . iii. iii. 使用最小外接矩形函数计算得到 m a x C o n t o u r maxContour maxContour的最小外接矩形 b o x box box,如图3-5( g g g)所示;
i v . iv. iv. 在原始图像中将 b o x box box对应的区域裁剪出来,得到裁剪目标区域步骤的处理结果,如图3-5( h h h)所示。
v . v. v. 将图像尺寸调整为 224 ∗ 224 224*224 224∗224,如图3-5( i i i)所示。
def rotate(img, box):
'''旋转图像并剪裁'''
pt1, pt2, pt3, pt4 = box
withRect = math.sqrt((pt4[0] - pt1[0]) ** 2 + (pt4[1] - pt1[1]) ** 2) # 矩形框的宽度
angle = math.acos((pt4[0] - pt1[0]) / withRect) * (180 / math.pi) # 矩形框旋转角度
if pt4[1]>pt1[1]:
info = '顺时针旋转'
else:
info = '逆时针旋转'
angle=-angle
height = img.shape[0] # 原始图像高度
width = img.shape[1] # 原始图像宽度
rotateMat = cv2.getRotationMatrix2D((width / 2, height / 2), angle, 1) # 按angle角度旋转图像
heightNew = int(width * math.fabs(math.sin(math.radians(angle))) + height * math.fabs(math.cos(math.radians(angle))))
widthNew = int(height * math.fabs(math.sin(math.radians(angle))) + width * math.fabs(math.cos(math.radians(angle))))
rotateMat[0, 2] += (widthNew - width) / 2
rotateMat[1, 2] += (heightNew - height) / 2
imgRotation = cv2.warpAffine(img, rotateMat, (widthNew, heightNew), borderValue=(255, 255, 255))
# 旋转后图像的四点坐标
[[pt1[0]], [pt1[1]]] = np.dot(rotateMat, np.array([[pt1[0]], [pt1[1]], [1]]))
[[pt3[0]], [pt3[1]]] = np.dot(rotateMat, np.array([[pt3[0]], [pt3[1]], [1]]))
[[pt2[0]], [pt2[1]]] = np.dot(rotateMat, np.array([[pt2[0]], [pt2[1]], [1]]))
[[pt4[0]], [pt4[1]]] = np.dot(rotateMat, np.array([[pt4[0]], [pt4[1]], [1]]))
# 处理反转的情况
if pt2[1]>pt4[1]:
pt2[1],pt4[1]=pt4[1],pt2[1]
if pt1[0]>pt3[0]:
pt1[0],pt3[0]=pt3[0],pt1[0]
imgOut = imgRotation[int(pt2[1]):int(pt4[1]), int(pt1[0]):int(pt3[0])]
imgOut = cv2.resize(imgOut, (image_height, image_width),)
return imgOut, info
(1) 基于MobileNetV1与XGBoost的垃圾图像分类算法
基于MobileNetV1与XGBoost的垃圾图像分类算法输入一张经过预处理的垃圾图片,得到预测的小类类别,最终的输出为该小类类别所所属的大类类别。例如:输入一张易拉罐图片,预测得到小类类别为易拉罐,将易拉罐小类所属的大类类别可回收垃圾作为输出。
本文采用MobileNet系列中的MobileNetV1网络,原生MobileNetV1网络输出Softmax层结点数量为1000(对应1000类别)。本文将Softmax层结点数改为15(对应15个垃圾小类)进行训练。在进行预测时砍掉Softmax层,直接输出Softmax层前的全局池化层输出结果,长度为1024的一维向量。XGBoost算法输入为MobileNetV1网络的输出(长度为1024的一维向量),输出为分类结果(15个垃圾小类之一)。
(2) 垃圾图像分类算法优化技巧
a. 数据增强
在MobileNetV1与XGBoost网络训练过程中使用数据增强策略,对原始图片使用随机翻转、旋转、裁剪等手段生成不同的图片数据,以此获得更多的数据训练网络。原始数据集见博客末尾的百度网盘链接。
使用TensorFlow框架的ImageDataGenerator工具对训练集数据使用随机裁剪、旋转等手段进行扩充。ImageDataGenerator工具参数设置如表3-2所示。
Parameter | Value |
---|---|
rotation_range | 180 |
width_shift_range | 0.2 |
height_shift_range | 0.2 |
shear_range | 0.2 |
zoom_range | 0.2 |
fill_mode | ‘nearest’ |
cval | 0.0 |
horizontal_flip | True |
vertical_flip | True |
rescale | 1./255 |
b. 迁移学习
使用迁移学习技巧训练MobileNetV1网络,即在ImageNet预训练模型基础上对MobileNetV1权重进行微调。使用TensorFlow框架提供的Keras接口实现MobileNetV1加载ImageNet预训练权重。
c. 模型量化
模型量化方法可以实现减小模型尺寸、减少模型内存消耗以及加快模型推理速度等目标。
使用TensorFlow Lite工具,对训练完成的MobileNetV1网络进行量化,将32位浮点数运算量化为16位浮点数运算。使用TF Lite工具可在维持原有精度的基础上大幅降低模型推理预测耗时,这点在Raspberry Pi 3B+得到了良好的体现。
使用TensorFlow将H5模型文件转化为TFLite文件的Python代码如下,原H5文件和输出的TFLite文件均在models子目录下。
# -*- coding: utf-8 -*-
"""
Created on Thu Oct 15 15:35:58 2020
@author: qiqi
"""
import tensorflow as tf
from tensorflow.keras.models import load_model, Model
MobileNetV1 = load_model('models/MobileNetV1.h5')
MobileNetV1 = Model(inputs=MobileNetV1.input, outputs=MobileNetV1.get_layer('global_average_pooling2d').output)
converter = tf.lite.TFLiteConverter.from_keras_model(MobileNetV1)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
tflite_quant_model = converter.convert()
open('models/MobileNetV1.tflite', 'wb').write(tflite_quant_model)
舵机驱动模块基于双舵机硬件实现,根据垃圾图像分类模块结果驱动舵机旋转,具体而言分为以下步骤。舵机部分源码整合在了程序main模块中。
(1) 根据图像分类模块得到的垃圾大类类别,控制舵机-1旋转指定角度,从而带动旋转圆盘转动,使得固定在旋转圆盘上的托盘旋转至对应的垃圾大类回收桶箱正上方。托盘初始位置位于有害垃圾回收桶箱正上方。若垃圾大类类别为有害垃圾,则舵机-1不转动;若垃圾大类类别为可回收垃圾,则舵机-1顺时针转动270°;若垃圾大类类别为厨余垃圾,则舵机-1顺时针转动180°;若垃圾大类类别为其他垃圾,则舵机-1顺时针转动90°。
(2) 舵机-1旋转完成后,舵机-0逆时针旋转90°带动托盘转动。托盘移开后等待2s,垃圾自动掉落至正确回收桶箱中。
(3) 舵机-0与舵机-1归位,重新回到最初状态,等待下一次任务。
博客记录了我本科毕业设计的主要内容。作品很low,没有什么实用价值。可能是由于我们大组只有我一个人做了软硬件结合作品,而其他人都是纯软件,最后毕设成绩还不错。感谢评审老师搭救!!!
博客只提供了部分源码,完整源码见我的Github(https://github.com/jiaozi12/Intelligent-Trash-Can)。各位大佬觉得没那么差的话,欢迎给Star!!!
垃圾图片数据集和作品演示视频请见百度网盘链接(https://pan.baidu.com/s/1o9NG4A6d91b6Hc8rRexp8w),提取码:n0qu。