当前位置:   article > 正文

图像格式转化在人脸识别应用中的实践

人脸识别注册图像格式有哪些

ArcFace 2.0 API目前支持多种图像格式:BGR24、NV21、NV12、I420、YUYV(Android、IOS只支持其中的部分)。接下来将开始介绍这几种图像格式以及部分转换方式。
一、相关图像颜色空间介绍
1.RGB颜色空间
RGB颜色空间以Red、Green、Blue三种基本色为基础,进行不同程度的叠加,产生丰富而广泛的颜色,所以俗称三基色模式。
常见的RGB格式有:RGB_565、RGB_888、ARGB_8888、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交叉存储,如NV21、NV12
二、相关图像格式介绍
1.BGR24图像格式
BGR24图像格式是一种采用24bpp(bit per pixel)的格式。每个颜色通道B、G、R各占8bpp。
排列方式如:

  1. B G R B G R B G R B G R B G R B G R B G R B G R
  2. B G R B G R B G R B G R B G R B G R B G R B G R
  3. B G R B G R B G R B G R B G R B G R B G R B G R
  4. 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交叉排序。
排列方式如:

  1. Y Y Y Y Y Y Y Y
  2. Y Y Y Y Y Y Y Y
  3. Y Y Y Y Y Y Y Y
  4. Y Y Y Y Y Y Y Y
  5. V U V U V U V U
  6. V U V U V U V U

3.NV12图像格式
NV12图像格式属于 YUV颜色空间中的YUV420SP格式,每四个Y分量共用一组U分量和V分量,Y连续排序,U与V交叉排序(NV12和NV21只是U与V的位置相反)。
排列方式如:

  1. Y Y Y Y Y Y Y Y
  2. Y Y Y Y Y Y Y Y
  3. Y Y Y Y Y Y Y Y
  4. Y Y Y Y Y Y Y Y
  5. U V U V U V U V
  6. U V U V U V U V

  

4.I420图像格式
I420图像格式属于 YUV颜色空间中的YUV420P格式,每四个Y分量共用一组U分量和V分量,Y、U、V各自连续排序。
排列方式如:

  1. Y Y Y Y Y Y Y Y
  2. Y Y Y Y Y Y Y Y
  3. Y Y Y Y Y Y Y Y
  4. Y Y Y Y Y Y Y Y
  5. U U U U U U U U
  6. V V V V V V V V  

5.YV12图像格式
YV12图像格式属于 YUV颜色空间中的YUV420P格式,每四个Y分量共用一组U分量和V分量,Y、U、V各自连续排序(YV12和I420只是U与V的位置相反)。
排列方式如:

  1. Y Y Y Y Y Y Y Y
  2. Y Y Y Y Y Y Y Y
  3. Y Y Y Y Y Y Y Y
  4. Y Y Y Y Y Y Y Y
  5. V V V V V V V V
  6. U U U U U U U U

6.YUYV图像格式
YUYV图像格式属于 YUV颜色空间中的YUV422格式,每两个Y分量公用一组U分量和V分量,Y、U、V交叉排序。
排列方式如:

  1. Y U Y V Y U Y V Y U Y V Y U Y V
  2. Y U Y V Y U Y V Y U Y V Y U Y V
  3. Y U Y V Y U Y V Y U Y V Y U Y V
  4. Y U Y V Y U Y V Y U Y V Y U Y V

三、图像格式转换
由于图像的格式多种多样,转换的方法也不胜枚举,以下只列出部分的图像转换参考代码。
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转换为NV21

根据一个比较常见的rgb转yuv的算法:

 

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

即可编写ARGB转NV21的方法。

