1. 程式人生 > >Android: Image類淺析(結合YUV_420_888)

Android: Image類淺析(結合YUV_420_888)

https://www.polarxiong.com/archives/Android-Image%E7%B1%BB%E6%B5%85%E6%9E%90-%E7%BB%93%E5%90%88YUV_420_888.html

簡介

Image類在API 19中引入,但真正開始發揮作用還是在API 21引入CameraDeviceMediaCodec的增強後。API 21引入了Camera2,deprecated掉了Camera,確立Image作為相機得到的原始幀資料的載體;硬體編解碼的MediaCodec類加入了對ImageImage的封裝ImageReader的全面支援。可以預見,Image將會用來統一Android內部混亂的中間圖片資料(這裡中間圖片資料指如各式YUV格式資料,在處理過程中產生和銷燬)管理。

本文主要介紹YUV_420_888格式的圖片資料如何在Image中儲存和管理。

從YUV420談起

YUV即通過Y、U和V三個分量表示顏色空間,其中Y表示亮度,U和V表示色度。不同於RGB中每個畫素點都有獨立的R、G和B三個顏色分量值,YUV根據U和V取樣數目的不同,分為如YUV444、YUV422和YUV420等,而YUV420表示的就是每個畫素點有一個獨立的亮度表示,即Y分量;而色度,即U和V分量則由每4個畫素點共享一個。舉例來說,對於4x4的圖片,在YUV420下,有16個Y值,4個U值和4個V值。

YUV420根據顏色資料的儲存順序不同,又分為了多種不同的格式,如YUV420Planar、YUV420PackedPlanar、YUV420SemiPlanar和YUV420PackedSemiPlanar,這些格式實際儲存的資訊還是完全一致的。舉例來說,對於4x4的圖片,在YUV420下,任何格式都有16個Y值,4個U值和4個V值,不同格式只是Y、U和V的排列順序變化。I420(YUV420Planar

的一種)則為YYYYYYYYYYYYYYYYUUUUVVVV,NV21(YUV420SemiPlanar)則為YYYYYYYYYYYYYYYYUVUVUVUV。也就是說,YUV420是一類格式的集合,YUV420並不能完全確定顏色資料的儲存順序。

Image

這麼多眼花繚亂的格式名字自然是不利於程式開發的,Image就這樣橫空出世了。

長和寬

對於YUV來說圖片的寬和高是必不可少的,因為YUV本身只儲存顏色資訊,想要還原出圖片,必須知道圖片的長寬。Image儲存有圖片的寬和高,可以通過getWidth()getHeight()得到。

圖片格式

每個Image當然有自己的格式,這個格式由ImageFormat

確定。對於YUV420,ImageFormat在API 21中新加入了YUV_420_888型別,其表示YUV420格式的集合,888表示Y、U、V分量中每個顏色佔8bit。既然只能指定YUV420這個格式集合,那怎麼知道具體的格式呢?馬上就來回答這個問題。

YUV分量

Y、U和V三個分量的資料分別儲存在三個Plane類中,可以通過getPlanes()得到。Plane實際是對ByteBuffer的封裝。Image保證了plane #0一定是Y,#1一定是U,#2一定是V。且對於plane #0,Y分量資料一定是連續儲存的,中間不會有U或V資料穿插,也就是說我們一定能夠一次性得到所有Y分量的值。

接下來看看U和V分量,我們考慮其中的三類格式:Planar,SemiPlanar和PackedSemiPlanar。

Planar

Planar下U和V分量是分開存放的,所以我們也應當能夠一次性從plane #1和plane #2中獲得所有的U和V分量值,事實也是如此。

下面是一段YUV420Planar格式Image解析的記錄

image format: 35
get data from 3 planes
pixelStride 1
rowStride 1920
width 1920
height 1080
buffer size 2088960
Finished reading data from plane 0
pixelStride 1
rowStride 960
width 1920
height 1080
buffer size 522240
Finished reading data from plane 1
pixelStride 1
rowStride 960
width 1920
height 1080
buffer size 522240
Finished reading data from plane 2

