Android 百度图像识别(详细步骤+源码)

百度图像识别

    • 运行效果图
    • 一、创建平台应用
    • 二、创建Android项目
    • 三、网络访问框架
    • 四、添加请求API接口
    • 五、获取鉴权认证Token
    • 六、网络图片Url识别
    • 七、相册图片识别
    • 八、拍照图片识别
    • 九、源码

运行效果图

在这里插入图片描述

如果你对这个效果图不满意就不用往下看了,那样只会浪费你的时间。

一、创建平台应用

先登录百度开放平台
在这里插入图片描述

然后进入管理控制台找到图像识别
在这里插入图片描述
点击进入。
在这里插入图片描述
创建应用
在这里插入图片描述
然后点击下方的立即创建按钮。
在这里插入图片描述
注意看下图标注的信息

在这里插入图片描述
由于图像识别没有直接的Android SDK,因此本文将通过API访问进行图像的识别。

二、创建Android项目

通过API方式,则需要先完成鉴权认证,然后拿到Access Token,通过这个Access Token才能去请求这个图像识别的接口,这里要分为两步走。现在思路清楚了,下面先创建一个项目吧,命名为ImageDiscernDemo。
在这里插入图片描述
项目创建好之后,先配置项目。打开工程的build.gradle,添加如下代码:

	maven { url "https://jitpack.io" }

添加位置如下:
在这里插入图片描述

然后是修改app下的build.gradle,有两处

	compileOptions {sourceCompatibility = 1.8targetCompatibility = 1.8}
	//权限请求框架implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'implementation "io.reactivex.rxjava2:rxjava:2.0.0"//retrofit2implementation 'com.squareup.retrofit2:retrofit:2.4.0'implementation 'com.squareup.retrofit2:converter-gson:2.4.0'implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'//RecyclerView最好的适配器,让你的适配器一目了然,告别代码冗余implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30'//Glide框架implementation 'com.github.bumptech.glide:glide:4.12.0'annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'//添加material库implementation 'com.google.android.material:material:1.2.1'

添加位置如下:
在这里插入图片描述

在这里插入图片描述

然后Sync Now,完成后来配置AndroidManifest.xml,添加如下权限:

	<!--网络权限--><uses-permission android:name="android.permission.INTERNET"/><!--文件读写--><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><!--相机--><uses-permission android:name="android.permission.CAMERA"/>

在这里插入图片描述
再来配置一个FileProvider,在res文件夹下新建一个xml文件夹,与layout文件夹同级,在xml文件夹下新建一个file_paths.xml,里面的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><external-path name="my_images" path="" />
</paths>

然后在AndroidManifest.xml中配置

		<providerandroid:name="androidx.core.content.FileProvider"android:authorities="com.llw.imagediscerndemo.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths"/></provider>

添加位置如下图:
在这里插入图片描述

下面我们首先简单来写一个网络访问的工具类。

三、网络访问框架

在com.llw.imagediscerndemo下新建一个network包,包下新建一个ServiceGenerator类,里面的代码如下:

package com.llw.imagediscerndemo.network;import java.util.concurrent.TimeUnit;import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;/*** 接口地址管理** @author llw*/
public class ServiceGenerator {/*** 默认地址*/public static String BASE_URL = "https://aip.baidubce.com";/*** 创建服务  参数就是API服务** @param serviceClass 服务接口* @param <T>          泛型规范* @return api接口服务*/public static <T> T createService(Class<T> serviceClass) {//创建OkHttpClient构建器对象OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();//设置请求超时的时间,这里是10秒okHttpClientBuilder.connectTimeout(20000, TimeUnit.MILLISECONDS);//消息拦截器  因为有时候接口不同在排错的时候 需要先从接口的响应中做分析。利用了消息拦截器可以清楚的看到接口返回的所有内容HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();//setlevel用来设置日志打印的级别,共包括了四个级别:NONE,BASIC,HEADER,BODY//BASEIC:请求/响应行//HEADER:请求/响应行 + 头//BODY:请求/响应航 + 头 + 体httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);//为OkHttp添加消息拦截器okHttpClientBuilder.addInterceptor(httpLoggingInterceptor);//在Retrofit中设置httpclient//设置地址  就是上面的固定地址,如果你是本地访问的话,可以拼接上端口号  例如 +":8080"Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL)//用Gson把服务端返回的json数据解析成实体.addConverterFactory(GsonConverterFactory.create())//放入OKHttp,之前说过retrofit是对OkHttp的进一步封装.client(okHttpClientBuilder.build()).build();//返回这个创建好的API服务return retrofit.create(serviceClass);}}

很简单的代码,也都是网络上常见的,OkHttp + Retrofit。

这里面的默认地址 https://aip.baidubce.com是图像识别API的固定地址,后面的有变化的,通过接口来配置。

然后再写一个NetCallBack类,用来处理Retrofit的返回,里面的代码如下:

package com.llw.imagediscerndemo.network;import android.util.Log;import com.google.gson.Gson;import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;/*** 网络请求回调** @param <T>*/
public abstract class NetCallBack<T> implements Callback<T> {//这里实现了retrofit2.Callback//访问成功回调@Overridepublic void onResponse(Call<T> call, Response<T> response) {//数据返回if (response != null && response.body() != null && response.isSuccessful()) {onSuccess(call, response);} else {onFailed(response.raw().toString());}}//访问失败回调@Overridepublic void onFailure(Call<T> call, Throwable t) {Log.d("data str", t.toString());onFailed(t.toString());}//数据返回public abstract void onSuccess(Call<T> call, Response<T> response);//失败异常public abstract void onFailed(String errorStr);}

那么这样简单的网络框架写好了。

四、添加请求API接口

百度的图像识别,首先要完成鉴权认证,拿到一个Token,然后通过这个Token再去请求图像识别的API接口才行,所以要完成两步操作。先来看第一步
鉴权的地址如下:

https://aip.baidubce.com/oauth/2.0/token

这里面需要再带三个Url参数,使用Post请求方式。

grant_type: 必须参数,固定为client_credentials;
client_id: 必须参数,应用的API Key;
client_secret: 必须参数,应用的Secret Key

返回的是一串JSON字符串,如下:

{"refresh_token": "25.b55fe1d287227ca97aab219bb249b8ab.315360000.1798284651.282335-8574074","expires_in": 2592000,"scope": "public wise_adapt","session_key": "9mzdDZXu3dENdFZQurfg0Vz8slgSgvvOAUebNFzyzcpQ5EnbxbF+hfG9DQkpUVQdh4p6HbQcAiz5RmuBAja1JJGgIdJI","access_token": "24.6c5e1ff107f0e8bcef8c46d3424a0e78.2592000.1485516651.282335-8574074","session_secret": "dfac94a3489fe9fca7c3221cbf7525ff"
}

当请求和返回都确定之后,我们就可以构建请求实体和返回实体了。
而请求实体都是放在Url中的,因此不需要通过实体来构建,直接传参数就好。

在com.llw.imagediscerndemo包下新建一个model包,包下新建一个GetTokenResponse类,里面的代码如下:

package com.llw.imagediscerndemo.model;/*** 获取鉴权认证Token响应实体*/
public class GetTokenResponse {/*** refresh_token : 25.b55fe1d287227ca97aab219bb249b8ab.315360000.1798284651.282335-8574074* expires_in : 2592000* scope : public wise_adapt* session_key : 9mzdDZXu3dENdFZQurfg0Vz8slgSgvvOAUebNFzyzcpQ5EnbxbF+hfG9DQkpUVQdh4p6HbQcAiz5RmuBAja1JJGgIdJI* access_token : 24.6c5e1ff107f0e8bcef8c46d3424a0e78.2592000.1485516651.282335-8574074* session_secret : dfac94a3489fe9fca7c3221cbf7525ff*/private String refresh_token;private long expires_in;private String scope;private String session_key;private String access_token;private String session_secret;public String getRefresh_token() {return refresh_token;}public void setRefresh_token(String refresh_token) {this.refresh_token = refresh_token;}public long getExpires_in() {return expires_in;}public void setExpires_in(long expires_in) {this.expires_in = expires_in;}public String getScope() {return scope;}public void setScope(String scope) {this.scope = scope;}public String getSession_key() {return session_key;}public void setSession_key(String session_key) {this.session_key = session_key;}public String getAccess_token() {return access_token;}public void setAccess_token(String access_token) {this.access_token = access_token;}public String getSession_secret() {return session_secret;}public void setSession_secret(String session_secret) {this.session_secret = session_secret;}
}

这是通过刚才的返回数据生成的实体Bean,当网络请求返回数据后通过Retrofit会解析成这个返回实体。
下面添加接口,在network包下新建一个ApiService接口,里面的代码如下:

package com.llw.imagediscerndemo.network;import com.llw.imagediscerndemo.model.GetTokenResponse;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.Headers;
import retrofit2.http.POST;/*** API服务** @author llw* @date 2021/4/1 17:48*/
public interface ApiService {/*** 获取鉴权认证Token* @param grant_type 类型* @param client_id API Key* @param client_secret Secret Key* @return GetTokenResponse*/@FormUrlEncoded@POST("/oauth/2.0/token")Call<GetTokenResponse> getToken(@Field("grant_type") String grant_type,@Field("client_id") String client_id,@Field("client_secret") String client_secret);}

这里还有一个接口呢,那就是图像识别接口。地址为:

https://aip.baidubce.com/rest/2.0/image-classify/v2/advanced_general

找个接口比较特殊,官方文档的描述如下:
在这里插入图片描述
看你是否理解了。返回数据如下:

{"log_id": 327863200205075661,"result_num": 5,"result": [{"score": 0.967622,"root": "公众人物","baike_info": {"baike_url": "http://baike.baidu.com/item/%E6%96%B0%E5%9E%A3%E7%BB%93%E8%A1%A3/8035884","image_url": "http://imgsrc.baidu.com/baike/pic/item/91ef76c6a7efce1b27893518a451f3deb58f6546.jpg","description": "新垣结衣(Aragaki Yui),1988年6月11日出生于冲绳县那霸市。日本女演员、歌手、模特。毕业于日出高中。2001年,参加《nicola》模特比赛并获得最优秀奖。2005年,因出演现代剧《涩谷15》而作为演员出道。2006年,参演校园剧《我的老大,我的英雄》;同年,她还出版了个人首本写真集《水漾青春》。2007年,她从日出高校毕业后开始专注于演艺发展,并发表个人首张音乐专辑《天空》;同年,新垣结衣还主演了爱情片《恋空》,而她也凭借该片获得了多个电影新人奖项。2010年,主演爱情片《花水木》。2011年,主演都市剧《全开女孩》。2012年,相继参演现代剧《Legal High》、剧情片《剧场版新参者:麒麟之翼》。2013年,主演都市剧《飞翔情报室》。2014年,她主演了剧情片《黎明的沙耶》。2016年,主演爱情喜剧《逃避虽可耻但有用》,并凭借该剧获得了多个电视剧女主角奖项。2017年,主演爱情片《恋爱回旋》,凭借该片获得第60届蓝丝带奖最佳女主角;同年11月,她还凭借医疗剧《Code Blue 3》获得第94届日剧学院赏最佳女配角。"},"keyword": "新垣结衣"},{"score": 0.716067,"root": "人物-人物特写","keyword": "头发"},{"score": 0.421281,"root": "商品-穿戴","keyword": "围巾"},{"score": 0.22347,"root": "商品-五金","keyword": "拉链"},{"score": 0.028031,"root": "商品-穿戴","keyword": "脖套"}]
}

通过这个返回示例数据,可以生成一个实体Bean。在model包下新建一个GetDiscernResultResponse类,代码如下:

package com.llw.imagediscerndemo.model;import java.util.List;/*** 获取识别结果响应实体*/
public class GetDiscernResultResponse {/*** log_id : 327863200205075661* result_num : 5* result : [{"score":0.967622,"root":"公众人物","baike_info":{"baike_url":"http://baike.baidu.com/item/%E6%96%B0%E5%9E%A3%E7%BB%93%E8%A1%A3/8035884","image_url":"http://imgsrc.baidu.com/baike/pic/item/91ef76c6a7efce1b27893518a451f3deb58f6546.jpg","description":"新垣结衣(Aragaki Yui),1988年6月11日出生于冲绳县那霸市。日本女演员、歌手、模特。毕业于日出高中。2001年,参加《nicola》模特比赛并获得最优秀奖。2005年,因出演现代剧《涩谷15》而作为演员出道。2006年,参演校园剧《我的老大,我的英雄》;同年,她还出版了个人首本写真集《水漾青春》。2007年,她从日出高校毕业后开始专注于演艺发展,并发表个人首张音乐专辑《天空》;同年,新垣结衣还主演了爱情片《恋空》,而她也凭借该片获得了多个电影新人奖项。2010年,主演爱情片《花水木》。2011年,主演都市剧《全开女孩》。2012年,相继参演现代剧《Legal High》、剧情片《剧场版新参者:麒麟之翼》。2013年,主演都市剧《飞翔情报室》。2014年,她主演了剧情片《黎明的沙耶》。2016年,主演爱情喜剧《逃避虽可耻但有用》,并凭借该剧获得了多个电视剧女主角奖项。2017年,主演爱情片《恋爱回旋》,凭借该片获得第60届蓝丝带奖最佳女主角;同年11月,她还凭借医疗剧《Code Blue 3》获得第94届日剧学院赏最佳女配角。"},"keyword":"新垣结衣"},{"score":0.716067,"root":"人物-人物特写","keyword":"头发"},{"score":0.421281,"root":"商品-穿戴","keyword":"围巾"},{"score":0.22347,"root":"商品-五金","keyword":"拉链"},{"score":0.028031,"root":"商品-穿戴","keyword":"脖套"}]*/private long log_id;private int result_num;private List<ResultBean> result;public long getLog_id() {return log_id;}public void setLog_id(long log_id) {this.log_id = log_id;}public int getResult_num() {return result_num;}public void setResult_num(int result_num) {this.result_num = result_num;}public List<ResultBean> getResult() {return result;}public void setResult(List<ResultBean> result) {this.result = result;}public static class ResultBean {/*** score : 0.967622* root : 公众人物* baike_info : {"baike_url":"http://baike.baidu.com/item/%E6%96%B0%E5%9E%A3%E7%BB%93%E8%A1%A3/8035884","image_url":"http://imgsrc.baidu.com/baike/pic/item/91ef76c6a7efce1b27893518a451f3deb58f6546.jpg","description":"新垣结衣(Aragaki Yui),1988年6月11日出生于冲绳县那霸市。日本女演员、歌手、模特。毕业于日出高中。2001年,参加《nicola》模特比赛并获得最优秀奖。2005年,因出演现代剧《涩谷15》而作为演员出道。2006年,参演校园剧《我的老大,我的英雄》;同年,她还出版了个人首本写真集《水漾青春》。2007年,她从日出高校毕业后开始专注于演艺发展,并发表个人首张音乐专辑《天空》;同年,新垣结衣还主演了爱情片《恋空》,而她也凭借该片获得了多个电影新人奖项。2010年,主演爱情片《花水木》。2011年,主演都市剧《全开女孩》。2012年,相继参演现代剧《Legal High》、剧情片《剧场版新参者:麒麟之翼》。2013年,主演都市剧《飞翔情报室》。2014年,她主演了剧情片《黎明的沙耶》。2016年,主演爱情喜剧《逃避虽可耻但有用》,并凭借该剧获得了多个电视剧女主角奖项。2017年,主演爱情片《恋爱回旋》,凭借该片获得第60届蓝丝带奖最佳女主角;同年11月,她还凭借医疗剧《Code Blue 3》获得第94届日剧学院赏最佳女配角。"}* keyword : 新垣结衣*/private double score;private String root;private BaikeInfoBean baike_info;private String keyword;public double getScore() {return score;}public void setScore(double score) {this.score = score;}public String getRoot() {return root;}public void setRoot(String root) {this.root = root;}public BaikeInfoBean getBaike_info() {return baike_info;}public void setBaike_info(BaikeInfoBean baike_info) {this.baike_info = baike_info;}public String getKeyword() {return keyword;}public void setKeyword(String keyword) {this.keyword = keyword;}public static class BaikeInfoBean {/*** baike_url : http://baike.baidu.com/item/%E6%96%B0%E5%9E%A3%E7%BB%93%E8%A1%A3/8035884* image_url : http://imgsrc.baidu.com/baike/pic/item/91ef76c6a7efce1b27893518a451f3deb58f6546.jpg* description : 新垣结衣(Aragaki Yui),1988年6月11日出生于冲绳县那霸市。日本女演员、歌手、模特。毕业于日出高中。2001年,参加《nicola》模特比赛并获得最优秀奖。2005年,因出演现代剧《涩谷15》而作为演员出道。2006年,参演校园剧《我的老大,我的英雄》;同年,她还出版了个人首本写真集《水漾青春》。2007年,她从日出高校毕业后开始专注于演艺发展,并发表个人首张音乐专辑《天空》;同年,新垣结衣还主演了爱情片《恋空》,而她也凭借该片获得了多个电影新人奖项。2010年,主演爱情片《花水木》。2011年,主演都市剧《全开女孩》。2012年,相继参演现代剧《Legal High》、剧情片《剧场版新参者:麒麟之翼》。2013年,主演都市剧《飞翔情报室》。2014年,她主演了剧情片《黎明的沙耶》。2016年,主演爱情喜剧《逃避虽可耻但有用》,并凭借该剧获得了多个电视剧女主角奖项。2017年,主演爱情片《恋爱回旋》,凭借该片获得第60届蓝丝带奖最佳女主角;同年11月,她还凭借医疗剧《Code Blue 3》获得第94届日剧学院赏最佳女配角。*/private String baike_url;private String image_url;private String description;public String getBaike_url() {return baike_url;}public void setBaike_url(String baike_url) {this.baike_url = baike_url;}public String getImage_url() {return image_url;}public void setImage_url(String image_url) {this.image_url = image_url;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}}}
}

下面在ApiService中添加接口。

