周国维

  • 2024-12-05
  • 发表了主题帖: 【嵌入式AI挑战营 】SCRFD模型量化部署再rv1106,不需要移植inspireface

    本帖最后由 周国维 于 2024-12-6 09:05 编辑 继续书接上文,在使用inspireface完成人脸识别过后,接下来就该需要用rknn模型来进行人脸识别,但需要先了解一下目前的模型熟悉。 上次下载的Pikachu文件是一个归档文件,也就是个压缩包,在文件名后面加个.tar作为后缀名,执行tar -cvf  Pikachu.tar,就能看到里面的文件   里面都是各种ai模型,__inspire__是说明文档,下面是上次使用的face_detect_320的模型属性,主要了解到类型是MNN,输出输入精度都为float32,推理平台是cpu   mnn模型是转化不成rknn模型,所以需要的是onnx模型,然后就在github翻了一会就有了 https://bj.bcebos.com/paddlehub/fastdeploy/scrfd_500m_bnkps_shape320x320.onnx 可以用netron看一下模型的算子,要保证rv1106支持这些算子,不然就得自定义算子 rknn-toolkit2/doc/05_RKNN_Compiler_Support_Operator_List_V2.3.0.pdf at master · airockchip/rknn-toolkit2   然后就是onnx to rknn,rknn-tool2环境的搭建就不再这里多言了,网上一搜一大堆,照着弄就行,下面是我的模型转化脚本 from rknn.api import RKNN if __name__ == '__main__': model_path="./Python/model/onnx/scrfd_500m_bnkps_shape320x320.onnx" #模型路径 rknn_path="./Python/model/scrfd_500m_bnkps_shape320x320.rknn" #模型路径 dataset_path = "./Python/model/dataset_320.txt" rknn = RKNN(verbose=True) print('--> Config model') rknn.config(mean_values=[123.675, 116.28, 103.53],std_values=[58.395, 57.12, 57.375], target_platform='rv1106', quantized_algorithm='mmse') print('--> Loading model') ret = rknn.load_onnx(model=model_path) if ret != 0: print('Load model failed!') exit(ret) print('--> Building model') ret = rknn.build(do_quantization=True, dataset=dataset_path) if ret != 0: print('Build model failed!') exit(ret) print('--> Export rknn model') ret = rknn.export_rknn(rknn_path) if ret != 0: print('Export rknn model failed!') exit(ret)    mean_values和std_values可能会影响rknn模型的精度,要手动调整一下,quantized_algorithm选择mmse是为了更高的精度 dataset就比较关键了,因为模型的输入图像是320*320,所以dataset里数据集的大小最好也是320*320,不然也会影响精度,就copy了一份脚本处理下 import os import glob import cv2 import numpy as np from tqdm import tqdm import shutil class BOX_RECT: def __init__(self): self.left = 0 self.right = 0 self.top = 0 self.bottom = 0 def save_jpg_paths(folder_path): # 使用 glob 查找所有 .jpg 文件 jpg_files = glob.glob(os.path.join(folder_path, '**/*.jpg'), recursive=True) # 获取相对路径 relative_paths = [os.path.relpath(file) for file in jpg_files] return relative_paths def letterbox(image, target_size, pad_color=(114, 114, 114)): # 调整图像大小以适应目标尺寸的比例 h, w = image.shape[:2] scale = min(target_size[0] / h, target_size[1] / w) new_w, new_h = int(scale * w), int(scale * h) resized_image = cv2.resize(image, (new_w, new_h)) # 计算填充大小 pad_width = target_size[1] - new_w pad_height = target_size[0] - new_h pads = BOX_RECT() pads.left = pad_width // 2 pads.right = pad_width - pads.left pads.top = pad_height // 2 pads.bottom = pad_height - pads.top # 在图像周围添加填充 padded_image = cv2.copyMakeBorder(resized_image, pads.top, pads.bottom, pads.left, pads.right, cv2.BORDER_CONSTANT, value=pad_color) return padded_image, pads, scale # 示例用法 folder_path = './Python/model/lfw' # 替换为您的文件夹路径 images_list = save_jpg_paths(folder_path) cali_num = len(images_list) jump_num = int(len(images_list) / cali_num) cali_list = images_list[0::jump_num][:cali_num] input_w = 320 input_h = input_w target_size = (input_w, input_h) pad_color = (0, 0, 0) save_txt = "./dataset_320.txt" save_path = "./calib" absolute_save_path = os.path.abspath(save_path) # 如果保存路径存在则清空,不存在则创建 if os.path.exists(absolute_save_path): # 使用shutil来删除目录内容 for filename in os.listdir(absolute_save_path): file_path = os.path.join(absolute_save_path, filename) try: if os.path.isfile(file_path) or os.path.islink(file_path): os.unlink(file_path) elif os.path.isdir(file_path): shutil.rmtree(file_path) except Exception as e: print(f'Failed to delete {file_path}. Reason: {e}') else: os.makedirs(absolute_save_path) count = 0 with open(save_txt, "w") as f: for img in cali_list: count += 1 img_abs_path = img img_suffix = os.path.splitext(img_abs_path)[-1] img_ndarry = cv2.imread(img_abs_path) processed_img, pads, scale = letterbox(img_ndarry, target_size, pad_color) cv2.imwrite(os.path.join(absolute_save_path, str(count) + img_suffix), processed_img) img_save_abs_path = os.path.join(absolute_save_path, str(count) + img_suffix) f.write(img_save_abs_path + "\n") if count > 30: break rknn-toolkit2说明文档为rknn-toolkit2/doc/03_Rockchip_RKNPU_API_Reference_RKNN_Toolkit2_V2.3.0_CN.pdf at master · airockchip/rknn-toolkit2 执行模型转换脚本,就能得到scrfd_500m_bnkps_shape320x320.rknn,一个输入通道,九个输出通道     一开始得到rknn模型文件后,我以为修改一下__inspire__说明文档,再把rknn模型也打包就归档文件就行了, 但到执行sample例程的时候噩耗来袭——inspireface不兼容rv1106。 inspireface默认rknn的输出精度类型是uint8,但是rv1106 npu的默认量化输出类型是int8,而且不支持浮点数,只支持int类型,所以必须要进行量化,才能执行转换       而且inspireface里面的rknn api函数也有部分是rv1106不支持的,这意味着rv1106只能用零拷贝的方式设置输入,获取输出       突然就觉得都没必要把inspireface移植到rv1106上,直接用rknn就能把模型量化部署在rv1106,为什么还得莫名奇妙的加个不兼容的中间层,都不知道活动主办方是不是没移植过,多此一举啊,所以还得是从头写代码。 参考rk的文档写的零拷贝流程程序   rknn_context rk_ctx_; ///< The context manager for RKNN. rknn_input_output_num rk_io_num_; ///< The number of input and output streams in RKNN. std::vector<rknn_tensor_attr> input_attrs_; ///< Attributes of input tensors. std::vector<rknn_tensor_attr> output_attrs_; ///< Attributes of output tensors. std::vector<rknn_tensor_mem > input_mems; std::vector<rknn_tensor_mem > output_mems; int ret = rknn_init(&rk_ctx_, (void *)model_path, 0, 0, NULL); // INSPIRE_LOG_INFO("RKNN Init ok."); if (ret < 0) { printf("rknn_init fail! ret=%d\n", ret); return -1; } rknn_sdk_version version; int ret = rknn_query(rk_ctx_, RKNN_QUERY_SDK_VERSION, &version, sizeof(rknn_sdk_version)); if (ret < 0) { printf("4 rknn_init fail! ret=%d", ret); return -1; } printf("sdk version: %s driver version: %s\n", version.api_version, version.drv_version); ret = rknn_query(rk_ctx_, RKNN_QUERY_IN_OUT_NUM, &rk_io_num_, sizeof(rk_io_num_)); if (ret != RKNN_SUCC) { printf("rknn_query ctx fail! ret=%d", ret); return -1; } printf("models input num: %d, output num: %d\n", rk_io_num_.n_input, rk_io_num_.n_output); // spdlog::trace("input tensors: "); input_attrs_.resize(rk_io_num_.n_input); output_attrs_.resize(rk_io_num_.n_output); input_mems.resize(rk_io_num_.n_input); output_mems.resize(rk_io_num_.n_output); ; for (int i = 0; i < rk_io_num_.n_input; ++i) { memset(&input_attrs_[i], 0, sizeof(input_attrs_[i])); memset(&input_mems[i], 0, sizeof(input_mems[i])); ret = rknn_query(rk_ctx_, RKNN_QUERY_INPUT_ATTR, &(input_attrs_[i]), sizeof(rknn_tensor_attr)); dump_tensor_attr(&(input_attrs_[i])); printf("input node index %d\n", i); printf("models input height=%d, width=%d, channel=%d\n", height_, width_, channel_); if (ret != RKNN_SUCC) { printf("rknn_query fail! ret=%d\n", ret); return -1; } input_attrs_[i].type = RKNN_TENSOR_UINT8; input_attrs_[i].fmt = RKNN_TENSOR_NHWC; //printf("input_attrs_[i].size_with_stride: %d\n", input_attrs_[i].size_with_stride); rknn_tensor_mem* tmp_mems = rknn_create_mem(rk_ctx_, input_attrs_[i].size_with_stride); memcpy(&input_mems[i], tmp_mems, sizeof(rknn_tensor_mem)); ret = rknn_set_io_mem(rk_ctx_, &input_mems[i], &input_attrs_[0]); if (ret < 0) { printf("input_mems rknn_set_io_mem fail! ret=%d\n", ret); return -1; } } for (int i = 0; i < rk_io_num_.n_output; ++i) { memset(&output_attrs_[i], 0, sizeof(output_attrs_[i])); output_attrs_[i].index = i; ret = rknn_query(rk_ctx_, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs_[i]), sizeof(rknn_tensor_attr)); if (ret != RKNN_SUCC) { printf("rknn_query fail! ret=%d\n", ret); return -1; } dump_tensor_attr(&(output_attrs_[i])); output_mems[i] = *(rknn_create_mem(rk_ctx_, output_attrs_[i].size_with_stride)); //printf("output_size mem [%d] = %d \n",i ,output_attrs_[i].size_with_stride); ret = rknn_set_io_mem(rk_ctx_, &output_mems[i], &output_attrs_[i]); if (ret < 0) { printf("output_mems rknn_set_io_mem fail! ret=%d\n", ret); return -1; } }   完成rknn配置后,就是执行模型,读取输出数据,对数据进行后处理,然后画框标记,后处理大部分程序都是参考inspireface里的cpp/inspireface/track_module/face_detect/face_detect.cpp这个程序,多了一步把int8类型的数据反量化为float32的类型   static float deqnt_affine_to_f32(int8_t qnt, int32_t zp, float scale) { return ((float)qnt - (float)zp) * scale; } std::vector<FaceLoc> results; std::vector<int> strides = {8, 16, 32}; for (int i = 0; i < strides.size(); ++i) { std::vector<float> tensor_cls; std::vector<float> tensor_box; std::vector<float> tensor_lmk; int index = i; uint8_t *cls_tensor = (uint8_t *)(output_mems[index].virt_addr); for(int j = 0; j < output_attrs_[index].size; ++j) tensor_cls.push_back(deqnt_affine_to_f32(cls_tensor[j], output_attrs_[index].zp, output_attrs_[index].scale)); index = i+3; uint8_t *box_tensor = (uint8_t *)(output_mems[index].virt_addr); for(int j = 0; j < output_attrs_[index].size; ++j) tensor_box.push_back(deqnt_affine_to_f32(box_tensor[j], output_attrs_[index].zp, output_attrs_[index].scale)); index = i+6; uint8_t *lmk_tensor = (uint8_t *)(output_mems[index].virt_addr); for(int j = 0; j < output_attrs_[index].size; ++j) tensor_lmk.push_back(deqnt_affine_to_f32(lmk_tensor[j], output_attrs_[index].zp, output_attrs_[index].scale)); m_face_detect_._decode(tensor_cls, tensor_box, tensor_lmk, strides[i], results); 执行程序,花费了0.15秒,感觉程序再优化下应该能达到0.1s以下,和0.45s相比大致降低了四倍,也符合从float32量化到int8的运算过程   效果杠杠的   后面再看下能不能继续优化,试试减枝和蒸馏是怎样的                  

  • 2024-11-29
  • 发表了主题帖: InspireFace交叉编译,于RV1106部署运行

    本帖最后由 周国维 于 2024-11-29 20:46 编辑 书接上文,完成了SDK验证后,接下来就是InspireFace的交叉编译和部署   交叉编译第一步就是先确定目标机的芯片架构,很简单就可以确定rv1106是32位的armv7芯片   第二步是下载InspireFace的源码,https://github.com/HyperInspire/InspireFace,这个不是整个SDK,只有C++的源码,干净点 InspireFace还依赖了很多别的包,都要手动下载,git clone下不来就下载压缩包,在解压到目标文件夹下     第三步设置交叉编译链,按项目文档来说应该是在docker镜像上编译的,但懒得弄了,直接把ubuntu上前文的RV1106系统SDK的交叉编译链在CMAKE上设置为环境编译变量,而且最好还是用官方sdk提供的编译链进行交叉编译,不然就算编译通过到目标机上执行也可能有依赖问题。     PS: 一定要把project(InspireFace)放到环境编译变量之下,不然cmake就会进入奇怪的构建循环 第四步,配置编译环境定义,cmake默认编译的目标程序是运行在X86平台上的,要修改为上文所说的armv7     第五步执行mkdir build && cd build && cmake .. 如果没有报错就执行make,然后接下来就是趟雷区,哪里编译不过改哪里 第一个雷出现在rknn_init这个函数,因为使能了RKNN,所以会编译RKNN相关的函数,但RKNN又分为RKNPU1和RKNPU2,而InspireFaced是默认支持rv1109和rv1126,是比较老的RKNPU1,rknn_init这个函数有变动,所以需要手动改完rknn_init的入参,还好不多。     第二个雷出现在编译MNN这个库的时候,如果按默认的配置进行编译,编译会通过,但在执行链接的时候你会喜提很多函数的 undefined symbol  究其根本这一切都和MNN_USE_NEON这个宏变量有关,因为MNN_USE_NEON默认是没使能的,但对某些函数,它只限定了实现,没限定声明, 所以会出现undefined symbol ,而armv7是默认支持NEON操作的,所以要修改MNN的CMakeLists,使能MNN_USE_NEON       关闭MNN_ARM82 是因为某个函数找不到实现,懒得找了就直接关闭使用 第三个雷是在链接opencv这个库的时候,它的.o文件会有些古老的底层函数一直都是undefined symbol,可能和交叉编译链版本有关   按经验告诉我,可能又要升级,又要改配置,但最后发现改了一堆东西都没卵用,所以灵光一闪,想到http://git@github.com:LuckfoxTECH/luckfox_pico_rkmpi_example.git这个例程源码里 有opencv-mobile已经编好的.a文件,直接设置导入路径就能用了·   作为代价就是有些sample编译不过,要注释掉   但无关大雅,最后还是成功通过编译,然后确定libInspireFace.so是否为32位ARM架构,是的话就没什么问题了   编译完后,执行adb pull .\libInspireFace.so /lib,把动态链接库放到目标机的/lib目录下,然后下载InspireFace的模型包,我选的是Pikachu试试水   把里面的Pikachu和build/sample/FaceDetect这个sample例程同样放到/root目录下,然后第四个雷就来了 首先是test_res这个包里的jpeg图片居然cv::imread读不出来,还得转成jpg图像才能读,而更奇葩的是Pikachu这个模型包里找不到sample源码里的face_detect这个模型, 如果不是遍历了模型包的参数,还不知道模型名称对不上,后面还加了尺寸大小,真是坑爹。   修改完程序后执行,需要的话到附件下载   再看下人脸识别效果   效果可以,但如果只是0.4秒的速度那真的只能当玩具了,不过现在是单单用cpu计算的,后面看来就要用上rknpu加速和优化模型了 移植InspireFace比移植open3D点云库简单多了,不过后面的工作就进到技术深水区,共勉。                                                            

  • 2024-11-27
  • 发表了主题帖: Luckfox Pico Max 从开箱速通到自带的人脸识别例程

        因为参加了这个活动https://bbs.eeworld.com.cn/thread-1297648-1-1.html,所以水一篇开发板的SDK验证,争取获得免费的开发板。         拿到开发板第一步就是先下载SDK,这是下载地址https://github.com/LuckfoxTECH/luckfox-pico,下面是解压出来的目录,相比与原厂的SDK最大的改动是板级配置包和rootfs加了很多自己的东西。   在解压目录下执行 ./build.sh lunch,就会弹出板级配置包选型,按以下选择就是开发板的默认配置   这个目录就是我们刚刚选择的板级配置文件,里面有各种config,dts以及dts,感兴趣可以搜索看看   这个sdk我是放在ubuntu上的,不用额外配置系统环境,直接执行./build.sh,就可以执行全部编译,下面就是编译生成的镜像   接着连接摄像头,用type-c连接上电,再打开下载工具,按压按钮,就可以烧录镜像了     烧录完毕后,直接执行adb shell,就能登陆系统,如果执行dmesg,有如下打印就说明sensor驱动已经正常起来了,没有反接和坏掉   然后在下载例程源码http://git@github.com:LuckfoxTECH/luckfox_pico_rkmpi_example.git,选择luckfox_pico_rtsp_retinaface_osd进行编译, 再把编译出来的应用程序以及retinaface.rknn,通过adb push 下发到/root目录下执行,再用VLC.就可以看到人脸识别效果了   PS: 懒得找网线了,我改了程序把视频流保存为文件,再通过adb pull把文件下载到本地电脑上     除去下载和编译的时间,总耗时不过3分钟,现在可以确认硬件BSP正常,后面就是ai模型训练部署,第一次弄这种活,希望贴友们可以指点下 另外开发板的SDK把瑞芯微原厂的SDK里的开发文档去掉了,https://github.com/beanjs/blacksilk-alpine-rv1106/tree/master/docs,可以在这个链接里看到原厂的开发文档      

  • 2024-11-21
  • 回复了主题帖: 入围名单公布:嵌入式工程师AI挑战营(进阶)的挑战者们,领取板卡啦

    个人信息已确认,领取板卡,可继续完成任务。

  • 2024-11-20
  • 回复了主题帖: 嵌入式工程师AI挑战营(进阶):在RV1106部署InsightFace算法的多人实时人脸识别实战

    申请理由:     本人开发过多款ISP摄像头,主要负责BSP驱动开发工作,使用过的芯片包括RV1106,RK3588和RK3568,在rv1106上移植调通过AIISP功能,可在微光环境下得到清晰的图像,本次申请的目的是想学习AI模型是如何部署在边缘设备端,以及验证在暗室环境下人脸识别模型是否可以起到正常工作效果。     InsightFace是一套基于深度学习的人脸分析工具,包括人脸检测、识别和表征等功能,具有高精度和高效率的特点。在RV1106上部署InsightFace算法主要有以下步骤:     1.在RV1106的SDK上,搭建一套适配硬件摄像头的板级配置,并验证摄像头驱动是否正常工作,V4L2能否抓取到图像数据。     2. 把InsightFace的AI模型通过RKNN转化为可在rv1106上运行的格式,并验证是否起效。     3. 使用量化,减枝蒸馏等模式减少模型大小和计算量,加快推理速度和降低系统功耗。     计划部署的应用:         在黑暗环境下的进行多人实时人脸识别,可应用在夜间微光地段的安保监控,或者野外设施的看管监控     

最近访客

< 1/1 >

统计信息

已有7人来访过

  • 芯积分:40
  • 好友:--
  • 主题:3
  • 回复:2

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言