개요

 


 

1. 모델 설명 | Used to

모델 설명
  • 모델명: MediaPipe BlazePose GHUM 3D
  • 구체적인 설명
Lite (3MB size), Full (6 MB size) and Heavy (26 MB size) models, to estimate the full 3D body pose of an individual in videos captured by a smartphone or web camera.
Optimized for on-device, real-time fitness applications: Lite model runs ~44 FPS on a CPU via XNNPack TFLite and ~49 FPS via TFLite GPU on a Pixel 3. Full model runs ~18 FPS on a CPU via XNNPack TFLite and ~40 FPS via TFLite GPU on a Pixel 3.
Heavy model runs ~4FPS on a CPU via XNNPack TFLite and ~19 FPS via TFLite GPU on a Pixel 3.

 

Used to
Applications
3D full body pose estimation for single-person videos on mobile, desktop and in browser.

Domain & Users
 - Augmented reality
 - 3D Pose and gesture recognition
 - Fitness and repetition counting
 - 3D pose measurements (angles / distances)

Out-of-scope applications
Multiple people in an image.
 - People too far away from the camera (e.g. further than 14 feet/4 meters)
 - Head is not visible Applications requiring metric accurate depth
 - Any form of surveillance or identity recognition is explicitly out of scope and not enabled by this technology

 


 

2. Model type 및 Model architecture

모델 카드 인용
Convolutional Neural Network: MobileNetV2-like with customized blocks for real-time performance.

 

Model type: CNN

 

Model architecture : MobileNetV2

 


 

3. input, output

Inputs
Regions in the video frames where a person has been detected.
Represented as a 256x256x3 array with aligned human full body part, centered by mid-hip in vertical body pose and rotation distortion of (-10, 10) . Channels order: RGB with values in [0.0, 1.0].

Output(s)
33x5 array corresponding to (x, y, z, visibility, presence).
 - X, Y coordinates are local to the region of interest and range from [0.0, 255.0].
 - Z coordinate is measured in "image pixels" like the X and Y coordinates and represents the distance relative to the plane of the subject's hips, which is the origin of the Z axis. Negative values are between the hips and the camera; positive values are behind the hips.
Z coordinate scale is similar with X, Y scales but has different nature as obtained not via human annotation, by fitting synthetic data (GHUM model) to the 2D annotation. Note, that Z is not metric but up to scale.
 - Visibility is in the range of [min_float, max_float] and after user-applied sigmoid denotes the probability that a keypoint is located within the frame and not occluded by another bigger body part or another object.
 - Presence is in the range of [min_float, max_float] and after user-applied sigmoid denotes the probability that a keypoint is located within the frame.
[
  {
    score: 0.8,
    keypoints: [
      {x: 230, y: 220, score: 0.9, score: 0.99, name: "nose"},
      {x: 212, y: 190, score: 0.8, score: 0.91, name: "left_eye"},
      ...
    ],
    keypoints3D: [
      {x: 0.65, y: 0.11, z: 0.05, score: 0.99, name: "nose"},
      ...
    ],
    segmentation: {
      maskValueToLabel: (maskValue: number) => { return 'person' },
      mask: {
        toCanvasImageSource(): ...
        toImageData(): ...
        toTensor(): ...
        getUnderlyingType(): ...
      }
    }
  }
]

 


 

4. evaluation metric

pose의 metric : PDJ
  • average Percentage of Detected Joints: 감지된 부위의 평균 비율 오차
We consider a keypoint to be correctly detected if predicted visibility for it matches ground truth and the absolute 2D Euclidean error between the reference and target keypoint normalized by the 2D torso diameter projection
is smaller than 20%.
This value was determined during development as the maximum value that does not degrade accuracy in classifying pose / asana based solely on the key points without perceiving the original RGB image. The model is providing 3D coordinates, but the z-coordinate is obtained from synthetic data, so for a fair comparison with human annotations, only 2D coordinates are employed.

 

evaluation results
  • geographical
Evaluation across 14 regions of heavy, full and lite models on smartphone back-facing camera photos dataset results an average performance of 94.2% +/- 1.3% stdev with a range of [91.4%, 96.2%] across regions for the heavy model, an average performance of 91.8% +/- 1.4% stdev with a range of [89.2%, 94.0%] across regions for the full model and an average performance of 87.0% +/-2.0% stdev with a range of [83.2%, 89.7%] across regions for the lite model.
Comparison with our fairness criteria yields a maximum discrepancy between average and worst performing regions of 4.8% for the heavy, 4.8% for the full and 6.5% for the light model.
  • skin tone and gender
