关于二维码图片识别的研究

前言

如今二维码图片处处可见,形形色色的App,支持二维码扫码和识别的不在少数。最近研究了一下二维码图片的识别以及提高识别率的方法。

关于二维码识别

说起二维码识别,最知名的库应该就是zxing了吧。大多数应用似乎都是采用zxing来实现二维码功能的。zxing是Google推出的一个针对二维码、条形码识别的第三方库。这个库比较杂,支持的功能也比较多,Google也没有提供一份详细的文档,对于想要研究zxing来说,平添了不少麻烦。但是常用的功能比较少,主要就是二维码、条形码扫描,二维码图片识别,生成二维码。这篇文章主要是研究二维码图片识别,以及如何提高识别精度。

使用zxing

zxing的github地址:https://github.com/zxing/zxing
首先在gradle中引入zxing核心库:
api ‘com.google.zxing:android-core:3.3.0’
api ‘com.google.zxing:core:3.3.2
然后把zxing的android包下载下来,通过module的方式引入到AS中。
zxing的android包.png
以上是zxing android包的目录,可以看到,相关类还是很多的。但我们通常使用不到这么多功能。可以按自己的需求做相应的修改。这里我直接修改CaptureActivity来实现二维码图片识别。CaptureActivity中已经实现了二维码扫码的功能,可以自行研究。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
* 打开系统相册
*/
private void discernPicture() {
Intent innerIntent = new Intent(Intent.ACTION_GET_CONTENT);
innerIntent.setType("image/*");
startActivityForResult(innerIntent, REQUEST_CODE_SCAN_GALLERY);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (resultCode == Activity.RESULT_OK) {
switch (requestCode) {
case REQUEST_CODE_SCAN_GALLERY:
handlePicture(intent);
break;
}
}
}
/**
* 扫描图片中的二维码
* @param path
* @return
*/
private Result scanQRCode(String path) {
if (TextUtils.isEmpty(path)) {
return null;
}
Hashtable<DecodeHintType, Object> hints = new Hashtable<>();
// 设置二维码内容的编码
hints.put(DecodeHintType.CHARACTER_SET, "UTF8");
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE);
// 只针对QR解码
Bitmap scanBitmap = decodeSampledBitmapFromFile(path, 512, 512);
// 获取bitmap的宽高,像素矩阵
int width = scanBitmap.getWidth();
int height = scanBitmap.getHeight();
int[] pixels = new int[width * height];
scanBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
RGBLuminanceSource source = new RGBLuminanceSource(width, height, pixels);
BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
QRCodeReader reader = new QRCodeReader();
try {
return reader.decode(bitmap1, hints);
} catch (NotFoundException e) {
e.printStackTrace();
} catch (FormatException e) {
e.printStackTrace();
} catch (ChecksumException e) {
e.printStackTrace();
}
return null;
}
/**
* 根据uri获取路径
* @param intent
* @return
*/
private String getPathByUri(Intent intent) {
Uri uri = intent.getData();
if (uri == null) {
return "";
}
String path = "";
if (DocumentsContract.isDocumentUri(this, uri)) {
String docId = DocumentsContract.getDocumentId(uri);
if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
String id = docId.split(":")[1];
String selection = MediaStore.Images.Media._ID + "=" + id;
path = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
} else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
path = getImagePath(contentUri, "");
}
} else if ("content".equals(uri.getScheme())) {
path = getImagePath(uri, "");
} else if ("file".equals(uri.getScheme())) {
path = uri.getPath();
}
Log.i("@@@#", "path: " + path);
return path;
}
private String getImagePath(Uri uri, String selection) {
String path = "";
Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
/**
* 将图片根据压缩比压缩成固定宽高的Bitmap,实际解析的图片大小可能和#reqWidth、#reqHeight不一样。
*
* @param imgPath 图片地址
* @param reqWidth 需要压缩到的宽度
* @param reqHeight 需要压缩到的高度
* @return Bitmap
*/
public static Bitmap decodeSampledBitmapFromFile(String imgPath, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imgPath, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(imgPath, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}

以上就是使用zxing做二维码识别的的所有代码了,聚体步骤基本就是:

  1. 打开系统相册;
  2. 重写onActivityResult,并在这里处理图片;
  3. 对图片做压缩,避免大图出现OOM;
  4. 使用zxing的QRCodeReader来识别图片。

其中,关于如何提高二维码图片的识别精度可以参考这篇博客https://iluhcm.com/2016/01/08/scan-qr-code-and-recognize-it-from-picture-fastly-using-zxing 。关于这边博客里介绍的几种的方式,我都有尝试,但效果并不明显。对于有遮挡和反光严重的图片,依然难以识别。这也许就是zxing的极限了吧。
因此我也尝试了另外的库,希望来进一步提升识别精度。然后找到了zbar(目前了解的二维码识别库好像就这两个呢。。。)。

使用zbar

zbar是使用C语言编写,具有更好的跨平台性。不过该库上一次提交已经是6年前了,看来作者并没有继续更新维护的想法了吧。
既然zbar是C语言编写的,那我们就没法直接引用,需要先打成so包。不过github上也有不少已经打包的,倒不用我们自己再次打包了。这里我选择了android-zbar-sdk,地址:https://github.com/yanzhenjie/android-zbar-sdk。
在gradle中引入api ‘com.yanzhenjie.zbar:zbar:1.0.0’
相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 通过zbar识别二维码图片
* @param data
* @param width
* @param height
* @return
*/
private String scanByZBar(byte[] data, int width, int height) {
Image barcode = new Image(width, height, "Y800");
barcode.setData(data);
// Set the image capture area.
// barcode.setCrop(startX, startY, width, height);
String qrCodeString = null;
ImageScanner imageScanner = new ImageScanner();
int result = imageScanner.scanImage(barcode);
if (result != 0) {
SymbolSet symSet = imageScanner.getResults();
for (Symbol sym : symSet)
qrCodeString = sym.getData();
}
Toast.makeText(this, "zbar: " + qrCodeString, Toast.LENGTH_SHORT).show();
return qrCodeString;
}

最后

通过测试,发现zbar的识别率的确要比zxing好上一点。
以上,便是关于二维码图片识别研究的全部内容。基本都是通俗易懂的东西,大多借用了第三方代码。关于详细的实现原理和算法研究可以参考以上我提到的两个网址。