Bootstrap

百度EasyDL物体检测离线SDK部署Android

今天介绍一下使用百度AI EasyDL平台训练物体检测的模型,然后将模型的离线SDK部署到Android端,在APP上运行物体检测的功能。

先来成品效果展示:

VID_20221130_140257

移植Android端后APP运行结果图片

9cfb703940904d83affc492640f20334.png

        

        物体检测目标图片:

84738b2a09c94ee0a7b7a82026486401.jpeg

        Android APP 物体检测识别效果:

d3a003ca03354d32907c2aaa02ba3a8e.png

        物体检测目标图片:

37c2b1a997c747e284ec0c2c154c55aa.jpeg

        Android APP 物体检测识别效果:

 

361d9ac21c99499092731a933fc5a3b3.png

 

 

        物体检测目标图片:

0e34a4d8db304f57910d99e1f861416c.jpeg

 

      Android APP 物体检测识别效果:

 

53ea203583bc41258f43fb16fecf5efc.png

 

制作流程:

        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,随后编译运行上述代码即可在指定文件夹数据图片的数据集,数据集采集效果:

1545226a700e46d9bf8f2035c99bb447.png

89ea39f571db45b68f8505520a32c0f0.png

 

3、EasyDL标注数据集

        使用百度AI EasyDL物体检测平台进行数据集的标注,数据集做好之后可将文件夹压缩zip包,然后再导入EasyDL平台进行标注。

首先压缩数据集

ecdb25693efc46059f9eeb1cf3ec3977.png

 登录百度AI平台创建模型,填写好个人信息点击完成创建。

fc6e698754a842dd9eeed13d68bb0c95.png

 

创建数据集

e7e1b20a6ebd42498279573659d33a2e.png

 点击导入

18424ee60b2349fbaaedb3d7a0915d12.png

 c9d5cb1810ec4f01868853cb8268964e.png

上传压缩包数据集

ebfd3b1f73f446d58cf555be02a4cb9c.png

 导入完成后点击查看与标注

afdc64934e6747f4a6fe61d23871aa74.png

 然后就是对图片进行识别物体的标注,这里的标签一定要记得添加,可以是识别物体的名称,然后拖动鼠标进行框选识别区域,如下图所示:

fec5aa0336db4ea288aed288f20e2f32.png

 如果数据集较大的话,可以对每个识别的物体进行标注10张以上即可开启智能标注功能,这个功能简直极大缩短了数据集的标注时间,直接好评,但是智能标注完了之后也一定要记得检查平台自动标注的结果是否正确,偶尔还是会有识别混乱的情况出现的。

 

4、EasyDL训练模型

数据集制作好了之后就可以进行模型训练了。点击左侧训练模型选项,选择创建的目标模型,选择对应的数据集。

6dd6da3b405844d6a8dabeb70f1ec1ec.png

随后选择本地小型设备部署训练,最后点击开始训练你的模型就会在平台进行训练了。

d59152b16e434e42962fd267df8d05e0.png

 经过一段时间的等待后,模型就训练好了,如果模型的精确率等训练效果达到了你的预期就可以进入下一步了,反之可修改平台模型训练的算法、模式等再次进行训练。

5441f037afbb490ab2a77426ca43d267.png

 之后我们点击申请发布模型,选择SDK纯离线服务,点击发布。

d520d4fe6b8f41a5b98bacb0c4ca5b26.png

 因为是部署到Android上的所以这里我们选择系统为Android,通用芯片即可,最后点击发布。

aac188855eb44037a54c54f93322ab97.png

 等待模型发布成功后点击后面的APP体验,测试模型识别的效果。

11831d75ad264a8d914e5f4edc4910cc.png

 

将APP安装后即可进行拍照识别和实时识别,效果如文章开头所示。

c383fe920b6c48debe17812720e50289.jpeg

0ebd9cca325e455bb5906a65a4769f37.jpeg

dff86f5c8a294e4da9ba4ab1f2c8be52.png

 

5、发布离线SDK,部署Android端

那么接下来就到了最重要的Android端部署环节了。

部署流程如下:

1、下载模型离线SDK

2、新建Android工程

3、

 

1、下载模型SDK

46540965a8fc4f4caa60ef15a3505126.png

 解压缩文件

928048b252fb4531b0efec847b2110c2.png

2、首先Android端先新建一个工程。

72ba248981ab4ec1a2e5f6b60ea3a9b9.png

3、将下载离线SDK中app\libs下的文件复制到自己工程的libs目录下

8163a73ef5d94180b581ec4abe22a9b8.png

16b9dcc9a9af45a4b93ab470e72bc3d4.png

 右击点击Add AS Library为项目添加依赖

b034389ff84a4cbc9b01963412bcd1b0.png

 4、build gradle中添加模型要用的架构

打开下载的SDK中的build.gradle(记事本打开)

cd4fe696855e4d59a6b91517fed4aec8.png

 将其构架包复制到我们自己的项目中d04b8fff144e415eb265968c8f6aaed8.png

 

b7e20fc7ffbe4deba0e7fe12cf1eb1a9.png

然后同步工程

7aecdffcba1d4eb3a5a3e19813d6cd36.png

 5、添加assets文件,将离线SDK app\src\main目录下的assets文件添加到自己的工程

32b8a8d653844fa78d0d165a1c062ab8.png

6、Manifest配置添加联网权限手机存储权限等

d854b3b13fde4ce89942abd36535dc2c.png

<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部署文档

代码会贴在下面

118c2a0a9dbd484597a7e092981bdda6.png

 这里要添加文件存储权限,非常重要!!!(我就是忘记添加权限了,搞了两三个小时)

4d78e810d5fb4c4182310bb8e1ebe628.png

还有一个特别重要的就是序列号了,需要根据模型的不同选择不同的序列号,选择基础版或者加速版

fb92f49d3be4452ab2d9f1ff3ef33c0d.png

 然后修改代码中的序列号,当项目在手机上运行就会激活序列号

ba3f69e304be418598a39fbb3591b97d.png

然后就可以运行项目了,输出数据结果

c63e15dc02224603b46d6a3713780061.png

 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>

好了最后运行成功后就能看得到识别结果等。

 

 

 

 

 

 

 

 

 

 

 

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;