结合ZXing实现类似微信扫二维码放大摄像头
目前android中实现扫二维码大多数使用的是zxing这个开源框架,要使用android的核心源码,因为我们需要在源码中做修改,将框架添加到项目中,这里就不多说了,网上都有,这里只说一下放大摄像头部分。涉及到的文件主要有DecodeHandler,MultiFormatReader,QRCodeReader。
实际应用中,我们都知道镜头离二维码太远或者太近都影响识别,二维码恰好处于扫描框中最好。
思路:
1、当要扫的二维码处于扫描框中时,获取该二维码在扫描框中的宽度,与扫描框的宽度进行对比,小于扫描框宽度的1/4,则认为二维码在扫描框中较小(镜头较远),则需要放大摄像头焦距,而不需要移动手机来调整
2、摄像头焦距的放大
一、获取二维码在扫描框中的宽度
要获取二维码在扫描框中的宽度,首先要对QR码相关部分了解,具体可以参考这篇博客http://blog.csdn.net/mihenyinghua/article/details/17224019,我也是读了这篇博客后知道从哪一块进行着手,而不是盲目去看源码,毕竟zxing这个库的源码还是不少的,而且我们只关心二维码这一块,所以没必要所有源码都去阅读。
从这篇博客中了解了从扫描到得到二维码中的信息分三步,具体的大家去阅读博客就行了:
1)、将图像进行二值化处理,1、0代表黑、白。
2)、寻找定位符、校正符,然后将原图像中符号码部分取出。(detector代码实现的功能)
3)、对符号码矩阵按照编码规范进行解码,得到实际信息(decoder代码实现的功能)
首先是扫描框的尺寸,在CamerManager中getFramingRect()方法是获取扫描框的矩形尺寸,frameRect.right-frameRect.left来获取扫描框宽度。
Rect frameRect = activity.cameraManager.getFramingRect();
if(frameRect!=null){
int frameWidth = frameRect.right-frameRect.left;
}
最终的结果在DecodeHandler文件中,decode(byte[] data, int width, int height),是对扫描结果的解析处理。先看一下源码:
/**
* Decode the data within the viewfinder rectangle, and time how long it took. For efficiency,
* reuse the same reader objects from one decode to the next.
*
* @param data The YUV preview frame.
* @param width The width of the preview frame.
* @param height The height of the preview frame.
*/
private void decode(byte[] data, int width, int height) {
long start = System.currentTimeMillis();
Result rawResult = null;
PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
if (source != null) {
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
rawResult = multiFormatReader.decodeWithState(bitmap);
} catch (ReaderException re) {
// continue
} finally {
multiFormatReader.reset();
}
}
Handler handler = activity.getHandler();
if (rawResult != null) {
// Don't log the barcode contents for security.
long end = System.currentTimeMillis();
Log.d(TAG, "Found barcode in " + (end - start) + " ms");
if (handler != null) {
Message message = Message.obtain(handler, Ids.decode_succeeded, rawResult);
Bundle bundle = new Bundle();
bundleThumbnail(source, bundle);
message.setData(bundle);
message.sendToTarget();
}
} else {
if (handler != null) {
Message message = Message.obtain(handler, Ids.decode_failed);
message.sendToTarget();
}
}
}
Result 就是结果,我们需要从中获取一些信息。要获取二维码的尺寸,只需要获取两个定位的点就行了,这个通过Result的getResultPoints()来获取,得到的是一个ResultPoint数组,通过调试,结合安卓中的坐标系得知的结果是resultPoints[0],resultPoints[1]分别对应的是下图中的左下角--左上角的点,有这两个点就足够了,然后通过两点间的距离公式来获得大致尺寸。
现在来获取扫描框中的二维码大小:
下面是我提取出来相关的主要文件,一步一步涉及到的文件过程如下
DecodeHandler-->
-->MultiFormatReader multiFormatReader = new MultiFormatReader();
MultiFormatReader-->
setHints(){
//二维码
if (formats.contains(BarcodeFormat.QR_CODE)) {
readers.add(new QRCodeReader());
}
}
QRCodeReader-->Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints){
}
我们先添加一个QRCodeReader的构造方法,因为摄像头的相关配置以及扫描框相关信息在CaptureActivity中,所以把CaptureActivity放到QRCodeReader的构造方法中去
private CaptureActivity activity;
public QRCodeReader(CaptureActivity activity) {
this.activity = activity;
}
接着是MultiFormatReader,
private CaptureActivity activity;
public void setActivity(CaptureActivity activity) {
this.activity = activity;
}
//这个方法中有关QRCodeReader的调用改成调用有参的构造方法
setHints(Map<DecodeHintType,?> hints){
if (formats.contains(BarcodeFormat.QR_CODE)) {
readers.add(new QRCodeReader(activity));
}
//..... 接着是
if (readers.isEmpty()){
readers.add(new QRCodeReader(activity));
}
}
然后在DecodeHandler的构造方法中对MultiFormatReader的调用setHints之前先调用setActivity()保证CaptureActivity不为空
DecodeHandler(CaptureActivity activity, Map<DecodeHintType, Object> hints) {
multiFormatReader = new MultiFormatReader();
multiFormatReader.setActivity(activity);
multiFormatReader.setHints(hints);
this.activity = activity;
}
接下来看QRCodeReader中的这个方法,我又加了三个关键注释
@Override
public final Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
throws NotFoundException, ChecksumException, FormatException {
DecoderResult decoderResult;
ResultPoint[] points;
if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
BitMatrix bits = extractPureBits(image.getBlackMatrix());
decoderResult = decoder.decode(bits, hints);
points = NO_POINTS;
} else {
//1、将图像进行二值化处理,1、0代表黑、白。( 二维码的使用getBlackMatrix方法 )
//2、寻找定位符、校正符,然后将原图像中符号码部分取出。(detector代码实现的功能)
DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
//3、对符号码矩阵按照编码规范进行解码,得到实际信息(decoder代码实现的功能)
decoderResult = decoder.decode(detectorResult.getBits(), hints);
points = detectorResult.getPoints();
}
// If the code was mirrored: swap the bottom-left and the top-right points.
if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
}
Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE);
List<byte[]> byteSegments = decoderResult.getByteSegments();
if (byteSegments != null) {
result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
}
String ecLevel = decoderResult.getECLevel();
if (ecLevel != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
}
if (decoderResult.hasStructuredAppend()) {
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
decoderResult.getStructuredAppendSequenceNumber());
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
decoderResult.getStructuredAppendParity());
}
return result;
}
通过DetectorResult ,我们可以获取二维码的定位符,所以在执行解码前利用定位符来获取在扫描框中的二维码大小,和扫描框进行大小对比来判断是否需要获取放大摄像头。
处理结果如下
@Override
public final Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
throws NotFoundException, ChecksumException, FormatException {
DecoderResult decoderResult;
ResultPoint[] points;
if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
BitMatrix bits = extractPureBits(image.getBlackMatrix());
decoderResult = decoder.decode(bits, hints);
points = NO_POINTS;
} else {
//1、将图像进行二值化处理,1、0代表黑、白。( 二维码的使用getBlackMatrix方法 )
//2、寻找定位符、校正符,然后将原图像中符号码部分取出。(detector代码实现的功能)
DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
if(activity!=null){
CameraManager cameraManager = activity.cameraManager;
ResultPoint[] p = detectorResult.getPoints();
//计算扫描框中的二维码的宽度,两点间距离公式
float point1X = p[0].getX();
float point1Y = p[0].getY();
float point2X = p[1].getX();
float point2Y = p[1].getY();
int len =(int) Math.sqrt(Math.abs(point1X-point2X)*Math.abs(point1X-point2X)+Math.abs(point1Y-point2Y)*Math.abs(point1Y-point2Y));
Rect frameRect = cameraManager.getFramingRect();
if(frameRect!=null){
int frameWidth = frameRect.right-frameRect.left;
Camera camera = cameraManager.getOpenCamera().getCamera();
Camera.Parameters parameters = camera.getParameters();
int maxZoom = parameters.getMaxZoom();
int zoom = parameters.getZoom();
if(parameters.isZoomSupported()){
if(len <= frameWidth/4) {//二维码在扫描框中的宽度小于扫描框的1/4,放大镜头
if (zoom == 0) {
zoom = maxZoom / 2;
} else if (zoom <= maxZoom - 10) {
zoom = zoom + 10;
} else {
zoom = maxZoom;
}
parameters.setZoom(zoom);
camera.setParameters(parameters);
return null;
}
}
}
}
//3、对符号码矩阵按照编码规范进行解码,得到实际信息(decoder代码实现的功能)
decoderResult = decoder.decode(detectorResult.getBits(), hints);
points = detectorResult.getPoints();
}
// If the code was mirrored: swap the bottom-left and the top-right points.
if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
}
Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE);
List<byte[]> byteSegments = decoderResult.getByteSegments();
if (byteSegments != null) {
result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
}
String ecLevel = decoderResult.getECLevel();
if (ecLevel != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
}
if (decoderResult.hasStructuredAppend()) {
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
decoderResult.getStructuredAppendSequenceNumber());
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
decoderResult.getStructuredAppendParity());
}
return result;
}
//计算扫描框中的二维码的宽度,两点间距离公式
float point1X = rawResult.getResultPoints()[0].getX();
float point1Y = rawResult.getResultPoints()[0].getY();
float point2X = rawResult.getResultPoints()[1].getX();
float point2Y = rawResult.getResultPoints()[1].getY();
int len =(int) Math.sqrt(Math.abs(point1X-point2X)*Math.abs(point1X-point2X)+Math.abs(point1Y-point2Y)*Math.abs(point1Y-point2Y));
有了扫描框的宽度和二维码的尺寸,就可以判断二维码在扫描框中是不是太小。
小于扫描框宽度的1/4,就放大镜头,通过handler告知此次扫描失败重新扫描,否则不用
二、放大摄像头,调整焦距
Camera的当前设置信息在Parameters可以获取,通过getParameters()获取Parameters。
放大摄像头有个前提条件就是你的手机要支持摄像头焦距的放大和缩小,不过目前大多数手机都支持了,我们还是判断一下为好。parameters.isZoomSupported(),判断是否支持焦距缩放。支持,然后设置需要放大多少,通过parameters.setZoom(int value)来设置,这个值有个限制, The valid range is 0 to {@link #getMaxZoom}.
也就是最大能设置到maxzoom,这个最大值通过getMaxZoom()来获取。设置完成后,然后再调用camera.setParameters(parameters);让设置生效。另外,我加了一个判断,当扫描的是二维码才进行焦距的缩放rawResult.getBarcodeFormat() == BarcodeFormat.QR_CODE
不需要的可以不用加。
最后运行的时候会有个QRCodeMultiReader报的错误,因为它继承了QRCodeReader,而我们添加了一个有参的构造函数,所以按照提示把它的构造也添加上就行了
public QRCodeMultiReader(CaptureActivity activity) {
super(activity);
}
运行后就可以实现类似微信的那种效果了。
需要demo的,可以到https://github.com/Alvin9234/CommonLibrary中下载