AIoT应用创新大赛-基于TencentOS Tiny 的多功能行人检测仪

2022-03-28 16:01:31 浏览数 (2)

本文章为团队原创,如果有转载请在文章头部注明出处以及本原文链接,谢谢合作!

一、背景

大家好,很高兴能够参与这次腾讯云AIoT应用创新大赛,非常希望能够在这次比赛中得到收获与提升,同时也希望能够通过这次比赛能与各位交流学习。

AIoT(人工智能物联网)=AI(人工智能) IoT(物联网)。AIoT融合AI技术和IoT技术,通过物联网产生、收集来自不同维度的、海量的数据存储于云端、边缘端,再通过大数据分析,以及更高形式的人工智能,实现万物数据化、万物智联化。随着边缘计算和深度学习领域的不断发展,越来越多的边缘计算设备开始出现在我们的视野中。我们本次提出的这一作品着眼于边缘计算与深度学习场景,提出了一款多功能多应用的目标检测仪。

多功能行人检测仪可用于多种应用场景,本作品提供行人识别、行人检测、检测行人异常停留行为等功能。如:作为路侧设施监测行人是否遵守交通规则;可应用于疫情防控下的商场和景区等场景的行人检测;也可用于对无人值守区域得安防。

且只需要简单修改数据集就可以改为识别除行人以外的其它类,比如辅助野生动物专家监测和保护野生动物等。相比于依靠特定传感器的传统IOT方法,AIOT具有更大的潜力!

从上述例举的场景可知,为了满足这些场景的需求,我们需要完成以下基本功能:

  • 行人识别和行人检测(两个功能可选择使用!)
  • 区域内人流计数
  • 异常逗留行为监测

本作品将使用一套标准嵌入式AI开发流程,将探讨把AI深度学习算法模型应用在嵌入式微处理器上,包括模型训练、模型测试、模型部署、应用程序开发等,不仅仅是单一功能的展示,同时也是对嵌入式AI系统构建方法的完整的展示,共享AIOT的魅力。

二、系统介绍

本系统主要作为基于交通场景下的快速行人计数仪 。其具有成本低、功耗低等优势。可以大量分布式投放且可以基于具体场景需求修改模型功能。此处我们以智能交通和智能安防场景下的行人检测为例,本系统可以实现人流测量以及对于违反交通规则的行人进行智能识别,并且将人流计数和异常现象及时上报至云端,从而实现边缘AI与tencentos-tiny完美结合的智能交通场景下的行人检测案例。并对行人识别探测进行尝试性的升级,可检测到同一张图片内存在多个行人并且画框。

本系统亮点:

  • 使用嵌入式AI技术在开发板部署AI深度学习算法模型,并使用cmsis-nn 算子加速推理,以达到在保证精确度的情况下,可以快速进行检测并且后期可根据需要切换识别目标类型。
  • AI模型占用内存非常小,无论是目标识别还是目标检测,除了能在NXP RT1062以及STM32H750这种带有大的片外内存开发板上运行,也可以在更低功耗且内存更小(RAM仅320kb)的STM32L496开发板中运行。
  • 支持腾讯连连小程序查看人流计数结果,以及是否存在异常行为。

结构图

实物图

PPT

Alot腾讯.pptx

视频:

视频内容

三、搭建流程

1.前期准备

(1)硬件组成

开发板:EVB_AIoT RT1062

摄像头:OV5640

显示屏:Waveshare 4.3inch(800x480 Pixels)

WiFi:Esp8266

(2)软件组成

NXP MCUXpresso IDE 、TenserFlow 2.5.0、WSL(或linux系统)、Darknet

(3)系统逻辑

2.系统开发流程

step1: 基础摄像头 显示的BSP

直接使用腾讯提供的官方demo即可,基本无需自己移植任何OS或驱动

step2: AI模型训练

(1)行人识别模型:MobileNetV1

