基于MAX78000设计的趣味答题器
本设计是一款基于MAX78000设计的趣味答题系统。系统会自动出题,用户看到题目之后通过手势回答题目,回答结果会在屏幕上进行显示。
标签
嵌入式系统
MAX78000
mp3
星辰i
更新2023-01-31
745

一、项目介绍

本设计将实现一个趣味答题系统。系统会自动出题,用户看到题目之后通过手势回答题目,通过摄像头采集手势图片,然后经过CNN计算识别出是什么手势,回答结果会在屏幕上进行显示。

本项目采用实时计算,随时采集随时计算,并通过TFTLCD显示出手势类型、答题情况。

 

二、项目设计思路

项目所需的外设功能:

摄像头(板载):用于捕获手势,通过 Camera_IF 工程范例进行调试,调试初期,关闭ENABLE_TFT宏定义,使用工程自带的 pc_utility 脚本,通过串口接收摄像头捕获的图像并在PC上进行显示;

串口(板载):用于输出调试信息;

显示屏(外购):用于实时显示摄像头捕获的图像信息,采购的是 DFRobot 2.8" 320x240 TFT电阻触摸显示屏,驱动芯片为 ILI9341,通过 TFT_Demo 工程范例进行调试测试;

 

整体流程如下:

1、寻找素材,通过网上查找,自己拍摄等方式收集两种手势与空白时的照片,单种数量越多越好。

2、训练模型,搭建训练环境,由于该板卡仅支持在Windows中进行AI模型部署,模型训练和量化还需在Linux环境下进行。因此我们需要搭建Linux环境,选择使用WSL(Windows Subsystem for Linux),它是适用于 Linux 的 Windows 子系统,修改学习率,多次尝试取最优值。

3、量化模型,量化后,测试模型准确率。

4、转换代码,部署板卡,修改编译脚本,部署测试训练图片在板卡上的情况。

5、编写摄像头、屏幕等驱动,使用SPI驱动LCD显示屏,编写汉字显示代码,驱动摄像头采集图像,并显示到LCD上。

6、调节识别情况,通过摄像头采集图像,看一下识别效果,根据识别情况去调节判断阈值。

7、编写代码,编写题目显示,题目判断逻辑,并测试实际情况。

 

FhbAlY2-1srfypDlVA6rxgenuCbs

 

三、搜集素材的思路

1、数据集图片

(1). HaGRID数据集

手势识别(HGR)系统的大型图像数据集HaGRIDHAnd Gesture Recognition Image Dataset)。您可以将其用于图像分类或图像检测任务。提出的数据集允许构建HGR系统,该系统可用于视频会议服务(Zoom,Skype,Discord,Jazz等),家庭自动化系统,汽车行业等。

HaGRID大小为716GB,数据集包含552,992张FullHD(1920×1080)RGB图像,分为18类手势。此外,如果框架中有第二个空闲的手,则某些图像具有类。这个额外的类包含 123,589 个样本。数据分为训练92%和测试8%的受试者,其中509,323张图像用于训练,43,669张图像用于测试。

下载地址:GitHub - hukenovs/hagrid: HAnd Gesture Recognition Image Dataset

 

(2). ASL Alphabet数据集

该数据集是美国手语字母图像的集合,分为 29 个文件夹,代表各个类别。

训练数据集包含 87,000张200x200 像素的图像。有29个类,其中26个用于字母AZ,3 个类用于SPACE、DELETE和NOTHING。这3个类在实时应用和分类中非常有用。测试数据集仅包含 29 张图像,以鼓励使用真实世界的测试图像。

下载地址:ASL Alphabet | Kaggle

 

2、自拍摄图片

由于训练集都是在使用比较好的摄像头进行采集,而MAX78000板卡上板载的摄像头拍摄的效果跟像素并不好,只使用数据集的数据实际部署到板卡上有可能出现“水土不服”的问题,因此我们需要使用MAX78000板载的摄像头采集一些图像用于补充数据集。美信提供了相关例程,参考官方提供的例程做一个拍摄照片并将拍摄好的素材保存到SD卡中。

 