	/*** 获取图像识别结果* @param accessToken 获取鉴权认证Token* @param url 网络图片Url* @return JsonObject*/@FormUrlEncoded@POST("/rest/2.0/image-classify/v2/advanced_general")@Headers("Content-Type:application/x-www-form-urlencoded; charset=utf-8")Call<GetDiscernResultResponse> getDiscernResult(@Field("access_token") String accessToken,@Field("url") String url);

我这个写法和官方说明好像有点不同,我没有用Body。选择直接把数据放在Url中请求。

现在万事具备了,下面先来获取鉴权认证Token。

五、获取鉴权认证Token

打开MainActivity,添加如下代码:

	private static final String TAG = "MainActivity";/*** Api服务*/private ApiService service;/*** 鉴权Toeken*/private String accessToken;

然后在onCreate方法中对ApiService进行实例化。

	service = ServiceGenerator.createService(ApiService.class);

新增如下方法获取Token,

	/*** 访问API获取接口*/private void requestApiGetToken() {String grantType = "client_credentials";String apiKey = "TjPChftoEyBq7Nzm65KNerqr";String apiSecret = "eTph4jO95te6R3G2aecktGMbkieOv7rS";service.getToken(grantType, apiKey, apiSecret).enqueue(new NetCallBack<GetTokenResponse>() {@Overridepublic void onSuccess(Call<GetTokenResponse> call, Response<GetTokenResponse> response) {if (response.body() != null) {//鉴权TokenaccessToken = response.body().getAccess_token();Log.d(TAG,accessToken);}}@Overridepublic void onFailed(String errorStr) {Log.e(TAG, "获取Token失败,失败原因:" + errorStr);accessToken = null;}});}

然后在onCreate中调用它。
在这里插入图片描述
运行一下,看一下控制台是否打印了日志。
在这里插入图片描述
你可以看到这个Token还是挺长的。对于这个Token,是有有效期的,基本上是一个月,所以你可以不用每次使用时都重新请求这个接口去获取Token,这里可以用缓存来解决这个问题。

说一下逻辑,当通过接口拿到Token时保存Token、Token获取时间、Token有效时长三个数据到缓存中,每一次使用前进行一次判断,首先是判断有没有Token,其次是判断Token有没有过期。那么按照这个思路我们就可以这么写代码了。

这里为了方便我在com.llw.imagediscerndemo包下新建一个util包,包下新建一个Constant类,里面的代码如下:

package com.llw.imagediscerndemo.util;/*** 全局常量*/
public class 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";
}

这三个值,我刚才也说明过了。下面写一个缓存的SPUtils工具类,里面的代码如下:

package com.llw.imagediscerndemo.util;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();}}

也是很简单的代码,相信你一眼就看明白了,下面就该在MainActivity中去处理缓存数据的存取了。
首先是放缓存,这当然是在请求接口的成功数据返回中放,修改onSuccess中的代码,如下。

					@Overridepublic void onSuccess(Call<GetTokenResponse> call, Response<GetTokenResponse> response) {if (response.body() != null) {//鉴权TokenaccessToken = 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);}}

然后写一个判断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的时间,然后获取Token的有效时长,再获取当前系统时间,然后通过当前系统时间减去获得Token的时间,得到的值再与Token有效期做比较,如果大于等于有效期则说明Token过期,返回true,否则返回false。

下面再写一个方法,用来获取Token,同时将我们之前写的代码给串起来。

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

  首先获取缓存中的Token,应用第一次进入肯定是没有值的,没有值则返回默认值null,那么token变量此时为null,那么就会通过接口去获取Token,当获取之后存入缓存,再次进入时,就不是null了,那么就会通过isTokenExpired()方法来判断Token是否过期,过期了也是通过网络请求重新拿到Token放入缓存,如果没有过期则直接使用缓存中的Token,最后返回Token。这方法在onCreate中调用
在这里插入图片描述

六、网络图片Url识别

Token拿到以后我们来进行网络图片Url识别。先说一下思路,首先是通过网络图片url和Token去请求接口,然后获得返回值,此时要显示一个加载条,然后通过返回数据渲染列表,当数据显示在列表之后就完成了。

首先找一个网络图片Url,如下:

https://bce-baiyu.cdn.bcebos.com/14ce36d3d539b6004ef2e45fe050352ac65cb71e.jpeg

这个网络图片是一个水杯的图片,如下所示:
在这里插入图片描述
首先修改布局activity_main.xml,里面的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"tools:context=".MainActivity"><ImageViewandroid:id="@+id/iv_picture"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="centerCrop" /><LinearLayoutandroid:layout_marginBottom="16dp"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:gravity="center"><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="IdentifyWebPictures"android:text="识别网络图片" /></LinearLayout><ProgressBarandroid:visibility="gone"android:id="@+id/pb_loading"android:layout_centerInParent="true"android:layout_width="60dp"android:layout_height="60dp"/></RelativeLayout>

然后在MainActivity中写入IdentifyWebPictures方法,代码如下:

	/*** 识别网络图片** @param view*/public void IdentifyWebPictures(View view) {}

首先创建对象

	/*** 显示图片*/private ImageView ivPicture;/*** 进度条*/private ProgressBar pbLoading;

然后在onCreate中绑定xml中的控件id。

		ivPicture = findViewById(R.id.iv_picture);pbLoading = findViewById(R.id.pb_loading);

下面来修改IdentifyWebPictures()方法的代码,如下:

	public void IdentifyWebPictures(View view) {pbLoading.setVisibility(View.VISIBLE);if (accessToken == null) {showMsg("获取AccessToken到null");return;}String imgUrl = "https://bce-baiyu.cdn.bcebos.com/14ce36d3d539b6004ef2e45fe050352ac65cb71e.jpeg";//显示图片Glide.with(this).load(imgUrl).into(ivPicture);showMsg("图像识别中");service.getDiscernResult(accessToken, imgUrl).enqueue(new NetCallBack<GetDiscernResultResponse>() {@Overridepublic void onSuccess(Call<GetDiscernResultResponse> call, Response<GetDiscernResultResponse> response) {List<GetDiscernResultResponse.ResultBean> result = response.body() != null ? response.body().getResult() : null;if (result != null && result.size() > 0) {//显示识别结果showDiscernResult(result);} else {pbLoading.setVisibility(View.GONE);showMsg("未获得相应的识别结果");}}@Overridepublic void onFailed(String errorStr) {pbLoading.setVisibility(View.GONE);Log.e(TAG, "图像识别失败,失败原因:" + errorStr);}});}

当点击按钮时,显示进度条,然后通过getAccessToken()方法获取Token,这里获取Token,有两种方式,通过网络获取和本地缓存获取,之后显示网络图片在ImageView控件中,Toast提示一下,之后请求的成功和失败的回调了,在成功的回调中先判断数据是否为空,不为空再通过showDiscernResult()方法去显示数据,下面写这个方法。

	/*** 显示识别的结果列表** @param result*/private void showDiscernResult(List<GetDiscernResultResponse.ResultBean> result) {}

showMsg方法:

	/*** Toast提示* @param msg 内容*/private void showMsg(String msg){Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();}

为了不占用屏幕的控件,我这里打算用一个弹窗来显示数据,弹窗里面是一个列表,列表通过item布局构建,数据由刚才的方法传递进来,我们一步一步来写,首先构建item的布局。在layout下新建一个item_result_rv.xml,里面的代码如下:

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

item布局有了,下面构建弹窗的布局代码,在layout下新建一个 dialog_bottom.xml,里面的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#EEE"android:orientation="vertical"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:background="#FFF"android:gravity="center"android:padding="16dp"android:text="识别结果" /><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_result"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="1dp" /></LinearLayout>

下面布局都有了,先构建这个列表的适配器,在com.llw.imagediscerndemo下新建一个adapter包,包下新建一个DiscernResultAdapter类,里面的代码如下:

package com.llw.imagediscerndemo.adapter;import androidx.annotation.Nullable;import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.llw.imagediscerndemo.R;
import com.llw.imagediscerndemo.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()));}
}

万事具备,只差显示数据了,下面进入MainActivity中,首先创建对象

	/*** 底部弹窗*/private BottomSheetDialog bottomSheetDialog;/*** 弹窗视图*/private View bottomView;

然后在onCreate中实例化,

	bottomSheetDialog = new BottomSheetDialog(this);bottomView = getLayoutInflater().inflate(R.layout.dialog_bottom, null);

然后修改showDiscernResult方法,代码如下:

	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);//隐藏加载pbLoading.setVisibility(View.GONE);//显示弹窗bottomSheetDialog.show();}

下面运行一下:
在这里插入图片描述
可以看到结果识别到了。

七、相册图片识别

在实际应用中,更多是采用本地的图片进行识别,通常是选择拍照的图片或者打开相册获取图片,先来看看通过相册获取图片进行图像识别。要实现这个功能首先要改一下接口,加一个image参数。

在这里插入图片描述
然后修改ImageDiscern方法。

