Android 垃圾分类APP(四)垃圾分类之图像输入

图像输入

  • 前言
  • 正文
    • 一、创建平台应用
    • 二、新建图像识别页面
    • 三、网络订阅
    • 四、编写页面代码
    • 五、识别网络图片
    • 六、识别相册图片
    • 七、识别拍照图片
    • 八、垃圾分类
    • 九、源码

前言

  在上一篇文章中完成了语音输入,这一篇来写图像输入

正文

  图像输入无非就是图片识别嘛,再通俗一点就是识别手机中的照片,分析里面的物品,然后进行垃圾分类。图像识别还是有很多的SDK可以使用的,这里面我目前用过的就是百度的图像识别,感觉还是蛮好的,而且有我之前的文章做普遍,那么本文是属于APP功能编写,这与单独写介绍SDK使用的文章完全是两回事。那么就来看看实践中怎么插入这个图像识别了。

如果你还有时间的话,不妨先去看看Android 百度图像识别(详细步骤+源码)

因为毕竟是写过一次的东西了,只是应用环境不同,所以下面就只是介绍业务逻辑和贴代码,不再去详细讲解。

一、创建平台应用

既然要用百度的SDK,自然要先去百度智能云注册登录,登录之后呢。点击管理控制台,然后点击左侧产品服务箭头左侧展开,找到图像识别点进去。
在这里插入图片描述
点击创建应用
在这里插入图片描述
输入相关的信息就可以了。

在这里插入图片描述
填写好资料后点击立即创建。
在这里插入图片描述
查看应用详情。
在这里插入图片描述
这里有三个关键的信息:AppID、API Key、Secret Key,这三个值在后面会用到,请使用自己创建应用时生成的值。现在先把它们放到常量里面,打开Constant,这里的四个常量,对应的值就是你在平台上申请应用产生的,记得使用自己的。
在这里插入图片描述

二、新建图像识别页面

在ui包下新建一个ImageInputActivity,对应的xml为activity_image_input.xml,创建好之后,再MainActivity页面中写一个按钮,点击之后进入刚才创建的这个图像识别页面。
修改activity_main.xml,在语音输入的下面加一个图像输入的按钮,代码如下:

	<!--图像输入--><com.google.android.material.button.MaterialButtonstyle="@style/Widget.MaterialComponents.Button.UnelevatedButton"android:layout_width="match_parent"android:layout_height="@dimen/dp_60"android:layout_margin="@dimen/dp_16"android:gravity="center"android:insetTop="@dimen/dp_0"android:insetBottom="@dimen/dp_0"android:onClick="jumpImageInput"android:text="图像输入"android:textSize="@dimen/sp_16"android:theme="@style/Theme.MaterialComponents.Light.DarkActionBar"app:backgroundTint="@color/colorPrimaryDark"app:cornerRadius="@dimen/dp_12"app:icon="@mipmap/icon_image_input"app:iconGravity="textStart"app:iconSize="@dimen/dp_24" />

进入到MainActivity中,新增一个方法jumpImageInput。

	/*** 进入图像输入页面*/public void jumpImageInput(View view) {gotoActivity(ImageInputActivity.class);}

