K210垃圾自动分类箱(图像识别+语音识别+阿里云)

先来一段视频演示

实物演示视频

项目目标:

(1)通过视觉检测和语音识别对垃圾进行识别,并完成分类:

(2)实现垃圾满载检测已经满载时,信息上报服务器或云平台;

(3)垃圾分类完成后,能够进行语音播报;

资源准备

sipeed的K210用于垃圾识别

STM32ZET6开发板作为主控(需要用到很多串口)

LD3320用于语音识别

JQ8900用于语音播报

ESP12F连接wifi(ESP8266都行)

步进电机驱动器和两个步进电机(舵机什么的都行,取决于机械结构)

12V以上电源

硬件接线

主控与K210芯片连接说明如下:

STM32

K210

PA9

10

PA10

9

VCC

5V

GND

GND

驱动与主控连接说明如下:

STM32

步进电机驱动1

STM32

步进电机驱动2

PF1

PUL-

PF3

PUL-

+5v

PUL+

+5v

PUL+

PF2

DIR+

PF4

DIR+

GND

DIR-

GND

DIR-

电源及驱动连接说明

电源采用了4节18650电池并联提供14.4V的电压,用于给驱动42BYGH34-401A供电,驱动连接步进电机。

驱动与步进电机连接如下:

驱动

步进电机

VMOT

12V电源+

GND

12V电源-

B2

B-

A2

B+

A1

A+

B1

A-

主控与语音播报模块连接说明如下:

STM32

JQ8900

PA2

3

PA3

4

5V

6

GND

5

主控与语音识别模块连接说明如下:

STM32

LD3320

PC10

RXD

PC11

TXD

5V

5V

GND

GND

红外模块连接说明如下:

STM32

红外

GND

GND

PC6

OUT

5V

5V

WIFI模块连接说明如下:

STM32

ESP8266

PB10

RXD

PB11

TXD

5V

5V

GND

GND

软件设计

首先进行需要收集垃圾检测的数据集,需要进行多角度的拍照,最好就是用K210进行拍照,不要使用手机取样。写个代码,按键按下就拍照,将图片保存在SD卡,拍照时把垃圾的背景弄成A4纸垫上,识别效果会好些。数据集准备好后就要进行数据集标注,像这样

标注完后进行训练,这里我勾选了随机模糊和数据均衡。

即便是同一个设置,每次的训练都有不同的效果。还有不是说上面的成功lv越高就效果越好,放在芯片中跑的时候不一定,光线的影响是很大的,有条件的话保证光线一致。

然后直接就导入模型到K210了,会自动生成一个的代码。给代码加上串口发送的功能,就能把数据发到STM32了。附上K210代码。

# generated by maixhub, tested on maixpy3 v0.4.8
# copy files to TF card and plug into board and power on
import sensor, image, lcd, time
import KPU as kpu
import gc, sys
from machine import UART
from fpioa_manager import fm
fm.register(10, fm.fpioa.UART1_TX, force=True)
fm.register(9, fm.fpioa.UART1_RX, force=True)
uart = UART(UART.UART1, 9600, 8, 0, 1, timeout=1000, read_buf_len=4096)
input_size = (224, 224)
labels = ['4', '2', '5', '1']
anchors = [2.62, 2.47, 1.5, 0.84, 2.09, 1.16, 1.5, 1.38, 1.13, 1.09]

def lcd_show_except(e):
    import uio
    err_str = uio.StringIO()
    sys.print_exception(e, err_str)
    err_str = err_str.getvalue()
    img = image.Image(size=input_size)
    img.draw_string(0, 10, err_str, scale=1, color=(0xff,0x00,0x00))
    lcd.display(img)

