2018年4月18日 星期三

【Android】camera2 -- YUV格式圖檔

Android5以後的android.hardware.camera被列為「@Deprecated,請改用android.hardware.camera2。」

但就算不提「android.hardware.camera2」的複雜,傳入的照片資料結構本身跟「android.hardware.camera」的差異也非常大,如果要升級,光是改寫相機硬體與權限套件還不夠,還要連照片的轉檔也一併完成。


Android相機預設的檔案格式稱為YUV,它是種「用三種向量的方式來記錄圖片資料」的格式(完全無法理解怎麼實作),一共有420、422和444三種格式。(但也可以指定相機傳入舊格式檔案,但會造成「無法預覽」,自然也無法進行拍照。這格式的檔案在實用上功能有點不明。)

這檔案的格式技術細節未來需要慢慢補完。



Android使用這種格式的檔案奇妙之處在於:View元件本身幾乎都不支援這個格式的檔案,依然支援傳統RGB(Bitmap)或Drawalbe。

「android.hardware.camera」中取得的是YUV圖片的raw資料格式,也就byte矩陣檔。

但「android.hardware.caemra2」中經由「ImageReader」取得的是YUV圖片的資料格式封裝物件「Image」,這東西並不是YUV檔的實體,也無法直接轉成raw資料格式。

一個Image中有三層「Plane」,每一層都可以視為是對應「YUV」中的一種向量資料,因為是向量,所以每一張照片中的每一層Plane資料格式大小其實都不一定,跟RGB格式不一樣,只要知道的圖片長寬和格式,幾乎就可以順利估算出檔案大小或資料長度。

將Image物件格式轉成raw格式的演算法如下。三層ByteBuffer剛好是依序三層Plane的ByteBuffer資料。

// nv12: true = NV12, false = NV21
    public static byte[] YUV_420_888toNV(ByteBuffer yBuffer, ByteBuffer uBuffer, ByteBuffer vBuffer, boolean nv12) {
        byte[] nv;

        int ySize = yBuffer.remaining();
        int uSize = uBuffer.remaining();
        int vSize = vBuffer.remaining();

        nv = new byte[ySize + uSize + vSize];

        yBuffer.get(nv, 0, ySize);
        if (nv12) {//U and V are swapped
            vBuffer.get(nv, ySize, vSize);
            uBuffer.get(nv, ySize + vSize, uSize);
        } else {
            uBuffer.get(nv, ySize , uSize);
            vBuffer.get(nv, ySize + uSize, vSize);
        }
        return nv;
    }

但用QRCode Decode的套件:ZXing作測試,它可以接受「android.hardware.camera」的raw資料,但仍無法接收這個格式的資料。所以這個演算法還需要再檢討。

但奇妙的是:這個演算法獲得的raw格式資料確實可以轉成RGB格式檔案/Bitmap物件。

演算法如下。


public static byte[] NV21toJPEG(byte[] nv21, int width, int height, int quality) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
        yuv.compressToJpeg(new Rect(0, 0, width, height), quality, out);
        return out.toByteArray();
    }

參考連結

https://stackoverflow.com/questions/9325861/converting-yuv-rgbimage-processing-yuv-during-onpreviewframe-in-android