Android 二维码识别学习

最近一直在搞二维码的扫描工作。当然Zxing这个开源项目是要使用的。从网上抄来一些代码,左改右改终于调通了。但是还是没有理解Zxing。接下来要边记录边学习

  1. 图形拉伸问题

如果摄像头拍摄的图片和设备屏幕的宽高比不同的话,就会引起图像的拉伸变形。针对这个问题,Zxing项目是有细致的解决办法的。在CameraConfigurationManager类里的initFromCameraParameters方法里

  /**
   * Reads, one time, values from the camera that are needed by the app.
   */
  void initFromCameraParameters(OpenCamera camera) {
    Camera.Parameters parameters = camera.getCamera().getParameters();
    WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = manager.getDefaultDisplay();

    //判断屏幕方向,是否有需要从自然角度旋转到显示器角度
    int displayRotation = display.getRotation();
    int cwRotationFromNaturalToDisplay;
    switch (displayRotation) {
      case Surface.ROTATION_0:
        cwRotationFromNaturalToDisplay = 0;
        break;
      case Surface.ROTATION_90:
        cwRotationFromNaturalToDisplay = 90;
        break;
      case Surface.ROTATION_180:
        cwRotationFromNaturalToDisplay = 180;
        break;
      case Surface.ROTATION_270:
        cwRotationFromNaturalToDisplay = 270;
        break;
      default:
        // Have seen this return incorrect values like -90
        if (displayRotation % 90 == 0) {
          cwRotationFromNaturalToDisplay = (360 + displayRotation) % 360;
        } else {
          throw new IllegalArgumentException("Bad rotation: " + displayRotation);
        }
    }
    Log.i(TAG, "Display at: " + cwRotationFromNaturalToDisplay);

    int cwRotationFromNaturalToCamera = camera.getOrientation();
    Log.i(TAG, "Camera at: " + cwRotationFromNaturalToCamera);

    // Still not 100% sure about this. But acts like we need to flip this:
    if (camera.getFacing() == CameraFacing.FRONT) {
      cwRotationFromNaturalToCamera = (360 - cwRotationFromNaturalToCamera) % 360;
      Log.i(TAG, "Front camera overriden to: " + cwRotationFromNaturalToCamera);
    }

    /*
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    String overrideRotationString;
    if (camera.getFacing() == CameraFacing.FRONT) {
      overrideRotationString = prefs.getString(PreferencesActivity.KEY_FORCE_CAMERA_ORIENTATION_FRONT, null);
    } else {
      overrideRotationString = prefs.getString(PreferencesActivity.KEY_FORCE_CAMERA_ORIENTATION, null);
    }
    if (overrideRotationString != null && !"-".equals(overrideRotationString)) {
      Log.i(TAG, "Overriding camera manually to " + overrideRotationString);
      cwRotationFromNaturalToCamera = Integer.parseInt(overrideRotationString);
    }
     */

    //根据屏幕方向和相机方向判断是否有需要进行旋转
    cwRotationFromDisplayToCamera =
        (360 + cwRotationFromNaturalToCamera - cwRotationFromNaturalToDisplay) % 360;
    Log.i(TAG, "Final display orientation: " + cwRotationFromDisplayToCamera);
    if (camera.getFacing() == CameraFacing.FRONT) {
      Log.i(TAG, "Compensating rotation for front camera");
      cwNeededRotation = (360 - cwRotationFromDisplayToCamera) % 360;
    } else {
      cwNeededRotation = cwRotationFromDisplayToCamera;
    }
    Log.i(TAG, "Clockwise rotation from display to camera: " + cwNeededRotation);

    Point theScreenResolution = new Point();
    display.getSize(theScreenResolution);
    screenResolution = theScreenResolution;
    Log.i(TAG, "Screen resolution in current orientation: " + screenResolution);

    // 寻找最佳的预览宽高值
    cameraResolution = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
    Log.i(TAG, "Camera resolution: " + cameraResolution);
    bestPreviewSize = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
    Log.i(TAG, "Best available preview size: " + bestPreviewSize);

    boolean isScreenPortrait = screenResolution.x < screenResolution.y;
    boolean isPreviewSizePortrait = bestPreviewSize.x < bestPreviewSize.y;

    if (isScreenPortrait == isPreviewSizePortrait) {
      previewSizeOnScreen = bestPreviewSize;
    } else {
      previewSizeOnScreen = new Point(bestPreviewSize.y, bestPreviewSize.x);
    }
    Log.i(TAG, "Preview size on screen: " + previewSizeOnScreen);
  }