MobileNetV1是一个非常经典的分类模型,主要特点是采用了深度可分离卷积,相比普通卷积获得了相同的效果,但运算量却大大降低,所以非常适合运用在微处理器上。尽管peson_detect是谷歌TFLite-micro的一个已有案例,但从学习的角度来说还是自己走一遍训练流程为好,可能下一个应用场景的识别对象就不是行人了。

选取来自COCO开源数据集中的一部分,因为COCO包含了常见的80多类物体,但我们这里只需要”person“这个单类,所以需要设定一个Python脚本循环遍历数据集中的每张图像和对应的边界框,如果图像带有标签为”person“且所占面积大于图像面积的50%,则重新制作标签将整张图像标记为”有人“,否则标记为”无人“,最终形成Visual Wake Words数据集。

训练方法可以参考谷歌提供的教程:

https://github.com/tensorflow/tflite-micro/blob/main/tensorflow/lite/micro/examples/person_detection/training_a_model.md

数据集介绍和制作方法可以参考这篇论文:

https://arxiv.org/abs/1906.05721

训练时Accuracy变化的示意图:

(2)行人检测模型:裁剪YOLO-fastest

先说YOLO-fastest,是我最近看到在目标检测领域非常牛的存在,相比于经典的YOLO-tiny系列,不仅内存压缩了好几倍,反而精度不降甚至还有所提升,可惜模型还是偏大,量化前大约1.4MB,尽管可能可以在RT1062的片外SRAM勉强跑,但对于一般性能没这么强劲、内存资源没这么富裕的通用MCU上还是非常吃力的。况且,YOLO-fastest中使用了上采样,这个步骤在TFLite-micro中是没有算子支持的,尽管可以自己实现,但效率应该就低了,所以还是需要对YOLO-fastest模型做进一步的裁剪。

这里要特别感谢一下RTT的陈老师提出的模型裁剪方案并传授了我一些细节:

参考:https://github.com/EdgeAIWithRTT/Project4-Person_detection_RT-AK

尽管我模型的裁剪方式和他不完全相同,但思路基本一致,都是去多尺度特征金字塔,只保留单输出,然后再对其它部分做适量的裁剪,之后参考Yolo-fastest的训练方法在VOC2017完成模型训练。裁剪后的只需要不到150kb的内存,不仅仅是NXP RT1062和STM32H7可以用,我在STM32L496上移植使用一样没问题,该裁剪方案后的模型是真小真快。

数据集获取:http://host.robots.ox.ac.uk/pascal/VOC/voc2007/

贴出裁剪后模型在VOC2007测试集上的跑分结果:

Model Size

Recall(%)

mAP(%)

mAP@.5:.95(%)

FLOPs

YOLO-fastest

1.3MB

75.67%

61.02%

24.4%

0.23BFLOPs

裁剪YOLO-fastest

0.149MB

71.00%

55.04%

21.2%

0.012BFLOPs

Precision

Recall

Accuracy

F1_scores

裁剪YOLO-fastest

85.0%

71.0%

83.0%

77.37%

step3: AI模型上位机测试

首先进行模型的格式转换,我们希望在上位机可以直接得到量化后的.tflit格式模型,然而使用tensorflow训练后的行人识别模型为.pb格式,因此需要借助 TensorFlow Lite 转换器将pb模型转换为.tflite模型。其中我们直接采用训练后量化的方式;而对于行人检测模型通过darknet训练后为.weights格式,需要借助第三方工具来完成向.tflite格式的转化,参考链接:https://github.com/david8862/keras-YOLOv3-model-set

在上位机搭建对tflite模型的测试程序,使用和开发板上一样的API及数据预处理方式,这样可以提前在上位机对模型性能进行测试,比如从本地导入图片做单张测试,也可以对数据集做批量测试,在送入模型之前使用和开发板同样的归一化方式,模型输出之后使用和开发板同样的后处理算法,在一定程度上提前模拟了开发板上运行的情况,可以节约很多上板调试的时间。

上位机测试代码主函数:

代码语言:javascript复制
def main():
    anchor = [[13, 24], [33, 42], [36, 87], [94, 63], [68, 118]]
    logging.getLogger().setLevel(logging.INFO)
    model_path = './Model/yolo-s_relu.tflite'
    #选择图片文件作为输入
    img_path = "./img1.png"
    img_raw = cv2.imread(str(img_path))
    frame = cv2.cvtColor(img_raw, cv2.COLOR_BGR2GRAY)
    small_frame = cv2.resize(frame, (160, 160), cv2.INTER_AREA) # 改变尺寸
    #选择图片转换成的数组作为输入,用于和开发板使用相同输入数据做比对
    #frame = np.array(pic_data.list_pic)
    #small_frame = frame.reshape(160,160)
    # load model
    tflite_model = tf.lite.Interpreter(model_path)
    tflite_model.allocate_tensors()
    #输入维度处理
    small_frame = np.expand_dims(small_frame, 0)    # 扩展dim=0维度
    small_frame = np.expand_dims(small_frame, 3)    # 扩展dim=3维度
    tflife_input_data = np.float32(small_frame)     # 类型转为float32
    if(tflife_input_data.dtype == 'float32'): #两种归一化方式选其一
        #tflife_input_data = tflife_input_data/128-1
        tflife_input_data = tflife_input_data/256.0
    tflife_input_details = tflite_model.get_input_details()[0]
    tflife_output_details = tflite_model.get_output_details()[0]
    tflite_model.set_tensor(tflife_input_details['index'], tflife_input_data)
    # model run
    start_time = getTime_ms()
    tflite_model.invoke()
    # model output
    output_tflite = tflite_model.get_tensor(tflife_output_details['index'])[0] 
    output_tflite = np.expand_dims(output_tflite,axis=0)
    img_shape = img_raw.shape[:-1][::-1]  # weight, height
    #yolo decode解码
    pred_xywh, objectness, class_scores = yolo_decode( output_tflite, anchor, num_classes=1,
                                                    input_dims=(160, 160), scale_x_y=0,
                                                    use_softmax=False)
    img_shape = img_shape[:-1]
    #nms
    boxes, classes, scores = filter(pred_xywh, objectness, class_scores, img_shape)
    end_time = getTime_ms()-start_time
    print("time=" , end_time)
    for box in boxes:
        draw_img(box, img_raw)

在预装tensorflow的环境里,使用python inference_yolo-s_tflite.py运行就可以了

选取几张行人检测效果还凑合的图片:

模型精度可能不是太高,但是要理解的是,模型本身大小就只有150kb不到...牺牲了部分精度但是在速度上获得了很大的提升,并且占用内存非常非常小...

step4: AI模型部署

(1)更新TensorFlow lite micro软件包

首先为什么我们不直接使用NXP的eiq工具呢?首先必须承认的是eiq工具非常强大和方便,开发者只需要点点点就完事了,但是我遇到问题后,在群里和NXP的老师也请教了很多次,他们人也很nice,无奈本菜鸡还是没法解决问题,一部分原因也是看不到eiq底层.a文件的源码,所以无法定位问题,在得知eiq底层也是用的tflite后,于是索性自己移植了一遍,另一个考虑是:tos所支持的平台可能不只是NXP的芯片,以后如果使用到其它系列的芯片又该如何呢?

其实我们在去年年初的时候,就为tos贡献过TF lite micro的软件包,下面是使用指南:

https://github.com/OpenAtomFoundation/TencentOS-tiny/tree/ver2.4.5/components/ai/tflite_micro

甚至我们还分享了如何制作该软件包lib库的教程:

https://github.com/OpenAtomFoundation/TencentOS-tiny/blob/ver2.4.5/components/ai/tflite_micro/TFlite_Micro_Component_User_Guide.md

今年也必须有点新东西,我们使用了谷歌最新的TensorFlow Lite Micro源码重新制作了软件包,的确和老软件包有些许不一样,至少当我加载目标检测模型的时候,老版本会直接初始化失败、新版本就不会出现问题,可能也是对复杂结构模型的支持更好了吧~

新版本TFLM移植过程:

1.下载最新源码

