Android 识别二代身份证证件信息与Camera取景

识别身份证信息需要用到图像处理的知识,无奈水平不够,于是寻找第三方接口,但是找到的都是收费的,后来找到一个叫云脉的产品http://ocr.ccyunmai.com/,发现他可以免费使用15天,但是15天后就是按识别次数收费的,其价格十分昂贵,0.3元/次,对于苦逼的穷屌来说,这真是天价啊。

但是皇天不负有心人,云脉提供了一个在线识别的demo,其地址为http://ocr.ccyunmai.com/idcard/,但是这个地址有什么用呢,别急,作为专业抓数据出身的来说,我们可以利用该地址进行识别,而且不费一分钱。先打开该网址看看长什么样

我们利用云脉提供的一张测试图片上传进行测试

在上传前,记得打开开发者模式,Chrome里按F12,切换到NetWork,点击上传,上传完成后会返回识别结果,找到UploadImg.action

点击查看其请求体

我们着重看红色方框里的内容,只有我们将这些信息提供给该接口http://ocr.ccyunmai.com/UploadImg.action,只要身份证图片正确,它便会给我们返回识别信息。我们要做的就是用程序模拟这个过程。

请求体里需要传递HostOriginRefererUser-Agent,其直直接从浏览器得到的信息中复制即可,请求方式是POSTPOST的内容分为三部分,一个是callbackurl,其值为/idcard/,一个是action,其值为idcard,还有一个就是上传的文件了,叫做img,其文件名就是我们上传的文件名,这里我的文件是test-idcard.jpg,然后其Content-Typeimage/jpeg,接下来我们来模拟这个过程。

我们使用OkHttp作为网络层,结合之前的文章Android OkHttp文件上传与下载的进度监听扩展进行扩展。

增加gradle依赖

compile ‘com.squareup.okhttp:okhttp:2.5.0‘
compile ‘cn.edu.zafu:coreprogress:0.0.1‘
compile ‘org.jsoup:jsoup:1.8.3‘

我们看到还依赖了jsoup,其实后续会用到它进行解析返回结果。

需要使用网络进行上传,并且需要文件的读取,增加这两个权限

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

初始化OkHttp,避免超时,设置超时时间

OkHttpClient mOkHttpClient=new OkHttpClient();
private void initClient() {
    mOkHttpClient.setConnectTimeout(1000, TimeUnit.MINUTES);
    mOkHttpClient.setReadTimeout(1000, TimeUnit.MINUTES);
    mOkHttpClient.setWriteTimeout(1000, TimeUnit.MINUTES);
}

然后我们需要对一个变量进行赋值,让它存储本地的身份证图片,其值为文件路径

private String mPhotoPath="文件路径";

接下来开始构造请求头和POST的信息,上传文件的过程中需要监听进度,所以这里使用了前文所说的库

private void uploadAndRecognize() {
    if (!TextUtils.isEmpty(mPhotoPath)){
        File file=new File(mPhotoPath);
        //构造请求体
        RequestBody requestBody = new MultipartBuilder().type(MultipartBuilder.FORM)
                .addPart(Headers.of("Content-Disposition", "form-data; name=\"callbackurl\""), RequestBody.create(null, "/idcard/"))
                .addPart(Headers.of("Content-Disposition", "form-data; name=\"action\""), RequestBody.create(null, "idcard"))
                .addPart(Headers.of("Content-Disposition", "form-data; name=\"img\"; filename=\"idcardFront_user.jpg\""), RequestBody.create(MediaType.parse("image/jpeg"), file))
                .build();
        //这个是ui线程回调,可直接操作UI
        final UIProgressRequestListener uiProgressRequestListener = new UIProgressRequestListener() {
            @Override
            public void onUIRequestProgress(long bytesWrite, long contentLength, boolean done) {
                Log.e("TAG", "bytesWrite:" + bytesWrite);
                Log.e("TAG", "contentLength" + contentLength);
                Log.e("TAG", (100 * bytesWrite) / contentLength + " % done ");
                Log.e("TAG", "done:" + done);
                Log.e("TAG", "================================");
                //ui层回调
                mProgressBar.setProgress((int) ((100 * bytesWrite) / contentLength));
            }
        };
        //构造请求头
        final Request request = new Request.Builder()
                .header("Host", "ocr.ccyunmai.com")
                .header("Origin", "http://ocr.ccyunmai.com")
                .header("Referer", "http://ocr.ccyunmai.com/idcard/")
                .header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2398.0 Safari/537.36")
                .url("http://ocr.ccyunmai.com/UploadImg.action")
                .post(ProgressHelper.addProgressRequestListener(requestBody, uiProgressRequestListener))
                .build();
        //开始请求
        mOkHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                Log.e("TAG", "error");
            }
            @Override
            public void onResponse(Response response) throws IOException {
                String result=response.body().string();
            }
        });
    }
}