下面来写ImageInputActivity页面的代码,写代码之前,先完成布局编写,修改activity_image_input,里面的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:fitsSystemWindows="true"android:orientation="vertical"><!--标题--><com.google.android.material.appbar.MaterialToolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="@color/white"android:elevation="@dimen/dp_2"app:navigationIcon="@mipmap/icon_back"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="图像输入"android:textColor="@color/black"android:textSize="18sp" /></com.google.android.material.appbar.MaterialToolbar><!--滑动控件--><androidx.core.widget.NestedScrollViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:overScrollMode="never"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center_horizontal"android:orientation="vertical"><ImageViewandroid:id="@+id/iv_picture"android:layout_width="@dimen/dp_200"android:layout_height="@dimen/dp_300"android:layout_marginTop="@dimen/dp_12"android:visibility="gone" /><EditTextandroid:id="@+id/et_image_url"android:layout_width="match_parent"android:layout_height="@dimen/dp_50"android:background="@drawable/shape_et_bg"android:hint="网络图片Url"android:layout_margin="@dimen/dp_1"android:textCursorDrawable="@drawable/cursor_style"android:paddingStart="@dimen/dp_12"android:paddingEnd="@dimen/dp_12"android:singleLine="true"android:imeOptions="actionGo"android:textSize="@dimen/sp_14"android:visibility="gone" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:padding="@dimen/dp_12"><com.google.android.material.button.MaterialButtonandroid:id="@+id/btn_web_picture"style="@style/Widget.MaterialComponents.Button.UnelevatedButton"android:layout_width="0dp"android:layout_height="@dimen/dp_50"android:layout_marginEnd="@dimen/dp_6"android:layout_weight="1"android:gravity="center"android:insetTop="@dimen/dp_0"android:insetBottom="@dimen/dp_0"android:text="网络图片"android:textSize="@dimen/sp_16"android:theme="@style/Theme.MaterialComponents.Light.DarkActionBar"app:backgroundTint="@color/colorPrimaryDark"app:cornerRadius="@dimen/dp_12"app:iconGravity="textStart"app:iconSize="@dimen/dp_24" /><com.google.android.material.button.MaterialButtonandroid:id="@+id/btn_open_album"style="@style/Widget.MaterialComponents.Button.UnelevatedButton"android:layout_width="0dp"android:layout_height="@dimen/dp_50"android:layout_marginStart="@dimen/dp_6"android:layout_marginEnd="@dimen/dp_6"android:layout_weight="1"android:gravity="center"android:insetTop="@dimen/dp_0"android:insetBottom="@dimen/dp_0"android:text="相册图片"android:textSize="@dimen/sp_16"android:theme="@style/Theme.MaterialComponents.Light.DarkActionBar"app:backgroundTint="@color/colorPrimaryDark"app:cornerRadius="@dimen/dp_12"app:iconGravity="textStart"app:iconSize="@dimen/dp_24" /><com.google.android.material.button.MaterialButtonandroid:id="@+id/btn_take_photo"style="@style/Widget.MaterialComponents.Button.UnelevatedButton"android:layout_width="0dp"android:layout_height="@dimen/dp_50"android:layout_marginStart="@dimen/dp_6"android:layout_weight="1"android:gravity="center"android:insetTop="@dimen/dp_0"android:insetBottom="@dimen/dp_0"android:text="拍照图片"android:textSize="@dimen/sp_16"android:theme="@style/Theme.MaterialComponents.Light.DarkActionBar"app:backgroundTint="@color/colorPrimaryDark"app:cornerRadius="@dimen/dp_12"app:iconGravity="textStart"app:iconSize="@dimen/dp_24" /></LinearLayout><!--图像识别结果--><LinearLayoutandroid:id="@+id/lay_recognition_result"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center_horizontal"android:orientation="vertical"android:visibility="gone"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center_vertical"><Viewandroid:layout_width="@dimen/dp_30"android:layout_height="@dimen/dp_1"android:background="@color/line_color" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="@dimen/dp_12"android:text="识别结果" /><Viewandroid:layout_width="@dimen/dp_30"android:layout_height="@dimen/dp_1"android:background="@color/line_color" /></LinearLayout><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_recognition_result"android:layout_width="match_parent"android:layout_height="wrap_content"android:overScrollMode="never" /></LinearLayout><!--垃圾分类结果--><LinearLayoutandroid:id="@+id/lay_classification_result"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center_horizontal"android:orientation="vertical"android:visibility="gone"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center_vertical"><Viewandroid:layout_width="@dimen/dp_30"android:layout_height="@dimen/dp_1"android:background="@color/line_color" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="@dimen/dp_12"android:text="分类结果" /><Viewandroid:layout_width="@dimen/dp_30"android:layout_height="@dimen/dp_1"android:background="@color/line_color" /></LinearLayout><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_classification_result"android:layout_width="match_parent"android:layout_height="wrap_content"android:overScrollMode="never" /></LinearLayout></LinearLayout></androidx.core.widget.NestedScrollView>
</LinearLayout>

然后回到ImageInputActivity页面,再写代码之前,先想一下这个页面要做什么?首先是获取百度的鉴权Token,然后进行图片识别,最后进行物品的垃圾分类。那么就需要三次网络请求,这里需要重写一个订阅。

三、网络订阅