代码语言:javascript复制
git clone git@github.com:tensorflow/tflite-micro.git

2.利用python脚本生成代码工程

代码语言:javascript复制
python3 tensorflow/lite/micro/tools/project_generation/create_tflm_tree.py 
  -e hello_world -e magic_wand -e micro_speech -e person_detection 
  --makefile_options="TARGET=cortex_m_generic OPTIMIZED_KERNEL_DIR=cmsis_nn TARGET_ARCH=project_generation" 
  /tmp/tflm-cmsis

这里脚本传入的参数,指定了目标平台架构,是否使用CMSIS-NN加速,以及拷贝哪些exmaple code,生成的代码放在 /tmp/tflm-cmsis目录

  1. 解决编译问题
  2. 将生成的tflm-cmsis文件夹拷贝至MCUXpresso的source code目录,在工程属性设置中添加头文件路径(可以根据编译报错信息添加)
  3. 注意cmsis_gcc.h的版本,如果工程源码cmsis_gcc.h版本较低,编译报错,可以用tflm-cmsisthird_partycmsisCMSISCoreIncludecmsis_gcc.h代替
  4. 另外,可以将tflm-cmsis编译成单独的静态库,减少工程的编译时间
  5. 编写demo程序
  6. 定义tflm的printf函数
代码语言:javascript复制
void debug_log_printf(const char* s)
{
    PRINTF(s);
}
  • 初始化
代码语言:javascript复制
 //ftlite_micro_initial
RegisterDebugLogCallback(debug_log_printf);
setup();
  • 在main_function.cc编写主函数(loop function)
  • 调用loop函数执行推理
  • 调试优化
  • 使用Release模式,关闭调试选项
  • 编译优化等级O3
  • 编译宏定义增加CMSIS_NN,使用CMSIS_NN算子加速
  • 变量读写速度优化,配置全局变量保存位置为SDRAM或者SRAM_OC
(2)算法模型转换

其实谷歌自家对于.tflite如何转换为tflite-micro可以使用的格式肯定有教程支持的:

https://www.tensorflow.org/lite/microcontrollers/build_convert#model_conversion

其实如果你直接用上面这个教程,还是有一丢丢麻烦的...所以我写了个自动化工具,如果是windows需要在git或者wsl里运行,这里我直接放我的关键函数吧,可以根据需要再改进:

代码语言:javascript复制
def c_model_convert(self, imx6ull_out , model_path):
    c_model_name = model_path[9:-7]
    tflite_model = Path(imx6ull_out) / "TFLite/App"
    assert tflite_model.exists(), "No TFLite/App exists, pls check the path!!!"
    # convert model to c_model
    model_data_c_path = str(tflite_model) "/" c_model_name "_data.c"
    model_data_h_path = str(tflite_model) "/" c_model_name "_data.h"
    print("c_model_name: "   c_model_name)
    print("model_data_c path:" , model_data_c_path)
    os.system("xxd -i "  "./Models/" c_model_name  ".tflite" " > "  model_data_c_path)
    print("xxd convert over")
    with open(model_data_c_path , 'r ') as f:
        content = f.read()
        f.seek(0,0)
        include_path = "#include <TFLite/APP/" c_model_name "_data.h>nn"
        f.write(include_path 'const ' content)
    with open(model_data_h_path , 'w ') as f:
        content = f.read()
        f.seek(0,0)
        c_model_data_define = "extern const unsigned char"   " __Models_" c_model_name "_tflite[];n"
        f.write(c_model_data_define)

然后在tensorflow lite micro中的main_functions.cc中修改setup()函数,需要做三件事:

  • 修改static tflite::MicroMutableOpResolver<7> micro_op_resolver;以下需要加载的算子,这个要看具体的模型,需要什么算子就加什么算子,如果不确定多加几个也没事。
  • model = tflite::GetModel(g_person_detect_model_data);把括号里的模型名称换成自己模型转换为C数组以后的数字名称。
  • input处图像或其它输入需要修改为对应指针,ouput输出要指定是float还是int,这取决于如下所述的预处理和后处理如何实现。
