如何使用Kinect for windows SDK中的NUI(彩色图像获取)

前言

微软于2011年6月16日推出的windows平台体感设备kinect的开发包beta版。尽管还有很多不足,许多功能都不完整,但是已经让我们这些期盼了半年多的程序员们兴奋不已了。

我也是初次接触这个SDK,以前一直使用OpenNI。在使用过程中发现一些问题,整体构架什么的,SDK远不如OpenNI完善,甚至于SDK中的例子,也像是匆忙赶制出来的 -_-#

好吧,我承认,即使这样,我也依然要使用这个SDK……

为了让大家的NI没白学,也让新人能快速入门,特意写下一些心得,算是入门级的教程了。

何人适合阅读此教程

只要你C/C++语法没问题,WINDOWS平台下的编程能通过阅读代码理解,就可以了。

但是为了便于提高输出的FPS,我使用了OpenGL而不是例子中自带的D3D,所以你最好有点OpenGL基础(认真研究过NI的童鞋们笑而不语)。不过不会也没关系,我尽量把它们分开讲解。

此教程包含什么

kinect设备包括一个彩色摄像头,一个红外发射摄像头及一个红外接收摄像头,另外,还包括一组由4个高性能的降噪麦克阵列组成的语音设备(可是TYYD为什么不提供降噪API)。

SDK中包含了以上所有设备的访问功能,尤其是降噪麦克的读入加上speech库积累多年的识别训练,它终于不再是摆设了(恐怕所有从其他第三方驱动转型到SDK中的童鞋们,都是冲着这个来的 ^_^)。

此教程不会完整到包含全部使用的程度,看标题就知道了,目的仅仅是让大家能通过此教程,快速学会如何使用SDK获取/使用Kinect设备中的彩色图像数据、深度图像数据、用户数据、骨骼数据。这部分都是用来实现自然用户界面(NUI)的功能的,也就是我们常说的“体感”。因此这些内容统称为NUI。

至于其他诸如audio及语音识别等部分,考虑以后再说吧(如果2012过完我还活着的话 -_-#)

好了,废话讲完,让我们开始吧,首先从读取/显示彩色图像开始 ^_^

一.基本设置

1.1 在vs2010项目中,需要设置C++目录

包含目录中加入 $(KINECTSDK10_DIR)\inc;

库目录中加入    $(KINECTSDK10_DIR)\lib\x86

(注意安装SDK1.7以后,路径不一样了,环境变量名变成KINECTSDK10_DIR)

MSRKINECTSDK是环境变量,正确安装MS KINECT FRO WINDOWS SDK 后,会在计算机中的环境变量中看到。

1.2 添加特定库

除了指定目录外,你还需要在链接器中设置附加依赖项,填入KinectNUI.lib

1.3 头文件

为了使用NUI中的API,首先我们要包含 NuiApi.h

#include "NuiApi.h" 以前是"NuiApi.h

切记,在这之前,要保证你已经包含了windows.h

#include <Windows.h>

#include "NuiApi.h"

否则 Nuiapi中很多根据windos平台定义的数据类型及宏都不生效。

二.初始化NUI

接下来,任何想使用微软提供的API来操作KINECT,都必须在所有操作之前,调用NUI的初始化函数

HRESULT NuiInitialize(DWORD dwFlags);

dwFlags参数是以标志位的含义存在的。你可以使用下面几个值来指定你打算使用NUI中的哪些内容。

NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX     使用NUI中的带用户信息的深度图数据

NUI_INITIALIZE_FLAG_USES_COLOR                                       使用NUI中的彩色图数据

NUI_INITIALIZE_FLAG_USES_SKELETON                                 使用NUI中的骨骼追踪数据

NUI_INITIALIZE_FLAG_USES_DEPTH                                      仅仅使用深度图数据(如果你自己有良好的场景分析或物体识别算法,那么你应该用这个)

以上4个标志位,你可以使用一个,也可以用 | 操作符将它们组合在一起。例如:

//只使用彩色图

HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR);

//使用带用户信息的深度图/使用用户骨骼框架/使用彩色图

HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX | NUI_INITIALIZE_FLAG_USES_SKELETON | NUI_INITIALIZE_FLAG_USES_COLOR);

一个应用程序对一个KINECT设备,必须要调用此函数一次,并且也只能调用一次。如果在这之后又调用一次初始化,势必会引起逻辑错误(即使是2个不同程序)。

