Android 自定義相機(jī) 識(shí)別身份證(上)

前言

最近在忙著港股券商上線,在做港股的過程中,用戶開戶功能涉及到拍照識(shí)別身份證的功能,如圖:

調(diào)用相機(jī)的兩種方法

一.使用相機(jī)應(yīng)用程序進(jìn)行拍照

利用一個(gè)描述了執(zhí)行目的Intent對(duì)象怎茫,Android可以將某些執(zhí)行任務(wù)委托給其他應(yīng)用,比如調(diào)用相機(jī)。整個(gè)過程包含三部分: Intent 本身状原,一個(gè)函數(shù)調(diào)用來啟動(dòng)外部的 Activity嘱吗,當(dāng)焦點(diǎn)返回到我們的Activity時(shí)船惨,處理返回圖像數(shù)據(jù)的代碼债沮。
下面的函數(shù)通過發(fā)送一個(gè)Intent來捕獲照片:

static final int REQUEST_IMAGE_CAPTURE = 1;

private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
    }
}

注意在調(diào)用startActivityForResult()方法之前,先調(diào)用resolveActivity()鹿鳖,這個(gè)方法會(huì)返回能處理該Intent的第一個(gè)Activity(即檢查有沒有能處理這個(gè)Intent的Activity)扁眯。執(zhí)行這個(gè)檢查非常重要,因?yàn)槿绻谡{(diào)用startActivityForResult()時(shí)翅帜,沒有應(yīng)用能處理你的Intent姻檀,應(yīng)用將會(huì)崩潰。所以只要返回結(jié)果不為null涝滴,使用該Intent就是安全的绣版。(因?yàn)檫@種方法并不能實(shí)現(xiàn)我們的需求,因此不做過多解釋,需要的童鞋可以去搜索一下具體用法)

二.通過使用Android框架所提供的API來直接控制相機(jī)硬件,實(shí)現(xiàn)自定義相機(jī)模塊狭莱。

1.打開相機(jī)對(duì)象

獲取一個(gè) Camera 實(shí)例是直接控制相機(jī)的第一步僵娃。Camera是最主要的類概作,用于管理和操作camera資源腋妙。它提供了完整的相機(jī)底層接口,支持相機(jī)資源切換讯榕,設(shè)置預(yù)覽/拍攝尺寸骤素,設(shè)定光圈、曝光愚屁、聚焦等相關(guān)參數(shù)济竹,獲取預(yù)覽/拍攝幀數(shù)據(jù)等功能,我會(huì)在下面介紹一下他的主要方法.
編寫一個(gè)打開相機(jī)的方法,在onResume()方法里面去調(diào)用執(zhí)行霎槐,單獨(dú)的方法使得代碼更容易重用送浊,也便于保持控制流程更加簡(jiǎn)單。

private int mCameraId = 0;

 /**
 * 獲取Camera實(shí)例
 *
 * @return
 */
private void safeOpenCamera() {
    try {
        releaseCamera();
        mCamera = Camera.open(mCameraId);
    } catch (Exception e) {
        Log.e(getString(R.string.app_name), "failed to open Camera");
        e.printStackTrace();
    }
}

/**
 * 釋放相機(jī)資源
 */
private void releaseCamera() {
    if (mCamera != null) {
        mCamera.setPreviewCallback(null);
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }
}

需要注意的是:由于Android機(jī)制的緣故丘跌,Android相機(jī)只能夠被一個(gè)APP線程所綁定袭景,也就是說如果你正在使用相機(jī)進(jìn)行拍照攝像的時(shí)候,另一個(gè)程序便不能使用Camera類來啟用相機(jī)闭树,而且會(huì)報(bào)出Can not release的錯(cuò)誤耸棒,因?yàn)槲覀冃枰褂胻ry語(yǔ)句塊進(jìn)行捕獲一下.那么如何判定你是否使用了相機(jī)呢,其實(shí)只要使用了Camera.open()方法獲得了相機(jī)的示例报辱,系統(tǒng)就認(rèn)為你使用了相機(jī)与殃,所以當(dāng)你使用了完了相機(jī)一定記得要釋放相機(jī)的資源,不然別的應(yīng)用程序用不了呀,我們可以使用 Camera.release()來釋放相機(jī)資源幅疼,Google官方的意見是重寫Activity的onPause()方法來Camera.release().
另外從API level 9開始米奸,相機(jī)框架可以支持多個(gè)攝像頭的打開操作。如果使用舊的API衣屏,在調(diào)用open()時(shí)不傳入?yún)?shù)指定打開哪個(gè)攝像頭躏升,默認(rèn)情況下會(huì)使用后置攝像頭。0是后置攝像頭,1是前置攝像頭.