	/*** 图像识别请求** @param token       token* @param imageBase64 图片Base64* @param imgUrl      网络图片Url*/private void ImageDiscern(String token, String imageBase64, String imgUrl) {service.getDiscernResult(token, imageBase64, imgUrl).enqueue(new NetCallBack<GetDiscernResultResponse>() {@Overridepublic void onSuccess(Call<GetDiscernResultResponse> call, Response<GetDiscernResultResponse> response) {List<GetDiscernResultResponse.ResultBean> result = response.body() != null ? response.body().getResult() : null;if (result != null && result.size() > 0) {//显示识别结果showDiscernResult(result);} else {pbLoading.setVisibility(View.GONE);showMsg("未获得相应的识别结果");}}@Overridepublic void onFailed(String errorStr) {pbLoading.setVisibility(View.GONE);Log.e(TAG, "图像识别失败,失败原因:" + errorStr);}});}

这个方法接收三个参数,Token、ImageBase64、图片Url。ImageBase64和图片Url只能二选一。选其中一个另一个则传null。比如之前的通过网络图片Url识别。
在这里插入图片描述
接口的相关方法都改好了,下面来写打开相册的方法。Android6.0以后读写文件都属于危险权限,因此需要动态请求。在MainActivity中声明:

	private RxPermissions rxPermissions;

然后在onCreate中实例化

	rxPermissions = new RxPermissions(this);

下面修改布局,在之前的按钮后面再加一个按钮

	<Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="IdentifyAlbumPictures"android:text="识别相册图片" />

然后在MainActivity中增加IdentifyAlbumPictures方法,代码如下:

	/*** 识别相册图片** @param view*/@SuppressLint("CheckResult")public void IdentifyAlbumPictures(View view) {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();}}

当获取到权限之后通过openAlbum()方法打开相册,openAlbum方法代码如下:

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

这里定义了一个请求码

	/*** 打开相册*/private static final int OPEN_ALBUM_CODE = 100;

打开相册之后就要返回了,重写 onActivityResult方法

	@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);if (resultCode == RESULT_OK) {pbLoading.setVisibility(View.VISIBLE);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("什么都没有");}}

相册返回之后先拿到图片的Uri,然后通过Uri得到图片的路径,然后通过这个路径将图片转成字节,再转Base64,首先来看localImageDiscern方法。代码如下:

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

这里面有两个工具类FileUtil和Base64Util,代码如下:

FileUtil.java

package com.llw.imagediscerndemo.util;import java.io.*;/*** 文件读取工具类*/
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.java

package com.llw.imagediscerndemo.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();}
}

都放在util包下,那么现在就可以直接运行了。
在这里插入图片描述
通过这个图可以看到第一次识别失败了,第二次成功了,后续的都会成功,不知道是什么奇葩原因,有知道的记得告诉我啊。

八、拍照图片识别

首先还在在activity_main.xml中识别相册图片按钮的后面加一个识别拍照图片按钮,如下:

		<Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="IdentifyTakePhotoImage"android:text="识别拍照图片" />

在MainActivity中增加IdentifyTakePhotoImage方法,代码如下:

	/*** 识别拍照图片** @param view*/@SuppressLint("CheckResult")public void IdentifyTakePhotoImage(View view) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {rxPermissions.request(Manifest.permission.CAMERA).subscribe(grant -> {if (grant) {//获得权限turnOnCamera();} else {showMsg("未获取到权限");}});} else {turnOnCamera();}}

来看看turnOnCamera方法。在此之前创建变量,用来保存拍照后的图片

	private File outputImage;

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.imagediscerndemo.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);}

这里同样配置了一个打开相机的请求码

	/*** 打开相机*/private static final int TAKE_PHOTO_CODE = 101;

下面进入到onActivityResult方法,加一个条件分支。
在这里插入图片描述
通过这个图片保存文件得到图片的路径,然后通过localImageDiscern()方法对这个路径下的文件进行处理,和打开相册之后拿到路径之后调用的是同一个方法。下面来运行一下:
在这里插入图片描述
嗯,那么到这里整个Demo就写完了,是不是还挺简单的,只要思路明确再加上细节处理的到位,任何的功能都不在话下,对吧。

九、源码

考虑到有时候GitHub会抽风,因此加上了CSDN的资源下载地址,我设置的0积分,随便下载。

如果你用的好,不妨给我的源码或者资源点个赞给个好评啥的。

GitHub源码地址:ImageDiscernDemo

CSDN资源地址:ImageDiscernDemo.rar

扫码下载APK使用:
在这里插入图片描述

我是初学者-Study,山高水长,后会有期~

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

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

相关文章

微信小程序,图像识别源码

目录 前言百度端配置信息小程序中代码&#xff1a;结语智能识图小程序源码下载路径&#xff1a;https://pan.baidu.com/s/1OGE7vhogS7L7nn0JIFPVWw 提取码&#xff1a;8ze9 前言 基于近期的工作内容关系&#xff0c;在查询一些资料的同时&#xff0c;在微信小程序端集成了图像…

图像识别小程序(含源码)【推荐】

目录 前言百度端配置信息小程序中代码&#xff1a;结语智能识图小程序源码下载路径&#xff1a;https://pan.baidu.com/s/1OGE7vhogS7L7nn0JIFPVWw 提取码&#xff1a;8ze9 前言 基于近期的工作内容关系&#xff0c;在查询一些资料的同时&#xff0c;在微信小程序端集成了图像…

(数学实验)Matlab实现猜数小游戏(增加了错误输入的判断)

刚开始做的时候不知道matlab没有自减运算&#xff0c;在网上查了很久资料&#xff0c;都没发现有对猜数游戏加错误输入判断的&#xff0c;经过多次试错&#xff0c;我弄出来了有判断的程序&#xff0c;在这里分享一下。 文章目录 前言一、问题描述二、解题思路 1.for循环2.whil…

使用Python为二年级的学生批量生成数学题

文章目录 一.使用Python为二年级的学生批量生成数学题1.1 背景 二.解决思路及其代码三.排版及其打印四.本文源码 一.使用Python为二年级的学生批量生成数学题 1.1 背景 我妹妹今年上二年级&#xff0c;她的老师今天给他们布置了一项作业&#xff1a; 从今天起到开学&#xff…

Fdog系列(一):思来想去,不如写一个聊天软件,那就从仿QQ注册页面开始吧。

文章目录 一.前言1. 基础布局2. 自动切换图片3. 添加内容4. 自动缩放&#xff0c;控件的显示和隐藏5.响应用户输入操作 所有文章源码已整体打包上传至github&#xff0c;求星星&#xff01; 一.前言 两年的大学生活马上就要结束了&#xff0c;马上面临实习&#xff0c;突然心…

基于Python的网络拓扑图绘制

最近写论文画了许多图&#xff0c;在这里记录一些。当然&#xff0c;如果仅仅是展示性图片的话也可以使用visio&#xff0c;但是这里我仍然想探究一下如何使用pyhon画出美观的网络拓扑图。 一、画出网络拓扑图 给出邻接矩阵&#xff0c;画出网络的拓扑图&#xff1a; import…

认识网络、几种常用的网络拓扑图

交换协议&#xff1a; VLAN技术&#xff1a;虚拟局域网 STP技术&#xff1a;生成树协议 VRRP技术&#xff1a;虚拟路由冗余协议 VPN&#xff1a;虚拟专用网络 名词解释 路由协议&#xff1a;http、HTTPS、tcp、ip 静态路由配置 OSPF协议 RIP协议 ACL访问控制 什么是网络&…

快速读懂网络拓扑图

快速读懂网络拓扑图 几重常见的网络拓扑总线型拓扑简介优点缺点 环型拓扑简介优点缺点 星型拓扑简介优点缺点 网络层级机构节点结点链路通路 不同的连接线代表什么意思&#xff1f;不同颜色、粗细的直线代表什么意思&#xff1f;闪电线-串行链路 几重常见的网络拓扑 总线型拓扑…

盘点5款常用的网络拓扑图制作工具

网络拓扑能直观明了的展示网络中各网元之间的关系&#xff0c;极大方便运维人员对网络进行优化配置、故障排查等操作。那么这个专业性比较强的拓扑图&#xff0c;要用什么工具把它轻松&#xff0c;简便的画出来呢&#xff1f;现对市场5款主流的拓扑制作软件进行介绍&#xff1a…

网络拓扑图怎么画 详细教程

大数据时代&#xff0c;如何更好地去运营、呈现数据&#xff0c;并从其中发掘出更多信息成为了人们探索的方向。网络拓扑图就是一种非常有用的信息化图表&#xff0c;这种网状关系呈现出来的利器可以使我们把想要传递的信息更加清晰、有逻辑的呈现在别人的眼前。 1. 什么是网络…

网络拓扑图

转载自&#xff1a;https://blog.csdn.net/weixin_40792878/article/details/82555594 什么是拓扑结构?   首先我们来解释一下拓扑的含义&#xff0c;所谓“拓扑”就是把实体抽象成与其大小、形状无关的“点”&#xff0c;而把连接实体的线路抽象成“线”&#xff0c;进而…

【新手】网络拓扑图要这样画

网络拓扑设计分为单核心和双核心两种 1. 单核心网络拓扑设计&#xff08;如图&#xff09; &#xff08;上图写的是路由器连接外网&#xff0c;也可以连接公司别的分支机构&#xff0c;比如现在是上海分公司&#xff0c;也可以用路由器和北京分公司相连&#xff0c;当然要借助…

关于网络拓扑图,你想知道的都在这

这篇文章&#xff0c;我将集中回答以下这四个问题&#xff1a; 网络拓扑图的定义网络拓扑图的分类网络拓扑图的设计规范网络拓扑图的绘制步骤 一、网络拓扑图的定义 在认识网络拓扑图前&#xff0c;我们先来了解下网络拓扑结构。 所谓网络拓扑结构&#xff0c;是指用传输媒…

作为程序员, 我如何使用ChatGPT来帮我写代码

从快速学习到调试程序&#xff0c; 甚至将繁琐的工作自动化。 我们每个人都渴望成功&#xff0c; 而获得成功的最有效的方式之一就是&#xff0c; 在尽可能短的时间内&#xff0c; 解决尽可能多的人的问题&#xff0c; 特别是大家都有的问题。 我们可以观察身边优秀的产品&…

只知道ChatGPT?这些AI工具同样值得收藏

B站|公众号&#xff1a;啥都会一点的研究生 人工智能革命带来了许多能够提高生产力和转变工作方式的工具&#xff0c;本期将重点介绍音频、视频、设计以及图像和数据清理中的顶级 AI 工具。 音视频类AI工具&#xff1a; VoicePen AI https://voicepen.ai&#xff1a;该工具可…

行情数据接口-美股版

一、美股历史数据API 1.数据列表 待完善补充 下载美股公司列表&#xff0c;分别是纳斯达克&#xff0c;纽交所&#xff0c;美国证券交易所&#xff0c;三个CSV文件 下载地址&#xff0c;纳斯达克官网&#xff1a;Company List: NASDAQ, NYSE, & AMEX Companies http:/…

彩票系统

**用c#简单实现彩票系统**首先它能实现哪些功能&#xff1a; 1.红球号码不能超过1~33&#xff0c;彩票号码不能重复&#xff1b; 2.随机产生一注彩票&#xff1b; 3.设奖及中奖 奖级表&#xff1a; 奖级 中奖说明 单注奖金 &#xff08;前面数字代表红球数量、后面数字代表篮…

大乐透机选号码生产器

1、蓝色球5个&#xff0c;1~35 不能重复 2、红色球2个&#xff0c;1~12 不能重复 代码如下&#xff1a; public class demo {public static void main(String[] args) {System.out.println("现在时间为" LocalDateTime.now()"&#xff0c;今天体彩大乐透的开…

如何理解单目视觉slam中尺寸漂移问题

最近学习了ORB_SLAM2中计算sim3变换&#xff0c;其中对什么是尺寸因子和尺寸漂移问题还存在一些知识盲区&#xff0c;因此查看了一些别人写的文章以及自己的理解整理了一下这个问题。&#xff08;感觉自己的理解还不是非常的准确&#xff0c;如果有朋友发现我理解不对的地方&am…

微软推出 Hierarchical Transformer 实现更高准确率的语音评测

对于语言学习者来说&#xff0c;练习发音并获得及时准确的反馈&#xff0c;是提高口语水平的重要环节。多年来&#xff0c;微软一直深耕基于 Azure 认知服务的语音功能&#xff0c;不断优化语音评测[1]功能的底层技术&#xff0c;从准确率、流畅度、完整性和语音语调等方面&…