这里需要增加一个网络访问地址,因为使用的是百度的API,而本身有一个天行的API地址,这里需要对两个地址进行一个控制。打开NetworkApi,在里面增加如下方法。

	/*** 修改访问地址* @param type*/private static void getBaseUrl(int type) {switch (type) {case 0://天行API地址mBaseUrl = "http://api.tianapi.com";break;case 1://百度SDK地址mBaseUrl = "https://aip.baidubce.com";break;default:break;}}

这个方法根据传入类型的不同,使用不同的网络地址,之前写博客时疏忽了,写的嗨了,漏掉了这一部分,现在补上。然后在createService方法中增加一个type参数,之后调用getBaseUrl方法获取访问地址。
在这里插入图片描述
现在你的这个createService方法改动了,那么其他调用了这个方法的地方也要做相应的改动,比如之前在做文字输入进行垃圾分类识别时,TextContract中的调用,之前是没有type的,现在你加一个0就可以了,0表示就是访问天行API。
在这里插入图片描述
其他的地方记也要修改,否则会报错的。改好之后,就可以来写这个图像识别到的订阅器了,如下:

在contract包下新建一个ImageContract类,里面的代码如下:

package com.llw.goodtrash.contract;import android.annotation.SuppressLint;import com.llw.goodtrash.api.ApiService;
import com.llw.goodtrash.model.GetDiscernResultResponse;
import com.llw.goodtrash.model.GetTokenResponse;
import com.llw.goodtrash.model.TrashResponse;
import com.llw.mvplibrary.base.BasePresenter;
import com.llw.mvplibrary.base.BaseView;
import com.llw.mvplibrary.network.NetworkApi;
import com.llw.mvplibrary.network.observer.BaseObserver;import static com.llw.goodtrash.utils.Constant.*;/*** 图像输入页面访问网络** @author llw* @date 2021/3/30 15:28*/
public class ImageContract {public static class ImagePresenter extends BasePresenter<ImageView> {/*** 获取鉴权Token*/@SuppressLint("CheckResult")public void getToken() {ApiService service = NetworkApi.createService(ApiService.class, 1);service.getToken(GRANT_TYPE, API_KEY, API_SECRET).compose(NetworkApi.applySchedulers(new BaseObserver<GetTokenResponse>() {@Overridepublic void onSuccess(GetTokenResponse getTokenResponse) {if (getView() != null) {getView().getTokenResponse(getTokenResponse);}}@Overridepublic void onFailure(Throwable e) {if (getView() != null) {getView().getTokenFailed(e);}}}));}/*** 获取图像识别结果** @param token 鉴权Token* @param image 图片base64* @param url   网络图片url*/@SuppressLint("CheckResult")public void getDiscernResult(String token, String image, String url) {ApiService service = NetworkApi.createService(ApiService.class, 1);service.getDiscernResult(token, image, url).compose(NetworkApi.applySchedulers(new BaseObserver<GetDiscernResultResponse>() {@Overridepublic void onSuccess(GetDiscernResultResponse getTokenResponse) {if (getView() != null) {getView().getDiscernResultResponse(getTokenResponse);}}@Overridepublic void onFailure(Throwable e) {if (getView() != null) {getView().getDiscernResultFailed(e);}}}));}/*** 搜索物品** @param word 物品名*/@SuppressLint("CheckResult")public void searchGoods(String word) {ApiService service = NetworkApi.createService(ApiService.class, 0);service.searchGoods(word).compose(NetworkApi.applySchedulers(new BaseObserver<TrashResponse>() {@Overridepublic void onSuccess(TrashResponse groupResponse) {if (getView() != null) {getView().getSearchResponse(groupResponse);}}@Overridepublic void onFailure(Throwable e) {if (getView() != null) {getView().getSearchResponseFailed(e);}}}));}}public interface ImageView extends BaseView {/*** 获取鉴权Token** @param response GetTokenResponse*/void getTokenResponse(GetTokenResponse response);/*** 获取鉴权Token异常返回** @param throwable 异常*/void getTokenFailed(Throwable throwable);/*** 获取图像识别结果** @param response GetDiscernResultResponse*/void getDiscernResultResponse(GetDiscernResultResponse response);/*** 获取图像识别结果失败** @param throwable 异常*/void getDiscernResultFailed(Throwable throwable);/*** 搜索物品返回** @param response TrashResponse*/void getSearchResponse(TrashResponse response);/*** 搜索物品异常返回** @param throwable 异常*/void getSearchResponseFailed(Throwable throwable);}
}

鉴权方法中的几个全局变量在Constant中定义,

	/*** 鉴权Token*/public static final String TOKEN = "accessToken";/*** 获取Token的时间*/public static final String GET_TOKEN_TIME = "getTokenTime";/*** Token有效期*/public static final String TOKEN_VALID_PERIOD = "tokenValidPeriod";/*** 百度鉴权认证参数值*/public static final String GRANT_TYPE = "client_credentials";/*** 百度图像识别 APP ID  GoodTrash*/public static final String APP_ID = "23943795";/*** 百度图像识别 APP Key  GoodTrash*/public static final String API_KEY = "PAUCX7vSAd4ZBwv897GAfhEQ";

请注意,这里的值是我在百度开放平台上注册应用时生成的,请替换为自己的。

下面回到ImageInputActivity,修改代码后如下:

package com.llw.goodtrash.ui;import android.os.Bundle;
import com.llw.goodtrash.R;
import com.llw.goodtrash.contract.ImageContract;
import com.llw.goodtrash.model.GetDiscernResultResponse;
import com.llw.goodtrash.model.GetTokenResponse;
import com.llw.goodtrash.model.TrashResponse;
import com.llw.mvplibrary.mvp.MvpActivity;/*** 图像输入物品进行垃圾分类** @author llw* @date 2021/4/7 11:04*/
public class ImageInputActivity extends MvpActivity<ImageContract.ImagePresenter> implements ImageContract.ImageView {@Overridepublic void initData(Bundle savedInstanceState) {}@Overridepublic int getLayoutId() {return R.layout.activity_image_input;}@Overrideprotected ImageContract.ImagePresenter createPresenter() {return new ImageContract.ImagePresenter();}@Overridepublic void getTokenResponse(GetTokenResponse response) {}@Overridepublic void getTokenFailed(Throwable throwable) {}@Overridepublic void getDiscernResultResponse(GetDiscernResultResponse response) {}@Overridepublic void getDiscernResultFailed(Throwable throwable) {}@Overridepublic void getSearchResponse(TrashResponse response) {}@Overridepublic void getSearchResponseFailed(Throwable throwable) {}}

这里使用了MVP,通过P来处理M和V,三个网络请求对应六个返回,下面进行页面的初始化

四、编写页面代码

先声明一些变量

	private static final String TAG = "ImageInputActivity";/*** 打开相册*/private static final int OPEN_ALBUM_CODE = 100;/*** 打开相机*/private static final int TAKE_PHOTO_CODE = 101;/*** 鉴权Toeken*/private String accessToken;private Toolbar toolbar;private ImageView ivPicture;private EditText etImageUrl;private LinearLayout layRecognitionResult,layClassificationResult;private RecyclerView rvRecognitionResult,rvClassificationResult;private RxPermissions rxPermissions;private File outputImage;

然后新增一个initView的方法。

	/*** 初始化*/private void initView() {toolbar = findViewById(R.id.toolbar);ivPicture = findViewById(R.id.iv_picture);etImageUrl = findViewById(R.id.et_image_url);findViewById(R.id.btn_web_picture).setOnClickListener(this);findViewById(R.id.btn_open_album).setOnClickListener(this);findViewById(R.id.btn_take_photo).setOnClickListener(this);layRecognitionResult = findViewById(R.id.lay_recognition_result);layClassificationResult = findViewById(R.id.lay_classification_result);rvRecognitionResult = findViewById(R.id.rv_recognition_result);rvClassificationResult = findViewById(R.id.rv_classification_result);//设置页面状态栏setStatubar(this, R.color.white, true);back(toolbar, true);rxPermissions = new RxPermissions(this);}

然后在initData中调用它。

	@Overridepublic void initData(Bundle savedInstanceState) {initView();}

然后继承控件的点击监听回调
在这里插入图片描述
重写onClick方法。

	@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_web_picture://网络图片break;case R.id.btn_open_album://相册图片break;case R.id.btn_take_photo://拍照图片break;default:break;}}

由于Token存在有效期的关系,因此不需要每一次都去获取,所以可以在第一次获取之后存入缓存,只要Token在有效期内都可以直接从缓存中获取,这样就可以少请求一次网络。
下面先写一个缓存的工具类。
在utils包下新增一个SPUtils类,里面的代码如下:

package com.llw.goodtrash.utils;import android.content.Context;
import android.content.SharedPreferences;/*** SharedPreferences工具类** @author llw*/
public class SPUtils {private static final String NAME = "config";public static void putBoolean(String key, boolean value, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);sp.edit().putBoolean(key, value).commit();}public static boolean getBoolean(String key, boolean defValue, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);return sp.getBoolean(key, defValue);}public static void putString(String key, String value, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);sp.edit().putString(key, value).commit();}public static String getString(String key, String defValue, Context context) {if (context != null) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);return sp.getString(key, defValue);}return "";}public static void putInt(String key, int value, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);sp.edit().putInt(key, value).commit();}public static int getInt(String key, int defValue, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);return sp.getInt(key, defValue);}public static void putLong(String key, long value, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);sp.edit().putLong(key, value).commit();}public static long getLong(String key, long defValue, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);return sp.getLong(key, defValue);}public static void remove(String key, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);sp.edit().remove(key).commit();}}

