• 回复
  • 收藏
  • 点赞
  • 分享
  • 发新帖

【 DigiKey DIY原创大赛】无线车辆计数

   作品简介:道路和高速公路是城市的重要基础设施,但是交通事故不可避免会发生。随着时间的增长,道路恶化出现坑洼、裂缝和不平坦的路面,那么道路的安全性和可靠性可能会受到影响,这样将会大大增加交通事故发生概率,并且造成交通不便,虽然各种因素会导致道路恶化,但影响最大的因素之一是道路接收的整体交通负荷,也就是车辆通过的数量。本设计基于MAX78000通过在本地部署Ai模型,可以快速准确的对过往车辆进行检测,了解当前车辆通过的数量。边缘Ai可以很好保护数据隐私性,只需要将结果接入到全球网络中,就可以实时了解城市交通网络的负载情况。

   系统框图:

   系统框图使用得捷Scheme- it在线设计工具完成完成框图绘制。

   硬件设计:

   硬件设计主要由两部分组成,基于MAX78000核心板和OV5640-TFT显示扩展板组成,本次主要介绍基于MAX78000核心板的硬件设计部分。核心板使用Kicad绘制的四层板。

   供电电路:

   供电电路采用的是两个TI的DC-DC电源芯片TLV62569DBVR,Typec接口提供5V,分别作为芯片3V3供电电路和CNN 1.2V升压电路。芯片3V3供电电路经过一个电感L2和电容滤波网络来减少输入的噪声并提供稳定的电源,R5和R6电阻组成分压电路,用于控制输出电压,并且还带有一个LED指示灯和限流电阻R7,用做电源指示灯。CNN 1.2V升压电路,主要是为CNN加速器供电,提供充足的能量,EN脚由P2_5控制,在不使用CNN加速器的时候可以关闭以减少能耗。CNN加速器由64个并行处理器和512KB基于sram的存储组成,每个处理器包括一个池化单元和一个具有专用权重存储器的卷积引擎。

      芯片外围电路:

      芯片外围电路由多个去耦电容,滤除电源噪声,提供稳定的供电,保各电压域的稳定性,提供可靠的供电。晶振部分采用的是一颗32.768kHz的无源晶振,两个10pF的负载电容保证晶振能可靠启动和维持振荡。

   复位及按键电路:

   两个按键电路,一个复位电路,通过一个SN74LVC1G07DCKR 缓冲器来控制系统的复位信号,起到在复位信号传输中起到稳定和驱动的作用,确保复位信号的可靠性。一个用户按键电路由P0_2控制。

   效果图:

   物料清单:

   软件设计:

   软件主体部分采用FreeRTOS,先初始化时钟、LED、CNN加速器升压电路等等,再切换到CNN推理的任务中,Task_Cnn任务首先打开RGB灯进行补光,其中最主要的函数是App_Cnn_Execute(),用来使用CNN加速器执行推理,清空屏幕(MXC_TFT_ClearScreen())以显示新结果。接着,调用 cnn_start() 启动 CNN 推理,然后通过 App_Camera_Cnn_Get() 获取摄像头数据并传入 CNN。接下来设置系统控制寄存器,确保处理器不会进入深度睡眠,以便 CNN 正常运行。  然后,程序进入低功耗等待状态,直到 CNN 完成推理。之后,调用 get_priors() 生成先验框。

static void Task_Cnn(void *pvParameters)
{
    TickType_t xLastWakeTime;
    xLastWakeTime = xTaskGetTickCount();
    MXC_GPIO2->out_clr = MXC_GPIO_PIN_0;
    MXC_GPIO2->out_clr = MXC_GPIO_PIN_1;
    MXC_GPIO2->out_clr = MXC_GPIO_PIN_2;
    while (1)
    {
        App_Cnn_Execute();
        vTaskDelayUntil(&xLastWakeTime, 10);
    }
}

   目标检测是计算机视觉领域的核心问题之一,其任务就是找出图像中所有感兴趣的目标,确定他们的类别和位置。由于各类不同物体有不同的外观,姿态,以及不同程度的遮挡,加上成像是光照等因素的干扰,目标检测一直以来是一个很有挑战性的问题。SSD、YOLO系列目标检测需要生成大量的预测框在MCU上并不适合部署,为了减少内存资源的消耗,所以在此次设计中采用的是TinySSD目标检测模型,主要使用了论文中的Fire4、Fire8、Fire9、Fire10、Conv12-2和Conv13-2层搭建核心模型。

   MAX78000的边缘Ai开发主要分为以下几个部分,数据集制作、模型搭建以及训练量化生成C代码

   数据集制作中使用扩展板上的OV5640进行拍照,通过SD卡保存数据,用labelImg对于PNG图片进行标注,再通过python脚本将数据集按照8:2划分为训练集和测试集,并且按照顺序排序。