请求成功后在onResponse里会进行回调,局部变量拿到的就是最终的返回结果。

我们在浏览器中查看下返回的信息的源代码,以进一步便于解析识别结果

返回结果被包含在一个div中,其classleft,在div中还包含了一层fieldset,识别结果就在这里面,于是,我们对result进行解析,使用的就是前面加入的依赖Jsoup

String result=response.body().string();
Document parse = Jsoup.parse(result);
Elements select = parse.select("div.left fieldset");
Log.e("TAG",select.text());
Document parse1 = Jsoup.parse(select.text());
StringBuilder builder=new StringBuilder();
String name=parse1.select("name").text();
String cardno=parse1.select("cardno").text();
String sex=parse1.select("sex").text();
String folk=parse1.select("folk").text();
String birthday=parse1.select("birthday").text();
String address=parse1.select("address").text();
String issue_authority=parse1.select("issue_authority").text();
String valid_period=parse1.select("valid_period").text();
builder.append("name:"+name)
        .append("\n")
        .append("cardno:" + cardno)
        .append("\n")
        .append("sex:" + sex)
        .append("\n")
        .append("folk:" + folk)
        .append("\n")
        .append("birthday:" + birthday)
        .append("\n")
        .append("address:" + address)
        .append("\n")
        .append("issue_authority:" + issue_authority)
        .append("\n")
        .append("valid_period:" + valid_period)
        .append("\n");
Log.e("TAG", "name:" + name);
Log.e("TAG","cardno:"+cardno);
Log.e("TAG","sex:"+sex);
Log.e("TAG","folk:"+folk);
Log.e("TAG","birthday:"+birthday);
Log.e("TAG","address:"+address);
Log.e("TAG","issue_authority:"+issue_authority);
Log.e("TAG","valid_period:"+valid_period);

很简单有木有,所有信息都被抽取出来了,看下Log,看下识别结果是什么。

其实识别的还是挺准的。识别是可以识别了,但是我们希望能够自己拍摄照片然后上传识别,就像这样子。

这个就涉及到Android Camera和SurfaceView的知识了。在这之前,我们先编写一个自动对焦的管理类。


/**
 * 自动对焦
 * User:lizhangqu(513163535@qq.com)
 * Date:2015-09-05
 * Time: 11:11
 */
public class AutoFocusManager implements Camera.AutoFocusCallback{
    private static final String TAG = AutoFocusManager.class.getSimpleName();
    private static final long AUTO_FOCUS_INTERVAL_MS = 2000L;
    private static final Collection<String> FOCUS_MODES_CALLING_AF;
    static {
        FOCUS_MODES_CALLING_AF = new ArrayList<String>(2);
        FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_AUTO);
        FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_MACRO);
    }

    private boolean stopped;
    private boolean focusing;
    private final boolean useAutoFocus;
    private final Camera camera;
    private AsyncTask<?,?,?> outstandingTask;

    public AutoFocusManager(Camera camera) {
        this.camera = camera;
        String currentFocusMode = camera.getParameters().getFocusMode();
        useAutoFocus = FOCUS_MODES_CALLING_AF.contains(currentFocusMode);
        Log.e(TAG, "Current focus mode ‘" + currentFocusMode + "‘; use auto focus? " + useAutoFocus);
        start();
    }

    @Override
    public synchronized void onAutoFocus(boolean success, Camera theCamera) {
        focusing = false;
        autoFocusAgainLater();
    }

    private synchronized void autoFocusAgainLater() {
        if (!stopped && outstandingTask == null) {
            AutoFocusTask newTask = new AutoFocusTask();
            try {
                newTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
                outstandingTask = newTask;
            } catch (RejectedExecutionException ree) {
                Log.e(TAG, "Could not request auto focus", ree);
            }
        }
    }

    /**
     * 开始自动对焦
     */
    public synchronized void start() {
        if (useAutoFocus) {
            outstandingTask = null;
            if (!stopped && !focusing) {
                try {
                    camera.autoFocus(this);
                    focusing = true;
                } catch (RuntimeException re) {
                    // Have heard RuntimeException reported in Android 4.0.x+; continue?
                    Log.e(TAG, "Unexpected exception while focusing", re);
                    // Try again later to keep cycle going
                    autoFocusAgainLater();
                }
            }
        }
    }

    private synchronized void cancelOutstandingTask() {
        if (outstandingTask != null) {
            if (outstandingTask.getStatus() != AsyncTask.Status.FINISHED) {
                outstandingTask.cancel(true);
            }
            outstandingTask = null;
        }
    }

    /**
     * 停止自动对焦
     */
    public synchronized void stop() {
        stopped = true;
        if (useAutoFocus) {
            cancelOutstandingTask();
            // Doesn‘t hurt to call this even if not focusing
            try {
                camera.cancelAutoFocus();
            } catch (RuntimeException re) {
                // Have heard RuntimeException reported in Android 4.0.x+; continue?
                Log.e(TAG, "Unexpected exception while cancelling focusing", re);
            }
        }
    }

    private final class AutoFocusTask extends AsyncTask<Object,Object,Object> {
        @Override
        protected Object doInBackground(Object... voids) {
            try {
                Thread.sleep(AUTO_FOCUS_INTERVAL_MS);
            } catch (InterruptedException e) {
                // continue
            }
            start();
            return null;
        }
    }
}