然后在ImageInputActivity中写一个获取Token的方法,代码如下:

	/*** 获取鉴权Token*/private String getAccessToken() {String token = SPUtils.getString(Constant.TOKEN, null, this);if (token == null) {//访问API获取接口mPresenter.getToken();} else {//则判断Token是否过期if (isTokenExpired()) {//过期mPresenter.getToken();} else {accessToken = token;}}return accessToken;}

这里你的isTokenExpired()方法会报红,这是一个用来判断Token是否过期的方法。里面的代码如下:

	/*** Token是否过期** @return*/private boolean isTokenExpired() {//获取Token的时间long getTokenTime = SPUtils.getLong(Constant.GET_TOKEN_TIME, 0, this);//获取Token的有效时间long effectiveTime = SPUtils.getLong(Constant.TOKEN_VALID_PERIOD, 0, this);//获取当前系统时间long currentTime = System.currentTimeMillis() / 1000;return (currentTime - getTokenTime) >= effectiveTime;}

刚才在获取Token方法中,通过mPresenter.getToken();发起了一个网络请求,返回的结果有成功和失败,成功后会有Token返回,失败了会有失败原因返回。

修改如下方法:

	/*** 获取鉴权Token成功返回* @param response GetTokenResponse*/@Overridepublic void getTokenResponse(GetTokenResponse response) {if (response != null) {//鉴权TokenaccessToken = response.getAccess_token();//过期时间 秒long expiresIn = response.getExpires_in();//当前时间 秒long currentTimeMillis = System.currentTimeMillis() / 1000;//放入缓存SPUtils.putString(Constant.TOKEN, accessToken, this);SPUtils.putLong(Constant.GET_TOKEN_TIME, currentTimeMillis, this);SPUtils.putLong(Constant.TOKEN_VALID_PERIOD, expiresIn, this);} else {showMsg("Token为null");}}/*** 获取Token失败返回* @param throwable 异常*/@Overridepublic void getTokenFailed(Throwable throwable) {Log.d(TAG, "Token获取失败:"+throwable.toString());}

我已经写了注释了,那么你就知道这个方法是做什么的了。

五、识别网络图片

我的想法是当我点击这个网络图片的按钮时,页面出现一个输入框,当我输入完成之后,点击键盘的回车直接识别,虽后隐藏这个输入框,嗯,就是这样。
首先来写点击网络图片时的业务逻辑代码。

			case R.id.btn_web_picture://网络图片etImageUrl.setVisibility(View.VISIBLE);etImageUrl.setOnKeyListener((view, keyCode, keyEvent) -> {if (keyCode == KeyEvent.KEYCODE_ENTER && keyEvent.getAction() == KeyEvent.ACTION_UP) {String webImageUrl = etImageUrl.getText().toString().trim();String defaultWebImageUrl = "https://bce-baiyu.cdn.bcebos.com/14ce36d3d539b6004ef2e45fe050352ac65cb71e.jpeg";String imageUrl = "".equals(webImageUrl) ?defaultWebImageUrl : webImageUrl;//识别网络图片UrlshowLoadingDialog();Glide.with(context).load(imageUrl).into(ivPicture);mPresenter.getDiscernResult(accessToken,null,imageUrl);etImageUrl.setVisibility(View.GONE);}return false;});break;

在这里发起了一个图片识别的请求,下面来看返回的方法处理。

	/*** 图片识别成功返回* @param response GetDiscernResultResponse*/@Overridepublic void getDiscernResultResponse(GetDiscernResultResponse response) {if(response == null){hideLoadingDialog();showMsg("未获得相应的识别结果");return;}ivPicture.setVisibility(View.VISIBLE);List<GetDiscernResultResponse.ResultBean> result = response.getResult();if (result != null && result.size() > 0) {//显示识别结果showDiscernResult(result);} else {hideLoadingDialog();showMsg("未获得相应的识别结果");}}/*** 图片识别成功失败* @param throwable 异常*/@Overridepublic void getDiscernResultFailed(Throwable throwable) {Log.d(TAG, "图片识别失败:"+throwable.toString());}

返回成功之后,如果数据不为空则显示要识别的图片,然后通过列表展示识别结果数据,
首先得有一个识别的结果列表item布局,item_distinguish_result_rv.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/item_distinguish_rv"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginBottom="1dp"android:background="#FFF"android:foreground="?attr/selectableItemBackground"android:padding="16dp"><TextViewandroid:id="@+id/tv_keyword"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textColor="#000"android:textSize="16sp" /><TextViewandroid:id="@+id/tv_root"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@+id/tv_keyword"android:layout_marginTop="@dimen/dp_4"android:textSize="14sp" /><TextViewandroid:id="@+id/tv_score"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="true" />
</RelativeLayout>

下面写适配器代码,在adapter下新建一个DiscernResultAdapter类,代码如下:

package com.llw.goodtrash.adapter;import androidx.annotation.Nullable;import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.llw.goodtrash.R;
import com.llw.goodtrash.model.GetDiscernResultResponse;import java.util.List;/*** 识别结果列表适配器* @author llw*/
public class DiscernResultAdapter extends BaseQuickAdapter<GetDiscernResultResponse.ResultBean, BaseViewHolder> {public DiscernResultAdapter(int layoutResId, @Nullable List<GetDiscernResultResponse.ResultBean> data) {super(layoutResId, data);}@Overrideprotected void convert(BaseViewHolder helper, GetDiscernResultResponse.ResultBean item) {helper.setText(R.id.tv_keyword,item.getKeyword()).setText(R.id.tv_root,item.getRoot()).setText(R.id.tv_score,String.valueOf(item.getScore())).addOnClickListener(R.id.item_distinguish_rv);}
}

适配器和列表item都写好了,下面回到ImageInputActivity中新增方法showDiscernResult(),代码如下:

	/*** 显示识别的结果列表** @param result*/private void showDiscernResult(List<GetDiscernResultResponse.ResultBean> result) {DiscernResultAdapter adapter = new DiscernResultAdapter(R.layout.item_distinguish_result_rv, result);rvRecognitionResult.setLayoutManager(new LinearLayoutManager(this));rvRecognitionResult.setAdapter(adapter);//隐藏加载hideLoadingDialog();//显示弹窗layRecognitionResult.setVisibility(View.VISIBLE);layClassificationResult.setVisibility(View.GONE);}

下面来运行一下:
在这里插入图片描述
不一定第一次就能识别出来,看你的人品。下面识别相册图片

六、识别相册图片

下面写点击这个相册图片按钮的业务逻辑,如下:

			case R.id.btn_open_album://相册图片if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {rxPermissions.request(Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE).subscribe(grant -> {if (grant) {//获得权限openAlbum();} else {showMsg("未获取到权限");}});} else {openAlbum();}break;

打开相册的openAlbum()方法,代码如下:

	/*** 打开相册*/private void openAlbum() {Intent intent = new Intent();intent.setAction(Intent.ACTION_PICK);intent.setType("image/*");startActivityForResult(intent, OPEN_ALBUM_CODE);}

下面就是从相册返回的处理

	@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);if (resultCode == RESULT_OK) {showLoadingDialog();if (requestCode == OPEN_ALBUM_CODE) {//打开相册返回String[] filePathColumns = {MediaStore.Images.Media.DATA};final Uri imageUri = Objects.requireNonNull(data).getData();Cursor cursor = getContentResolver().query(imageUri, filePathColumns, null, null, null);cursor.moveToFirst();int columnIndex = cursor.getColumnIndex(filePathColumns[0]);//获取图片路径String imagePath = cursor.getString(columnIndex);cursor.close();//识别localImageDiscern(imagePath);}} else {showMsg("什么都没有");}}

通过相册图片获取图片的路径,通过localImageDiscern方法将这个路径去转字节,再转base64。代码如下:

	/*** 本地图片识别*/private void localImageDiscern(String imagePath) {try {String token = getAccessToken();//通过图片路径显示图片Glide.with(this).load(imagePath).into(ivPicture);//按字节读取文件byte[] imgData = FileUtil.readFileByBytes(imagePath);//字节转Base64String imageBase64 = Base64Util.encode(imgData);//本地图片识别mPresenter.getDiscernResult(token, imageBase64, null);} catch (IOException e) {e.printStackTrace();}}

这里面还有两个工具类FileUtil和Base64Util。下面在utils包下新建一个FileUtil类,里面的代码如下:

package com.llw.goodtrash.utils;import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;/*** 文件读取工具类*/
public class FileUtil {/*** 读取文件内容,作为字符串返回*/public static String readFileAsString(String filePath) throws IOException {File file = new File(filePath);if (!file.exists()) {throw new FileNotFoundException(filePath);} if (file.length() > 1024 * 1024 * 1024) {throw new IOException("File is too large");} StringBuilder sb = new StringBuilder((int) (file.length()));// 创建字节输入流  FileInputStream fis = new FileInputStream(filePath);  // 创建一个长度为10240的Bufferbyte[] bbuf = new byte[10240];  // 用于保存实际读取的字节数  int hasRead = 0;  while ( (hasRead = fis.read(bbuf)) > 0 ) {  sb.append(new String(bbuf, 0, hasRead));  }  fis.close();  return sb.toString();}/*** 根据文件路径读取byte[] 数组*/public static byte[] readFileByBytes(String filePath) throws IOException {File file = new File(filePath);if (!file.exists()) {throw new FileNotFoundException(filePath);} else {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;while (-1 != (len1 = in.read(buffer, 0, bufSize))) {bos.write(buffer, 0, len1);}byte[] var7 = bos.toByteArray();return var7;} finally {try {if (in != null) {in.close();}} catch (IOException var14) {var14.printStackTrace();}bos.close();}}}
}

然后是Base64Util类,代码如下:

package com.llw.goodtrash.utils;/*** 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();}
}

下面可以直接运行了。
在这里插入图片描述
下面该识别拍照图片

七、识别拍照图片

点击拍照图片按钮的业务逻辑代码,

			case R.id.btn_take_photo://拍照图片if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {rxPermissions.request(Manifest.permission.CAMERA).subscribe(grant -> {if (grant) {//获得权限turnOnCamera();} else {showMsg("未获取到权限");}});} else {turnOnCamera();}break;

turnOnCamera是用来打开相机的方法,里面的代码如下:

	/*** 打开相机*/private void turnOnCamera() {SimpleDateFormat timeStampFormat = new SimpleDateFormat("HH_mm_ss");String filename = timeStampFormat.format(new Date());//创建File对象outputImage = new File(getExternalCacheDir(), "takePhoto" + filename + ".jpg");Uri imageUri;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {imageUri = FileProvider.getUriForFile(this,"com.llw.goodtrash.fileprovider", outputImage);} else {imageUri = Uri.fromFile(outputImage);}//打开相机Intent intent = new Intent();intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);startActivityForResult(intent, TAKE_PHOTO_CODE);}

然后同样要在onActivityResult方法中添加相机返回的分支判断。

	else if (requestCode == TAKE_PHOTO_CODE) {//拍照返回String imagePath = outputImage.getAbsolutePath();//识别localImageDiscern(imagePath);}

添加位置如下:
在这里插入图片描述
下面就可以直接运行了。
在这里插入图片描述
OK,现在就来进行垃圾分类了。

八、垃圾分类

刚才通过图像识别已经拿到物品结果了,下面通过点击这个物品去进行垃圾分类。添加adapter的适配器,在showDiscernResult方法中添加如下代码:

		//添加列表Item点击adapter.setOnItemChildClickListener((adapter1, view, position) -> {showLoadingDialog();//垃圾分类mPresenter.searchGoods(result.get(position).getKeyword());});

添加位置如下:
在这里插入图片描述
下面进行搜索物品的返回数据处理。

	/*** 搜索物品进行垃圾分类成功返回** @param response TrashResponse*/@Overridepublic void getSearchResponse(TrashResponse response) {//请求成功  进行数据的渲染if (response.getCode() == Constant.SUCCESS_CODE) {List<TrashResponse.NewslistBean> result = response.getNewslist();if (result != null && result.size() > 0) {//显示分类结果showClassificationResult(result);} else {showMsg("触及到了知识盲区");}} else {hideLoadingDialog();showMsg(response.getMsg());}}@Overridepublic void getSearchResponseFailed(Throwable throwable) {Log.d(TAG, "搜索物品进行垃圾分类失败:" + throwable.toString());}

通过showClassificationResult方法进行物品垃圾分类结果显示,代码如下:

	/*** 显示物品分类结果* @param result*/private void showClassificationResult(List<TrashResponse.NewslistBean> result) {SearchGoodsAdapter adapter = new SearchGoodsAdapter(R.layout.item_search_rv,result);rvClassificationResult.setLayoutManager(new LinearLayoutManager(context));rvClassificationResult.setAdapter(adapter);//隐藏加载hideLoadingDialog();//显示弹窗layClassificationResult.setVisibility(View.VISIBLE);}

下面运行一下:
在这里插入图片描述
下面再优化一下,就是让数据显示之后,滑动到屏幕底部,
修改activity_image_input.xml
给NestedScrollView控件添加一个id。

android:id="@+id/nestedScrollView"

然后进入ImageInputActivity中,初始化。
在这里插入图片描述
写一个方法

	/*** 滑动到屏幕底部*/private void scrollToEnd() {nestedScrollView.post(() -> {nestedScrollView.fullScroll(View.FOCUS_DOWN);//滚到底部//nestedScrollView.fullScroll(ScrollView.FOCUS_UP);//滚到顶部});}

在显示列表数据时后调用,有两处。
第一处,图像识别的结果列表显示之后
在这里插入图片描述
第二处,显示物品垃圾分类结果显示之后
在这里插入图片描述
运行一下:
在这里插入图片描述
那么这个页面的功能就写完了。
下一篇:垃圾分类APP(五)垃圾分类新闻展示

九、源码

GitHub源码地址如下:GoodTrash

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/64872.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

垃圾分类图片数据集分享-约10w张数据集

1.获取方式 点赞本博客评论区留邮箱&#xff0c;博主在会发送(私信博主)。 访问的人太多了&#xff0c;需要私信联系。 截至到2021.03.30评论区所有邮箱已无偿发送 图片数据集直接留邮箱即可。 2.问题描述 最近在做一个相关项目&#xff0c;从网上整理了许许多多的有关于垃圾…

垃圾分类调研

垃圾分类 welcome&#xff0c;我们是电子科技大学的学生&#xff0c;因为对垃圾分类的一些问题感兴趣&#xff0c;于是我们进行了一些调查。 小组信息 学校&#xff1a;UESTC 年纪&#xff1a;2019级大一学生 成员&#xff1a;林浩&#xff0c;李则程&#xff0c;张智霖 分…

工程训练赛——智能垃圾分类

刚刚搞完工程训练赛&#xff0c;忙活两个月了&#xff0c;由于缺少参赛经验&#xff0c;比赛时出了意外状况&#xff0c;结果还是功亏一篑&#xff0c;就写一篇博客记录一下大学参加的第一次竞赛。 比赛要求 软件思路 由于我刚好在做项目的时候用的是PyQt5来写界面&#xff0…

垃圾分类解决方案-最新全套文件

垃圾分类解决方案-最新全套文件 一、建设背景垃圾分类的意义1.为什么要进行垃圾分类2.智慧垃圾分类的重要性 二、建设思路三、建设方案四、获取 - 垃圾分类全套最新解决方案合集 一、建设背景 垃圾分类的意义 1.为什么要进行垃圾分类 将易腐有机成分为主的厨房垃圾单独分类&…

垃圾分类全套技术方案

向AI转型的程序员都关注了这个号&#x1f447;&#x1f447;&#x1f447; 设计构思与创意 本作品以微信小程序为“个人”平台&#xff0c;用户可在微信小程序中录入必要的人脸等个人信息&#xff0c;并且能够以微信小程序为窗口查询自己的垃圾分类详情。为保证微信小程序的丰富…

垃圾分类资料汇总

目录 一、前言二、垃圾分类话题简介三、当前存在的一些有用参考资源四、当前存在的垃圾分类小程序或者APP五、当前规模比较大的产品六、个人想法参考资料注意事项 一、前言 自从上海实行了垃圾分类之后&#xff0c;垃圾分类这个话题就成为了一个热点话题&#xff0c;比较流行的…

智能垃圾分类

智能垃圾分类 2021.4.9&#xff0c;浙江省举办了第七届工程训练大赛&#xff0c;我们组参加的是垃圾分类的项目&#xff0c;我们组顺利挺进决赛&#xff0c;但是我们看决赛规则并没有标注多种垃圾分类&#xff0c;我们没有完全的准备好应对多种垃圾分类&#xff0c;所以与国赛…

【图像分类数据集】非常全面实用的垃圾分类图片数据集共享

【图像分类数据集】非常全面实用的垃圾分类图片数据集共享 数据集介绍&#xff1a; 训练集 文件夹结构如下&#xff08;部分&#xff1a; 第0类文件夹下数据展示如下&#xff08;部分&#xff1a; 测试集 大致如下&#xff1a; 数据集获取方式&#xff1a; 总结&#xf…

VS2019安装不上 怎么弄啊

之前的版本是VS2017,因为笔记本很卡,就重装了个系统 再次安VS的时候就怎么都安不上去,是这样,卡到2/67,就不动了,然后就失败 安的时候选了C++有关的项目,不行,然后什么负载都不选,还是上面那样,还把C盘里的Microsoft.Net文件夹给删了,专门的卸载程序试过、控制面板…

计算机重新启动进不去系统,电脑关机重启进不了系统怎么办

可能还有些网友不太了解电脑关机重启进不了系统的情况&#xff0c;下面就由学习啦小编给你们介绍电脑关机重启进不了系统的原因及解决方法吧&#xff0c;希望能帮到大家哦! 电脑关机重启进不了系统的解决方法一&#xff1a; 蓝屏代码或事件查看器里面的内容普通人是看不懂的&am…

8Manage:分散的软件正在扼杀公司的生产力

在企业领域&#xff0c;数字化不仅仅是指工具能力&#xff0c;而是指用户如何很好地应用他们的知识来做决策&#xff0c;培养关系&#xff0c;建立声誉&#xff0c;以及动员同事、团队。几十年来&#xff0c;企业已经部署了生产力、搜索和协作平台&#xff0c;以提高员工和业务…

中高端洪流已至,酒店企业如何趁势突围

五一小长假即将到来&#xff0c;在人们热切盼望能出行游玩时&#xff0c;频发的疫情却挡住了人们出行的脚步。原本2020年突发的疫情“黑天鹅”&#xff0c;在近两年已经成为常态&#xff0c;对旅游业、酒店业造成严重影响。不过&#xff0c;从酒店行业整体来看&#xff0c;走向…

酒店预订网客户流失分析案例

阅读路线 项目介绍&#xff1a;该项目对某酒店预订网在一段时间内的客户预定信息数据进行分析&#xff0c;其中着重对该网站整体消费情况和用户行为展开分析&#xff0c;找出高价值用户人群&#xff0c;对客户进行用户画像分析&#xff0c;从而为该网站的精细化营销提供相关建议…

宏昆酒店集团携手DataPipeline打造实时数据融合平台,酒店业精益管理的新秘诀

酒店选址数字化审批、刷脸核身和无证核验、多渠道动态联动营销、客户个性化服务......数字化创新正在成为酒店未来的核心竞争力&#xff0c;且目前已成为大、中型酒店的“标配”。把“创新”写入了企业精神的宏昆酒店集团&#xff0c;早已超过业内大部分企业&#xff0c;在数字…

幸福消费成酒店投资趋势红利,荟语酒店凭何打造品牌核心优势

酒店行业经历数十年的高速增长&#xff0c;历经了一轮轮商业嬗变。时至今日&#xff0c;中高端酒店已成为市场中不可忽视的生力军&#xff0c;其中自然幸福系酒店品牌——荟语酒店更是已成为酒店投资市场的瞩目亮点。 那么&#xff0c;在中高端酒店市场中&#xff0c;荟语酒店凭…

数据分析项目实战:酒店需求分析(hotel demand booking)

1 项目背景 使用2015年7月到2017年8月两年的订单数据进行分析&#xff0c;了解酒店预订需求的基本情况&#xff0c;找出导致订单取消的特征。 2 数据初步探索 2.1 数据结构梳理 拿到数据之后&#xff0c;首先看看里面具体有哪些内容&#xff0c;理解每个字段&#xff08;变…

酒店预订分析

Hotel Booking Analysis 目的&#xff1a;从我们拥有的数据集中创建有意义的估计量&#xff0c;并通过将它们与不同的ML模型和ROC曲线的准确性得分进行比较&#xff0c;来选择预测性能最好的模型。 1- EDA 2- Preprocessing 3- Models and ROC Curve Comparison Logistic …

酒店应用爆发式增长,“API即服务”已成趋势!

据谷歌发布的《2021API经济报告》显示&#xff1a;2020年&#xff0c;近四分之三的组织继续在数字化转型上投资&#xff0c;其中三分之二的组织加大投资或作出战略调整&#xff0c;实行数字优先战略。 而数字化转型的核心&#xff0c;就是将组织的服务、资产和能力打包成互联网…

数据储存技术演进趋势研判

如果以 1987 年 Symmetrix 高端存储产品的诞生作为独立外置存储行业出现的标志&#xff0c;那么外置存储行业已经历了探索、成长和成熟三个阶段。在探索和成长期内&#xff0c;行业发展出大量令人惊叹的创新存储技术&#xff0c;如&#xff1a; SAN/NAS/iSCSI/Object 等存储协议…

英特尔数据存储创新三大技术看点和猜想

“话说天下大势&#xff0c;分久必合&#xff0c;合久必分。周末七国分争&#xff0c;并入于秦。及秦灭之后&#xff0c;楚、汉分争&#xff0c;又并入于汉。汉朝自高祖斩白蛇而起义&#xff0c;一统天下&#xff0c;后来光武中兴&#xff0c;传至献帝&#xff0c;遂分为三国。…