Home 캡스톤일지 | ~ 09.24 서비스 기획과 미디어파이프 테스트
Post
Cancel

캡스톤일지 | ~ 09.24 서비스 기획과 미디어파이프 테스트

막학기 캡스톤(일명 졸작)을 하며 있었던 일들과 배운 내용들을 기록해보려 한다.
나중에 프로젝트 진행사항 문서 작성할 때 도움이 되지 않을까.. 하고..
제안서 초안 발표는 추석 전 끝났고 이번주까지 제안서 수정본을 제출해야하며 내일은 요구사항 분석서에 대한 수업을 들을 예정




현지가 만든 귀염뽀짝한 기획 PPT

image image




미디어파이프(mediapipe)

https://mediapipe.dev/

구글에서 제공하는 비디오, 오디오와 같은 형식의 데이터 처리할 수 있는 머신러닝 방식의 프레임워크 솔루션
실시간 신체 움직임 추적, 얼굴 인식, 홍채 인식 등 다양한 머신러닝 솔루션 사용 가능

그 중 우리가 주로 사용할 부분은 HandTracking image image 이렇게 마다마다 포인트를 찾아주며 각 포인트는 손가락 위치에 따라 번호가 매겨져 있다.

파이썬 개발환경 세팅

파이썬 미디어파이프 라이브러리 설치할려고 했으나 며칠동안 밤을 셌지만 결국은 대 실 패..
구글 코랩 이용해서 테스트라도 해보려했지만 코랩에서는 웹캠 사용이 안되서 이마저도 실패
물론 코랩에서 웹캡을 쓸 수 있는 코드를 제공하긴 함

코드 스니펫 > camera capture image

코드 실행 image

이렇게 카메라가 돌아가고 저 위에 capture 버튼을 클릭하면 image

영상이 캡쳐되고 저장됨!

저 코드를 잘 활용해서 실시간으로 영상에 미디어파이프를 적용하려 했지만.. 잘 모르겠다.
진짜 미디어파이프 설치로 며칠을 날렸는지.. 구글링해도 자료가 별로 없어서 정말 머리 터지는줄 알았다.
후에 다른 팀원분께 들어보니 미디어파이프 설치하는게 꽤나 어렵다고 한다. 역시 내가 설치를 못하는데는 이유가 있었어..

그래서 방향을 바꿨다.. 노드로..!

노드 개발환경 세팅

파이썬 라이브러리 설치가 안되서 그냥 한번 해볼까? 하고 npm으로 미디어파이프를 설치해보았다.
하나하나 쉬운게 없었던 파이썬과 달리,, 노드는 모든것이 아주 순조로웠다 ^^..
처음에 @tensorflow-models/handpose 이용하려고 예제 코드를 따라했는데 잘 안되서 고민하던 중 현지가 보내준 미디어파이프 데모사이트를 보고 불현듯 미디어파이프를 그냥 쓰면 되지 왜 내가 텐서플로우를..? 하고 생각났다.

📄 현지가 보내준 데모 페이지 https://storage.googleapis.com/tfjs-models/demos/handtrack/index.html

📄 내가 참고한 미디어파이프 공식 문서(역시 모를땐 공식문서가 답..)
Mediapipe > Docs > Solutions > Hands > Javascript Solution API https://google.github.io/mediapipe/solutions/hands.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
</head>
<body>
  <div class="container">
    <video class="input_video"></video>
    <canvas class="output_canvas" width="1280px" height="720px"></canvas>
  </div>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<script type="module">
const videoElement = document.getElementsByClassName('input_video')[0];
const canvasElement = document.getElementsByClassName('output_canvas')[0];
const canvasCtx = canvasElement.getContext('2d');

function onResults(results) {
  canvasCtx.save();
  canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
  canvasCtx.drawImage(
      results.image, 0, 0, canvasElement.width, canvasElement.height);
  if (results.multiHandLandmarks) {
    for (const landmarks of results.multiHandLandmarks) {
      drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS,
                     {color: '#00FF00', lineWidth: 5});
      drawLandmarks(canvasCtx, landmarks, {color: '#FF0000', lineWidth: 2});
    }
  }
  canvasCtx.restore();
}

