目录目录前言一、机械结构设计附机械部分图纸​编辑二、树莓派视觉识别1.数据集的制作1数据集处理部分2数据集的优化2.数据集打标签建议1网站推荐2问题示例3.树莓派环境搭建部分(1) YOLOv5-Lite环境的部署4.具体Yolov5-lite算法部分(1)引入库部分(2)视觉识别主要部分代码(3)树莓派控制舵机部分代码三、淘晶池串口屏通讯代码四、stm32部分代码1.UART 通讯部分函数前言本人有幸参加2023年10月的“联通杯”江苏省工程实践与创新能力大赛即中国大学生工训赛江苏省省赛。我组在初赛时十中八识别成功率100但是由于我们在机械结构方面考虑不佳导致垃圾卡住最后获得江苏省一等奖。以下代码仅用于分享不做商业用途硬件设备树莓派4B8G用于视觉识别和控制系统总控stm32f103c8t6用于满载和人体红外检测淘晶池串口屏x5七寸电阻触摸款本文模板参考csdn关于工训赛的多个相关文档感谢多位博主的无私分享使我组的垃圾桶在构建之初可以集众家之所长。但碍于网络上并没有关于此竞赛的完整搭建方案故作此篇仅作分享。附照片佐证一、机械结构设计我组在讨论中为了防止投放垂直高度过高的问题采取了增加斜坡的方式来减小下落速度但是由于投放垃圾中存在扁平的碎砖片表面过于粗糙导致在斜坡上无法正常滑下导致在初赛中发生了垃圾卡在投放的问题。虽然我们为这个比赛准备时间很长但是真正实践动手时间较短大概20多天很多实际问题只有在真正做出来后才能发现不足加之我们只有一个机械方面学长参加大爱学长为了比赛承担了很多责任导致我们在机械结构方面只是达到了最基础的功能还有一个很重要的原因是我们都是控制小白实现最基本的功能已经耗费我们太多时间了附机械部分图纸机械结构建议​有能力的还是要搞机械臂这才是最终方案或者多个差速传送带也是不错的选择虽说其他省有一些裁判对暂存装置的理解不一样但是国赛还是允许的平心而论个人认为传送带打破了只能有一个暂存区的设定属于bug范畴但是采用传送带大家实属无奈之举二、树莓派视觉识别1.数据集的制作1数据集处理部分数据集的制作是识别训练模型最为关键的一点并且在我的实际操作过程中发现有几大注意要点一般人可不会告诉你哦1.一定要注意数据集的制作的多角度——以垃圾识别为例在拍摄时一定要多角度多方位的拍摄多组照片包括一个垃圾的360°照片和在托盘上的所有方位位置点照片。因为垃圾掉落下去具有不确定因素可能在托盘停稳后落在托盘上的各个方位并且旋转成为各个角度给目标检测带来不确定性。为了更好的模拟识别环境我选择在摄像头固定好位置后在树莓派上拍摄照片以追求更好的仿真性以下是我在树莓派上运行的拍照程序程序运行后将会逐帧保存拍摄照片——保存在images文件夹里打标签时可以从树莓派里提取出来import cv2 from threading import Thread import uuid import os import time count 0 def image_collect(cap): global count while True: success, img cap.read() if success: file_name str(uuid.uuid4()).jpg cv2.imwrite(os.path.join(images,file_name),img) count count1 print(save %d %s%(count,file_name)) time.sleep(0.4) if __name__ __main__: os.makedirs(images,exist_okTrue) # 打开摄像头 cap cv2.VideoCapture(0) m_thread Thread(targetimage_collect, args([cap]),daemonTrue) while True: # 读取一帧图像 success, img cap.read() if not success: continue cv2.imshow(video,img) key cv2.waitKey(1) 0xFF # 按键 q 退出 if key ord(c): m_thread.start() continue elif key ord(q): break cap.release()代码参考了csdn大佬——链接如下 基于树莓派4B的YOLOv5-Lite目标检测的移植与部署含训练教程_树莓派yolo-CSDN博客2数据集的优化自己在手动拍摄时无法做到真正的360°并且如果全靠手动调整角度后期修改时会无形的添加工作量。为了更好的添加多角度照片以下是我旋转已有照片的代码。使用前先在文件夹里创建images、result文件夹然后把需要旋转的照片放在images里from math import * import cv2 import os import glob import imutils import numpy as np def rotate_img(img, angle): img --image angle --rotation angle return--rotated img h, w img.shape[:2] rotate_center (w / 2, h / 2) # 获取旋转矩阵 # 参数1为旋转中心点; # 参数2为旋转角度,正值-逆时针旋转;负值-顺时针旋转 # 参数3为各向同性的比例因子,1.0原图2.0变成原来的2倍0.5变成原来的0.5倍 M cv2.getRotationMatrix2D(rotate_center, angle, 1.0) # 计算图像新边界 new_w int(h * np.abs(M[0, 1]) w * np.abs(M[0, 0])) new_h int(h * np.abs(M[0, 0]) w * np.abs(M[0, 1])) # 调整旋转矩阵以考虑平 M[0, 2] (new_w - w) / 2 M[1, 2] (new_h - h) / 2 rotated_img cv2.warpAffine(img, M, (new_w, new_h)) return rotated_img if __name__ __main__: output_dir result image_names glob.glob(images/e080b004-ddd9-404c-a518-a8f5671bc658.jpg)#这里改为自己需要旋转的照片名称注意路径 for image_name in image_names: image cv2.imread(image_name, -1) for i in range(1, 361, 3): # 修改步长为3每3°旋转一次可修改 rotated_img1 rotate_img(image, i) basename os.path.basename(image_name) tag, _ os.path.splitext(basename) cv2.imwrite(os.path.join(output_dir, qqq-%d.jpg % i), rotated_img1)#这里可以修改自己的文件名称2.数据集打标签建议1网站推荐官方指定的labelimg特别容易崩溃本人深受其害后发现makesense网站出奇好用现推荐给大家——附链接 Make Sense2问题示例以本项目为例垃圾识别需要四种垃圾标签需要转出为yolo形式yolo中的第一个数字含义为物体的种类如下图所示不同数字代表不同种类。所以如果是小组四个人在打标签一个人打一种在配置标签种类时四种也都需要配上并且每个人打标签的名称顺序以及名称的命名也要保持一致01230 0.503021 0.511827 0.477341 0.2614983 0.319119 0.725658 0.050600 0.053973如果打错了想要修改标签数字怎么办以下是我的代码import os def modify_yolo_txt(file_path, new_value): with open(file_path, r) as file: lines file.readlines() modified_lines [] for line in lines: parts line.split( ) parts[0] str(new_value) modified_lines.append( .join(parts)) with open(file_path, w) as file: file.writelines(modified_lines) # 遍历指定目录下的所有txt文件 directory C:/Users/under/Desktop/Hazardous/labels/val#修改的标签文件夹注意绝对路径 new_value 1 #修改想要的数字 for filename in os.listdir(directory): if filename.endswith(.txt): file_path os.path.join(directory, filename) modify_yolo_txt(file_path, new_value)3.树莓派环境搭建部分本项目识别采用Yolov5-lite算法YOLOv5-Lite 与 YOLOv5 相比虽然牺牲了部分网络模型精度但是缺极大的提升了模型的推理速度该模型属性将更适合实战部署使用并且使用onnxruntime替代pytorch其运行速度更一步提升。(1) YOLOv5-Lite环境的部署requirements.txt请在文章末尾资源中下载pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple4.具体Yolov5-lite算法部分(1)引入库部分#!/usr/bin/python import serial import time import math import smbus import cv2 import numpy as np import onnxruntime as ort import random(2)视觉识别主要部分代码if __name____main__: model_pb_path ...... # 这里改为自己的onnx模型注意用yolov5-lite中的export.py导出 so ort.SessionOptions() net ort.InferenceSession(model_pb_path, so) dic_labels { 0:RecyclableWaste, 1:Hazardous, 2:kitchengarbage, 3:Other waste } model_h 320 model_w 320 nl 3 na 3 stride [8., 16., 32.] anchors [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]] anchor_grid np.asarray(anchors, dtypenp.float32).reshape(nl, -1, 2) video 0 cap cv2.VideoCapture(video) flag_det True last_detect time.time() while True: success, img0 cap.read() if success: if flag_det and time.time() - last_detect 1.0: img cv2.resize(img0, [model_w, model_h], interpolationcv2.INTER_AREA) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img img.astype(np.float32) / 255.0 blob np.expand_dims(np.transpose(img, (2, 0, 1)), axis0) outs net.run(None, {net.get_inputs()[0].name: blob})[0].squeeze(axis0) outs cal_outputs(outs, nl, na, model_w, model_h, anchor_grid, stride) img_h, img_w, _ np.shape(img0) boxes, confs, ids post_process_opencv(outs, model_h, model_w, img_h, img_w, thred_nms0.4, thred_cond0.5) #FPS t1 time.time() det_boxes, scores, ids infer_img(img0, net, model_h, model_w, nl, na, stride, anchor_grid, thred_nms0.4, thred_cond0.5) t2 time.time() receive_and_send() for box, score, id in zip(det_boxes, scores, ids): label %s:%.2f % (dic_labels[id], score) plot_one_box(box.astype(np.int16), img0, color(255, 0, 0), labellabel, line_thicknessNone)代码参考了csdn大佬——链接如下 基于树莓派4B的YOLOv5-Lite目标检测的移植与部署含训练教程_树莓派yolo-CSDN博客(3)树莓派控制舵机部分代码class PCA9685: # Registers/etc. __SUBADR1 0x02 __SUBADR2 0x03 __SUBADR3 0x04 __MODE1 0x00 __PRESCALE 0xFE __LED0_ON_L 0x06 __LED0_ON_H 0x07 __LED0_OFF_L 0x08 __LED0_OFF_H 0x09 __ALLLED_ON_L 0xFA __ALLLED_ON_H 0xFB __ALLLED_OFF_L 0xFC __ALLLED_OFF_H 0xFD def __init__(self, address0x40, debugFalse): self.bus smbus.SMBus(1) self.address address self.debug debug if (self.debug): print(Reseting PCA9685) self.write(self.__MODE1, 0x00) def write(self, reg, value): Writes an 8-bit value to the specified register/address self.bus.write_byte_data(self.address, reg, value) if (self.debug): print(I2C: Write 0x%02X to register 0x%02X % (value, reg)) def read(self, reg): Read an unsigned byte from the I2C device result self.bus.read_byte_data(self.address, reg) if (self.debug): print(I2C: Device 0x%02X returned 0x%02X from reg 0x%02X % (self.address, result 0xFF, reg)) return result def setPWMFreq(self, freq): Sets the PWM frequency prescaleval 25000000.0 # 25MHz prescaleval / 4096.0 # 12-bit prescaleval / float(freq) prescaleval - 1.0 if (self.debug): print(Setting PWM frequency to %d Hz % freq) print(Estimated pre-scale: %d % prescaleval) prescale math.floor(prescaleval 0.5) if (self.debug): print(Final pre-scale: %d % prescale) oldmode self.read(self.__MODE1); newmode (oldmode 0x7F) | 0x10 # sleep self.write(self.__MODE1, newmode) # go to sleep self.write(self.__PRESCALE, int(math.floor(prescale))) self.write(self.__MODE1, oldmode) time.sleep(0.005) self.write(self.__MODE1, oldmode | 0x80) def setPWM(self, channel, on, off): Sets a single PWM channel self.write(self.__LED0_ON_L 4 * channel, on 0xFF) self.write(self.__LED0_ON_H 4 * channel, on 8) self.write(self.__LED0_OFF_L 4 * channel, off 0xFF) self.write(self.__LED0_OFF_H 4 * channel, off 8) if (self.debug): print(channel: %d LED_ON: %d LED_OFF: %d % (channel, on, off)) def setServoPulse(self, channel, pulse): Sets the Servo Pulse,The PWM frequency must be 50HZ pulse pulse * 4096 / 20000 # PWM frequency is 50HZ,the period is 20000us # 调整舵机旋转速度更快 pulse pulse * 1 self.setPWM(channel, 0, int(pulse))三、淘晶池串口屏通讯代码# 根据接收到的数据发送对应的控制指令 if value 1: # 向串口屏发送控制指令 send_command(click b5,1) send_end() elif value 2: # 向串口屏发送控制指令 send_command(click b6,1) send_end() elif value 3: # 向串口屏发送控制指令 send_command(click b7,1) send_end() elif value 4: # 向串口屏发送控制指令 send_command(click b8,1) send_end() elif value 5: # 向串口屏发送控制指令 send_command(click b0,1) send_end() else: print(Invalid value: , value) # 处理无效的值 except ValueError: print(Invalid data format)四、stm32部分代码1.UART 通讯部分函数树莓派与stm32通讯我们采用的是树莓派4B与 STM32 的 UART 通讯 此种方法我们认为较为简单代码比较容易ser32 serial.Serial(/dev/ttyAMA0, 9600, timeout1) # 解码从STM32接收到的数据 def decode_data(data): decoded_data data.decode().strip() # 使用ASCII解码并移除首尾空白字符 return decoded_data def receive_and_send(): # 从STM32接收数据 data ser32.read(1) # 解码接收到的数据 decoded_data decode_data(data) # 如果接收到的数据不为空 if decoded_data: # 尝试将数据转换为整数 try: value int(decoded_data)代码包含——yolov5lite环境部署视觉识别stm32部分所有代码淘晶池串口屏打包代码及UI界面舵机控制代码以及所有代码整合。数据集共四千多张近五千张已标注本项目所有代码均已开源需要的可以私聊 可提供垃圾桶搭建前后相关建议