2.創(chuàng)建相機(jī)預(yù)覽界面

拍照通常需要向用戶提供一個(gè)預(yù)覽界面來顯示待拍攝的畫面內(nèi)容狼忱。我們可以使用SurfaceView來呈現(xiàn)相機(jī)采集到的圖像畫面膨疏。SurfaceView這個(gè)類是用于繪制相機(jī)預(yù)覽圖像的,提供給用戶實(shí)時(shí)的預(yù)覽圖像钻弄。普通的view以及派生類都是共享同一個(gè)surface的佃却,所有的繪制都必須在UI線程中進(jìn)行。而surfaceview是一種比較特殊的view窘俺,它并不與其他普通view共享surface饲帅,而是在內(nèi)部持有了一個(gè)獨(dú)立的surface,surfaceview負(fù)責(zé)管理這個(gè)surface的格式、尺寸以及顯示位置瘤泪。由于UI線程還要同時(shí)處理其他交互邏輯灶泵,因此對(duì)view的更新速度和幀率無法保證,而surfaceview由于持有一個(gè)獨(dú)立的surface对途,因而可以在獨(dú)立的線程中進(jìn)行繪制赦邻,因此可以提供更高的幀率。自定義相機(jī)的預(yù)覽圖像由于對(duì)更新速度和幀率要求比較高实檀,所以比較適合用surfaceview來顯示惶洲。

SurfaceHolder :surfaceholder是控制surface的一個(gè)抽象接口,它能夠控制surface的尺寸和格式膳犹,修改surface的像素恬吕,監(jiān)視surface的變化等等,surfaceholder的典型應(yīng)用就是用于surfaceview中须床。surfaceview通過getHolder()方法獲得surfaceholder 實(shí)例铐料,通過后者管理監(jiān)聽surface 的狀態(tài)。

SurfaceHolder.Callback 接口 :負(fù)責(zé)監(jiān)聽surface狀態(tài)變化的接口豺旬,有三個(gè)方法:

surfaceCreated(SurfaceHolder holder):在surface創(chuàng)建后立即被調(diào)用钠惩。在開發(fā)自定義相機(jī)時(shí),可以通過重載這個(gè)函數(shù)調(diào)用camera.open()哈垢、camera.setPreviewDisplay()妻柒,來實(shí)現(xiàn)獲取相機(jī)資源、連接camera和surface等操作耘分。

surfaceChanged(SurfaceHolder holder, int format, int width, int height):在surface發(fā)生format或size變化時(shí)調(diào)用举塔。在開發(fā)自定義相機(jī)時(shí)绑警,可以通過重載這個(gè)函數(shù)調(diào)用camera.startPreview來開啟相機(jī)預(yù)覽,使得camera預(yù)覽幀數(shù)據(jù)可以傳遞給surface央渣,從而實(shí)時(shí)顯示相機(jī)預(yù)覽圖像计盒。

surfaceDestroyed(SurfaceHolder holder):在surface銷毀之前被調(diào)用。在開發(fā)自定義相機(jī)時(shí)芽丹,可以通過重載這個(gè)函數(shù)調(diào)用camera.stopPreview()北启,camera.release()來實(shí)現(xiàn)停止相機(jī)預(yù)覽及釋放相機(jī)資源等操作。

在這里你可以直接在activity中直接implements SurfaceHolder.Callback 的接口,也可以自定義一個(gè)surfaceview然后把camera這個(gè)實(shí)例傳給她來使用.

3.設(shè)置和啟動(dòng)Preview

一個(gè)Camera實(shí)例與它相關(guān)的Preview必須按照特定的順序來創(chuàng)建拔第,通常來說Camera對(duì)象優(yōu)先被創(chuàng)建,而Preview對(duì)象必須在surfaceChanged()這一回調(diào)方法里面重新啟用(restart),寫一個(gè)方法用來設(shè)置相機(jī)的參數(shù)和執(zhí)行相機(jī)的預(yù)覽Camera.startPreview()方法.

/**
 * 預(yù)覽相機(jī)
 */
