개요

  • 하나의 파일, 하나의 프로젝트 내에서 함수의 개수가 점점 많아지고 유사한 기능이 점점 많아지다보면, 함수명을 짓는게 힘듦.
  • 특히 상속 받은 함수, 클래스 내 고유한 함수를 구분해주어야 하는 등 속성 측면에서도 함수명을 보다 구체적이고 구분 되도록 지어야 함.
  • 따라서 이번 프로젝트 (hand-pose-detection, face-landmarks-detection, pose-detection) 에서는 함수를 구분하기 위해 ①상속 받은 함수는 부모 클래스와 동일한 함수명 ②static 키워드 ③_함수명 을 가장 많이 사용하였음
  • 이하에서는 위 방법 중 ②, ③ 에 대해 소개함. 

 


 

1. static 

 

정적 메서드와 정적 프로퍼티

 

ko.javascript.info

  • 핵심 코드
static detect(imageData) {
  if (!this._detector) {
      this._createDetector().then(detector => {
          this._detector = detector;
          console.log("model loading success!, detector: ", this._detector);
      })
  }
  //...//
}
  • 의미, 기능
    • 정적 메서드
    • 클래스의 prototype이 아닌 함수 자체에 메서드를 설정 → 양자는 메서드 접근 방법에서 차이가 있음
const HandposeDetector = require('./detector-handpose');

// prototype에 메서드를 설정 : 생성자 호출 -> 메서드 호출  
const handposeDetector = new HandposeDetector();
handposeDetector.detect(imageData);

// 함수 자체에 메서드를 설정 : 클래스명.메서드명으로 메서드 호출
HandposeDetector.detect(imageData);
  • HandposeDetector.detect(imageData) 이 코드 같은 메서드 접근 방법에서도 알 수 있듯, 정적 메서드는 프로퍼티 형태로 직접 할당하는 것처럼 메서드를 정의
    (마치 프로퍼티에 접근하는 것처럼 메서드를 호출)
  • 정적 메서드도 상속됨. 따라서 자식클래스명.함수명 형태로 호출 가능
  • 사용 이유 : 데이터를 클래스 수준에 저장하고 싶을 때
    • 프로젝트에 적용
    • HandposeDetector, FaceDetector, PoseDetector 같은 커스텀 detector를 만들 때, Detector 클래스를 상속 받아 만들었음
    • 그래서 상속 받은 함수와 커스텀 detector 만의 함수를 구분 지을 필요 有, 따라서 초기에 static으로 구분
  • 정적 프로퍼티도 static 프로퍼티 정의 형태로 사용하면 됨

 


 

2. _함수명

  • 예시 코드
_createDetector() {
  const model = SupportedModels.MediaPipeHands;
  const detector = createDetector(model, {
      runtime: "tfjs",
      modelType: "lite",
      maxHands: 2, // or 2~10.
      flipHorizontal: false,
      staticImageMode: false,
      detectorModelUrl:
          "/static/tensorflow-models/tfjs-model_handpose_3d_detector_lite_1/model.json",
      landmarkModelUrl:
          "/static/tensorflow-models/tfjs-model_handpose_3d_landmark_lite_1/model.json",
  });
  return detector;
}
  • HandposeDetector, FaceDetector, PoseDetector 같은 커스텀 detector를 만들 때, Detector 클래스를 상속 받아 만들었음
  • 그래서 상속 받은 함수와 커스텀 detector 만의 함수를 구분 지을 필요 有
  • 코딩 초기 당시 static 키워드를 활용하여 구분하였음. 그러나 이 방법은 중간 중간 해당 함수 호출할 때, 클래스명.함수명 으로 호출해야하여 번거롭다는 단점
  • 따라서 사수께서 추천해주신 방법은 _함수명() 임. 위처럼 함수를 정의하고 호출할 때는 this._createDetector() 로 호출하면 됨

