【Android Camera2】玩转图像数据

  • 业务场景介绍
  • NV21数据旋转
    • 逐像素遍历法
  • NV21数据镜像
    • 逐像素遍历法
    • 中心翻转法
  • NV21转RGB/RGBA数据
    • 逐像素遍历法
  • NV21组合操作(旋转,镜像,转RGB/RGBA)
    • NV21数组顺时针转90度+转换RGBA数据
    • NV21数组顺时针转270度+镜像+转换RGBA数据
  • 文章总结
  • 代码获取
    • NV21数据向右旋转90°
    • NV21数据向右旋转270°
    • NV21数据镜像
    • NV21数据转RGBA数据
    • NV21向右旋转90+镜像
    • NV21向右旋转270+镜像
    • NV21向右旋转90+转RGBA
    • NV21数组顺时针转270度+镜像+转换RGBA数据

业务场景介绍

  最近也是在做Android相机这一块,使用的API是Android Camera2。熟悉Android 相机开发的小伙伴都知道Android Camera开发的过程和NV21打交道是必不可少的。上一篇文章相机NV21数据获取详细介绍了如何从将从相机得到的Image转换成NV21,得到了NV21数据后,当然就是要和它打交道了~
  举个例子,由于硬件系统,相机的Api,以及前后置相机的差异,我们得到数据有可能是需要调整的。比如从相机的前置摄像头拿到数据时,我们可能需要对相机进行镜像,又或者拿到的数据本身就是倒着的,我们要将它旋转90°等。
  其实网上关于NV21数据的旋转,镜像和转换RGBA的数据大多数都有,但是我们很多情况不能使用拿来主义直接COPY。我们需要根据我们自己的场景,选择并修改代码,才能达到最好的效果。为什么呢?
  举例来说:当你只是需要对图像做镜像操作,那么你就应该使用我介绍的中心翻转法而不应该使用逐像素法,因为它的耗时比逐像素遍历法的一半;
  当你需要对图像旋转90°再镜像时:你可能需要自己修改代码来达到性能的最佳。当然你也可以从网上找代码,首先对图像进行旋转90°,再去做一个镜像,这样做只是效率大大折扣而已。
  如果直接拿网上的代码,并且不同原理,当你的适用场景变了的时候,你可能没法应对。另外更重要的一点,网上的代码不一定适合你的运用场景,直接拿过来用可能导致性能损耗。
  本文首先大致讲解各种操作的方法,以及它的性能。然后分析当需要组合操作时,我们应该如何修改代码来降低性能的损耗。最后对代码进行了一个汇总,对于各个场景给出一个应该使用的代码,如果不想看原理的同学可以直接去取(当然还是希望大家能够懂原理,能够自己去修改,这样才能够应对不同的场景)。本文会对关键的算法进行一个耗时的统计,使用的手机是小米12,图像的分辨率大小为1280*720.

NV21数据旋转

  NV21数据的旋转,其实就是一个数组的旋转。就是类似于leetcode 48题 旋转图像,我们要将一个二维数据的数据进行旋转。不同的地方有两点:1、NV21数据的数据是nm形式的矩阵,而题中给出的数据是nn型的,因此官方题解中的原地旋转法用翻转代替旋转法就不可以用了,我们只能使用逐像素遍历法了。2、对于NV21的数据来说,它是先由图像像素点的Y值排列,然后由像素点的UV序列排列而成的。我们旋转的时候需要分开来转,首先对Y序列旋转,然后将UV序列旋转,再将它们组合起来。
  对于我这里讲的不太明白的小伙伴可以先去看一下我上一篇文章相机NV21数据获取了解一下图像NV21的数据格式。这样有助于理解代码。

逐像素遍历法

  图像顺时针旋转90°。代码比较好理解,首先是对Y序列进行旋转,也就是对应着Rotate the Y luma部分。第二部分是对UV数据进行旋转,也就是对应着Rotate the U and V color components。将两个部分依次排列也就得到了最后旋转的结果。

 private byte[] rotateYUVDegree90(byte[] data, int imageWidth, int imageHeight) {byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];// Rotate the Y lumaint i = 0;for (int x = 0; x < imageWidth; x++) {for (int y = imageHeight - 1; y >= 0; y--) {yuv[i] = data[y * imageWidth + x];i++;}}// Rotate the U and V color componentsfor (int x = 0; x < imageWidth; x = x + 2) {for (int y = imageHeight / 2 - 1; y >=0; y--) {yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];i++;yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + (x + 1)];i++;}}return yuv;}