Evaluation on smartphone back-facing camera photos dataset results in an average performance of 93.6% with a range of [89.3%, 95.0%] across all skin tones for the heavy model, an average performance of 91.1% with a range of [85.9%, 92.9%] across all skin tones for the full model and an average performance of 86.4% with a range of [80.5%, 87.8%] across regions for the lite model. The maximum discrepancy between worst and best performing categories is 5.7% for the heavy model, 7.0% for the full model and 7.3% for the lite model.
Evaluation across gender yields an average performance of 94.8% with a range of [94.2%, 95.3%] for the heavy model, an average performance of 92.3% with a range of [91.2%, 93.4%] for the full model, and an average of 83.7% with a range of [86.0%, 89.1%] for the lite model.
The maximum discrepancy is 1.1% for the heavy model, 2.2% for the full model and 3.1% for the lite model.

문제 인식

  • 정지 버튼을 누르면 캔버스에 그려졌던 손, 얼굴, 포즈의 keypoints와 path가 남아있는 현상
  • 사수께서 clearRect() 함수가 정상적으로 적용되지 않았다고 알려주심

 


 

cleatRect() 함수 사용법

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(20, 20, 200, 100);
ctx.clearRect(40, 40, 50, 50);
  • 결과물 : 빨간색 context 내부에 설정한 크기의 사각형의 색이 지워졌음

  • 그러나 이 코드가 개별 detector 함수에 들어가면 안 됨 (∵ 향후 다른 detector 함수와 함께 실행하였을 때 오류 가능성)
  • 따라서 detector 관련 코드를 모두 관리하는 detector-manager 코드에서 clearRect() 함수를 적용해야 함

 


 

해결법

  • draw 하여 캔버스에 비디오를 그리기 전에 clearRect 하여 이전의 캔버스를 지워야함
  • 지속적으로 렌더링되어 캔버스에 이미지가 그려지기 때문
if (result) {
  if (dt.drawBox) {
      dt.context.clearRect(0,0,Detector.DIMENSIONS[0],Detector.DIMENSIONS[1]);
      dt.detector.draw(dt.canvas);
      this._renderer.updateBitmapSkin(dt.skinId, dt.canvas, 1);
  } 
} else {
  if (dt.drawBox && hasResult) {
      dt.context.clearRect(0,0,Detector.DIMENSIONS[0],Detector.DIMENSIONS[1]);
      this._renderer.updateBitmapSkin(dt.skinId,dt.canvas,1);
  }
}

문제 인식

  • 일렉트론으로 구현해보았을 때는 이하 코드처럼 라이브 비디오 자체를 매개변수로 예측하였음.
const hands = await detector.estimateHands(camera.video, { flipHorizontal: false });
  • 그러나 자사 소프트웨어 기존 코드에서는 estimate 함수의 매개변수가 이미지임. 따라서 기존 방식을 따라 가야 했음

 


 

1. Tensorflow.js 공식 깃헙

  • 매개변수의 확인 → 이미지와 비디오 둘 다 가능!
  • 따라서 이미지로 estimate 함수 사용하여 일렉트론으로 구현해보고자 함
// 이미지
const result = await detector.estimateHands(image, {
  staticImageMode: true
} as handPoseDetection.MediaPipeHandsTfjsEstimationConfig);

// 비디오
const hands = await detector.estimateHands(video, null /* config */);

 


 

2. 일렉트론으로 구현 

  • 핵심 로직
    1. ctx.drawImage(video, 0, 0, canvas.width, canvas.height) 
      : 웹 캠에서 받아온 비디오 현재 프레임을 캔버스에 표시
    2. ctx.getImageData(0, 0, canvas.width, canvas.height)
      : 캔버스의 이미지 데이터를 가져옴 (캡처)
  • 핵심 코드 
_captureImageData(video) {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  canvas.width = video.videoWidth;  
  canvas.height = video.videoHeight;
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);  
  return ctx.getImageData(0, 0, canvas.width, canvas.height);
}

// estimate 함수에 적용
const detector = createDetector(SupportedModels.MediapipeHands);
const imageData = HandposeDetector.captureImageData(video);
const result = await detector.estimateHands(imageData);

 


 

해결법 (요약)

  • GUI 프로젝트 코드에 이미 video → image 로 바꾸는 로직이 작성되어 있었음
