今天介绍一下使用百度AI EasyDL平台训练物体检测的模型,然后将模型的离线SDK部署到Android端,在APP上运行物体检测的功能。
先来成品效果展示:
VID_20221130_140257
移植Android端后APP运行结果图片
物体检测目标图片:
Android APP 物体检测识别效果:
物体检测目标图片:
Android APP 物体检测识别效果:
物体检测目标图片:
Android APP 物体检测识别效果:
制作流程:
1、环境的搭建;
2、制作数据集;
3、EasyDL标注数据集;
4、EasyDL训练模型;
5、发布离线SDK,部署Android端;
6、运行物体检测APP;
1、首先环境的搭建
完成上述物体检测效果需要安装一些环境,这里会给出一些开发环境的下载链接,安装教程可以去网上学习,这里不过多赘述。
1>. Android studio使用的是2020.3.1.26版本,jdk包和gradle工具也可在链接中下载。
链接:https://pan.baidu.com/s/1Z_j2rM0hOo33PfpUtAN5_w
提取码:wd4z
2>. pycharm安装包下载链接。
链接:https://pan.baidu.com/s/1ZxouNM9hzYgPefyfxaQKUw
提取码:0xdh
2、制作数据集
这里我的模型以竞赛中的车型图片为例,数据集我是截取了一段视频,然后使用pycharm编译器用python代码截取视频帧画面,将其保存至本地文件夹,这样基础的数据集就做好了。
这里注意视频格式我使用的是avi的格式,可通过网络一些视频在线转换工具将视频格式转换过来,小米手机MIUI12系统及以上支持重命名修改视频文件格式,在文件系统中重命名修改格式即可。
1>. 推荐的视频转换工具网址:
在线 & 免费地将 MP4 转换成 AVI — Convertio
2>. 截取视频帧python示例代码:
import os
import cv2
# 1、修改输入视频路径名
# 2、修改name文件夹名
# 3、修改保存路径
# 3、修改fileName中数据帧名,也就是图片名称
i = 0
cap = cv2.VideoCapture("E:\mp4task\交通标志or车型识别数据资料\/avi文件\VID_20221130_151521.avi") # 输入视频
isOpened = cap.isOpened
name = "go_straight/" # 修改目标文件夹名(自定义)
savedpath = r'./pictures/ds1/' + name # 修改保存路径(自定义)
isExists = os.path.exists(savedpath)
if not isExists:
os.makedirs(savedpath)
print('path of %s is build' % savedpath)
else:
print('path of %s already exist and rebuild' % savedpath)
while isOpened:
if i == 150:
break
else:
i = i + 1
(flag, frame) = cap.read()
fileName = 'go_straight' + str(i) + '.jpg' # 照片名字
if flag == True:
print(fileName)
cv2.imwrite(savedpath + fileName, frame) # 输出照片
如果没有安装opencv的需要先去安装opencv,随后编译运行上述代码即可在指定文件夹数据图片的数据集,数据集采集效果:
3、EasyDL标注数据集
使用百度AI EasyDL物体检测平台进行数据集的标注,数据集做好之后可将文件夹压缩zip包,然后再导入EasyDL平台进行标注。
首先压缩数据集
登录百度AI平台创建模型,填写好个人信息点击完成创建。
创建数据集
点击导入
上传压缩包数据集
导入完成后点击查看与标注
然后就是对图片进行识别物体的标注,这里的标签一定要记得添加,可以是识别物体的名称,然后拖动鼠标进行框选识别区域,如下图所示:
如果数据集较大的话,可以对每个识别的物体进行标注10张以上即可开启智能标注功能,这个功能简直极大缩短了数据集的标注时间,直接好评,但是智能标注完了之后也一定要记得检查平台自动标注的结果是否正确,偶尔还是会有识别混乱的情况出现的。
4、EasyDL训练模型
数据集制作好了之后就可以进行模型训练了。点击左侧训练模型选项,选择创建的目标模型,选择对应的数据集。
随后选择本地小型设备部署训练,最后点击开始训练你的模型就会在平台进行训练了。
经过一段时间的等待后,模型就训练好了,如果模型的精确率等训练效果达到了你的预期就可以进入下一步了,反之可修改平台模型训练的算法、模式等再次进行训练。
之后我们点击申请发布模型,选择SDK纯离线服务,点击发布。
因为是部署到Android上的所以这里我们选择系统为Android,通用芯片即可,最后点击发布。
等待模型发布成功后点击后面的APP体验,测试模型识别的效果。
将APP安装后即可进行拍照识别和实时识别,效果如文章开头所示。
5、发布离线SDK,部署Android端
那么接下来就到了最重要的Android端部署环节了。
部署流程如下:
1、下载模型离线SDK
2、新建Android工程
3、
1、下载模型SDK
解压缩文件
2、首先Android端先新建一个工程。
3、将下载离线SDK中app\libs下的文件复制到自己工程的libs目录下
右击点击Add AS Library为项目添加依赖
4、build gradle中添加模型要用的架构
打开下载的SDK中的build.gradle(记事本打开)
将其构架包复制到我们自己的项目中
然后同步工程
5、添加assets文件,将离线SDK app\src\main目录下的assets文件添加到自己的工程
6、Manifest配置添加联网权限手机存储权限等
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- Android 11 支持 -->
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<!-- 高版本 Android 支持 -->
<application
android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true">
</application>
然后就是调用程序代码导包,这里的调用接口都是参考官网Android部署文档
代码会贴在下面
这里要添加文件存储权限,非常重要!!!(我就是忘记添加权限了,搞了两三个小时)
还有一个特别重要的就是序列号了,需要根据模型的不同选择不同的序列号,选择基础版或者加速版
然后修改代码中的序列号,当项目在手机上运行就会激活序列号
然后就可以运行项目了,输出数据结果
MainActivity.java文件
package com.example.tesk2;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.baidu.ai.edge.core.base.BaseException;
import com.baidu.ai.edge.core.base.BaseManager;
import com.baidu.ai.edge.core.base.CallException;
import com.baidu.ai.edge.core.detect.DetectionResultModel;
import com.baidu.ai.edge.core.infer.InferConfig;
import com.baidu.ai.edge.core.infer.InferManager;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final int MY_PERMISSIONS_REQUEST_SAVE_PICTRUE = 1;
private static final int REQUEST_EXTERNAL_STORAGE = 1;
/* ---------------------------------------------------------------------------- */
// 交通标志识别 --- 百度EasyDL模型检测序列号
public static String SERIAL_NUM_TRAFFIC = "B32E-8807-E4DF-2D96";
public static final float CONFIDENCE = 0.5f; // 检测置信度
public static boolean Traffic_identifyFlag = false; // 交通标志识别标志位
public static InferConfig mInferConfig = null;
public static InferManager manager = null;
/* ---------------------------------------------------------------------------- */
public static Button mBtn_shibie;
public static Bitmap bitmapDis = null;
public static Bitmap tempBitmap = null;
public static TextView mTv_result,mResult_Ple,mResult_Zxd;
public static ImageView mImgDis;
public Activity activity;
private static final String[] PERMISSIONS_STORAGE = {
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE" };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
activity = this;
getPermissions(this); // 获取手机存储权限
verifyStoragePermissions(this);
initTraffic(); //初始化车型识别SDK
bitmapDis = BitmapFactory.decodeResource(getResources(), R.drawable.img);
// 创建线程对象,开启线程 (交通标志识别线程)
vehicleType_thread vehicleType_thread = new vehicleType_thread();
vehicleType_thread.start();
mImgDis = findViewById(R.id.imgDis);
mTv_result = findViewById(R.id.tv_result);
mResult_Ple = findViewById(R.id.result_Ple);
mResult_Zxd = findViewById(R.id.result_Zxd);
mBtn_shibie = findViewById(R.id.btn_shibie);
mBtn_shibie.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Traffic_identifyFlag = true;
mImgDis.setImageBitmap(bitmapDis);
mTv_result.setText("识别中~~~");
mResult_Ple.setText("");
mResult_Zxd.setText("");
mResult_Zxd.append("\u00A0\u00A0\u00A0\u00A0\u00A0");
}
});
}
public static Handler handler = new Handler(Looper.myLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case 167:
if(msg.obj == "识别状态") mTv_result.setText("识别完成");
break;
case 168:
if(msg.obj == "图像显示") mImgDis.setImageBitmap(tempBitmap);
case 169:
if(msg.arg1 == 1){
mResult_Ple.append(msg.obj.toString()+" ");
}
break;
case 170:
if(msg.arg1 == 1){
mResult_Zxd.append(msg.obj.toString()+" ");
}
default:
break;
}
}
};
/**
* 百度AI EasyDL离线SDK初始化
*/
public void initTraffic(){
try {
mInferConfig = new InferConfig(getAssets(), "infer");
manager = new InferManager(this, mInferConfig, SERIAL_NUM_TRAFFIC); // config为上一步的InferConfig
} catch (BaseException e) {
e.printStackTrace();
}
}
/**
* 获取存储权限
*/
public static void getPermissions(Context context){
if (ContextCompat.checkSelfPermission(context,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions((Activity) context,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_SAVE_PICTRUE);
//权限还没有授予,需要在这里写申请权限的代码
}
else{
//权限已申请,执行XXX操作
System.out.println("权限已申请");
}
}
//然后通过一个函数来申请
public static void verifyStoragePermissions(Activity activity) {
try {
//检测是否有写的权限
int permission = ActivityCompat.checkSelfPermission(activity,
"android.permission.WRITE_EXTERNAL_STORAGE");
if (permission != PackageManager.PERMISSION_GRANTED) {
// 没有写的权限,去申请写的权限,会弹出对话框
ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里是一个子线程,推荐在子线程中实现功能 vehicleType_thread.java文件
package com.example.tesk2;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Message;
import android.util.Log;
import com.baidu.ai.edge.core.base.BaseException;
import com.baidu.ai.edge.core.base.CallException;
import com.baidu.ai.edge.core.detect.DetectionResultModel;
import java.util.List;
import static com.example.tesk2.MainActivity.Traffic_identifyFlag;
import static com.example.tesk2.MainActivity.bitmapDis;
import static com.example.tesk2.MainActivity.handler;
import static com.example.tesk2.MainActivity.manager;
import static com.example.tesk2.MainActivity.tempBitmap;
public class vehicleType_thread extends Thread{
@Override
public void run() {
while(true){
try {
Thread.sleep(10);
// 判断是否进行交通标志物识别
if(Traffic_identifyFlag) {
traffic();
Traffic_identifyFlag = false;
System.out.println(Traffic_identifyFlag+" 交通标志识别结束");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 交通标志物识别
*/
public void traffic(){
Bitmap trafficBitmap = null;
try {
/* 获取图像 */
trafficBitmap = bitmapDis;
tempBitmap = trafficBitmap.copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(tempBitmap);
/* 2.2 推理图片及解析结果 */
List<DetectionResultModel> results = null;
String resStr;
for (int j = 0; j < 1; j++) {
// 在模型销毁前可以不断调用。但是不支持多线程。
if(trafficBitmap != null) {
results = manager.detect(trafficBitmap, 0.5f);
System.out.println("ceshi9996");
}
double denceMax = 0;
int denceIndex = 0;
// 解析结果
if (results != null) {
Message message = new Message();
message.obj = "识别状态";
message.what = 167;
handler.sendMessage(message);
System.out.println(results.size()+"数");
// 遍历获取最大置信度的图片
for (int i = 0; i < results.size(); i++) {
if (results.get(i).getConfidence() > denceMax) {
denceMax = results.get(i).getConfidence();
if (denceMax > 0.5) { // 过滤置信度低于0.5的结果
denceIndex = i;
}
}
resStr = "{size:" + results.size() + ", firstRes:{";
resStr += "labelName:" + results.get(denceIndex).getLabel() + ", "
+ "confidence:" + results.get(denceIndex).getConfidence() + ", "
+ "bounds:" + results.get(denceIndex).getBounds();
System.out.println(("Predict " + j + ": " + resStr + "\n"));
System.out.println("识别结果:" + results.get(denceIndex).getLabel());
System.out.println("置信度为:" + results.get(denceIndex).getConfidence());
Log.e("TAG", "识别结果: " + resStr);
Rect rect = results.get(0).getBounds();
Log.e("TAG", "外包围矩形:" + rect);
/**---------------------------------------------------------------------*/
//图像上画矩形
Paint paint = new Paint();
// 防锯齿
paint.setColor(Color.RED);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
//设置文本的对齐方式,可选值有 Paint.Align.LEFT、Paint.Align.CENTER、Paint.Align.RIGHT
//等。
paint.setTextAlign(Paint.Align.LEFT);
int sp = 200;
//设置文本大小,单位是 px,这个和我们平时使用的字体单位 sp 不同,所以最好进行转
//换。
paint.setTextSize(sp);
//设置文本的倾斜程度,skewx 取值于 0~1 之间,正负表示倾斜的方向 正表示向左倾斜。
paint.setTextSkewX(0.0f);
//给文本添加下载线,underline 为 true 表示添加
paint.setUnderlineText(false);
//设置文本的粗体样式,bold 为 true 表示粗体。
paint.setFakeBoldText(false);
//画识别结果
canvas.drawText(results.get(i).getLabel(), results.get(i).getBounds().centerX(),
results.get(i).getBounds().centerY(), paint);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);//不填充
paint.setStrokeWidth(10); //线的宽度
//画识别矩形框
canvas.drawRect(results.get(i).getBounds(), paint);
Message message1 = new Message();
message1.obj = "图像显示";
message1.what = 168;
handler.sendMessage(message1);
/**---------------------------------------------------------------------*/
Message message2 = new Message();
message2.obj = results.get(i).getLabel();
message2.what = 169;
message2.arg1 = 1;
handler.sendMessage(message2);
Message message3 = new Message();
message3.obj = results.get(i).getConfidence();
message3.what = 170;
message3.arg1 = 1;
handler.sendMessage(message3);
}
}
}
Traffic_identifyFlag = false;
} catch (CallException e) {
e.printStackTrace();
} catch (BaseException e) {
e.printStackTrace();
}
Traffic_identifyFlag = false;
}
}
activity_main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="360dp">
<ImageView
android:id="@+id/imgDis"
android:layout_width="wrap_content"
android:src="@drawable/img"
android:layout_height="360dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_margin="10dp"
android:orientation="vertical"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_margin="3dp"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:text="@string/tev_result"
android:textSize="21sp"
android:textColor="@color/black"
android:layout_marginStart="5dp"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_result"
android:textSize="21sp"
android:layout_width="wrap_content"
android:text="@string/null_tv"
android:layout_height="wrap_content"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_margin="3dp"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:text="@string/result_ple"
android:textColor="@color/black"
android:textSize="21sp"
android:layout_marginStart="5dp"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/result_Ple"
android:textSize="21sp"
android:layout_width="match_parent"
android:text="@string/null_tv"
android:layout_height="wrap_content"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_margin="3dp"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:text="@string/result_Zxd"
android:textColor="@color/black"
android:textSize="21sp"
android:layout_marginStart="5dp"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/result_Zxd"
android:textSize="21sp"
android:layout_width="match_parent"
android:text="@string/null_tv"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:gravity="center"
android:layout_height="wrap_content">
<Button
android:id="@+id/btn_shibie"
android:layout_width="wrap_content"
android:text="@string/shibie"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
strings.xml文件
<string name="shibie">识别</string> <string name="tev_result">识别状态:</string> <string name="null_tv" /> <string name="result_ple">识别结果:</string> <string name="result_Zxd">置信度:</string>
好了最后运行成功后就能看得到识别结果等。