Skip to content

Make Processing

조회 수 804 추천 수 0 댓글 5
Atachment
첨부파일 '1'
?

단축키

Prev이전 문서

Next다음 문서

+ - Up Down Comment Print
?

단축키

Prev이전 문서

Next다음 문서

+ - Up Down Comment Print

Kinect 출시는 인터랙티브의 역사의 한 획을 그었다. 기존에 적외선 카메라를 이용해서 사람의 움직임과 동작을 인식하기 위해서 OpenCV를 사용했는데 그 번거로움을 한방에 날려주었다.

사람의 스켈레톤을 인식하고 거리에 따라서 깊이 값을 보여주는 Kinect는 획기적이라고 할수 있었다. 모든 분(?)이 아시다시피 Kinect 는 Xbox 360의 센서로 출시되었으나. 많은 프로그래머들의 해킹으로 인해 Kinect for Windows 버전이 출시 되었으며,  현재 Kinect One 이라는 이름으로 V2 버전이 출시되어 있는 상태이다.

Processing 3.0.2 ( http://processing.org ) 버전에서 구현된 코드로 Kinect v1  model 1414, 1517  버전에서 테스트 되었다. 사용된 라이브러리는 Kinect for Processing Library 이다.

일반적으로 인터랙티브 요소에서 Kinect를 사용할 때 스켈레톤을 많이 사용하는데 MO-ON lab 에서는 DepthMap 을 주로 사용하게 된다. 그 이유는 꼭 사람의 뼈대를 인식하는 콘텐츠를 제외하고는 Processing에서  스켈레톤 보다는 DepthMap 의 반응 속도가 더 빠르기 때문이다. 그리고 요즘의 인터랙티브 콘텐츠는 다중의 관객이 함께 할수 있는 콘텐츠를 윈하기 때문에 DepthMap을 이용할 경우  인식되는 사람(덩어리로 인식)의 수를 늘릴수 있는 장점을 가지기 때문이다.

http://www.telecom.ulg.ac.be

depth map

 

http://michaelkipp.de/

skeleton

 

코드실행

  1. Kinect SDK 1.8 설치
  2. Processing 에서 Kinect4WinSDK를 설치
  3. 실행
  4. 화면에 노란색 depthmap 이미지가 나올때 까지 기다린다.(인식되는데 5초 정도 걸린는 듯하다.)

 

본 코드는 DepthMap을 기준으로 0 ~255 까지 들어오는 명암 값을 기준으로 일정한 각격의 노란색 사각형을 그리게 된다. 그리고 그려진 사각형의 모양을 Blob 하여 덩어리로 인식 시키고 각 덩어리를 하나의 아이디로 부여 하고 좌표를 받아 온다. 그리고 스켄레톤을 이용하여 왼손과 오른손의 좌표를 DepthMap 과 매칭하여 보여주게 된다.

kinect

왼쪽 빨간점과 오른쪽 노란점 그리고 머리 부분의 흰점이 각 신체 부위를 나타낸다.

하늘색 사각형은 감지영역을 나타낸다. 하늘색 바란색 부분에만 좌표가 생성되게 된다.

프로그램은 OSC 통신으로 데이터를 전송하게 된다. 기본적으로 30개 의 좌표 데이터를 출력되게 된다.

제공되는 소스는 정리되지 않은 소스(?) 이기는 하지만 작동에는 전혀 문제가 없다.

 

 

*가끔 작동 아래와 같은 에러를 보여주며 작동되지 않는 컴퓨터들이 있다.

kinect1

 

이하 코드.

kinect_bone_01.pde

// Processing 3.0.2 with sdk 1.8
// MO-ON Creative Lab, 2016
// http://lab.themo-on.com
// code by Jaejoong Lee (developer@themo-on.com / only104@naver.com)
//
 
import oscP5.*;
import netP5.*;

import kinect4WinSDK.Kinect;
import kinect4WinSDK.SkeletonData;

import blobDetection.*;  //

int table_x1 = 0; // 감지 영역크기 조정 
int table_y1 = 0; // 감지 영역크기 조정
 
int table_x2 = 640;  // 감지 영역크기 조정
int table_y2 = 400;   // 감지 영역크기 조정

Kinect kinect;
ArrayList <SkeletonData> bodies;

color backColor = color(0);

PVector head_pos;
PVector left_H_pos;
PVector right_H_pos;

int play_delay = 0;
PImage img;
BlobDetection theBlobDetection;

OscP5 oscP5;
NetAddress myRemoteLocation;

int hand_pos_num = 30;  // 손 좌표 개수 
float[] hand_xpos = new float[hand_pos_num];
float[] hand_ypos = new float[hand_pos_num];

int now_time;
int person_del_time;
int mode_change_time = 20000;  // 모드 정상화 시간 

void setup()
{
  size(640, 480);
  background(255);
  
  oscP5 = new OscP5(this, 12005);

  myRemoteLocation = new NetAddress("127.0.0.1", 7001);
  
  kinect = new Kinect(this);
  bodies = new ArrayList<SkeletonData>();
  
  kinect = new Kinect(this);

  smooth();
  
  now_time = millis();
  person_del_time = millis();
  img = new PImage(80, 60);
    theBlobDetection = new BlobDetection(img.width, img.height);
    theBlobDetection.setPosDiscrimination(true);
    theBlobDetection.setThreshold(0.2f); // will detect bright areas whose luminosity > 
    play_delay = millis();
    
    
}

void draw()
{
   background(0);
  PImage b = kinect.GetDepth();
//  image(kinect.GetImage(), 320, 0, 320, 240);
 noStroke();
 int step = 5;
  for(int y = 0; y <480; y+=step)
  {
   for(int x = 0; x < 640; x+=step)
   {
      if( 150 < brightness(b.pixels[x+640*y])) // depth map의 깊이 조정 
       //if( 200 > brightness(b.get(x, y)))
       {
           fill(#ffcc33);
           rect(x, y, step, step);  
       }
       else
       {
        // fill(#ffcc33);
       }
   }
  }
  
  fill(255);
  text(frameRate , 100, 100);
  rectMode(CORNER);
  //image(b, 640, 0, 640, 480);
  
  PImage temp_img = get(table_x1, table_y1, table_x2 - table_x1, table_y2 - table_y1);
  //PImage temp_img = get(0, 0, table_w, table_h);
  img.copy(temp_img, 0, 0, temp_img.width, temp_img.height, 0, 0, img.width, img.height);

  fastblur(img, 2);
  theBlobDetection.computeBlobs(img.pixels);
  drawBlobsAndEdges(true, true);
  
  stroke(#16CAD8); // table area
  fill(#16CAD8, 50);
  rect(table_x1, table_y1, table_x2 - table_x1, table_y2 - table_y1);
  
  if(mode == 1) // 폭발 모드 에서 
  {
    if(millis() - now_time > mode_change_time) // 일정 시간이 지나면 
    {
        mode = 0; // 일반 모드로 전환
    } 
  }
  
  for (int i=0; i<bodies.size (); i++) 
  {
    drawSkeleton(i, bodies.get(i));
    //drawPosition(bodies.get(i));
  //  println("Total Person :" + bodies.size () +"EA   id : "+ i + " - "+ bodies.get(i).dwTrackingID);
  }
  
  osc_data();    // hand data reset
   for (int i = 0; i < hand_pos_num; i ++)
   {
        hand_xpos[i] = table_x1;
        hand_ypos[i] = table_y1;
  }
  
  //println("person num : " + bodies.size());
//  osc_data();
}

void keyReleased()
{
  for (int i=0; i<bodies.size (); i++) 
  {
    disappearEvent(bodies.get(i));
  }
}

 

blur.pde

// ==================================================
// drawBlobsAndEdges()
// ==================================================
void drawBlobsAndEdges(boolean drawBlobs, boolean drawEdges)
{
  noFill();
  Blob b;
  EdgeVertex eA, eB;
  person_num = theBlobDetection.getBlobNb();
  for (int n=0 ; n<theBlobDetection.getBlobNb() ; n++)
  {
    b=theBlobDetection.getBlob(n);
    if (b!=null)
    {
      // Edges
      if (drawEdges)
      {
        strokeWeight(3);
        stroke(0, 255, 0);
        for (int m=0;m<b.getEdgeNb();m++)
        {
          eA = b.getEdgeVertexA(m);
          eB = b.getEdgeVertexB(m);
          if (eA !=null && eB !=null)
            line(
            eA.x * (table_x2 - table_x1) + table_x1, eA.y * (table_y2 - table_y1)  + table_y1, 
            eB.x * (table_x2 - table_x1) + table_x1, eB.y * (table_y2 - table_y1)  + table_y1
              );
        }
      }

      // Blobs
      if (drawBlobs)
      {
        strokeWeight(1);
        stroke(255, 0, 0);
        rect( b.xMin * (table_x2 - table_x1) + table_x1, b.yMin * (table_y2 - table_y1) + table_y1, b.w * (table_x2 - table_x1), b.h * (table_y2 - table_y1) );
        fill(#ff0000);
        noStroke();
        if(n < hand_pos_num)
        {
            hand_xpos[n] = b.xMin * (table_x2 - table_x1) + (b.w * (table_x2 - table_x1))/2 + table_x1; // x position send
            //if(b.yMin * (table_y2 - table_y1) > (table_y2 - table_y1)/2)  // 테이블 위아래 위치에 따라서 기준점을 바꿔야 해서  
           //{//
             //hand_ypos[n] = b.yMin * (table_y2 - table_y1)  + table_y1;
           //} 
           //else
           //{
            //hand_ypos[n] = b.yMin * (table_y2 - table_y1) + (b.h * (table_y2 - table_y1)) + table_y1; //  상단기준 
            hand_ypos[n] = b.yMin * (table_y2 - table_y1) + (b.h * (table_y2 - table_y1))/2 + table_y1;
           //}
    
            ellipse(hand_xpos[n], hand_ypos[n], 10, 10);
            noFill();
        }
      }
    }
  }
}

// ==================================================
// Super Fast Blur v1.1
// by Mario Klingemann 
// <http://incubator.quasimondo.com>
// ==================================================
void fastblur(PImage img, int radius)
{
  if (radius<1) {
    return;
  }
  int w=img.width;
  int h=img.height;
  int wm=w-1;
  int hm=h-1;
  int wh=w*h;
  int div=radius+radius+1;
  int r[]=new int[wh];
  int g[]=new int[wh];
  int b[]=new int[wh];
  int rsum, gsum, bsum, x, y, i, p, p1, p2, yp, yi, yw;
  int vmin[] = new int[max(w, h)];
  int vmax[] = new int[max(w, h)];
  int[] pix=img.pixels;
  int dv[]=new int[256*div];
  for (i=0;i<256*div;i++) {
    dv[i]=(i/div);
  }

  yw=yi=0;

  for (y=0;y<h;y++) {
    rsum=gsum=bsum=0;
    for (i=-radius;i<=radius;i++) {
      p=pix[yi+min(wm, max(i, 0))];
      rsum+=(p & 0xff0000)>>16;
      gsum+=(p & 0x00ff00)>>8;
      bsum+= p & 0x0000ff;
    }
    for (x=0;x<w;x++) {

      r[yi]=dv[rsum];
      g[yi]=dv[gsum];
      b[yi]=dv[bsum];

      if (y==0) {
        vmin[x]=min(x+radius+1, wm);
        vmax[x]=max(x-radius, 0);
      }
      p1=pix[yw+vmin[x]];
      p2=pix[yw+vmax[x]];

      rsum+=((p1 & 0xff0000)-(p2 & 0xff0000))>>16;
      gsum+=((p1 & 0x00ff00)-(p2 & 0x00ff00))>>8;
      bsum+= (p1 & 0x0000ff)-(p2 & 0x0000ff);
      yi++;
    }
    yw+=w;
  }

  for (x=0;x<w;x++) {
    rsum=gsum=bsum=0;
    yp=-radius*w;
    for (i=-radius;i<=radius;i++) {
      yi=max(0, yp)+x;
      rsum+=r[yi];
      gsum+=g[yi];
      bsum+=b[yi];
      yp+=w;
    }
    yi=x;
    for (y=0;y<h;y++) {
      pix[yi]=0xff000000 | (dv[rsum]<<16) | (dv[gsum]<<8) | dv[bsum];
      if (x==0) {
        vmin[y]=min(y+radius+1, hm)*w;
        vmax[y]=max(y-radius, 0)*w;
      }
      p1=x+vmin[y];
      p2=x+vmax[y];

      rsum+=r[p1]-r[p2];
      gsum+=g[p1]-g[p2];
      bsum+=b[p1]-b[p2];

      yi+=w;
    }
  }
}

 

bone.pde

void drawPosition(SkeletonData _s) 
{
  noStroke();
  fill(0, 100, 255);
  String s1 = str(_s.dwTrackingID);
  text(s1, _s.position.x*width, _s.position.y*height);
}

String transPosition(float v, float v1, int kinect_v, int visual_v_min, int visual_v_max)
{
  int temp_v = int(map(v, 0, kinect_v, visual_v_min, visual_v_max));
  int temp_v1 = int(map(v1, 0, kinect_v, visual_v_min, visual_v_max));
  
  String temp_s = str(temp_v) + "," + str(temp_v1);   
  return temp_s;
}

void drawSkeleton(int p_cnt, SkeletonData _s) 
{
  fill(255);
  ellipse( _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HEAD].x*width, _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HEAD].y*height, 10, 10); // 머리
  
  float xpos = _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_LEFT].x*width;
  float ypos = _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_LEFT].y*height;
  
  float xpos1 = _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].x*width;
  float ypos1 = _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].y*height;
  
 // println(str(_s.dwTrackingID) +" :   " +  xpos + "   :   " + ypos); 
  
  rectMode(CENTER);
  fill(#00ff00);
  
  //float temp_x = map(xpos, 0, 640, 0, 2560);
  //float temp_y = map(ypos, 0, 480, 0, 1440);
  
//  hand_xpos[p_cnt] = transPosition(xpos, xpos1, 640, -200, 1024);
//  hand_ypos[p_cnt] = transPosition(ypos, ypos1, 480, -200, 200);
  
  //rect(hand_xpos[p_cnt], hand_ypos[p_cnt], 5, 5 );
  
  if(_s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HEAD].y >  _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].y)
  {
    backColor = color(#ffcc33); 
  }
  else if(_s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HEAD].y >  _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_LEFT].y)
  {
    backColor = color(#ff0000);
  }
  else
  {
    backColor = color(0);
  }
  
  if(_s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HEAD].y >  _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].y && _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HEAD].y >  _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_LEFT].y)
  {
    backColor = color(#ffffff); 
    mode = 1; // 폭발 모드 
    now_time = millis(); // 폭발모드 시작 
  }

  fill(#ffcc33);
  //ellipse(hand_xpos[p_cnt], hand_ypos[p_cnt], 10, 10 );
  ellipse( _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].x*width, _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].y*height, 10, 10); // 오른손
  fill(#ff0000);
  ellipse( _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_LEFT].x*width, _s.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_LEFT].y*height, 10, 10); // 왼손
}

