Android開發(fā)中關(guān)于攝像頭方向的理解

安卓開發(fā)中經(jīng)常有需要使用攝像頭的應(yīng)用場(chǎng)景,對(duì)于初次接觸的同學(xué)攝像頭的方向是一個(gè)比較難弄清楚的概念,開發(fā)時(shí)很容易處理不當(dāng),本文將詳述該部分內(nèi)容幫助理解。

一、攝像頭捕獲的圖像

先看一個(gè)簡(jiǎn)單的場(chǎng)景,打開手機(jī)的后置攝像頭拍攝,攝像頭捕獲的圖像幀數(shù)據(jù)可通過Camera.PreviewCallback回調(diào)中獲取,也就是攝像頭的輸出數(shù)據(jù),

void onPreviewFrame(byte[] data, Camera camera);

這里我們先忽略屏幕上的預(yù)覽,只關(guān)注攝像頭的輸出。如果把它保存為圖片或直接顯示出來,可以看到圖像和原始畫面相比逆時(shí)針旋轉(zhuǎn)了90度。



而我們?nèi)绻瑯邮褂胕Phone手機(jī)拍攝,輸出的結(jié)果是一個(gè)正向的圖片。

二、攝像頭的正向

為什么輸出的圖像相比原始畫面旋轉(zhuǎn)了90度?因?yàn)樵O(shè)備的攝像頭存在一個(gè)“正向角度”,什么是攝像頭的正向?
通俗一點(diǎn)講,設(shè)備相當(dāng)于人的身體,眼睛相當(dāng)于攝像頭,眼睛把接收到的畫面反饋給大腦處理,相當(dāng)于攝像頭把接收到的數(shù)據(jù)給應(yīng)用程序處理。人眼能判斷出我們頭頂向上的方向是我們視覺上的正向,而后置攝像頭判斷的正向并不是手機(jī)物理屏幕向上的方向,而是物理屏幕右側(cè)的方向。我們想象一下,如果人眼是這個(gè)攝像頭,它認(rèn)為右側(cè)才是我們的視覺正向,那我們看到的東西是不是都是旋轉(zhuǎn)90度的?這樣就比較好理解了。


上圖是手機(jī)在豎直和水平方向攝像頭“看”到的畫面。
固定設(shè)備,指定的攝像頭,正向角度固定的(0/90/180/270),和屏幕旋轉(zhuǎn)、橫豎屏切換無關(guān),一般都在屏幕的右側(cè)(但不排除某些廠商修改成別的)。
這個(gè)角度在代碼中可通過Camera.CameraInfo的orientation獲取,官方文檔也有解釋:

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.

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.

意思就是輸出的圖片需要順時(shí)針旋轉(zhuǎn)多少度,才能在自然方向上正確顯示。這里的自然方向就是以標(biāo)題欄左上角為原點(diǎn)的屏幕渲染坐標(biāo)系,圖片旋轉(zhuǎn)后,把它放到渲染坐標(biāo)系中,能和原始畫面一樣正常顯示。



上圖紅點(diǎn)代表了圖片的坐標(biāo)原點(diǎn),藍(lán)點(diǎn)則代表屏幕渲染坐標(biāo)的原點(diǎn)。只有做了旋轉(zhuǎn)處理,渲染到屏幕上的預(yù)覽圖像才是正確的(和原始畫面一樣),而這個(gè)旋轉(zhuǎn)的角度,就是orientation的值。
注意,正向始終在物理屏幕的右側(cè)(想象音量鍵那邊有一個(gè)正向箭頭),orientation就等于從攝像頭的角度(想象成人眼)看,從物理設(shè)備的正上方向(想象聽筒位置有個(gè)箭頭),需要順時(shí)針旋轉(zhuǎn)多少度才能到正向的箭頭。所以,根據(jù)這個(gè)想象一下前后攝像頭的區(qū)別,這個(gè)值后置攝像頭是90,前置攝像頭是270。

