飞道的博客

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

221人阅读  评论(0)


前言

本人有幸代表内蒙古工业大学参加内蒙古自治区的全国工程训练大赛省赛,并在初赛取得前三名的成绩。可惜后来决赛由于树莓派死机导致程序崩溃,从而无缘国赛。但是经过测试,我们的识别程序可以做到识别率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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场