四、预训练实现过程

训练脚本

#!/bin/sh
python3 train.py --epochs 100 --optimizer Adam --lr 0.00030 --batch-size 256 --deterministic --compress schedule-okno.yaml --model ai85aslnet --dataset OkNo --confusion --device MAX78000 --use-bias "$@"

 

训练过程(最后一次训练与验证)

2023-01-12 01:24:16,907 - Training epoch: 8100 samples (256 per mini-batch)
2023-01-12 01:24:43,706 - Epoch: [99][   10/   32]    Overall Loss 0.241984    Objective Loss 0.241984                                        LR 0.000300    Time 2.679598    
2023-01-12 01:25:07,537 - Epoch: [99][   20/   32]    Overall Loss 0.241805    Objective Loss 0.241805                                        LR 0.000300    Time 2.531213    
2023-01-12 01:25:30,176 - Epoch: [99][   30/   32]    Overall Loss 0.241677    Objective Loss 0.241677                                        LR 0.000300    Time 2.442066    
2023-01-12 01:25:33,737 - Epoch: [99][   32/   32]    Overall Loss 0.241651    Objective Loss 0.241651    Top1 100.000000    LR 0.000300    Time 2.400650    
2023-01-12 01:25:33,980 - --- validate (epoch=99)-----------
2023-01-12 01:25:33,986 - 900 samples (256 per mini-batch)
2023-01-12 01:25:39,475 - Epoch: [99][    4/    4]    Loss 0.249281    Top1 99.666667    
2023-01-12 01:25:39,681 - ==> Top1: 99.667    Loss: 0.249

2023-01-12 01:25:39,695 - ==> Confusion:
[[283   0   3]
 [  0 307   0]
 [  0   0 307]]

2023-01-12 01:25:39,707 - ==> Best [Top1: 100.000   Sparsity:0.00   Params: 59568 on epoch: 98]
2023-01-12 01:25:39,708 - Saving checkpoint to: logs/2023.01.11-230830/qat_checkpoint.pth.tar
2023-01-12 01:25:39,719 - --- test ---------------------
2023-01-12 01:25:39,720 - 3 samples (256 per mini-batch)
2023-01-12 01:25:40,819 - Test: [    1/    1]    Loss 0.241214    Top1 100.000000    
2023-01-12 01:25:41,024 - ==> Top1: 100.000    Loss: 0.241

2023-01-12 01:25:41,025 - ==> Confusion:
[[1 0 0]
 [0 1 0]
 [0 0 1]]

 

量化模型

Fr9iW3b66B9cQGE43BJCNjRO3j2Z

 

生成代码

FnaVQ8KbD5RTh4ulqV2D13E8Obct

 

五、实现结果展示

界面布局

FkFHMDwCKiB0-4svQxw0mAmOPaPN

 

识别为空

Fr5X6dfo7VBbnZDa7hCbLdeiVUbT

 

识别为NO

Ft7cSxwZ5Jn3uZiLhw63bHcF-lma

 

识别为OK

FtYgdAwdatPGjRi0j66qUBZqol-K

 

回答错误

Fu1aIDwN7CiFWYqyfjWrW0Oz7d2S

 

六、主要代码

主函数

