在本教程中,我们将讨论和实施在谷歌移动视觉API中存在的条码API. 要了解使用视觉API的面部检测的实施,请参阅Vision API( / 社区 / 教程 / Android - 面部检测)。
适用于 Android 的条形码扫描器
With the introduction of Google Vision API, implementing Barcodes in an application has got a lot easier for developers. Following are the major formats that the Vision API supports.
- 1D条形码:EAN-13,EAN-8,UPC-A,UPC-E,代码-39,代码-93,代码-128,ITF,Codabar
- 2D条形码:QR代码,数据矩阵,PDF-417,AZTEC
条形码可以扫描东西从URL,联系信息到地理位置,WIFI,司机许可证ID。QR代码是最流行的格式,通常在许多应用中看到。下面,我们将开发一个应用程序,从位图像扫描QR代码值,以及通过相机检测QR代码并执行相关操作。
用于 Android 项目结构的 QR 代码扫描器
配置 Android Studio for Barcode 图书馆
将以下内容添加到 build.gradle
文件中。
1implementation 'com.google.android.gms:play-services-vision:11.8.0'
将下列内容添加到AndroidManifest.xml
文件应用程序标签中,以便在应用程序中启用条形码检测。
1<meta-data
2 android:name="com.google.android.gms.vision.DEPENDENCIES"
3 android:value="barcode" />
QR代码扫描器从图像
activity_main.xml
布局文件的代码在下面。
1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 android:paddingBottom="@dimen/activity_vertical_margin"
6 android:paddingLeft="@dimen/activity_horizontal_margin"
7 android:paddingRight="@dimen/activity_horizontal_margin"
8 android:paddingTop="@dimen/activity_vertical_margin">
9
10 <Button
11 android:id="@+id/btnTakePicture"
12 android:layout_width="wrap_content"
13 android:layout_height="wrap_content"
14 android:layout_centerHorizontal="true"
15 android:layout_centerVertical="true"
16 android:text="@string/take_barcode_picture" />
17
18 <Button
19 android:id="@+id/btnScanBarcode"
20 android:layout_width="wrap_content"
21 android:layout_height="wrap_content"
22 android:layout_below="@+id/btnTakePicture"
23 android:layout_centerHorizontal="true"
24 android:layout_marginTop="@dimen/activity_horizontal_margin"
25 android:text="@string/scan_barcode" />
26</RelativeLayout>
MainActivity.java 的代码在下面。
1package com.journaldev.barcodevisionapi;
2
3import android.content.Intent;
4import android.support.v7.app.AppCompatActivity;
5import android.os.Bundle;
6import android.view.View;
7import android.widget.Button;
8
9public class MainActivity extends AppCompatActivity implements View.OnClickListener {
10
11 Button btnTakePicture, btnScanBarcode;
12
13 @Override
14 protected void onCreate(Bundle savedInstanceState) {
15 super.onCreate(savedInstanceState);
16 setContentView(R.layout.activity_main);
17 initViews();
18 }
19
20 private void initViews() {
21 btnTakePicture = findViewById(R.id.btnTakePicture);
22 btnScanBarcode = findViewById(R.id.btnScanBarcode);
23 btnTakePicture.setOnClickListener(this);
24 btnScanBarcode.setOnClickListener(this);
25 }
26
27 @Override
28 public void onClick(View v) {
29
30 switch (v.getId()) {
31 case R.id.btnTakePicture:
32 startActivity(new Intent(MainActivity.this, PictureBarcodeActivity.class));
33 break;
34 case R.id.btnScanBarcode:
35 startActivity(new Intent(MainActivity.this, ScannedBarcodeActivity.class));
36 break;
37 }
38
39 }
40}
MainActivity.java
包含两个按钮. 第一个启动了一个活动,该活动在摄像机捕获的比特图像中扫描QR代码,并返回QR代码中的数据(如果有)。
1<uses-feature
2 android:name="android.hardware.camera"
3 android:required="true" />
4
5 <uses-permission android:name="android.permission.CAMERA" />
6 <uses-feature android:name="android.hardware.camera.autofocus" />
7
8 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
9 <uses-permission android:name="android.permission.INTERNET" />
要共享和访问由其他应用程序创建的文件,我们需要在AndroidManifest.xml
文件中添加下面的供应商
标签。
1<provider
2 android:name="android.support.v4.content.FileProvider"
3 android:authorities="${applicationId}.provider"
4 android:exported="false"
5 android:grantUriPermissions="true">
6 <meta-data
7 android:name="android.support.FILE_PROVIDER_PATHS"
8 android:resource="@xml/provider_paths" />
9 </provider>
这需要从我们的QR代码扫描器中获取摄像头捕获的图像,以便为Android应用程序。让我们从第一个,即PictureBarcodeActivity.java
开始。
1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
3 xmlns:tools="https://schemas.android.com/tools"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent"
6 android:paddingBottom="@dimen/activity_vertical_margin"
7 android:paddingLeft="@dimen/activity_horizontal_margin"
8 android:paddingRight="@dimen/activity_horizontal_margin"
9 android:paddingTop="@dimen/activity_vertical_margin">
10
11 <ImageView
12 android:id="@+id/imageView"
13 android:layout_width="150dp"
14 android:layout_height="150dp"
15 android:layout_centerHorizontal="true"
16 android:src="@mipmap/journaldev_logo" />
17
18 <TextView
19 android:id="@+id/txtResultsHeader"
20 android:layout_width="wrap_content"
21 android:layout_height="wrap_content"
22 android:layout_below="@+id/imageView"
23 android:layout_centerHorizontal="true"
24 android:text="Results"
25 android:textSize="18sp"
26 android:textStyle="bold" />
27
28 <TextView
29 android:id="@+id/txtResultsBody"
30 android:layout_width="match_parent"
31 android:layout_height="wrap_content"
32 android:layout_below="@+id/txtResultsHeader"
33 android:layout_centerHorizontal="true"
34 android:layout_marginTop="@dimen/activity_horizontal_margin"
35 android:gravity="center" />
36
37 <Button
38 android:id="@+id/btnOpenCamera"
39 android:layout_width="wrap_content"
40 android:layout_height="wrap_content"
41 android:layout_alignParentBottom="true"
42 android:layout_centerHorizontal="true"
43 android:layout_marginBottom="@dimen/activity_horizontal_margin"
44 android:layout_marginTop="@dimen/activity_horizontal_margin"
45 android:text="@string/open_camera" />
46</RelativeLayout>
下面是PictureCodeActivity.java
类的代码。
1package com.journaldev.barcodevisionapi;
2
3import android.Manifest;
4import android.content.Context;
5import android.content.Intent;
6import android.content.pm.PackageManager;
7import android.graphics.Bitmap;
8import android.graphics.BitmapFactory;
9import android.net.Uri;
10import android.os.Bundle;
11import android.os.Environment;
12import android.provider.MediaStore;
13import android.support.annotation.NonNull;
14import android.support.v4.app.ActivityCompat;
15import android.support.v4.content.FileProvider;
16import android.support.v7.app.AppCompatActivity;
17import android.util.Log;
18import android.util.SparseArray;
19import android.view.View;
20import android.widget.Button;
21import android.widget.TextView;
22import android.widget.Toast;
23
24import com.google.android.gms.vision.Frame;
25import com.google.android.gms.vision.barcode.Barcode;
26import com.google.android.gms.vision.barcode.BarcodeDetector;
27
28import java.io.File;
29import java.io.FileNotFoundException;
30
31public class PictureBarcodeActivity extends AppCompatActivity implements View.OnClickListener {
32
33 Button btnOpenCamera;
34 TextView txtResultBody;
35
36 private BarcodeDetector detector;
37 private Uri imageUri;
38 private static final int REQUEST_CAMERA_PERMISSION = 200;
39 private static final int CAMERA_REQUEST = 101;
40 private static final String TAG = "API123";
41 private static final String SAVED_INSTANCE_URI = "uri";
42 private static final String SAVED_INSTANCE_RESULT = "result";
43
44 @Override
45 protected void onCreate(Bundle savedInstanceState) {
46 super.onCreate(savedInstanceState);
47 setContentView(R.layout.activity_barcode_picture);
48
49 initViews();
50
51 if (savedInstanceState != null) {
52 if (imageUri != null) {
53 imageUri = Uri.parse(savedInstanceState.getString(SAVED_INSTANCE_URI));
54 txtResultBody.setText(savedInstanceState.getString(SAVED_INSTANCE_RESULT));
55 }
56 }
57
58 detector = new BarcodeDetector.Builder(getApplicationContext())
59 .setBarcodeFormats(Barcode.DATA_MATRIX | Barcode.QR_CODE)
60 .build();
61
62 if (!detector.isOperational()) {
63 txtResultBody.setText("Detector initialisation failed");
64 return;
65 }
66 }
67
68 private void initViews() {
69 txtResultBody = findViewById(R.id.txtResultsBody);
70 btnOpenCamera = findViewById(R.id.btnTakePicture);
71 txtResultBody = findViewById(R.id.txtResultsBody);
72 btnOpenCamera.setOnClickListener(this);
73 }
74
75 @Override
76 public void onClick(View v) {
77
78 switch (v.getId()) {
79 case R.id.btnTakePicture:
80 ActivityCompat.requestPermissions(PictureBarcodeActivity.this, new
81 String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
82 break;
83 }
84
85 }
86
87 @Override
88 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
89 super.onRequestPermissionsResult(requestCode, permissions, grantResults);
90 switch (requestCode) {
91 case REQUEST_CAMERA_PERMISSION:
92 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
93 takeBarcodePicture();
94 } else {
95 Toast.makeText(getApplicationContext(), "Permission Denied!", Toast.LENGTH_SHORT).show();
96 }
97 }
98 }
99
100 @Override
101 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
102 if (requestCode == CAMERA_REQUEST && resultCode == RESULT_OK) {
103 launchMediaScanIntent();
104 try {
105
106 Bitmap bitmap = decodeBitmapUri(this, imageUri);
107 if (detector.isOperational() && bitmap != null) {
108 Frame frame = new Frame.Builder().setBitmap(bitmap).build();
109 SparseArray<Barcode> barcodes = detector.detect(frame);
110 for (int index = 0; index < barcodes.size(); index++) {
111 Barcode code = barcodes.valueAt(index);
112 txtResultBody.setText(txtResultBody.getText() + "\n" + code.displayValue + "\n");
113
114 int type = barcodes.valueAt(index).valueFormat;
115 switch (type) {
116 case Barcode.CONTACT_INFO:
117 Log.i(TAG, code.contactInfo.title);
118 break;
119 case Barcode.EMAIL:
120 Log.i(TAG, code.displayValue);
121 break;
122 case Barcode.ISBN:
123 Log.i(TAG, code.rawValue);
124 break;
125 case Barcode.PHONE:
126 Log.i(TAG, code.phone.number);
127 break;
128 case Barcode.PRODUCT:
129 Log.i(TAG, code.rawValue);
130 break;
131 case Barcode.SMS:
132 Log.i(TAG, code.sms.message);
133 break;
134 case Barcode.TEXT:
135 Log.i(TAG, code.displayValue);
136 break;
137 case Barcode.URL:
138 Log.i(TAG, "url: " + code.displayValue);
139 break;
140 case Barcode.WIFI:
141 Log.i(TAG, code.wifi.ssid);
142 break;
143 case Barcode.GEO:
144 Log.i(TAG, code.geoPoint.lat + ":" + code.geoPoint.lng);
145 break;
146 case Barcode.CALENDAR_EVENT:
147 Log.i(TAG, code.calendarEvent.description);
148 break;
149 case Barcode.DRIVER_LICENSE:
150 Log.i(TAG, code.driverLicense.licenseNumber);
151 break;
152 default:
153 Log.i(TAG, code.rawValue);
154 break;
155 }
156 }
157 if (barcodes.size() == 0) {
158 txtResultBody.setText("No barcode could be detected. Please try again.");
159 }
160 } else {
161 txtResultBody.setText("Detector initialisation failed");
162 }
163 } catch (Exception e) {
164 Toast.makeText(getApplicationContext(), "Failed to load Image", Toast.LENGTH_SHORT)
165 .show();
166 Log.e(TAG, e.toString());
167 }
168 }
169 }
170
171 private void takeBarcodePicture() {
172 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
173 File photo = new File(Environment.getExternalStorageDirectory(), "pic.jpg");
174 imageUri = FileProvider.getUriForFile(PictureBarcodeActivity.this,
175 BuildConfig.APPLICATION_ID + ".provider", photo);
176 intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
177 startActivityForResult(intent, CAMERA_REQUEST);
178 }
179
180 @Override
181 protected void onSaveInstanceState(Bundle outState) {
182 if (imageUri != null) {
183 outState.putString(SAVED_INSTANCE_URI, imageUri.toString());
184 outState.putString(SAVED_INSTANCE_RESULT, txtResultBody.getText().toString());
185 }
186 super.onSaveInstanceState(outState);
187 }
188
189 private void launchMediaScanIntent() {
190 Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
191 mediaScanIntent.setData(imageUri);
192 this.sendBroadcast(mediaScanIntent);
193 }
194
195 private Bitmap decodeBitmapUri(Context ctx, Uri uri) throws FileNotFoundException {
196 int targetW = 600;
197 int targetH = 600;
198 BitmapFactory.Options bmOptions = new BitmapFactory.Options();
199 bmOptions.inJustDecodeBounds = true;
200 BitmapFactory.decodeStream(ctx.getContentResolver().openInputStream(uri), null, bmOptions);
201 int photoW = bmOptions.outWidth;
202 int photoH = bmOptions.outHeight;
203
204 int scaleFactor = Math.min(photoW / targetW, photoH / targetH);
205 bmOptions.inJustDecodeBounds = false;
206 bmOptions.inSampleSize = scaleFactor;
207
208 return BitmapFactory.decodeStream(ctx.getContentResolver()
209 .openInputStream(uri), null, bmOptions);
210 }
211}
下面是从上面的代码中得出的几点推断。
- 联合国 以下代码创建了条码检测器 ()
) QQ
探测器 = 新的条码检测器. Builder(getApplicationContext () ()
). set Barcode Formats (Barcode.DATA_MATRIX QQ barcode.QR_CODE
.built ();
.
. * 需要扫描的格式类型设定在
set Barcode Formats ()'. *
take BarcodePicture ()'函数发射相机的地方。 要取回图像, 我们使用发射介质ScanIntent ()
来调用图像 URI 搜索图像 。 (_) ) * A'Frame.Builder"用于创建比图图像的框架. 在框架上方,条形码探测器扫描可能的QR码. 以上代码中的以下一行创建出 Bitmap. QQ 框架 = 新框架. Builder (. setBitmap (bitmap).building (); QQ - 我们已经创建了 SparseArray , 它将通过在 Barcode 检测器上引用
detect ()' 方法, 包含图像中所有可能的 QR 代码 。 QQ SparseArray < Barcode > 条码 = detect (frame); (_) (中文(简体) ). 要获得 QR 代码的格式,
valueFormat' 字段在条码实例之上被调用, 如下文所示 。 (_ _) 条码. valueAt (index). valueFormat - 要获取显示的值和原始值,请引用以下内容: __ 条码. valueAt(index.displayValue 条码. valueAt(index.rawValue _ )* 返回的相关值在文本视图中显示。 对于一个位图中的条码,其值被附加到当前文本View. (_) (英语)
ScannedBarcodeActivity.java
类通过相机扫描条形码,我们从 这里生成了两个自定义QR代码。
1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 android:padding="@dimen/activity_horizontal_margin">
6
7 <SurfaceView
8 android:id="@+id/surfaceView"
9 android:layout_width="match_parent"
10 android:layout_height="match_parent"
11 android:layout_above="@+id/btnAction"
12 android:layout_alignParentLeft="true"
13 android:layout_alignParentStart="true"
14 android:layout_centerVertical="true" />
15
16 <TextView
17 android:id="@+id/txtBarcodeValue"
18 android:layout_width="wrap_content"
19 android:layout_height="wrap_content"
20 android:layout_alignParentTop="true"
21 android:layout_marginLeft="@dimen/activity_horizontal_margin"
22 android:layout_marginStart="@dimen/activity_horizontal_margin"
23 android:text="No Barcode Detected"
24 android:textColor="@android:color/white"
25 android:textSize="20sp" />
26
27 <Button
28 android:id="@+id/btnAction"
29 android:layout_width="match_parent"
30 android:layout_height="wrap_content"
31 android:layout_alignParentBottom="true"
32 android:text="ADD CONTENT IN THE MAIL" />
33
34</RelativeLayout>
来自摄像头的 Android 条形码扫描器
ScannedBarcodeActivity.java
的代码在下面。
1package com.journaldev.barcodevisionapi;
2
3import android.Manifest;
4import android.content.Intent;
5import android.content.pm.PackageManager;
6import android.net.Uri;
7import android.os.Bundle;
8import android.support.v4.app.ActivityCompat;
9import android.support.v7.app.AppCompatActivity;
10import android.util.SparseArray;
11import android.view.SurfaceHolder;
12import android.view.SurfaceView;
13import android.view.View;
14import android.widget.Button;
15import android.widget.TextView;
16import android.widget.Toast;
17
18import com.google.android.gms.vision.CameraSource;
19import com.google.android.gms.vision.Detector;
20import com.google.android.gms.vision.barcode.Barcode;
21import com.google.android.gms.vision.barcode.BarcodeDetector;
22
23import java.io.IOException;
24
25public class ScannedBarcodeActivity extends AppCompatActivity {
26
27 SurfaceView surfaceView;
28 TextView txtBarcodeValue;
29 private BarcodeDetector barcodeDetector;
30 private CameraSource cameraSource;
31 private static final int REQUEST_CAMERA_PERMISSION = 201;
32 Button btnAction;
33 String intentData = "";
34 boolean isEmail = false;
35
36 @Override
37 protected void onCreate(Bundle savedInstanceState) {
38 super.onCreate(savedInstanceState);
39 setContentView(R.layout.activity_scan_barcode);
40
41 initViews();
42 }
43
44 private void initViews() {
45 txtBarcodeValue = findViewById(R.id.txtBarcodeValue);
46 surfaceView = findViewById(R.id.surfaceView);
47 btnAction = findViewById(R.id.btnAction);
48
49 btnAction.setOnClickListener(new View.OnClickListener() {
50 @Override
51 public void onClick(View v) {
52
53 if (intentData.length() > 0) {
54 if (isEmail)
55 startActivity(new Intent(ScannedBarcodeActivity.this, EmailActivity.class).putExtra("email_address", intentData));
56 else {
57 startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(intentData)));
58 }
59 }
60
61 }
62 });
63 }
64
65 private void initialiseDetectorsAndSources() {
66
67 Toast.makeText(getApplicationContext(), "Barcode scanner started", Toast.LENGTH_SHORT).show();
68
69 barcodeDetector = new BarcodeDetector.Builder(this)
70 .setBarcodeFormats(Barcode.ALL_FORMATS)
71 .build();
72
73 cameraSource = new CameraSource.Builder(this, barcodeDetector)
74 .setRequestedPreviewSize(1920, 1080)
75 .setAutoFocusEnabled(true) //you should add this feature
76 .build();
77
78 surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
79 @Override
80 public void surfaceCreated(SurfaceHolder holder) {
81 try {
82 if (ActivityCompat.checkSelfPermission(ScannedBarcodeActivity.this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
83 cameraSource.start(surfaceView.getHolder());
84 } else {
85 ActivityCompat.requestPermissions(ScannedBarcodeActivity.this, new
86 String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
87 }
88
89 } catch (IOException e) {
90 e.printStackTrace();
91 }
92
93 }
94
95 @Override
96 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
97 }
98
99 @Override
100 public void surfaceDestroyed(SurfaceHolder holder) {
101 cameraSource.stop();
102 }
103 });
104
105 barcodeDetector.setProcessor(new Detector.Processor<Barcode>() {
106 @Override
107 public void release() {
108 Toast.makeText(getApplicationContext(), "To prevent memory leaks barcode scanner has been stopped", Toast.LENGTH_SHORT).show();
109 }
110
111 @Override
112 public void receiveDetections(Detector.Detections<Barcode> detections) {
113 final SparseArray<Barcode> barcodes = detections.getDetectedItems();
114 if (barcodes.size() != 0) {
115
116 txtBarcodeValue.post(new Runnable() {
117
118 @Override
119 public void run() {
120
121 if (barcodes.valueAt(0).email != null) {
122 txtBarcodeValue.removeCallbacks(null);
123 intentData = barcodes.valueAt(0).email.address;
124 txtBarcodeValue.setText(intentData);
125 isEmail = true;
126 btnAction.setText("ADD CONTENT TO THE MAIL");
127 } else {
128 isEmail = false;
129 btnAction.setText("LAUNCH URL");
130 intentData = barcodes.valueAt(0).displayValue;
131 txtBarcodeValue.setText(intentData);
132
133 }
134 }
135 });
136
137 }
138 }
139 });
140 }
141
142 @Override
143 protected void onPause() {
144 super.onPause();
145 cameraSource.release();
146 }
147
148 @Override
149 protected void onResume() {
150 super.onResume();
151 initialiseDetectorsAndSources();
152 }
153}
下面是从上面的代码中得出的几点推断。
SurfaceView'在使图形用户界面快速化时能够显示相机预览图像。 界面 " SurfaceHolder " 。 Callback"用于接收表面所发生变化的信息(在此情况下是相机预览). " 沙发手" 回调
执行三种方法:(_) ) - ** 变脸** : 当表面的大小或格式发生变化时,这种方法就称为.
- ** 硬面所创造** : 当首先产生地表时,这个方法叫做.
- ** surface被破坏**: 当表面被破坏时就叫这个 (_) (
)* " Camera Source " 与一个探测器一起管理相机。 这里表面 视图是基础探测器.
CameraSource.start ()' 打开相机,开始向 SurfaceView 发送预览框。 相机源创建方式如下: 相机源 (- ) 相机源 (- ) 相机源 (- ) 相机源 (- ) 相机源 (- ) 相机源 (- ) 相机源源 (-) 相机源 (- ) 相机源 (- ) 相机源源(- ) 相机源(- ) * 我们在条形码探测器上指定了一个使用
setProcessor ()`的处理器。 界面包含对接收从相机预览中接收QR代码并添加到SparseArray的方法的回调( --). ( )* QR 代码的值在 TextView 中使用了可操作的 Runnable 显示, 因为行码在背景线索中被检测出 。
- 在这个例子中,我们用发电机创建了两个条码. 一个包含URL。 第二份载有电子邮件地址。 点击按钮后,根据检测到的QR代码值,我们要么启动URL,要么向从QR代码检测到的相关电子邮件地址发送电子邮件. ( (英语)
The output of the PictureBarcodeActivity.java
is given below. The output of the
ScannedBarcodeActivity.java
activity in action is given below. That's all for QR code scanner project for android using Mobile Vision API. We've added the sample QR Codes to the source code. You can download the final Android QR Code Scanner/Barcode Scanner Project from the link below and play around with different QR Codes.