Image中獲得的圖片格式是35,即YUV_420_888,一共有3個planes,圖片解析度為1920x1080,畫素點個數為2073600;可以看到Y分量包含有全部的畫素點,而U和V都只含有1/4的畫素點,顯然是YUV420。更為明顯的是,Y分量中rowStride為1920,pixelStride代表行內顏色值間隔,取1表示無間隔,即對於一行1920個畫素點每個都有獨立的值,根據其buffer size可以得出共有1080行;而U分量中,一行1920個畫素點只有960個值,即行內每兩個畫素點共用一個U值,根據其buffer size得出共有540行,即行間每兩個畫素點共用一個U值;這就是YUV420的取樣了。

SemiPlanar

再來看看SemiPlanar,此格式下U和V分量交叉儲存,Image並沒有為我們將U和V分量分離出來

下面是一段YUV420SemiPlanar格式Image解析的記錄

image format: 35
get data from 3 planes
pixelStride 1
rowStride 1920
width 1920
height 1080
buffer size 2088960
Finished reading data from plane 0
pixelStride 2
rowStride 1920
width 1920
height 1080
buffer size 1044479
Finished reading data from plane 1
pixelStride 2
rowStride 1920
width 1920
height 1080
buffer size 1044479
Finished reading data from plane 2

圖片格式依然是YUV_420_888,Y分量與上述Planar中一樣。但U和V分量出現了變化,buffer size是Y分量的1/2,如果說U分量只包含有U分量資訊的話應當是1/4,多出來了1/4的內容,我們稍後再仔細看。注意到U中rowStride為1920,即U中每1920個數據代表一行,但pixelStride為2,代表行內顏色值間隔為1,就說是隻有行內索引為0 2 4 6 ...才有U分量資料,這樣來說還是行內每兩個畫素點共用一個U值,行間每兩個畫素點共用一個U值,即YUV420。

U和V的pixelStride都是2,我們從U和V中挑相同位置的20個byte值出來看看相互之間的關係。

124 -127 124 -127 123 -127 122 -127 122 -127 123 -127 123 -127 123 -127 122 -127 123 -127
-127 124 -127 123 -127 122 -127 122 -127 123 -127 123 -127 123 -127 122 -127 123 -127 123

上面一行來自U,下面一行來自V,最前面一個byte的索引值相同,且為偶數。可以明顯發現U和V分量只是進行了一次移位,而這個移位就保證了從索引0開始間隔取值就一定能取到自己分量的值。所以可以簡單來說U和V分量就是複製的UV交叉的資料。

這樣想要獲取U分量值的話只需要以pixelStride為間隔獲取就好了,V分量也是一樣。雖然你也可以只從U或V分量得到U和V分量的資訊,但畢竟官方並沒有保證這一點,多少有些風險。另外如果想要知道更多的細節,也可以去翻Android原始碼。

PackedSemiPlanar

這個簡單點說,不知為何,在我的裝置上PackedSemiPlanar和SemiPlanar的表現是一致的,也就是說,可能Android已經幫我們解決了Packed的問題,只有Semi留給我們自己解決。

綜上,我們只需要根據pixelStride和rowStride就能在對應的plane中獲取到相應的顏色資料,而不必知道具體的YUV420格式。

關於CropRect

根據官方文件的介紹是說,CropRect指定了圖片內的一個矩形區域,只有這個區域內的畫素才是有效的,但鑑於我目前還沒碰到這個問題,也不好詳細解釋。不過有兩點要注意,首先是座標系的變換,一定要弄清楚Rect和圖片的長和寬的關係;其次是U和V的偏移量問題,U和V中顏色點是Y的1/4,在Rect中要計算好U和V中資料的範圍,避免發生錯位等。

小結

本篇主要介紹YUV_420_888格式的圖片資料如在Image中的儲存和管理,其中重點又放在U和V分量的管理上。本篇算作對官方文件的一點補充,如果想要深入理解,還需從原始碼入手。

參考