void DrawBone(SkeletonData _s, int _j1, int _j2) 
{
  noFill();
  stroke(255, 255, 0);
  if (_s.skeletonPositionTrackingState[_j1] != Kinect.NUI_SKELETON_POSITION_NOT_TRACKED &&
    _s.skeletonPositionTrackingState[_j2] != Kinect.NUI_SKELETON_POSITION_NOT_TRACKED) {
    line(_s.skeletonPositions[_j1].x*width, 
    _s.skeletonPositions[_j1].y*height, 
    _s.skeletonPositions[_j2].x*width, 
    _s.skeletonPositions[_j2].y*height);
  }
}

void appearEvent(SkeletonData _s) 
{
  if (_s.trackingState == Kinect.NUI_SKELETON_NOT_TRACKED) 
  {
    return;
  }
  synchronized(bodies) {
    
    bodies.add(_s);
  }
}

void disappearEvent(SkeletonData _s) 
{
  synchronized(bodies) {
    for (int i=bodies.size ()-1; i>=0; i--) 
    {
      if (_s.dwTrackingID == bodies.get(i).dwTrackingID) 
      {
        bodies.remove(i);
      }
    }
  }
}

void moveEvent(SkeletonData _b, SkeletonData _a) 
{
  if (_a.trackingState == Kinect.NUI_SKELETON_NOT_TRACKED) 
  {
    return;
  }
  synchronized(bodies) {
    for (int i=bodies.size ()-1; i>=0; i--) 
    {
      if (_b.dwTrackingID == bodies.get(i).dwTrackingID) 
      {
        bodies.get(i).copy(_a);
        break;
      }
    }
  }
}

 