def main(anchors, labels = None, model_addr="/sd/m.kmodel", sensor_window=input_size, lcd_rotation=0, sensor_hmirror=False, sensor_vflip=False):
    sensor.reset()
    sensor.set_pixformat(sensor.RGB565)
    sensor.set_framesize(sensor.QVGA)
    sensor.set_windowing(sensor_window)
    sensor.set_hmirror(sensor_hmirror)
    sensor.set_vflip(sensor_vflip)
    sensor.run(1)

    lcd.init(type=1)
    lcd.rotation(lcd_rotation)
    lcd.clear(lcd.WHITE)

    if not labels:
        with open('labels.txt','r') as f:
            exec(f.read())
    if not labels:
        print("no labels.txt")
        img = image.Image(size=(320, 240))
        img.draw_string(90, 110, "no labels.txt", color=(255, 0, 0), scale=2)
        lcd.display(img)
        return 1
    try:
        img = image.Image("startup.jpg")
        lcd.display(img)
    except Exception:
        img = image.Image(size=(320, 240))
        img.draw_string(90, 110, "loading model...", color=(255, 255, 255), scale=2)
        lcd.display(img)

    try:
        task = None
        task = kpu.load(model_addr)
        kpu.init_yolo2(task, 0.5, 0.3, 5, anchors) # threshold:[0,1], nms_value: [0, 1]
        while(True):
            img = sensor.snapshot()
            t = time.ticks_ms()
            objects = kpu.run_yolo2(task, img)
            t = time.ticks_ms() - t
            if objects:
                for obj in objects:
                    pos = obj.rect()
                    img.draw_rectangle(pos)
                    img.draw_string(pos[0], pos[1], "%s : %.2f" %(labels[obj.classid()], obj.value()), scale=2, color=(255, 0, 0))
                    if obj.value()>0.5:
                        uart.write('%s\r\n'%(labels[obj.classid()]))#·¢±êÇ©
            img.draw_string(0, 200, "t:%dms" %(t), scale=2, color=(255, 0, 0))
            lcd.display(img)
    except Exception as e:
        raise e
    finally:
        if not task is None:
            kpu.deinit(task)


if __name__ == "__main__":
    try:
        # main(anchors = anchors, labels=labels, model_addr=0x300000, lcd_rotation=0)
        main(anchors = anchors, labels=labels, model_addr="/sd/model-30957.kmodel")
    except Exception as e:
        sys.print_exception(e)
        lcd_show_except(e)
    finally:
        gc.collect()

stm32端的数据接收使用正点原子那套就行,K210发送的结尾是回车换行。视觉识别实现了,下面是语音识别,找到卖家给的代码,有一个函数,用来添加识别的语音。

/************************************************************************
功能描述: 向LD模块添加关键词
入口参数: none
返 回 值: flag:1->添加成功
其他说明: 用户修改.
					 1、根据如下格式添加拼音关键词,同时注意修改sRecog 和pCode 数组的长度
					 和对应变了k的循环置。拼音串和识别码是一一对应的。
					 2、开发者可以学习"语音识别芯片LD3320高阶秘籍.pdf"中
           关于垃圾词语吸收错误的用法,来提供识别效果。
					 3、”xiao jie “ 为口令,故在每次识别时,必须先发一级口令“小捷”
**************************************************************************/
uint8 LD_AsrAddFixed()
{
  uint8 k, flag;
  uint8 nAsrAddLength;
#define DATE_A 50   /*数组二维数值*/
#define DATE_B 20		/*数组一维数值*/  
  uint8 code sRecog[DATE_A][DATE_B] =
  {
    "xiao jie",\    
    "dian chi",\    
    "ju zi",\
    "zhi ban",\
    "che lun",\
  };	/*添加关键词,用户修改*/
  uint8 code pCode[DATE_A] =
  {
    CODE_CMD, \
		
  };	/*添加识别码,用户修改*/
  flag = 1;
  for (k=0; k<DATE_A; k++)
  {

    if(LD_Check_ASRBusyFlag_b2() == 0)
    {
      flag = 0;
      break;
    }

    LD_WriteReg(0xc1, pCode[k] );
    LD_WriteReg(0xc3, 0 );
    LD_WriteReg(0x08, 0x04);
    delay(1);
    LD_WriteReg(0x08, 0x00);
    delay(1);

    for (nAsrAddLength=0; nAsrAddLength<DATE_B; nAsrAddLength++)
    {
      if (sRecog[k][nAsrAddLength] == 0)
        break;
      LD_WriteReg(0x5, sRecog[k][nAsrAddLength]);
    }
    LD_WriteReg(0xb9, nAsrAddLength);
    LD_WriteReg(0xb2, 0xff);
    LD_WriteReg(0x37, 0x04);
  }
  return flag;
}

