Android: Camera相機開發詳解(上) —— 知識儲備

android.jpg

前言

  • 上一篇文章介紹了如何調用系統相機進行拍照裁剪等功能,一般情況下這些已經能滿足我們的需求了。但是在有些場景和特殊需求下,比如要進行人臉檢測、要不間斷地抓取多張照片等等,那就需要使用原生Camera來進行開發啦

  • 這里并不打算講如何用代碼去實現,而是先給小伙們介紹相關的知識點,等對這些知識有了大致了解后在動手去寫,這樣既能有目的的去寫又能加深對知識點的理解

  • 本篇文章主要給大家講解進行Camera開發需要用到的類和方法,以及在開發過程中遇到的方向問題的分析


進行Camra開發主要用到了以下兩個類:

  1. Camera
  2. SurfaceView (當然也可以是TextureView,本文我們使用SurfaceView)

這兩者的關系如下圖:

圖一、關系類比.png

一、 SurfaceView 、Surface 、 SurfaceHolder

關系圖

圖二、SurfaceView.jpg

Surface

什么是Surface?源碼中是這樣描述的:


 * Handle onto a raw buffer that is being managed by the screen compositor.
 *
 * <p>A Surface is generally created by or from a consumer of image buffers (such as a
 * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, or
 * {@link android.renderscript.Allocation}), and is handed to some kind of producer (such as
 * {@link android.opengl.EGL14#eglCreateWindowSurface(android.opengl.EGLDisplay,android.opengl.EGLConfig,java.lang.Object,int[],int) OpenGL},
 * {@link android.media.MediaPlayer#setSurface MediaPlayer}, or
 * {@link android.hardware.camera2.CameraDevice#createCaptureSession CameraDevice}) to draw
 * into.</p>

簡單翻譯一下,大概意思就是:

Surfaces是用來處理屏幕顯示內容合成器所管理的原始緩存區的工具。它通常由圖像緩沖區的消費者來創建(如:SurfaceTexture,MediaRecorder),然后被移交給生產者(如:MediaPlayer)或者是顯示到其上(如:CameraDevice)

SurfaceHolder

源碼描述:

 * Abstract interface to someone holding a display surface.  Allows you to
 * control the surface size and format, edit the pixels in the surface, and
 * monitor changes to the surface.  This interface is typically available
 * through the {@link SurfaceView} class.

簡單翻譯一下:

一個抽象接口,給持有surface的對象使用。它可以控制surface的大小和格式,編輯surface中的像素,以及監聽surface的變化。這個接口通常通過SurfaceView類獲得

SurfaceHolder中有一個Callbcak接口,它有3個回調方法

  • surfaceCreated(SurfaceHolder holder)
    surface第一次創建時回調

  • surfaceChanged(SurfaceHolder holder, int format, int width,
    int height)
    surface變化的時候回調(格式/大小)

  • surfaceDestroyed(SurfaceHolder holder)
    surface銷毀的時候回調

這個回調接口就是源碼中所提到的 “monitor changes to the surface”(監聽surface的變化),我們后面會用到

SurfaceView

源碼描述:

 * Provides a dedicated drawing surface embedded inside of a view hierarchy.
 * You can control the format of this surface and, if you like, its size; the
 * SurfaceView takes care of placing the surface at the correct location on the
 * screen

大致翻譯一下:

SurfaceView提供了嵌入視圖層級中的專用surface。你可以控制surface的格式或大小。SurfaceView負責把surface顯示在屏幕的正確位置

SurfaceView繼承自View,其中有兩個成員變量,一個是Surface對象,一個是SuraceHolder對象。(請參考上面第二幅關系圖)

  • SurfaceView把Surface顯示在屏幕上
  • SurfaceView通過SuraceHolder告訴我們Surface的狀態(創建、變化、銷毀)
  • 通過getHolder()方法獲得當前SurfaceView的SuraceHolder對象,然后就可以對SuraceHolder對象添加回調來監聽Surface的狀態

SurfaceView小結

  1. SurfaceView是一個view對象,用于在屏幕上顯示相機的預覽畫面
  2. SurfaceView中有兩個對象,Surface和SuraceHolder。 我們通過SuraceHolder中的回調可以知道Surface的狀態(創建、變化、銷毀)
  3. 通過getHolder()方法獲得當前SurfaceView的SuraceHolder對象

二、Camera

概覽圖

Camera類中主要的內部類和接口,如下圖:

圖三、Camera內部類和接口.jpg

Camera類中有很多內部類和方法,下面分別對這些類和方法做介紹:

Camera類中的內部類

CameraInfo

CameraInfo類用來描述相機信息,通過Camera類中getCameraInfo(int cameraId, CameraInfo cameraInfo)方法獲得,主要包括以下兩個成員變量:

  • facing
    facing 代表相機的方向,它的值只能是CAMERA_FACING_BACK(后置攝像頭) 或者CAMERA_FACING_FRONT(前置攝像頭)。

CAMERA_FACING_BACK 和 CAMERA_FACING_FRONT 是CameraInfo類中的靜態變量

  • orientation
    這個就比較有意思了,也比較重要,源碼中是這樣描述的:
         * <p>The orientation of the camera image. The value is the angle that the
         * camera image needs to be rotated clockwise so it shows correctly on
         * the display in its natural orientation. It should be 0, 90, 180, or 270.</p>
         *
         * <p>For example, suppose a device has a naturally tall screen. The
         * back-facing camera sensor is mounted in landscape. You are looking at
         * the screen. If the top side of the camera sensor is aligned with the
         * right edge of the screen in natural orientation, the value should be
         * 90. If the top side of a front-facing camera sensor is aligned with
         * the right of the screen, the value should be 270.</p>

翻譯一下,大概意思是:

orientation是相機采集圖片的角度。這個值是相機所采集的圖片需要順時針旋轉至自然方向的角度值。它必須是0,90,180或270中的一個。
舉個栗子:
假如你自然地豎著拿著手機(就是自拍時候的樣子...),后置攝像頭的傳感器在手機里是水平方向的,你現在看著手機,如果傳感器的頂部在自然方向上手機屏幕的右邊(此時,手機是豎屏,傳感器是橫屏),那么這個orientation的值就是90。 如果前置攝像頭的傳感器頂部在手機屏幕右邊,那么這個值就是270.

  • setDisplayOrientation
    源碼描述如下:
     * Set the clockwise rotation of preview display in degrees. This affects
     * the preview frames and the picture displayed after snapshot. This method
     * is useful for portrait mode applications. Note that preview display of
     * front-facing cameras is flipped horizontally before the rotation, that
     * is, the image is reflected along the central vertical axis of the camera
     * sensor. So the users can see themselves as looking into a mirror.

翻譯一下:

設置預覽畫面順時針旋轉的角度。這個方法會影響預覽圖像和拍照后顯示的照片。這個方法對豎屏應用非常有用。
注意,前置攝像頭在進行角度旋轉之前,圖像會進行一個水平的鏡像翻轉。
所以用戶在看預覽圖像的時候就像照鏡子,看到的是現實的水平方向的鏡像。

注:setDisplayOrientation(int degrees)是Camea類中的一個方法,之所以穿插在這里來講,是為了和上面提到的orientation做一個統一講解,因為這兩個都涉及到了方向問題

看了上面的介紹是不是有點懵逼。。。沒關系,下面給小伙伴們詳細介紹一下Android手機上幾個方向的概念以及在使用Camera過程中會遇到的方向問題:

注:如果你是第一次使用Camera的話,首先要了解以下幾點:

  1. 相機圖像數據都是來自于相機硬件的圖像傳感器(Image Sensor),這個Sensor被固定到手機之后是有一個默認的取景方向,且不會改變
  2. 相機在預覽的時候是有一個預覽方向的,可以通過setDisplayOrientation()設置
  3. 相機所采集的照片也是有一個方向的(就是上面剛剛提到的orientation),這個方向與預覽時的方向互不相干

  • 屏幕坐標: 在Android系統中,屏幕的左上角是坐標系統的原點(0,0)坐標。原點向右延伸是X軸正方向,原點向下延伸是Y軸正方向

  • 自然方向:每個設備都有一個自然方向,手機和平板的自然方向不同。手機的自然方向是portrait(豎屏),平板的自然方向是landscape(橫屏)

  • 圖像傳感器(Image Sensor)方向:手機相機的圖像數據都是來自于攝像頭硬件的圖像傳感器,這個傳感器在被固定到手機上后有一個默認的取景方向

圖四、傳感器方向.png
  • 相機的預覽方向:將圖像傳感器捕獲的圖像,顯示在屏幕上的方向。在默認情況下,與圖像傳感器方向一致。在相機API中可以通過setDisplayOrientation()設置相機預覽方向。在默認情況下,這個值為0,與圖像傳感器方向一致
圖五、相機預覽方向.png
  • 相機采集的圖像方向
    相機采集圖像后需要進行順時針旋轉的角度,即上面介紹的orientation的值:
圖六、采集的圖像方向.png

這里先做個小結:

  • 絕大部分安卓手機中圖像傳感器方向是橫向的,且不能改變,所以orientation是90或是270,也就是說,當點擊拍照后保存圖片的時候,需要對圖片做旋轉處理,使其為"自然方向"。 (可能存在一些特殊的定制或是能外接攝像頭的安卓機,他們的orientation會是0或者180)

  • 通過setDisplayOrientation方法設置預覽方向,使預覽畫面為"自然方向"。前置攝像頭在進行角度旋轉之前,圖像會進行一個水平的鏡像翻轉,所以用戶在看預覽圖像的時候就像照鏡子一樣。

這些都是些理論,在下篇文章中,會通過實例代碼一步步地驗證。

好,我們接著看Camera類中的內部類:

Size

圖片大小,里面包含兩個變量:width和height(圖片的寬和高)

Parameters

Parameters是相機服務設置,不同的相機可能是不相同的。比如相機所支持的圖片大小,對焦模式等等。下面介紹一下這個類中常用的方法

  • getSupportedPreviewSizes()
    獲得相機支持的預覽圖片大小,返回值是一個List<Size>數組

  • setPreviewSize(int width, int height)
    設置相機預覽圖片的大小

  • getSupportedPreviewFormats()
    獲得相機支持的圖片預覽格式,所有的相機都支持ImageFormat.NV21
    更多的圖片格式可以自行百度或是查看ImageFormat類

  • setPreviewFormat(int pixel_format)
    設置預覽圖片的格式

  • getSupportedPictureSizes()
    獲得相機支持的采集的圖片大小(即拍照后保存的圖片的大小)

  • setPictureSize(int width, int height)
    設置保存的圖片的大小

  • getSupportedPictureFormats()
    獲得相機支持的圖片格式

  • setPictureFormat(int pixel_format)
    設置保存的圖片的格式

  • getSupportedFocusModes()
    獲得相機支持的對焦模式

  • setFocusMode(String value)
    設置相機的對焦模式

  • getMaxNumDetectedFaces()
    返回當前相機所支持的最大的人臉檢測個數
    比如,我的手機是vivo x9,后置攝像頭所支持最大的人臉檢測個數是10,也就是說當畫面中人臉數超過10個的時候,只能檢測到10張人臉

PreviewCallback

PreviewCallback是一個抽象接口

  • void onPreviewFrame(byte[] data, Camera camera)
    通過onPreviewFrame方法來獲取到相機預覽的數據,第一個參數data,就是相機預覽到的原始數據。

這些預覽到的原始數據是非常有用的,比如我們可以保存下來當做一張照片,還有很多第三方的人臉檢測及靜默活體檢測的sdk,都需要我們把相機預覽的數據實時地傳遞過去。

Face

Face類用來描述通過Camera的人臉檢測功能檢測到的人臉信息

  • rect
    rect 是一個Rect對象,它所表示的就是檢測到的人臉的區域。

注意:這個Rect對象中的坐標系并不是安卓屏幕的坐標系,需要進行轉換后才能使用,具體會在后面實現人臉檢測功能的時候詳細介紹

  • score
    檢測到的人臉的可信度,范圍是1 到100

  • leftEye
    leftEye 是一個Point對象,表示檢測到的左眼的位置坐標

  • rightEye
    rightEye是一個Point對象,表示檢測到的右眼的位置坐標

  • mouth
    mouth是一個Point對象,表示檢測到的嘴的位置坐標

leftEye ,rightEye和mouth這3個人臉中關鍵點,并不是所有相機都支持的,如果相機不支持的話,這3個的值為null

FaceDetectionListener

這是一個抽象接口,當開始人臉檢測時開始回調

  • onFaceDetection(Face[] faces, Camera camera)
    第一參數代表檢測到的人臉,是一個Face數組(畫面內可能存在多張人臉)

Camera類中的方法

  • getNumberOfCameras()
    返回當前設備上可用的攝像頭個數

  • open()
    使用當前設備上第一個后置攝像頭創建一個Camera對象。如果當前設備沒有后置攝像頭,則返回值為null

  • open(int cameraId)
    使用傳入id所表示的攝像頭創建一個Camera對象,如果id所表示的攝像頭已經被打開,則會拋出異常

設備上每一個物理攝像都是有一個id的,id從0開始,到getNumberOfCameras() - 1 結束

例如,一般的手機上都有前后兩個攝像頭,那么后置攝像頭id就是0,前置攝像頭id就是1

  • getCameraInfo(int cameraId, CameraInfo cameraInfo)
    返回指定id所表示的攝像頭的信息

  • setDisplayOrientation(int degrees)
    設置相機預覽畫面旋轉的角度
    上面已經講過,見圖五

  • setPreviewDisplay(SurfaceHolder holder)
    設置一個Surface對象用來實時預覽
    我們看一下源碼:

  public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
        if (holder != null) {
            setPreviewSurface(holder.getSurface());
        } else {
            setPreviewSurface((Surface)null);
        }
    }
     
 public native final void setPreviewSurface(Surface surface) throws IOException;

可以看到 ,這個方法調用了setPreviewSurface(Surface surface),傳入的surface對象是holder.getSurface()
那么holder.getSurface()所表示的surface是什么呢?
在SurfaceView的源碼中(1088行)找到了答案:

        @Override
        public Surface getSurface() {
            return mSurface;
        }

holder.getSurface()所返回的surface對象就是SurfaceView中的mSurface對象! 這三者的關系見圖二

  • setPreviewCallback(PreviewCallback cb)
    設置相機預覽數據的回調,參數是一個PreviewCallback對象,上面在介紹內部類的時候已講過

  • getParameters()
    返回當前相機的參數信息,返回值是一個Parameters對象

  • setParameters(Parameters params)
    設置當前相機的參數信息

  • startPreview()
    開始預覽
    調用此方法之前,如果沒有setPreviewDisplay(SurfaceHolder) 或 setPreviewTexture(SurfaceTexture)的話,是沒有效果的

  • stopPreview()
    停止預覽

  • startFaceDetection()
    開始人臉檢測
    這個方法必須在開始預覽之后調用

  • stopFaceDetection()
    停止人臉檢測

  • setFaceDetectionListener(FaceDetectionListener listener)
    設置人臉檢測監聽回調

  • release()
    釋放相機

三、總結

  1. Camera負責采集數據和各種操作,SurfaceView負責把Camera采集到的數據實時地顯示在屏幕上

  2. 我們通過SuraceHolder中的回調可以監聽Surface的狀態(創建、變化、銷毀)

  3. 相機圖像數據都是來自于相機硬件的圖像傳感器這個方向是不能改變的

  4. 相機在預覽的時候是有一個預覽方向的,可以通過setDisplayOrientation()設置

  5. 前置攝像頭在進行角度旋轉之前,圖像會進行一個水平的鏡像翻轉,所以用戶在看預覽圖像的時候就像照鏡子一樣

  6. 相機所采集的照片也是有一個方向的,這個方向與預覽時的方向互不相干

  7. 我們可以通過setParameters(Parameters params)設置當前相機的參數信息,比如 保存的圖片大小,對焦模式等等

  8. 在關閉頁面 或者 打開其他攝像頭之前,一定要先調用release()方法釋放當前相機資源

好,到這里關于Camera開發所需要的知識點已經介紹完了??戳诉@么多枯燥的知識點是不是已經雙手癢癢,迫不及待動手擼代碼了?在下篇文章中,我將會帶著小伙們一塊,從零開始實現自己的Camera相機!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,767評論 25 708
  • 上一篇介紹了如何使用系統相機簡單、快速的進行拍照,本篇將介紹如何使用框架提供的API直接控制攝像機硬件。 你還在為...
    Xiao_Mai閱讀 7,223評論 4 18
  • 以下內容翻譯來源,Google官方開發文檔:https://developer.android.google.cn...
    肱二頭肌的孤單閱讀 5,269評論 5 47
  • 今天是2017年10月17日,農歷,8月28。今天是一個特別的日子,是個重要的日子。早晨天還是陰沉沉的,但已經沒有...
    星之夢lyx閱讀 269評論 0 0
  • 沒有看過劇版“使徒行者”,無法與電影版做比較。偶有一個鏡頭會閃現腦中,電影接近尾聲,臥底張家輝拉著反派撞向一輛行駛...
    徐行者也閱讀 290評論 1 1