内容简介:ArcFace 2.0 API目前支持多种图像格式:在
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。
排列方式如:
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 R 复制代码
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、U、V的共用关系,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各自连续排序(为了便于说明Y、U、V的共用关系,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 复制代码
三、图像格式转换
由于图像的格式多种多样,转换的方法也不胜枚举,只要了解了 YUV
和 RGB
数据的排列方式,自己编写图像转换代码也花不了多少时间。以下列出部分的图像转换的 Java 代码供参考。
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
转换为 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] = argb8888[argb8888Index + 2];
bgr24[bgr24Index + 1] = argb8888[argb8888Index + 1];
bgr24[bgr24Index + 2] = argb8888[argb8888Index];
bgr24Index += 3;
argb8888Index += 4;
}
return bgr24;
}
复制代码
3. 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; 复制代码
转换方法:
-
int[]类型的ARGB_8888数据转换为NV21:
private static byte[] argbToNv21(int[] argb, int width, int height) {
if (argb == null || argb.length == 0 || width * height != argb.length) {
throw new IllegalArgumentException("invalid image params!");
}
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;
nv21[yIndex++] = (byte) (y < 0 ? 0 : (y > 0xFF ? 0xFF : y));
if ((j & 1) == 0 && (argbIndex & 1) == 0 && uvIndex < nv21.length - 2) {
int u = (-38 * r - 74 * g + 112 * b + 128 >> 8) + 128;
int v = (112 * r - 94 * g - 18 * b + 128 >> 8) + 128;
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) {
if (argb == null || argb.length == 0 || width * height * 4 != argb.length) {
throw new IllegalArgumentException("invalid image params!");
}
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++];
r &= 0x000000FF;
g &= 0x000000FF;
b &= 0x000000FF;
int y = ((66 * r + 129 * g + 25 * b + 128 >> 8) + 16);
nv21[yIndex++] = (byte) (y > 0xFF ? 0xFF : (y < 0 ? 0 : y));
if ((j & 1) == 0 && ((argbIndex >> 2) & 1) == 0 && uvIndex < nv21.length - 2) {
int u = ((-38 * r - 74 * g + 112 * b + 128 >> 8) + 128);
int v = ((112 * r - 94 * g - 18 * b + 128 >> 8) + 128);
nv21[uvIndex++] = (byte) (v > 0xFF ? 0xFF : (v < 0 ? 0 : v));
nv21[uvIndex++] = (byte) (u > 0xFF ? 0xFF : (u < 0 ? 0 : u));
}
}
}
return nv21;
}
复制代码
4. NV21
转换为 BGR24
yuv
转 rgb
算法:
int r = (int) ((y & 0xFF) + 1.4075 * ((v & 0xFF) - 128)); int g = (int) ((y & 0xFF) - 0.3455 * ((u & 0xFF) - 128) - 0.7169 * ((v & 0xFF) - 128)); int b = (int) ((y & 0xFF) + 1.779 * ((u & 0xFF) - 128)); 复制代码
转换方法:
private static byte[] nv21ToBgr24(byte[] nv21, int width, int height) {
if (nv21 == null || nv21.length == 0 || width * height * 3 / 2 != nv21.length) {
throw new IllegalArgumentException("invalid image params!");
}
byte[] bgr24 = new byte[width * height * 3];
int bgrLineSize = width * 3;
//偶数行的bgr数据下标
int evenLineBgrIndex = 0;
//奇数行的bgr数据下标
int oddLineBgrIndex = bgrLineSize;
//当前一行y数据最左边的下标
int yLineStart = 0;
//uv数据的下标
int uvIndex = width * height;
//由于NV21的共用关系,每2行做一次转换
for (int i = 0; i < height; i += 2) {
for (int widthOffset = 0; widthOffset < width; widthOffset++) {
byte v = nv21[uvIndex];
byte u = nv21[uvIndex + 1];
byte yEven = nv21[yLineStart + widthOffset];
byte yOdd = nv21[yLineStart + width + widthOffset];
//偶数行YUV转RGB
int r, g, b;
r = (int) ((yEven & 0xFF) + 1.4075 * ((v & 0xFF) - 128));
g = (int) ((yEven & 0xFF) - 0.3455 * ((u & 0xFF) - 128) - 0.7169 * ((v & 0xFF) - 128));
b = (int) ((yEven & 0xFF) + 1.779 * ((u & 0xFF) - 128));
r = r < 0 ? 0 : r > 0xFF ? 0xFF : r;
g = g < 0 ? 0 : g > 0xFF ? 0xFF : g;
b = b < 0 ? 0 : b > 0xFF ? 0xFF : b;
bgr24[evenLineBgrIndex++] = (byte) b;
bgr24[evenLineBgrIndex++] = (byte) g;
bgr24[evenLineBgrIndex++] = (byte) r;
//奇数行YUV转RGB
r = (int) ((yOdd & 0xFF) + 1.4075 * ((v & 0xFF) - 128));
g = (int) ((yOdd & 0xFF) - 0.3455 * ((u & 0xFF) - 128) - 0.7169 * ((v & 0xFF) - 128));
b = (int) ((yOdd & 0xFF) + 1.779 * ((u & 0xFF) - 128));
r = r < 0 ? 0 : r > 0xFF ? 0xFF : r;
g = g < 0 ? 0 : g > 0xFF ? 0xFF : g;
b = b < 0 ? 0 : b > 0xFF ? 0xFF : b;
bgr24[oddLineBgrIndex++] = (byte) b;
bgr24[oddLineBgrIndex++] = (byte) g;
bgr24[oddLineBgrIndex++] = (byte) r;
//每两个y将uv下标增1
if ((widthOffset & 1) == 1) {
uvIndex += 2;
}
}
//由于在内层循环中已经做过width * 3次自增,所以外层循环中只需要增加一行
evenLineBgrIndex += bgrLineSize;
oddLineBgrIndex += bgrLineSize;
//y增2行
yLineStart += width * 2;
}
return bgr24;
}
复制代码
5. 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;
}
复制代码
6. 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;
}
复制代码
7. YUYV
转 NV12
在 YUYV
格式中,两个 Y
共用一组 U
和 V
,而 NV12
是四个 Y
共用一组 U
和 V
,因此,这是一个 YUV422
转 YUV420
的过程,需要舍弃一半的 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;
}
复制代码
8. 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;
}
复制代码
9. I420
转换为 YUYV
I420
和 YUYV
相比, I420
的 U
和 V
只有 YUYV
的一半,这是一个 YUV420
转 YUV422
的过程,缺损的数据只能通过复用 U
和 V
弥补。
public static byte[] i420ToYuyv(byte[] i420, int width, int height) {
if (i420 == null || i420.length == 0 || i420.length != width * height * 3 / 2) {
throw new IllegalArgumentException("invalid image params!");
}
byte[] yuyv = new byte[width * height * 2];
int yuyvLineSize = width * 2;
int i420YIndex = 0;
int i420UIndex = width * height;
int i420VIndex = width * height * 5 / 4;
int yuyvLineStart = 0;
for (int i = 0; i < height; i += 2) {
for (int lineOffset = 0; lineOffset < yuyvLineSize; lineOffset += 4) {
byte u = i420[i420UIndex++];
byte v = i420[i420VIndex++];
//偶数行数据赋值
int yuyvOffset = yuyvLineStart + lineOffset;
yuyv[yuyvOffset] = i420[i420YIndex];
yuyv[yuyvOffset + 1] = u;
yuyv[yuyvOffset + 2] = i420[i420YIndex + 1];
yuyv[yuyvOffset + 3] = v;
//奇数行数据赋值
int yuyvNextLineOffset = yuyvLineStart + yuyvLineSize + lineOffset;
yuyv[yuyvNextLineOffset] = i420[i420YIndex + width];
yuyv[yuyvNextLineOffset + 1] = u;
yuyv[yuyvNextLineOffset + 2] = i420[i420YIndex + width + 1];
yuyv[yuyvNextLineOffset + 3] = v;
i420YIndex += 2;
}
i420YIndex += width;
yuyvLineStart += (width << 2);
}
return yuyv;
}
复制代码
四、图像裁剪
与格式转换相同,只要了解的图像的排列方式,图像的裁剪也并不困难。本文提供一种 RGB
颜色空间的图像裁剪和一种 YUV
颜色空间的图像裁剪。
1. 裁剪NV21
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;
}
复制代码
9. I420
转换为 YUYV
I420
和 YUYV
相比, I420
的 U
和 V
只有 YUYV
的一半,这是一个 YUV420
转 YUV422
的过程,缺损的数据只能通过复用 U
和 V
弥补。
public static byte[] i420ToYuyv(byte[] i420, int width, int height) {
if (i420 == null || i420.length == 0 || i420.length != width * height * 3 / 2) {
throw new IllegalArgumentException("invalid image params!");
}
byte[] yuyv = new byte[width * height * 2];
int yuyvLineSize = width * 2;
int i420YIndex = 0;
int i420UIndex = width * height;
int i420VIndex = width * height * 5 / 4;
int yuyvLineStart = 0;
for (int i = 0; i < height; i += 2) {
for (int lineOffset = 0; lineOffset < yuyvLineSize; lineOffset += 4) {
byte u = i420[i420UIndex++];
byte v = i420[i420VIndex++];
//偶数行数据赋值
int yuyvOffset = yuyvLineStart + lineOffset;
yuyv[yuyvOffset] = i420[i420YIndex];
yuyv[yuyvOffset + 1] = u;
yuyv[yuyvOffset + 2] = i420[i420YIndex + 1];
yuyv[yuyvOffset + 3] = v;
//奇数行数据赋值
int yuyvNextLineOffset = yuyvLineStart + yuyvLineSize + lineOffset;
yuyv[yuyvNextLineOffset] = i420[i420YIndex + width];
yuyv[yuyvNextLineOffset + 1] = u;
yuyv[yuyvNextLineOffset + 2] = i420[i420YIndex + width + 1];
yuyv[yuyvNextLineOffset + 3] = v;
i420YIndex += 2;
}
i420YIndex += width;
yuyvLineStart += (width << 2);
}
return yuyv;
}
复制代码
四、图像裁剪
与格式转换相同,只要了解的图像的排列方式,图像的裁剪也并不困难。本文提供一种 RGB
颜色空间的图像裁剪和一种 YUV
颜色空间的图像裁剪。
1. 裁剪NV21或NV12
public static byte[] cropYuv420sp(byte[] yuv420sp, int width, int height, int left, int top, int right, int bottom) {
if (yuv420sp == null || yuv420sp.length == 0 || width * height * 3 / 2 != yuv420sp.length) {
throw new IllegalArgumentException("invalid image params!");
}
if (left < 0 || top < 0 || right > width || bottom > height) {
throw new IllegalArgumentException("rect out of bounds!");
}
if (right < left || bottom < top) {
throw new IllegalArgumentException("invalid rect!");
}
if (((right - left) & 1) == 1 || ((bottom - top) & 1) == 1) {
throw new IllegalArgumentException("yuv420sp width and height must be even!");
}
if ((left & 1 )== 1){
throw new IllegalArgumentException("yuv420sp crop left borderIndex and right borderIndex must be even!");
}
int cropImageWidth = right - left;
int cropImageHeight = bottom - top;
byte[] cropYuv420sp = new byte[cropImageWidth * cropImageHeight * 3 / 2];
//复制Y
int originalYLineStart = top * width;
int targetYIndex = 0;
//复制UV
int originalUVLineStart = width * height + top * width / 2;
int targetUVIndex = cropImageWidth * cropImageHeight;
for (int i = top; i < bottom; i++) {
System.arraycopy(yuv420sp, originalYLineStart + left, cropYuv420sp, targetYIndex, cropImageWidth);
originalYLineStart += width;
targetYIndex += cropImageWidth;
if ((i & 1) == 0) {
System.arraycopy(yuv420sp, originalUVLineStart + left, cropYuv420sp, targetUVIndex, cropImageWidth);
originalUVLineStart += width;
targetUVIndex += cropImageWidth;
}
}
return cropYuv420sp;
}
复制代码
2. 裁剪BGR24
public static byte[] cropBgr24(byte[] bgr24, int width, int height, int left, int top, int right, int bottom) {
if (bgr24 == null || bgr24.length == 0 || width * height * 3 != bgr24.length) {
throw new IllegalArgumentException("invalid image params!");
}
if (left < 0 || top < 0 || right > width || bottom > height) {
throw new IllegalArgumentException("rect out of bounds!");
}
if (right < left || bottom < top) {
throw new IllegalArgumentException("invalid rect!");
}
int cropImageWidth = right - left;
int cropImageHeight = bottom - top;
byte[] cropBgr24 = new byte[cropImageWidth * cropImageHeight * 3];
int originalLineStart = top * width * 3;
int targetIndex = 0;
for (int i = top; i < bottom; i++) {
System.arraycopy(bgr24, originalLineStart + left * 3, cropBgr24, targetIndex, cropImageWidth * 3);
originalLineStart += width * 3;
targetIndex += cropImageWidth * 3;
}
return cropBgr24;
}
复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 他把一块屏摆进数百家便利店 人脸识别挖掘线下流量 已转化会员过万
- js的类型转化三两事儿
- archTIS:将数据安全转化为经济增长
- 如何将JavaScript转化成Swift?(一)
- python3 第十章 - 如何进行进制转化
- 用Golang将图片转化成ASCII码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Masterminds of Programming
Federico Biancuzzi、Chromatic / O'Reilly Media / 2009-03-27 / USD 39.99
Description Masterminds of Programming features exclusive interviews with the creators of several historic and highly influential programming languages. Think along with Adin D. Falkoff (APL), Jame......一起来看看 《Masterminds of Programming》 这本书的介绍吧!