개요

  • 이번 프로젝트에서는 ①클래스를 상속 받아 기능 확장하여, ②Tensorflow-models 모델의 전형적인 구조에 따라 핵심 코드를 작성하였음
  • 상속 
    • 의미 : 부모 클래스의 프로퍼티, 메서드를 자식 클래스가 물려받는 것
    • 기능 : 부모 클래스 기능을 자식 클래스에서 확장시킬 수 있음.
    • ES6 모듈 개발 전 : 프로토타입 체인 방식으로 상속, 그러나 이는 가독성 떨어짐.
    • ES6 모듈 개발 후 : Class + extends 키워드를 사용하여 상속, 상속하는 것 확실히 티 낼 수 있게 됨
  • 이번 프로젝트에서의 상속
    • Detector 클래스
class Detector {
  constructor () { }

// 그 외 내용: 자바스크립트에서의 getter and setter
// https://ko.javascript.info/property-accessors
  static get DIMENSIONS () {  // 
      return [480, 360];
  }
  enable () {
      this._enable = true;
  }
  disable () {
      this._enable = false;
      this._result = null;
  }
  detect (imageData) { }
  isExistContent (result) {
      return result && result.length > 0;
  }
  draw (canvas) { }
}
module.exports = Detector;

 

  • HandposeDetector 클래스
class HandposeDetector extends Detector {
  constructor() {
      super();
      this._result;
  }

  enable() {
      super.enable();
      this._canvas = document.createElement("canvas");
      this._canvas.width = Detector.DIMENSIONS[0];
      this._canvas.height = Detector.DIMENSIONS[1];
      this._context = this._canvas.getContext("2d");
  }

  disable() {
      super.disable();
      delete this._context;
      delete this._canvas;
      this._context = null;
      this._canvas = null;
  }

  _createDetector() {
      const model = SupportedModels.MediaPipeHands;
      const detector = createDetector(model, {
          runtime: "tfjs",
          modelType: "lite",
          maxHands: 2, // or 2~10.
          flipHorizontal: false,
          staticImageMode: false,
          detectorModelUrl:
              "/static/tensorflow-models/tfjs-model_handpose_3d_detector_lite_1/model.json",
          landmarkModelUrl:
              "/static/tensorflow-models/tfjs-model_handpose_3d_landmark_lite_1/model.json",
      });

      console.log("model loading success!, detector: ", this._detector);
      return detector;
  }
  
  detect(imageData) {
      if (!this._detector) {
          this._createDetector().then(detector => {
              this._detector = detector;
              console.log("model loading success!, detector: ", this._detector);
          })
      }

      return new Promise((resolve, reject) => {   
          this._detector.estimateHands(imageData).then(result => {
              this._result = result;
              console.log(`this._result: `, this._result);

              this._result.forEach((res) => {
                  console.log(`${res.handedness} hand keypoints:`);
                  res.keypoints.forEach((keypoint, i) => {
                      let x = keypoint.x - 320;
                      let y = keypoint.y - 240;
                      console.log(`Keypoint ${i}: [${x}, ${y}]`);
                  })
              })
              resolve(this._result);
          }).catch(e => {
              reject(e);
          });
      })
  }
/**
중략
**/
}
module.exports = HandposeDetector;

 


 

개념 정리

출처 : https://ko.javascript.info/class-inheritance

1. class와 prototype

  • 부모 클래스 Animal과 객체 animal
class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  run(speed) {
    this.speed = speed;
    alert(`${this.name} 은/는 속도 ${this.speed}로 달립니다.`);
  }
  stop() {
    this.speed = 0;
    alert(`${this.name} 이/가 멈췄습니다.`);
  }
}

let animal = new Animal("동물");
  • 객체 animal과 클래스 Animal의 관계

  • Animal vs Animal.prototype
    Animal : Class Definition Animal.prototype : Prototype-Based Definition
    class 키워드로 정의한 클래스 객체의 프로토타입을 확장하여 메서드와 속성을 추가한 것
    ES6 이후 문법 ES6 이전 문법
    클래스 = 메서드(run, stop) + 속성(speed, name)  
    클래스는 생성자(constructor)를 가질 수 있음  
    • constructor
      • 함수 자신, 따라서 위 그림에서 constructor : Animal 로 표시됨
        (∵ 자바스크립트에서 클래스⊂함수, 클래스는 생성자 함수로써 내부적으로 생성자 함수와 프로토타입을 생성하기 때문)
      • 만약 Animal 함수의 프로퍼티 ‘prototype’에 constructor 객체 하나만 있다면 ‘디폴트 프로퍼티가 있다’고 표현 가능
        (디폴트 프로퍼티 : 개발자가 별도로 할당하지 않아도 기본적으로 가지는 프로퍼티)
      • 기능적으로 클래스에서 객체를 생성하고 초기화하기 위해 사용하는 메서드라고 볼 수 있음. 클래스를 통해 새로운 객체를 생성할 때 자동 호출됨

 


 

