腾讯OCR光学识别官网介绍:(https://cloud.tencent.com/document/product/866/36214)
注:全程使用java开发
界面需求(需要两个界面):
第一个界面:需要两个按钮。一个用来触发拍照,另外一个用来确定识别的效果,触发识别,需要一个ImageView用来显示图片
第二个界面:
七个TextView,用来标识名片字段,
七个EditText。存放识别信息
界面效果如下:
界面1
代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:background="@drawable/btn"
android:id="@+id/btn_original"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="拍照/重拍"
android:textColor="@color/black"
android:textSize="17sp"/>
<Button
android:id="@+id/btn_ocr"
android:background="@drawable/btn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="确定"
android:textColor="@color/black"
android:textSize="17sp"/>
</LinearLayout>
<ImageView
android:id="@+id/iv_photo"
android:layout_width="match_parent"
android:layout_height="360dp"
android:scaleType="fitCenter"/>
</LinearLayout>
界面2
代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/allinfo"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/lin_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="姓名"
android:textSize="17sp"/>
<EditText
android:id="@+id/Last_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="17sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="職務"
android:textSize="17sp"/>
<EditText
android:id="@+id/Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="17sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="公司"
android:textSize="17sp"/>
<EditText
android:id="@+id/Account_Name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="17sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="地址"
android:textSize="17sp"/>
<EditText
android:id="@+id/Mailing_Country"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="17sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="郵箱"
android:textSize="17sp"/>
<EditText
android:id="@+id/Email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="17sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="手機"
android:textSize="17sp"/>
<EditText
android:id="@+id/Mobile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="17sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="電話"
android:textSize="17sp"/>
<EditText
android:id="@+id/Phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="17sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="主要生產產品"
android:textSize="17sp"/>
<EditText
android:id="@+id/edit_field20"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="17sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="所屬產業"
android:textSize="17sp"/>
<Spinner
android:id="@+id/sp_Industry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:spinnerMode="dialog"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="引合來源"
android:textSize="17sp"/>
<Spinner
android:id="@+id/sp_Lead_Source"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:spinnerMode="dialog"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="線索狀態"
android:textSize="17sp"/>
<Spinner
android:id="@+id/sp_Lead_Status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:spinnerMode="dialog"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="客戶等級"
android:textSize="17sp"/>
<Spinner
android:id="@+id/sp_Rating"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:spinnerMode="dialog"/>
<View
android:layout_width="16dp"
android:layout_height="30dp" />
<Button
android:id="@+id/btn_sub"
android:background="@drawable/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="同步至CRM"/>
<View
android:layout_width="16dp"
android:layout_height="15dp" />
</LinearLayout>
</ScrollView>
</LinearLayout>
在模块build.gradle中添加包
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'com.tencentcloudapi:tencentcloud-sdk-java-ocr:3.1.777'
implementation 'xerces:xercesImpl:2.12.0'
}
建立BusinessCardOCR,用于识别照片文件,代码如下
package com.example.ocr.util;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.ocr.v20181119.OcrClient;
import com.tencentcloudapi.ocr.v20181119.models.*;
public class BusinessCardOCR {
public String ocrJson(String encodedImage){
try{
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
Credential cred = new Credential("秘钥ID", "秘钥Key");
// 实例化一个http选项,可选的,没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("ocr.tencentcloudapi.com");
// 实例化一个client选项,可选的,没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
OcrClient client = new OcrClient(cred, "ap-shanghai", clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
BusinessCardOCRRequest req = new BusinessCardOCRRequest();
req.setImageBase64(encodedImage);
// 返回的resp是一个BusinessCardOCRResponse的实例,与请求对象对应
BusinessCardOCRResponse resp = client.BusinessCardOCR(req);
// 输出json格式的字符串回包
return BusinessCardOCRResponse.toJsonString(resp);
} catch (TencentCloudSDKException e) {
return e.toString();
}
}
}
在Credential cred = new Credential("秘钥ID", "秘钥Key");填写自己的秘钥
因为识别照片需要将照片转换为base64编码,故在函数中传入形参String,也就是base64编码,还需要返回一个String用来返回识别结果,识别的结果是json数据
在AndroidManifest.xml/manifest标签下面声明权限
<uses-permission android:name="android.permission.CAMERA" /> <!-- 录音 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- 存储卡读写 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <!-- 获取网络状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 互联网 -->
<uses-permission android:name="android.permission.INTERNET" />
权限声明代码:
//检查相机权限
private boolean checkCameraPermission() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED;
}
private void requestCameraPermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
// 显示权限说明对话框
// 可以使用一个对话框或其他方式向用户解释为什么需要相机权限,并在用户同意后请求权限
Toast.makeText(MainActivity.this,"未授權相機權限",Toast.LENGTH_SHORT).show();
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
takeOriginalPhoto();
} else {
// 相机权限被拒绝,可以显示一条消息或执行其他操作
Toast.makeText(MainActivity.this,"未授權相機權限",Toast.LENGTH_SHORT).show();
}
}
}
// 拍照时获取原始图片
private void takeOriginalPhoto() {
// Android10开始必须由系统自动分配路径,同时该方式也能自动刷新相册
ContentValues values = new ContentValues();
// 指定图片文件的名称
values.put(MediaStore.Images.Media.DISPLAY_NAME, "photo_" + DateUtil.getNowDateTime());
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");// 类型为图像
// 通过内容解析器插入一条外部内容的路径信息
mImageUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
launcherOriginal.launch(mImageUri);
}
}
说明,判断mImageUri也就是图片是不是空的,不是空的就是已经有图片,进行返回,并将图片放置在ImageView下面。声明一个意图,将识别的名片信息,包裹在意图中,并跳转到第二个界面
if (mImageUri != null){
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), mImageUri);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos);
byte[] imageBytes = baos.toByteArray();
baos.close();
bitmap.recycle();
String encodedImage = Base64.encodeToString(imageBytes, Base64.DEFAULT);
// 在单独的线程上执行网络请求
new Thread(() -> {
BusinessCardOCR businessCardOCR = new BusinessCardOCR();
String result = businessCardOCR.ocrJson(encodedImage);
// 判断是否是json的数据格式
isjson json = new isjson();
boolean ISJSON =json.json_Str(result);
if (ISJSON){
Intent intent = new Intent(this,sendDate.class);
Bundle bundle = new Bundle();
bundle.putString("result",result);
intent.putExtras(bundle);
startActivity(intent);
}else {
runOnUiThread(() ->{
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("未識別到名片資訊,請重新拍攝");
builder.setPositiveButton("確認",(dialog, which) -> {
dialog.dismiss();
});
AlertDialog alert = builder.create();
alert.show();
});
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
解析识别结果并填充至相应位置
注意,识别的结果比如电话有多个,姓名中可能有中文姓名和英文姓名,但我们只需要一个姓名,所以使用先进行全部解析
代码如下:
jsonObject = new JSONObject(request);
JSONArray businessCardInfos = jsonObject.getJSONArray("BusinessCardInfos");
System.out.println(request);
for (int i = 0; i < businessCardInfos.length(); i++) {
JSONObject info = businessCardInfos.getJSONObject(i);
String infoname = info.getString("Name");
String infoValue = info.getString("Value");
在代码中声明Hashsat<>
Set<String> ocrLast_name = new HashSet<>();
判断infoname中是否包含“姓名”如果有,就将值给到ocrLast_name,这样我们就拿到了相应的值
if (infoname.contains("姓名")) {
ocrLast_name.add(infoValue);
}
其他的如法炮制
还需要判断ocrLast_name大小是不是大于0,只有大于0时才获取,不然不报错,角标越界
if (ocrLast_name.size() > 0) {
Last_name.setText((CharSequence) ocrLast_name.toArray()[0]);
}
以上是逻辑
并查看返回的数据的状态,执行相应情况
{
// 获取刷新的code
RefreshToken refreshToken = new RefreshToken();
String refresh = RefreshToken.refresh_token();
// 判断执行的结果
if (!refresh.equals("error")) {
ZodoData zodoData = new ZodoData();
String zoho = null;
System.out.println("开始检测信息" + Last_name_value + Title_value + Account_Name_value + Mailing_Country_value + Email_value + Mobile_value + Phone_value);
try {
zoho = ZodoData.zodoData(refresh, Last_name_value, Title_value, Account_Name_value, Mailing_Country_value, Email_value, Mobile_value, Phone_value,field20value,spIndustryvalue,spLead_Sourcevalue,spLead_Statusvalue,spRatingvalue);
if (zoho.equals("SUCCESS")) {
runOnUiThread(() -> {
AlertDialog.Builder builder = new AlertDialog.Builder(sendDate.this);
builder.setTitle("上傳至CRM成功");
builder.setPositiveButton("確認", (dialog, which) -> {
dialog.dismiss();
finish();
});
AlertDialog alert = builder.create();
alert.show();
});
}
RefreshToken文件代码,属性Token,填写client_id,client_secret、refresh_token
package com.example.ocr.util;
import java.io.IOException;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class RefreshToken {
public static String refresh_token() {
// 定义客户端ID,客户端密钥和刷新令牌
String client_id = "xxxxxxxxxxxxxxxxxxxxx";
String client_secret = "xxxxxxxxxxxxxxxxxxxxxxxx";
String refresh_token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// 构建请求
String url = "https://accounts.zoho.com.cn/oauth/v2/token";
RequestBody formBody = new FormBody.Builder()
.add("grant_type", "refresh_token")
.add("client_id", client_id)
.add("client_secret", client_secret)
.add("refresh_token", refresh_token)
.build();
Request request = new Request.Builder()
.url(url)
.post(formBody)
.build();
// 创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();
// 发送请求
try (Response response = client.newCall(request).execute()) {
// 解析响应
if (response.isSuccessful()) {
String acc = response.body().string();
String access_token = acc.substring(acc.indexOf("access_token") + 15, acc.indexOf("scope") - 3);
return access_token;
} else {
return "error";
}
} catch (IOException e) {
e.printStackTrace();
return "error";
}
}
}
ZodoData文件构建请求,封包数据发送
package com.example.ocr.util;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONObject;
public class ZodoData {
public static String zodoData(String AUTH_TOKEN, String Last_name, String Title,String Account_name, String Mailing_Country,String emailvalue,String Moblievalue,String phonevalue,String field20value,String Industryvalue,String Lead_Sourcevalue,String Lead_Statusvalue,String Ratingvalue) throws JSONException {
// 定义授权令牌和API端点
String ZOHOCRM_AUTH_TOKEN = AUTH_TOKEN;
String ZOHOCRM_API_ENDPOINT = "https://www.zohoapis.com.cn/crm/v2/Leads";
// 构建扫描信息
JSONObject scanned_info = new JSONObject();
scanned_info.put("name", Last_name);//姓名
scanned_info.put("jobtitle", Title);// 工作职称
scanned_info.put("company", Account_name); // 公司名称
scanned_info.put("address", Mailing_Country); // 公司地址
scanned_info.put("emailvalue", emailvalue);
scanned_info.put("Moblievalue", Moblievalue);
scanned_info.put("phonevalue", phonevalue);
scanned_info.put("field20value", field20value);
scanned_info.put("Industryvalue", Industryvalue);
scanned_info.put("Lead_Sourcevalue", Lead_Sourcevalue);
scanned_info.put("Lead_Statusvalue", Lead_Statusvalue);
scanned_info.put("Ratingvalue", Ratingvalue);
// 构建联系人信息
JSONObject contact_info = new JSONObject();
contact_info.put("Last_Name", scanned_info.getString("name")); // 姓名
contact_info.put("Designation", scanned_info.getString("jobtitle")); // 工作职称
contact_info.put("Company", scanned_info.getString("company")); // 公司名称
contact_info.put("Country", scanned_info.getString("address")); // 公司地址
contact_info.put("Email", scanned_info.getString("emailvalue")); // 邮箱地址
contact_info.put("Mobile", scanned_info.getString("Moblievalue")); // 手机号码
contact_info.put("Phone", scanned_info.getString("phonevalue")); // 手机号码
contact_info.put("field20", scanned_info.getString("field20value")); // 主要生產產品
contact_info.put("Industry", scanned_info.getString("Industryvalue")); // 所屬產業
contact_info.put("Lead_Source", scanned_info.getString("Lead_Sourcevalue")); // 引合來源
contact_info.put("Lead_Status", scanned_info.getString("Lead_Statusvalue")); // 線索狀態
contact_info.put("Rating", scanned_info.getString("Ratingvalue")); // 客戶等級
JSONArray data = new JSONArray();
data.put(contact_info);
JSONObject lead_info = new JSONObject();
lead_info.put("data", data);
// 构建请求
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(JSON, lead_info.toString());
Request request = new Request.Builder()
.url(ZOHOCRM_API_ENDPOINT)
.addHeader("Authorization", "Zoho-oauthtoken " + ZOHOCRM_AUTH_TOKEN)
.addHeader("Content-Type", "application/json")
.post(body)
.build();
// 创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();
// 发送请求
try (Response response = client.newCall(request).execute()) {
// 解析响应
if (response.isSuccessful()) {
String jsonStr = response.body().string();
JSONObject jsonObject = new JSONObject(jsonStr);
JSONArray businessCardInfos =jsonObject.getJSONArray("data");
JSONObject firstBusinessCardInfo = businessCardInfos.getJSONObject(0);
String status = firstBusinessCardInfo.getString("code");
System.out.println(jsonStr);
return status;
} else {
String err = response.body().string();
return err;
}
} catch (IOException e) {
e.printStackTrace();
return "error";
}
}
}
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), mImageUri);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos);
byte[] imageBytes = baos.toByteArray();
baos.close();
bitmap.recycle();
String encodedImage = Base64.encodeToString(imageBytes, Base64.DEFAULT);
长时间执行的任务需要创建一个线程执行,比如在上述的代码中,识别图片信息,这样的工作必须的创建线程执行,不然会让界面闪退
new Thread(() -> {
BusinessCardOCR businessCardOCR = new BusinessCardOCR();
String result = businessCardOCR.ocrJson(encodedImage);
// 判断是否是json的数据格式
isjson json = new isjson();
boolean ISJSON =json.json_Str(result);
if (ISJSON){
Intent intent = new Intent(this,sendDate.class);
Bundle bundle = new Bundle();
bundle.putString("result",result);
intent.putExtras(bundle);
startActivity(intent);
}else {
runOnUiThread(() ->{
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("未識別到名片資訊,請重新拍攝");
builder.setPositiveButton("確認",(dialog, which) -> {
dialog.dismiss();
});
AlertDialog alert = builder.create();
alert.show();
});
}
}).start();
依据情况可能需要在线程中执行
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("請先拍攝照片");
builder.setPositiveButton("確認",(dialog, which) -> {
dialog.dismiss();
});
AlertDialog alert = builder.create();
alert.show();
private String [] RatingArray = {"A級(產業符合度:高)","B級(產業符合度:中)","C級(產業符合度:低)","不確定"};
ArrayAdapter<String> Rating_value = new ArrayAdapter<String>(this,
androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, RatingArray);
// 从布局文件中获取名叫sp_dropdown的下拉框
Spinner sp_Rating = findViewById(R.id.sp_Rating);// 引合來源列表
// 设置下拉框的标题。对话框模式才显示标题,下拉模式不显示标题
sp_Rating.setPrompt("請選擇客戶等級");
sp_Rating.setAdapter(Rating_value); // 设置下拉框的数组适配器
sp_Rating.setSelection(0); // 设置下拉框默认显示第一项
which) -> {
dialog.dismiss();
});
AlertDialog alert = builder.create();
alert.show();
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。