int[]类型的ARGB_8888数据转换为NV21:

  1. private static byte[] argbToNv21(int[] argb, int width, int height) {
  2. int yIndex = 0;
  3. int uvIndex = width * height;
  4. int argbIndex = 0;
  5. byte[] nv21 = new byte[width * height * 3 / 2];
  6. for (int j = 0; j < height; ++j) {
  7. for (int i = 0; i < width; ++i) {
  8. //对于int型color数据,格式为0xAARRGGBB,可进行与运算后移位取对应A R G B,
  9. //但是该YUV转换公式中不需要ALPHA,因此我们只需要取R G B 即可。
  10. int r = (argb[argbIndex] & 0xFF0000) >> 16;
  11. int g = (argb[argbIndex] & 0x00FF00) >> 8;
  12. int b = argb[argbIndex] & 0x0000FF;
  13. //获取该像素点的R G B,并转换为Y U V,但byte范围是0x00~0xFF,因此在赋值时还需进行判断
  14. int y = (66 * r + 129 * g + 25 * b + 128 >> 8) + 16;
  15. int u = (-38 * r - 74 * g + 112 * b + 128 >> 8) + 128;
  16. int v = (112 * r - 94 * g - 18 * b + 128 >> 8) + 128;
  17. nv21[yIndex++] = (byte) (y < 0 ? 0 : (y > 0xFF ? 0xFF : y));
  18. if ((j & 1) == 0 && (argbIndex & 1) == 0 && uvIndex < nv21.length - 2) {
  19. nv21[uvIndex++] = (byte) (v < 0 ? 0 : (v > 0xFF ? 0xFF : v));
  20. nv21[uvIndex++] = (byte) (u < 0 ? 0 : (u > 0xFF ? 0xFF : u));
  21. }
  22. ++argbIndex;
  23. }
  24. }
  25. return nv21;
  26. }


byte[]类型的ARGB_8888数据转换为NV21(原理同方法1):

  1. private static byte[] argbToNv21(byte[] argb, int width, int height) {
  2. int yIndex = 0;
  3. int uvIndex = width * height;
  4. int argbIndex = 0;
  5. byte[] nv21 = new byte[width * height * 3 / 2];
  6. for (int j = 0; j < height; ++j) {
  7. for (int i = 0; i < width; ++i) {
  8. argbIndex++;
  9. int r = argb[argbIndex++];
  10. int g = argb[argbIndex++];
  11. int b = argb[argbIndex++];
  12. /**
  13. * byte在强制转换为int时高位会自动以符号位扩充,如:
  14. * 0x80(byte类型,十六进制) -> 10000000(byte类型,二进制) -> 11111111_11111111_11111111_10000000(int类型,二进制) -> -128(int类型,十进制)
  15. * 0x7F(byte类型,十六进制) -> 01111111(byte类型,二进制) -> 00000000_00000000_00000000_01111111(int类型,二进制) -> 127(int类型,十进制)
  16. * 因此需要取低八位获取原byte的无符号值
  17. */
  18. r &= 0x000000FF;
  19. g &= 0x000000FF;
  20. b &= 0x000000FF;
  21. int y = ((66 * r + 129 * g + 25 * b + 128 >> 8) + 16);
  22. int u = ((-38 * r - 74 * g + 112 * b + 128 >> 8) + 128);
  23. int v = ((112 * r - 94 * g - 18 * b + 128 >> 8) + 128);
  24. nv21[yIndex++] = (byte) (y > 0xFF ? 0xFF : (y < 0 ? 0 : y));
  25. if ((j & 1) == 0 && ((argbIndex >> 2) & 1) == 0 && uvIndex < nv21.length - 2) {
  26. nv21[uvIndex++] = (byte) (v > 0xFF ? 0xFF : (v < 0 ? 0 : v));
  27. nv21[uvIndex++] = (byte) (u > 0xFF ? 0xFF : (u < 0 ? 0 : u));
  28. }
  29. }
  30. }
  31. return nv21;
  32. }

  

3.ARGB_8888转换为BGR_24
举个例子,对于4x2的图片,ARGB_8888格式内容为:

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


那么若需要转化为BGR_24,内容将变成:

  1. B1 G1 R1 B2 G2 R2 B3 G3 R3 B4 G4 R4
  2. 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),其对应关系为:

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

对应的转换代码:

 

  1. public static byte[] argb8888ToBgr24(byte[] argb8888) {
  2. if (argb8888 == null){
  3. throw new IllegalArgumentException("invalid image params!");
  4. }
  5. int groupNum = argb8888.length / 4;
  6. byte[] bgr24 = new byte[groupNum * 3];
  7. int bgr24Index = 0;
  8. int argb8888Index = 0;
  9. for (int i = 0; i < groupNum; i++) {
  10. bgr24[bgr24Index + 0] = argb8888[argb8888Index + 2];
  11. bgr24[bgr24Index + 1] = argb8888[argb8888Index + 1];
  12. bgr24[bgr24Index + 2] = argb8888[argb8888Index + 0];
  13. bgr24Index += 3;
  14. argb8888Index += 4;
  15. }
  16. return bgr24;
  17. }


