精华内容
下载资源
问答
  • 基于MNNAndroid手机上实现图像分类
    千次阅读
    2020-09-05 21:57:05

    原文博客:Doi技术团队
    链接地址:https://blog.doiduoyi.com/authors/1584446358138
    初心:记录优秀的Doi技术团队学习经历
    本文链接:基于MNN在Android手机上实现图像分类

    前言

    MNN是一个轻量级的深度神经网络推理引擎,在端侧加载深度神经网络模型进行推理预测。目前,MNN已经在阿里巴巴的手机淘宝、手机天猫、优酷等20多个App中使用,覆盖直播、短视频、搜索推荐、商品图像搜索、互动营销、权益发放、安全风控等场景。此外,IoT等场景下也有若干应用。

    下面就介绍如何使用MNN在Android设备上实现图像分类。

    教程源码地址:https://github.com/yeyupiaoling/ClassificationForAndroid/blob/master/MNNClassification

    编译库和转换模型

    编译MNN的Android动态库

    1. https://developer.android.com/ndk/downloads/下载安装NDK,建议使用最新稳定版本
    2. 在 .bashrc 或者 .bash_profile 中设置 NDK 环境变量,例如:export ANDROID_NDK=/Users/username/path/to/android-ndk-r14b
    3. cd /path/to/MNN
    4. ./schema/generate.sh
    5. cd project/android
    6. 编译armv7动态库:mkdir build_32 && cd build_32 && ../build_32.sh
    7. 编译armv8动态库:mkdir build_64 && cd build_64 && ../build_64.sh

    模型转换

    执行下面命令,得到模型转换工具MNNConvert

    cd MNN/
    ./schema/generate.sh
    mkdir build
    cd build
    cmake .. -DMNN_BUILD_CONVERTER=true && make -j4
    

    通过以下命令可以把其他框架的模型转换为MNN模型。

    TensorFlow -> MNN

    把Tensorflow的冻结图模型转换为MNN模型,bizCode指定标记码,这个随便吧。如果冻结图转换不成功,可以使用下面的Tensorflow Lite模型,这个通常会成功。

    ./MNNConvert -f TF --modelFile XXX.pb --MNNModel XXX.mnn --bizCode biz
    

    TensorFlow Lite -> MNN

    把Tensorflow Lite的模型转换为MNN模型,bizCode指定标记码。

    ./MNNConvert -f TFLITE --modelFile XXX.tflite --MNNModel XXX.mnn --bizCode biz
    

    Caffe -> MNN

    把Caffe的模型转换为MNN模型,bizCode指定标记码。

    ./MNNConvert -f CAFFE --modelFile XXX.caffemodel --prototxt XXX.prototxt --MNNModel XXX.mnn --bizCode biz
    

    ONNX -> MNN

    把ONNX 的模型转换为MNN模型,bizCode指定标记码。

    ./MNNConvert -f ONNX --modelFile XXX.onnx --MNNModel XXX.mnn --bizCode biz
    

    Android应用开发

    把生成的C++的头文件放在app/include/MNN/目录下,把生成的动态库文件放在app/src/main/jniLibs/目录下,在app/src/main/cpp/目录下编写JNI的C++代码,com.yeyupiaoling.mnnclassification.mnn包下放JNI的java代码和MNN的相关工具类,将转换的模型放在assets目录下。

    MNN工具

    编写一个MNNClassification.java工具类,关于MNN的操作都在这里完成,如加载模型、预测。在构造方法中,通过参数传递的模型路径加载模型,在加载模型的时候配置预测信息,例如是否使用CPU或者GPU,同时获取网络的输入输出层。同时MNN还提供了很多的图像预处理工具,对图像的预处理非常简单。要注意的是图像的均值dataConfig.mean和标准差dataConfig.normal,还有图片的输入通道顺序dataConfig.dest,因为在训练的时候图像预处理可能不一样的,有些读者出现在电脑上准确率很高,但在手机上准确率很低,多数情况下就是这个图像预处理做得不对。

    public MNNClassification(String modelPath) throws Exception {
        dataConfig = new MNNImageProcess.Config();
        dataConfig.mean = new float[]{128.0f, 128.0f, 128.0f};
        dataConfig.normal = new float[]{0.0078125f, 0.0078125f, 0.0078125f};
        dataConfig.dest = MNNImageProcess.Format.RGB;
        imgData = new Matrix();
    
        File file = new File(modelPath);
        if (!file.exists()) {
            throw new Exception("model file is not exists!");
        }
        try {
            mNetInstance = MNNNetInstance.createFromFile(modelPath);
            MNNNetInstance.Config config = new MNNNetInstance.Config();
            config.numThread = NUM_THREADS;
            config.forwardType = MNNForwardType.FORWARD_CPU.type;
            mSession = mNetInstance.createSession(config);
            mInputTensor = mSession.getInput(null);
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("load model fail!");
        }
    }
    

    为了兼容图片路径和Bitmap格式的图片预测,这里创建了两个重载方法,它们都是通过调用predict()

    public int predictImage(String image_path) throws Exception {
        if (!new File(image_path).exists()) {
            throw new Exception("image file is not exists!");
        }
        FileInputStream fis = new FileInputStream(image_path);
        Bitmap bitmap = BitmapFactory.decodeStream(fis);
        int result = predictImage(bitmap);
        if (bitmap.isRecycled()) {
            bitmap.recycle();
        }
        return result;
    }
    
    public int predictImage(Bitmap bitmap) throws Exception {
        return predict(bitmap);
    }
    

    这里创建一个获取最大概率值,并把下标返回的方法,其实就是获取概率最大的预测标签。

    public static int getMaxResult(float[] result) {
        float probability = 0;
        int r = 0;
        for (int i = 0; i < result.length; i++) {
            if (probability < result[i]) {
                probability = result[i];
                r = i;
            }
        }
        return r;
    }
    

    这个方法就是MNN执行预测的最后一步,通过执行mSession.run()对输入的数据进行预测并得到预测结果,通过解析获取到最大的概率的预测标签,并返回。到这里MNN的工具就完成了。

    private float[] predict(Bitmap bmp) throws Exception {
        imgData.reset();
        imgData.postScale(inputWidth / (float) bmp.getWidth(), inputHeight / (float) bmp.getHeight());
        imgData.invert(imgData);
        MNNImageProcess.convertBitmap(bmp, mInputTensor, dataConfig, imgData);
    
        try {
            mSession.run();
        } catch (Exception e) {
            throw new Exception("predict image fail! log:" + e);
        }
        MNNNetInstance.Session.Tensor output = mSession.getOutput(null);
        float[] result = output.getFloatData();
        Log.d(TAG, Arrays.toString(result));
        int l = getMaxResult(result);
        return new float[]{l, result[l]};
    }
    

    选择图片预测

    本教程会有两个页面,一个是选择图片进行预测的页面,另一个是使用相机实时预测并显示预测结果。以下为activity_main.xml的代码,通过按钮选择图片,并在该页面显示图片和预测结果。

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout 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:orientation="vertical"
        tools:context=".MainActivity">
    
        <ImageView
            android:id="@+id/image_view"
            android:layout_width="match_parent"
            android:layout_height="400dp" />
    
        <TextView
            android:id="@+id/result_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/image_view"
            android:text="识别结果"
            android:textSize="16sp" />
    
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:orientation="horizontal">
    
            <Button
                android:id="@+id/select_img_btn"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="选择照片" />
    
    
            <Button
                android:id="@+id/open_camera"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="实时预测" />
    
        </LinearLayout>
    
    </RelativeLayout>
    

    MainActivity.java中,进入到页面我们就要先加载模型,我们是把模型放在Android项目的assets目录的,我们需要把模型复制到一个缓存目录,然后再从缓存目录加载模型,同时还有读取标签名,标签名称按照训练的label顺序存放在assets的label_list.txt,以下为实现代码。

    classNames = Utils.ReadListFromFile(getAssets(), "label_list.txt");
    String classificationModelPath = getCacheDir().getAbsolutePath() + File.separator + "mobilenet_v2.mnn";
    Utils.copyFileFromAsset(MainActivity.this, "mobilenet_v2.mnn", classificationModelPath);
    try {
        mnnClassification = new MNNClassification(classificationModelPath);
        Toast.makeText(MainActivity.this, "模型加载成功!", Toast.LENGTH_SHORT).show();
    } catch (Exception e) {
        Toast.makeText(MainActivity.this, "模型加载失败!", Toast.LENGTH_SHORT).show();
        e.printStackTrace();
        finish();
    }
    

    添加两个按钮点击事件,可以选择打开相册读取图片进行预测,或者打开另一个Activity进行调用摄像头实时识别。

    Button selectImgBtn = findViewById(R.id.select_img_btn);
    Button openCamera = findViewById(R.id.open_camera);
    imageView = findViewById(R.id.image_view);
    textView = findViewById(R.id.result_text);
    selectImgBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 打开相册
            Intent intent = new Intent(Intent.ACTION_PICK);
            intent.setType("image/*");
            startActivityForResult(intent, 1);
        }
    });
    openCamera.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 打开实时拍摄识别页面
            Intent intent = new Intent(MainActivity.this, CameraActivity.class);
            startActivity(intent);
        }
    });
    

    当打开相册选择照片之后,回到原来的页面,在下面这个回调方法中获取选择图片的Uri,通过Uri可以获取到图片的绝对路径。如果Android8以上的设备获取不到图片,需要在AndroidManifest.xml配置文件中的application添加android:requestLegacyExternalStorage="true"。拿到图片路径之后,调用TFLiteClassificationUtil类中的predictImage()方法预测并获取预测值,在页面上显示预测的标签、对应标签的名称、概率值和预测时间。

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        String image_path;
        if (resultCode == Activity.RESULT_OK) {
            if (requestCode == 1) {
                if (data == null) {
                    Log.w("onActivityResult", "user photo data is null");
                    return;
                }
                Uri image_uri = data.getData();
                image_path = getPathFromURI(MainActivity.this, image_uri);
                try {
                    // 预测图像
                    FileInputStream fis = new FileInputStream(image_path);
                    imageView.setImageBitmap(BitmapFactory.decodeStream(fis));
                    long start = System.currentTimeMillis();
                    float[] result = mnnClassification.predictImage(image_path);
                    long end = System.currentTimeMillis();
                    String show_text = "预测结果标签:" + (int) result[0] +
                            "\n名称:" +  classNames[(int) result[0]] +
                            "\n概率:" + result[1] +
                            "\n时间:" + (end - start) + "ms";
                    textView.setText(show_text);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    上面获取的Uri可以通过下面这个方法把Url转换成绝对路径。

    // get photo from Uri
    public static String getPathFromURI(Context context, Uri uri) {
        String result;
        Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
        if (cursor == null) {
            result = uri.getPath();
        } else {
            cursor.moveToFirst();
            int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
            result = cursor.getString(idx);
            cursor.close();
        }
        return result;
    }
    

    摄像头实时预测

    在调用相机实时预测我就不再介绍了,原理都差不多,具体可以查看https://github.com/yeyupiaoling/ClassificationForAndroid/tree/master/TFLiteClassification中的源代码。核心代码如下,创建一个子线程,子线程中不断从摄像头预览的AutoFitTextureView上获取图像,并执行预测,并在页面上显示预测的标签、对应标签的名称、概率值和预测时间。每一次预测完成之后都立即获取图片继续预测,只要预测速度够快,就可以看成实时预测。

    private Runnable periodicClassify =
            new Runnable() {
                @Override
                public void run() {
                    synchronized (lock) {
                        if (runClassifier) {
                            // 开始预测前要判断相机是否已经准备好
                            if (getApplicationContext() != null && mCameraDevice != null && mnnClassification != null) {
                                predict();
                            }
                        }
                    }
                    if (mInferThread != null && mInferHandler != null && mCaptureHandler != null && mCaptureThread != null) {
                        mInferHandler.post(periodicClassify);
                    }
                }
            };
    
    // 预测相机捕获的图像
    private void predict() {
        // 获取相机捕获的图像
        Bitmap bitmap = mTextureView.getBitmap();
        try {
            // 预测图像
            long start = System.currentTimeMillis();
            float[] result = mnnClassification.predictImage(bitmap);
            long end = System.currentTimeMillis();
            String show_text = "预测结果标签:" + (int) result[0] +
                    "\n名称:" +  classNames[(int) result[0]] +
                    "\n概率:" + result[1] +
                    "\n时间:" + (end - start) + "ms";
            textView.setText(show_text);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

    本项目中使用的了读取图片的权限和打开相机的权限,所以不要忘记在AndroidManifest.xml添加以下权限申请。

    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    

    如果是Android 6 以上的设备还要动态申请权限。

        // check had permission
        private boolean hasPermission() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                return checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&
                        checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
                        checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
            } else {
                return true;
            }
        }
    
        // request permission
        private void requestPermission() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                requestPermissions(new String[]{Manifest.permission.CAMERA,
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
            }
        }
    

    效果图:
    在这里插入图片描述

    更多相关内容
  • 该移动端web分类页面是使用HTML5+CSS3+JQuery写的,样式采用LESS写法,适应各类分辨率的手机屏幕,适合前端小白对移动端web端开发的实践项目,欢迎下载学习!
  • 自己做的手机分类界面效果,简约大方; 介绍:https://blog.csdn.net/hanzhuhuaa/article/details/121277938
  • html5手机端导航分类选项卡滑动效果 html5手机端导航分类选项卡滑动效果 html5手机端导航分类选项卡滑动效果 html5手机端导航分类选项卡滑动效果
  • 基于TNNAndroid手机上实现图像分类

    千次阅读 热门讨论 2020-09-06 11:29:00
    本文链接:基于TNNAndroid手机上实现图像分类 前言 TNN:由腾讯优图实验室打造,移动端高性能、轻量级推理框架,同时拥有跨平台、高性能、模型压缩、代码裁剪等众多突出优势。TNN框架原有Rapidnet、ncnn框架的...

    原文博客:Doi技术团队
    链接地址:https://blog.doiduoyi.com/authors/1584446358138
    初心:记录优秀的Doi技术团队学习经历
    本文链接:基于TNN在Android手机上实现图像分类

    前言

    TNN:由腾讯优图实验室打造,移动端高性能、轻量级推理框架,同时拥有跨平台、高性能、模型压缩、代码裁剪等众多突出优势。TNN框架在原有Rapidnet、ncnn框架的基础上进一步加强了移动端设备的支持以及性能优化,同时也借鉴了业界主流开源框架高性能和良好拓展性的优点。

    教程源码地址:https://github.com/yeyupiaoling/ClassificationForAndroid/tree/master/TNNClassification

    编译Android库

    1. 安装cmake 3.12
    # 卸载旧的cmake
    sudo apt-get autoremove cmake
    
    # 下载cmake3.12
    wget https://cmake.org/files/v3.12/cmake-3.12.2-Linux-x86_64.tar.gz
    tar zxvf cmake-3.12.2-Linux-x86_64.tar.gz
    
    # 移动目录并添加软连接
    sudo mv cmake-3.12.2-Linux-x86_64 /opt/cmake-3.12.2
    sudo ln -sf /opt/cmake-3.12.2/bin/*  /usr/bin/
    
    1. 添加Android NDK
    wget https://dl.google.com/android/repository/android-ndk-r21b-linux-x86_64.zip
    unzip android-ndk-r21b-linux-x86_64.zip
    # 添加环境变量,留意你实际下载地址
    export ANDROID_NDK=/mnt/d/android-ndk-r21b
    
    1. 安装编译环境
    sudo apt-get install attr
    
    1. 开始编译
    git clone https://github.com/Tencent/TNN.git
    cd TNN/scripts
    
    vim build_android.sh
    
     ABIA32="armeabi-v7a"
     ABIA64="arm64-v8a"
     STL="c++_static"
     SHARED_LIB="ON"                # ON表示编译动态库,OFF表示编译静态库
     ARM="ON"                       # ON表示编译带有Arm CPU版本的库
     OPENMP="ON"                    # ON表示打开OpenMP
     OPENCL="ON"                    # ON表示编译带有Arm GPU版本的库
     SHARING_MEM_WITH_OPENGL=0      # 1表示OpenGL的Texture可以与OpenCL共享
    

    执行编译

    ./build_android.sh
    

    编译完成后,会在当前目录的release目录下生成对应的armeabi-v7a库,arm64-v8a库和include头文件,这些文件在下一步的Android开发都需要使用到。

    模型转换

    接下来我们需要把Tensorflow,onnx等其他的模型转换为TNN的模型。目前 TNN 支持业界主流的模型文件格式,包括ONNX、PyTorch、TensorFlow 以及 Caffe 等。TNN 将 ONNX 作为中间层,借助于ONNX 开源社区的力量,来支持多种模型文件格式。如果要将PyTorch、TensorFlow 以及 Caffe 等模型文件格式转换为 TNN,首先需要使用对应的模型转换工具,统一将各种模型格式转换成为 ONNX 模型格式,然后将 ONNX 模型转换成 TNN 模型。

    sudo docker pull turandotkay/tnn-convert
    sudo docker tag turandotkay/tnn-convert:latest tnn-convert:latest
    sudo docker rmi turandotkay/tnn-convert:latest
    

    针对不同的模型转换,有不同的命令,如onnx2tnn,caffe2tnn,tf2tnn。

    docker run --volume=$(pwd):/workspace -it tnn-convert:latest  python3 ./converter.py tf2tnn \
        -tp /workspace/mobilenet_v1.pb \
        -in "input[1,224,224,3]" \
        -on MobilenetV1/Predictions/Reshape_1 \
        -v v1.0 \
        -optimize
    

    通过上面的输出,可以发现针对 TF 模型的转换,convert2tnn 工具提供了很多参数,我们一次对下面的参数进行解释:

    • tp 参数(必须)
      通过 “-tp” 参数指定需要转换的模型的路径。目前只支持单个 TF模型的转换,不支持多个 TF 模型的一起转换。
    • in 参数(必须)
      通过 “-in” 参数指定模型输入的名称,输入的名称需要放到“”中,例如,-in “name”。如果模型有多个输入,请使用 “;”进行分割。有的 TensorFlow 模型没有指定 batch 导致无法成功转换为 ONNX 模型,进而无法成功转换为 TNN 模型。你可以通过在名称后添加输入 shape 进行指定。shape 信息需要放在 [] 中。例如:-in “name[1,28,28,3]”。
    • on 参数(必须)
      通过 “-on” 参数指定模型输入的名称,如果模型有多个输出,请使用 “;”进行分割
    • output_dir 参数:
      可以通过 “-o ” 参数指定输出路径,但是在 docker 中我们一般不使用这个参数,默认会将生成的 TNN 模型放在当前和 TF 模型相同的路径下。
    • optimize 参数(可选)
      可以通过 “-optimize” 参数来对模型进行优化,我们强烈建议你开启这个选项,只有在开启这个选项模型转换失败时,我们才建议你去掉 “-optimize” 参数进行重新尝试
    • v 参数(可选)
      可以通过 -v 来指定模型的版本号,以便于后期对模型进行追踪和区分。
    • half 参数(可选)
      可以通过 -half 参数指定,模型数据通过 FP16 进行存储,减少模型的大小,默认是通过 FP32 的方式进行存储模型数据的。
    • align 参数(可选)
      可以通过 -align 参数指定,将 转换得到的 TNN 模型和原模型进行对齐,确定 TNN 模型是否转换成功。当前仅支持单输入单输出模型和单输入多输出模型。 align 只支持 FP32 模型的校验,所以使用 align 的时候不能使用 half
    • input_file 参数(可选)
      可以通过 -input_file 参数指定模型对齐所需要的输入文件的名称,输入需要遵循如下格式
    • ref_file 参数(可选)
      可以通过 -ref_file 参数指定待对齐的输出文件的名称,输出需遵循如下格式。生成输出的代码可以参考

    成功转换会输出以下的日志。

    ----------  convert model, please wait a moment ----------
    
    Converter Tensorflow to TNN model
    
    Convert TensorFlow to ONNX model succeed!
    
    Converter ONNX to TNN Model
    
    Converter ONNX to TNN model succeed!
    

    最终会得到这两个模型文件,mobilenet_v1.opt.tnnmodel mobilenet_v1.opt.tnnproto

    开发Android项目

    1. 将转换的模型放在assets目录下。
    2. 把上一步编译得到的include目录复制到Android项目的app目录下。
    3. 把上一步编译得到的armeabi-v7aarm64-v8a目录复制到main/jniLibs下。
    4. app/src/main/cpp/目录下编写JNI的C++代码。

    TNN工具

    编写一个ImageClassifyUtil.java工具类,关于TNN的操作都在这里完成,如加载模型、预测。

    下面三个就是TNN的JNI接口,通过这个接口完成模型加载,预测,当不使用的时候和可以调用deinit()清空对象。

    public native int init(String modelPath, String protoPath, int computeUnitType);
    
    public native float[] predict(Bitmap image, int width, int height);
    
    public native int deinit();
    

    通过上面的JNI接口,下面就可以实现图像识别了,WIDTHHEIGHT是模型输入图片的大小。为了兼容图片路径和Bitmap格式的图片预测,这里创建了两个重载方法。

    private static final int WIDTH = 224;
    private  static final int HEIGHT = 224;
    
    public ImageClassifyUtil() {
        System.loadLibrary("TNN");
        System.loadLibrary("tnn_wrapper");
    }
    
    // 重载方法,根据图片路径转Bitmap预测
    public float[] predictImage(String image_path) throws Exception {
        if (!new File(image_path).exists()) {
            throw new Exception("image file is not exists!");
        }
        FileInputStream fis = new FileInputStream(image_path);
        Bitmap bitmap = BitmapFactory.decodeStream(fis);
        Bitmap scaleBitmap = Bitmap.createScaledBitmap(bitmap, WIDTH, HEIGHT, false);
        float[] result = predictImage(scaleBitmap);
        if (bitmap.isRecycled()) {
            bitmap.recycle();
        }
        return result;
    }
    
    // 重载方法,直接使用Bitmap预测
    public float[] predictImage(Bitmap bitmap) {
        Bitmap scaleBitmap = Bitmap.createScaledBitmap(bitmap, WIDTH, HEIGHT, false);
        float[] results = predict(scaleBitmap, WIDTH, HEIGHT);
        int l = getMaxResult(results);
        return new float[]{l, results[l] * 0.01f};
    }
    

    这里创建一个获取最大概率值,并把下标返回的方法,其实就是获取概率最大的预测标签。

    public static int getMaxResult(float[] result) {
        float probability = 0;
        int r = 0;
        for (int i = 0; i < result.length; i++) {
            if (probability < result[i]) {
                probability = result[i];
                r = i;
            }
        }
        return r;
    }
    

    不同的模型,训练的预处理方式可能不一样,TNN 的图像预处理在C++中完成,代码片段

    TNN_NS::MatConvertParam input_cvt_param;
    input_cvt_param.scale = {1.0 / (255 * 0.229), 1.0 / (255 * 0.224), 1.0 / (255 * 0.225), 0.0};
    input_cvt_param.bias  = {-0.485 / 0.229, -0.456 / 0.224, -0.406 / 0.225, 0.0};
    auto status = instance_->SetInputMat(input_mat, input_cvt_param);
    

    选择图片预测

    本教程会有两个页面,一个是选择图片进行预测的页面,另一个是使用相机实时预测并显示预测结果。以下为activity_main.xml的代码,通过按钮选择图片,并在该页面显示图片和预测结果。

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout 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:orientation="vertical"
        tools:context=".MainActivity">
    
        <ImageView
            android:id="@+id/image_view"
            android:layout_width="match_parent"
            android:layout_height="400dp" />
    
        <TextView
            android:id="@+id/result_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/image_view"
            android:text="识别结果"
            android:textSize="16sp" />
    
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:orientation="horizontal">
    
            <Button
                android:id="@+id/select_img_btn"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="选择照片" />
    
    
            <Button
                android:id="@+id/open_camera"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="实时预测" />
    
        </LinearLayout>
    
    </RelativeLayout>
    

    MainActivity.java中,进入到页面我们就要先加载模型,我们是把模型放在Android项目的assets目录的,我们需要把模型复制到一个缓存目录,然后再从缓存目录加载模型,同时还有读取标签名,标签名称按照训练的label顺序存放在assets的label_list.txt,以下为实现代码。

    classNames = Utils.ReadListFromFile(getAssets(), "label_list.txt");
    String protoContent = getCacheDir().getAbsolutePath() + File.separator + "squeezenet_v1.1.tnnproto";
    Utils.copyFileFromAsset(MainActivity.this, "squeezenet_v1.1.tnnproto", protoContent);
    String modelContent = getCacheDir().getAbsolutePath() + File.separator + "squeezenet_v1.1.tnnmodel";
    Utils.copyFileFromAsset(MainActivity.this, "squeezenet_v1.1.tnnmodel", modelContent);
    
    imageClassifyUtil = new ImageClassifyUtil();
    int status = imageClassifyUtil.init(modelContent, protoContent, USE_GPU ? 1 : 0);
    if (status == 0){
        Toast.makeText(MainActivity.this, "模型加载成功!", Toast.LENGTH_SHORT).show();
    }else {
        Toast.makeText(MainActivity.this, "模型加载失败!", Toast.LENGTH_SHORT).show();
        finish();
    }
    

    添加两个按钮点击事件,可以选择打开相册读取图片进行预测,或者打开另一个Activity进行调用摄像头实时识别。

    Button selectImgBtn = findViewById(R.id.select_img_btn);
    Button openCamera = findViewById(R.id.open_camera);
    imageView = findViewById(R.id.image_view);
    textView = findViewById(R.id.result_text);
    selectImgBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 打开相册
            Intent intent = new Intent(Intent.ACTION_PICK);
            intent.setType("image/*");
            startActivityForResult(intent, 1);
        }
    });
    openCamera.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 打开实时拍摄识别页面
            Intent intent = new Intent(MainActivity.this, CameraActivity.class);
            startActivity(intent);
        }
    });
    

    当打开相册选择照片之后,回到原来的页面,在下面这个回调方法中获取选择图片的Uri,通过Uri可以获取到图片的绝对路径。如果Android8以上的设备获取不到图片,需要在AndroidManifest.xml配置文件中的application添加android:requestLegacyExternalStorage="true"。拿到图片路径之后,调用TFLiteClassificationUtil类中的predictImage()方法预测并获取预测值,在页面上显示预测的标签、对应标签的名称、概率值和预测时间。

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        String image_path;
        if (resultCode == Activity.RESULT_OK) {
            if (requestCode == 1) {
                if (data == null) {
                    Log.w("onActivityResult", "user photo data is null");
                    return;
                }
                Uri image_uri = data.getData();
                image_path = getPathFromURI(MainActivity.this, image_uri);
                try {
                    // 预测图像
                    FileInputStream fis = new FileInputStream(image_path);
                    imageView.setImageBitmap(BitmapFactory.decodeStream(fis));
                    long start = System.currentTimeMillis();
                    float[] result = imageClassifyUtil.predictImage(image_path);
                    long end = System.currentTimeMillis();
                    String show_text = "预测结果标签:" + (int) result[0] +
                            "\n名称:" +  classNames[(int) result[0]] +
                            "\n概率:" + result[1] +
                            "\n时间:" + (end - start) + "ms";
                    textView.setText(show_text);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    上面获取的Uri可以通过下面这个方法把Url转换成绝对路径。

    // get photo from Uri
    public static String getPathFromURI(Context context, Uri uri) {
        String result;
        Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
        if (cursor == null) {
            result = uri.getPath();
        } else {
            cursor.moveToFirst();
            int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
            result = cursor.getString(idx);
            cursor.close();
        }
        return result;
    }
    

    摄像头实时预测

    在调用相机实时预测我就不再介绍了,原理都差不多,具体可以查看https://github.com/yeyupiaoling/ClassificationForAndroid/tree/master/TFLiteClassification中的源代码。核心代码如下,创建一个子线程,子线程中不断从摄像头预览的AutoFitTextureView上获取图像,并执行预测,并在页面上显示预测的标签、对应标签的名称、概率值和预测时间。每一次预测完成之后都立即获取图片继续预测,只要预测速度够快,就可以看成实时预测。

    private Runnable periodicClassify =
            new Runnable() {
                @Override
                public void run() {
                    synchronized (lock) {
                        if (runClassifier) {
                            // 开始预测前要判断相机是否已经准备好
                            if (getApplicationContext() != null && mCameraDevice != null && mnnClassification != null) {
                                predict();
                            }
                        }
                    }
                    if (mInferThread != null && mInferHandler != null && mCaptureHandler != null && mCaptureThread != null) {
                        mInferHandler.post(periodicClassify);
                    }
                }
            };
    
    // 预测相机捕获的图像
    private void predict() {
        // 获取相机捕获的图像
        Bitmap bitmap = mTextureView.getBitmap();
        try {
            // 预测图像
            long start = System.currentTimeMillis();
            float[] result = imageClassifyUtil.predictImage(bitmap);
            long end = System.currentTimeMillis();
            String show_text = "预测结果标签:" + (int) result[0] +
                    "\n名称:" +  classNames[(int) result[0]] +
                    "\n概率:" + result[1] +
                    "\n时间:" + (end - start) + "ms";
            textView.setText(show_text);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

    本项目中使用的了读取图片的权限和打开相机的权限,所以不要忘记在AndroidManifest.xml添加以下权限申请。

    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    

    如果是Android 6 以上的设备还要动态申请权限。

        // check had permission
        private boolean hasPermission() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                return checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&
                        checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
                        checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
            } else {
                return true;
            }
        }
    
        // request permission
        private void requestPermission() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                requestPermissions(new String[]{Manifest.permission.CAMERA,
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
            }
        }
    

    效果图:
    在这里插入图片描述

    展开全文
  • 仿京东分类Html5页面

    2019-03-05 14:53:01
    仿京东分类Html5页面,左右分栏,可滑动,支持多层级扩展,CSS样式
  • demo地址:http://demo.lanrenzhijia.com/demo/41/4133/demo/ 项目中用到的分类界面要的效果类似于京东移动端的分类页面:两侧都可滑动,且左侧选择后右侧自动定位
  • 支持支付宝,财付通支付插件5:支持QQ登陆6: 支持vip会员格子显示vip单独标志,支持置顶功能7:图片广告管理功能牡丹江分类信息港带手机版前台页面牡丹江分类信息港带手机手机页面牡丹江分类信息港带手机版后台...
  • jQuery京东手机端商品分类滑动切换是一款仿京东移动web端商品分类导航滑动效果展示。
  • html5手机端商城模板,完整的一套商城系统的所有静态网页 。登陆页面、首页、分类、购物车、我的等等,基本一个商城所有的模块都覆盖到了,希望能帮到你。
  • 简单易用,容易上手,简洁手机微信商城页面模板,全套模板,包括首页、新增收货地址、商品分类、物物车、支付、管理收货地址、商品详情、订单等手机模板页面
  • HTML怎么在手机打开

    千次阅读 2021-06-17 03:31:54
    2回答2021-05-06浏览:9分类:其他问题回答:HTML文件可以用手机浏览器打开。从本质来说,Internet( 互联网)是一个由一系列传输协议和各类文档所组成的集合,HTML文件只是其中的一种。这些HTML文件存储分布于...

    2

    回答

    2021-05-06

    浏览:9

    分类:其他问题

    回答:

    HTML文件可以用手机浏览器打开。

    从本质上来说,Internet( 互联网)是一个由一系列传输协议和各类文档所组成的集合,HTML文件只是其中的一种。这些HTML文件存储在分布于世界各地的服务器硬盘上,通过传输协议用户可以远程获取这些文件所传达的资讯和信息。

    网络浏览器,能够解释HTML文件来显示网页,这是网络浏览器的主要作用。当你使用浏览器在互联网上浏览网页时,浏览器软件就自动完成HTML文件到网页的转换。

    6d9d126cf8f2dc9c093f252ee80ef467.gif

    2

    回答

    2021-01-30

    浏览:53

    分类:网页设计

    回答:

    html文件可以用手机浏览器打开。

    从本质上来说,Internet( 互联网)是一个由一系列传输协议和各类文档所组成的集合,html文件只是其中的一种。这些HTML文件存储在分布于世界各地的服务器硬盘上,通过传输协议用户可以远程获取这些文件所传达的资讯和信息。

    网络浏览器,能够解释HTML文件来显示网页,这是网络浏览器的主要作用。当你使用浏览器在互联网上浏览网页时,浏览器软件就自动完成HTML文件到网页的转换。

    e98e4d26d4341080e5281db92b7ea913.gif

    2

    回答

    2021-05-06

    浏览:9

    分类:其他问题

    回答:1、以小米手机为例,首先在手机上利用QQ接收一个HTML文件。

    2、然后在手机QQ中点击该HTML文件,选择用其他应用打开。

    3、然后在其他应用中选择浏览器,点击下方的仅此一次。

    4、打开完成后的效果如图所示,HTML文件成功被在浏览器中打开HTML文件是可以被多种网页浏览器读取,传递各类资讯的文件。从本质上来说,Internet( 互联网)是一个由一系列传输协议和各类文档所组成的集合,HTML文件只是其中的一种。这些HTML文件存储在分布于世界各地的服务器硬盘上,通过传输协议用户可以远程获取这些文件所传达的资讯和信息。 网络浏览器,例如Netscape Navigator或Microsoft Internet explorer,能够解释HTML文件来显示网页,这是网络浏览器的主要作用。当你使用浏览器在互联网上浏览网页时,浏览器软件就自动完成HTML文件到网页的转换。 HTML文件是被网络浏览器读取,产生网页的文件。从本质上来说,环球网只是一个由HTML文件及一系列传输协议所组成的集合。这些HTML文件存储在分布于世界各地的计算机的硬盘上,而传输协议能把这些文件从一台计算机传输到另一台计算机。 网络浏览器,例如Netscape Navigator或Microsoft Internet explorer,能够解释HTML文件来显示网页,这是网络浏览器的主要作用。当你使用浏览器在环球网上浏览网页时,浏览器软件就自动完成HTML文件到网页的转换

    2

    回答

    2021-05-06

    浏览:4

    分类:其他问题

    回答:如果是AndroID手机现在就比较简单---本人刚回答过一个。。。

    目前个人手机只发现:

    AndroID手机、4.0版系统、UC浏览器V9.9.6.495,还是比较方便的预览HTML原型文件;

    首先把HTML文件通过PC等手段传输到手机存储卡中(最好建个新文件夹放进去方便记忆查找)

    1、打开UC浏览器

    2、打开隐藏的menu菜单(设置/皮肤什么的...菜单)

    3、点击菜单中的“下载/应用”

    4、页头会看到有“下载管理”“手机文件”“热门资源”

    5、点击“手机文件”

    (看到“安装包”“压缩文件”等之类无视它们,UC提供此功能有存储卡文件管理的赶脚)

    6、去看此处页面底部有个“存储卡”“返回”文字;

    7、点击“存储卡”;

    8、去翻找下你复制传输的HTML文件在手机存储卡位置;

    9、找到文件位置后打开,直接点击打开HTML文件,就发现一切都美好了... ...

    1

    回答

    2021-05-06

    浏览:5

    分类:其他问题

    回答:你想问的是如何在使用HTML5的移动网站吧。

    你只需要做一个移动网站,上传到空间,然后绑定域名空间就可以了。如果你有PC网站的话,需要进行一下适配。

    2

    回答

    2020-12-21

    浏览:4

    分类:办公入门

    回答:

    以华为mate20手机为例:

    1、首先点击手机的文件管理。

    2a3d5436e2b7520963652c5799495c13.gif

    2、点击分类下的文档。

    5269197fe3210d9b9b222824b627b083.gif

    3、以打开图上所示的Word文档为例,点击。

    509613c26a78b01ef8748cc03bf97424.gif

    4、在弹出的对话框中选择WPS,仅此一次。

    5a8f4c97e1018a300066b553121f88e2.gif

    5、可以看到Word文档被成功的打开了,点击左上角的编辑还可以编辑该Word文档。

    7ad14d4473c26206bebc12a89e8db9b6.gif

    2

    回答

    2021-05-06

    浏览:1

    分类:其他问题

    回答:

    1、以小米手机为例,首先在手机上利用QQ接收一个HTML文件。

    b282792b4a4a657bccab859d037f3d06.gif

    2、然后在手机QQ中点击该HTML文件,选择用其他应用打开。

    0b5e1b0b8324188efe2692a8e89f9855.gif

    3、然后在其他应用中选择浏览器,点击下方的仅此一次。

    41c7862d78a026ee2e8fd8b9ec1728ce.gif

    4、打开完成后的效果如图所示,HTML文件成功被在浏览器中打开。

    f900122f1e8e87ffcce5f30f9936a15c.gif

    2

    回答

    2021-01-30

    浏览:184

    分类:网页设计

    回答:

    1、以小米手机为例,首先在手机上利用QQ接收一个HTML文件。

    f7892882abc8263cda791aadbe4413fa.gif

    2、然后在手机QQ中点击该HTML文件,选择用其他应用打开。

    762962cdb2a479e1154b47d4a97d1447.gif

    3、然后在其他应用中选择浏览器,点击下方的仅此一次。

    603936cd0a5013611f870d2938e44b57.gif

    4、打开完成后的效果如图所示,HTML文件成功被在浏览器中打开。

    bf392daae189e6a723fea9b470fca36c.gif

    2

    回答

    2020-11-30

    浏览:8

    分类:办公入门

    回答:在手机上安装WPS Office 手机版,然后找到文档即可打开。

    WPS Office手机版是金山办公软件推出的、运行于Android平台上的全功能办公软件,支持查看、创建和编辑DOC/DOCX/WPS/XLS/XLSX/PPT/PPTX/TXT/PDF等23种常用Office文档,方便用户在手机和平板上使用,满足用户随时随地的办公需求,让用户的Office文档操作尽在掌控。

    软件特色:

    1.文档编辑掌上进行,随时随地享受办公乐趣。

    金山WPS Office移动版支持本地和在线存储文档的查看和编辑。编辑功能包括常用的文字编辑、格式处理、表格、图片对象等功能。

    2.强大的邮件"亲密"集成,轻松编辑发送附件。

    3.支持多种文档格式,管理文档更方便金山WPS Office移动版完美支持多种文档格式。

    2

    回答

    2020-10-19

    浏览:7

    分类:办公入门

    回答:手机怎么看word文档

    手机上必须保证已经安装了【wPS】这个应用专,没有安装的请在百度应用中心搜属索下,并安装好。如图,我的手机已经安装了wPS,所以看word文档完全无压力。

    点击【wPS】应用图标,进入wPS的首页面,再点击【所有文档】选项即可。

    在你手机的文件夹中找到你要查看的word文档,我随机选择了一个word,然后点击该文档打开即可。

    如图,该word文档被完美运行打开,查看非常的方便,还可以任意缩放大小。

    你也可以点击【wPS】顶端的功能选项,对该word文档的字体、段落、格式、排版等进行编辑、修改、调整,这一切就看你自己的工作学习需要了,不再详述。

    END

    2

    回答

    2021-05-06

    浏览:1

    分类:其他问题

    回答:目前个人手机只发现:

    AndroID手机、4.0版系统、UC浏览器V9.9.6.495,还是比较方便的预览HTML原型文件;

    首先把HTML文件通过PC等手段传输到手机存储卡中(最好建个新文件夹放进去方便记忆查找)

    1、打开UC浏览器

    2、打开隐藏的menu菜单(设置/皮肤什么的...菜单)

    3、点击菜单中的“下载/应用”

    4、页头会看到有“下载管理”“手机文件”“热门资源”

    5、点击“手机文件”

    (看到“安装包”“压缩文件”等之类无视它们,UC提供此功能有存储卡文件管理的赶脚)

    6、去看此处页面底部有个“存储卡”“返回”文字;

    7、点击“存储卡”;

    8、去翻找下你复制传输的HTML文件在手机存储卡位置;

    9、找到文件位置后打开,直接点击打开HTML文件,就发现一切都美好了... ...

    2

    回答

    2021-03-18

    浏览:2

    分类:办公入门

    回答:手机怎么看Word文档

    手机上必须保证已经安装了【WPS】这个应用,没有安装的请在百度应用中心搜索下,并安装好。如图,我的手机已经安装了WPS,所以看Word文档完全无压力。

    点击【WPS】应用图标,进入WPS的首页面,再点击【所有文档】选项即可。

    在你手机的文件夹中找到你要查看的Word文档,我随机选择了一个Word,然后点击该文档打开即可。

    如图,该Word文档被完美运行打开,查看非常的方便,还可以任意缩放大小。

    你也可以点击【WPS】顶端的功能选项,对该Word文档的字体、段落、格式、排版等进行编辑、修改、调整,这一切就看你自己的工作学习需要了,不再详述。

    END

    2

    回答

    2021-01-30

    浏览:71

    分类:网页设计

    回答:HTML文件在手机上可以用浏览器打开,比如QQ浏览器,UC浏览器等,浏览器是专门用于上网的解析HTML文件的,无论是手机或电脑,都用浏览器打开。

    1

    回答

    2021-05-06

    浏览:4

    分类:其他问题

    回答:如果你是要在手机上查看HTML源码,就需要下载个应用才可以;

    如果只是查看HTML的效果,可以直接用手机自带的浏览器查看。

    2

    回答

    2021-05-13

    浏览:0

    分类:办公入门

    回答:

    方法:

    1、手机可下载APP办公软件,如:“WPS Office 手机版”。

    f31f6f9bf1b8928b234dae282e3c76a6.gif

    2、根据不同型号手机,下载对应APP软件后,安装后即可打开Word及excel等文档。

    2

    回答

    2021-03-11

    浏览:6

    分类:其他问题

    回答:以安卓手机为例,安装WPS Office软件即可打开。

    手机上下载安装WPS Office软件。

    安装完毕后,找到doc文件就可以直接打开了。

    WPS Office AndroID版是全球领先的AndroID办公软件,适用于智能手机、平板电脑、智能电视、智能投影仪等多种设备,随时随地移动办公。包含文字(doc/docx)、表格(xls/xlsx)、演示(PPT/PPTx)、PDF四大组件,完美支持多达24种Office文档格式的查看及编辑。

    WPS Office还推出了iOS用于iPhone、ipad等移动设备使用。

    2

    回答

    2021-02-26

    浏览:1

    分类:办公入门

    回答:不知道啊

    1

    回答

    2021-05-06

    浏览:11

    分类:其他问题

    回答:

    使用浏览器。

    首先,需要打开.HTML文档需要使用浏览器打开才行。

    解决方法:将文件拖到系统浏览器中进行打开。

    下载手机百度,把文件放到手机百度中打开。

    下载其它浏览器进行打开。

    Iphone 6s可以使用默认的浏览器直接打开。

    注意:单个页面需要把CSS样式写在同一个HTML文件中,否则无法正常显示。

    HTML格式文件是网页的文件。那么打开方式不言而喻,用浏览器就可以。在苹果手机打开本地HTML文件方法有如下三种。

    将HTML放在一个云同步服务中,比如坚果云、dropbox、百度云盘,然后使用safari登录云服务,直接打开HTML即可。

    不越狱,通过GoodReader打开的,HMLT+CSS功能能够实现,但是JS有的功能可以实现,有的不可以。比如菜单移入移出和表格隔行换色,我在工具里面都能访问成功。

    越狱装插件,为使 iPhone Safari 能够使用本地文件 ,需要安装一个插件 :file://Schema in Safari ,这个插件可在 Cydia 上下载安装。

    点击安装浏览器或者选中文件点击更多打开方式;

    选择6s上面的默认浏览器进行打开;

    安装浏览器,如360浏览器,百度浏览器,点击等多发送至百度、360浏览器进行打开浏览。

    0

    回答

    2021-06-09

    浏览:1

    分类:其他问题

    1

    回答

    2020-12-15

    浏览:11

    分类:办公入门

    回答:

    手机怎么看Word文档手机上必须保证已经安装了【WPS】这个应用,没有安装的请在百度应用中心搜索下,并安装好。如图,我的手机已经安装了WPS,所以看Word文档完全无压力。

    点击【WPS】应用图标,进入WPS的首页面,再点击【所有文档】选项即可。

    947d1e319f9a4c0243bcd4dba0d3d95c.gif

    在你手机的文件夹中找到你要查看的Word文档,我随机选择了一个Word,然后点击该文档打开即可。

    f853da58d72f712e88f178c40292a010.gif

    如图,该Word文档被完美运行打开,查看非常的方便,还可以任意缩放大小。

    fff53d876bfd30e7aab5156d56bbf561.gif

    你也可以点击【WPS】顶端的功能选项,对该Word文档的字体、段落、格式、排版等进行编辑、修改、调整,这一切就看你自己的工作学习需要了,不再详述。

    2287233cd26aa01bb45e6ba8be411713.gif

    展开全文
  • golang抓取网站(http://www.bixinshui.com)的全国各省市手机号码段,并分类保存到本地文本文件中,刚接触golang不久,练手用的,没考虑到性能问题,不过能正常运行,liteide和goland下编译通过。
  • H5手机页面,列表页+内容页
  • 基于Tensorflow2 LiteAndroid手机上实现图像分类

    千次阅读 多人点赞 2020-07-22 14:35:47
    Tensorflow2之后,训练保存的模型也有所变化,基于Keras接口搭建的网络模型默认保存的模型是h5格式的,而之前...本教程就是介绍如何使用Tensorflow2的Keras接口训练分类模型并使用Tensorflow Lite部署到Android设备

    原文博客:Doi技术团队
    链接地址:https://blog.doiduoyi.com/authors/1584446358138
    初心:记录优秀的Doi技术团队学习经历
    本文链接:基于Tensorflow2 Lite在Android手机上实现图像分类

    前言

    Tensorflow2之后,训练保存的模型也有所变化,基于Keras接口搭建的网络模型默认保存的模型是h5格式的,而之前的模型格式是pb。Tensorflow2的h5格式的模型转换成tflite格式模型非常方便。本教程就是介绍如何使用Tensorflow2的Keras接口训练分类模型并使用Tensorflow Lite部署到Android设备上。

    本教程源码:https://github.com/yeyupiaoling/ClassificationForAndroid/tree/master/TFLiteClassification

    训练和转换模型

    以下是使用Tensorflow2的keras搭建的一个MobileNetV2模型并训练自定义数据集,本教程主要是介绍如何在Android设备上使用Tensorflow Lite部署分类模型,所以关于训练模型只是简单介绍,代码并不完整。通过下面的训练模型,我们最终会得到一个mobilenet_v2.h5模型。

    import os
    import tensorflow as tf
    import reader
    import config as cfg
    
    # 获取模型
    input_shape = (cfg.IMAGE_SIZE, cfg.IMAGE_SIZE, cfg.IMAGE_CHANNEL)
    model = tf.keras.Sequential(
        [tf.keras.applications.MobileNetV2(input_shape=input_shape, include_top=False, pooling='max'),
         tf.keras.layers.Dense(units=cfg.CLASS_DIM, activation='softmax')])
    model.summary()
    
    # 获取训练数据
    train_data = reader.train_reader(data_list_path=cfg.TRAIN_LIST_PATH, batch_size=cfg.BATCH_SIZE)
    
    # 定义训练参数
    model.compile(optimizer=tf.keras.optimizers.RMSprop(),
                  loss=tf.keras.losses.SparseCategoricalCrossentropy(),
                  metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])
    
    # 开始训练
    model.fit(train_data, epochs=cfg.EPOCH_SUM, workers=4)
    
    # 保存h5模型
    if not os.path.exists(os.path.dirname(cfg.H5_MODEL_PATH)):
        os.makedirs(os.path.dirname(cfg.H5_MODEL_PATH))
    model.save(filepath=cfg.H5_MODEL_PATH)
    print('saved h5 model!')
    

    通过上面得到的mobilenet_v2.h5模型,我们需要转换为tflite格式的模型,在Tensorflow2之后,这个转换就变动很简单了,通过下面的几行代码即可完成转换,最终我们会得到一个mobilenet_v2.tflite模型。

    import tensorflow as tf
    import config as cfg
    
    # 加载模型
    model = tf.keras.models.load_model(cfg.H5_MODEL_PATH)
    
    # 生成非量化的tflite模型
    converter = tf.lite.TFLiteConverter.from_keras_model(model)
    tflite_model = converter.convert()
    open(cfg.TFLITE_MODEL_FILE, 'wb').write(tflite_model)
    print('saved tflite model!')
    

    如果保存的模型格式不是h5,而是tf格式的,如下代码,保存的模型是tf格式的。

    import tensorflow as tf
    
    model = tf.keras.applications.MobileNetV2(input_shape=(224, 224, 3))
    
    model.save(filepath='mobilenet_v2', save_format='tf')
    

    如果是tf格式的模型,那需要使用以下转换模型的方式。

    import tensorflow as tf
    
    converter = tf.lite.TFLiteConverter.from_saved_model('mobilenet_v2')
    tflite_model = converter.convert()
    open("mobilenet_v2.tflite", "wb").write(tflite_model)
    

    在部署到Android中可能需要到输入输出层的名称,通过下面代码可以获取到输入输出层的名称和shape。

    import tensorflow as tf
    
    model_path = 'models/mobilenet_v2.tflite'
    
    interpreter = tf.lite.Interpreter(model_path=model_path)
    interpreter.allocate_tensors()
    
    # 获取输入和输出张量。
    input_details = interpreter.get_input_details()
    output_details = interpreter.get_output_details()
    
    print(input_details)
    print(output_details)
    

    部署到Android设备

    首先要在build.gradle导入这三个库,如果不使用GPU可以只导入两个库。

    implementation 'org.tensorflow:tensorflow-lite:2.3.0'
    implementation 'org.tensorflow:tensorflow-lite-gpu:2.3.0'
    implementation 'org.tensorflow:tensorflow-lite-support:0.1.0-rc1'
    

    在以前还需要在android下添加以下代码,避免在打包apk的是对模型有压缩操作,损坏模型。现在好像不加也没有关系,但是为了安全起见,还是添加上去。

        aaptOptions {
            noCompress "tflite"
        }
    

    复制转换的预测模型到app/src/main/assets目录下,还有类别的标签,每一行对应一个标签名称。

    Tensorflow Lite工具

    编写一个TFLiteClassificationUtil工具类,关于Tensorflow Lite的操作都在这里完成,如加载模型、预测。在构造方法中,通过参数传递的模型路径加载模型,在加载模型的时候配置预测信息,例如是否使用Android底层神经网络APINnApiDelegate或者是否使用GPUGpuDelegate,同时获取网络的输入输出层。有了tensorflow-lite-support库,数据预处理就变得非常简单,通过ImageProcessor创建一个数据预处理的工具,之后在预测之前使用这个工具对图像进行预处理,处理速度还是挺快的,要注意的是图像的均值IMAGE_MEAN和标准差IMAGE_STD,因为在训练的时候图像预处理可能不一样的,有些读者出现在电脑上准确率很高,但在手机上准确率很低,多数情况下就是这个图像预处理做得不对。

    private static final float[] IMAGE_MEAN = new float[]{128.0f, 128.0f, 128.0f};
    private static final float[] IMAGE_STD = new float[]{128.0f, 128.0f, 128.0f};
    
    public TFLiteClassificationUtil(String modelPath) throws Exception {
        File file = new File(modelPath);
        if (!file.exists()) {
            throw new Exception("model file is not exists!");
        }
    
        try {
            Interpreter.Options options = new Interpreter.Options();
            // 使用多线程预测
            options.setNumThreads(NUM_THREADS);
            // 使用Android自带的API或者GPU加速
            NnApiDelegate delegate = new NnApiDelegate();
    //            GpuDelegate delegate = new GpuDelegate();
            options.addDelegate(delegate);
            tflite = new Interpreter(file, options);
            // 获取输入,shape为{1, height, width, 3}
            int[] imageShape = tflite.getInputTensor(tflite.getInputIndex("input_1")).shape();
            DataType imageDataType = tflite.getInputTensor(tflite.getInputIndex("input_1")).dataType();
            inputImageBuffer = new TensorImage(imageDataType);
            // 获取输入,shape为{1, NUM_CLASSES}
            int[] probabilityShape = tflite.getOutputTensor(tflite.getOutputIndex("Identity")).shape();
            DataType probabilityDataType = tflite.getOutputTensor(tflite.getOutputIndex("Identity")).dataType();
            outputProbabilityBuffer = TensorBuffer.createFixedSize(probabilityShape, probabilityDataType);
    
            // 添加图像预处理方式
            imageProcessor = new ImageProcessor.Builder()
                    .add(new ResizeOp(imageShape[1], imageShape[2], ResizeOp.ResizeMethod.NEAREST_NEIGHBOR))
                    .add(new NormalizeOp(IMAGE_MEAN, IMAGE_STD))
                    .build();
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("load model fail!");
        }
    }
    

    为了兼容图片路径和Bitmap格式的图片预测,这里创建了两个重载方法,它们都是通过调用predict()

    public int predictImage(String image_path) throws Exception {
        if (!new File(image_path).exists()) {
            throw new Exception("image file is not exists!");
        }
        FileInputStream fis = new FileInputStream(image_path);
        Bitmap bitmap = BitmapFactory.decodeStream(fis);
        int result = predictImage(bitmap);
        if (bitmap.isRecycled()) {
            bitmap.recycle();
        }
        return result;
    }
    
    public int predictImage(Bitmap bitmap) throws Exception {
        return predict(bitmap);
    }
    

    这里创建一个获取最大概率值,并把下标返回的方法,其实就是获取概率最大的预测标签。

    public static int getMaxResult(float[] result) {
        float probability = 0;
        int r = 0;
        for (int i = 0; i < result.length; i++) {
            if (probability < result[i]) {
                probability = result[i];
                r = i;
            }
        }
        return r;
    }
    

    这个方法就是Tensorflow Lite执行预测的最后一步,通过执行tflite.run()对输入的数据进行预测并得到预测结果,通过解析获取到最大的概率的预测标签,并返回。到这里Tensorflow Lite的工具就完成了。

    private int predict(Bitmap bmp) throws Exception {
        inputImageBuffer = loadImage(bmp);
    
        try {
            tflite.run(inputImageBuffer.getBuffer(), outputProbabilityBuffer.getBuffer().rewind());
        } catch (Exception e) {
            throw new Exception("predict image fail! log:" + e);
        }
    
        float[] results = outputProbabilityBuffer.getFloatArray();
        Log.d(TAG, Arrays.toString(results));
        return getMaxResult(results);
    }
    

    选择图片预测

    本教程会有两个页面,一个是选择图片进行预测的页面,另一个是使用相机实时预测并显示预测结果。以下为activity_main.xml的代码,通过按钮选择图片,并在该页面显示图片和预测结果。

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout 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:orientation="vertical"
        tools:context=".MainActivity">
    
        <ImageView
            android:id="@+id/image_view"
            android:layout_width="match_parent"
            android:layout_height="400dp" />
    
        <TextView
            android:id="@+id/result_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/image_view"
            android:text="识别结果"
            android:textSize="16sp" />
    
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:orientation="horizontal">
    
            <Button
                android:id="@+id/select_img_btn"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="选择照片" />
    
    
            <Button
                android:id="@+id/open_camera"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="实时预测" />
    
        </LinearLayout>
    
    </RelativeLayout>
    

    MainActivity.java中,进入到页面我们就要先加载模型,我们是把模型放在Android项目的assets目录的,但是Tensorflow Lite并不建议直接在assets读取模型,所以我们需要把模型复制到一个缓存目录,然后再从缓存目录加载模型,同时还有读取标签名,标签名称按照训练的label顺序存放在assets的label_list.txt,以下为实现代码。

    classNames = Utils.ReadListFromFile(getAssets(), "label_list.txt");
    String classificationModelPath = getCacheDir().getAbsolutePath() + File.separator + "mobilenet_v2.tflite";
    Utils.copyFileFromAsset(MainActivity.this, "mobilenet_v2.tflite", classificationModelPath);
    try {
        tfLiteClassificationUtil = new TFLiteClassificationUtil(classificationModelPath);
        Toast.makeText(MainActivity.this, "模型加载成功!", Toast.LENGTH_SHORT).show();
    } catch (Exception e) {
        Toast.makeText(MainActivity.this, "模型加载失败!", Toast.LENGTH_SHORT).show();
        e.printStackTrace();
        finish();
    }
    

    添加两个按钮点击事件,可以选择打开相册读取图片进行预测,或者打开另一个Activity进行调用摄像头实时识别。

    Button selectImgBtn = findViewById(R.id.select_img_btn);
    Button openCamera = findViewById(R.id.open_camera);
    imageView = findViewById(R.id.image_view);
    textView = findViewById(R.id.result_text);
    selectImgBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 打开相册
            Intent intent = new Intent(Intent.ACTION_PICK);
            intent.setType("image/*");
            startActivityForResult(intent, 1);
        }
    });
    openCamera.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 打开实时拍摄识别页面
            Intent intent = new Intent(MainActivity.this, CameraActivity.class);
            startActivity(intent);
        }
    });
    

    当打开相册选择照片之后,回到原来的页面,在下面这个回调方法中获取选择图片的Uri,通过Uri可以获取到图片的绝对路径。如果Android8以上的设备获取不到图片,需要在AndroidManifest.xml配置文件中的application添加android:requestLegacyExternalStorage="true"。拿到图片路径之后,调用TFLiteClassificationUtil类中的predictImage()方法预测并获取预测值,在页面上显示预测的标签、对应标签的名称、概率值和预测时间。

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        String image_path;
        if (resultCode == Activity.RESULT_OK) {
            if (requestCode == 1) {
                if (data == null) {
                    Log.w("onActivityResult", "user photo data is null");
                    return;
                }
                Uri image_uri = data.getData();
                image_path = getPathFromURI(MainActivity.this, image_uri);
                try {
                    // 预测图像
                    FileInputStream fis = new FileInputStream(image_path);
                    imageView.setImageBitmap(BitmapFactory.decodeStream(fis));
                    long start = System.currentTimeMillis();
                    float[] result = tfLiteClassificationUtil.predictImage(image_path);
                    long end = System.currentTimeMillis();
                    String show_text = "预测结果标签:" + (int) result[0] +
                            "\n名称:" +  classNames[(int) result[0]] +
                            "\n概率:" + result[1] +
                            "\n时间:" + (end - start) + "ms";
                    textView.setText(show_text);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    上面获取的Uri可以通过下面这个方法把Url转换成绝对路径。

    // get photo from Uri
    public static String getPathFromURI(Context context, Uri uri) {
        String result;
        Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
        if (cursor == null) {
            result = uri.getPath();
        } else {
            cursor.moveToFirst();
            int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
            result = cursor.getString(idx);
            cursor.close();
        }
        return result;
    }
    

    摄像头实时预测

    在调用相机实时预测我就不再介绍了,原理都差不多,具体可以查看https://github.com/yeyupiaoling/ClassificationForAndroid/tree/master/TFLiteClassification中的源代码。核心代码如下,创建一个子线程,子线程中不断从摄像头预览的AutoFitTextureView上获取图像,并执行预测,并在页面上显示预测的标签、对应标签的名称、概率值和预测时间。每一次预测完成之后都立即获取图片继续预测,只要预测速度够快,就可以看成实时预测。

    private Runnable periodicClassify =
            new Runnable() {
                @Override
                public void run() {
                    synchronized (lock) {
                        if (runClassifier) {
                            // 开始预测前要判断相机是否已经准备好
                            if (getApplicationContext() != null && mCameraDevice != null && tfLiteClassificationUtil != null) {
                                predict();
                            }
                        }
                    }
                    if (mInferThread != null && mInferHandler != null && mCaptureHandler != null && mCaptureThread != null) {
                        mInferHandler.post(periodicClassify);
                    }
                }
            };
    
    // 预测相机捕获的图像
    private void predict() {
        // 获取相机捕获的图像
        Bitmap bitmap = mTextureView.getBitmap();
        try {
            // 预测图像
            long start = System.currentTimeMillis();
            float[] result = tfLiteClassificationUtil.predictImage(bitmap);
            long end = System.currentTimeMillis();
            String show_text = "预测结果标签:" + (int) result[0] +
                    "\n名称:" +  classNames[(int) result[0]] +
                    "\n概率:" + result[1] +
                    "\n时间:" + (end - start) + "ms";
            textView.setText(show_text);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

    本项目中使用的了读取图片的权限和打开相机的权限,所以不要忘记在AndroidManifest.xml添加以下权限申请。

    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    

    如果是Android 6 以上的设备还要动态申请权限。

        // check had permission
        private boolean hasPermission() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                return checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&
                        checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
                        checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
            } else {
                return true;
            }
        }
    
        // request permission
        private void requestPermission() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                requestPermissions(new String[]{Manifest.permission.CAMERA,
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
            }
        }
    

    选择图片识别效果图:
    在这里插入图片描述

    相机实时识别效果图:
    在这里插入图片描述

    展开全文
  • jQuery京东手机端商品分类滑动切换是一款仿京东移动web端商品分类导航滑动效果展示。
  • 生活分类信息网站电脑加手机版小程序安装说明生活分类信息网站电脑加手机版小程序,是以ASP+ACC进行开发的分类信息源码,PC,手机,微信,小程序。后台登陆地址/admin456 账号 admin 密码admin功能说明:1、电脑手机版...
  • 首页无需懂得代码直接填入要显示的分类就可以展示你要展示的分类。内容详细介绍支持图文和HTML排版,网站地址加NOF属性,防止权重导出,不影响本站权重!TAG标签属性已全站做好拼音伪静态。内容页面增加30天访问量...
  • 怎么在手机上打开HTML文件怎么打开

    千次阅读 2021-06-09 16:02:26
    2 回答2021-05-06 浏览:4 分类:其他问题回答:HTML文件可以用手机浏览器打开。从本质来说,Internet( 互联网)是一个由一系列传输协议和各类文档所组成的集合,HTML文件只是其中的一种。这些HTML文件存储分布于...
  • 内含23个html页面加js ,可直接运行,首页轮播图 分类商品展示,预约-预约记录,商品详情,购物车,会员中心、消费记录等
  • 纯css仿写的小米官网手机
  • 支持置顶功能7、图片广告管理功能生活分类信息港电脑加手机加微信2019新版4.0更新1、增加手机版底部浮动导航菜单2、优化手机版信息发布页面,搜索页面3、PC首页添加置顶分类,修复通栏广告首页显示4、优化PC信息...
  • 手机外卖app页面模板

    2020-10-18 11:05:23
    通用的生鲜水果食品外卖商城app前端手机页面模板html全套下载。主要有:订单详情,结算,购物车,分类,商品详情,个人中心,搜索等总共23个页面。
  • jquery手机触屏版HTML5页面左侧弹出导航菜单代码
  • 15个手机页面模板源码,html5,下载后导入工程即可
  • 仿京东商城系列7------商品分类页面

    千次阅读 2021-06-11 07:53:28
    废话不说,图:商品分类.gif内容商品分类展示页面涉及到的技术有Okhttp , Fresco加载机制,MaterialRefreshLayout + recyclerview下拉加载控件。关于上述控件的说明之前就已经有所说明。此不做介绍。关于页面...
  • Android手机上使用腾讯的ncnn实现图像分类

    万次阅读 热门讨论 2018-09-05 19:40:31
    本章中,笔者将会介绍使用腾讯的开源手机深度学习框架ncnn来实现Android手机实现图像分类,这个框架开源时间比较长,相对稳定很多。 ncnn的GitHub地址:https://github.com/Tencent/ncnn 使用Ubuntu编译ncnn库 1...
  • 店铺商家信息分类手机版网站模板 抖音短视频分享源码下载,视频手机网站模板 红色的今日头条移动端手机网网站模板 红色的每日一点app新闻资讯手机模板 红色的汽车代理商售后服务app手机模板 货车帮物流平台...
  • 手机页面通用样式

    千次阅读 2018-07-20 16:53:16
    &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;meta charset="UTF-8"&gt; &lt;meta name="viewport" content="width=device-width, ...s

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 62,732
精华内容 25,092
关键字:

怎么在手机页面上分类