思路:
- 当要扫的二维码处于扫描框中时,获取该二维码在扫描框中的宽度,与扫描框的宽度进行对比,小于扫描框宽度的1/4,则认为二维码在扫描框中较小(镜头较远),则需要放大摄像头焦距,而不需要移动手机来调整。
- 放大焦距。
解码流程
- 将图像进行二值化处理,1、0代表黑、白。
- 寻找定位符、校正符,然后将原图像中符号码部分取出。(detector代码实现的功能)。
- 对符号码矩阵按照编码规范进行解码,得到实际信息(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;
}
放大摄像头,调整焦距
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
不需要的可以不用加。