共计 6666 个字符,预计需要花费 17 分钟才能阅读完成。
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、YV12packed: Y、U、V 交叉存储,如 YUYVsemi-planar: Y 连续存储,U、V 交叉存储,如 NV21、NV12 二、相关图像格式介绍 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 交叉排序。排列方式如:
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 的位置相反)。排列方式如:
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 交叉排序。
排列方式如:
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
三、图像格式转换由于图像的格式多种多样,转换的方法也不胜枚举,以下只列出部分的图像转换参考代码。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 的算法:
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));
}
}
3.ARGB_8888 转换为 BGR_24 举个例子,对于 4 ×2 的图片,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 转 YV12NV21 转化为 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;
}