NV21数据镜像

逐像素遍历法

  逐像素遍历法,和前面图像的旋转90°思路一样的,只不过旋转前和旋转后图像对应的位置关系发生了变化。你可以看到,两个代码都是相似,都是先对y通道处理,在对uv通道处理,两次循环,不同的地方不过在于如何处理它们的映射关系罢了。也就是在 yuv[i] 的赋值语句中,data数组内的下标不同而已。此处代码用时1.82ms。

    private byte[] MirrorYUV(byte[] data, int imageWidth, int imageHeight) {byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];// Mirror the Y lumaint i = 0;for (int y = 0; y < imageHeight; y++) {for (int x = imageWidth-1; x >=0; x--) {yuv[i] = data[y * imageWidth + x];i++;}}// Rotate the U and V color componentsfor (int y = 0; y < imageHeight/2; y++) {for (int x = imageWidth-1; x >=0; x= x-2) {yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x - 1];i++;yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];i++;}}return yuv;}

中心翻转法

  由于镜像操作的特殊性,镜像后的图像和数据其实就是图像按照中心线进行了左右的对调。利用这一个特征,我们可以对算法进行简化,也就是只遍历图像的前半部分,然后让前半部分后后半部分进项交换,这样遍历的次数就少了一半。算法用时0.8ms。