const hands = new Hands({locateFile: (file) => {
  return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
}});
hands.setOptions({
  maxNumHands: 2,
  minDetectionConfidence: 0.5,
  minTrackingConfidence: 0.5
});
hands.onResults(onResults);

const camera = new Camera(videoElement, {
  onFrame: async () => {
    await hands.send({image: videoElement});
  },
  width: 1280,
  height: 720
});
camera.start();
</script>

구글링 참고한 node 실행 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import * as http from 'http';
import * as fs from 'fs';
import path from 'path';

const __dirname = path.resolve();

var app = http.createServer(function(request,response){
    var url = request.url;
    if (request.url == '/') {
      url = '/index.html';
    }
    if (request.url == '/favicon.ico') {
      return response.writeHead(404);
    }
    response.writeHead(200);
    response.end(fs.readFileSync(__dirname + url));
});

app.listen(3001);

image 결국 성공!

노드 미디어파이프 모듈 손동작 감지

원래 파이썬으로 하고싶어서 찾아보다가 발견한 글
📄 Mediapipe를 이용한 제스쳐인식 https://developeralice.tistory.com/10

위의 글에서 나온 파이썬 코드를 내 자바스크립트 코드에 맞춰 바꾸고 적용하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<script type="module">
const videoElement = document.getElementsByClassName('input_video')[0];
const canvasElement = document.getElementsByClassName('output_canvas')[0];
const canvasCtx = canvasElement.getContext('2d');

// check handpose
const compareIndex = [[18, 4], [6, 8], [10, 12], [14, 16], [18, 20]] // 비교할 손가락 point
var open = [false, false, false, false, false] // 각 손가락의 펼쳐짐 혹은 접힘 여부

// 지정된 제스처
const gesture = [[true, true, true, true, true, "Hi!"], [false, true, true, false, false, "Yeah!"], [true, true, false, false, true, "SpiderMan!"]]

function dist(x1, y1, x2, y2) { // 두 점 사이 거리 비교하여 손가락의 접힘 여부 판단
  return Math.sqrt(Math.pow(x1-x2, 2)) + Math.sqrt(Math.pow(y1-y2, 2));
}

function onResults(results) {
  canvasCtx.save();
  canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
  canvasCtx.drawImage(
      results.image, 0, 0, canvasElement.width, canvasElement.height);
      
  if (results.multiHandLandmarks) {
    for (const landmarks of results.multiHandLandmarks) { // landmarks.length == 21 (0-20)
      for (var i=0; i<5; i++) {
        // true 손가락 펼쳐짐, false 손가락 접힘
          open[i] = dist(landmarks[0]['x'], landmarks[0]['y'], landmarks[compareIndex[i][0]]['x'], landmarks[compareIndex[i][0]]['y']) < dist(landmarks[0]['x'], landmarks[0]['y'], landmarks[compareIndex[i][1]]['x'], landmarks[compareIndex[i][1]]['y'])
      }

      // 지정된 제스처와 비교
      for (var i=0; i<3; i++) {
        var flag = true;
        for (var j=0; j<5; j++) {
          if (gesture[i][j] != open[j]) {
            flag = false
          }
        }
        if (flag == true) { // 지정된 제스처와 동일할 때
          console.log('result: ', gesture[i][5]);
        }
      }

      drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS, {color: '#00FF00', lineWidth: 5});
      drawLandmarks(canvasCtx, landmarks, {color: '#FF0000', lineWidth: 2});
    }
  }
  canvasCtx.restore();
}

const hands = new Hands({locateFile: (file) => {
  return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
}});

hands.setOptions({
  maxNumHands: 2,
  minDetectionConfidence: 0.5,
  minTrackingConfidence: 0.5
});

hands.onResults(onResults);

const camera = new Camera(videoElement, {
  onFrame: async () => {
    await hands.send({image: videoElement});
  },
  width: 1280,
  height: 720
});

camera.start();
</script>

동작 원리 image

