背景:前段时间公司有个需求,需要用app提取医院化验单上的有用数据,利用OCR识别技术,当然为了提高识别准确率,对OCR拍照界面实现自定义,去引导用户正确使用拍照,提高精确度

实现思路:给SurfaceView加背景,取我们需要的部分;拍摄照片分辨率设置,选择最佳;调整自动对焦和闪光灯

1、布局分为三个部分:引导页、预览界面、拍摄界面

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

<RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">


        <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="visible" />

        //实现拍照后预览
        <ImageView
            android:id="@+id/current_img"
            android:visibility="gone"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <View
            android:id="@+id/mTopView"
            android:layout_width="match_parent"
            android:layout_height="54dp"
            android:background="#ff000000" />


        <FrameLayout
            android:id="@+id/layout_camera"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@+id/mTopView"
            android:visibility="visible">
            
            //拍照背景图
            <RelativeLayout
                android:id="@+id/layout_button_btn"
                android:layout_width="309.5dp"
                android:layout_height="354dp"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="44dp"
                android:background="@mipmap/camera_content" />


        </FrameLayout>

        //底部工具栏
        <LinearLayout
            android:id="@+id/bottom_ll"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:background="#ff000000"
            android:gravity="center_vertical"
            android:orientation="horizontal"
            android:paddingTop="26dp"
            android:paddingBottom="26dp">

            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1" />

            <TextView
                android:id="@+id/btn_cancel"
                android:layout_width="42dp"
                android:layout_height="42dp"
                android:background="@drawable/circle_white"
                android:gravity="center"
                android:text="重拍"
                android:textColor="#ffffffff"
                android:textSize="9sp"
                android:visibility="gone" />

            <TextView
                android:id="@+id/btn_cancel1"
                android:layout_width="42dp"
                android:layout_height="42dp"
                android:background="@drawable/circle_white"
                android:gravity="center"
                android:text="取消"
                android:textColor="#ffffffff"
                android:textSize="9sp"
                android:visibility="visible" />

            <Button
                android:id="@+id/btn_take_photo"
                android:layout_width="66dp"
                android:layout_height="66dp"
                android:layout_marginLeft="50dp"
                android:layout_marginRight="50dp"
                android:background="@mipmap/take_photo_background" />

            <Button
                android:id="@+id/btn_finish"
                style="@style/text_white_18"
                android:layout_width="66dp"
                android:layout_height="66dp"
                android:layout_marginLeft="50dp"
                android:layout_marginRight="50dp"
                android:background="@mipmap/camera_save"
                android:text="@string/save"
                android:visibility="gone" />

            <Button
                android:id="@+id/flash_light"
                android:layout_width="42dp"
                android:layout_height="42dp"
                android:background="@mipmap/close_led"
                android:visibility="visible" />

            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1" />
        </LinearLayout>

        //首次进入提示用户拍照方式
        <LinearLayout
            android:id="@+id/is_show_hint_ll"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@+id/mTopView"
            android:background="#90000000">


            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginLeft="30dp"
                android:layout_marginRight="30dp"
                android:layout_marginBottom="38dp"
                android:background="@drawable/white_card_bck"
                android:gravity="center_horizontal"
                android:orientation="vertical"
                android:paddingLeft="12dp"
                android:paddingRight="12dp">

                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_horizontal"
                    android:layout_marginTop="16dp"
                    android:src="@mipmap/guide_ocr" />

                <TextView
                    style="@style/color_blue_20"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_horizontal"
                    android:layout_marginTop="17dp"
                    android:text="请将蓝色虚线与表头对齐" />


                <TextView
                    android:id="@+id/camera_go_on"
                    style="@style/text_666_16"
                    android:layout_width="match_parent"
                    android:layout_height="44dp"
                    android:layout_gravity="left"
                    android:layout_marginLeft="4dp"
                    android:layout_marginTop="44dp"
                    android:layout_marginRight="4dp"
                    android:background="@drawable/gray_btn"
                    android:gravity="center"
                    android:text="马上试一试" />

                <CheckBox
                    android:id="@+id/is_continue_hint"
                    style="@style/color_999_12"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="14dp"
                    android:layout_marginBottom="15dp"
                    android:text="下次使用不再提醒我" />

            </LinearLayout>
        </LinearLayout>
    </RelativeLayout>

</layout>

2、初始化surfaceView,设置自动对焦、并初始化相机、调整分辨率、闪光等,用完记得释放资源。