4.NV12和NV21的互换
NV21和NV12只是U与V的数据位置不同,因此,NV21转换为NV12的代码同样适用于NV12转换为NV21。可参考如下代码:

 

  1. public static byte[] nv21ToNv12(byte[] nv21, int width, int height) {
  2. if (nv21 == null || nv21.length == 0 || width * height * 3 / 2 != nv21.length) {
  3. throw new IllegalArgumentException("invalid image params!");
  4. }
  5. final int ySize = width * height;
  6. int totalSize = width * height * 3 / 2;
  7. byte[] nv12 = new byte[nv21.length];
  8. //复制Y
  9. System.arraycopy(nv21, 0, nv12, 0, ySize);
  10. //UV互换
  11. for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
  12. nv12[uvIndex] = nv21[uvIndex + 1];
  13. nv12[uvIndex + 1] = nv21[uvIndex];
  14. }
  15. return nv12;
  16. }

5.NV21转YV12
NV21转化为YV12的过程主要是将其UV数据的交叉排序修改为连续排序。可参考如下代码:

  1. public static byte[] nv21ToYv12(byte[] nv21, int width, int height) {
  2. if (nv21 == null || nv21.length == 0 || width * height * 3 / 2 != nv21.length) {
  3. throw new IllegalArgumentException("invalid image params!");
  4. }
  5. final int ySize = width * height;
  6. int totalSize = width * height * 3 / 2;
  7. byte[] yv12 = new byte[nv21.length];
  8. int yv12UIndex = ySize;
  9. int yv12VIndex = ySize * 5 / 4;
  10. //复制Y
  11. System.arraycopy(nv21, 0, yv12, 0, ySize);
  12. //复制UV
  13. for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
  14. yv12[yv12UIndex++] = nv21[uvIndex];
  15. yv12[yv12VIndex++] = nv21[uvIndex + 1];
  16. }
  17. return yv12;
  18. }


6.YUYV转NV12
在YUYV格式中,两个Y共用一组U和V,而NV12是四个Y公用一组U和V,因此,若需要将YUYV转化为NV12,需要舍弃一半的U和V。可参考如下代码:

 

  1. public static byte[] yuyvToNv12(byte[] yuyv, int width, int height) {
  2. if (yuyv == null || yuyv.length == 0) {
  3. throw new IllegalArgumentException("invalid image params!");
  4. }
  5. int ySize = yuyv.length / 2;
  6. byte[] nv12 = new byte[yuyv.length * 3 / 4];
  7. int nv12YIndex = 0;
  8. int nv12UVIndex = ySize;
  9. boolean copyUV = false;
  10. int lineDataSize = width * 2;
  11. for (int i = 0, yuyvIndex = 0; i < height; i++, yuyvIndex += lineDataSize) {
  12. if (copyUV) {
  13. for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) {
  14. //复制Y
  15. nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset];
  16. nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset + 2];
  17. //复制UV
  18. nv12[nv12UVIndex++] = yuyv[yuyvIndex + lineOffset + 1];
  19. nv12[nv12UVIndex++] = yuyv[yuyvIndex + lineOffset + 3];
  20. }
  21. } else {
  22. for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) {
  23. //复制Y
  24. nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset];
  25. nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset + 2];
  26. }
  27. }
  28. copyUV = !copyUV;
  29. }
  30. return nv12;
  31. }

7.I420和YV12的互换
I420和YV12只是U与V的数据位置不同,因此,I420转换为YV12的代码同样适用于YV12转换为I420。可参考如下代码:

  1. public static byte[] i420ToYv12(byte[] i420) {
  2. if (i420 == null || i420.length == 0 || i420.length % 6 != 0) {
  3. throw new IllegalArgumentException("invalid image params!");
  4. }
  5. int ySize = i420.length * 2 / 3;
  6. int uvSize = i420.length / 6;
  7. byte[] yv12 = new byte[i420.length];
  8. //复制Y
  9. System.arraycopy(i420, 0, yv12, 0, ySize);
  10. //UV互换
  11. System.arraycopy(i420, ySize, yv12, ySize + uvSize, uvSize);
  12. System.arraycopy(i420, ySize + uvSize, yv12, ySize, uvSize);
  13. return yv12;
  14. }

  

转载于:https://www.cnblogs.com/Zzz-/p/10511097.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/46116?site
推荐阅读
相关标签
  

闽ICP备14008679号