(3)图像输入预处理

神经网络模型要求输入图像尺寸尽可能的小,具有相同的大小和纵横比且为灰度照片。而摄像头获取的图像为320x240RGB565彩色图片,不符合神经网络模型的要求,所以需要对其进行预处理工作。

行人识别输入:96x96 灰度图

行人检测输入:160x160 灰度图

1.RGB565转灰度

RGB彩图转灰度图像:从输入的RGB图像中解析出R、G、B三通道的值,再根据心理学公式计算出单个像素点的值。心理学公式如下:

Gray = R * 0.299 G * 0.587 B * 0.114

由公式可知该运算为浮点型运算,而在实际应用中为了方便计算,使用整数型运算。所以需要对该公式进行缩放处理。本系统中对其进行缩放1000倍而得到整数型运算公式如下:

Gray = (R * 299 G * 587 B * 114 500) / 1000

使用该公式即可加快图像处理速度,但是我们使用移位来代替除法会使其速度更快。

Gray = (R * 39 G * 75 B * 15) >> 7

2.尺度缩放

以将320x240的灰度图片缩放为160x160的灰度图片为例子。代码如下:

代码语言:javascript复制
void bilinera_interpolation(uint8_t* in_array, short height, short width,
                            uint8_t* out_array, short out_height, short out_width)
{
    double h_times = (double)out_height / (double)height,
           w_times = (double)out_width / (double)width;
    short  x1, y1, x2, y2, f11, f12, f21, f22;
    double x, y;


    for (int i = 0; i < out_height; i  ){
        for (int j = 0; j < out_width; j  ){
            x = j / w_times;
            y = i / h_times;


            x1 = (short)(x - 1);
            x2 = (short)(x   1);
            y1 = (short)(y   1);
            y2 = (short)(y - 1);
            f11 = is_in_array(x1, y1, height, width) ? in_array[y1*width x1] : 0;
            f12 = is_in_array(x1, y2, height, width) ? in_array[y2*width x1] : 0;
            f21 = is_in_array(x2, y1, height, width) ? in_array[y1*width x2] : 0;
            f22 = is_in_array(x2, y2, height, width) ? in_array[y2*width x2] : 0;
            out_array[i*out_width j] = (uint8_t)(((f11 * (x2 - x) * (y2 - y))  
                                       (f21 * (x - x1) * (y2 - y))  
                                       (f12 * (x2 - x) * (y - y1))  
                                       (f22 * (x - x1) * (y - y1))) / ((x2 - x1) * (y2 - y1)));
        }
    }
}

3.归一化处理

行人识别模型的输入为uint8_t,故不需要进行归一化。对于行人检测功能,输入为float32,所以需要进行归一化处理,其中可选的归一化方式有两种:

choose 1: dst = src / 2 - 128.0

choose 2: dst = src / 256.0

其中我们目前选在的是第二种归一化方式,和训练时的归一化方式一致,然而在NXP的eiq工具中使用的是第一种。这两种方式在后来的实测中发现尽管略有偏差,在上位机测试的时候都可以检测出人像并画框,在下位机使用第二种方式则更好。

(4)模型输出后处理
a.行人识别的后处理

output有两个输出,其中person score表示的是存在行人的得分,而no person score表示的是不存在行人的得分,得分越高表示该事件存在的概率越大。本示例中通过设定阈值dec的方法,通过阈值设定的方法可以消除扰动,即:

person score - no person score >= 50,说明确实有人存在,反之则无人

b.行人检测的后处理

这部分代码参考source/yolo_layer.c中的实现

YOLO解码器实现

YOLO解码操作(YOLO decode)是为了将神经网络的预测值和真实的图片预测框相对应,即如何通过神经网络的输出值在图片中对目标物体画出检测框。

YOLO label的坐标和真实图像中坐标的对应关系:

每一次神经网络推理完成后的输出进行解码操作,从而获得真实检测框的坐标

引用论文:《Yolov3: An incremental improvement》

nms(非极大值抑制)

