🙏

素人がChat GPT君ちゃんさん氏とアプリ作ってお勉強する話

2023/12/10に公開

きっかけ

筆者(以下、”お鍋”とする)は絶望した。必ず、かの開発PJTにかかり避けては通れぬコードの理解をせねばならぬと決意した。お鍋はコードがわからぬ。お鍋の前職はリサーチ(ネットサーフィン野郎)である。それっぽい提言をして、顧客のいう通りに資料を作成して暮らしてきた。けれども、自身が「このままじゃあマズイぞォ???エンニジアを困らせちゃうぞ???」には人一倍に敏感であった(自伝:走れお鍋 冒頭より)。

というわけで!!!!リサーチ出身のプロジェクトマネージャーが、アプリ作成を通して少しでも開発に触れてエンジニアとお話しするぞォ!!がこの記事のモチベです。
※Zenn記事作成デビューでもあるので初物づくしです
初物づくしの新春バーゲンセール
ちなみにこの画像を小さくするのに10分かかりました

コードは友達。三浦は大知

できたもの

リンク左上の再生ボタンを押してください 起動に3秒くらいかかります

参考にさせていただいたもの

https://qiita.com/youtoy/items/57623faa9f39f4a3a64b
https://zenn.dev/tkyko13/scraps/b028812b0b9dc3
https://p5js.org/

アプリってどう作るの??

アプリ?アプリってそもそも何なのだ???


すぐ聞いちゃう
ふ〜〜〜〜〜〜〜〜ん
とりあえず一番軽いのがいいです(弱気)。

へ〜〜〜〜〜〜〜〜!!ウェブアプリにしよ!(単純)
個人的に響いたのポイントは、以下の2点(記事作成に慣れるため、果敢にリスト化するゾ)

  • 環境設定の簡易さ:ウェブアプリはブラウザとテキストエディタがあればいいらしい。以前、ツチノコ師匠(土屋さん)に指導いただき、VSCodeで環境構築を手取り足取りしてもらったのですが、ひじょ〜〜に手間がかかり泣きそうでしたので、今回は”コードとお友達になる!!”がテーマなので、”環境構築を体験する”はフォーカスからは外します。
  • プラットフォームの独立性:ブラウザ上で動作するのでOSに依存しない。私は神聖かまってちゃんなので、作ってもらったものをいろんな人に触ってほしいぞ。
    ⇨結論:ウェブアプリで作成

ウェブアプリいっぱいあるやん、、、

ウェブアプリって200種類あんねん(無い)、、、どないしよ、、、