private void initCamera() {
        surfaceView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                if (!status && camera != null) {
                    //添加自动对焦回调
                    camera.autoFocus(autoFocusCallback);
                }
                return false;
            }
        });
        MySurfaceCallback mySurfaceCallback = new MySurfaceCallback();
        holder = surfaceView.getHolder();
        holder.setKeepScreenOn(true);// 屏幕常亮
        mSensorController = SensorController.getInstance(mActivity);
       mSensorController.setCameraFocusListener(newSensorController.CameraFocusListener() {
            @Override
            public void onFocus() {
                if (camera != null ) {
        DisplayMetricsdisplayMetrics=mActivity.getApplicationContext().getResources().getDisplayMetrics();
                    mMScreenWidth = displayMetrics.widthPixels;//获取屏幕参数
                    if (!mSensorController.isFocusLocked()) {
                        if (newFocus(mMScreenWidth / 2, mMScreenWidth / 2, camera)) {
                            mSensorController.lockFocus();//设置自动对焦后锁住
                        }
                    }
                }
            }
        });
        mSensorController.start();//对焦加速度传感器
        holder.addCallback(mySurfaceCallback);//添加拍摄回调
        holder.lockCanvas();//锁住画布
    }

    
    private boolean newFocus(int x, int y ,Camera mCamera) {
        //正在对焦时返回
        if (mCamera == null || isFocusing) {
            return false;
        }
        isFocusing = true;
        Camera.Parameters mCameraParameters = mCamera.getParameters();
        setMeteringRect(x, y,mCameraParameters);
        mCameraParameters.setExposureCompensation(15);
        mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        mCamera.cancelAutoFocus(); // 先要取消掉进程中所有的聚焦功能
        try {
            mCamera.setParameters(mCameraParameters);
            mCamera.autoFocus(autoFocusCallback);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

 /**
     * 设置感光区域
     * 需要将屏幕坐标映射到Rect对象对应的单元格矩形
     *
     * @param x
     * @param y
     */
    private void setMeteringRect(int x, int y, Camera.Parameters mCameraParameters) {
        if (mCameraParameters.getMaxNumMeteringAreas() > 0) {
            List<Camera.Area> areas  = new ArrayList<Camera.Area>();
            Rect              rect   = new Rect(x - 100, y - 100, x + 100, y + 100);
            int               left   = rect.left * 2000 / mMScreenWidth - 1000;
            int               top    = rect.top * 2000 / mMScreenWidth - 1000;
            int               right  = rect.right * 2000 / mMScreenWidth - 1000;
            int               bottom = rect.bottom * 2000 / mMScreenWidth - 1000;
            // 如果超出了(-1000,1000)到(1000, 1000)的范围,则会导致相机崩溃
            left = left < -1000 ? -1000 : left;
            top = top < -1000 ? -1000 : top;
            right = right > 1000 ? 1000 : right;
            bottom = bottom > 1000 ? 1000 : bottom;
            Rect area1 = new Rect(left, top, right, bottom);
            //只有一个感光区,直接设置权重为1000了
            areas.add(new Camera.Area(area1, 400));

            //在图像的中心指定一个测光区域
            Rect areaRect1 = new Rect(-100, -100, 100, 100);
            //设置权重为600,最高1000
            areas.add(new Camera.Area(areaRect1, 600));
            mCameraParameters.setMeteringAreas(areas);
        }
    }

private void initCameraParamsAndOpen() {
        try {
            // surfaceview创建之后,就去打开相机
            camera = getCameraInstance();
            camera.setPreviewDisplay(holder);
            updateCameraParameters(camera);
            camera.startPreview();
        } catch (Exception e) {
            if (camera != null) {
                camera.release();
            }
            e.printStackTrace();
        }
    }


//定义surface回调
 class MySurfaceCallback implements SurfaceHolder.Callback
    {
        @Override
        public void surfaceCreated(SurfaceHolder surfaceHolder) {
            if(camera !=null) {
                camera.release();
                camera = null;
            }

            initCameraParamsAndOpen();//重新初始化相机
        }

        @Override
        public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
            //当surface的格式或大小发生改变,这个方法就被调用,或者View被隐藏
            status = false;
            btnTakePhoto.setVisibility(View.VISIBLE);  //拍照按钮显示
            btnFinish.setVisibility(View.GONE); //拍照完成按钮隐藏
            btnCancel.setVisibility(View.GONE);
            if(camera !=null) {
                camera.release();
                camera = null;
            }
            initCameraParamsAndOpen();  //重新初始化相机
        }

        //释放资源
        @Override
        public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
            if (camera != null) {
                camera.stopPreview();
                camera.release();
                mSensorController.stop();
                camera = null;
            }
        }
    }


