当前位置:   article > 正文

Android拍摄并进行图像识别(二)_android开发幼儿识图代码

android开发幼儿识图代码

一、简介

​ 最近在学习实现Android的拍照AI识别功能,主要通过调用手机系统的摄像机来完成拍照,并通过网络服务访问百度智慧云上的图像识别api来进行识图,最后将识图结果返回到app中。我把这个功能分成两个部分,分别是拍摄和识别,这篇文章讲的是图像识别部分,想看拍摄部分分析请点击:Android拍摄并进行图像识别(一)_Super500000的博客-CSDN博客

二、程序流程图

在这里插入图片描述

三、核心代码分析

1.获取鉴权Token
private String getAccessToken() {
    String token = SPUtils.getString(Constant.TOKEN, null, this);
    if (token == null) {
        //访问API获取接口
        Log.d("Camera","执行至getAccessToken()");
        requestApiGetToken();
    } else {
        //则判断Token是否过期
        if (isTokenExpired()) {
            //过期,再获取一次
            requestApiGetToken();
        } else {
            accessToken = token;
        }
    }
    return accessToken;
}

private void requestApiGetToken() {
        String grantType = "client_credentials";
        String apiKey = "A63XnnuZfncKbBpMpnnpL54P";
        String apiSecret = "v2OmvaINWEw9tETlawLN62zYrYgjvCe";//应用匹配
        service.getToken(grantType, apiKey, apiSecret)
                .enqueue(new NetCallBack<GetTokenResponse>() {
                    @Override
                    public void onSuccess(Call<GetTokenResponse> call, Response<GetTokenResponse> response) {
                        if (response.body() != null) {
                            //鉴权Token
                            accessToken = response.body().getAccess_token();
                            //过期时间 秒
                            long expiresIn = response.body().getExpires_in();
                            //当前时间 秒
                            long currentTimeMillis = System.currentTimeMillis() / 1000;

                            //放入缓存
                            SPUtils.putString(Constant.TOKEN, accessToken, MainActivity.this);
                            SPUtils.putLong(Constant.GET_TOKEN_TIME, currentTimeMillis, MainActivity.this);
                            SPUtils.putLong(Constant.TOKEN_VALID_PERIOD, expiresIn, MainActivity.this);
                        }
                    }

                    @Override
                    public void onFailed(String errorStr) {
                        Log.d("Camera", "获取Token失败,失败原因:" + errorStr);
                        accessToken = null;
                    }
                });
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

这里的apiKey和apiSecret是从百度智慧云的应用列表中负责的,每创建一个应用都会有一个独一无二的apiKey和apiSecret
在这里插入图片描述

2.获取返回的图片路径
private void showMsg(String msg) {
    Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    Log.d("Camera","3-----");
    if (requestCode == TAKE_PHOTO_CODE) {
        //获取返回的数据,也就是照片路径
        String imagePath = data.getStringExtra("photo");
        //识别
        localImageDiscern(imagePath);
    }
    else {
        showMsg("什么都没有");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
3.按字节读取文件
//按字节读取文件
byte[] imgData = FileUtil.readFileByBytes(imagePath);
  • 1
  • 2
/**
 * 根据文件路径读取byte[] 数组
 */
public static byte[] readFileByBytes(String filePath) throws IOException {
    File file = new File(filePath);
    if (!file.exists()) {
        throw new FileNotFoundException(filePath);
    } else {
        //ByteArrayOutputStream字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。
        ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length());
        BufferedInputStream in = null;

        try {
            in = new BufferedInputStream(new FileInputStream(file));
            short bufSize = 1024;
            byte[] buffer = new byte[bufSize];      //存储每次读取的数据
            int len1;           //记录每次读取的有效字节个数
            //声明一个byte数组作为buffer,然后循环将文本内容循环读入到buffer中,
            //从文件中按字节读取内容,到文件尾部时read方法将返回-1
            while (-1 != (len1 = in.read(buffer, 0, bufSize))) {    
                //buffer代表目标缓冲区,0表示开始存储字节的偏移量,bufSize表示要读取的最大字节数
                bos.write(buffer, 0, len1);     //从buffer写到bos里面去
            }

            byte[] var7 = bos.toByteArray();//将文件的内容转换成byte数祖
            return var7;
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException var14) {
                var14.printStackTrace();
            }

            bos.close();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
4.将照片转成Base64格式
//字节转Base64
String imageBase64 = Base64Util.encode(imgData);
  • 1
  • 2

Base64Util.java:

package com.example.speakandlisten.util;

/**
 * Base64 工具类
 */
public class Base64Util {
    private static final char last2byte = (char) Integer.parseInt("00000011", 2);
    private static final char last4byte = (char) Integer.parseInt("00001111", 2);
    private static final char last6byte = (char) Integer.parseInt("00111111", 2);
    private static final char lead6byte = (char) Integer.parseInt("11111100", 2);
    private static final char lead4byte = (char) Integer.parseInt("11110000", 2);
    private static final char lead2byte = (char) Integer.parseInt("11000000", 2);
    private static final char[] encodeTable = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

    public Base64Util() {
    }

    public static String encode(byte[] from) {
        StringBuilder to = new StringBuilder((int) ((double) from.length * 1.34D) + 3);//内容可变的字符串类
        int num = 0;
        char currentByte = 0;

        int i;
        for (i = 0; i < from.length; ++i) {
            for (num %= 8; num < 8; num += 6) {
                switch (num) {
                    case 0:
                        currentByte = (char) (from[i] & lead6byte);
                        currentByte = (char) (currentByte >>> 2);
                    case 1:
                    case 3:
                    case 5:
                    default:
                        break;
                    case 2:
                        currentByte = (char) (from[i] & last6byte);
                        break;
                    case 4:
                        currentByte = (char) (from[i] & last4byte);
                        currentByte = (char) (currentByte << 2);
                        if (i + 1 < from.length) {
                            currentByte = (char) (currentByte | (from[i + 1] & lead2byte) >>> 6);
                        }
                        break;
                    case 6:
                        currentByte = (char) (from[i] & last2byte);
                        currentByte = (char) (currentByte << 4);
                        if (i + 1 < from.length) {
                            currentByte = (char) (currentByte | (from[i + 1] & lead4byte) >>> 4);
                        }
                }

                to.append(encodeTable[currentByte]);
            }
        }

        if (to.length() % 4 != 0) {
            for (i = 4 - to.length() % 4; i > 0; --i) {
                to.append("=");
            }
        }

        return to.toString();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

这里讲一下Base64编码Base64 编码要求把 3 个 8 位字节(3 * 8=24 转化为 4 个 6 位的字节(4 * 6=24),之后在 6 位的前面补两个 0,形成 8 位一个字节的形式。 如果剩下的字符不足 3 个字节,则用 0 填充,输出字符使用 =,因此编码后输出的文本末尾可能会出现 1 或 2 个 =。

举一个简单的例子:
在这里插入图片描述

5.图像识别请求
/**
 * 图像识别请求
 *
 * @param token       token
 * @param imageBase64 图片Base64
 */
public void easydlObjectDetection(String token, String imageBase64) {
    try {
        Map<String, Object> map = new HashMap<>();
        map.put("image", imageBase64);
        map.put("top_num","1");
        String param = GsonUtils.toJson(map);   //将对象转换为json字符串
        // 注意这里仅为了简化编码每一次请求都去获取access_token,线上环境access_token有过期时间,客户端可自行缓存,过期后重新获取。
        mytoken = token;
        myparam = param;
        Log.d("myresult","进入识别");
        Log.d("myresult",mytoken);
        //创建线程
        Thread accessWebServiceThread = new Thread(new WebServiceHandler());
        accessWebServiceThread.start();
    } catch (Exception e) {
        e.printStackTrace();    //在命令行打印异常信息在程序中出错的位置及原因。
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
6.访问百度智慧云api接口
//访问百度智能云的api接口进行图像识别
class WebServiceHandler implements Runnable {
    @Override
    public synchronized void run(){
        try {
            //lock1是ReentrantLock是一个可重入的互斥锁,在同一个时间点只能被一个线程锁持有,可重入表示,
            //ReentrantLock锁可以被同一个线程多次获取,通过一个FIFO的等待队列来管理获取该锁的所有线程
            lock1.lock();
            String jsonString = HttpUtil.post("https://aip.baidubce.com/rpc/2.0/ai_custom/v1/detection/typhlosole", mytoken, "application/json", myparam);//contentType用于定义文件的类型和网页编码
            Log.d("myresult",jsonString);
            Gson gson = new Gson();
            
            
            //获取图像识别结果
            GetDiscernResultResponse getDiscernResultResponse = gson.fromJson(jsonString,GetDiscernResultResponse.class);//通过fromJson转变成单一实体对象
            List<GetDiscernResultResponse.ResultBean> results = getDiscernResultResponse.getResult();
            String name = results.get(0).getName();
            Log.d("myresult",name);
            speakText3("前方出现"+ name + "。请小心前行!");
            sleep(4000);
        } catch (Exception e) {
            try {
                speakText3("暂时未能识别到前方障碍物,请小心前行!");
                sleep(4000);
            } catch (InterruptedException r) {
                r.printStackTrace();
            }
        }finally {
            wancheng = false;
            lock1.unlock();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
/**
 * 获取识别结果响应实体
 */
public class GetDiscernResultResponse<T> {
    @Override
    public String toString() {
        return "GetDiscernResultResponse{" +
                "log_id=" + log_id +
                ", result=" + results +
                '}';
    }

    public long log_id;
    public List<ResultBean> results;
    public long getLog_id() {
        return log_id;
    }
    public void setLog_id(long log_id) {
        this.log_id = log_id;
    }
    public List<ResultBean> getResult() {
        return results;
    }

    public void setResult(List<ResultBean> results) {
        this.results = results;
    }
    public static class ResultBean {
        String name;//分类名称
        double score;//置信度

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public double getScore() {
            return score;
        }

        public void setScore(double score) {
            this.score = score;
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

图像识别方法:

private void ImageDiscern(String token, String imageBase64) {
    easydlObjectDetection(token,imageBase64);
  • 1
  • 2
7.将图像识别结果用列表显示
/**
 * 识别结果列表适配器
 */
public class DiscernResultAdapter extends BaseQuickAdapter<GetDiscernResultResponse.ResultBean, BaseViewHolder> {
    public DiscernResultAdapter(int layoutResId, @Nullable List<GetDiscernResultResponse.ResultBean> data) {
        //@Nullable 注解可以使用在方法、属性、参数上,分别表示方法返回可以为空、属性值可以为空、参数值可以为空。
        super(layoutResId, data.subList(0,1));  //subList()获取列表中指定范围的子列表
    }
    @Override
    protected void convert(BaseViewHolder helper, GetDiscernResultResponse.ResultBean item) {
        Log.d("Camera","调用的类"+getClass());
        helper.setText(R.id.tv_keyword,item.getName());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
private void showDiscernResult(List<GetDiscernResultResponse.ResultBean> result) {
    bottomSheetDialog.setContentView(bottomView);
    bottomSheetDialog.getWindow().findViewById(R.id.design_bottom_sheet).setBackgroundColor(Color.TRANSPARENT);
    RecyclerView rvResult = bottomView.findViewById(R.id.rv_result);
    DiscernResultAdapter adapter = new DiscernResultAdapter(R.layout.item_result_rv, result);
    rvResult.setLayoutManager(new LinearLayoutManager(this));
    rvResult.setAdapter(adapter);
    
    //显示弹窗
    bottomSheetDialog.show();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
8.本地图片识别主函数
private void localImageDiscern(String imagePath) {
    try {
        //获取鉴权Token
        String token = getAccessToken();
        //按字节读取文件
        byte[] imgData = FileUtil.readFileByBytes(imagePath);
        //字节转Base64
        String imageBase64 = Base64Util.encode(imgData);
        //图像识别
        ImageDiscern(token, imageBase64);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

四、总结

​ 图像识别这一部分还是比较复杂的,主要是图片转码,调用百度智慧云图像识别api的部分,要先根据apiKey和apiSecret获取Token,然后再和Base64的图片数据一起访问百度智慧云的图像识别,识别后获取识别结果也需要我们再写一个结果的JavaBean类来获取。逻辑还是比较复杂的,要根据功能来一步一步写代码。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/566852
推荐阅读
相关标签
  

闽ICP备14008679号