即聞いちゃう
他にもネットサーフィンしてみると、ウェブアプリ(というか言語?)はどうやら各々得意とするものが違うようだぞ、、、
ということで、下記の手順で進めてみました(アドバイスください
1.アプリのコンセプトを決める
2.アプリの機能を小分けにする(単位化)
3.機能の優先順位というよりかは難易度が最も高い単位と機能的に親和性が高いウェブアプリ(というか言語?)を選定(これはChat GPTに聞く)
4.コードの作成

このあたりの進め方は下記の点でモヤモヤしております。もっとスマートな方法があるのではないかな、、、
-まず上の流れが適当か(機能⇨作成ツール?の選定
-ウェブエディタがあるもの、という基準で探したが、これはまず言語から探すべき?それは得意な言語がある人の選び方?

ウェブアプリ選定

アプリのコンセプトを決める

漠然と浮かぶものとしては下記

  • カメラ使いたい
  • 画像認識したい
  • 認識したことがわかるようにレスポンスが欲しい
  • ちょっと面白いことしたい
    うーーーーーんと悩んで煮詰まったので、現実逃避のためにYouTubeザッピング開始(この間、40秒)
    これだ!!!!!!!!!!!!!!!!!!!

    ポプテピピックというクソ作品(公称)から引用
    やっちゃいけないハンドサインを認識してモザイクをかける!世界を救うアプリだ!

    ちなみにこんなのもダメ 順仁くんダメだぞ

アプリの機能を小分けにする(単位化)

思いついたものを機能にするとこんな感じ?

  • カメラで手と指の位置を認識する(ハンドジェスチャーの認識)
  • カメラで任意の手と指の位置を登録する(任意のハンドジェスチャーの登録)
  • 任意のハンドジェスチャーを認識してモザイクをかける
  • モザイクもなんかルールありそうだけど、ひとまず無視

機能の優先順位というよりかは難易度が最も高い単位と機能的に親和性が高いウェブアプリ(というか言語?)を選定

こうは言ったものの難易度も何もわからんから、丸投げるのだ!!!!

あらすてき
JavaScriptはギリギリ大学生の時に触ったことあるぞ〜!(3時間くらい)
これ、GTPさんにアドバイス頂けなければ、Python(しか知らんので)でどうにかならんか〜???で一日溶けてたと思います。

PythonよりJavaScriptなの。なぁぜなぁぜ?

Tensor Flowもライブラリとして提示されていましたが、ネットで調べると、なんだかp5.js と ml5.jsが親和性が高く、かつウェブエディタが用意されてるのこの組み合わせで実施します!
⇨結論:言語はJavaScriptで、ライブラリとしてp5とml5を使う

コード作成(本番)

ジェスチャーの記録

まずは、何がアウトなハンドジェスチャーが定義しなきゃいけないと思いました。
ml5のライブラリを漁っていると、指認識のコードが!すごい!

ml5.Handpose

これは、どうやら、指の位置をトラッキングしてくれる機械学習モデルらしい。
なんだかコードを眺めていると、トラッキングした座標で表している様子。
なので、GPTさんに私がカメラに向かって実施したハンドジェスチャーを座標で返してくれるコードを書いてもらおう!!!!!!。これで、公序良俗に反したハンドジェスチャーを返してくれるはず。

〜1時間くらい格闘〜

コード
let handpose;
let video;
let predictions = [];

function setup() {
  createCanvas(640, 480);
  video = createCapture(VIDEO);
  video.size(width, height);

  handpose = ml5.handpose(video, modelReady);

  handpose.on("predict", results => {
    predictions = results;
  });

  video.hide();
}

function modelReady() {
  console.log("Model ready!");
}

function draw() {
  image(video, 0, 0, width, height);

  if (predictions.length > 0) {
    const hand = predictions[0]; // 最初の予測結果を使用
    const handShape = getHandShape(hand);
    console.log(handShape); // コンソールに手の形状を表示
  }
}

function getHandShape(hand) {
  const handShape = {
    thumb: [],
    indexFinger: [],
    middleFinger: [],
    ringFinger: [],
    pinky: [],
    palmBase: []
  };

  hand.landmarks.forEach((point, index) => {
    const [x, y, z] = point;
    if (hand.annotations.thumb.includes(index)) {
      handShape.thumb.push({ x, y, z });
    } else if (hand.annotations.indexFinger.includes(index)) {
      handShape.indexFinger.push({ x, y, z });
    } else if (hand.annotations.middleFinger.includes(index)) {
      handShape.middleFinger.push({ x, y, z });
    } else if (hand.annotations.ringFinger.includes(index)) {
      handShape.ringFinger.push({ x, y, z });
    } else if (hand.annotations.pinky.includes(index)) {
      handShape.pinky.push({ x, y, z });
    } else if (hand.annotations.palmBase.includes(index)) {
      handShape.palmBase.push({ x, y, z });
    }
  });

  return handShape;
}

↑一度起動させてみましたが、座標をリアルタイムで確認できなかったので、コンソールに指の形状をオブジェクト形式で表示するよう、追加でお願いしています。

これを起動させると、、、動いた!!!!

うーん。しかし左右で座標情報に変化がないぞ、、、
Handposeのサイトに「こんな感じでoutputするよ」のがあったので、それを提示して同じように表してくださいとお願いしてみました。

[
    {
      handInViewConfidence: 1, // The probability of a hand being present.
      boundingBox: { // The bounding box surrounding the hand.
        topLeft: [162.91, -17.42],
        bottomRight: [548.56, 368.23],
      },
      landmarks: [ // The 3D coordinates of each hand landmark.
        [472.52, 298.59, 0.00],
        [412.80, 315.64, -6.18],
        ...
      ],
      annotations: { // Semantic groupings of the `landmarks` coordinates.
        thumb: [
          [412.80, 315.64, -6.18]
          [350.02, 298.38, -7.14],
          ...
        ],
        ...
      }
    }
  ]

結果はこれ

コード
let handpose;
let video;
let predictions = [];

function setup() {
  createCanvas(640, 480);
  video = createCapture(VIDEO);
  video.size(width, height);

  handpose = ml5.handpose(video, modelReady);

  handpose.on("predict", results => {
    predictions = results;
  });

  video.hide();
}

function modelReady() {
  console.log("Model ready!");
}

function draw() {
  image(video, 0, 0, width, height);

  if (predictions.length > 0) {
    const hand = predictions[0]; // 最初の予測結果を使用
    const handAnnotations = hand.annotations;
    displayHandAnnotations(handAnnotations);
  }
}

function displayHandAnnotations(annotations) {
  for (const [fingerName, fingerPoints] of Object.entries(annotations)) {
    console.log(`${fingerName}:`);
    fingerPoints.forEach((point, index) => {
      const [x, y, z] = point;
      console.log(`  Point ${index}: (${x.toFixed(2)}, ${y.toFixed(2)}, ${z.toFixed(2)})`);

      // キーポイントをキャンバス上に描画
      fill(0, 255, 0);
      noStroke();
      ellipse(x, y, 10, 10);
    });
  }
}

よくなったけど、これ各指の相対座標じゃなくて画面上の絶対座標だ、、、同じ手の形状でも位置で座標変わってしまう、、、

この辺りで下記に気づきました、、、、、

でも、待てよ???座標で返したら、そのままバチっと決まる手の形しか返してくれないのでは???気づくのが遅かった、、、ということで、下記のとおり定義し直しました。

中指を立てる:他全ての指のマークポイントより中指の先端が高い位置にある
親指を立てて下向き:親指を下向きにかつ、親指以外を握り込んだ状態

この条件の時に指の先端にモザイクをかけるようにGPTさんにコードを書いてもらう。

コード
let handpose;
let video;
let predictions = [];

function setup() {
  createCanvas(640, 480);
  video = createCapture(VIDEO);
  video.size(width, height);

  handpose = ml5.handpose(video, () => {
    console.log("Model ready!");
  });

  handpose.on("predict", (results) => {
    predictions = results;
  });

  video.hide();
}

function draw() {
  image(video, 0, 0, width, height);
  applyMosaicIfGestureDetected();
}

function applyMosaicIfGestureDetected() {
  const mosaicSize = 80;

  predictions.forEach((prediction) => {
    if (isMiddleFingerUp(prediction)) {
      applyMosaic(prediction.annotations.middleFinger[3], mosaicSize);
    } else if (isThumbDown(prediction)) {
      applyMosaic(prediction.annotations.thumb[3], mosaicSize);
    }
  });
}

function isMiddleFingerUp(prediction) {
  const middleFinger = prediction.annotations.middleFinger;
  return middleFinger && middleFinger.length >= 4 && middleFinger[0][1] > middleFinger[3][1];
}

function isThumbDown(prediction) {
  const thumb = prediction.annotations.thumb;
  if (!thumb || thumb.length < 4) return false;

  const otherFingersFolded = Object.entries(prediction.annotations)
    .filter(([fingerName,]) => fingerName !== "thumb")
    .every(([, points]) => points.length >= 4 && points[3][1] > points[0][1]);
  
  return otherFingersFolded && thumb[0][1] < thumb[3][1];
}

function applyMosaic(point, size) {
  const [x, y] = point;
  fill(colorAt(video.pixels, x - size / 2, y - size / 2));
  rect(x - size / 2, y - size / 2, size, size);
}

function colorAt(pixels, x, y) {
  const idx = 4 * int(y * width + x);
  return [
    pixels[idx],
    pixels[idx + 1],
    pixels[idx + 2],
    pixels[idx + 3]
  ];
}

素敵!!できた!!!!!

ちょっと親指対応モザイクが微妙

親指へのモザイク処理が一部分で大事な部分が丸見えでしたので、書き換えをお願いしました。下記の通り定義の説明も合わせて返してくれました。なるほど。そう考えるのか。


コードの解説をしてくれるのも初心者にはありがたい

おお〜!できました!!!

まとめ

今回GPTさんにお願いして簡単なウェブアプリを作成しました。
所感として、、、

  • 条件さえ指定すればコードを書いてもらえる(今回はほんの一部だけ)
  • コードの解説をお願いすれば、非エンジニアでもなんとなくコードの意味することが読める(書けるようにはなるには別の勉強が必要)
  • 今回は簡単な機能だけでしたが、作成するアプリや機能がどのくらいの規模や機能の複雑さから、GPTでうまく対応できなくなるか知りたいなと思いました。ウェブアプリ以外も体験しなければいけない気がします。

私の職種はPMなのですが、フロントに立ってお客さんとエンジニアの橋渡しをする必要があります。エンジニアが最大限力を発揮できるよう、用件定義のためにお客様に項目立ててヒアリングしなければならないと日頃から意識しているのですが、及ばず至らず、、と感じることが多いです。Chat GPTに今回聞いたことは、エンジニアに対する質問;仕様にかかる「お客様に聞かなければいけないこと」に置き換えられるんだろうなあと思い(意外とGPTに聞くことは多かった)、日頃からエンジニアのコミュニケーションを密にしなければと感じました。また、いつも足りない言葉をコードで補ってくれてありがとうございますエンジニアの皆さん。

Discussion