ChatGPTを使ってP5.jsとMediapipeでサクッと指認識した話

# ChatGPTの1周年とP5.js+Mediapipeを使用した手の指認識

## ChatGPTの1周年について
2022年の11月30日にChatGPTがリリースされて1年が経過しました。この1年であらゆる環境にAIが活躍するようになり、世界が大きく変わった1年だったように思います。勿論このものすごいスピードでの変化は今も続いていますが、個人的にもChatGPTをはじめとするAIツールはいくつも試していますが、やはり1番利用しているのはChatGPTです。月3000円の有料課金は正直高いですが（2000円なら言う事なし！）、毎日使えば3000円以上の価値は十分にあると思います。逆に言うと月3000円を高いと思って使っていない人の方が断然多い訳で、そんな人たちと差を作るのなら今しかないとも思っています。様々な分野で活躍しますし、GPT-4はめちゃくちゃ賢いですから。絶対契約して毎日使うと人生が変わる気がしています。

## P5.jsとMediapipeでの手の指認識について
今回はP5.jsとMediapipeで手の指認識をした話をさせてください。ChatGPTを使えば理解がメチャクチャ早くなる事例を含めての話です。

### P5.js+Mediapipeを使いたかった訳
画像認識や手の認識には以前から興味があり、今回指の認識をしたいと思いました。高性能画像認識はGoogleが開発したMediapipe！これ本当にすごいですよね。P5.jsの前にRaspberry Pi 4で動かしていたのですが、処理が遅くて実用的ではありませんでした。そこで調べていくとGoogleのサンプル等をブラウザで試すとめちゃ軽い！JavaScriptの方がパフォーマンスが良いとわかったので、試してみる事に！

以下を参考にしました。
[参考記事](https://qiita.com/youtoy/items/70571c7066d41729c2bd)

P5.jsとindex.htmlとsketch.jsを記述する事で描画できるようになります。index.htmlの内容は、p5.jsとMediaPipe Hands APIを使用して、ブラウザでリアルタイムに手の追跡を行うウェブアプリケーションの基盤を構築するためのHTMLドキュメントです。主要な構成要素は以下の通りです：

- p5.jsライブラリの読み込み: クリエイティブコーディングに使われるJavaScriptライブラリで、描画やアニメーションのために利用されます。
- MediaPipeライブラリの読み込み: カメラ操作、UIコントロール、描画機能、そして手の追跡機能を提供するための複数のJavaScriptスクリプトが含まれています。
- スタイルシート: 外部のCSSファイルを参照し、ウェブページのスタイルを定義しています。
- ビデオ要素: カメラの映像を表示し、MediaPipeでの手の追跡に利用されるHTML要素です。
- JavaScriptスクリプト: p5.jsやMediaPipeを使った具体的な処理を記述するための外部スクリプトファイル（sketch.js）を読み込んでいます。

この構造により、ユーザーのブラウザ上でカメラを通じて手の動きを捉え、それをリアルタイムで画面上に描画するアプリケーションを実現しています。

### sketch.jsによるMediaPipeの実装
```javascript
const isFlipped = true;

let keypointsHand = [];

const videoElement = document.getElementsByClassName("input_video")[0];
videoElement.style.display = "none";

function onHandsResults(results) {
  keypointsHand = results.multiHandLandmarks;
}

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

hands.setOptions({
  selfieMode: isFlipped,
  maxNumHands: 1,
  modelComplexity: 1,
  minDetectionConfidence: 0.5,
  minTrackingConfidence: 0.5,
});
hands.onResults(onHandsResults);

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

let videoImage;

function setup() {
  const canvas = createCanvas(500, 400);
  videoImage = createGraphics(320, 180);
}

function draw() {
  clear();
  background("rgba(100, 100, 255, 0.2)");

  videoImage.drawingContext.drawImage(
    videoElement,
    0,
    0,
    videoImage.width,
    videoImage.height
  );

  push();
  if (isFlipped) {
    translate(width, 0);
    scale(-1, 1);
  }
  displayWidth = width;
  displayHeight = (width * videoImage.height) / videoImage.width;
  image(videoImage, 0, 0, displayWidth, displayHeight);
  pop();

  if (keypointsHand.length > 0) {
    const indexTip = keypointsHand[0][8];
    ellipse(indexTip.x * displayWidth, indexTip.y * displayHeight, 10);
  }
}
```

### index.html
```html
<!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>
```