Camera.PictureCallback myPictureCallback = new Camera.PictureCallback(){
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
           Log.e("lenita", " onPictureTaken");
            //卡住预览页面,让用户看
            camera.stopPreview();
            //保存图片
            tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
            if (tempBitmap != null) {
                saveBitmap(tempBitmap);
            }

        }
    };

    //保存图片
    public void saveBitmap(Bitmap bitmap) {
        if(tempFile != null){
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();

            if(width < height) {
                bitmap = Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight());
            }else {
                Matrix matrix = new Matrix();
                matrix.postRotate(90);//旋转90度,解决在有些机型系统相机拍摄横屏分问题
                bitmap = Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
            }
            try {
                FileOutputStream out = new FileOutputStream(tempFile);
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
                out.flush();
                out.close();
                currentImg.setImageBitmap(bitmap);
                currentImg.setVisibility(View.VISIBLE);
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                Log.e("lenita","saveBitmap e = "+e);
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                Log.e("lenita","saveBitmap e = "+e);
                e.printStackTrace();
            }
        }else {
            Log.e("save", "tempFile == null");
        }

    }


private Handler mHandler = new Handler();
    Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
        @Override
        public void onAutoFocus(boolean b, Camera camera) {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    //一秒之后才能再次对焦
                    isFocusing = false;
                    mSensorController.unlockFocus();
                }
            }, 1000);
        }

    };

    public static Camera getCameraInstance() {
        Camera c = null;
        try {
            c = Camera.open(0); // attempt to get a Camera instance
        } catch (Exception e) {
            // Camera is not available (in use or does not exist)
        }
        return c;
    }

3、做到这一步已经基本可以完成拍照并上传到服务器进行识别了,但是经过多种机型测试,发现在有些手机上会出现一些问题:照片质量很模糊,所以就需要做适配。以下是修改后的适配方案,适配之前的代码已经删掉了(PS:所以一定要养成一个善于记录笔记的良好习惯) 

private Point mScreenResolution;//屏幕分辨率
    private Point previewSizeOnScreen;//相机预览尺寸
    private Point pictureSizeOnScreen;//图片尺寸


    //设置相机参数,拍照尺寸、预览尺寸等
    private void updateCameraParameters(Camera camera) {
        if (camera != null) {
            Camera.Parameters parameters = camera.getParameters();
            WindowManager windowManager = (WindowManager) EmpApp.getInstance().getSystemService(Context.WINDOW_SERVICE);
            Display display = windowManager.getDefaultDisplay();
            Point theScreenResolution = new Point();
            display.getSize(theScreenResolution);//得到屏幕的尺寸,单位是像素
            mScreenResolution = theScreenResolution;
            previewSizeOnScreen = CameraUtils.findBestPreviewSizeValue(parameters, theScreenResolution);//通过相机尺寸、屏幕尺寸来得到最好的展示尺寸,此尺寸为相机的
            parameters.setPreviewSize(previewSizeOnScreen.x, previewSizeOnScreen.y);
            pictureSizeOnScreen = CameraUtils.findBestPictureSizeValue(parameters, theScreenResolution);//通过相机尺寸、屏幕尺寸来得到最好的展示尺寸,此尺寸为相机的
            parameters.setPictureSize(pictureSizeOnScreen.x, pictureSizeOnScreen.y);
            boolean isScreenPortrait = mScreenResolution.x < mScreenResolution.y;
            boolean isPreviewSizePortrait = previewSizeOnScreen.x < previewSizeOnScreen.y;
            if (isScreenPortrait != isPreviewSizePortrait) {//相机与屏幕一个方向,则使用相机尺寸
                previewSizeOnScreen = new Point(previewSizeOnScreen.y, previewSizeOnScreen.x);//否则翻个
            }
            // 设置照片的格式
            parameters.setPictureFormat(ImageFormat.JPEG);
            parameters.setRotation(90);//防止保存的图片旋转,有些手机在设置了这个仍然会横屏,因为有些厂商改动了底层
            CameraUtils.setFocus(parameters, true, false, true);//设置相机对焦模式
            CameraUtils.setBarcodeSceneMode(parameters, Camera.Parameters.SCENE_MODE_BARCODE);//设置相机场景模式
            CameraUtils.setBestPreviewFPS(parameters);//设置相机帧数
            camera.setParameters(parameters);
            // 系统相机默认是横屏的,我们要旋转90°
            camera.setDisplayOrientation(90);

        }
    }

4、还有个问题是在拍照和预览的时候,有些手机显示有拉伸的情况,所以还需要做屏幕适配

在项目引入:

implementation 'com.github.MrRightChen:ScreenAdapter:v1.0.0'

