圖像格式轉化在人臉識別應用中的實踐

ArcFace 2.0 API目前支持多種圖像格式:BGR2四、NV2一、NV十二、I420、YUYV(Android、IOS只支持其中的部分)。接下來將開始介紹這幾種圖像格式以及部分轉換方式。 1、相關圖像顏色空間介紹 1.RGB顏色空間 RGB顏色空間以Red、Green、Blue三種基本色爲基礎,進行不一樣程度的疊加,產生豐富而普遍的顏色,因此俗稱三基色模式。 常見的RGB格式有:RGB_56五、RGB_88八、ARGB_888八、ARGB_4444等。 2.YUV顏色空間 在YUV顏色空間中,Y用來表示亮度,U和V用來表示色度。 常見的YUV格式有如下幾大類: planar: Y、U、V所有連續存儲,如I420、YV12 packed: Y、U、V交叉存儲,如YUYV semi-planar: Y連續存儲,U、V交叉存儲,如NV2一、NV12 2、相關圖像格式介紹 1.BGR24圖像格式 BGR24圖像格式是一種採用24bpp(bit per pixel)的格式。每一個顏色通道B、G、R各佔8bpp。 排列方式如:算法

B G R  B G R  B G R  B G R  B G R  B G R  B G R  B G R 

B G R  B G R  B G R  B G R  B G R  B G R  B G R  B G R 

B G R  B G R  B G R  B G R  B G R  B G R  B G R  B G R 

B G R  B G R  B G R  B G R  B G R  B G R  B G R  B G

2.NV21圖像格式 NV21圖像格式屬於 YUV顏色空間中的YUV420SP格式,每四個Y份量共用一組U份量和V份量,Y連續排序,U與V交叉排序。 排列方式如:ide

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

V U  V U  V U  V U
V U  V U  V U  V U

3.NV12圖像格式 NV12圖像格式屬於 YUV顏色空間中的YUV420SP格式,每四個Y份量共用一組U份量和V份量,Y連續排序,U與V交叉排序(NV12和NV21只是U與V的位置相反)。 排列方式如:code

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

U V  U V  U V  U V

U V  U V  U V  U V

4.I420圖像格式 I420圖像格式屬於 YUV顏色空間中的YUV420P格式,每四個Y份量共用一組U份量和V份量,Y、U、V各自連續排序。 排列方式如:排序

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

U  U  U  U  U  U  U  U 
V  V  V  V  V  V  V  V

5.YV12圖像格式 YV12圖像格式屬於 YUV顏色空間中的YUV420P格式,每四個Y份量共用一組U份量和V份量,Y、U、V各自連續排序(YV12和I420只是U與V的位置相反)。 排列方式如:圖片

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

V  V  V  V  V  V  V  V 
U  U  U  U  U  U  U  U

6.YUYV圖像格式 YUYV圖像格式屬於 YUV顏色空間中的YUV422格式,每兩個Y份量公用一組U份量和V份量,Y、U、V交叉排序。 排列方式如:get

Y U Y V   Y U Y V  Y U Y V  Y U Y V

Y U Y V   Y U Y V  Y U Y V  Y U Y V

Y U Y V   Y U Y V  Y U Y V  Y U Y V

Y U Y V   Y U Y V  Y U Y V  Y U Y V

3、圖像格式轉換 因爲圖像的格式多種多樣,轉換的方法也不勝枚舉,如下只列出部分的圖像轉換參考代碼。 1.從Bitmap中獲取ARGB_8888圖像格式數據(Android平臺) Bitmap支持多種格式:ALPHA_8,RGB_565,ARGB_4444,ARGB_8888,RGBA_F16,HARDWARE。咱們目前主要選擇ARGB_8888進行格式轉換。 咱們可以使用Bitmap類中的 public void getPixels(@ColorInt int[] pixels, int offset, int stride, int x, int y, int width, int height) 方法獲取int[]類型的argb數據或 public void copyPixelsToBuffer (Buffer dst)方法獲取byte[]類型的ARGB_8888數據。 2.ARGB_8888轉換爲NV21it

根據一個比較常見的rgb轉yuv的算法:io

int y = (66 * r + 129 * g + 25 * b + 128 >> 8) + 16;
int u = (-38 * r - 74 * g + 112 * b + 128 >> 8) + 128;
int v = (112 * r - 94 * g - 18 * b + 128 >> 8) + 128;

便可編寫ARGB轉NV21的方法。基礎

int[]類型的ARGB_8888數據轉換爲NV21:原理