int main(void)
{
    int ret = 0;
    int slaveAddress;
    int id;
    g_app_settings.dma_mode = USE_DMA;
    g_app_settings.imgres_w = IMAGE_XRES;
    g_app_settings.imgres_h = IMAGE_YRES;
    g_app_settings.pixel_format = PIXFORMAT_RGB565; 
    MXC_ICC_Enable(MXC_ICC0);
    MXC_SYS_Clock_Select(MXC_SYS_CLOCK_IPO);
    SystemCoreClockUpdate();
    Bsp_Led_Init();
    MXC_GPIO2->out_clr = MXC_GPIO_PIN_0;
    MXC_GPIO2->out_clr = MXC_GPIO_PIN_1;
    MXC_GPIO2->out_clr = MXC_GPIO_PIN_2;
    MXC_DMA_Init();
    g_app_settings.dma_channel = MXC_DMA_AcquireChannel();
    camera_init(CAMERA_FREQ);
    slaveAddress = camera_get_slave_address();
    ret = camera_get_manufacture_id(&id);
    g_app_settings.imgres_w = 74;
    g_app_settings.imgres_h = 74;   
    while (1) {
        if(id_car < 50)
        {
            cnn_img_data_t img_data = stream_img(g_app_settings.imgres_w, g_app_settings.imgres_h,
                                                 g_app_settings.pixel_format,
                                                 g_app_settings.dma_channel);
            save_stream_sd(img_data, id_c);
            id_car ++;
            MXC_Delay(50000);
        }
    }
}

   训练量化生成C代码均在算力云上搭建的环境中,生成相应的C代码,目标检测任务中只需要 cnn.c cnn.h weight.h,分别包含了CNN加速器启动过程和加载权重所需要的参数。在App_Camera_Cnn_Get函数中获取相机图像,将分别相机图像格式输入给CNN加速器和TFT屏。CNN加速器推理出来的结果通过localize_objects函数,通过非极大值抑制(NMS)过滤掉多余的检测框,然后根据剩下的结果生成物体的边界框坐标,并在图像上绘制检测到的物体边框以及识别出的类别。

void App_Camera_Cnn_Get(void)
{
        uint8_t *frame_buffer;
        uint8_t *buffer;
        uint32_t imgLen;
        uint32_t w, h, x, y;
        uint8_t r, g, b;
        uint32_t *cnn_mem;
        uint32_t *fifo_addr =  (volatile uint32_t *) 0x50000008;
        camera_start_capture_image();
        camera_get_image(&frame_buffer, &imgLen, &w, &h);
        for (int y = 0; y < IMAGE_SIZE_Y; y++)
        {
            for (x = 0; x < IMAGE_SIZE_X; x++)
            {
                r = *buffer++;
                g = *buffer++;
                b = *buffer++;
                buffer++; // skip msb=0x00
                while (((*((volatile uint32_t *) 0x50000004) & 1)) != 0); 
                *fifo_addr = ((b << 16) | (g << 8) | r) ^ 0x00808080;                
                color = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3);
                MXC_TFT_WritePixel(x * IMG_SCALE, y * IMG_SCALE, IMG_SCALE, IMG_SCALE, color);
            }
        }
}
void localize_objects(void)
{
    float prior_cxcy[4];
    float cxcy[4];
    float xy[4];
    int class_idx, prior_idx, global_prior_idx;
    nms();
    for (class_idx = 0; class_idx < (NUM_CLASSES - 2); ++class_idx)
    {
        for (prior_idx = 0; prior_idx < num_nms_priors[class_idx]; ++prior_idx)
        {
            if (nms_removed[class_idx][prior_idx] != 1)
            {
                global_prior_idx = nms_indices[class_idx][prior_idx];
                get_cxcy(prior_cxcy, global_prior_idx);
                gcxgcy_to_cxcy(cxcy, global_prior_idx, prior_cxcy);
                cxcy_to_xy(xy, cxcy);
                x1 = 74 * xy[0];
                test_2[class_idx] = class_idx + 1;
                draw_obj_rect(xy, class_idx, IMAGE_SIZE_X, IMAGE_SIZE_Y, IMG_SCALE);
            }
        }
    }
}

   功能展示:

   制作花絮:

   1:第一次手动贴BGA封装的芯片,虽然能正常亮灯但CNN加速器不正常,把芯片拆了又拆,怀疑这个那个的,只能花钱SMT了,SMT加沉金感觉就是不一样。

   2:之前用来测试的TFT小PCB板,弄了半天背光不亮,三极管还发烫严重,最后才发现是限流电阻的电阻值搞错了加FPC接口方向反了,还弄坏了一个TFT屏幕。

   总结:最后感谢电源网与得捷电子此次举办的大赛,祝越来越好!

全部回复(0)
正序查看
倒序查看
现在还没有回复呢,说说你的想法