比如你运行一个SDK的例子,在没关闭它的前提下,再运行一个,那么后运行的就无法初始化成功,但不会影响之前的程序继续运行。

如果你的程序想使用多台KINECT,那么请使用INuiInstance接口来初始化你的设备。(至今为止我还没有测试过多台,留到以后再说吧 -_-#)

另外,作为一名KINECT程序员,你需要记得的是,微软SDK中提供的运行环境在处理KINECT传输数据时,是遵循一条3步骤的运行管线的。

第一阶段只处理彩色和深度数据

第二阶段处理用户索引并根据用户索引将颜色信息追加到深度图中。(这回你明白为什么NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX这个标志位起这么长的名字了吧 -_-#)

第三阶段处理骨骼追踪数据

NuiInitialize就是应用程序用通过传递给dwFlags参数具体值,来初始化这个管线中必须的阶段。因此,我们总是先在标志位中指定图像类型,才可以在接下来的环节中去调用NuiImageStreamOpen之类的函数。如果你初始化的时候没指定NUI_INITIALIZE_FLAG_USES_COLOR,那你以后就别指望NuiImageStreamOpen能打开彩色数据了,它肯定会调用失败,因为没初始化嘛。(我自己都觉得这些东西说的太罗嗦了,不知道看的人懂没懂 -_-#)



好了,现在我们初始化一下NUI,本程序只读取彩色图,那么标志位如何设置?你懂的……

[cpp] view plaincopyprint?

  1. HRESULThr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR);
  2. //这是一种处理返回值的方式
  3. if( FAILED( hr ) )
  4. {
  5. cout<<"NuiInitialize failed"<<endl;
  6. returnhr;
  7. }
  8. //这是另一种处理返回值的方式
  9. if(hr == S_OK)
  10. {
  11. cout<<"NuiInitialize successfully"<<endl;
  12. }



我特意准备了2种对NuiInitialize返回值进行处理的代码。只是想通过这个例子说明,我推荐使用后者。

NuiInitialize返回值必须是S_OK才可以让你的程序继续下去,你也只应该对返回值判断是否==或者!= S_OK.

三.释放NUI

OK,初始化以后,在我们继续其他深入获取NUI设备的数据之前,先了解一下如何关闭你的程序与NUI之间的联系。

VOID NuiShutdown();

关于这个函数,没什么可说的,你的程序退出时,都应该调用一下。甚至于,你的程序暂时不使用KINECT了,就放开对设备的控制权,好让其他程序可以访问KINECT。

放开后再访问呢??自己想 -_-#

友情提示使用OpenGL的程序员们,如果你们是在使用glut库,那么不要在glMainLoop()后面调用NuiShutdown(),因为它不会执行,你应该在窗口关闭以及任意你执行了退出代码的时刻调用它。

四.打开对NUI设备的访问通道

HRESULT NuiImageStreamOpen(NUI_IMAGE_TYPE eImageType,NUI_IMAGE_RESOLUTION eResolution,DWORD dwImageFrameFlags_NotUsed,DWORD dwFrameLimit,HANDLE hNextFrameEvent,HANDLE *phStreamHandle);
我们使用这个函数来打开kinect彩色或者深度图的访问通道,当然,其内部原理是通过"流"来实现的,因此,你也可以把这个函数理解为,创建一个访问彩色或者深度图的数据流.

似乎从很久远的时候开始,微软就在windows中开始使用流来访问所有硬件设备了,隐约记得那次更新,但忘记具体的原因和细节了,算了追究也没用 -_-#



参数:

eImageType

[in] 这是一个 NUI_IMAGE_TYPE 枚举类型的值,用来详细指定你要创建的流类型。

比如你要打开彩色图,就使用 NUI_IMAGE_TYPE_COLOR。

要打开深度图,就使用 NUI_IMAGE_TYPE_DEPTH。

具体这个枚举有多少个成员,我建议你们仔细阅读API手册。

但是有一点是需要注意的,还记的初始化函数么?对,刚才就提醒过你们,如果你现在要打开的是深度图,但是初始化的时候却没有指定深度图的标志位,那么……你就废了……

不是你的程序废了,是你废了,我都这么强调了你还那么干,谁也救不了你了。

记住!!!你能打开的图像类型,必须是你在初始化的时候指定过的。



eResolution

[in] 这是一个 NUI_IMAGE_RESOLUTION 枚举类型的值,用来指定你要以什么分辨率来打开eImageType(参数1)中指定的图像类别。

假如你在参数eImageType中指定的是彩色图NUI_IMAGE_TYPE_COLOR,那么你可以选择2种分辨率

NUI_IMAGE_RESOLUTION_1280x1024,NUI_IMAGE_RESOLUTION_640x480

如果你在参数eImageType中指定的是深度图NUI_IMAGE_TYPE_DEPTH,那么你可以选择3种分辨率

NUI_IMAGE_RESOLUTION_640x480, NUI_IMAGE_RESOLUTION_320x240, NUI_IMAGE_RESOLUTION_80x60

API手册里,详细描述了这个对照表,各种图像类型都支持什么分辨率,你们应该仔细查看,相信看过一遍之后就会记住的 ^_^



dwImageFrameFlags_NotUsed

[in] 你看参数名就知道了,这是个废物参数,一点用没有,你随便给个整数就行了。至于以后的版本里它会不会有意义,我懒得去猜 -_-#



dwFrameLimit

指定NUI运行时环境将要为你所打开的图像类型建立几个缓冲。最大值是NUI_IMAGE_STREAM_FRAME_LIMIT_MAXIMUM(当前版本为 4)

对于大多数啊程序来说,2就足够了。(关于这个值设置的高低,还有另外的用意,但是我打算以后再讨论 ^_^)



hNextFrameEvent 

[in, optional] 一个用来手动重置信号是否可用的事件句柄(event),该信号用来控制KINECT是否可以开始读取下一帧数据。也就是说在这里指定一个句柄后,随着程序往后继续推进,当你在任何时候想要控制kinect读取下一帧数据时,都应该先使用WaitForSingleObject判断一下该句柄。




phStreamHandle

[out] 出参,指定一个句柄的地址。函数成功执行后,将会创建对应的数据访问通道(流),并且让该句柄保存这个通道的地址。也就是说,如果现在创建成功了。那么以后你想读取数据,就要通过这个句柄了。(现在看不懂没关系,下面的代码会比这么描述清晰的多 ^_^)





返回值

只有S_OK表示成功打开,错误原因却有很多,比如打开一个没初始化过的数据流;打开一个已被使用的数据流;参数phStreamHandle为NULL等等。自己查阅API手册吧。



好了,让我们实战一小下吧~

[cpp] view plaincopyprint?

  1. //初始化NUI
  2. HRESULThr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR);//指定要访问彩色图信息
  3. if( hr != S_OK )
  4. {
  5. cout<<"NuiInitialize failed"<<endl;
  6. returnhr;
  7. }
  8. HANDLEh1 = CreateEvent( NULL, TRUE, FALSE, NULL );//创建读取下一帧的信号事件句柄
  9. HANDLEh2 = NULL;//用来保存彩色图通道(流)句柄
  10. //打开彩色图数据流,并用h2保存该流的句柄,以便于以后读取
  11. hr = NuiImageStreamOpen(NUI_IMAGE_TYPE_COLOR,NUI_IMAGE_RESOLUTION_640x480,0,2,h1,&h2);
  12. if( hr != S_OK )
  13. {
  14. switch(hr)
  15. {
  16. caseE_POINTER:
  17. cout<<"The value of phStreamHandle is NULL,please check it"<<endl;
  18. break;
  19. caseE_INVALIDARG:
  20. cout<<"The value of dwFrameLimit is outside the range from 1- NUI_IMAGE_STREAM_FRAME_LIMIT_MAXIMUM"<<endl;
  21. break;
  22. //…………
  23. }
  24. cout<<"Could not open image stream video"<<endl;
  25. returnhr;
  26. }




五.读取彩色图数据

HRESULT NuiImageStreamGetNextFrame(HANDLE hStream,DWORD dwMillisecondsToWait,CONST NUI_IMAGE_FRAME **ppcImageFrame);

参数:

hStream

[in] 还记得我们前面打开数据流的时候,将流句柄保存到哪了么?这里要的就是流句柄。



dwMillisecondsToWait

[in] 延迟时间,以微秒为单位的整数。当运行环境在读取之前,会先等待这个时间。



ppcImageFrame

[out] 出参,指定一个 NUI_IMAGE_FRAME 结构的指针,当读取成功后,该函数会将读取到的数据地址返回,保存在此参数中。



返回值

同样是S_OK表示成功



好了,让我们读取一帧吧

[cpp] view plaincopyprint?

  1. constNUI_IMAGE_FRAME * pImageFrame = NULL;
  2. hr = NuiImageStreamGetNextFrame(h2,0,&pImageFrame );
  3. if( hr != S_OK )
  4. {
  5. cout<<"Get Image Frame Failed"<<endl;
  6. returnhr;
  7. }





如果你没有遇到什么错误的话,那么刚才KINECT就捕获了一副画面,并将该画面的信息保存在一个NUI_IMAGE_FRAME结构中,pImageFrame指向该结构的地址。

pImageFrame包含了很多有用信息,包括:图像类型,分辨率,图像缓冲区,时间戳等等。相关信息翻阅API手册

其中最有用的就是成员

NuiImageBuffer *pFrameTexture;

那么我们就先保存一下这个成员吧

NuiImageBuffer * pTexture = pImageFrame->pFrameTexture; //这是接着前面的代码继续来的



应用程序必须调用NuiImageBuffer::L ockRect方法,来获取当前帧中,跟图形有关的缓冲(还记得前面说过,你可以指定1-4个缓冲区么),继续我们的代码

KINECT_LOCKED_RECT LockedRect;

pTexture->LockRect( 0, &LockedRect, NULL, 0 );

好了,现在真正保存图像的对象LockedRect我们已经有了,并且也将图像信息写入这个对象了。

LockedRect的数据类型是KINECT_LOCKED_RECT结构类型,该结构只包含2个成员

INT    Pitch;

void * pBits;

其中pBits就是用来存储所有像素点的数组地址。而pitch指明了图像中一行数据的大小(字节)

我们之前指定的分辨率是640x480,也就是说,这307200个像素点,全都被保存在一个很大的数组中,每个像素点的颜色信息都是以32位RGB形式存储的,所以,你可以理解,这个数组一共占用1228800个字节。而数组的起始地址,就是LockedRect->pBits;

你可以用pTexture的成员BufferLen来验证数组大小的有效性。(你们试试pitch又指明什么呢?)

cout<<"当前帧图像占用内存"<<pTexture->BufferLen<<"字节"<<endl;



当然,这没什么实际意义,还是抓紧读取我们的彩色图像素信息吧。



BYTE * pBuffer = (BYTE*) LockedRect.pBits;

//显示x200y400位置上的像素信息

pBuffer += (200+399*640)*4;

printf("x:200 y:400坐标处的像素颜色:r:%d g:%d b:%d\n",pBuffer[2],pBuffer[1],pBuffer[0]);



聪明的童鞋们,200+399*640代表什么含义,你懂的,乘以4又代表什么含义,你也懂的……


终于讲完了,单个点都会读取了,那么从头遍历到结尾,只需要一个嵌套循环而已

[cpp] view plaincopyprint?

  1. BYTE* pBuffer = (BYTE*) LockedRect.pBits;
  2. for(inty = 0; y < 480; ++y)
  3. {
  4. constBYTE* pImage = pBuffer;
  5. for(intx = 0; x < 640; ++x)
  6. {
  7. //第y行第x列像素的信息
  8. //pImage[3]   A
  9. //pImage[2]   R
  10. //pImage[1]   G
  11. //pImage[0]   B
  12. //怎么用就看童鞋们自己了,是设置OpenGL的纹理还是直接Paint到窗体DC上我就不管了,你们随便
  13. //但是友情提醒一下,如果你直接Paint到DC上,还是考虑用离屏界面吧 memory DC
  14. pImage+=4;//每读取完一个像素,向后移动到下一个像素点。
  15. }
  16. pBuffer += 640 * 4; //640x4是什么意思?为了尽可能简化代码,我没有定义宏或常量,但是你们懂的……
  17. }




最后,上完整代码,考虑到E文问题,所以我把所有注释换成中文了

[cpp] view plaincopyprint?

    [cpp] view plaincopyprint?

    1. // kongzhitai.cpp : 定义控制台应用程序的入口点。
    2. //
    3. //#include "stdafx.h"
    4. #include "opencv2/highgui/highgui.hpp"
    5. #include "opencv2/imgproc/imgproc.hpp"
    6. #include <iostream>
    7. usingnamespacestd;
    8. usingnamespacecv;
    9. //KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
    10. #include "windows.h"
    11. #include "NuiApi.h"
    12. //KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
    13. intmain(intargc,char* argv[])
    14. {
    15. //初始化NUI
    16. HRESULThr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR);
    17. if( hr != S_OK )
    18. {
    19. cout<<"NuiInitializefailed"<<endl;
    20. returnhr;
    21. }
    22. //打开KINECT设备的彩色图信息通道
    23. HANDLEh1 = CreateEvent( NULL,TRUE, FALSE, NULL );
    24. HANDLEh2 = NULL;
    25. hr = NuiImageStreamOpen(NUI_IMAGE_TYPE_COLOR,NUI_IMAGE_RESOLUTION_640x480,0,4,h1,&h2);
    26. if( FAILED( hr ) )
    27. {
    28. cout<<"Could not open image stream video"<<endl;
    29. returnhr;
    30. }
    31. //开始读取彩色图数据;
    32. Mat img;
    33. img.create(480,640,CV_8UC3);
    34. uchar *pData;
    35. while(1)
    36. {
    37. WaitForSingleObject(h1,INFINITE);
    38. constNUI_IMAGE_FRAME *pImageFrame = NULL;
    39. hr = NuiImageStreamGetNextFrame(h2, 0, &pImageFrame );
    40. if( FAILED( hr ) )
    41. {
    42. cout<<"GetImage Frame Failed"<<endl;
    43. continue;
    44. }
    45. INuiFrameTexture * pTexture =pImageFrame->pFrameTexture;
    46. NUI_LOCKED_RECT LockedRect;
    47. pTexture->LockRect( 0,&LockedRect, NULL, 0 );
    48. if( LockedRect.Pitch != 0 )
    49. {
    50. BYTE* pBuffer = (BYTE*)LockedRect.pBits;
    51. //赋值文艺青年方式;
    52. for(inti=0;i<480;i++)
    53. {
    54. pData=img.ptr<uchar>(i);
    55. for(intj=0;j<640;j++)
    56. {
    57. for(intk=0;k<3;k++)
    58. *(pData+j*3+k)=*(pBuffer+i*4*640+j*4+k);
    59. }
    60. }
    61. imshow("rgb",img);
    62. waitKey(36);
    63. imwrite("F:\\out.jpg",img);
    64. }
    65. if( LockedRect.Pitch != 0 )
    66. {
    67. BYTE* pBuffer = (BYTE*)LockedRect.pBits;
    68. //显示x200y400位置上的像素信息
    69. pBuffer +=(200+399*640)*4;
    70. printf("x:200 y:400坐标处的像素颜色:r:%d g:%d b:%d\n",pBuffer[2],pBuffer[1],pBuffer[0]);
    71. }
    72. else
    73. {
    74. cout<<"Bufferlength of received texture is bogus\r\n"<<endl;
    75. }
    76. //释放本帧数据,准备迎接下一帧
    77. NuiImageStreamReleaseFrame(h2, pImageFrame );
    78. }
    79. //关闭NUI链接
    80. NuiShutdown();
    81. return0;
    82. }

    微软Kinect for windows SDK 使用教程 (NUI部分)相关推荐

    1. Kinect for Windows SDK开发入门

      Kinect for Windows SDK开发入门(一):开发环境配置 首先来看一下Kinect设备: 1. Kinect设备 黑色的Kinect设备如下图:基座和感应器之间有一个电动的马达,通过程 ...

    2. [译]Kinect for Windows SDK开发入门(八):骨骼追踪进阶 上

      前7篇文件我们介绍了Kinect SDK中各种传感器的各种基本知识,我们用实验的方式演示了这些基本对象和方法的如何使用,这些都是Kinect开发最基本的知识.了解了这些基本知识后,就可以开发出一个基于 ...

    3. 深度相机(八)--OpenNI及与Kinect for windows SDK的比较

       OpenNI(开放自然交互)是一个多语言,跨平台的框架,它定义了编写应用程序,并利用其自然交互的API.OpenNI API由一组可用来编写通用自然交互应用的接口组成.OpenNI的主要目的是要 ...

    4. 【计算机视觉】深度相机(八)--OpenNI及与Kinect for windows SDK的比较

      OpenNI(开放自然交互)是一个多语言,跨平台的框架,它定义了编写应用程序,并利用其自然交互的API.OpenNI API由一组可用来编写通用自然交互应用的接口组成.OpenNI的主要目的是要形成一 ...

    5. Kinect for Windows SDK v2.0 开发笔记 (十三) 高清面部帧(4) 面部模型构建器

       (转载请注明出处) 使用SDK: Kinect for Windows SDK v2.0 public preview1409 同前面,因为SDK未完成,不附上函数/方法/接口的超链接. 这次让 ...

    6. Kinect for Windows SDK v2.0 开发笔记 (十二) 高清面部帧(3) 面部模型(2D)

       (转载请注明出处) 使用SDK: Kinect for Windows SDK v2.0 public preview1409 同前面,因为SDK未完成,不附上函数/方法/接口的超链接. 是的, ...

    7. Kinect for Windows SDK 1.6的改进及新特性

      转自http://www.cnblogs.com/yangecnu/archive/2012/10/09/New-features-in-Kinect-for-Windows-SDK1_6.html ...

    8. Kinect for Windows SDK v2.0 开发笔记 (五)骨骼帧与笑面男

      (转载请注明出处) 使用SDK: Kinect for Windows SDK v2.0 public preview 这次说说这骨骼帧的获取.嗯,Kinect买来就为这个啊.不然其他数据,买其他产品 ...

    9. Kinect for Windows SDK发布

      转载请注明出处为KlayGE游戏引擎,本文地址为http://www.klayge.org/2011/06/17/kinect-for-windows-sdk%e5%8f%91%e5%b8%83/ 前 ...

    10. [译]Kinect for Windows SDK开发入门(二):基础知识 上

      上篇文章介绍了Kinect开发的环境配置,这篇文章和下一篇文章将介绍Kinect开发的基本知识,为深入研究Kinect for Windows SDK做好基础. 每一个Kinect应用都有一些基本元素 ...

    最新文章

    1. CentOS7内核升级
    2. Java 位图法排序
    3. 十年的老代码,你敢动?
    4. CodeForces - 1407D Discrete Centrifugal Jumps(单调栈+dp)
    5. 提前祝大家十一中秋节快乐
    6. mxnet 训练--如何生成rec 数据 +自己在本机测试的结果
    7. 0x00007FFEBAD050D8 处(位于 first.exe 中)有未经处理的异常: Microsoft C++ 异常: cv::Exception,位于内存位置 0x0000000DD73CE
    8. (10)Microsoft office Word 2013版本操作入门_word表格
    9. SourceTree的使用
    10. 抽奖滚动文字如何透明地覆盖在动态视频背景上?-活动现场双屏管理系统操作提示
    11. 全网首发:怎样制作CDKEY(2)-数据构造
    12. GreenPlum分布式集群数据库实战培训课程(2天速成版)
    13. 国家/地区 语言代码缩写
    14. 输入月份自动生成excel考勤表,周末高亮,内容可以勾选
    15. ST32位最小系统微控制器STM32F401介绍
    16. 计算机考研哪个专业好就业,计算机专业考研选择哪个方向比较好就业?
    17. 2021-11-09 PMIC RK817 处理POWER键流程linux 部分的简单分析,dts 中会用 interrupt-parent interrupts去处理按键的中断。
    18. Python对word文档进行操作
    19. 将1自动补位为01_自动补位为辅助后游戏就输了一半?那是你不懂辅助的正确打开方式...
    20. 【JavaScript算法】---希尔排序(转载自我的老师 Alley-巷子)

    热门文章

    1. C++中的extern
    2. chrome浏览器升级导致无法手动设置cookie,设置无法保存
    3. 五个手指含义(必会)
    4. java foxpro,在Visual FoxPro中更新顶部
    5. 【读书笔记】你离考研成功就差这本书
    6. QT画贝塞尔曲线 和 曲线与斜率、一阶导数 、二阶导数的关系
    7. Sandboxie免费开源沙箱软件下载与详细使用教程
    8. UK EU 码对照表,USA EURO SIZE码对照表,国外衣服码对照表
    9. r语言怎么做经验分布_医学统计与R语言:对数正态分布与卡方分布
    10. CSS实现播放暂停按钮样式