目标检测是通过算法找出图像中所有感兴趣的目标(物体),确定它们的类别和位置。本文介绍了基于 高通Snapdragon Neural Processing Engine SDK和YoloNAS模型实现一个图片检测功能的应用,通过手机的相机采集视频画面,并对画面的目标检测与分类。该应用的所有源代码都可以在https://github.com/quic/qidk/tree/master/Solutions/VisionSolution1-ObjectDetection-YoloNas
上获得。
我们使用Snapdragon 8 Gen 2 的Android手机验证测试这些,实际上这些示例代码可以在任何支持Snapdragon Neural Processing Engine SDK的Android手机上都可以运行,真正的一次编写多次部署。
这里使用到的模型为YoloNAS:YOLO(You Only Look Once)是一种对象检测算法,YOLO-NAS是一种新的目标检测模型,由Deci AI团队开发。“NAS”代表“神经架构搜索”,是一种用于自动化神经网络架构设计过程的技术。NAS 不依赖手动设计和人类直觉,而是采用优化算法来发现最适合给定任务的架构。NAS 的目标是找到一种在准确性、计算复杂性和模型大小之间实现最佳权衡的架构。
示例代码前置条件
- 在运行Android示例应用程序之前,请使用提供的链接按照设置Qualcomm Neural Processing SDK 的说明进行操作。https://developer.qualcomm.com/sites/default/files/docs/snpe/setup.html
- 下载Coco 2014 数据集,并将其路径提供给Generate_DLC.ipynb。在对应的位置子修改“dataset_path”的值。
- 高通Snapdragon 安卓手机,推荐Snapdragon 8 Gen 2系列手机
- 一台Linux机器
操作步骤:
1. 生成DLC
运行示例代码中的 jupyter notebook GenerateDLC.ipynb,生成YoloNAS量化的dlc。
YoloNAS模型是在COCOdataset 上为80类日常对象进行训练的。类的列表可以在dataset 中找到:https://cocodataset.org/#explore
2. 代码关键实现过程
此应用程序打开相机预览,收集所有帧并将其转换为位图。通过将Quant_YoloNas_s_320.dlc作为输入,通过神经网络生成器来构建网络。然后将位图提供给模型进行推理,该模型返回相应的对象预测和定位。
代码目录结构
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
图像处理
位图图像作为openCV Mat传递给本地,然后转换为大小为320x320x3的BGR Mat。基本图像处理取决于模型所需的输入形状类型,然后将处理后的图像写入应用程序缓冲区。
cv::Mat img320;
cv::resize(img,img320,cv::Size(320,320),cv::INTER_LINEAR);
float inputScale = 0.00392156862745f; //normalization value, this is 1/255
float * accumulator = reinterpret_cast<float *> (&dest_buffer[0]);
//opencv read in BGRA by default
cvtColor(img320, img320, CV_BGRA2BGR);
LOGI("num of channels: %d",img320.channels());
int lim = img320.rows*img320.cols*3;
for(int idx = 0; idx<lim; idx++)
accumulator[idx]= img320.data[idx]*inputScale;
后期处理
这包括为每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
绘制边界框
RectangleBox rbox = boxlist.get(j);
float y = rbox.left;
float y1 = rbox.right;
float x = rbox.top;
float x1 = rbox.bottom;
String fps_textLabel = "FPS: "+String.valueOf(rbox.fps);
canvas.drawText(fps_textLabel,10,70,mTextColor);
String processingTimeTextLabel= rbox.processing_time+"ms";
canvas.drawRect(x1, y, x, y1, mBorderColor);
canvas.drawText(rbox.label,x1+10, y+40, mTextColor);
canvas.drawText(processingTimeTextLabel,x1+10, y+90, mTextColor);
整体执行操作流程
- Clone QIDK repo。
- 从脚本所在的目录中运行以下脚本,以解析此项目的依赖项。
- 把snpe-release.aar文件从$snpe_ROOT复制到Android project中的“snpe-reease”目录
注意-如果您使用的是SNPE 2.11或更高版本,请更改resolveDependencies.sh中的以下行。
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,为量化YOLO_NAMES DLC生成DLC(s)。另外,将dataset_path更改Coco Dataset 路径。
- 此脚本生成所需的dlc(s)并将其粘贴到适当的位置。
4.进行Gradle Sync
5.编译项目。
6.生成APK文件:app-debug.APK
7.使用Qualcomm Innovators开发工具包来安装应用程序(不要在模拟器上运行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 app-debug.apk
10.启动应用程序
以下是“目标检测”Android应用程序的基本操作
1.首次启动应用程序时,用户需要提供相机权限
2.如果屏幕上可以看到人,相机将打开并看到目标
3.用户可以为模型选择合适的run-time,并观察性能差异
应用程序演示
文章作者:高通工程师,戴忠忠
更多高通开发者资源及技术问题请访问:高通开发者论坛