其实这个类是从Zxing中提取出来的,其功能就是每隔一段时间进行自动对焦,看代码就能看懂,这里不再累赘。

接下来就是和Camera相关的管理类,这个类也是从Zxing中提取出来进行了精简


/**
 * Camera管理类
 * User:lizhangqu([email protected])
 * Date:2015-09-05
 * Time: 10:56
 */
public class CameraManager {
    private static final String TAG = CameraManager.class.getName();
    private Camera camera;
    private Camera.Parameters parameters;
    private AutoFocusManager autoFocusManager;
    private int requestedCameraId = -1;

    private boolean initialized;
    private boolean previewing;

    /**
     * 打开摄像头
     *
     * @param cameraId 摄像头id
     * @return Camera
     */
    public Camera open(int cameraId) {
        int numCameras = Camera.getNumberOfCameras();
        if (numCameras == 0) {
            Log.e(TAG, "No cameras!");
            return null;
        }
        boolean explicitRequest = cameraId >= 0;
        if (!explicitRequest) {
            // Select a camera if no explicit camera requested
            int index = 0;
            while (index < numCameras) {
                Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
                Camera.getCameraInfo(index, cameraInfo);
                if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                    break;
                }
                index++;
            }
            cameraId = index;
        }
        Camera camera;
        if (cameraId < numCameras) {
            Log.e(TAG, "Opening camera #" + cameraId);
            camera = Camera.open(cameraId);
        } else {
            if (explicitRequest) {
                Log.e(TAG, "Requested camera does not exist: " + cameraId);
                camera = null;
            } else {
                Log.e(TAG, "No camera facing back; returning camera #0");
                camera = Camera.open(0);
            }
        }
        return camera;
    }

    /**
     * 打开camera
     *
     * @param holder SurfaceHolder
     * @throws IOException IOException
     */
    public synchronized void openDriver(SurfaceHolder holder)
            throws IOException {
        Log.e(TAG, "openDriver");
        Camera theCamera = camera;
        if (theCamera == null) {
            theCamera = open(requestedCameraId);
            if (theCamera == null) {
                throw new IOException();
            }
            camera = theCamera;
        }
        theCamera.setPreviewDisplay(holder);

        if (!initialized) {
            initialized = true;
            parameters = camera.getParameters();
            parameters.setPreviewSize(800, 600);
            parameters.setPictureFormat(ImageFormat.JPEG);
            parameters.setJpegQuality(100);
            parameters.setPictureSize(800, 600);
            theCamera.setParameters(parameters);
        }
    }

    /**
     * camera是否打开
     *
     * @return camera是否打开
     */
    public synchronized boolean isOpen() {
        return camera != null;
    }

    /**
     * 关闭camera
     */
    public synchronized void closeDriver() {
        Log.e(TAG, "closeDriver");
        if (camera != null) {
            camera.release();
            camera = null;
        }
    }

    /**
     * 开始预览
     */
    public synchronized void startPreview() {
        Log.e(TAG, "startPreview");
        Camera theCamera = camera;
        if (theCamera != null && !previewing) {
            theCamera.startPreview();
            previewing = true;
            autoFocusManager = new AutoFocusManager(camera);
        }
    }

    /**
     * 关闭预览
     */
    public synchronized void stopPreview() {
        Log.e(TAG, "stopPreview");
        if (autoFocusManager != null) {
            autoFocusManager.stop();
            autoFocusManager = null;
        }
        if (camera != null && previewing) {
            camera.stopPreview();
            previewing = false;
        }
    }

    /**
     * 打开闪光灯
     */
    public synchronized void openLight() {
        Log.e(TAG, "openLight");
        if (camera != null) {
            parameters = camera.getParameters();
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
            camera.setParameters(parameters);
        }
    }

    /**
     * 关闭闪光灯
     */
    public synchronized void offLight() {
        Log.e(TAG, "offLight");
        if (camera != null) {
            parameters = camera.getParameters();
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
            camera.setParameters(parameters);
        }
    }

    /**
     * 拍照
     *
     * @param shutter ShutterCallback
     * @param raw     PictureCallback
     * @param jpeg    PictureCallback
     */
    public synchronized void takePicture(final Camera.ShutterCallback shutter, final Camera.PictureCallback raw,
                                         final Camera.PictureCallback jpeg) {

        camera.takePicture(shutter, raw, jpeg);

    }
}