// drawImage()
if (lastUpdate + cacheTimeout < now) {
  try {
      if (mirror) {
          context.scale(-1, 1);
          context.translate(width * -1, 0);
      }
      context.drawImage(this._element,
          // source x, y, width, height
          0, 0, elementDimension.width, elementDimension.height,
          // dest x, y, width, height
          0, 0, width, height
      );
      context.setTransform(1, 0, 0, 1, 0, 0);
      workspace.lastUpdate = now;
  } catch {
      return null;
  }
}

// getImageData()
if (formatCache.lastUpdate + cacheTimeout < now) {
	if (format === CameraProvider.FORMAT_IMAGE_DATA) {
	    formatCache.lastData = context.getImageData(0, 0, width, height);
}

 

  • 나는 이미 정해져 있는 detect 함수의 매개변수인 imageData를 가져와 처리함
detect(imageData) {
	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);
    });
	})
}

 

 

 

개요

 


 

1. npm install 해야 할 최소한의 모듈

<!-- 둘이 set-->
npm install @tensorflow-models/hand-pose-detection --legacy-peer-deps
npm install @mediapipe/hands --legacy-peer-deps

<!-- 위의 핵심 라이브러리를 실행할 수 있도록 하기 위한 peer dependency-->
npm install @tensorflow/tfjs --legacy-peer-deps
npm install @tensorflow/tfjs-converter --legacy-peer-deps
npm install @tensorflow/tfjs-core --legacy-peer-deps
  • 추가적으로 필요하다고 여겨지는 @tensorflow/tfjs-backend-webgl @tensorflow/tfjs-backend-wasm @tensorflow/tfjs-backend-webgpu 은 이하 peer dependency 에 이미 포함되어 있음
  • 특히 webgl을 tf.setBackend(’webgl’) 식으로 설정할 필요도 없음. 아래 설치하고 핵심 라이브러리를 require 하면 자동 설정됨
  • 주의할 점 : peer dependency 충돌 해결하는 글 보러 가기

개요

Tensorflow-models에 속해 있는 모델들의 전형적인 사용 구조를 설명하려고 함. 미래의 나를 위해... 이하 모든 글과 코드는 근본적으로 Tensorflow-models 공식 깃헙에 출처가 있음.

 


 

1. model loading : createDetector

_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;
}

 


 

2. detect

  • detector.estimateHands(video or image)
  • estimateFaces 등 다양한 시리즈 있음
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);
      });
  })
}

 


 

3. draw : draw(canvas) 등 매개 변수는 다양할 수 있음

  • drawKeypoint
  • drawPath
draw(canvas) {
    if (!canvas || !this.isExistContent(this._result)) return canvas;
    const ctx = canvas.getContext("2d");
    if (this._result) {
        this._result.forEach( (res) => {
            ctx.fillStyle = res.handedness === "Left" ? "Red" : "Blue";
            ctx.strokeStyle = "White";
            ctx.lineWidth = 2;
            res.keypoints.forEach(keypoint => {
                this._drawKeypoint(ctx, keypoint);
            });
            Object.keys(FINGER_INDICES).forEach(finger => {
                const points = FINGER_INDICES[finger].map(idx => res.keypoints[idx]);
                this._drawPath(ctx, points, false);
            });
        });
    }
    return canvas;
}

_drawKeypoint(ctx, keypoint) {
    ctx.beginPath();
    ctx.arc(keypoint.x - 2, keypoint.y - 2, 3, 0, 2 * Math.PI);
    ctx.fill();
}

_drawPath(ctx, points, closePath) {
    const region = new Path2D();
    region.moveTo(points[0].x, points[0].y);
    points.slice(1).forEach(point => region.lineTo(point.x, point.y));
    if (closePath) {
        region.closePath();
    }
    ctx.stroke(region);
}

 

 

이하 글은 Tensorflow-models 모델 로딩을 위해 공부하고 문제를 해결한 글입니다.

모델 버전 관리 - 1. model-loading 글 보러 가기 

2023.11.30 - [AI/TensorFlow | Tensorflow.js] - [Tensorflow-models] 모델 버전 관리 - 1. model loading

 


 

문제 인식

  • npm install 
    • npm install @tensorflow/tfjs npm install @tensorflow/tfjs-converter npm install @tensorflow/tfjs-core npm install @tensorflow-models/hand-pose-detection 등을 실행하여 npm 모듈을 설치할 때, peer dependency 충돌이 있으면 다음과 같은 오류가 발생함.
npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR! 
npm ERR! While resolving: @tensorflow-models/mobilenet@2.0.4
npm ERR! Found: @tensorflow/tfjs-converter@4.10.0
npm ERR! node_modules/@tensorflow/tfjs-converter
npm ERR!   peer @tensorflow/tfjs-converter@"^4.9.0" from @tensorflow-models/hand-pose-detection@2.0.1
npm ERR!   node_modules/@tensorflow-models/hand-pose-detection
npm ERR!     @tensorflow-models/hand-pose-detection@"^2.0.1" from the root project
npm ERR!   @tensorflow/tfjs-converter@"4.10.0" from @tensorflow/tfjs@4.10.0
npm ERR!   node_modules/@tensorflow/tfjs
npm ERR!     @tensorflow/tfjs@"^4.10.0" from the root project
npm ERR!   2 more (the root project, @tensorflow-models/face-detection)
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer @tensorflow/tfjs-converter@"~1.2.1" from @tensorflow-models/mobilenet@2.0.4
npm ERR! node_modules/@tensorflow-models/mobilenet
npm ERR!   @tensorflow-models/mobilenet@"2.0.4" from the root project
npm ERR!
npm ERR! Conflicting peer dependency: @tensorflow/tfjs-converter@1.2.11
npm ERR! node_modules/@tensorflow/tfjs-converter
npm ERR!   peer @tensorflow/tfjs-converter@"~1.2.1" from @tensorflow-models/mobilenet@2.0.4
npm ERR!   node_modules/@tensorflow-models/mobilenet
npm ERR!     @tensorflow-models/mobilenet@"2.0.4" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

 

  • npm 모듈 사용(실행)
    • 특정 모델을 로딩하여 예측 시도할 때 (코드 실행할 때), peer dependency 충돌이 있으면 다음과 같은 오류가 발생하기도 함
    • 그러나 오류 양상이 다양하므로 밑 종류 하나 만으로 한정할 수 없음
    • 따라서 ‘~찾을 수 없음’, ‘resolve 하다가 ~없어서 문제 발생함’ 등의 뉘앙스의 오류는 peer dependency로 인한 오류가 아닐까? 의심해야 함
client:159 ./node_modules/scratch-vm/node_modules/@tensorflow/tfjs-converter/dist/tf-converter.esm.js
Module build failed: Error: ENOENT: no such file or directory, open 'C:\Workspace\Rogic\scratch-gui\node_modules\scratch-vm\node_modules@tensorflow\tfjs-converter\dist\tf-converter.esm.js'

 

  • 주의할 점은 npm install 할 때를 제외하고, peer dependency로 인한 오류임에도 콘솔에서 직접적으로 알려주지 않는다는 것
    • 따라서 두번째 느낌으로 발생한 오류는 궁극적으로 peer dependency로 발생하는 오류일 수 있음을 인지해야 함
    • 여타 오류 해결 방법을 사용하기 전, 후에 peer dependency로 인한 오류가 아닐까? 의심해야 함

 


 

충돌 원인 : 실제 설치된 패키지 버전 ≠ peer dependency에서 요구하는 패키지 버전

  • peer dependency 의미
    • 친구 패키지 명 + 버전
    • 친구 패키지 : 해당 패키지를 정상적으로 활용하기 위해 필요한 다른 패키지
    • 즉 실제로 패키지에서 직접 require(import) 하지는 않더라도 호환성이 필요한 경우 명시하는 것
    • npm 3 버전까지는 peer dependency를 자동으로 설치해줌, 4 - 6 버전에서는 경고 메시지만 띄워줌, 7 버전부터는 peer dependency 버전이 맞지 않으면 설치 불가
      → 나의 npm - -version : 9.6.4 , 그렇다면 실제 설치된 버전 ≠ peer dependency 요구 버전이라면 해당 패키지 아예 설치 및 실행 불가? NO!
      → 해결법은 맨 마지막에!

 

  • peer dependency 확인
    • package-lock.json
"node_modules/@tensorflow-models/hand-pose-detection": {
    "version": "2.0.1",
    "resolved": "https://registry.npmjs.org/@tensorflow-models/hand-pose-detection/-/hand-pose-detection-2.0.1.tgz",
    "integrity": "sha512-zRA+jz2qOq5FMcyqSFxaQyi6T4YNbMbQhd6SQMI791FQ8yYj23kLgYa73g2NssR5AmM/2ATu9Vcjnf7LUrVLOQ==",
    "dependencies": {
        "rimraf": "^3.0.2",
        "tslib": "^2.6.1"
    },
    "peerDependencies": {
        "@mediapipe/hands": "~0.4.0",
        "@tensorflow/tfjs-backend-webgl": "^4.9.0",
        "@tensorflow/tfjs-converter": "^4.9.0",
        "@tensorflow/tfjs-core": "^4.9.0"
    }
  },
  • dependency vs dev dependency
    1. dependency
      • 앱에 종속된 가장 일반적인 종속성
      • 런타임빌드타임개발중 일 때 모두 필요
      • 따라서 앱이 빌드될 때 여기에 속해 있는 패키지들이 모두 번들에 포함되어 배포
    2. dev dependency
      • 빌드타임개발중 일 때 필요
      • 따라서 앱이 빌드될 때 도움 주거나 참조 되지만, 번들에 포함X

 


 

