Android: Image類淺析(結合YUV_420_888)
簡介
Image
類在API 19中引入,但真正開始發揮作用還是在API 21引入CameraDevice
和MediaCodec
的增強後。API 21引入了Camera2
,deprecated掉了Camera
,確立Image
作為相機得到的原始幀資料的載體;硬體編解碼的MediaCodec
類加入了對Image
和Image
的封裝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
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分量的管理上。本篇算作對官方文件的一點補充,如果想要深入理解,還需從原始碼入手。