在讨论用于目标检测中提取分数最高的窗口时,例如在行人检测中,滑动窗口经提取特征,然后经分类器分类识别后,每个窗口都会得到一个分数。但是滑动窗口会导致很多窗口与其他窗口存在包含或者大部分交叉的情况。这时就需要用到NMS来选取那些邻域里分数最高(是行人的概率最大),并且抑制那些分数低的窗口。

NMS处理示意图:

step5: 设备联网

腾讯物联网开发平台-腾讯连连小程序开发

为了方便用户实时地查看设备端上传的信息(是否有异常报警、人流量计数等),我们利用腾讯云IoT Explorer提的供开发平台,开发腾讯连连小程序。

登录腾讯云开发平台,开发过程如下:

(1)新建产品

(2)根据场景以及功能设计数据模板

设备端上传的只读数据:

  • 行人检测:设备端检测到行人时,标志位置为1;
  • 人流量计数:当设备端的行人检测结果从无人变化到有人时,人流量计数值 1

设备端接收的控制指令:

  • 异常停留:当用户看到有异常停留,标记位置为1。

(3)设计腾讯连连小程序面板界面

使用上图左边的腾讯连连模板,以数字、图标以及进度条的样式来体现功能。

  • 人流计数:主屏显示人数,下方附上进度条来宏观显示人数;
  • 行人检测:使用有人和无人图标直观显示是否检测到行人;
  • 异常现象:同样使用无异常以及异常停留两个按钮来显示行人是否存在异常停留现象。

(4)设备调试

  • 将产品ID、密钥、设备号信息填入设备端的程序;
  • 基于腾讯TOS的AT组件和ESP8266适配的SAL框架,移植程序;
  • 设备端编写上行和下行数据处理逻辑。

3.系统测试

在各个芯片平台部署算法模型的数据,其中NXP RT1062仍在进一步优化中,待达到最佳效果好再及时更新

(1)推理时间和内存占用

1.行人目标识别推理数据:

芯片平台

单帧推理时间

连续十帧检测时间

RAM总占用

Flash总占用

STM32H750

65ms

674ms

448KB

635KB

STM32L496

651ms

7218ms

221KB

636KB

NXP RT1062

以上三者都是使用的TFLite-micro方案

2.行人目标检测推理数据:

芯片平台

单帧推理时间

连续十帧检测时间

RAM总占用

Flash总占用

STM32H750

58ms

624ms

402KB

586KB

STM32L496

668ms

7420ms

251KB

676KB

NXP RT1062

(2)串口打印输出

1.行人识别串口输出

输出的是有人和没人的打分:

2.行人检测串口输出:

输出的是检测框坐标:

(3)实物效果展示:

1.行人识别实物展示

有行人在摄像头前的时候,蓝灯亮起

2.行人检测实物展示

对预置的一张图片中的行人进行检测并画检测框

(4)误差分析

行人识别在性能更强的RT1062开发板上获得了更高的帧率,随着系统灵敏度的提升,在实际体验中,检测行人相比其它开发板更为准确。

行人检测在RT1062开发板上的检测结果相比上位机有一定程度上的精度损失,对于同一张输入图片,在上位机获得了比较不错的检测结果,但在开发板上需要调整yolo_decode中objectness的缩放倍数才可以获得检测框,并且检测框的位置出现了一定程度上的偏移,其中蓝色是上位机的检测结果,而绿色则是下位机的检测结果,如下图所示:

四、结语

感谢TencentOS-tiny团队的大力支持、NXP的张岩和许鹏两位工程师给予的非常耐心细心的指导,感谢西电的姜政同学在我开发板出问题无法使用的时候,借用他自己的开发板给我调试,通过本次大赛学习实践了AIOT部署的全套流程,从模型训练、模型部署到上云实测,在其中也顺带开发了一些有意思的小工具,尽管作品还有很多不足之处,但已经感到收获良多!

代码仓库:

https://gitee.com/yangqings/tencent-os_-tiny_-aio-t_-person-detection

0 人点赞