iPhone的攝像頭正向就是物理設(shè)備的正上方,對(duì)應(yīng)的正向角度是0,所以輸出的圖像是正向的。

三、如何正確地預(yù)覽圖像

其實(shí)不預(yù)覽應(yīng)用程序也能正確獲取到攝像頭的輸入,但是一般應(yīng)用打開攝像頭后都會(huì)在屏幕上顯示當(dāng)前拍攝到的畫面,這是用戶的基本的使用體驗(yàn)。正確的預(yù)覽圖像就是讓攝像頭輸出的圖像能夠正確的在屏幕上顯示給用戶。

如上節(jié)所述,攝像頭采集到的圖像按照orientation旋轉(zhuǎn)和渲染坐標(biāo)系對(duì)齊即可,這樣就能正確顯示圖像了。不過這是在屏幕方向鎖定的情況下,就是渲染坐標(biāo)系始終在物理屏幕的左上方。

如果我們打開了設(shè)備陀螺儀(鎖屏),屏幕可以在四個(gè)方向上切換,對(duì)應(yīng)渲染的坐標(biāo)原點(diǎn)(藍(lán)點(diǎn))會(huì)在物理屏幕的四個(gè)角上切換。
切換方向的角度可以通過activity.getWindowManager().getDefaultDisplay().getRotation();獲取,和前后攝像頭無關(guān):

屏幕切換的角度值

這個(gè)角度可以理解為,以物理設(shè)備左上角為原點(diǎn)的渲染坐標(biāo)系(聽筒左邊的角點(diǎn)),需要順時(shí)針旋轉(zhuǎn)多少度,才能變成當(dāng)前的渲染坐標(biāo)系。打開陀螺儀后,無論手機(jī)怎么旋轉(zhuǎn),當(dāng)前的渲染坐標(biāo)系永遠(yuǎn)以絕對(duì)左上角為原點(diǎn)(視覺左上方角點(diǎn))。這個(gè)角度恰好和物理旋轉(zhuǎn)的角度相反。

我們只要根據(jù)當(dāng)前的切換角度+攝像頭正向角度正確地設(shè)置顯示角度就行了,官方文檔也有現(xiàn)成的適配代碼,詳見setDisplayOrientation
注意,setDisplayOrientation只會(huì)對(duì)預(yù)覽顯示的圖像有影響,并不會(huì)影響onPreviewFrame回調(diào)的數(shù)據(jù)。

 public static void setCameraDisplayOrientation(Activity activity,
         int cameraId, android.hardware.Camera camera) {
     android.hardware.Camera.CameraInfo info =
             new android.hardware.Camera.CameraInfo();
     android.hardware.Camera.getCameraInfo(cameraId, info);
     int rotation = activity.getWindowManager().getDefaultDisplay()
             .getRotation();
     int degrees = 0;
     switch (rotation) {
         case Surface.ROTATION_0: degrees = 0; break;
         case Surface.ROTATION_90: degrees = 90; break;
         case Surface.ROTATION_180: degrees = 180; break;
         case Surface.ROTATION_270: degrees = 270; break;
     }

     int result;
     if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
         result = (info.orientation + degrees) % 360;
         result = (360 - result) % 360;  // compensate the mirror
     } else {  // back-facing
         result = (info.orientation - degrees + 360) % 360;
     }
     camera.setDisplayOrientation(result);
 }

如果還不理解這段代碼的意思,看下這張圖就明白了:


藍(lán)點(diǎn)是渲染的坐標(biāo)原點(diǎn),紅點(diǎn)是輸出圖片的原點(diǎn)。每一次旋轉(zhuǎn)圖片的原點(diǎn)就會(huì)變換到絕對(duì)位置的左上角,setDisplayOrientation要設(shè)置的值就是圖像要順時(shí)針旋轉(zhuǎn)的角度,使圖片能在渲染坐標(biāo)系中正確顯示。從圖中也能看出來,它就是第二列的箭頭需要順時(shí)針旋轉(zhuǎn)到第一列箭頭的角度。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容