int main(void)
{
    int i, dma_channel;
    int ret = 0;
    int result[CNN_NUM_OUTPUTS] = {0};

    // Wait for PMIC 1.8V to become available, about 180ms after power up.
    MXC_Delay(200000);
    /* Enable camera power */
    Camera_Power(POWER_ON);
    MXC_ICC_Enable(MXC_ICC0); // Enable cache

    // Switch to 100 MHz clock
    MXC_SYS_Clock_Select(MXC_SYS_CLOCK_IPO);
    SystemCoreClockUpdate();
    MXC_Delay(SEC(2)); // Let debugger interrupt if needed
    cnn_enable(MXC_S_GCR_PCLKDIV_CNNCLKSEL_PCLK, MXC_S_GCR_PCLKDIV_CNNCLKDIV_DIV1);

    mxc_gpio_cfg_t gpio_out;
    gpio_out.port = MXC_GPIO2;
    gpio_out.mask = MXC_GPIO_PIN_5;
    gpio_out.pad  = MXC_GPIO_PAD_NONE;
    gpio_out.func = MXC_GPIO_FUNC_OUT;
    MXC_GPIO_Config(&gpio_out);
    MXC_GPIO_OutSet(gpio_out.port, gpio_out.mask);

    mxc_gpio_cfg_t tft_reset_pin = {MXC_GPIO0, MXC_GPIO_PIN_19, MXC_GPIO_FUNC_OUT, MXC_GPIO_PAD_NONE, MXC_GPIO_VSSEL_VDDIOH};
    mxc_gpio_cfg_t tft_blen_pin = {MXC_GPIO0, MXC_GPIO_PIN_9, MXC_GPIO_FUNC_OUT, MXC_GPIO_PAD_NONE, MXC_GPIO_VSSEL_VDDIOH};
    MXC_TFT_Init(MXC_SPI0, 1, &tft_reset_pin, &tft_blen_pin);
    MXC_TFT_SetRotation(ROTATE_270);
    MXC_TFT_ShowImage(0, 0, image_bitmap_1);
    MXC_TFT_SetForeGroundColor(WHITE); // set chars to white
    MXC_Delay(1000000);

    MXC_DMA_Init();
    dma_channel = MXC_DMA_AcquireChannel();

    camera_init(CAMERA_FREQ);
    ret = camera_setup(IMAGE_SIZE_X, IMAGE_SIZE_Y, PIXFORMAT_RGB888, FIFO_THREE_BYTE, USE_DMA,
                       dma_channel);
    if (ret != STATUS_OK) {
        printf("Error returned from setting up camera. Error %d\n", ret);
        return -1;
    }

    MXC_TFT_SetPalette(image_bitmap_2);
    MXC_TFT_SetBackGroundColor(4);
    memset(buff, ' ', TFT_BUFF_SIZE);


    uint8_t question_num = 0, answer = 0, user_answer = 0, past_user_answer = 0, state = 0;
    while (1)
    {
        capture_camera_img();
        process_camera_img(input_0_camera, input_1_camera, input_2_camera);
        convert_img_unsigned_to_signed(input_0_camera, input_1_camera, input_2_camera);
        cnn_init();         // Bring state machine into consistent state
        cnn_load_weights(); // Load kernels
        cnn_load_bias();
        cnn_configure(); // Configure state machine
        cnn_start();     // Start CNN processing
        load_input();    // Load data input via FIFO
        MXC_TMR_SW_Start(MXC_TMR0);

        while (cnn_time == 0)
            __WFI(); // Wait for CNN

        softmax_layer();


        for (i = 0; i < CNN_NUM_OUTPUTS; i++)
        {
        	result[i] = ((1000 * ml_softmax[i] + 0x4000) >> 15) / 10;
        }

        if (result[0] > 60) //适应实际修改
        {
        	//No
            TFT_Print(buff, 180, 55, font_2, sprintf(buff, "No     "));
            user_answer = 2;
        }
        else if (result[1] > 60)
        {
        	//Ok
            TFT_Print(buff, 180, 55, font_2, sprintf(buff, "Ok     "));
            user_answer = 1;
        }
        else if (result[2] > 60)
        {
        	//空
            TFT_Print(buff, 180, 55, font_2, sprintf(buff, "Empty  "));
            user_answer = 0;
        }
        else
        {
            TFT_Print(buff, 180, 55, font_2, sprintf(buff, "Unknown"));
            user_answer = 0;
        }

        if(state == 0)
        {
        	MXC_TFT_WritePixel(0, 0, 319, 40, 0);
        	MXC_TFT_WritePixel(60, 140, 80, 16, 0);
        	answer = display_question(question_num);
        	question_num ++;
        	state = 1;
        }
        else if(user_answer != 0)
        {
        	if(past_user_answer == user_answer) state ++;
			else                                state = 1;

        	if(state == 5)
        	{
        		if(user_answer == answer) TFT_printf_str(60, 140, buff, sprintf(buff, "回答正确!"));
        		else                      TFT_printf_str(60, 140, buff, sprintf(buff, "回答错误!"));
        	}

        	past_user_answer = user_answer;
        }

        if(user_answer == 0 && state >= 5 && question_num < 10)
        {
        	state = 0;
        }


        TFT_Print(buff, 180, 160, font_2, sprintf(buff, "No:%d  ",result[0]));
        TFT_Print(buff, 180, 180, font_2, sprintf(buff, "Ok:%d  ",result[1]));
        TFT_Print(buff, 180, 200, font_2, sprintf(buff, "Unknown:%d  ",result[2]));
        convert_img_signed_to_unsigned(input_0_camera, input_1_camera, input_2_camera);
        lcd_show_sampledata(input_0_camera, input_1_camera, input_2_camera, 180, 85, 1024);
    }

    return 0;
}

 