해결법 (요약)

  • npm 7 버전부터는 peer dependency 버전이 맞지 않으면 설치 불가
    • 그러나 무조건 peer dependency에서 얘기하는 딱! 그! 버전만 설치해야 패키지 실행할 수 있다는 것은 말이 안 됨 (사용자마다 프로젝트, pc 상황이 다를 수 있기 때문)
    • 해결 방법은 npm 공식 깃헙 블로그 기준 2가지가 있음 : --force 또는 --legacy-peer-deps
    • 더보기
      You have the option to retry with --force to bypass the conflict or --legacy-peer-deps command to ignore peer dependencies entirely (this behavior is similar to versions 4-6). - in npm github blog
  • 2가지 방법 요약
    --force --legacy-peer-deps
    충돌 우회 충돌 무시
    package-lock.json에 몇 가지의 다른 의존 버전들을 추가 peerDependency가 맞지 않아도 일단 설치
    • 이번 프로젝트에서는 주로 --legacy-peer-deps 를 활용하여 peer dependency 충돌 해결 (사수 제안)
    • 그러나 일반적인 경우 --force--legacy-peer-deps 로 적용해보는 것이 좋을 것 같음
      ( ∵ --force--legacy-peer-deps 보다 덜 강제적)

 


 

이번 프로젝트 (Tensorflow-models 구현) 에서 주의할 점

  • peer dependency를 설치한 경우, 해당 패키지가 실제 실행 가능한지 빌드 + 실행해서 확인 필수
    • 실행해보아야 하는 것 : ①모델 로딩(create detector) ②예측(estimateHands)
  • 해당 프로젝트의 경우 다음과 같은 버전 특징을 갖고 있었음
      hand-pose-detection face-landmarks-detection pose-detection
    peer dependency 권장 버전 "peerDependencies":
    { "@mediapipe/hands": "~0.4.0", "@tensorflow/tfjs-backend-webgl": "^4.9.0", "@tensorflow/tfjs-converter": "^4.9.0", "@tensorflow/tfjs-core": "^4.9.0" }
    "peerDependencies":
    { "@mediapipe/face_detection": "~0.4.0", "@tensorflow/tfjs-backend-webgl": "^4.4.0", "@tensorflow/tfjs-converter": "^4.4.0", "@tensorflow/tfjs-core": "^4.4.0" }
    "peerDependencies":
    { "@mediapipe/pose": "~0.5.0", "@tensorflow/tfjs-backend-wasm": "^4.10.0", "@tensorflow/tfjs-backend-webgl": "^4.10.0", "@tensorflow/tfjs-backend-webgpu": "^4.10.0", "@tensorflow/tfjs-converter": "^4.10.0", "@tensorflow/tfjs-core": "^4.10.0" }
    실제 실행 가능한 최소 버전 ( hand-pose-detection, face-landmarks-detection, pose-detection 최신 버전 기준) - tfjs, tfjs-core, tfjs-converter : ^3.3.0 - tfjs, tfjs-core, tfjs-converter : ^3.3.0 - tfjs, tfjs-core, tfjs-converter : ^4.4.0
    • 위처럼 ①모델마다 실제 실행 가능한 최소 버전 및 권장 peer dependency 버전이 상이하고 ②향후 프로젝트 확장 가능성 고려하여 , 사수님 제안으로 tfjs, tfjs-core, tfjs-converter 모델 모두 4.10.0 으로 설치함
    • tfjs, tfjs-core, tfjs-converter 모델은 2점대 이후로 버전이 한 몸으로 움직임. 따라서 버전업 할 때도 한 번에 했음
    • 위 3개 모델을 제외한 나머지 모델 ( ex. @tensorflow/tfjs-backend-webgl @tensorflow/tfjs-backend-wasm @tensorflow/tfjs-backend-webgpu) 은 위 3개 모델을 설치하면 자동 실행되므로 별도 설치 필요 없음
      ( 사실 @tensorflow/tfjs-converter 도 4점대 이후에는 포함되어있지만 그 전에는 포함되어 있지 않으므로, 보수적으로 별도 설치함)

 

+ Recent posts