private byte[] NV21_mirror_center(byte[] nv21_data, int width, int height){int i;int left, right;byte temp;int startPos = 0;// mirror Yfor (i = 0; i < height; i++){left  = startPos;right = startPos + width - 1;while (left < right) {temp = nv21_data[left];nv21_data[left]  = nv21_data[right];nv21_data[right] = temp;left ++;right--;}startPos += width;}// mirror U and Vint offset = width * height;startPos = 0;for (i = 0; i < height / 2; i++){left  = offset + startPos;right = offset + startPos + width - 2;while (left < right){temp = nv21_data[left ];nv21_data[left ] = nv21_data[right];nv21_data[right] = temp;left ++;right--;temp = nv21_data[left ];nv21_data[left ] = nv21_data[right];nv21_data[right] = temp;left ++;right--;}startPos += width;}return nv21_data;}

  显然我们可以看出,当你只用对图像进行镜像操作时,你肯定不应该选择逐像素遍历法,而要使用中心翻转法,否则会让你的执行效率直接降低一半多。

NV21转RGB/RGBA数据

逐像素遍历法

  NV21图像转RGB图像,其实就是需要将每一个图像的像素点的RGB值通过NV21数组中的数据计算出来,然后排列成数组。所以算法要做的是遍历图像每一个像素点,找到像素点对应的n,u,v的值,根据rgb和nuv的转换公式计算出该像素点的rgb的值,然后赋给数组就行。具体图像yuv空间和Rgb空间的转换原理和公式可以看一下YUV 格式与 RGB 格式的相互转换公式及C++ 代码或者其它的文章。我将讲解简单写在代码注释里。

 public void NV212BGRA(byte[]input , int width , int height , byte[]output, boolean isRGB){int nvOff = width * height ;int  i, j, yIndex = 0;int y, u, v;int r, g, b, nvIndex = 0;//遍历图像每一个像素点,依次计算r,g,b的值。for(i = 0; i < height; i++){for(j = 0; j < width; j ++,++yIndex){//对于位置为i,j的像素,找到对应的y,u,v的值nvIndex = (i / 2)  * width + j - j % 2;y = input[yIndex] & 0xff;u = input[nvOff + nvIndex ] & 0xff;v = input[nvOff + nvIndex + 1] & 0xff;//对于位置为i,j的像素,根据其yuv的值计算出r,g,b的值r = y + ((351 * (v-128))>>8);  //rg = y - ((179 * (v-128) + 86 * (u-128))>>8); //gb = y + ((443 * (u-128))>>8); //b//要求的rgb值是byte类型,范围是0--255,消除越界的值。r = ((r>255) ?255 :(r<0)?0:r);g = ((g>255) ?255 :(g<0)?0:g);b = ((b>255) ?255 :(b<0)?0:b);// 对结果rgb/bgr的值赋值,a通道表示透明度,这里都给255(根据你自己的场景设置)//由于rgba格式和bgra格式只是r,g,b的顺序不同,因此这里通过bool值判断,既可以得到rgba的格式也可以得到bgra格式的数组。if(isRGB){output[yIndex*4 + 0] = (byte) b;output[yIndex*4 + 1] = (byte) g;output[yIndex*4 + 2] = (byte) r;output[yIndex*4 + 3] = (byte) 255;}else{output[yIndex*4 + 0] = (byte) r;output[yIndex*4 + 1] = (byte) g;output[yIndex*4 + 2] = (byte) b;output[yIndex*4 + 3] = (byte) 255;}}}}

NV21组合操作(旋转,镜像,转RGB/RGBA)

  重点其实在于组合操作了。当你的图像需要将上述三个步骤结合起来的时候,会怎样呢?举个例子,你需要NV21的数据顺时针旋转90°然后转成RGB,你会怎么做呢?先旋转再转RGB?这样就有点愚笨了。其实你注意到了吗,旋转,镜像,转rgb都是可以逐像素去做的。这三种操作的组合,也是可以逐像素做的。不管是怎样的组合,你只要对像素所有点进行一次遍历即可。重点是找到变换前后像素的映射关系。下面讲两个操作,NV21数组顺时针转90度+转换RGBA数据和NV21数组顺时针转270度+镜像+转换成RGBA数据。这两个操作,一次遍历就可以了,不要分两步实现,并且代码几乎完全一样。

NV21数组顺时针转90度+转换RGBA数据

public void NV21rRotate90Degree2BGRA(byte[]input , int width , int height , byte[]output, boolean isRGB){int nvOff = width * height ;int  i, j, yIndex = 0;int y, u, v;int r, g, b, nvIndex = 0;for(i = 0; i < height; i++){for(j = 0; j < width; j ++,++yIndex){nvIndex = (i / 2)  * width + j - j % 2;y = input[yIndex] & 0xff;u = input[nvOff + nvIndex ] & 0xff;v = input[nvOff + nvIndex + 1] & 0xff;// yuv to rgbr = y + ((351 * (v-128))>>8);  //rg = y - ((179 * (v-128) + 86 * (u-128))>>8); //gb = y + ((443 * (u-128))>>8); //br = ((r>255) ?255 :(r<0)?0:r);g = ((g>255) ?255 :(g<0)?0:g);b = ((b>255) ?255 :(b<0)?0:b);int index = j*height+(height - i)-1;if(isRGB){output[index*4 + 0] = (byte) b;output[index*4 + 1] = (byte) g;output[index*4 + 2] = (byte) r;output[index*4 + 3] = (byte) 255;}else{output[index*4 + 0] = (byte) r;output[index*4 + 1] = (byte) g;output[index*4 + 2] = (byte) b;output[index*4 + 3] = (byte) 255;}}}}

NV21数组顺时针转270度+镜像+转换RGBA数据

public void NV21Rotate270Degree2BGRAandMirror(byte[]input , int width , int height , byte[]output, boolean isRGB){int nvOff = width * height ;int  i, j, yIndex = 0;int y, u, v;int r, g, b, nvIndex = 0;for(i = 0; i < height; i++){for(j = 0; j < width; j ++,++yIndex){nvIndex = (i / 2)  * width + j - j % 2;y = input[yIndex] & 0xff;u = input[nvOff + nvIndex ] & 0xff;v = input[nvOff + nvIndex + 1] & 0xff;// yuv to rgbr = y + ((351 * (v-128))>>8);  //rg = y - ((179 * (v-128) + 86 * (u-128))>>8); //gb = y + ((443 * (u-128))>>8); //br = ((r>255) ?255 :(r<0)?0:r);g = ((g>255) ?255 :(g<0)?0:g);b = ((b>255) ?255 :(b<0)?0:b);int index = (width-j-1)*height + (height-i-1);if(isRGB){output[index*4 + 0] = (byte) b;output[index*4 + 1] = (byte) g;output[index*4 + 2] = (byte) r;output[index*4 + 3] = (byte) 255;}else{output[index*4 + 0] = (byte) r;output[index*4 + 1] = (byte) g;output[index*4 + 2] = (byte) b;output[index*4 + 3] = (byte) 255;}}}}

  什么?你说NV21数组顺时针转90度+转换RGBA数据NV21数组顺时针转270度+镜像+转换RGBA数据NV21转RGB/RGBA数据三个算法是一样的?哈哈,的确很像,但仔细看不同的地方在于index的值不同。Index的值这里的含义就是找到变换后的像素点和变换前的像素点的映射关系。如果你对一张图象先转90度再转RGBA需要耗时13.5ms;如果你直接用上面的代码需要耗时11ms。当然你的组合操作越多,这样一步到位省时就越多。

文章总结

  本文最重要的核心总结一句话:
  1、如果你只要对NV21数据进行镜像的话,使用中心旋转法而不要用逐像素处理法,这样你的处理效率会提高一倍以上。
  2、如果你要对图像进行多个操作的话,不要按照你的操作步骤一步一步转换,而是直接找到图像操作前和操作后的像素映射关系,采用逐像素法一步到位。即你要对数组旋转+镜像+转RGBA,不要一次运行我上面的三个代码,直接重写一个一步到位的代码,这样效率会极大提升。

代码获取

  图像的操作的组合方式很多,我不可能穷尽,更重要的是思想和思路。如果下面的代码没有你需要的操作,那么你需要自己去重新实现,当然,根据我上面讲的,代码是非常简单的,你几乎只用改一行代码。

NV21数据向右旋转90°

 private byte[] rotateYUVDegree90(byte[] data, int imageWidth, int imageHeight) {byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];// Rotate the Y lumaint i = 0;for (int x = 0; x < imageWidth; x++) {for (int y = imageHeight - 1; y >= 0; y--) {yuv[i] = data[y * imageWidth + x];i++;}}// Rotate the U and V color componentsfor (int x = 0; x < imageWidth; x = x + 2) {for (int y = imageHeight / 2 - 1; y >=0; y--) {yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];i++;yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + (x + 1)];i++;}}return yuv;}

NV21数据向右旋转270°

  恭喜你,你需要自己去实现~~~~~~~~~~~~~~~

NV21数据镜像

private byte[] NV21_mirror_center(byte[] nv21_data, int width, int height){int i;int left, right;byte temp;int startPos = 0;// mirror Yfor (i = 0; i < height; i++){left  = startPos;right = startPos + width - 1;while (left < right) {temp = nv21_data[left];nv21_data[left]  = nv21_data[right];nv21_data[right] = temp;left ++;right--;}startPos += width;}// mirror U and Vint offset = width * height;startPos = 0;for (i = 0; i < height / 2; i++){left  = offset + startPos;right = offset + startPos + width - 2;while (left < right){temp = nv21_data[left ];nv21_data[left ] = nv21_data[right];nv21_data[right] = temp;left ++;right--;temp = nv21_data[left ];nv21_data[left ] = nv21_data[right];nv21_data[right] = temp;left ++;right--;}startPos += width;}return nv21_data;}

NV21数据转RGBA数据

  public void NV212BGRA(byte[]input , int width , int height , byte[]output, boolean isRGB){int nvOff = width * height ;int  i, j, yIndex = 0;int y, u, v;int r, g, b, nvIndex = 0;//遍历图像每一个像素点,依次计算r,g,b的值。for(i = 0; i < height; i++){for(j = 0; j < width; j ++,++yIndex){//对于位置为i,j的像素,找到对应的y,u,v的值nvIndex = (i / 2)  * width + j - j % 2;y = input[yIndex] & 0xff;u = input[nvOff + nvIndex ] & 0xff;v = input[nvOff + nvIndex + 1] & 0xff;//对于位置为i,j的像素,根据其yuv的值计算出r,g,b的值r = y + ((351 * (v-128))>>8);  //rg = y - ((179 * (v-128) + 86 * (u-128))>>8); //gb = y + ((443 * (u-128))>>8); //b//要求的rgb值是byte类型,范围是0--255,消除越界的值。r = ((r>255) ?255 :(r<0)?0:r);g = ((g>255) ?255 :(g<0)?0:g);b = ((b>255) ?255 :(b<0)?0:b);// 对结果rgb/bgr的值赋值,a通道表示透明度,这里都给255(根据你自己的场景设置)//由于rgba格式和bgra格式只是r,g,b的顺序不同,因此这里通过bool值判断,既可以得到rgba的格式也可以得到bgra格式的数组。if(isRGB){output[yIndex*4 + 0] = (byte) b;output[yIndex*4 + 1] = (byte) g;output[yIndex*4 + 2] = (byte) r;output[yIndex*4 + 3] = (byte) 255;}else{output[yIndex*4 + 0] = (byte) r;output[yIndex*4 + 1] = (byte) g;output[yIndex*4 + 2] = (byte) b;output[yIndex*4 + 3] = (byte) 255;}}}}

NV21向右旋转90+镜像

  恭喜你,你需要自己去实现~~~~~~~~~~~~

NV21向右旋转270+镜像

  恭喜你,你需要自己去实现~~~~~~~~~~~~

NV21向右旋转90+转RGBA

public void NV21rRotate90Degree2BGRA(byte[]input , int width , int height , byte[]output, boolean isRGB){int nvOff = width * height ;int  i, j, yIndex = 0;int y, u, v;int r, g, b, nvIndex = 0;for(i = 0; i < height; i++){for(j = 0; j < width; j ++,++yIndex){nvIndex = (i / 2)  * width + j - j % 2;y = input[yIndex] & 0xff;u = input[nvOff + nvIndex ] & 0xff;v = input[nvOff + nvIndex + 1] & 0xff;// yuv to rgbr = y + ((351 * (v-128))>>8);  //rg = y - ((179 * (v-128) + 86 * (u-128))>>8); //gb = y + ((443 * (u-128))>>8); //br = ((r>255) ?255 :(r<0)?0:r);g = ((g>255) ?255 :(g<0)?0:g);b = ((b>255) ?255 :(b<0)?0:b);int index = j*height+(height - i)-1;if(isRGB){output[index*4 + 0] = (byte) b;output[index*4 + 1] = (byte) g;output[index*4 + 2] = (byte) r;output[index*4 + 3] = (byte) 255;}else{output[index*4 + 0] = (byte) r;output[index*4 + 1] = (byte) g;output[index*4 + 2] = (byte) b;output[index*4 + 3] = (byte) 255;}}}}

NV21数组顺时针转270度+镜像+转换RGBA数据

public void NV21Rotate270Degree2BGRAandMirror(byte[]input , int width , int height , byte[]output, boolean isRGB){int nvOff = width * height ;int  i, j, yIndex = 0;int y, u, v;int r, g, b, nvIndex = 0;for(i = 0; i < height; i++){for(j = 0; j < width; j ++,++yIndex){nvIndex = (i / 2)  * width + j - j % 2;y = input[yIndex] & 0xff;u = input[nvOff + nvIndex ] & 0xff;v = input[nvOff + nvIndex + 1] & 0xff;// yuv to rgbr = y + ((351 * (v-128))>>8);  //rg = y - ((179 * (v-128) + 86 * (u-128))>>8); //gb = y + ((443 * (u-128))>>8); //br = ((r>255) ?255 :(r<0)?0:r);g = ((g>255) ?255 :(g<0)?0:g);b = ((b>255) ?255 :(b<0)?0:b);int index = (width-j-1)*height + (height-i-1);if(isRGB){output[index*4 + 0] = (byte) b;output[index*4 + 1] = (byte) g;output[index*4 + 2] = (byte) r;output[index*4 + 3] = (byte) 255;}else{output[index*4 + 0] = (byte) r;output[index*4 + 1] = (byte) g;output[index*4 + 2] = (byte) b;output[index*4 + 3] = (byte) 255;}}}}

【Android Camera2】玩转图像数据 -- NV21图像旋转,镜像,转rgba代码分析,性能优化相关推荐

  1. 【Android RTMP】x264 图像数据编码 ( Camera 图像数据采集 | NV21 图像数据传到 Native 处理 | JNI 传输字节数组 | 局部引用变量处理 | 线程互斥 )

    文章目录 安卓直播推流专栏博客总结 一. NV21 数据传入 Native 层 二. jbyte * 数据类型 ( Java 中的 byte[] 数组传入 JNI 处理方式 ) 三. 局部引用处理 四 ...

  2. 【高通SDM660平台 Android 10.0】(10) --- Camera Sensor lib 与 Kernel Camera Probe 代码分析

    [高通SDM660平台 Android 10.0]Camera Sensor lib 与 Kernel Camera Probe 代码分析 一.libmmcamera_imx258.so 代码分析 1 ...

  3. 基于C++的ITK图像分割与配准学习笔记1(图像数据表达-图像)

    ITK学习参考资料整理汇总(包含 ItkSoftwareGuide.PDF英文版.ItkSoftwareGuide-2.4.0-中文版.医学图像分割与配准(1ITK初步分册) (1)PDF. 医学图像 ...

  4. 图像数据增强方法一览(附python代码)

    在图像分类任务中,图像数据增强一般是大多数人会采用的方法之一,这是由于深度学习对数据集的大小有一定的要求,若原始的数据集比较小,无法很好地满足网络模型的训练,从而影响模型的性能,而图像增强是对原始图像 ...

  5. 基于OpenCV做图像数据增强(平移、镜像、缩放、旋转、仿射)

    前言: 基于OpenCV的基本使用,对图像的数据量进行数据增强,使得框架对神经网络进行训练,提高模型的鲁棒性以及准确性. 原图: 1.平移 平移通过自定义平移矩阵以及函数warpAffine实现: 代 ...

  6. android 蓝牙耗电量,安卓Android BLE低功耗蓝牙接受数据详解 只需100行代码轻松搞定...

    做了一个安卓手机通过蓝牙获取电子秤的重量的Demo,在此写下以供大家参考和讨论. 先上代码,着急用的可以迅速参考,后面再写说明 我跳过了扫描过程,直接根据蓝牙设备地址进行连接,可以运行官方Demo来获 ...

  7. 面对 10 亿数据量的挑战,如何对系统进行性能优化?

    作者 | 中华石杉 责编 | 伍杏玲 本文经授权转载自石杉的架构笔记 这篇文章,我们来聊一聊在十亿级的大数据量技术挑战下,世界上最优秀的大数据系统之一的Hadoop是如何将系统性能提升数十倍的? 首先 ...

  8. mysql 批量数据插入很慢(kettle 输入输出组件) 性能优化办法

    背景 最近在做数仓重构项目,遇到一些性能瓶颈,这里记录一下解决办法. 随着业务数据每天都在增加,几年前开发的etl任务开始跑不动了.大表一般是通过增量的方式插入,但是修复bug 或者每月/季度跑一次的 ...

  9. 1万条数据大概占多大空间_Java互联网架构-性能优化Mysql索引数据结构详解

    欢迎关注头条号:java小马哥 周一至周日下午三点半!精品技术文章准时送上!!! 精品学习资料获取通道,参见文末 一,索引数据结构红黑树,Hash,B+树详解 索引是帮助MySQL高效获取数据的排好序 ...

最新文章

  1. 开源Math.NET基础数学类库使用(11)C#计算相关系数
  2. linux下json数据解析,Linux下使用jq简单解析json的方法
  3. SIFT算法中概念简单解释
  4. 对设计领域中Tile和Card的理解
  5. 【linux】常用网络操作
  6. 计算机libeay32.dll丢失怎么办,电脑libeay32.dll丢失的解决方法
  7. Linux QQ 2.0 Beta版初体验
  8. 两种方法解除网页复制限制
  9. 老实人spring源码解析目录
  10. 在 LaTeX 中插入表格
  11. 从网易产品出发解读To B营销如何应用增长黑客
  12. 当docker pull mysql时,一直Waiting,很多等待,报:error pulling image configuration
  13. redis的incr+expire的坑
  14. python 实现获取与下载网页中图片的四种方案
  15. 振弦式应变计埋设与安装
  16. Smss.exe进程分析
  17. 蓝牙Bluetooth模块介绍
  18. Python 之父入职微软要搞事?!给大家推几本书压压惊
  19. 云通讯服务商有哪些?
  20. 软件缺陷主要包含哪些要素?

热门文章

  1. 语音特征:spectrogram、Fbank(fiterbank)、MFCC
  2. CS224n研究热点8 谷歌的多语种神经网络翻译系统
  3. 刚性PCB和柔性PCB的区别
  4. 【前端大屏可视化项目适配方案】
  5. 轨物范世:华为手机的影像哲学
  6. 督办管理系统——让企业工作落实到位
  7. linux运行getch吗,在linux中使用getch()函数
  8. 樊胜美有可能跑到与安迪一样的终点…
  9. 【FreeRTOS】FreeRTOS学习笔记(3)— FreeRTOS任务与协程
  10. 团队作业1 团队展示选题