昆虫识别

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

  • 安卓系统相机和相册的使用
  • 百度开放平台识别接口的使用
  • okhttp
  • 使用handler异步上传图片
  • 使用bundle线程间传递数据
  • 识别结果JSON的解析
  • dialog的使用
    由于几个比较简单就不详述,重点讲下相机,百度,handler,bundle

通过相机和相册获取照片

权限注册:

<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;

        }
    }
  • 先说相机回调:通过ContentValues拿到上文的uri,在变变变成bitmap,传入 identify(bitmap)方法中去识别,在下文百度ai中详细讲这个方法。
  • 再谈相册回调:都说是百度的了,前面都没看懂后面怎么可能会,先欠着,大致是一顿操作过后拿到bitmap传入identify()中去,和相机的回调收尾一样。

下面详细讲解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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,401评论 0 17
  • (1)闹钟 创建闹钟(ACTION_SET_ALARM)示例Intent: 注:为了调用ACTION_SET_AL...
    sunnygarden阅读 1,624评论 0 10
  • //gradle 下载慢 //可以直接下载gradle之后放在对应的目录里//或者修改 根目录下的文件bul...
    zeromemcpy阅读 884评论 0 0
  • 最近用到从系统图库和相机获取图片并裁剪当头像,根据郭霖大神的第一行代码调用相机和图册,来进行扩展和总结。 1、获取...
    axiaochao阅读 2,564评论 0 5
  • 爱情需得小心翼翼,有时候我们以为的一个不经意的小玩笑可能就是别人心里的一道疤。
    梦里何思归阅读 138评论 0 0