赞
踩
最近在学习实现Android的拍照AI识别功能,主要通过调用手机系统的摄像机来完成拍照,并通过网络服务访问百度智慧云上的图像识别api来进行识图,最后将识图结果返回到app中。我把这个功能分成两个部分,分别是拍摄和识别,这篇文章讲的是图像识别部分,想看拍摄部分分析请点击:Android拍摄并进行图像识别(一)_Super500000的博客-CSDN博客
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; } }); }
这里的apiKey和apiSecret是从百度智慧云的应用列表中负责的,每创建一个应用都会有一个独一无二的apiKey和apiSecret
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("什么都没有"); } }
//按字节读取文件
byte[] imgData = FileUtil.readFileByBytes(imagePath);
/** * 根据文件路径读取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(); } } }
//字节转Base64
String imageBase64 = Base64Util.encode(imgData);
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(); } }
这里讲一下Base64编码,Base64 编码要求把 3 个 8 位字节(3 * 8=24 转化为 4 个 6 位的字节(4 * 6=24),之后在 6 位的前面补两个 0,形成 8 位一个字节的形式。 如果剩下的字符不足 3 个字节,则用 0 填充,输出字符使用 =,因此编码后输出的文本末尾可能会出现 1 或 2 个 =。
举一个简单的例子:
/** * 图像识别请求 * * @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(); //在命令行打印异常信息在程序中出错的位置及原因。 } }
//访问百度智能云的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(); } } }
/** * 获取识别结果响应实体 */ 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; } } }
图像识别方法:
private void ImageDiscern(String token, String imageBase64) {
easydlObjectDetection(token,imageBase64);
/**
* 识别结果列表适配器
*/
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());
}
}
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();
}
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();
}
}
图像识别这一部分还是比较复杂的,主要是图片转码,调用百度智慧云图像识别api的部分,要先根据apiKey和apiSecret获取Token,然后再和Base64的图片数据一起访问百度智慧云的图像识别,识别后获取识别结果也需要我们再写一个结果的JavaBean类来获取。逻辑还是比较复杂的,要根据功能来一步一步写代码。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。