再来看一下CameraConfingrationUtils.findBestPreviewSizeValue(Camera.parameters parameters, Point screenResolution);方法

public static Point findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution) {

    // 获取当前手机支持的屏幕预览尺寸
    List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
    if (rawSupportedSizes == null) {
        Log.w(TAG, "Device returned no supported preview sizes; using default");
        Camera.Size defaultSize = parameters.getPreviewSize();
        if (defaultSize == null) {
            throw new IllegalStateException("Parameters contained no preview size!");
        }
        return new Point(defaultSize.width, defaultSize.height);
    }

    // 对这些尺寸根据像素值(即宽乘高的值)进行重小到大排序
    List<Camera.Size> supportedPreviewSizes = new ArrayList<>(rawSupportedSizes);
    Collections.sort(supportedPreviewSizes, new Comparator<Camera.Size>() {
        @Override
        public int compare(Camera.Size a, Camera.Size b) {
            int aPixels = a.height * a.width;
            int bPixels = b.height * b.width;
            if (bPixels < aPixels) {
                return -1;
            }
            if (bPixels > aPixels) {
                return 1;
            }
            return 0;
        }
    });

    double screenAspectRatio = (double) screenResolution.x / (double) screenResolution.y;

    Iterator<Camera.Size> it = supportedPreviewSizes.iterator();
    while (it.hasNext()) {
        Camera.Size supportedPreviewSize = it.next();
        int realWidth = supportedPreviewSize.width;
        int realHeight = supportedPreviewSize.height;
        // 首先把不符合最小预览像素值的尺寸排除
        if (realWidth * realHeight < MIN_PREVIEW_PIXELS) {
            it.remove();
            continue;
        }

        boolean isCandidatePortrait = realWidth < realHeight;
        int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
        int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
        double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight;
        double distortion = Math.abs(aspectRatio - screenAspectRatio);
        // 根据宽高比判断是否满足最大误差要求(默认最大值为0.15,即宽高比默认不能超过给定比例的15%)
        if (distortion > MAX_ASPECT_DISTORTION) {
            it.remove();
            continue;
        }

        if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) {
            Point exactPoint = new Point(realWidth, realHeight);
            Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint);
            return exactPoint;
        }
    }

    // 如果没有精确匹配到合适的尺寸,则使用最大的尺寸,这样设置便是预览图像可能产生拉伸的根本原因。
    if (!supportedPreviewSizes.isEmpty()) {
        Camera.Size largestPreview = supportedPreviewSizes.get(0);
        Point largestSize = new Point(largestPreview.width, largestPreview.height);
        Log.i(TAG, "Using largest suitable preview size: " + largestSize);
        return largestSize;
    }

    // 如果没有找到合适的尺寸,就返回默认设定的尺寸
    Camera.Size defaultPreview = parameters.getPreviewSize();
    if (defaultPreview == null) {
        throw new IllegalStateException("Parameters contained no preview size!");
    }
    Point defaultSize = new Point(defaultPreview.width, defaultPreview.height);
    Log.i(TAG, "No suitable preview sizes, using default: " + defaultSize);
    return defaultSize;
}

总结一下Zxing 查找最佳的尺寸值得步骤如下

  的那个尺寸。如果说尺寸集合在前面的过滤中已经被全部排除,则返回相机默认的尺寸值。 

  • 查找设备支持的像素集合,如果集合为空则返回默认的尺寸,如果不为空就从小到大排列集合。
  • 排除不满足最小像素要求的所有的尺寸。
  • 在剩余的集合中去除预览宽高比和屏幕分辨率宽高比之差在0.15的所有尺寸
  •  寻找能够精确的与屏幕宽高匹配的预览尺寸,如果存在则返回该宽高比;如果不存在,则使用尺寸集合中最大

zxing需找最佳预览尺寸的前三步提出了部分不符合要求的尺寸集合,在最后一步,如果没有精确匹配到与屏幕分辨率一样的尺寸

则使用最大的尺寸,问题的关键就在这里,最大的尺寸宽高比与屏幕宽高比相差可能很大(根据前面的逻辑,差距可能超15%)

在这里我们修改需找最佳尺寸的源码,将从最大的尺寸改为最接近的尺寸,这样能够最原始的接近屏幕分辨率的宽高比,即使拉伸也几乎看不出来。操作如下,首先定义一个比较器,用来对支持的预览尺寸集合进行排序。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值