2. extends키워드로 상속 받기

  • 자식 클래스 Rabbit
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} 이/가 숨었습니다!`);
  }
}

let rabbit = new Rabbit("흰 토끼");

rabbit.run(5); // 흰 토끼 은/는 속도 5로 달립니다.
rabbit.hide(); // 흰 토끼 이/가 숨었습니다!
  • 클래스 Rabbit을 이용하여 만든 객체 rabbit
    : rabbit은 rabbit.hide() 로 Rabbit 클래스의 메서드에도, rabbit.run() 으로 Animal의 메서드에도 접근 가능
  • extends는 프로토타입을 기반으로 동작
    • 따라서 extends는 Rabbit.prototype.[[Prototype]]Animal.prototype으로 설정함
    • Rabbit.prototype에서 메서드를 찾지 못하면 Animal.prototype에서 메서드를 가져옴

 

  • 프로젝트에 적용
class HandposeDetector extends Detector { }

 


 

3. 메서드 오버라이딩(Method Overriding) : super 키워드 활용

  • 의미 : 부모 클래스의 메서드재정의하여 자식 클래스에서 사용하는 것
    → 재정의 : 일부 기능만 변경 or 기능 확장
  • super 키워드 활용
    • super(…) : 부모 생성자 자체 호출, 따라서 자식 생성자 내부에서만 사용 가능
    • super.method명(…) : 부모 클래스의 method를 호출
  • 핵심 코드 : 부모 클래스의 stop() method를 오버라이딩한 rabbit.stop() 정의
class Rabbit extends Animal {
  hide() {
    alert(`${this.name}가 숨었습니다!`);
  }

  stop() {
    super.stop(); // 부모 클래스의 stop을 호출해 멈추고,
    this.hide(); // 숨습니다.
  }
}

let rabbit = new Rabbit("흰 토끼");
rabbit.run(5); // 흰 토끼가 속도 5로 달립니다.
rabbit.stop(); // 흰 토끼가 멈췄습니다. 흰 토끼가 숨었습니다!
  • 프로젝트에 적용
enable() {
      super.enable();
      this._canvas = document.createElement("canvas");
      this._canvas.width = Detector.DIMENSIONS[0];
      this._canvas.height = Detector.DIMENSIONS[1];
      this._context = this._canvas.getContext("2d");
  }

 


 

4. 생성자 오버라이딩(Constructor Overriding)

  • 의미 : 부모 클래스의 생성자재정의하여 자식 클래스에서 사용하는 것
  • super 키워드 활용
    • super 키워드가 상속 클래스의 생성자 함수가 실행되면, 부모 클래스의 생성자가 ①빈 객체를 만들고 ②this에 이 객체가 할당되도록 함
    • 따라서 super 키워드가 없으면 this가 될 객체가 생성되지 않아 자식 클래스 생성자 실행 시 오류 발생함
  • 핵심 코드
class Rabbit extends Animal {
  constructor(name, earLength) {
		super(name);
    this.earLength = earLength;
  }
  // ...
}
  • 프로젝트에 적용
class HandposeDetector extends Detector {
  constructor() {
      super();
      this._result;
  }
}

 


 

배운점 (요약)

  • 이번 프로젝트에서는 ①클래스를 상속 받아 기능 확장하여, ② Tensorflow-models 모델의 전형적인 구조 에 따라 핵심 코드를 작성하였음
  • 실제 코드를 작성할 때는 정확히 무슨 뜻인지 모르고, 용례를 보고 이런 느낌이구나 라고 생각하며 사용했음
  • 그러나 프로젝트를 마무리하며 개념 정리를 하니, 밑그림만 있던 것이 구체적인 채색까지 되는 느낌이었음. 따라서 향후 활용할 수 있는 자신감이 생겼음. 또한 효율적, 효과적인 코드를 위해 상속 및 오버라이딩은 필수 요소라고 생각했음.

npm link 에 대한 개념은 이전 글에서 정리하여 생략하였습니다!

2023.10.17 - [Backend/Etc : Setting | Git] - [Setting] 내부 서버로부터 파일 clone | npm link | build

 


 

문제 인식

  • 1번째 작업 : GUI의 develop 브랜치(root)와 VM의 feature/tfjs-handpose 브랜치에서는 빌드 성공 + 내가 작성한 코드가 반영 잘 됨
  • 그러나 2번째 작업 : face-detection을 적용하기 위한 feature/tfjs-face 브랜치에서 작업 후, 다시 feature/tfjs-handpose로 돌아와 빌드하면 빌드 실패
  • 오류 내용
node:internal/modules/cjs/loader:1084 Uncaught Error: Cannot find module '@mediapipe/hands'
Require stack:
C:\Workspace\AI\Handpose\ai_roborobo\node_modules@tensorflow-models\hand-pose-detection\dist\mediapipe\detector.js
C:\Workspace\AI\Handpose\ai_roborobo\node_modules@tensorflow-models\hand-pose-detection\dist\create_detector.js
C:\Workspace\AI\Handpose\ai_roborobo\node_modules@tensorflow-models\hand-pose-detection\dist\index.js
C:\Workspace\AI\Handpose\ai_roborobo\index.html at node:internal/modules/cjs/loader:1084:15 at Function._resolveFilename (node:electron/js2c/renderer_init:2:5527) at node:internal/modules/cjs/loader:929:27 at Function.<anonymous> (node:electron/js2c/asar_bundle:2:13327) at Function._load (node:electron/js2c/renderer_init:2:4757) at Module.require (node:internal/modules/cjs/loader:1150:19) at require (node:internal/modules/cjs/helpers:121:18) at Object.<anonymous> (C:\Workspace\AI\Handpose\ai_roborobo\node_modules@tensorflow-models\face-detection\dist\mediapipe\detector.js:56:21) at Object.<anonymous> (C:\Workspace\AI\Handpose\ai_roborobo\node_modules@tensorflow-models\face-detection\dist\mediapipe\detector.js:210:3) at Module._compile (node:internal/modules/cjs/loader:1271:14)
  • GUI에서 최종 빌드를 진행하기 때문에, 위 오류는 결국 GUI에서 @mediapipe/hands 모듈을 인식하지 못한다는 소리가 됨
  • GUI는 ①node_modules/@tensorflow-models 에서 hand-pose-detection 모델을 인식하고, ②node_modules/scratch-vm/~ 에서 VM과 관련된 코드를 인식함. 이 때 둘은 npm link로 이어져 있음
  • 그러나 나는 VM에 @mediapipe/hands 모듈을 설치해놓은 상태였음에도 GUI가 인식하지 못함
  • 따라서 npm link의 문제라고 생각함

 


 

1. npm link 의미(용도)

  • Symbolic link ( 줄여서 Symlink) = 바로가기 파일
  • GUI에서 npm link VM 을 실행하면, 외부 모듈(VM)이 현재 프로젝트(GUI)의 node_modules 디렉토리에 설치된 것처럼(npm install한 것처럼) 사용될 수 있게 해줌
  • 즉 해당 모듈(GUI)에 VM에 대한 전역 심볼릭 링크를 설정하는 것

 


 

2. npm link 사용법

  • 위 오류 해결 방법 후보
    1. 프로젝트 변경될 때마다 (VM에 브랜치 생성할 때마다), GUI의 develop 브랜치에 npm link
    2. 프로젝트 변경될 때마다 (VM에 브랜치 생성할 때마다), GUI에 브랜치 생성하여 npm link
  • 내 생각 1: GUI root 디렉토리를 계속 건드리는게 좋지 않아 보임, 따라서 후보2를 실행해보려고 함
  • 그러나 npm link는 브랜치 단위가 아닌 프로젝트 단위(디렉토리 단위)로 생성
    • 따라서 후보2를 실행해도 브랜치별로 다른 npm link가 걸리는 것이 아니라, 설정된 npm link가 계속 변경됨
    • 따라서 후보1 실행하기로 함
  • 핵심 코드
// blocks
npm link l10n

// l10n
npm link l10n

// VM
npm link l10n blocks --force  // peer dependency conflict 방지

// GUI
npm link l10n blocks VM
  • npm link를 프로젝트가 작은 것 → 큰 것 순서로 진행하는 느낌
  • --force 또는 --legacy-peer-deps 는 peer dependency conflict 를 방지하기 위해 실행 

 


 

해결법

프로젝트 변경될 때마다 (VM에 브랜치 생성할 때마다), GUI의 develop 브랜치에 npm link 하고 build

개요

어제 회사에서 향후 주어질 업무에 대해 차장님과 논의했다. 아마 나는 자사 제품 기능을 Node.js 로 일부 개발 + AI 개발하게 될 것 같다. 일단 너무 좋았다! 백엔드 + AI 조합 ㅎㅎ 그래서 본격적인 업무 시작 전 pc 환경설정을 하는 과정에서 다른 팀원 분들과 Node.js, npm 버전을 맞추어야 했고, 목표 버전은 Node.js는 v16.20.0, npm은 9.6.4이다. 이 때 npm은 Node Package Manager의 약자로 자바스크립트 코드 언어를 위한 노드 패키지를 관리해 주는 툴이다. 

 


 

1. Node.js : cmd를 이용하여 업데이트 하는 방법

npm을 이용
  • n 패키지를 이용한다. 구체적인 코드는 다음과 같다. 
npm install -g n  //global로 설치해 주어야 Node Vesion을 root에서 관리할 수 있다.

n stable  // 안정 버전 설치
n latest  //  최신 버전 설치
n lts  // lts 버전 설치
n x.x.x  // 특정 버전 설치 ( x.x.x 버전 )
n prune  // 이전 버전들 삭제해 주는 명령어

 

nvm( Node Vesion Manager )을 이용
  • nvm 자체를 이용한다. 구체적인 코드는 다음과 같다. 
nvm install [ version ]  // 특정 버전 설치
nvm install node           // 최신 버전 설치
nvm uninstall [ version ]  // 이전 버전을 삭제하는 명령어
nvm alias default [version]  // 여러 버전을 설치하고 필요한 환경으로 지정해 주는 명령어

 


 

2. npm : cmd를 이용하여 업데이트 하는 방법

위에서 Node.js 버전 업데이트가 npm을 이용하는 것이 있어서 사실 나는 npm 버전부터 업데이트 하려고 했다. 구체적인 코드는 다음과 같다.

npm install -g npm@9.6.4   // 특정 버전 설치 ( x.x.x 버전 )
npm install -g npm@latest  // 최신 버전 설치

 

꼬리 문제 

npm이 정상적으로 업데이트 되지 않았다. 검색하고, 생각해봐도 합리적인 원인을 찾기 어려웠다. 그 때 차장님께서 그냥 공식홈페이지 가서 설치하는 것이 빠르다고 조언을 해주셨다...ㅋㅋㅋㅋㅋㅋ 그래서 공홈 가서 릴리즈된 버전을 살펴보니 내가 설치하려 했던 npm 버전이 현재 내 Node.js 버전과 맞지 않았고, 그래서 설치가 되지 않았던 것이었다. 따라서 Node.js는 16.20.2 로 바꿔서 , npm은 9.6.4로 그대로 설치하기로 했다.  

 


 

3. 공식 홈페이지를 통한 업데이트

Node.js 공식 홈페이지로 들어가 OS와 버전을 확인하고 다운로드 받으면 된다. 다만, Node.js 를 설치하면 그에 맞는 npm이 자동 설치되므로 나중에 cmd로 npm 버전을 업데이트 해주어야 한다. 

 


 

4. 업데이트 후 버전 확인

버전 확인 결과

 


 

참고 자료

https://aiday.tistory.com/63

2023.10.18 3번째 완료

 

+ Recent posts