compareIndex는 각 손가락이 펼쳐진 상태인지 접힌 상태인지 판단하기 위해 비교할 (해당 손가락에 위치한) 두 지점

  • 손바닥 끝점인 0의 좌표값과 각 점들의 좌표값을 이용하여 거리 계산
    • 끝 부분까지의 거리가 더 길면(b보다 a가 더 크면) 해당 손가락은 펼친 상태(true)
    • 안쪽의 점과 거리가 더 길다면(a보다 b가 더 크면) 손가락은 접힌 상태(false)
  • 손가락 5개의 접힘 혹은 펼쳐짐 여부를 담은 배열 open
    • 지정된 제스처와 open을 비교하여 같을 경우 해당 제스처를 한 것으로 인식

손가락을 다 폈을 경우(true, true, true, true, true) Hi! image

이번에는([true, true, false, false, true]) SpiderMan! image

캡스톤 다 끝났구먼~~~~~

결국 spring & python 이였던 개발환경은 node로 변경됐다.
spring으로 했으면 파이썬을 어떻게 실행할까 고민이였는데 노드는 tensorflow.js, mediapipe js를 바로 사용할 수 있어서 좋다.

노드 공부 다시 시작해야겠당..

노드를 하며 만난 에러들

  1. require is not defined
    import가 아닌 require()로 불러왔을 때 생기는 에러
    1
    2
    
     var http = require('http');
     var fs = require('fs');
    

    ReferenceError: require is not defined in ES module scope, you can use import instead
    This file is being treated as an ES module because it has a ’.js’ file extension and ’/Users/juran/dev/capstone_ex/node/package.json’ contains ”type”: ”module”.To treat it as a CommonJS script, rename it to use the ’.cjs’ file extension.

    require()가 브라우저(클라이언트) 측 javascript에 존재하지 않기 때문

    그냥 import 방식으로 바꿔줬다.

    1
    2
    
     import * as http from 'http';
     import * as fs from 'fs';
    

    이렇게 하였더니

    1
    
     response.end(fs.readFileSync(__dirname + url));
    

    위 부분에서 __dirname을 바로 사용할 수 없는 오류가 발생
    CommonJS에서 사용하던 __dirname 변수가 ES 모듈에서는 없기 때문

    1
    2
    
     import path from 'path';
     const __dirname = path.resolve();
    

    그래서 직접 정의해주었다

  2. Cannot use import statement outside a module
    ReferenceError: require is not defined in ES module scope, you can use import instead
    This file is being treated as an ES module because it has a ’.js’ file extension and ’/Users/juran/dev/capstone_ex/node/package.json’ contains ”type”: ”module”. To treat it as a CommonJS script, rename it to use the ’.cjs’ file extension.

    commonjs는 package.json의 기본 모듈 처리 방식을 require()로 처리, 이 때문에 import 부분에서 에러 발생

    package.json 마지막에 type을 module로 지정하는 설정 추가

    1
    
     "type": "module"
    
  3. document is not defined
    이거는 다른 예제 코드들 실행하다 마주친 문제
    노드에서는 기본적으로 브라우저에서 사용되는 DOM에 관한 객체들이 존재하지 않는다.

    내가 실행했던 예제들은 노드로 js 단독 실행이 아닌 브라우저를 통한 javascript 실행이 필요했었나보다..
    jsdom 라이브러리를 이용하여 DOM 객체를 설정해주면 된다고 하는데 이런저런 방법 사용해보다 실패 ㅠㅠ

더 많은 오류가 있었는데 기록하면서 하지 않았더니 기억이 안나네..



아직까지 자바스크립트의 동작 방식이나 특히 ES 부분을 잘 모르는 것 같다. 화살표 함수 정말 낯설어..
노드로 개발해야되기도 하고 자바스크립트는 많이 쓰니까 차근차근 공부해가며 알아가야겠다.
졸려.. 오늘의 일지 끝!

This post is licensed under CC BY 4.0 by the author.

-

캡스톤일지 | ~ 10.04 생활코딩으로 간단히 살펴본 TensorflowJS