自定义 AdapterLinearLayout 继承LinearLayout,重写onMeasure()方法,根据屏幕分辨率和UI设计尺寸重新计算每个控件的大小

public class AdapterLinearLayout extends LinearLayout {
    private static boolean isFlag = true;
    public AdapterLinearLayout(Context context) {
        super(context);
    }

    public AdapterLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public AdapterLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        if(isFlag){
            int count = this.getChildCount();
            float scaleX =  UiUtils.getInstance(this.getContext()).getHorizontalScaleValue();
            float scaleY =  UiUtils.getInstance(this.getContext()).getVerticalScaleValue();

            Log.i("","x系数:"+scaleX);
            Log.i("","y系数:"+scaleY);
            for (int i = 0;i < count;i++){
                //获得当前布局的子布局
                View child = this.getChildAt(i);
                //代表的是当前空间的所有属性列表
                LayoutParams layoutParams = ( LayoutParams) child.getLayoutParams();
                int matchParent = LayoutParams.MATCH_PARENT;
                int wrapContent = LayoutParams.WRAP_CONTENT;
                if (layoutParams.width!=LayoutParams.MATCH_PARENT&&layoutParams.width!=LayoutParams.WRAP_CONTENT){
                    layoutParams.width = (int) (layoutParams.width * scaleX);
                }
                if (layoutParams.height!=LayoutParams.MATCH_PARENT&&layoutParams.height!=LayoutParams.WRAP_CONTENT){
                    layoutParams.height = (int) (layoutParams.height * scaleY);
                }
                layoutParams.rightMargin = (int) (layoutParams.rightMargin * scaleX);
                layoutParams.leftMargin = (int) (layoutParams.leftMargin * scaleX);
                layoutParams.topMargin = (int) (layoutParams.topMargin * scaleY);
                layoutParams.bottomMargin = (int) (layoutParams.bottomMargin * scaleY);
            }
            isFlag = false;
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);


    }

}

计算缩放比例

/**
 * 获得缩放系数
 * 假定Ui设计为1920 * 1080
 */
public class UiUtils {

    private Context mContext;
    private static UiUtils mInstance;
    //UI 设计尺寸
    public final float STANDARD_WIDTH = 1080f;
    public final float STANDARD_HEIGHT = 1920;
    private final String DIMEN_CLASS = "com.android.internal.R$dimen";

    //当前设备实际宽高
    public float mDisplayMetricsWidth ;
    public float mDisplayMetricsHeight ;
    //单例模式
    public static UiUtils getInstance(Context context){
        if (mInstance==null){
            mInstance = new UiUtils(context);
        }
        return mInstance;
    }

    //私有构造方法
    private UiUtils(Context mContext) {
        this.mContext = mContext;
        //拿到窗口管理器
        WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        //加载当前界面信息
        DisplayMetrics displayMetrics =new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);

        if (mDisplayMetricsWidth == 0.0f || mDisplayMetricsHeight == 0.0f){

            //获取状态栏的高度
            int systemBarHeight = getValue(mContext,"system_bar_height",48);
            if(displayMetrics.widthPixels > displayMetrics.heightPixels){
                this.mDisplayMetricsWidth = displayMetrics.heightPixels;
                this.mDisplayMetricsHeight = displayMetrics.widthPixels - systemBarHeight;
            }else{
                this.mDisplayMetricsWidth = displayMetrics.widthPixels;
                this.mDisplayMetricsHeight = displayMetrics.heightPixels - systemBarHeight;
            }
        }
    }
    //获取状态栏高度
    public int getValue(Context context,String systemid,int defValue) {

        try {
            Class<?> clazz = Class.forName(DIMEN_CLASS);
            Object r = clazz.newInstance();
            Field field = clazz.getField(systemid);
            int x = (int) field.get(r);
            return context.getResources().getDimensionPixelOffset(x);

        } catch (Exception e) {
            return defValue;
        }
    }
    //获取水平的缩放系数
    public float getHorizontalScaleValue(){
        return mDisplayMetricsWidth / STANDARD_WIDTH;
    }

    //获取垂直缩放系数
    public float getVerticalScaleValue(){

        Log.i("","displayMetricsHeight:"+mDisplayMetricsHeight);
        return mDisplayMetricsHeight / STANDARD_HEIGHT;
    }

}

然后将布局文件里所有的LinearLayout、RelativeLayout、FrameLayout替换成相应的Adapter......Layout

上结果截图

android 拍照URi android 拍照捕捉亮点_android

android 拍照URi android 拍照捕捉亮点_2d_02

 

我是新手,有什么疑问或建议欢迎评论区指出,共同讨论,一起进步。