private void startPreview(Camera camera, SurfaceHolder holder) {
    try {
        setupCamera(camera);
        camera.setPreviewDisplay(holder);
        CameraUtil.getInstance().setCameraDisplayOrientation(this, mCameraId, camera);
        camera.startPreview();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
4.修改相機(jī)設(shè)置

相機(jī)參數(shù)的修改可以改變拍照的成像效果咕村,例如縮放大小,曝光補(bǔ)償值等等蚊俺。

/**
 * 設(shè)置
 */
private void setupCamera(Camera camera) {
    Camera.Parameters parameters = camera.getParameters();
    if (parameters.getSupportedFocusModes().contains(
            Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
    }

    //這里第三個(gè)參數(shù)為最小尺寸 getPropPreviewSize方法會(huì)對(duì)從最小尺寸開始升序排列 取出所有支持尺寸的最小尺寸
    Camera.Size previewSize = CameraUtil.getInstance().getPropPreviewSize(parameters.getSupportedPreviewSizes(), 800);
    parameters.setPreviewSize(previewSize.width, previewSize.height);

    Camera.Size pictureSize = CameraUtil.getInstance().getPropPictureSize(parameters.getSupportedPictureSizes(), 800);
    parameters.setPictureSize(pictureSize.width, pictureSize.height);

    camera.setParameters(parameters);
}
5.設(shè)置預(yù)覽方向

借用官方文檔的說明:

Most camera applications lock the display into landscape mode because that is the natural orientation of the camera sensor. This setting does not prevent you from taking portrait-mode photos, because the orientation of the device is recorded in the EXIF header. The setCameraDisplayOrientation() method lets you change how the preview is displayed without affecting how the image is recorded. However, in Android prior to API level 14, you must stop your preview before changing the orientation and then restart it.

大多數(shù)相機(jī)程序會(huì)鎖定預(yù)覽方向?yàn)闄M屏狀態(tài)懈涛,因?yàn)樵摲较蚴窍鄼C(jī)傳感器的自然放置方向。當(dāng)然這一設(shè)定并不妨礙我們?nèi)ヅ呢Q屏的照片泳猬,這個(gè)時(shí)候設(shè)備的方向角度信息會(huì)被記錄在EXIF文件頭中批钠。setCameraDisplayOrientation()方法可以讓你在不影響照片拍攝過程的情況下棕兼,改變預(yù)覽的方向曲掰。然而,對(duì)于Android API level 14及更舊版本的系統(tǒng)柑司,在改變方向之前忙上,我們必須先停止相機(jī)預(yù)覽拷呆,設(shè)置方向之后,然后再重啟預(yù)覽晨横。

6.拍攝照片

一旦預(yù)覽啟動(dòng)成功之后洋腮,可以使用Camera.takePicture()方法拍攝照片箫柳。我們可以創(chuàng)建Camera.PictureCallback與Camera.ShutterCallback對(duì)象并將他們傳遞到Camera.takePicture()中手形。下面的代碼創(chuàng)建了一個(gè)Camera.PictureCallback.

Camera.PictureCallback mRectJpegPictureCallback = new Camera.PictureCallback() {
    public void onPictureTaken(byte[] data, Camera camera) {
        // TODO Auto-generated method stub
        Bitmap b = null;
        if (null != data) {
            b = BitmapFactory.decodeByteArray(data, 0, data.length);
            mCamera.stopPreview();
            isPreviewing = false;
        }
        if (null != b) {
            Bitmap rotaBitmap = ImageUtil.getRotateBitmap(b, 90.0f);
            int x = rotaBitmap.getWidth() / 2 - DST_RECT_WIDTH / 2;
            int y = rotaBitmap.getHeight() / 2 - DST_RECT_HEIGHT / 2;

            Bitmap rectBitmap = Bitmap.createBitmap(rotaBitmap, x, y, DST_RECT_WIDTH, DST_RECT_HEIGHT);
            FileUtil.saveBitmap(rectBitmap);
            Intent intent = new Intent();
            intent.putExtra(AppConstant.KEY.IMG_PATH, FileUtil.getImgPath());
            intent.putExtra(AppConstant.KEY.PIC_WIDTH, rectBitmap.getWidth());
            intent.putExtra(AppConstant.KEY.PIC_HEIGHT, rectBitmap.getHeight());
            setResult(AppConstant.RESULT_CODE.RESULT_OK, intent);
            finish();
            if (rotaBitmap.isRecycled()) {
                rotaBitmap.recycle();
                rotaBitmap = null;
            }
            if (rectBitmap.isRecycled()) {
                rectBitmap.recycle();
                rectBitmap = null;
            }
        }
        mCamera.startPreview();
        isPreviewing = true;
        if (!b.isRecycled()) {
            b.recycle();
            b = null;
        }
    }
};
8.停止預(yù)覽并釋放相機(jī)

當(dāng)應(yīng)用使用完相機(jī)之后,我們有必要進(jìn)行清理釋放資源的操作悯恍。尤其是库糠,我們必須釋放Camera對(duì)象,不然的話可能會(huì)引起其他應(yīng)用程序使用Camera實(shí)例的時(shí)候發(fā)生崩潰涮毫,包括我們自己應(yīng)用也同樣會(huì)遇到這個(gè)問題瞬欧。

