Android Camera2 API 錄影開發歷程 (四)

重點整理

  • 解說相機角度與螢幕角度對輸出實際影響之後的影響
  • 完成錄影

相機角度與螢幕角度

後鏡頭與相機的關係圖:

在Android Camera2 API 錄影開發歷程 (一)的時候,曾經提過這個被旋轉的圖像輸出,現在就要正式的講解更詳細的原因,以及相機硬體與螢幕的設計規則。

本章論述僅為個人的認知,並不代表百分之百一定完全正確,如有錯誤可以指正。

相機角度

相機基本原理,是光和影像通過針孔映射在一個平面上,然後這個影像會是跟實景相反的圖像。傳統的相機,針孔就是鏡頭,鏡頭透過光反射到相機內部的底片形成影像後,我們再去沖洗照片。

goole 一下相機基本構造就可以得到相關圖例,我就不再貼網站上來囉。

現今的數位相機或是手機上的相機,已經不再使用底片的方式感測圖像,改用了電子的感光元件。早期一般都是利用 CCD 感光元件進行影像感測,後期幾乎所有手機相機已經都使用了較為便宜的 CMOS 元件。

不論是 CCD 還是 CMOS,它們都是一種感光的元件,通過鏡頭入射進來的光成像通過這個元件進行一點一點像素的讀取,輸入到 memory成為圖片。

我找了一張手機與CMOS image sensor 的關係架構圖:

從鏡頭結構一路往下看,最後會看到一塊彩色的矩形,那個就是 CMOS image sensor,這張的架構圖很剛好可以說明一件事情,就是這個 Image sensor它是直立的焊在手機的電路板上,如果以順時針左上角為原點來看,就是旋轉了90度或是270度。

首先,要先引述一下 google api中的SENSOR_ORIENTATION:Link

上面講到,輸出圖片需要通過該角度做順時針旋轉以直立在設備的自然方向。

同時也定義了 rolling shutter readout 的方向,該方向來自sensor由上到下的座標系統。

正常的Sensor一般來說都是寬長高短,像是4:3 , 16:9 這種比例,所以CMOS是一種感測元件用來將光成像變成圖片,自然的圖片可以輸出的解析度一定跟CMOS這個規格有關。

CMOS Sensor上一定會標示一個規格:(H)x(V),這個意思類似是1920(H) x 1080(V) 的講法,也就是我有多少個像素點。

rolling shutter readout,是CMOS大多數讀取資料的方式,有點像是傳統快門相機的捲簾式快門,但今天要說明的不是rolling shutter readout,而是旋轉角度。

假設今天感測元件規格是:1920(H) x 1080(V),這塊Sensor不太可能會以這樣的方式設計在手機上:

紅色區塊就是Sensor,如果是這樣設計的話,這時將手機轉成90度,你會發現這個Sensor變成直立的。

這就跟我們手持手機的自然方向衝突了,所以通常Sensor會被安裝成與螢幕差90度角,然後在通過圖片資料輸出到系統後,系統對這張圖片做了旋轉處理。

前後鏡頭角度跟螢幕的關係

這時候就要講一下API上,輸出圖片需要通過該角度做順時針旋轉以直立在設備的自然方向,這句話的意思了。

假設正常硬體設計應該會是這樣:

簡單一點的思考方式就是,系統已經幫我們處理好需要90度旋轉的原始輸出圖片,而且這張圖片一定是寬比高長。

我們先看一下,從手機前後鏡頭在螢幕各角度時會從Sensor讀出的圖片(這已經是我確實從手機測出來的結果):

假如還不太了解,可以先記憶一件事情就好:記住手機0度的位置和輸出的方位,手機開始旋轉到90 , 180 , 270 的時候,就以順時針方式把0度的輸出旋轉就是結果。

這個方式前鏡頭也是一樣,所以90度的時候你就發現他整個顛倒過來了。理論上前鏡頭和後鏡頭,應該要是順時針相差180度才是一台手機合理的硬體配置。

備註:為何會說理論上,是因為還真的在其他討論Android開發相機的文章上看到有人遇到前後各270度的詭異情況,所以我沒有把這種配置定義成絕對。

探討成像原因

之所以一開始先提到 CMOS Image Sensor的原因,其實是為了明確的說明 SENSOR_ORIENTATION 這個參數的意義:

  • 輸出圖片需要通過該角度做順時針旋轉以直立在設備的自然方向。

請注意這句話的意思有:

第一點:在API文檔中沒有提到 Sensor 這個硬體是被旋轉了幾度,只有說輸出圖片,就連原文也沒有提到。

第二點:什麼是以直立在設備自然方向,設備自然方向制訂於手機本身有一個Sensor,它的初始值在某一方向的時候是為0,當開始順時針轉的時候,這個值會開始增加,範圍是 0 ~ 360。通常這個Sensor會對齊手機正上方,簡單的說,螢幕的上緣朝上就是0度的起點。

這裡先用圖解說一下,以設備0度搭配 Sensor 0度的示意:

然後依循API說明的:『通過該角度做順時針旋轉以直立在設備的自然方向』的意思,這張輸出圖片其實已經跟上緣對齊了,所以他沒有旋轉的必要,因此參數會是0度。

可是先前針對影像的寬高比曾說過,寬比高大是一種正常相機的圖片成像,只因為我們拿手機的方式是直立(設備自然方向也就是上緣朝上),而畫面也是直立輸出,且高比寬大。

所以來自相機的影像,如果還是攝像範圍還是這麼寬,將其圖片輸出到直立的螢幕時,就需要利用 scale 縮放的方式去呈現,scale type有幾項:fitXY,centerCrop, fitStart etc.

非常多,自己查查就可以得到了,但是這都不是重點,因為手機設置為了正常自然成像,這個Sensor通常是直立的貼在手機上,也可以說是原本這個Sensor是被旋轉90度或是270度在手機上,但我認為這跟像素讀取較無關係,跟旋轉比較有大的關聯性。

這是一張,設備0度搭配 Sensor 90度的示意:

那麼,Sensor 90度,API上說到:『通過該角度以順時針旋轉』,首先我們先想像一下圖中的那一個人的圖片,現在已經是高比寬大的結果了,可是注意一件事情,相機的圖片是 寬(H)x高(V) 而成,而通常寬比高大。

所以說,當你想要看這張圖片的時候,只要你在拍照的同時沒有設定對圖片做90度旋轉,然後開啟手機內的圖片App去查看圖片時,這時應該會以 scale centerInside 的方式呈現在螢幕上。然後,你會看到這張圖片被逆時針轉了90度,保持了寬高比例呈現在螢幕中間。

以前我在Camera1碰到這個情況的時候,我就想說,啊!那就直接旋轉90度就好了,反正SENSOR_ORIENTATION給了我90度,應該是要我旋轉90度的吧!但是當設備90度拍攝的時候,卻又不該轉90度,然後設備轉180度拍攝的時候,我轉了90度,看到了一張顛倒成像的照片。

這時候就覺得很奇怪,好在是當時最後該App的產品規格明確定義:沒有寬螢幕顯示,只能直立顯示來使用。

當我新接觸Camera2,我想要弄明白一點這個關係,想來想去終於明白,當 SENSOR_ORIENTAION = 90 的時候,到底代表著什麼意義。請先看一下示意圖:

再來講一下,在API中的一個關鍵名詞:

needs to be rotated to be 「upright」 on the device screen in its native orientation.

然後後面接著補充,直立在哪? on the device screen in its 「native orientation」.

看到 native orientation了嗎,這個並不是指當設備轉到該角度後的左上x,y座標系,orientation 表明的意思是安排,coordinate才有座標的意味。

正確的解釋應該是,設備螢幕安排的自然方向(請注意看紅色箭頭),輸出的圖片必須旋轉後直立對齊於它,所以我們可以這樣推論一件事情:每個輸出圖片都應旋轉某角度後,正常對齊設備螢幕上方。

因此在 SENSOR_ORIENTATION = 90時:

  • 當直立拍攝而設備0度,結果圖被逆轉90度,以方便之後將結果旋轉90度符合拍攝時的螢幕方向
  • 當橫立拍攝而設備90度,結果圖正常,也可以不用旋轉,如果你旋轉90度之後,也是跟著當時90度設備的上緣,符合拍攝時的螢幕方向。
  • 當直立拍攝而設備180度(倒立手機),上下顛倒的圖片被逆轉90度,以方便之後將結果旋轉90度符合拍攝時的螢幕方向
  • 當橫立拍攝而設備270度,結果圖上下相反,將其旋轉90度之後,符合拍攝時的螢幕方向。

這也就是為什麼,當你用手機180度拍攝一個人時,照片出來的結果是整個人身朝右方,好像原始圖片正轉了它90度,其實應該是這張圖片是上下顛倒的照片然後逆轉90度。

