키넥트 관련 자료를 포스팅합니다.
예전에 화상캠에 빨간색 값 하나 잘 받아오려고 필터 씌우고 알고리즘 쓰고 별 걸 다 했었는데.. 어느날 닌텐도 위(wii)가 나오더니만
그것 마저 다 쌈싸먹은 마소의 키넥트(Kinect )!!
일년이 지난 지금.. 영상관련 해서 정말 쉽게 개발 할 수 있는 시대가 왔군요. 주재.. 보고있나?
출처 : 지평선너머(snscho66) - http://snscho66.blog.me/100131461219
MS Kinect SDK Beta가 공개된지도 좀 지났다.
윈도우즈7과 연동되어 사용이 가능해짐으로써, 컴퓨터를 이용하는 방식이
많이 바뀔 것 같다. 이러한 변화는 게임에서 시작이 될 것이고,
이미 시작되고 있다.
이 포스트는 C#을 사용하여 간단한 키넥트 응용 프로그램을 작성하는
방법을 다룬다. 독자는 C#과 MS Visual Studio2010에 이미 익숙해져 있다고
가정한다.
1단계 - 개발환경 구축
Kinect .NET SDK를 사용하려면 다음의 요구사항을 만족해야 한다.
운영체제
- Windows 7 (x86 또는 x64)
하드웨어
- 듀얼코어, 2.66GHz 이상의 컴퓨터
- MS DirectX 9.0c 를 지원하는 그래픽 카드
- 2GB 이상의 램
- 키넥트 센서
소프트웨어
- MS Visual Studio 2010 (Express 에디션도 가능)
- MS .NET framework 4.0
- Kinect for Windows SDK (Beta)
2단계 - WPF project를 생성
Microsoft.Research.Kinect.Nui 에 대한 레퍼런스를 추가한다.
(C:\Program Files (x86)\Microsoft Research KinectSDK)에 위치해 있다.
샘플 타겟을 반드시 x86 platform으로 한다. 이 SDK는 오직 x86 만을 지원하기 때문이다.
응용 프로그램은 반드시 키넥트 센서를 초기화 해주어야 한다. 초기화는 Runtime.Initialize
를 호출함으로써 이루어 진다. 이 함수를 호출하면 프레임 캡쳐 엔진을 초기화 하고, 센서 데이터를
수신하는 스레드를 만들며, 프레임이 준비되었음을 알리는 시그날을 응용프로그램에 보내는 일을
한다. 또한 센서 데이터를 처리하고 수집하는 서브시스템을 초기화 한다. 이 초기화 함수가
키넥트 센서를 찾는데 실패하면 InvalidOperationException 을 발생시킨다.
윈도우즈 로드 이벤트를 만들고 InitializeNui를 호출한다.
private void InitializeNui()
{
try
{
// _kinectNui 를 Runtime 오브젝트로 선언, 이 것은 키넥트 센서 인스턴스이다.
_kinectNui = new Runtime();
//비디오와 뎁스 스트림을 오픈하고, 이벤트 핸들러를 초기화한다.
//다른 키넥트 함수를 부르기 전에 반드시 초기화를 해준다.
_kinectNui.Initialize(RuntimeOptions.UseDepthAndPlayerIndex |
RuntimeOptions.UseSkeletalTracking | RuntimeOptions.UseColor);
//스트림 칼라 이미지:
// • UseColor옵션을 반드시 포함한다.
// • 해상도는 1280x1024 와 640x480 두 가지 이다.
// • 이미지 타입은 Color, ColorYUV, 와ColorYUVRaw이다.
_kinectNui.VideoStream.Open(ImageStreamType.Video, 2,
ImageResolution.Resolution640x480, ImageType.ColorYuv);
//스트림 뎁스와 플레이어 색인 데이터:
// • UseDepthAndPlayerIndex 옵션은 반드시 포함한다.
// • 인텍스의 유효한 해상도는 320x240 와 80x60이다.
// • 유효한 이미지 타입은 DepthAndPlayerIndex뿐이다.
_kinectNui.DepthStream.Open(ImageStreamType.Depth, 2,
ageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex);
lastTime = DateTime.Now;
_kinectNui.VideoFrameReady += new EventHandler<ImageFrameReadyEventArgs>
NuiVideoFrameReady);
_kinectNui.DepthFrameReady += new EventHandler<ImageFrameReadyEventArgs>
(nui_DepthFrameReady);
}
catch (InvalidOperationException ex)
{
MessageBox.Show(ex.Message);
}
}
3단계 : 비디오 출력
비디오와 뎁스는 평평한 이미지를 리턴하므로 비트맵을 만들어서 UI에 뿌려주면 된다.
비디오 프레임 레디 이벤트 핸들러
void NuiVideoFrameReady(object sender, ImageFrameReadyEventArgs e)
{
PlanarImage Image = e.ImageFrame.Image;
image.Source = BitmapSource.Create(
Image.Width, Image.Height, 96, 96, PixelFormats.Bgr32, null,
Image.Bits, Image.Width * Image.BytesPerPixel);
imageCmyk32.Source = BitmapSource.Create(
Image.Width, Image.Height, 96, 96, PixelFormats.Cmyk32, null,
Image.Bits, Image.Width * Image.BytesPerPixel);
}
뎁스 프레임 레디 이벤트 핸들러
void nui_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
{
var Image = e.ImageFrame.Image;
var convertedDepthFrame = convertDepthFrame(Image.Bits);
depth.Source = BitmapSource.Create(
Image.Width, Image.Height, 96, 96, PixelFormats.Bgr32, null, convertedDepthFrame, Image.Width * 4);
CalculateFps();
}
// 플레이어 인덱스를 포함한 16비트 그레이스케일 뎁스 프레임을
// 32비트 프레임으로 바꾼다.
// 다른 사용자는 다른 컬러로 표현한다.
byte[] convertDepthFrame(byte[] depthFrame16)
{
for (int i16 = 0, i32 = 0; i16 < depthFrame16.Length && i32 < depthFrame32.Length; i16 += 2, i32 += 4)
{
int player = depthFrame16[i16] & 0x07;
int realDepth = (depthFrame16[i16 + 1] << 5) | (depthFrame16[i16] >> 3);
// 13비트 뎁스 정보를 8비트 로 바꾼다.
// 이것은 화면 표시를 위한 것이다.
byte intensity = (byte)(255 - (255 * realDepth / 0x0fff));
depthFrame32[i32 + RED_IDX] = intensity;
depthFrame32[i32 + BLUE_IDX] = intensity;
depthFrame32[i32 + GREEN_IDX] = intensity;
}
return depthFrame32;
}
void CalculateFps()
{
++totalFrames;
var cur = DateTime.Now;
if (cur.Subtract(lastTime) > TimeSpan.FromSeconds(1))
{
int frameDiff = totalFrames - lastFrames;
lastFrames = totalFrames;
lastTime = cur;
frameRate.Text = frameDiff.ToString() + " fps";
}
}
위에서 정상 화면과 32비트로 변환한 것, 뎁스만 추출한 것의 이미지를 보여준다.
4단계 : 카메라 각도 조절
키넥트에서 카메라 콘트롤은 매우 쉽다.
카메라 앵글의 최대값과 최소값이 있지만 단순히 키넥트 센서를 손으로 옮김으로서
자동으로 카메라 앵글을 조정한다.
키낵트 초기화를 한 다음
카메라 오브젝트를 만든다.
private Camera _cam;
_cam = _kinectNui.NuiCamera;
txtCameraName.Text = _cam.UniqueDeviceName;
다음은 카메라 클래스의 정의이다.
namespace Microsoft.Research.Kinect.Nui
{
public class Camera
{
public static readonly int ElevationMaximum;
public static readonly int ElevationMinimum;
public int ElevationAngle { get; set; }
public string UniqueDeviceName { get; }
public void GetColorPixelCoordinatesFromDepthPixel(ImageResolution colorResolution, ImageViewArea viewArea,
int depthX, int depthY, short depthValue, out int colorX,
out int colorY);
}
}
5단계 : 업 다운
private void BtnCameraUpClick(object sender, RoutedEventArgs e)
{
try
{
_cam.ElevationAngle = _cam.ElevationAngle + 5;
}
catch (InvalidOperationException ex)
{
MessageBox.Show(ex.Message);
}
catch (ArgumentOutOfRangeException outOfRangeException)
{
//Elevation angle must be between Elevation Minimum/Maximum"
MessageBox.Show(outOfRangeException.Message);
}
}
다음은 다운이다.
private void BtnCameraDownClick(object sender, RoutedEventArgs e)
{
try
{
_cam.ElevationAngle = _cam.ElevationAngle - 5;
}
catch (InvalidOperationException ex)
{
MessageBox.Show(ex.Message);
}
catch (ArgumentOutOfRangeException outOfRangeException)
{
//Elevation angle must be between Elevation Minimum/Maximum"
MessageBox.Show(outOfRangeException.Message);
}
}
배경 지식 : 뼈대 트랙킹
키넥트의 강력한 기능 중 하나는 센서 앞에 서 있는 사람의 뼈대를 발견해 내는 능력이다.
이 기능은 매우 빠르고 연습이 필요 없이 가능하다.
NUI Skeleton API는 센서앞에 서 있는 두 사람을 분별한다.
분별한 데이터는 좌표로 제공되는데 뼈대 포지션이라 한다.
이 데이터는 사람의 위치와 현재의 자세를 표현해 준다.
키넥트는 20개의 포인트로 사람의 자세를 판별하는데
아래의 그림으로 이를 나타낸다.
6 단계 : SkeletonFrameReady 에 등록
UseSkeletalTracking 옵션으로 초기화 한다. 이 옵션을 안 주면 뼈대 추적이 안된다.
_kinectNui.Initialize(RuntimeOptions.UseColor | RuntimeOptions.UseSkeletalTracking | RuntimeOptions.UseColor);
_kinectNui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(SkeletonFrameReady);
뼈대 추적은 3인 이상은 안된다.
키넥트 개발에서 디버깅은 쉬운일이 아니다. - 매번 당신은 똑같은 동작을 반복해야 한다.
뼈대 추적의 위치값은 다음의 enum 값이다.
namespace Microsoft.Research.Kinect.Nui
{
public enum JointID
{
HipCenter,
Spine,
ShoulderCenter,
Head,
ShoulderLeft,
ElbowLeft,
WristLeft,
HandLeft,
ShoulderRight,
ElbowRight,
WristRight,
HandRight,
HipLeft,
KneeLeft,
AnkleLeft,
FootLeft,
HipRight,
KneeRight,
AnkleRight,
FootRight,
Count,
}
}
7 댠계 : 뼈대 위치(Joint Position) 얻기
조인트 포지션은 카메라 공간에 있는 것이므로 우리는 사이즈와 위치를 변환할 필요가 있다.
뎁스 이미지 공간
뎁스 맵은 640x480, 320×240, or 80x60 픽셀 값을 가지는데 각 픽셀은
미리미터 단위로 거리를 나타낸다. 픽셀 값 0 은 센서가 물체를 찾지 못한 것을
의미한다.
뼈대 이미지 공간
사람의 뼈대 위치는 x,y,z 축으로 나타낸다. 뎁스 이미지 공간과 달리,
이 세가지 좌표는 메터 단위로 표현된다. 이것은 오른손 좌표계 시스템이다.
Z 축은 센서로 부터 멀어지는 값, Y축은 위로, X축은 왼쪽이다.
이를 아래의 그림에 나타낸다.
private Point getDisplayPosition(Joint joint)
{
float depthX, depthY;
_kinectNui.SkeletonEngine.SkeletonToDepthImage(joint.Position, out depthX, out depthY);
depthX = Math.Max(0, Math.Min(depthX * 320, 320)); //convert to 320, 240 space
depthY = Math.Max(0, Math.Min(depthY * 240, 240)); //convert to 320, 240 space
int colorX, colorY;
ImageViewArea iv = new ImageViewArea();
// only ImageResolution.Resolution640x480 is supported at this point _kinectNui.NuiCamera.GetColorPixelCoordinatesFromDepthPixel(ImageResolution.Resolution640x480, iv, (int)depthX, (int)depthY, (short)0, out colorX, out colorY);
// map back to skeleton.Width & skeleton.Height return new Point((int)(imageContainer.Width * colorX / 640.0) - 30, (int)(imageContainer.Height * colorY / 480) - 30);
}
8단계 : 뼈대 타입에 따라 이미지 삽입
Vector4(x,y,z,w)를 사용하여 뼈대의 중심점을 표현한다. 여기서 x,y,z는 카메라 공간 좌표이고,
w는 품질 레벨을 말한다.(0-1 사이의 값이다.)
void SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
foreach (SkeletonData data in e.SkeletonFrame.Skeletons)
{
//트랙킹이 되는 뼈대인지 아닌지 판별한다 트랙킹이 안되는 것이면 위치값만
얻을 수 있다..
if (SkeletonTrackingState.Tracked != data.TrackingState) continue;
//각 관절의 위치값은 Vector4: (x, y, z, w)로 표현된다.
foreach (Joint joint in data.Joints)
{
if (joint.Position.W < 0.6f) return;// Quality check
switch (joint.ID)
{
case JointID.Head:
var heanp = getDisplayPosition(joint);
Canvas.SetLeft(imgHead, heanp.X);
Canvas.SetTop(imgHead, heanp.Y);
break;
case JointID.HandRight:
var rhp = getDisplayPosition(joint);
Canvas.SetLeft(imgRightHand, rhp.X);
Canvas.SetTop(imgRightHand, rhp.Y);
break;
case JointID.HandLeft:
var lhp = getDisplayPosition(joint);
Canvas.SetLeft(imgLefttHand, lhp.X);
Canvas.SetTop(imgLefttHand, lhp.Y);
break;
}
}
}
}
이상으로 뼈대를 추적하여 주먹과 얼굴을 덧 씌우는 예제 프로그램을 보였다.
'2_ 바삭바삭 프로그래밍 > C# and Visual C++' 카테고리의 다른 글
C# - MP3 간단하게 재생하기 (mciSendString, winmm.dll) (2) | 2012.01.05 |
---|---|
C# - 트위터 사용 예제 (Using Twitter API Example) (2) | 2011.12.29 |
C# - 드래그로 창 이동, 폼 접기 / 폼 펼치기 / 최소화 (1) | 2011.08.17 |
[C#] 정규표현식 마스터! + 화이트 스페이스 없애기 (1) | 2011.08.05 |
[C#] MP3 재생 프로그램 - Playing MP3 files with C# (9) | 2011.07.26 |