我们看到上面的截图的取景框是蓝色边框,上面还有一行提示的字,这是个自定义的SurfaceView,我们需要自己去实现绘制逻辑


/**
 * 边框绘制
 * User:lizhangqu([email protected])
 * Date:2015-09-04
 * Time: 18:03
 */
public class PreviewBorderView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    private int mScreenH;
    private int mScreenW;
    private Canvas mCanvas;
    private Paint mPaint;
    private Paint mPaintLine;
    private SurfaceHolder mHolder;
    private Thread mThread;
    private static final String DEFAULT_TIPS_TEXT = "请将方框对准证件拍摄";
    private static final int DEFAULT_TIPS_TEXT_SIZE = 16;
    private static final int DEFAULT_TIPS_TEXT_COLOR = Color.GREEN;
    /**
     * 自定义属性
     */
    private float tipTextSize;
    private int tipTextColor;
    private String tipText;

    public PreviewBorderView(Context context) {
        this(context, null);
    }

    public PreviewBorderView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PreviewBorderView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);
        init();
    }

    /**
     * 初始化自定义属性
     *
     * @param context Context
     * @param attrs   AttributeSet
     */
    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PreviewBorderView);
        try {
            tipTextSize = a.getDimension(R.styleable.PreviewBorderView_tipTextSize, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_TIPS_TEXT_SIZE, getResources().getDisplayMetrics()));
            tipTextColor = a.getColor(R.styleable.PreviewBorderView_tipTextColor, DEFAULT_TIPS_TEXT_COLOR);
            tipText = a.getString(R.styleable.PreviewBorderView_tipText);
            if (tipText == null) {
                tipText = DEFAULT_TIPS_TEXT;
            }
        } finally {
            a.recycle();
        }

    }

    /**
     * 初始化绘图变量
     */
    private void init() {
        this.mHolder = getHolder();
        this.mHolder.addCallback(this);
        this.mHolder.setFormat(PixelFormat.TRANSPARENT);
        setZOrderOnTop(true);
        this.mPaint = new Paint();
        this.mPaint.setAntiAlias(true);
        this.mPaint.setColor(Color.WHITE);
        this.mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        this.mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        this.mPaintLine = new Paint();
        this.mPaintLine.setColor(tipTextColor);
        this.mPaintLine.setStrokeWidth(3.0F);
        setKeepScreenOn(true);
    }

    /**
     * 绘制取景框
     */
    private void draw() {
        try {
            this.mCanvas = this.mHolder.lockCanvas();
            this.mCanvas.drawARGB(100, 0, 0, 0);
            this.mScreenW = (this.mScreenH * 4 / 3);
            Log.e("TAG","mScreenW:"+mScreenW+" mScreenH:"+mScreenH);
            this.mCanvas.drawRect(new RectF(this.mScreenW / 2 - this.mScreenH * 2 / 3 + this.mScreenH * 1 / 6, this.mScreenH * 1 / 6, this.mScreenW / 2 + this.mScreenH * 2 / 3 - this.mScreenH * 1 / 6, this.mScreenH - this.mScreenH * 1 / 6), this.mPaint);
            this.mCanvas.drawLine(this.mScreenW / 2 - this.mScreenH * 2 / 3 + this.mScreenH * 1 / 6, this.mScreenH * 1 / 6, this.mScreenW / 2 - this.mScreenH * 2 / 3 + this.mScreenH * 1 / 6, this.mScreenH * 1 / 6 + 50, this.mPaintLine);
            this.mCanvas.drawLine(this.mScreenW / 2 - this.mScreenH * 2 / 3 + this.mScreenH * 1 / 6, this.mScreenH * 1 / 6, this.mScreenW / 2 - this.mScreenH * 2 / 3 + this.mScreenH * 1 / 6 + 50, this.mScreenH * 1 / 6, this.mPaintLine);
            this.mCanvas.drawLine(this.mScreenW / 2 + this.mScreenH * 2 / 3 - this.mScreenH * 1 / 6, this.mScreenH * 1 / 6, this.mScreenW / 2 + this.mScreenH * 2 / 3 - this.mScreenH * 1 / 6, this.mScreenH * 1 / 6 + 50, this.mPaintLine);
            this.mCanvas.drawLine(this.mScreenW / 2 + this.mScreenH * 2 / 3 - this.mScreenH * 1 / 6, this.mScreenH * 1 / 6, this.mScreenW / 2 + this.mScreenH * 2 / 3 - this.mScreenH * 1 / 6 - 50, this.mScreenH * 1 / 6, this.mPaintLine);
            this.mCanvas.drawLine(this.mScreenW / 2 - this.mScreenH * 2 / 3 + this.mScreenH * 1 / 6, this.mScreenH - this.mScreenH * 1 / 6, this.mScreenW / 2 - this.mScreenH * 2 / 3 + this.mScreenH * 1 / 6, this.mScreenH - this.mScreenH * 1 / 6 - 50, this.mPaintLine);
            this.mCanvas.drawLine(this.mScreenW / 2 - this.mScreenH * 2 / 3 + this.mScreenH * 1 / 6, this.mScreenH - this.mScreenH * 1 / 6, this.mScreenW / 2 - this.mScreenH * 2 / 3 + this.mScreenH * 1 / 6 + 50, this.mScreenH - this.mScreenH * 1 / 6, this.mPaintLine);
            this.mCanvas.drawLine(this.mScreenW / 2 + this.mScreenH * 2 / 3 - this.mScreenH * 1 / 6, this.mScreenH - this.mScreenH * 1 / 6, this.mScreenW / 2 + this.mScreenH * 2 / 3 - this.mScreenH * 1 / 6, this.mScreenH - this.mScreenH * 1 / 6 - 50, this.mPaintLine);
            this.mCanvas.drawLine(this.mScreenW / 2 + this.mScreenH * 2 / 3 - this.mScreenH * 1 / 6, this.mScreenH - this.mScreenH * 1 / 6, this.mScreenW / 2 + this.mScreenH * 2 / 3 - this.mScreenH * 1 / 6 - 50, this.mScreenH - this.mScreenH * 1 / 6, this.mPaintLine);

            mPaintLine.setTextSize(tipTextSize);
            mPaintLine.setAntiAlias(true);
            mPaintLine.setDither(true);
            float length = mPaintLine.measureText(tipText);
            this.mCanvas.drawText(tipText, this.mScreenW / 2 - this.mScreenH * 2 / 3 + this.mScreenH * 1 / 6 + this.mScreenH / 2 - length / 2, this.mScreenH * 1 / 6 - tipTextSize, mPaintLine);
            Log.e("TAG", "left:" + (this.mScreenW / 2 - this.mScreenH * 2 / 3 + this.mScreenH * 1 / 6));
            Log.e("TAG", "top:" + (this.mScreenH * 1 / 6));
            Log.e("TAG", "right:" + (this.mScreenW / 2 + this.mScreenH * 2 / 3 - this.mScreenH * 1 / 6));
            Log.e("TAG", "bottom:" + (this.mScreenH - this.mScreenH * 1 / 6));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (this.mCanvas != null) {
                this.mHolder.unlockCanvasAndPost(this.mCanvas);
            }
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        //获得宽高,开启子线程绘图
        this.mScreenW = getWidth();
        this.mScreenH = getHeight();
        this.mThread = new Thread(this);
        this.mThread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        //停止线程
        try {
            mThread.interrupt();
            mThread = null;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        //子线程绘图
        draw();
    }
}

这里面用到了图形的混合模式PorterDuff.Mode.CLEAR,注意SurfeceView的绘制是可以在子线程中进行的,还有一点就是取景宽外围的整个SurfaceView的宽高比例是4:3,这个和Camera的预览和图像的比例设置的一样,避免图形预览变形。这个类的绘制逻辑并不复杂,只不过绘制的长度等信息需要测量过。

还有几个自定义属性

<resources>
    <declare-styleable name="PreviewBorderView">
        <attr name="tipText" format="string"/>
        <attr name="tipTextColor" format="color|reference"/>
        <attr name="tipTextSize" format="dimension"/>
    </declare-styleable>
</resources>

最后剩下的就是预览并拍照的Activity了,里面有几个辅助方法用于获取长宽,然后重置了布局文件里的控件的长宽比例为4:3,并且这个Activity需要给调用者返回结果,返回的数据可能有点大,Bundle传递数据最大不能超过1M,于是这里直接传递保存的文件的路径回去。在onCreate里进行了Intent的获取,获取调用方传来的参数,如果没有传过来,则使用默认值。

里面有两个按钮,一个是拍照的,一个是打开或关闭闪光灯的,设置了事件监听并调用对应的方法,拍照需要传递一个回调,这个回调里面进行了数据的存储与返回调用方结果。其他的都是一些初始化和销毁的动作了,看下源码就知道了。


public class CameraActivity extends AppCompatActivity implements SurfaceHolder.Callback {
    private LinearLayout mLinearLayout;
    private PreviewBorderView mPreviewBorderView;
    private SurfaceView mSurfaceView;

    private CameraManager cameraManager;
    private boolean hasSurface;
    private Intent mIntent;
    private static final String DEFAULT_PATH = "/sdcard/";
    private static final String DEFAULT_NAME = "default.jpg";
    private static final String DEFAULT_TYPE = "default";
    private String filePath;
    private String fileName;
    private String type;
    private Button take, light;
    private boolean toggleLight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);
        initIntent();
        initLayoutParams();
    }

    private void initIntent() {
        mIntent = getIntent();
        filePath = mIntent.getStringExtra("path");
        fileName = mIntent.getStringExtra("name");
        type = mIntent.getStringExtra("type");
        if (filePath == null) {
            filePath = DEFAULT_PATH;
        }
        if (fileName == null) {
            fileName = DEFAULT_NAME;
        }
        if (type == null) {
            type = DEFAULT_TYPE;
        }
        Log.e("TAG", filePath + "/" + fileName + "_" + type);
    }

    /**
     * 重置surface宽高比例为3:4,不重置的话图形会拉伸变形
     */
    private void initLayoutParams() {
        take = (Button) findViewById(R.id.take);
        light = (Button) findViewById(R.id.light);
        take.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                cameraManager.takePicture(null, null, myjpegCallback);
            }
        });
        light.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!toggleLight) {
                    toggleLight = true;
                    cameraManager.openLight();
                } else {
                    toggleLight = false;
                    cameraManager.offLight();
                }
            }
        });

        //重置宽高,3:4
        int widthPixels = getScreenWidth(this);
        int heightPixels = getScreenHeight(this);
        mLinearLayout = (LinearLayout) findViewById(R.id.linearlaout);
        mPreviewBorderView = (PreviewBorderView) findViewById(R.id.borderview);
        mSurfaceView = (SurfaceView) findViewById(R.id.surfaceview);

        RelativeLayout.LayoutParams surfaceviewParams = (RelativeLayout.LayoutParams) mSurfaceView.getLayoutParams();
        surfaceviewParams.width = heightPixels * 4 / 3;
        surfaceviewParams.height = heightPixels;
        mSurfaceView.setLayoutParams(surfaceviewParams);

        RelativeLayout.LayoutParams borderViewParams = (RelativeLayout.LayoutParams) mPreviewBorderView.getLayoutParams();
        borderViewParams.width = heightPixels * 4 / 3;
        borderViewParams.height = heightPixels;
        mPreviewBorderView.setLayoutParams(borderViewParams);

        RelativeLayout.LayoutParams linearLayoutParams = (RelativeLayout.LayoutParams) mLinearLayout.getLayoutParams();
        linearLayoutParams.width = widthPixels - heightPixels * 4 / 3;
        linearLayoutParams.height = heightPixels;
        mLinearLayout.setLayoutParams(linearLayoutParams);

        Log.e("TAG","Screen width:"+heightPixels * 4 / 3);
        Log.e("TAG","Screen height:"+heightPixels);

    }

    @Override
    protected void onResume() {
        super.onResume();
        /**
         * 初始化camera
         */
        cameraManager = new CameraManager();
        SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceview);
        SurfaceHolder surfaceHolder = surfaceView.getHolder();

        if (hasSurface) {
            // activity在paused时但不会stopped,因此surface仍旧存在;
            // surfaceCreated()不会调用,因此在这里初始化camera
            initCamera(surfaceHolder);
        } else {
            // 重置callback,等待surfaceCreated()来初始化camera
            surfaceHolder.addCallback(this);
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        if (!hasSurface) {
            hasSurface = true;
            initCamera(holder);
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        hasSurface = false;
    }

    /**
     * 初始camera
     *
     * @param surfaceHolder SurfaceHolder
     */
    private void initCamera(SurfaceHolder surfaceHolder) {
        if (surfaceHolder == null) {
            throw new IllegalStateException("No SurfaceHolder provided");
        }
        if (cameraManager.isOpen()) {
            return;
        }
        try {
            // 打开Camera硬件设备
            cameraManager.openDriver(surfaceHolder);
            // 创建一个handler来打开预览,并抛出一个运行时异常
            cameraManager.startPreview();
        } catch (Exception ioe) {

        }
    }

    @Override
    protected void onPause() {
        /**
         * 停止camera,是否资源操作
         */
        cameraManager.stopPreview();
        cameraManager.closeDriver();
        if (!hasSurface) {
            SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceview);
            SurfaceHolder surfaceHolder = surfaceView.getHolder();
            surfaceHolder.removeCallback(this);
        }
        super.onPause();
    }

    /**
     * 拍照回调
     */
    Camera.PictureCallback myjpegCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(final byte[] data, Camera camera) {
            // 根据拍照所得的数据创建位图
            final Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,
                    data.length);
            int height = bitmap.getHeight();
            int width = bitmap.getWidth();
            final Bitmap bitmap1 = Bitmap.createBitmap(bitmap, (width - height) / 2, height / 6, height, height * 2 / 3);
            Log.e("TAG","width:"+width+" height:"+height);
            Log.e("TAG","x:"+(width - height) / 2+" y:"+height / 6+" width:"+height+" height:"+height * 2 / 3);
            // 创建一个位于SD卡上的文件

            File path=new File(filePath);
            if (!path.exists()){
                path.mkdirs();
            }
            File file = new File(path, type+"_"+fileName);

            FileOutputStream outStream = null;
            try {
                // 打开指定文件对应的输出流
                outStream = new FileOutputStream(file);
                // 把位图输出到指定文件中
                bitmap1.compress(Bitmap.CompressFormat.JPEG,
                        100, outStream);
                outStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

            Intent intent = new Intent();
            Bundle bundle = new Bundle();
            bundle.putString("path", file.getAbsolutePath());
            bundle.putString("type", type);
            intent.putExtras(bundle);
            setResult(RESULT_OK, intent);

            CameraActivity.this.finish();

        }
    };

    /**
     * 获得屏幕宽度,单位px
     *
     * @param context 上下文
     * @return 屏幕宽度
     */
    public int getScreenWidth(Context context) {
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        return dm.widthPixels;
    }

    /**
     * 获得屏幕高度
     *
     * @param context 上下文
     * @return 屏幕除去通知栏的高度
     */
    public int getScreenHeight(Context context) {
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        return dm.heightPixels-getStatusBarHeight(context);
    }
    /**
     * 获取通知栏高度
     *
     * @param context 上下文
     * @return 通知栏高度
     */
    public int getStatusBarHeight(Context context) {
        int statusBarHeight = 0;
        try {
            Class<?> clazz = Class.forName("com.android.internal.R$dimen");
            Object obj = clazz.newInstance();
            Field field = clazz.getField("status_bar_height");
            int temp = Integer.parseInt(field.get(obj).toString());
            statusBarHeight = context.getResources().getDimensionPixelSize(temp);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return statusBarHeight;
    }
}