這時,只要將此照片正轉90度,就會知道當時拍攝的角度是180度顛倒成像,這是硬體的限制,如果要正確的符合人所看的景象,應該是要把這張照片轉270度,即照片正常呈現。

同理,270度因為他相反成像,所以想要正確的符合攝像景象,你要轉它180度,即照片正常呈現。

前鏡頭 SENSOR_ORIENTATION = 270

接下來,要將上述的理論推到前鏡頭上,一開始一定馬上就會發現一個不同的參數值:SENSOR_ORIENTATION = 270。

還記得設備90度時,圖片正常不旋轉的示意圖嗎? 當你使用前鏡頭的時候,你發現這張圖片變成了上下顛倒的照片。當然很快就會有人知道其實只要將後鏡頭的輸出上下顛倒來看就好了。

是的沒錯,270 - 90 = 180,這是正確的算法,所以只要將 SENSOR_ORIENTATION = 90 的結果統一加180度就可以了。

!!到這裡其實可以跳過了,當然如果還想繼續的話在跟著一起往下探討!!

之前已經講解完 SENSOR_ORIENTATION 的意思,這裡我們就直接從設備0度開始討論:

  1. 前鏡頭和後鏡頭一樣,都被直立貼在手機上
  2. 前鏡頭一樣遵循 SENSOR_ORIENTATION 規則,要將輸出圖片做處理,利於之後對齊
  3. 結合以上兩點,請將0度的那張照片順時針做270度旋轉對齊螢幕上緣後,你就會看到直立拍攝結果

然後這裡有個非常大的輸出差異:在前鏡頭 SENSOR_ORIENTATION = 270 的時候,你會看到以下結果:

  • 90度時被旋轉了180度
  • 0度時轉了90度
  • 180度逆轉了90度
  • 270度好像正常沒有轉

先說明90度和270度的差異好了,事實上我們看到圖中設備90度時,圖片被轉了180,而設備270度時,圖片照正常輸出,像是沒有轉一樣。但是,該參數在API上說了,必須要用這個角度讓輸出圖片旋轉以符合拍攝的設備螢幕的方向。

這著這個說法,請把上面的示意圖中,每個設備方向的人物照片結果,通通翻轉270度看看!你就會發現每個結果都會符合拍攝時的螢幕方向。換句話說!SENSOR_ORIENTATION = 270的時候,所有輸出圖像全部都有被處理過,要一一自己通過圖像的旋轉結果去驗證才會發現。

其實為何 SENSOR_ORIENTATION 在前鏡頭的時候等於270,我想可能是因為大部分右撇子的使用者單手拿手機橫著自拍的關係吧,否則一般這個參數應該都直接設定SENSOR_ORIENTATION=90就可以了,像有的手機後鏡頭有兩個,兩個就都是SENSOR_ORIENTATION=90。

旋轉角度對應之計算

理解後鏡頭與前鏡頭互相的對齊之後,我們可以列出一個換算表格:

說實在,討論了這麼多東西,都是為了這個表格,接著我們要以這個表格在錄影的時候,將錄影的結果進行 orientation 的變換!

請先建立一個 class 命名為:VideoRecorder,然後我們要建立兩個二維陣列做查表的動作,這裏直接使用 google 範例中寫好的 SparseIntArray,總共有兩個:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class VideoRecorder {
companion object {
val SENSOR_ORIENTATION_DEFAULT_DEGREES = 90
val SENSOR_ORIENTATION_INVERSE_DEGREES = 270

val DEFAULT_ORIENTATIONS = SparseIntArray().apply {
append(Surface.ROTATION_0, 90)
append(Surface.ROTATION_90, 0)
append(Surface.ROTATION_180, 270)
append(Surface.ROTATION_270, 180)
}

val INVERSE_ORIENTATIONS = SparseIntArray().apply {
append(Surface.ROTATION_0, 270)
append(Surface.ROTATION_90, 180)
append(Surface.ROTATION_180, 90)
append(Surface.ROTATION_270, 0)
}
}
}

SENSOR_ORIENTATION_DEFAULT_DEGREES = 後鏡頭
SENSOR_ORIENTATION_INVERSE_DEGREES = 前鏡頭

這裏 google的命名其實滿貼切的,雖然沒有直接叫前後,但他用了 DEFAULT 和 INVERSE,把形容鏡頭的意義在抽象化一層,這樣如果有些後鏡頭得到的參數是270時,就不會衝突這個命名。