안녕하세요 Manngold입니다.
React 프레임워크를 공부하면서
'To Do App 말고 특정 라이브러리를 하나 선택해서 간단한 앱을 만들자'
라는 취지로 구경을 하던 도중
https://medium.com/@julienrioux/in-browser-ml-with-react-js-and-ml5-js-f3eeb5149404
이 포스트를 보고 약간의 수정을 거쳐서 함수형 컴포넌트로 만들어 보았습니다.
참고한 글은 이미지를 미리 넣어 놓았기 때문에 미리 설정을 해놓은 이미지의 결과만 나오지만
저는 살짝 바꿔서 이미지의 url을 입력 받도록 해서 다른 이미지도 분석 할 수 있도록 만들어 봤습니다.
npx create-react-app guess-what
create-react-app 명령어를 통해서 react app 개발을 위한 기초 환경을 세팅 해줍니다.
이후 프로젝트 파일에 들어가서 css파일과 test파일들을 삭제해줍니다.
yarn add ml5 styled-components
yarn 명령어를 통해서 ml5와 스타일링을 위한 styled components를 설치합니다.
개발하기 앞서 컴포넌트 구조에 대해서 살펴봅시다.
빨간색은 전체 앱 컴포넌트
주황색은 분석할 이미지의 url을 입력받는 컴포넌트 (Uploader 컴포넌트)
노란색은 입력받은 이미지를 바탕으로 분석한 결과를 출력해줄 컴포넌트 (Classifier 컴포넌트)
총 세 개의 컴포넌트가 필요합니다.
따라서 추가적으로 components 폴더를 만들어서 Upload.js와 Classifier.js 파일을 생성 합시다.
(기본적인 디렉토리 구조)
App 컴포넌트 안에는 두 개의 컴포넌트가 존재합니다
Uploader 컴포넌트와 Classifier 컴포넌트
그리고 url의 값은 app에서 useState
로 관리가 될 것입니다.
import React, { useState } from "react";
import styled from "styled-components";
import Classifier from "./components/Classifier";
import Uploader from "./components/Uploader";
const Container = styled.div`
height: 100vh;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
background-color: #ecf0f1;
`;
function App() {
const [imageSrc, setImageSrc] = useState();
return (
<Container>
<Uploader setImageSrc={setImageSrc} />
<Classifier imageSrc={imageSrc} />
</Container>
);
}
export default App;
Uploader 컴포넌트는 imageSrc
의 값을 변경 할 것이기 때문에 props로 setImageSrc
를 전달해줍시다.
Classifier 컴포넌트는 imageSrc
를 활용해서 이미지 분석을 하기 때문에 imageSrc
를 전달해줍시다.
import React from "react";
import styled from "styled-components";
const Container = styled.div``;
const Upload = styled.input`
border: none;
width: 16rem;
height: 2rem;
`;
function Uploader({ setImageSrc }) {
const changeImage = (e) => {
const imgSrc = e.target.value;
setImageSrc(imgSrc);
};
return (
<Container>
<Upload
type="text"
onChange={changeImage}
placeholder="Insert Image Url"
/>
</Container>
);
}
export default Uploader;
input 태그가 url을 입력 받았을 때 onChange를 통해서 input 태그 내에 있는 값을 뽑아서 setImageSrc
로 imageSrc
state 값을 설정합니다.
이미지를 받아서 분석을 하고 결과를 출력 해주는 컴포넌트입니다. 천천히 뜯어보면서 알아봅시다.
const [results, setResults] = useState([]);
const [isLoading, setLoading] = useState(false);
Classifier 컴포넌트에서는 두 개의 state를 사용합니다.
분석 결과를 담는 results
, loading을 구현하기 위한 isLoading
state
const classifyImg = () => {
setLoading(true);
const classifier = ml5.imageClassifier("MobileNet", () =>
console.log("Module loaded")
);
const image = document.querySelector("#image");
classifier
.predict(image, 5, (err, classifiedResults) => {
return classifiedResults;
})
.then((classifiedResults) => {
setLoading(false);
setResults(classifiedResults);
});
};
이미지 분석을 위해서 classifyImg 함수를 생성합니다.
분석을 진행 할 동안 isLoading
state의 값을 true
로 만들어주고,
classifier
변수에 ml5의 imageClassifier
를 담아줍니다.
이후 image를 담고 predict
메소드로 분석을 하고 분석이 끝나면 isLoading
state를 false
로 바꿔주고 분석 결과를 results
state에 담아줍니다.
return (
<Container>
<Image
src={imageSrc}
id="image"
onLoad={classifyImg}
crossOrigin="anonymous"
/>
{isLoading ? (
<ClipLoader />
) : (
<List>
{results.map((result, index) => {
const { label, confidence } = result;
return (
<Item key={index}>{`${
index + 1
}. Predictation : ${label} , ${Math.floor(
confidence * 100
)}%`}</Item>
);
})}
</List>
)}
</Container>
);
image 태그에 imageSrc
state를 담아서 이미지를 로드해줍니다.
그리고 onLoad
를 통해서 이미지가 로드되면 이미지 분석을 위한 classifyImg
함수를 실행시켜줍니다.
이후 나오는 코드는 loading과 결과값을 렌더링하는 코드입니다.
import React, { useState } from "react";
import styled from "styled-components";
import ml5 from "ml5";
import ClipLoader from "react-spinners/ClipLoader";
const Container = styled.div`
margin-top: 30px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
`;
const Image = styled.img`
height: 20rem;
width: 30rem;
`;
const List = styled.ul`
margin-top: 20px;
`;
const Item = styled.li`
list-style-type: none;
`;
function Classifier({ imageSrc }) {
const [results, setResults] = useState([]);
const [isLoading, setLoading] = useState(false);
const classifyImg = () => {
setLoading(true);
const classifier = ml5.imageClassifier("MobileNet", () =>
console.log("Module loaded")
);
const image = document.querySelector("#image");
classifier
.predict(image, 5, (err, classifiedResults) => {
return classifiedResults;
})
.then((classifiedResults) => {
setLoading(false);
setResults(classifiedResults);
});
};
return (
<Container>
<Image
src={imageSrc}
id="image"
onLoad={classifyImg}
crossOrigin="anonymous"
/>
{isLoading ? (
<ClipLoader />
) : (
<List>
{results.map((result, index) => {
const { label, confidence } = result;
return (
<Item key={index}>{`${
index + 1
}. Predictation : ${label} , ${Math.floor(
confidence * 100
)}%`}</Item>
);
})}
</List>
)}
</Container>
);
}
export default Classifier;
로딩 UI를 위해서 react-spinners
를 사용했습니다.
Demo : https://manngold.github.io/guess-what/
github : https://github.com/Manngold/guess-what