字符串显示(支持汉字显示与汉字、ASCII字符混合显示)

void TFT_printf_str(uint8_t x, uint8_t y, char *str, uint8_t lenght)
{
	uint8_t i = 0, j = 0, work = 0, num = 0;
	for(num = 0; num < lenght;)
	{
		if(str[num] > 127)
		{
			for(i=0;i<sizeof(word_bank_16X16_indexes);)
			{
				if(str[num] == word_bank_16X16_indexes[i] && str[num + 1] == word_bank_16X16_indexes[i+1])
				{
					break;
				}
				i+=2;
			}
			work = i/2;
			for(i = 0; i < 16; i++)
			{
				char temp = word_bank_16X16[i + work*32];
				for(j = 0; j < 8; j++)
				{
					if(temp & 0x01)
						MXC_TFT_WritePixel(x+i + num*8, y+j, 0, 0, 0xFFFF);
					temp = temp >> 1;
				}
			}
			for(i = 0; i < 16; i++)
			{
				char temp = word_bank_16X16[16 + i + work*32];
				for(j = 0; j < 8; j++)
				{
					if(temp & 0x01)
						MXC_TFT_WritePixel(x+i + num*8, y+8+j, 0, 0, 0xFFFF);
					temp = temp >> 1;
				}
			}
			num += 2;
		}
		else
		{
			TFT_Print(&str[num], x + num*8, y, font_1, 1);
			num++;
		}
	}
}

 

摄像头图像显示

void lcd_show_sampledata(uint32_t* data0, uint32_t* data1, uint32_t* data2, int xcord, int ycord,
                         int length)
{
    int i,j,x,y,r,g,b;
    int scale = 1.2;

    uint32_t color;
    uint8_t* ptr0;
    uint8_t* ptr1;
    uint8_t* ptr2;

    x = 0;
    y = 0;
    for (i = 0; i < length; i++)
    {
        ptr0 = (uint8_t*)&data0[i];
        ptr1 = (uint8_t*)&data1[i];
        ptr2 = (uint8_t*)&data2[i];
        for (j = 0; j < 4; j++)
        {
            r = ptr0[j];
            g = ptr1[j];
            b = ptr2[j];
            color = RGB(r, g, b); // convert to RGB565
            MXC_TFT_WritePixel(xcord * scale + x * scale, ycord * scale + y * scale, scale, scale, color);
            x += 1;
            if (x >= (IMAGE_SIZE_X))
            {
                x = 0;
                y += 1;
                if ((y + 6) >= (IMAGE_SIZE_Y))
                    return;
            }
        }
    }
}

 

七、问题与下一步计划

加入更多的手势识别内容,支持选择题的功能。

加入联网功能,支持在线获取题库。

优化训练模型,在极端情况下也能识别。

 

八、后记

很开心能参加这一次比赛,群里也有很多大佬在讨论,也给予了我许多的帮助,但是不得不说的是,咕咕咕太严重了,而且,公司事比我想象中的还要多!!!直到活动快结束才加把劲赶进度,实现的东西也比较简单,对此不是特别满意。希望自己再下一次的活动中,能快一点,不再咕咕咕。

 

附件下载
okno-demo.zip
程序
团队介绍
一个爱DIY的工程师
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号