那么何時(shí)應(yīng)該停止預(yù)覽并釋放相機(jī)呢?在預(yù)覽SurfaceView組件被銷毀之后罢防,調(diào)用釋放相機(jī)的方法.
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
releaseCamera();
}

9.遇到的問題 SurfaceView預(yù)覽圖像拉伸變形艘虎,拍攝照片尺寸不對(duì)

說明這個(gè)問題之前,同樣先說一下幾個(gè)跟相機(jī)有關(guān)的尺寸咒吐。

SurfaceView尺寸 :即自定義相機(jī)應(yīng)用中用于顯示相機(jī)預(yù)覽圖像的View的尺寸野建,當(dāng)它鋪滿全屏?xí)r就是屏幕的大小属划。這里surfaceview顯示的預(yù)覽圖像暫且稱作手機(jī)預(yù)覽圖像。

Previewsize :相機(jī)硬件提供的預(yù)覽幀數(shù)據(jù)尺寸候生。預(yù)覽幀數(shù)據(jù)傳遞給SurfaceView同眯,實(shí)現(xiàn)預(yù)覽圖像的顯示。這里預(yù)覽幀數(shù)據(jù)對(duì)應(yīng)的預(yù)覽圖像暫且稱作相機(jī)預(yù)覽圖像唯鸭。

Picturesize :相機(jī)硬件提供的拍攝幀數(shù)據(jù)尺寸须蜗。拍攝幀數(shù)據(jù)可以生成位圖文件,最終保存成.jpg或者.png等格式的圖片目溉。這里拍攝幀數(shù)據(jù)對(duì)應(yīng)的圖像稱作相機(jī)拍攝圖像明肮。圖4說明了以上幾種圖像及照片之間的關(guān)系。手機(jī)預(yù)覽圖像是直接提供給用戶看的圖像缭付,它由相機(jī)預(yù)覽圖像生成晤愧,拍攝照片的數(shù)據(jù)則來自于相機(jī)拍攝圖像。

原因是沒有正確設(shè)置比例 parameter.setPictureSize(width,height)蛉腌,這個(gè)比例不是你決定的官份,要先通過camera.getParameters().getSupportedPictureSizes()獲得手機(jī)支持的尺寸。

自定義相機(jī)其實(shí)還是比較容易的,按著步驟來都可以一點(diǎn)一點(diǎn)實(shí)現(xiàn).項(xiàng)目中的自定義相機(jī)部分首先是寫了一個(gè)遮罩view,預(yù)覽的時(shí)候是四周暗中間亮,其次就是對(duì)相機(jī)拍攝出來的照片的處理,在拍攝的時(shí)候會(huì)選取適合屏幕大小,但是我們想拍攝出中間亮的部分區(qū)域的照片,所以我們需要對(duì)拍攝出來的照片做一些處理,實(shí)現(xiàn)和源碼會(huì)在下一篇文章中展現(xiàn)Android 自定義相機(jī) 識(shí)別身份證(下).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末烙丛,一起剝皮案震驚了整個(gè)濱河市舅巷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌河咽,老刑警劉巖钠右,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異忘蟹,居然都是意外死亡飒房,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門媚值,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狠毯,“玉大人,你說我怎么就攤上這事褥芒〗浪桑” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵锰扶,是天一觀的道長(zhǎng)献酗。 經(jīng)常有香客問我,道長(zhǎng)坷牛,這世上最難降的妖魔是什么罕偎? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮京闰,結(jié)果婚禮上颜及,老公的妹妹穿的比我還像新娘痴怨。我一直安慰自己,他們只是感情好器予,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布浪藻。 她就那樣靜靜地躺著,像睡著了一般乾翔。 火紅的嫁衣襯著肌膚如雪爱葵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天反浓,我揣著相機(jī)與錄音萌丈,去河邊找鬼。 笑死雷则,一個(gè)胖子當(dāng)著我的面吹牛辆雾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播月劈,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼度迂,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了猜揪?” 一聲冷哼從身側(cè)響起惭墓,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎而姐,沒想到半個(gè)月后腊凶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拴念,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年钧萍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片政鼠。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡风瘦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缔俄,到底是詐尸還是另有隱情弛秋,我是刑警寧澤器躏,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布俐载,位于F島的核電站,受9級(jí)特大地震影響登失,放射性物質(zhì)發(fā)生泄漏遏佣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一揽浙、第九天 我趴在偏房一處隱蔽的房頂上張望状婶。 院中可真熱鬧意敛,春花似錦、人聲如沸膛虫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)稍刀。三九已至撩独,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間账月,已是汗流浹背综膀。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留局齿,地道東北人剧劝。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像抓歼,于是被迫代替她去往敵國(guó)和親讥此。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349