申明Activity为横屏模式以及拍照的权限等相关信息

    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-feature android:name="android.hardware.camera"/>
    <uses-feature android:name="android.hardware.camera.autofocus"/>

 <activity android:name="cn.edu.zafu.camera.activity.CameraActivity"
                  android:screenOrientation="landscape"
            />

在其他的Activity中直接调用即可

Intent intent = new Intent(MainActivity.this, CameraActivity.class);
String pathStr = mPath.getText().toString();
String nameStr = mName.getText().toString();
String typeStr = mType.getText().toString();
if (!TextUtils.isEmpty(pathStr)) {
    intent.putExtra("path", pathStr);
}
if (!TextUtils.isEmpty(nameStr)) {
    intent.putExtra("name", nameStr);
}
if (!TextUtils.isEmpty(typeStr)) {
    intent.putExtra("type", typeStr);
}
startActivityForResult(intent, 100);

获得返回结果并显示在ImageView上

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.e("TAG","onActivityResult");
    if (requestCode == 100) {
        if (resultCode == RESULT_OK) {
            Bundle extras = data.getExtras();
            String path=extras.getString("path");
            String type=extras.getString("type");
            Toast.makeText(getApplicationContext(),"path:"+ path + " type:" + type, Toast.LENGTH_LONG).show();
            File file = new File(path);
            FileInputStream inStream = null;
            try {
                inStream = new FileInputStream(file);
                Bitmap bitmap = BitmapFactory.decodeStream(inStream);
                mPhoto.setImageBitmap(bitmap);
                inStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
    super.onActivityResult(requestCode, resultCode, data);
}

最终的U如下所示

具体细节见源码吧,由于csdn抽了,文件上传不了,所以把代码传github了

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-04 06:39:52

Android 识别二代身份证证件信息与Camera取景的相关文章

将证件信息转化为文字的移动端证件识别

随着计算机网络飞速发展,信息电子化已经成为一个时代必然趋势.文字作为信息中最重要.最集中的载体,其电子化进程显得尤为重要.而OCR技术则是文字电子化过程中重要的环节,它改变了传统纸质介质资料输入的概念,开启了信息电子化的2.0时代. 易泊证件识别它是利用OCR(光学字符识别)技术,能够将图片中的信息迅速转换成文本信息.而且中安身份证识别SDK它还能支持Android.iOS.Windows及服务端平台,因此它可以轻松嵌入其他软件.硬件系统中运行.所以它可以嵌入其他软件系统中运行,满足不同企事业单

自助终端嵌入式多功能集成读写模块MT4D-00-A39417支持二代身份证阅读器模块、磁条模块、芯片读写模块集成

MT4多功能集成模块即MT4D-00-A39417是一款支持磁卡读取.ISO7816协议接触式芯片卡读写.ISO14443AB协议感应IC卡读写.二代身份证阅读于一体的多功能集成模块,广泛应用于各类终端设备.自助服务设备中. 产品特性 多功能读写模块 无需驱动.即插即用.使用方便快捷 可扩展二代证信息读取及外国人居留身份证信息读取功能 内嵌4个SAM小卡座 适用于各种Windows平台 支持卡型 接触式:符合ISO7816 标准的A 类5V/B 类3V 卡, 支持SLE4428,SLE4442,

Android如何获取SIM卡信息

android 获取sim卡运营商信息 TelephonyManager tm = (TelephonyManager)Context.getSystemService(Context.TELEPHONY_SERVICE); TelephonyManager 的使用 TelephonyManager 提供设备上获取通讯服务信息的入口,应用程序使用这个类的方法来获取电话的服务商或者状态.程序也可以注册一个监听器来监听电话状态的改变. 不需要直接实例化这个类,使用Context.getSystemS

ASP.NET程序读取二代身份证(附源码)

原文:ASP.NET程序读取二代身份证(附源码) 一般来说winform应用程序解决这个问题起来时很容易的,web应用程序就麻烦一点了. 这里我说说我的解决思路: 一.你必要有联机型居民身份证阅读器一个(带驱动光盘),这里我用的是精伦电子公司的iDR200,并有这个阅读器的开发接口说明. 二.新建一个winform控件项目ReadCardControl,添加一个主类ReadCard 1.  声明dll入口 [DllImport("Sdtapi.dll")] private static

二代身份证读取-微调版

原文:二代身份证读取-微调版 说起微调其实就是对 上一篇文章 今天意外和身份证结缘 中提到的代码码放和调用一些巧妙的用法(至少小鬼觉得很有意思很巧妙). 为什么有必要在写一篇 写的理由千千万:不写的理由万万千.对于小鬼来说就是:我吃饱了,撑着了,该找点事做了.于是神情恍惚,目光呆滞的敲着键盘,陶醉的望着跳动的光标,嘴角上扬. IDCardReader #region Using using System; using System.IO; using System.Runtime.Interop

Delphi百度文字识别【支持通用文字识别、身份证识别、银行卡识别、驾驶证识别、行驶证识别、车牌识别等功能】

Delphi百度文字识别          百度api文档 [Delphi百度文字识别]支持 通用文字识别.通用文字识别(高精度版).通用文字识别(含位置信息版).通用文字识别(高精度含位置版).手写文字识别.身份证识别.银行卡识别.营业执照识别.护照识别.名片识别.户口本识别.出生医学证明识别.港澳通行证识别.台湾通行证识别.通用票据识别.表格文字识别.通用票据识别.增值税发票识别.火车票识别.出租车票识别.定额发票识别.驾驶证识别.行驶证识别.车牌识别.机动车销售发票识别.车辆合格证识别.V

android之保存偏好设置信息到shareSharedPreferences,轻量级的保存数据的方法

android之保存偏好设置信息到shareSharedPreferences,轻量级的保存数据的方法 09. 四 / android基础 / 没有评论 SharedPreferences保存数据到xml文件 有时候要保存activity的某些状态数据,就可以保存到SharedPreferences 很简单的保存和获取方法.但很实用. itcast是xml的文件名

Android中利用SharedPreferences保存信息

package com.example.sharepreferen; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.vi

android的一些常用手机信息获取

Android中与电话功能相关的类是 TelephonyManager ,此类中定义了很多常量,以下分类说明 获取以下信息需要在AndroidManifest.xml中指定权限 一. 数据连接状态 获取数据连接状态:int getDataState() 获取数据活动状态:int getDataActivity() 常用的有这几个: int DATA_ACTIVITY_IN 数据连接状态:活动,正在接受数据 int DATA_ACTIVITY_OUT 数据连接状态:活动,正在发送数据 int DA