我这只添加了4个,第一个“xiao jie”,是一级指令,就像小爱同学一样,先呼叫一级指令,才会进行识别。假如识别到语音为电池,就会通过串口发送1\r\n。收到“橘子”,就会发2\r\n。STM32通过接收的数字,就知道识别的结果。语音识别完成。

接下来,就该根据K210和语音模块收到的结果,将物品通过步进电机推动进行分类。接下来是STM32端的根据识别结果驱动步进电机运动的代码。

 if (((USART_RX_STA & 0x8000) || (USART4_RX_STA & 0x8000))) //接收完成
        {
            if ((USART_RX_BUF[0] == 0x32) || (USART4_RX_BUF[0] == 0x33))
            {
                mode = 2;
                MyUSART_SendArr(Play2, 6); //可回收垃圾
                delay_ms(1863);
                MyUSART_SendArr(play[sum[mode]], 6); //垃圾数
            }
            if (USART_RX_BUF[0] == 0x31 || (USART4_RX_BUF[0] == 0x34)) //车轮
            {
                mode = 4;
                MyUSART_SendArr(Play4, 6);
                delay_ms(1863);
                MyUSART_SendArr(play[sum[mode]], 6);
            }
            if (USART_RX_BUF[0] == 0x34 || (USART4_RX_BUF[0] == 0x32)) //橘子
            {
                mode = 3;
                MyUSART_SendArr(Play3, 6);
                delay_ms(1863);
                MyUSART_SendArr(play[sum[mode]], 6);
            }
            if (USART_RX_BUF[0] == 0x35 || (USART4_RX_BUF[0] == 0x31)) //电池
            {
                mode = 1;
                MyUSART_SendArr(Play1, 6);
                delay_ms(1863);
                MyUSART_SendArr(play[sum[mode]], 6);
            }
            if ((mode == 1) || (mode == 2) || ((mode == 3) && (flags3 == 0)) || (mode == 4))
            {
                XYcontrol();//控制电机往返
                sum[mode]++;
                mode = 0;
            }
            LED0 = 1;
            LED1 = 1;
            USART_RX_STA = 0;
            USART4_RX_STA = 0;
        }
void motor(unsigned int motor1_dir, unsigned int motor1_step, unsigned int motor2_dir, unsigned int motor2_step)
{
    unsigned int i;	
		switch(motor1_dir)
		{
			case 0 : GPIO_SetBits(Motor_GPIO,Motor1_DIR); break; 
			case 1 : GPIO_ResetBits(Motor_GPIO,Motor1_DIR); break; 
			default : break; 
		}
		switch(motor2_dir)
		{
			case 0 : GPIO_SetBits(Motor_GPIO,Motor2_DIR); break; 
			case 1 : GPIO_ResetBits(Motor_GPIO,Motor2_DIR); break; 
			default : break; 
		}
		for(i = 0;i < motor1_step || i < motor2_step; i++)
		{
			if(i<motor1_step)
			{
				GPIO_SetBits(Motor_GPIO,Motor1_STEP);
				delay_us(motortime);									//周期motortime*4
				GPIO_ResetBits(Motor_GPIO,Motor1_STEP);
				delay_us(motortime);
			}
			if(i<motor2_step)
			{
				GPIO_SetBits(Motor_GPIO,Motor2_STEP);
				delay_us(motortime);									
				GPIO_ResetBits(Motor_GPIO,Motor2_STEP);
				delay_us(motortime);
			}
		}

    //delay_ms(2);					//延时一会
}
void XYcontrol(void)
{
	USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);//关闭串口接收中断
	//USART_ITConfig(USART3, USART_IT_RXNE, DISABLE);//关闭串口接收中断
	if(mode==1)
		motor(a[0],a[1],a[2],a[3]);
	if(mode==2)
		motor(b[0],b[1],b[2],b[3]);
	if(mode==3)
		motor(c[0],c[1],c[2],c[3]);
	if(mode==4)
		motor(d[0],d[1],d[2],d[3]);
	if(mode!=0)
	    flag=1;

	returninit();
}