private static byte[] argbToNv21(int[] argb, int width, int height) {
        int yIndex = 0;
        int uvIndex = width * height;
        int argbIndex = 0;
        byte[] nv21 = new byte[width * height * 3 / 2];
        for (int j = 0; j < height; ++j) {
            for (int i = 0; i < width; ++i) {
                //對於int型color數據,格式爲0xAARRGGBB,可進行與運算後移位取對應A R G B,
                //可是該YUV轉換公式中不須要ALPHA,所以咱們只須要取R G B 便可。
                int r = (argb[argbIndex] & 0xFF0000) >> 16;
                int g = (argb[argbIndex] & 0x00FF00) >> 8;
                int b = argb[argbIndex] & 0x0000FF;
                //獲取該像素點的R G B,並轉換爲Y U V,但byte範圍是0x00~0xFF,所以在賦值時還需進行判斷
                int y = (66 * r + 129 * g + 25 * b + 128 >> 8) + 16;
                int u = (-38 * r - 74 * g + 112 * b + 128 >> 8) + 128;
                int v = (112 * r - 94 * g - 18 * b + 128 >> 8) + 128;
                nv21[yIndex++] = (byte) (y < 0 ? 0 : (y > 0xFF ? 0xFF : y));
                if ((j & 1) == 0 && (argbIndex & 1) == 0 && uvIndex < nv21.length - 2) {
                    nv21[uvIndex++] = (byte) (v < 0 ? 0 : (v > 0xFF ? 0xFF : v));
                    nv21[uvIndex++] = (byte) (u < 0 ? 0 : (u > 0xFF ? 0xFF : u));
                }
                ++argbIndex;
            }
        }
        return nv21;
    }

byte[]類型的ARGB_8888數據轉換爲NV21(原理同方法1):

private static byte[] argbToNv21(byte[] argb, int width, int height) {
        int yIndex = 0;
        int uvIndex = width * height;
        int argbIndex = 0;
        byte[] nv21 = new byte[width * height * 3 / 2];
        for (int j = 0; j < height; ++j) {
            for (int i = 0; i < width; ++i) {
                argbIndex++;
                int r = argb[argbIndex++];
                int g = argb[argbIndex++];
                int b = argb[argbIndex++];
                /**
                 * byte在強制轉換爲int時高位會自動以符號位擴充,如:
                 * 0x80(byte類型,十六進制)  -> 10000000(byte類型,二進制) -> 11111111_11111111_11111111_10000000(int類型,二進制) -> -128(int類型,十進制)
                 * 0x7F(byte類型,十六進制)  -> 01111111(byte類型,二進制) -> 00000000_00000000_00000000_01111111(int類型,二進制) -> 127(int類型,十進制)
                 * 所以須要取低八位獲取原byte的無符號值
                 */
                r &= 0x000000FF;
                g &= 0x000000FF;
                b &= 0x000000FF;
                int y = ((66 * r + 129 * g + 25 * b + 128 >> 8) + 16);
                int u = ((-38 * r - 74 * g + 112 * b + 128 >> 8) + 128);
                int v = ((112 * r - 94 * g - 18 * b + 128 >> 8) + 128);
                nv21[yIndex++] = (byte) (y > 0xFF ? 0xFF : (y < 0 ? 0 : y));
                if ((j & 1) == 0 && ((argbIndex >> 2) & 1) == 0 && uvIndex < nv21.length - 2) {
                    nv21[uvIndex++] = (byte) (v > 0xFF ? 0xFF : (v < 0 ? 0 : v));
                    nv21[uvIndex++] = (byte) (u > 0xFF ? 0xFF : (u < 0 ? 0 : u));
                }
            }
        }
        return nv21;
    }

3.ARGB_8888轉換爲BGR_24 舉個例子,對於4x2的圖片,ARGB_8888格式內容爲:

A1 R1 G1 B1  A2 R2 G2 B2  A3 R3 G3 B3  A4 R4 G4 B4
A5 R5 G5 B5  A6 R6 G6 B6  A7 R7 G7 B7  A8 R8 G8 B8

那麼若須要轉化爲BGR_24,內容將變成:

B1 G1 R1  B2 G2 R2  B3 G3 R3  B4 G4 R4
B5 G5 R5  B6 G6 R6  B7 G7 R7  B8 G8 R8

BGR_24內容爲3個byte一組,ARGB_8888內容爲4個byte一組。所以,對於第一組ARGB_8888(A1 R1 G1 B1)和第一組BGR_24(B1 G1 R1),其對應關係爲:

bgr24[0] = argb8888[3];
bgr24[1] = argb8888[2];
bgr24[2] = argb8888[1];

對應的轉換代碼:

public static byte[] argb8888ToBgr24(byte[] argb8888) {
        if (argb8888 == null){
            throw new IllegalArgumentException("invalid image params!");
        }
        int groupNum = argb8888.length / 4;
        byte[] bgr24 = new byte[groupNum * 3];
        int bgr24Index = 0;
        int argb8888Index = 0;
        for (int i = 0; i < groupNum; i++) {
            bgr24[bgr24Index + 0] = argb8888[argb8888Index + 2];
            bgr24[bgr24Index + 1] = argb8888[argb8888Index + 1];
            bgr24[bgr24Index + 2] = argb8888[argb8888Index + 0];
            bgr24Index += 3;
            argb8888Index += 4;
        }
        return bgr24;
    }

4.NV12和NV21的互換 NV21和NV12只是U與V的數據位置不一樣,所以,NV21轉換爲NV12的代碼一樣適用於NV12轉換爲NV21。可參考以下代碼:

public static byte[] nv21ToNv12(byte[] nv21, int width, int height) {
        if (nv21 == null || nv21.length == 0 || width * height * 3 / 2 != nv21.length) {
            throw new IllegalArgumentException("invalid image params!");
        }
        final int ySize = width * height;
        int totalSize = width * height * 3 / 2;

        byte[] nv12 = new byte[nv21.length];
        //複製Y
        System.arraycopy(nv21, 0, nv12, 0, ySize);
        //UV互換
        for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
            nv12[uvIndex] = nv21[uvIndex + 1];
            nv12[uvIndex + 1] = nv21[uvIndex];
        }
        return nv12;
    }

5.NV21轉YV12 NV21轉化爲YV12的過程主要是將其UV數據的交叉排序修改成連續排序。可參考以下代碼:

public static byte[] nv21ToYv12(byte[] nv21, int width, int height) {
        if (nv21 == null || nv21.length == 0 || width * height * 3 / 2 != nv21.length) {
            throw new IllegalArgumentException("invalid image params!");
        }
        final int ySize = width * height;
        int totalSize = width * height * 3 / 2;
        byte[] yv12 = new byte[nv21.length];
        int yv12UIndex = ySize;
        int yv12VIndex = ySize * 5 / 4;
        //複製Y
        System.arraycopy(nv21, 0, yv12, 0, ySize);
        //複製UV
        for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
            yv12[yv12UIndex++] = nv21[uvIndex];
            yv12[yv12VIndex++] = nv21[uvIndex + 1];
        }
        return yv12;
    }

6.YUYV轉NV12 在YUYV格式中,兩個Y共用一組U和V,而NV12是四個Y公用一組U和V,所以,若須要將YUYV轉化爲NV12,須要捨棄一半的U和V。可參考以下代碼:

public static byte[] yuyvToNv12(byte[] yuyv, int width, int height) {
        if (yuyv == null || yuyv.length == 0) {
            throw new IllegalArgumentException("invalid image params!");
        }
        int ySize = yuyv.length / 2;
        byte[] nv12 = new byte[yuyv.length * 3 / 4];
        int nv12YIndex = 0;
        int nv12UVIndex = ySize;
        boolean copyUV = false;
        int lineDataSize = width * 2;
        for (int i = 0, yuyvIndex = 0; i < height; i++, yuyvIndex += lineDataSize) {
            if (copyUV) {
                for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) {
                    //複製Y
                    nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset];
                    nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset + 2];
                    //複製UV
                    nv12[nv12UVIndex++] = yuyv[yuyvIndex + lineOffset + 1];
                    nv12[nv12UVIndex++] = yuyv[yuyvIndex + lineOffset + 3];
                }
            } else {
                for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) {
                    //複製Y
                    nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset];
                    nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset + 2];
                }
            }
            copyUV = !copyUV;
        }
        return nv12;
    }

7.I420和YV12的互換 I420和YV12只是U與V的數據位置不一樣,所以,I420轉換爲YV12的代碼一樣適用於YV12轉換爲I420。可參考以下代碼:

public static byte[] i420ToYv12(byte[] i420) {
        if (i420 == null || i420.length == 0 || i420.length % 6 != 0) {
            throw new IllegalArgumentException("invalid image params!");
        }
        int ySize = i420.length * 2 / 3;
        int uvSize = i420.length / 6;
        byte[] yv12 = new byte[i420.length];
        //複製Y
        System.arraycopy(i420, 0, yv12, 0, ySize);
        //UV互換
        System.arraycopy(i420, ySize, yv12, ySize + uvSize, uvSize);
        System.arraycopy(i420, ySize + uvSize, yv12, ySize, uvSize);
        return yv12;
    }
相關文章
相關標籤/搜索