Javascript/Next

[NextJS] NextJS 에 티처블 머신(Teachable Machine) 붙이기

eulBlue 2023. 8. 9. 02:02

📱테스트 환경

"react": "18.2.0"
"react-dom": "18.2.0"
"typescript": "^5.1.6"

 

요즘 또 남들이 핫하다는 티처블 머신을 이용해서 개발해보고싶었다.

 

먼저 필요한 걸 설치해줬다.

npm install @tensorflow/tfjs @tensorflow-models/mobilenet @tensorflow-models/knn-classifier

 

코드 자체는 공식 홈페이지에 가도 설명이 너무 잘 돼어있어서 딱히 설명이 필요할 것 같진 않다.

import { useEffect, useRef } from 'react';
import * as tf from '@tensorflow/tfjs';
import * as tmImage from '@teachablemachine/image';

const TeachableMachinePage = () => {
  const videoRef = useRef();
  const classifierRef = useRef();

  useEffect(() => {
    async function loadModel() {
      const URL = '/path/to/your/model/'; // Teachable Machine에서 내보낸 모델의 경로
      const modelURL = URL + 'model.json';
      const metadataURL = URL + 'metadata.json';

      const model = await tf.loadLayersModel(modelURL);
      const metadata = await (await fetch(metadataURL)).json();

      classifierRef.current = await tmImage.create(
        model,
        metadata
      );

      startWebcam();
    }

    function startWebcam() {
      navigator.mediaDevices.getUserMedia({ video: true })
        .then((stream) => {
          videoRef.current.srcObject = stream;
          detect();
        })
        .catch((err) => console.error('Error accessing webcam: ', err));
    }

    async function detect() {
      if (classifierRef.current) {
        const prediction = await classifierRef.current.predict(videoRef.current);
        console.log(prediction);
        // 여기에서 예측 결과를 사용하여 필요한 동작을 수행합니다.
      }
      requestAnimationFrame(detect);
    }

    loadModel();
  }, []);

  return (
    <div>
      <video
        ref={videoRef}
        autoPlay
        playsInline
        muted
        style={{ maxWidth: '100%' }}
      />
    </div>
  );
};

export default TeachableMachinePage;

 

근데 학습데이터가 적으니 데이터도 정말 개판이였고, 화면 각도에 따라서도 데이터가 수시로 변하는지라,

그냥 이미지를 업로드해서 테스트 해보는 방식으로 나는 진행하였다.

  • tmImage 모듈은 Teachable Machine과 관련된 이미지 분류에 사용
  • onImageChange 함수는 이미지 선택 시 실행됨. 선택한 파일을 상태에 저장하고 파일 URL을 생성
  • classifyImage 함수는 이미지 분류를 함. classifier 객체의 classify 함수를 사용
  • gotResults 함수는 분류 결과를 처리하고 다음 웹캠 이미지에 대해 분류를 실행
  • predict 함수는 Teachable Machine 모델을 로드하고 이미지 예측을 실행
import { useEffect, useRef, useState } from "react";
import { Col } from "@nextui-org/react";
import * as tmImage from "@teachablemachine/image";
import { Body, Button, CameraBox, Title } from "@/styles/commonStyle";

import { useRouter } from "next/router";

let classifier: {
  classify: (
    arg0: File | null,
    arg1: (error: any, results: { label: any }[]) => Promise<void>
  ) => void;
};

const TeachableMachinePage = () => {
  const [fileURL, setFileURL] = useState<string>("");
  const [file, setFile] = useState<any>();
  const [label, setLabel] = useState<String>();
  const [results, setResults] = useState<
    { className: string; probability: number }[] | undefined
  >();
  const imgUploadInput = useRef<HTMLInputElement | null>(null);
  const router = useRouter();

  const onImageChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files) {
      setFile(event.target.files);

      if (event.target.files[0] !== undefined) {
        const newFileURL = URL.createObjectURL(event.target.files[0]);
        setFileURL(newFileURL);
      }
    }
  };

  const classifyImage = () => {
    try {
      // Check if file is not null before calling classifier.classify
      if (file) {
        classifier.classify(file, gotResults);
      }
    } catch (err) {
      console.log(err);
    }
  };

  const gotResults = async (error: any, results: { label: any }[]) => {
    setLabel(results[0].label); //Predicted label with highest confidence
    classifyImage(); // Run on next webcam image
  };

  async function predict() {
    const URL = "https://teachablemachine.withgoogle.com/path/to/your/model/";
    const modelURL = URL + "model.json";
    const metadataURL = URL + "metadata.json";

    let model;

    // predict can take in an image, video or canvas html element
    model = await tmImage.load(modelURL, metadataURL);
    const imageElement = new Image();
    imageElement.src = fileURL;
    const tempImage = document.getElementById("srcImg");
    const prediction = await model.predict(imageElement, false);
    prediction.sort((a, b) => b.probability - a.probability);
    setResults(prediction);
  }

  return (
    <>
      <Body>
        <Col
          onClick={(event) => {
            if (fileURL === "") {
              event.preventDefault();
              if (imgUploadInput.current) {
                imgUploadInput.current.click();
              }
            }
          }}
        >
          <CameraBox>
              <img width="256px" height="256px" src={fileURL} />
          </CameraBox>
        </Col>
      </Body>
      <input
        style={{ display: "none" }}
        type="file"
        id="img"
        accept="image/*"
        required
        ref={imgUploadInput}
        onChange={onImageChange}
      />
    </>
  );
};

export default TeachableMachinePage;