osc_data.pde

int person_num = 0;
int mode = 0;

void osc_data()
{
  //print(".");
  if (person_num > hand_pos_num)
  {
    person_num = hand_pos_num;
  }
  for (int i = 0; i < person_num+1; i++)
  {
    // print("/");
    OscMessage myMessage = new OscMessage("/kinect1");
    //    float temp_h_y_map = map(hand_xpos[i],0,1280,0, 1200);
    myMessage.add(person_num);
    myMessage.add(i); /* add an int to the osc message */

    // 포지션 노말라이즈 
    hand_xpos[i] = map(hand_xpos[i], table_x1, table_x2, -512, 512);
    hand_ypos[i] = map(hand_ypos[i], table_y1, table_y2, 700, 0);
    println("X "+i+" : " + hand_xpos[i] +"    " + "Y "+i+" : " + hand_ypos[i]);

    myMessage.add(hand_xpos[i]); /* add an int to the osc message */
    myMessage.add(hand_ypos[i]); /* add an int to the osc message */
    myMessage.add(mode); /* add an int to the osc message */
    print(person_num+"---------------------------");
    oscP5.send(myMessage, myRemoteLocation);
  }
  person_num = 0;
}

void oscEvent(OscMessage theOscMessage) {
  /* print the address pattern and the typetag of the received OscMessage */
  //  print("### received an osc message.");
  //  print(" addrpattern: "+theOscMessage.addrPattern());
  //  println(" typetag: "+theOscMessage.typetag());
}
?
  • profile
    smileblue 2016.07.24 13:57

    depth map의 밝기 값을 가지고 만든거라서 라이브러리 부분과 depth map 이미지만 바꿔주면 키넥트 2에서도 사용가능합니다.~(스켈레톤은 제외)

  • ?
    모랑 2016.07.25 18:50

    좋은정보감사합니다~프로세싱에서 사용하려면 키넥트v1이 v2보다 호환성이 좋나요?

    아 ~~ 중고 구하려고 해도 엑박용만 있지 개발자용은 뜨문뜨문하네요. 

  • profile
    smileblue 2016.07.26 10:45

    키넥트 2의 경우 USB 3.0을 지원해야 하며 USB 칩셋이 인텔 또는 nec 칩셋으로 구성되어 있어야합니다. 만약 사양이나 포트 호환이 안되신다면 성능차이는 있지만 키넥트 1을 추천 드립니다.

  • ?
    모랑 2016.10.16 23:57
    늦었지만 친절한 답변 감사합니다^^ 이글을 지금 확인하네요. 중고로 구입해보려고 했는데 시기가 안맞아서 꿩대신 닭으로..립모션으로 여러가지 해보고 있어요. 립모션은 뭔가 제약이 많아서 기회가 되면 키넥트1도 구해봐야겠어요~
  • ?
    lazy_black 2017.10.15 02:10
    Kinect SDK 1.8는 맥용이 없죠???

