文章介绍
姿态检测是通过算法从给定的图像中识别人物的关键点,如检测出人体的眼睛、鼻子、肩膀、手臂、臀部、膝盖等关键点,然后再将各关键点有序连接,形成骨架。本文介绍了基于高通Snapdragon Neural Processing Engine SDK和HRNET模型实现一个姿态检测功能的应用。此应用程序打开相机预览,采集所有帧并将其转换为bitmap位图。位图经过预处理,然后输入给YoloNAS模型来识别人,模型推理后显示人的坐标。当检测出人时,我们处理帧以包含人类的框之内的的所有数据到HRNET模型,然后该模型返回人类姿势的坐标。
该应用的所有源代码都可以在https://github.com/quic/qidk/tree/master/Solutions/VisionSolution4-PoseEstimation上获得。
我们使用Snapdragon® 8 Gen 2 的Android手机验证测试这些代码,实际上这些示例代码在任何支持Snapdragon Neural Processing Engine SDK的手机上都可以运行,一次编写多次部署。
前置条件
- 在启动Android应用程序之前,请按照提供的链接说明进行设置Qualcomm Neural Processing SDK。Snapdragon Neural Processing Engine SDK: SNPE Setup
- 下载CocoDataset 2014并将其路径提供给Generate_DLC.ipynb。在两个模型的量化过程中更改变量“dataset_path”。
- 高通Snapdragon 安卓手机,推荐Snapdragon 8 Gen 2系列手机,可以用于测试应用程序
- 一台Linux机器
操作步骤:
1. 生成DLC
使用jupyter运行 GenerateDLC.ipynb。此脚本将生成两个dlc。
- 生成YoloNAS SSD型号为Quant_YoloNAS_s_320.dlc
- 生成HRNET模型为HRNET_axis_int8.dlc
2. 代码实现过程
代码架构
demo : 包含演示GIF
app : 包含标准Android应用程序格式的源文件
app\src\main\assets:包含模型二进制文件DLC
app\src\main\java\com\qc\objectdetectionYoloNas:应用程序java源代码
app\src\main\cpp:本机源代码
sdk:包含openCV sdk
相机预览设置
相机预览帧的权限在以下文件中授予:
/app/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.CAMERA" />
为了使用camera2 API,请添加以下功能
<uses-feature android:name="android.hardware.camera2" />
加载模型
神经网络连接和加载模型的代码片段:
snpe = snpeBuilder.setOutputLayers({})
.setPerformanceProfile(zdl::DlSystem::PerformanceProfile_t::BURST)
.setExecutionPriorityHint(
zdl::DlSystem::ExecutionPriorityHint_t::HIGH)
.setRuntimeProcessorOrder(runtimeList)
.setUseUserSuppliedBuffers(useUserSuppliedBuffers)
.setPlatformConfig(platformConfig)
.setInitCacheMode(useCaching)
.setCPUFallbackMode(true)
.setUnconsumedTensorsAsOutputs(true) //To get output from two nodes
.build(); //builds the network
后期处理
这包括为每2100 boxes去获得具有最高置信度的类,并应用“Non-Max Suppression”来删除重叠的boxes。 // Non-Max Suppression-- 非最大抑制即抑制不是极大值的元素,搜索局部的极大值
for(int i =0;i<(2100);i++)
{
int start = i*80;
int end = (i+1)*80;
auto it = max_element (BBout_class.begin()+start, BBout_class.begin()+end);
int index = distance(BBout_class.begin()+start, it);
std::string classname = classnamemapping[index];
if(*it>=0.5 )
{
int x1 = BBout_boxcoords[i * 4 + 0];
int y1 = BBout_boxcoords[i * 4 + 1];
int x2 = BBout_boxcoords[i * 4 + 2];
int y2 = BBout_boxcoords[i * 4 + 3];
Boxlist.push_back(BoxCornerEncoding(x1, y1, x2, y2,*it,classname));
}
}
std::vector<BoxCornerEncoding> reslist = NonMaxSuppression(Boxlist,0.20);
然后我们只缩放原始图像的坐标
float top,bottom,left,right;
left = reslist[k].y1 * ratio_1; //y1
right = reslist[k].y2 * ratio_1; //y2
bottom = reslist[k].x1 * ratio_2; //x1
top = reslist[k].x2 * ratio_2; //x2
姿势检测
位图图像作为openCV Mat传递给本机,然后将BGR Mat大小转换为256x192x3。基本图像处理取决于模型所需的输入形状类型,然后将处理后的图像写入应用程序缓冲区。根据我们接收到到框体坐标,我们对图像进行变换,因此只有框体中包含的像素会被点亮,所有其他像素都会变黑。这样做是为了隐藏框架中的任何其他人,因为HRNET被设计为最适合单人使用。
//Preprocessing
getcenterscale(img.cols, img.rows, center, scale, bottom, left, top, right); //get center and scale based on Box coordinates
cv::Mat trans = get_affine_transform(HRNET_model_input_height, HRNET_model_input_width, 0, center, scale);
cv::Mat model_input(HRNET_model_input_width,HRNET_model_input_height, img.type());
cv::warpAffine(img, model_input, trans,model_input.size(), cv::INTER_LINEAR);
cvtColor(model_input, model_input, CV_BGRA2BGR); //Changing num of channels
//Writing into buffer for inference
int lim = model_input.rows*model_input.cols*3;
float * accumulator = reinterpret_cast<float *> (&dest_buffer[0]);
for(int idx = 0; idx<lim; ){
accumulator[idx]= (float) (((model_input.data[idx] / 255.0f) - 0.485f) / 0.229f);
accumulator[idx+1] = (float) (((model_input.data[idx+1] / 255.0f) - 0.456f) / 0.224f);
accumulator[idx+2] = (float) (((model_input.data[idx+2] / 255.0f) - 0.406f) / 0.225f);
idx=idx+3;
}
绘制姿势
Model返回相应的Float张量,从中可以推断出对象的形状及其名称。画布用于为所有已识别的人及其姿势绘制一个矩形。
for (int i = 0; i < Connections.length; i++) {
int kpt_a = Connections[i][0];
int kpt_b = Connections[i][1];
int x_a = (int) coords[kpt_a][0];
int y_a = (int) coords[kpt_a][1];
int x_b = (int) coords[kpt_b][0];
int y_b = (int) coords[kpt_b][1];
if ((x_a | y_a) != 0) {
canvas.drawCircle(x_a, y_a, 8, mPosepaint);
if ((x_b | y_b) != 0) {
canvas.drawCircle(x_b, y_b, 8, mPosepaint);
canvas.drawLine(x_a, y_a, x_b, y_b, mPosepaint);
}
}
3. 整体执行操作流程
- Clone QIDK repo。
- 从脚本所在的目录中运行以下脚本,以解析此项目的依赖项。
- 将把snpe-release.aar文件从$snpe_ROOT复制到Android项目中的“snpe-reease”目录。
Note:如果您使用的是SNPE 2.11或更高版本,请更改resolveDependencies.sh中的以下行。windows版本如果找不到对应文件,可以在LINUX版本获取
From: cp $SNPE_ROOT/android/snpe-release.aar snpe-release
To : cp $SNPE_ROOT/lib/android/snpe-release.aar snpe-release
- 下载opencv并粘贴到sdk目录,启用android Java的opencv。
bash resolveDependencies.sh
3. 运行jupyter notebook GenerateDLC.ipynb为YoloNAS_SSD生成DLC,生成HRNET_axis_int8.DLC。更改dataset_path 为Coco Dataset路径。
- 此脚本生成所需的dlc并将其粘贴到适当的位置。
4. gradle sync
5. 编译项目
6. 应生成APK文件: app-debug.apk
7. 使用Snapdragon 8 Gen 2手机安装应用程序(不要在模拟器上运行APK)
8. 如果未检测到未签名或已签名的DSP runtime,请检查logcat日志中的FastRPC错误。如果由于SELinux权限的原因,可能会无法检测到DSP runtime。请尝试以下命令来设置SELinux权限。
adb disable-verity
adb reboot
adb root
adb remount
adb shell setenforce 0
9. 安装和测试应用程序: app-debug.apk
adb install -r -t enhancement-debug.apk
10. 启动应用程序
以下是“Pose Detection Yolo NAS”Android应用程序的基本操作
1. 首次启动应用程序时,用户需要提供相机权限
2. 相机将打开,如果屏幕上可以看到有人,就可以看到人物姿态
3. 用户可以为模型选择合适的run-time,并观察性能差异
演示视频和性能细节如下所示:
作者:戴忠忠(Zhongzhong Dai),高通工程师,