昆虫识别

2019-04-06  本文已影响0人  leap_

基本步骤:打开相机和相册获取一张照片,转为base64,异步上传到百度ai平台,拿到返回结果。
总结知识点:

通过相机和相册获取照片

权限注册:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  1. 弹出一个dialog询问是相机还是相册:
String items [] = {"相机","相册"};// 用于作为dialog的选项
                AlertDialog dialog = new AlertDialog.Builder(getContext())
                        .setTitle("请选择图片获取途径")
                        .setItems(items, new DialogInterface.OnClickListener() {
                            @Override //  dialog的点击事件 
                            public void onClick(DialogInterface dialogInterface, int i) {
                                switch (i){
                                    case 0:
                                        openCamera();
                                        break;
                                    case 1:
                                        openAlbum();
                                        break;
                                }
                            }
                        }).create();
                dialog.show();

2.详解openCamera()

public static final int PHOTO_REQUEST_CAMERA = 1;// 拍照
 private Intent intent_camera;
public static File tempFile;
 private void openCamera() {
        //獲取系統版本
        int currentapiVersion = android.os.Build.VERSION.SDK_INT;
        // 创建隐式调用相机的intent
         intent_camera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // 判断存储卡是否可以用,可用进行存储
        if (hasSdcard()) {
            // 取得当前时间命名文件
            SimpleDateFormat timeStampFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
            String filename = timeStampFormat.format(new Date());
            //  创建存放照片的文件
            tempFile = new File(Environment.getExternalStorageDirectory(), filename + ".jpg");
            if (currentapiVersion < 24) {
                // 从文件中创建uri
                imageUri = Uri.fromFile(tempFile);
                //将当前uri放到系统指定的位置,(系统会自动将照片放到这个位置的uri中)
                intent_camera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
                startActivityForResult(intent_camera, PHOTO_REQUEST_CAMERA);
            } else {
                //兼容android7.0 使用共享文件的形式
                contentValues = new ContentValues(1);
                contentValues.put(MediaStore.Images.Media.DATA, tempFile.getAbsolutePath());
                imageUri = getActivity().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
                //检查是否有存储权限,以免崩溃
                if ( ContextCompat.checkSelfPermission(getActivity(),  
                        Manifest.permission.WRITE_EXTERNAL_STORAGE )
                        != PackageManager.PERMISSION_GRANTED ||
                        ContextCompat.checkSelfPermission(getActivity(),
                        Manifest.permission.CAMERA)
                        !=PackageManager.PERMISSION_GRANTED ) {
                    //申请WRITE_EXTERNAL_STORAGE权限
                    ActivityCompat.requestPermissions(getActivity(),
                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.CAMERA},
                            REQUEST_PERMISSSION_CAMERA);
                }else {     // 有权限
                    intent_camera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
                    // 开启一个带有返回值的Activity,请求码为PHOTO_REQUEST_CMERA
                    startActivityForResult(intent, PHOTO_REQUEST_CAMERA);
                }

            }
        }
    }

首先创建一个用于启动系统相机的intent和一个用于存放照片的file,接着获取当前的手机系统版本,(由于安卓7.0前后uri的获取方式有了变化,7.0以后使用共享文件的形式获取uri),如果有物理内存,获取当前的时间用于命名相册file,相对路径构造file,如果是7.0以下版本,直接用Uri.fromFile(tempFile)获得uri,放到系统指定的位置(MediaStore.EXTRA_OUTPUT),系统会将照片复制到这个uri上(我也不知道是不是复制啊,反正就是在拍照前指定MediaStore.EXTRA_OUTPUT一个uri,拍照后这个uri就是照片),然后startActivityForResult(intent_camera, PHOTO_REQUEST_CAMERA);在回调中拿到uri(照片),7.0以上除了uri获取不一样其他一模一样,就是那三行,核心使用contentValues,思路和7.0以下一样。

// 判断是否含有存储空间
private boolean hasSdcard() {
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    }

3.详解openAlbum()

public static final int PHOTO_REQUEST_ALBUM = 0; // 相册
private Intent intent_album;
    //  打开相册
    private void openAlbum() {
         intent_album = new Intent(Intent.ACTION_PICK);
         intent_album.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*");
         startActivityForResult(intent_album,PHOTO_REQUEST_ALBUM);
    }