프로세싱 사용자 포럼

프로세싱에 관한 무한한 이야기

  1. Depth map of Kinect v1 code (모온 크레이티브 랩 포스팅 복사)

    Kinect 출시는 인터랙티브의 역사의 한 획을 그었다. 기존에 적외선 카메라를 이용해서 사람의 움직임과 동작을 인식하기 위해서 OpenCV를 사용했는데 그 번거로움을 한방에 날려주었다. 사람의 스켈레톤을 인식하고 거리에 따라서 깊이 값을 보여주는 Kinect...
    Date2016.07.24 Bysmileblue Reply5 Views804 file
    Read More
  2. 프로세싱 홈페이지 안드로이드 프로세싱이 추가되었네요~

    http://android.processing.org/index.html 파이썬과 자바스크립트에 이어서 기존에 있던 안드로이모드를 정리해서 페이지를 추가 했네요... 역시 대단...합니다.
    Date2016.07.09 Bysmileblue Reply2 Views443 file
    Read More
  3. Processing 3 디버그 사용방법 튜토리얼

    Processing 3 Debugger from Processing Foundation on Vimeo.
    Date2015.09.29 Bysmileblue Reply1 Views599 file
    Read More
  4. 프로세싱 3.0a7 버전

    요번에 업데이트는 메이저 업데이트가 되었네요. 인터페이스도 바뀌고 내부적으로도 애플릿 방향이 완전히 바뀔듯합니다. 그리고 프로세싱 2.0과 3.0 두가지가 다른 방향으로 발전될 것이라는 이야기를 하고 있군요. 기존은 2.X 버전도 충분히 안정화가 ...
    Date2015.05.05 Bysmileblue Reply0 Views574 file
    Read More
  5. No Image

    String 변수에서 == 사용하실때 주의!!

    프로그램 개발중 ==(같다) 이걸 사용하는데 값이 제대로 들어오는데 비교를 못하는 사태가 발생해서 2일동안 삽질중에 발견 ㅡㅡ;;; String의 비교에서는 equals() 을 사용해야한다는 것!!!! https://processing.org/reference/String_equals_.h...
    Date2015.03.08 Bysmileblue Reply0 Views490
    Read More
  6. 원하는 모양으로 롤오버 하기

    /** * taken from http://wiki.processing.org/index.php/Using_AWT%27s_Polygon_class * @author Andreas Köberle */ Poly p; void setup() { int[] x = { 20, 40, 40, 60, 60, 20 }; int[] y = { 20, 20, 40, 40, 60, 60 }; p = new Poly(x, y, 6); } void d...
    Date2015.01.20 Bysmileblue Reply1 Views726 file
    Read More
  7. No Image

    언제 사용할지 모르지만 우선 4개 질렀습니다. ㅋㅋㅋ

    https://punchthrough.com/bean/
    Date2014.10.23 Bysmileblue Reply2 Views1042
    Read More
  8. 프로세싱 3 alpah 3 버전 한글 메뉴 지원

    이번에 알파 3번째 버전이 릴리즈 되었습니다. 여러 업데이트 중에서 다국어 지원 부분이 보이네요...^^ 아직 완전히 한글이 지원되는 것은 아니지만.. 왠지 반갑네요..^^
    Date2014.08.27 Bysmileblue Reply1 Views1286 file
    Read More
  9. 작가 안여현 홈페이지

    구글링을 하다가 발견한 타이포 그래픽과 디자인을 하시는 작가 분입니다.(개인적으로 몰라요 ㅜㅜ) 프로세싱관련 검색을 찾게 되었는데 미국에 계신것 같은데 2010년에 한국에서도 전시를 하셨었네요. 패턴느낌과 색감이 너무 좋아서 주소 링크합니다. ...
    Date2014.08.17 Bysmileblue Reply3 Views1622 file
    Read More
  10. 피아노 건반 이미지 공유 합니다.

    프로세싱으로 피아노 만들때 사용해 보심이 어떨런지요.ㅎㅎㅎㅎ
    Date2014.08.16 Bysmileblue Reply0 Views2597 file
    Read More
Board Pagination ‹ Prev 1 2 3 4 5 6 7 8 9 10 Next ›
/ 10

나눔글꼴 설치 안내


이 PC에는 나눔글꼴이 설치되어 있지 않습니다.

이 사이트를 나눔글꼴로 보기 위해서는
나눔글꼴을 설치해야 합니다.

설치 취소

Designed by sketchbooks.co.kr / sketchbook5 board skin

Sketchbook5, 스케치북5

Sketchbook5, 스케치북5

Sketchbook5, 스케치북5

Sketchbook5, 스케치북5

Copyright (c) 2012 Make Processing. All Right Reserved.

smileblue

sketchbook5, 스케치북5

sketchbook5, 스케치북5

나눔글꼴 설치 안내


이 PC에는 나눔글꼴이 설치되어 있지 않습니다.

이 사이트를 나눔글꼴로 보기 위해서는
나눔글꼴을 설치해야 합니다.

설치 취소