void returninit(void)
{
	unsigned char i;
	if(flag)
	{
	delay_ms(30);
	if(mode==1)
		motor(0,a[1],0,a[3]);
	if(mode==2)
		motor(1,b[1],0,b[3]);
	if(mode==3)
		motor(1,c[1],1,c[3]);
	if(mode==4)
		motor(0,d[1],1,d[3]);
	flag=0;
	for(i=0;i<USART_REC_LEN;i++)
	{
		USART_RX_BUF[i]=0;
		USART4_RX_BUF[i]=0;
	}                      //清楚串口接收缓冲
	MyUSART_SendArr(Play6,6);
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接收中断
	USART_ITConfig(UART4, USART_IT_RXNE, ENABLE);//开启串口接收中断
    }
}

这里说明一下,当时做这个项目的时候是大二,写代码很不规范,不过我现在也懒得改了。。。

mode变量是识别到的结果,flag3是满载检测标志位。MyUSART_SendArr()这个函数是用来和语音播报模块通信的,接下来介绍的就是JQ8900(不过现在已经很落后)

JQ8900支持文件系统,需要将提前生成的音频文件保存到其内部,然后通过串口去让它选择播报哪一个音频文件,使用串口发送的数据要符合他规定的协议。

u8 Play1[] = {0xAA, 0x07, 0x02, 0x00, 0x01, 0xB4};//有害垃圾
u8 Play2[] = {0xAA, 0x07, 0x02, 0x00, 0x02, 0xB5}; //可回收垃圾
u8 Play3[] = {0xAA, 0x07, 0x02, 0x00, 0x03, 0xB6}; //厨余垃圾
u8 Play4[] = {0xAA, 0x07, 0x02, 0x00, 0x04, 0xB7}; //其他垃圾
u8 Play5[] = {0xAA, 0x07, 0x02, 0x00, 0x05, 0xB8}; //垃圾已满
u8 Play6[] = {0xAA, 0x07, 0x02, 0x00, 0x0e, 0xC1}; //OK
u8 play[][6]={{0xAA, 0x07, 0x02, 0x00, 0x0d, 0xC0},{0xAA, 0x07, 0x02, 0x00, 0x06, 0xB9},{0xAA, 0x07, 0x02, 0x00, 0x07, 0xBA},
			 {0xAA, 0x07, 0x02, 0x00, 0x08, 0xBB},{0xAA, 0x07, 0x02, 0x00, 0x09, 0xBC},{0xAA, 0x07, 0x02, 0x00, 0x0a, 0xBD},{0xAA, 0x07, 0x02, 0x00, 0x0b, 0xBE},
			 {0xAA, 0x07, 0x02, 0x00, 0x0c, 0xBF}};//数字0~7

它的数据格式有一个求和校验,就拿有害垃圾的语音举例吧。最后一个0xB4是校验字节,他的低位是(A(也就是10)+7+2+0+1)%16=4,A+(A(也就是10)+7+2+0+1)/16=B,所以校验字节是0xB4。现在有的语音播报模块直接通过Printf发送文字就行了。

语音播报也完成,接下来就是满载检测和阿里云的信息上传了。

满载检测就不详细介绍了,就是垃圾箱顶端安装一个红外二极管,根据返回的电平信号判断。

阿里云的连接网上也有恒多资料,这里就不详细说明了。

  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值