前言
本人有幸代表内蒙古工业大学参加内蒙古自治区的全国工程训练大赛省赛,并在初赛取得前三名的成绩。可惜后来决赛由于树莓派死机导致程序崩溃,从而无缘国赛。但是经过测试,我们的识别程序可以做到识别率95%,分类准确率90%以上。
硬件设备:树莓派4B+8G(用于视觉识别以及播放视频)
stm32f103zet6 (用于下位机控制电机进行分类)
机械结构设计:双层履带交叉分拣
一、机械结构设计
1.Solidworks建模
示例:如图所示,采用双层履带结构
2.建模的不足以及改进
1.从上履带识别后,移交至第二层履带时。会出现飞出去的情况导致分类失败,于是我们在履带两侧以及垃圾桶整体四周加上挡板。这样情况大大改善。
挡板:
2.当进行调试过程中,如果出现瓶子、电池等容易滚的物体,很容易在投掷过程中滚下去导致无法实现识别。为此,我们决定在履带上用胶水粘上小突起,经检验这样能很好解决这个问题。
3.整体实物
整体实物图:
俯视图:
二、视觉识别部分
1.引入库
代码如下(示例):
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse
import io
import time
import numpy as np
#import picamera
import cv2
import RPi.GPIO as GPIO
#import tensorflow as tf
from PIL import Image
from tflite_runtime.interpreter import Interpreter
2.识别部分
代码如下:
def load_labels(path):
with open(path, 'r') as f:
return {
i: line.strip() for i, line in enumerate(f.readlines())}
def set_input_tensor(interpreter, image):
tensor_index = interpreter.get_input_details()[0]['index']
input_tensor = interpreter.tensor(tensor_index)()[0]
input_tensor[:, :] = image
def classify_image(interpreter, image, top_k=1):
"""Returns a sorted array of classification results."""
set_input_tensor(interpreter, image)
interpreter.invoke()
output_details = interpreter.get_output_details()[0]
output = np.squeeze(interpreter.get_tensor(output_details['index']))
# If the model is quantized (uint8 data), then dequantize the results
if output_details['dtype'] == np.uint8:
scale, zero_point = output_details['quantization']
output = scale * (output - zero_point)
ordered = np.argpartition(-output, top_k)
return [(i, output[i]) for i in ordered[:top_k]]
def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
'--model', help='File path of .tflite file.', required=True)
parser.add_argument(
'--labels', help='File path of labels file.', required=True)
args = parser.parse_args()
labels = load_labels(args.labels)
#interpreter = tf.lite.Interpreter(args.model)
interpreter = Interpreter(args.model)
interpreter.allocate_tensors()
_, height, width, _ = interpreter.get_input_details()[0]['shape']
#with picamera.PiCamera(resolution=(640, 480), framerate=30) as camera:
#camera.start_preview()
cap = cv2.VideoCapture(0)
#擷取畫面 寬度 設定為640
cap.set(cv2.CAP_PROP_FRAME_WIDTH,640)
#擷取畫面 高度 設定為480
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
key_detect = 0
times=1
while (key_detect==0):
ret,image_src =cap.read(0)
frame_width=image_src.shape[1]
frame_height=image_src.shape[0]
cut_d=int((frame_width-frame_height)/2)
crop_img=image_src[0:frame_height,cut_d:(cut_d+frame_height)]
image=cv2.resize(crop_img,(224,224),interpolation=cv2.INTER_AREA)
start_time = time.time()
if (times==1):
results = classify_image(interpreter, image)
elapsed_ms = (time.time() - start_time) * 1000
label_id, prob = results[0]
print(labels[label_id],prob)
num=int(label_id)
cv2.putText(crop_img,labels[label_id] + " " + str(round(prob,3)), (5,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 1, cv2.LINE_AA)
times=times+1
if (times>1):
times=1
cv2.imshow('Detecting....',crop_img)
if cv2.waitKey(1) & 0xFF == ord('q'):
key_detect = 1
cap.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
main()
以上基于tenserflow,tenserflow适合在树莓派上跑,但是如果数据集过大就会崩溃(我们就是因为这个原因,止步于省赛)建议数据集采样图片时控制在2000张左右,不然会崩
三、上下位机通信方式:
1.高低电平通信:
最开始因为下位机仅仅需要接受树莓派识别结果,而结果种类只有四种,于是乎最开始想到的是:树莓派往gpio写高低电平,stm32浮空输入电平结果,通过排列组合进行通信。源码如下:
communicate.h:
#define Type_2 PEin(10)// PF13
#define Type_3 PEin(11)// PF14
#define Type_4 PEin(12)// PF15
#define Type_5 PEin(13)// PF16
#define Nothing 0
#define hazardous_waste 1
#define other_waste 2
#define Recyclable_waste 3
#define Kitchen_waste 4
void communicate_Init(void);//初始化
int adjust(void);
communicate.c:
void communicate_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
//使能PB,PE端口时钟
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_10 |GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13;
//LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//IO口速度为50MHz
GPIO_Init(GPIOE, &GPIO_InitStructure);
//根据设定参数初始化GPIOB.5
GPIO_SetBits(GPIOE,GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13);
//PB.5 输出高
}
int adjust(void){
//判断函数
delay_ms(10);//防抖
if(Type_2==0&&Type_3==1&&Type_4==1&&Type_5==1)//第一个树莓派io输出低电平为可回收
return Recyclable_waste;
if(Type_2==1&&Type_3==0&&Type_4==1&&Type_5==1)//第二个io输出低电平为有害垃圾
return hazardous_waste;
if(Type_2==1&&Type_3==1&&Type_4==0&&Type_5==1)//第三个输出低电平为其他
return other_waste;
if(Type_2==1&&Type_3==1&&Type_4==1&&Type_5==0)//第四个输出低电平为厨余垃圾
return Kitchen_waste;
return 0;
}
1.2高低电平树莓派部分:
GPIO.setmode(GPIO.BCM)
GPIO.setup(2,GPIO.OUT)
GPIO.setup(3,GPIO.OUT)
GPIO.setup(4,GPIO.OUT)
GPIO.setup(17,GPIO.OUT)
if num == 0:
GPIO.output(2,GPIO.HIGH)
GPIO.output(3,GPIO.HIGH)
GPIO.output(4,GPIO.HIGH)
GPIO.output(17,GPIO.HIGH)
print('')
elif num == 1:
GPIO.output(2,GPIO.LOW)
GPIO.output(3,GPIO.HIGH)
GPIO.output(4,GPIO.HIGH)
GPIO.output(17,GPIO.HIGH)
print('可回收垃圾')
elif num == 2:
GPIO.output(2,GPIO.HIGH)
GPIO.output(3,GPIO.LOW)
GPIO.output(4,GPIO.HIGH)
GPIO.output(17,GPIO.HIGH)
print('有害垃圾')
elif num == 3:
GPIO.output(2,GPIO.HIGH)
GPIO.output(3,GPIO.HIGH)
GPIO.output(4,GPIO.LOW)
GPIO.output(17,GPIO.HIGH)
print('其他垃圾')
elif num == 4:
GPIO.output(2,GPIO.HIGH)
GPIO.output(3,GPIO.HIGH)
GPIO.output(4,GPIO.HIGH)
GPIO.output(17,GPIO.LOW)
print('厨余垃圾')
else:
GPIO.output(2,GPIO.HIGH)
GPIO.output(3,GPIO.HIGH)
GPIO.output(4,GPIO.HIGH)
GPIO.output(17,GPIO.HIGH)
2.stm32串口通信部分:
void usb_communicate(void){
u16 t;
u16 len;
if(USART_RX_STA&0x8000){
//防抖
delay_ms(100);
delay_ms(100);
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n\r\n");
for(t=0;t<len;t++)
{
USART_SendData(USART1, USART_RX_BUF[t]);//向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
}
}
}
int USB_adjust(void){
usb_communicate();
if(USART_RX_BUF[0]=='0'){
USART_RX_STA=0;
return Nothing ;//没有垃圾
}
if(USART_RX_BUF[0]=='1'){
USART_RX_STA=0;
return Recyclable_waste ;
}//可回收
if(USART_RX_BUF[0]=='2'){
USART_RX_STA=0;
return hazardous_waste ;
}//有害
if(USART_RX_BUF[0]=='3'){
USART_RX_STA=0;
return other_waste;}//可回收垃圾
if(USART_RX_BUF[0]=='4'){
USART_RX_STA=0;
return Kitchen_waste;}//厨余垃圾
USART_RX_STA=0;
return EOF;//错误标志位
}
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。
四、下位机电机驱动部分
1.电机:涡轮蜗杆电机(履带负载较大,不可直接用步进直流电机)
驱动:l298N(12V)
//.h
#ifndef __MOTOR_H
#define __MOTOR_H
#include "sys.h"
#define IN_1 PFout(1)// PB5
#define IN_2 PFout(2)// PE5
#define IN_3 PFout(3)// PB5
#define IN_4 PFout(4)// PE5
#define ZHENGXIANG 0
#define FANXIANG 1
#define STOP 2
void motor_Init(void);//初始化
void zongxiang_run(u16 model);
void hengxiang_run(u16 model);
void _delay_s(u16 s);
void stop(void);
void Recyclable_waste_work(void);//可回收3号
void hazardous_waste_work(void);//有害垃圾1号
void other_waste_waste_work(void);//其他垃圾2号
void Kitchen_waste_waste_work(void);//厨余垃圾4号
#endif
//.c
#include "motor.h"
#include "delay.h"
#define ZHENGXIANG 0
#define FANXIANG 1
#define STOP 2
void motor_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF, ENABLE); //使能PB,PE端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; //LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOF, &GPIO_InitStructure); //根据设定参数初始化GPIOB.5
}
void hengxiang_run(u16 model){
switch(model){
case ZHENGXIANG : {
IN_3=0;
IN_4=1;
break;
}
case FANXIANG :{
IN_3=1;
IN_4=0;
break;
}
case STOP:{
IN_3=1;
IN_4=1;
break;
}
}
}
void zongxiang_run(u16 model){
switch(model){
case ZHENGXIANG : {
IN_1=0;
IN_2=1;
break;
}
case FANXIANG :{
IN_1=1;
IN_2=0;
break;
}
case STOP:{
IN_1=1;
IN_2=1;
break;
}
}
}
void _delay_s(u16 s){
int i;
for(i=0;i<=s;i++)
delay_ms(1000);
}
void Recyclable_waste_work(void){
//可回收3号
hengxiang_run(FANXIANG);
zongxiang_run(FANXIANG);
_delay_s(5);
zongxiang_run(STOP);
hengxiang_run(STOP);
_delay_s(1);
}
void hazardous_waste_work(void){
//有害垃圾1号
zongxiang_run(ZHENGXIANG);
hengxiang_run(FANXIANG);
_delay_s(5);
zongxiang_run(STOP);
hengxiang_run(STOP);
_delay_s(1);
}
void other_waste_waste_work(void){
//其他垃圾2号
hengxiang_run(ZHENGXIANG);
zongxiang_run(ZHENGXIANG);
_delay_s(5);
zongxiang_run(STOP);
hengxiang_run(STOP);
_delay_s(1);
}
void Kitchen_waste_waste_work(void){
//厨余垃圾
hengxiang_run(ZHENGXIANG);
zongxiang_run(FANXIANG);
_delay_s(5);
zongxiang_run(STOP);
hengxiang_run(STOP);
_delay_s(1);
}
void stop(void){
zongxiang_run(STOP);
hengxiang_run(STOP);
}
2.有关于延时的改进:
本人在调试时发现keil环境中:delay_ms(1000)和delay_ms(3000)差别不大
竟然有一下发现:
void _delay_s(u16 s){
int i;
for(i=0;i<=s;i++)
delay_ms(1000);
}//明显好于s*delay_ms(1000)
3.stm32主函数:
int main(void)
{
// u16 adcx;
// float temp;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
LCD_Init();
Adc_Init(); //ADC初始化
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(60,50,200,16,16,"Elite STM32");
LCD_ShowString(60,70,200,16,16,"ADC TEST");
LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(60,110,200,16,16,"2015/1/14");
//显示提示信息
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(60,130,200,16,16,"ADC_CH0_VAL:");
LCD_ShowString(60,150,200,16,16,"ADC_CH0_VOL:0.000V");
motor_Init();
while(1)
{
if(adjust()){
delay_ms(200);
delay_ms(200);
switch(adjust()){
case hazardous_waste:{
//有害垃圾分类
hazardous_waste_work();
_delay_s(5);
break;}
case other_waste :{
//其他垃圾分类
other_waste_waste_work();
_delay_s(5);
break;}
case Recyclable_waste :{
//可回收垃圾分类
Recyclable_waste_work();
_delay_s(5);
break;}
case Kitchen_waste :{
//厨余垃圾分类
Kitchen_waste_waste_work();
_delay_s(5);
break;}
default :{
//没垃圾
stop();
break;}
}
}
}
}
五、炸电机:
在调试过程中,我们炸了六个电机驱动,弄坏了一个步进电机
原因:一夜炸了六个电机驱动,我们依次排查电路接线、程序,最后竟然是电池的原
因!!!
视觉由吉飞敏大力支持
id:weixin_45785085
机械由秦俊酉大力支持
转载:https://blog.csdn.net/qq_45290757/article/details/115670890
查看评论