先创建一个隐式打开相册的intent,(其实这个是我百度过来的,没看懂,缺个解析,19.4.6)
4.处理回调

 // 处理活动回调
    Bitmap bitmap;
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode){
            case PHOTO_REQUEST_CAREMA :  // 处理相机的回调
                // 自定义handler接收子线程传回来的result
                myHandler = new MyHandler();
                if (resultCode == RESULT_OK){
                    // 处理照片
                    try {
                        // 照片的bitmap
                        bitmap = BitmapFactory.decodeStream(getActivity().getContentResolver().openInputStream(imageUri));
                        imageView.setImageBitmap(bitmap);
                        // 识别
                        identify(bitmap);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case PHOTO_REQUEST_ALBUM:  // 相册的回调
                myHandler = new MyHandler();
                try {
                    Uri selectedImage = data.getData();
                    String[] filePathColumns = {MediaStore.Images.Media.DATA};
                    Cursor c = getActivity().getContentResolver().query(selectedImage, filePathColumns, null, null, null);
                    c.moveToFirst();
                    int columnIndex = c.getColumnIndex(filePathColumns[0]);
                    String imagePath = c.getString(columnIndex);
                    Bitmap bitmap = BitmapFactory.decodeFile(imaePath);
                    imageView.setImageBitmap(bitmap);
                    identify(bitmap);
                    c.close();
                }catch (NullPointerException e){
                    Log.d("E",e.getMessage());
                }
                break;

        }
    }

下面详细讲解identify()

百度ai开放平台

我们先贴上identify()代码

// 识别
    String access_token ;
    String param;
    private void identify(Bitmap bitmap) {
        // 压缩到0.1 拿到base64编码,合成param
        ByteArrayOutputStream bStream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 10, bStream);
        byte[] bytes = bStream.toByteArray();
        String base64 = Base64Util.encode(bytes);
        try {
            String image_param = URLEncoder.encode(base64,"UTF-8");
             param = "image=" + image_param + "&top_num=" + 6;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        // 获得access token 24.1b561ba2365e2ab484e69e953f09a48c.2592000.1555152863.282335-15652740
        String request_access_token = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=aHWDOH5MERuoXkwUIOtbwU7j&client_secret=2QbMhquS9aRY2Gd4hpjlU8SXpIGfC3zm&";
        OkHttpClient client = new OkHttpClient.Builder().build();
        Request request = new Request.Builder()
                .get()
                .url(request_access_token)
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                ToastUtil.toast(getContext(),"access__token请求失败"+e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response)  {
                // 解析拿到acces_stoken
                String jsonString = null;
                try {
                    jsonString = response.body().string();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                Log.d("tag",jsonString);
                Gson gson = new Gson();
                JsonAccess jsonAccess  = gson.fromJson(jsonString ,JsonAccess.class);
                 access_token = jsonAccess.access_token;
            }
        });

        // 开启子线程上传base64
        new Thread(new Runnable() {
            String url = "https://aip.baidubce.com/rest/2.0/image-classify/v1/animal";
                @Override
                public void run() {
                    try {
                        String result = HttpUtil.post(url,"24.1b561ba2365e2ab484e69e953f09a48c.2592000.1555152863.282335-15652740",param);
                            // bundle用于传递线程数据
                        Bundle bundle = new Bundle();
                        bundle.putString("result",result);
                        // 通过message发送到主线程
                        Message message = new Message();
                        message.setData(bundle);
                        message.what = 1;
                        myHandler.sendMessage(message);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    }

使用的是百度ai的动物识别接口,官方文档地址:https://ai.baidu.com/docs#/ImageClassify-API/top
在看之前,可以先看一下我的观后感:

  1. 先注册账号,创建应用,拿到Api key和Secret key。
  2. 然后看文档

我们传进来的是bitmap,百度需要的base64编码,去掉编码头后再进行urlencode,所以第一步先将我们的bitmap转变为base64,先创建ByteArrayOutputStream流,将bitmap压缩到bStream流里(通过compress方法,参数1为压缩类型,参数2为压缩比例10代表原图片的0.1,参数3为bitmap压缩的目标流),将流转为byte[],通过百度官方提供的Base64转换类的encode方法将byte转为base64,base64转换工具下载地址:https://ai.baidu.com/file/C8D81F3301E24D2892968F09AE1AD6E2
将得到的base64指定为UTF-8,然和合成post请求的param,指定参数image和top_num,下面一段代码是按照官方提示获得accessToken,没什么好说的,(注意我在代码中拿到的accesstoken是在子线程中的,我在下文使用的时候直接传的值,没有传变量,如果传变量需要用到bundle和handler)我们现在所有的操作都是在主线程中,无法进行耗时操作,发送图片为耗时操作,所以我们开启一个线程去post我们的图片给百度,请求返回结果,在子线程中,我们准备好请求的uri地址,accessToken和存放图片的param,通过百度的请求工具获取result,请求工具地址:https://ai.baidu.com/file/544D677F5D4E4F17B4122FBD60DB82B3
现在我们已经在子线程中拿到识别结果了,下面需要将他们传到主线程,解析,展示。

handler与bundle线程间传递数据

先定义MyHandler类用于处理子线程发送来的message

 class MyHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 取出bundle得到结果
            Bundle bundle = msg.getData();
            String result = bundle.getString("result");
            String s = "";
            try {
                JSONObject jsonObject = new JSONObject(result);
                JSONArray jsonArray = jsonObject.getJSONArray("result");
                for (int i = 0; i <1;i++ ){
                    JSONObject obj = (JSONObject) jsonArray.get(i);
                    s = s+obj.getString("name")+"\n";
                }
                textView.setText(s);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            textView.setVisibility(View.VISIBLE);
            intent_show = new Intent(getContext(),ShowActivity.class);
            intent_show.putExtra("result",result);
          //  getContext().startActivity(intent_show);
        }

    }

子线程发送数据(上文identify()中的子线程)

 new Thread(new Runnable() {
            String url = "https://aip.baidubce.com/rest/2.0/image-classify/v1/animal";
                @Override
                public void run() {
                    try {
                        String result = HttpUtil.post(url,"24.1b561ba2365e2ab484e69e953f09a48c.2592000.1555152863.282335-15652740",param);
                            // bundle用于传递线程数据
                        Bundle bundle = new Bundle();
                        bundle.putString("result",result);
                        // 通过message发送到主线程
                        Message message = new Message();
                        message.setData(bundle);
                        message.what = 1;
                        myHandler.sendMessage(message);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();

在子线程拿到了返回的result,通过bundle和myHandler传递到主线程,bundle用于存放数据,handler用于发送数据(需要借助handler的message类),在自定义的MyHandler类中我们handleMessage,取出bundle,取出result,解析JSON,然后展示给用户,这些handleMessage的逻辑是写在类里面的,什么时候执行啊,在我们的MyHandler被实例话的时候会执行,仔细去看下上文的onActivityResult在处理相机和相册的两个回调中,我们是不是初始化了我们的myHandler。

上一篇 下一篇

猜你喜欢

热点阅读