1. TensorFlow.js 란??
TenosorFlow.js는 javascript를 기반으로 하여 웹환경에서 사용되는 머신러닝 라이브러리입니다.
1.1 TensorFlow.js의 특징
- Web 기술과의 통합
- TensorFlow.js는 javascript로 작성되므로 웹 브라우저에서 ML model을 별도의 수정없이 동작가능하게 해줌
- ML 애플리케이션 배포에 용이
- Web browser 상에서의 다수의 backend 지원
- WebGL [GPU]: 웹 브라우저에서 GPU를 사용할 수 있게 해주는 표준 명세서로 GPU acceleration가능함. (3MB 이상의 ML모델에 적합)
- Web Assembly (WASM) [CPU]: CPU performance의 향상시킬수 있다는 특징. (3MB 이하의 ML 모델사용 시 WASM이 WebGL보다 빠름)
- CPU execution: CPU로 inference가 진행되며 3중에 가장 느림
- TensorFlow python API와의 호환
- TensorFlow로 학습된 모델과 호환되도록 API도 지원
- data privacy
- TensorFlow.js의 client-side machine learning 기술이 서버로 data나 model을 전송하지 않고 ML service를 사용할 수 있게끔 해줌
1.2 TensorFlow.js는 어디에 사용하지??
Javascript를 기반으로 하기 때문에 다양한 platform에서 쉽게 TensorFlow.js를 사용가능합니다.
- Client-side in the web browser (using Javascript)
- Server-side even IoT devices (using Node.js)
- Desktop apps (using Electron)
- Native mobile apps (using React Native)
2. TensorFlow.js 설치
TensorFlow.js의 설치방법은 2가지입니다.
- CDN(content delivery network)을 통해 배포되넌 축소된(minified) javascript code 사용
- 설치:
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js" type="text/javascript"></script>
- CDN service는 빠르며 정적인 리소스를 사용자들에게 제공하기에 안정적
- 공유 네트워크를 사용할 수 있는 환경에 적합 (인터넷이 되어야한다고 이해하세용ㅎ)
- 설치:
- npm 같이 package manager를 통해 배포되는 번들(bundle) package 사용
- 설치:
npm install @tensorflow/tfjs
- 공유 네트워크 사용할 수 없는 환경에서 사용되며 애플리케이션에 TensorFlow.js를 직접 포함시키는 방법
- 설치:
둘중에 한 방법으로 설치를 하셨다면 TensorFlow.js의 각종 클래스는 tf라는 이름의 namespace하위에서 찾을 수 있음을 알아두시면 됩니다. (저는 CDN방법으로 실습을 이어나갈 예정입니다.)
3. TensorFlow.js를 이용한 Detection model 예제
오늘 알려드릴 detection model 실습은 TensorFlow.js 에코시스템을 기반으로 진행하므로 먼저 TensorFlow.js 에코시스템부터 알아보져!
3.1 TensorFlow.js 에코시스템
TensorFlow.js의 에코시스템을 통해 다양한 고수준 라이브러리를 사용할 수 있으며 이는 새로운 연구 성과(ML model, algorithm, ...)을 빠르게 도입할 수 있게 해줍니다. 고수준 라이브러리 중에서는 pretrained된 ML모델을 제공하는 tfjs-models이 있고 다양한 종류의 데이터셋을 불러올 수 있는 tfjs-data도 있고 AI 비전공자도 쉽게 ML framework를 사용할 수 있게 해주는 ML5.js도 있습니다.
저는 이중에서 tfjs-models 라이브러리에서 제공하는 detection model(ssdlite)을 사용하여 실습을 진행할것입니다.
3.2 TensorFlow.js 으로 detection model inference
웹브라우저를 통해 inference를 하기 위해서는 html, javascript, css code가 필요합니다. (사실 실습을 진행하는 데 javascript(React) code가 거의 다입니다ㅎㅎ..) 저는 이번에 티스토리 블로그 글에 맞는 inference 코드를 만들어보겠습니다.
먼저 HTML skeleton 부터 보시죠!
<p data-ke-size="size16"> </p>
<noscript>
<!--클라이언트 사이드 스크립트(client-side scripts)를 사용하지 않도록 설정했거나
스크립트를 지원하지 않는 브라우저를 위한 별도의 콘텐츠를 정의할 때 사용합니다. -->
You need to enable JavaScript to run this app.
</noscript>
<div id="inference"> </div>
블로그용이다 보니까 <html>...</html>
, <head>...</head>
과 <body>...</body>
에 대한 DOM(Document Object Model)을 글안에 작성안해도 이미 들어가있습니다!! (짧아서 좋구만 허허)
문서 객체 모델(DOM)이란?
문서 객체 모델(DOM)은 XML이나 HTML 문서에 접근하기 위한 일종의 인터페이스입니다. 이 객체 모델은 문서 내의 모든 요소를 정의하고, 각각의 요소에 접근하는 방법을 제공합니다. (e.g. <html>, <a>, <p>, <div>, ...)
"<div>의 inference라는 id를 가지는 element는 머하는 얘지??"하는 생각하실텐데요. 뒤의 react코드를 작성해보면 알게되니 기억해두고 다음을 읽어봅시다! 먼저 react를 쓰기위해 필요한 javascript들을 CDN(Contents Delivery Network)으로 가져와보죠.
<!-- react에 필요한 javascript -->
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<!-- html에서 react compile에 필요한 javascript -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- tensorflow.js 사용하기 위한 javascript -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.0.1"> </script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/coco-ssd"> </script>
사용되는 javascript의 역할은 주석으로 써놓았습니다. 이제야 필요한 준비물이 모두 갖추어 졌으니 react로 가즈아! (부분부분 단락나눠가면서 설명 진행할게여)
<script type="text/babel">
class App extends React.Component {
//React.createRef() 는 특정 노드나 컴포넌트에 레퍼런스 값을 만들어주는 것
//Ref를 통해 인스턴스를 생성 후 render 코드 블록 쪽만 리랜더링후 다시 실행
videoRef = React.createRef();
canvasRef = React.createRef();
<script type="text/babel">
의 text/babel은 react(javascript)를 compile을 담당하고 아래부터 react code입니다. App
이라는 class이름을 설정하고 React의 component로 지정합니다. 그리고 videoRef
는 실시간으로 들어오는 webcam의 stream값을 받아 지속적으로 리랜더링 (task A라 칭함)하고 canvasRef
는 detection 결과를 visualization하는 역할(task B라 칭함)라 칭함)을 합니다.
componentDidMount() {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
const webCamPromise = navigator.mediaDevices //웹캠 사용하기
.getUserMedia({
//가져올 미디어에 대한 설정
audio: false,
video: {
facingMode: "user"
}
})
.then((stream) => {
window.stream = stream;
this.videoRef.current.srcObject = stream; //웹캠 stream을 videoRef에 할당
return new Promise((resolve, reject) => {
this.videoRef.current.onloadedmetadata = () => {
resolve();
};
});
});
const modelPromise = cocoSsd.load(); //detection 모델 로드
Promise.all([modelPromise, webCamPromise])
.then((values) => { //values: [modelPromise, webCamPromise]
this.detectFrame(this.videoRef.current, values[0]);
})
.catch((error) => {
console.error(error);
});
}
}
componentDidMount()
는 클래스를 생성하고 한번만 실행되는 함수인데 DOM을 제어가능한 생성자입니다.
if구문을 통해 navigator.mediaDevices.getUserMedia
을 통해 유저에게 webcam을 사용요청을 하게 되고 요청을 승락하면 해당 getUserMedia함수를 실행시켜 반환값을 stream이라는 arugment로 넘겨 위에서 만든 videoRef.current.srcObject
에 할당하게 되면서 task A역할을 수행하게 되는 것이죠.
그리고 cocoSsd.load()
을 통해 coco dataset으로 학습된 ssd 라는 detection 모델을 가져오게됩니다. 그리고 다음 Promise.all
을 통해 인자안의 모든 promise인 modelPromise
와 webCamPromise
가 준비될때까지 기다리게 됩니다. 준비가 완료되면 .then
을 통해 modelPromise와 webCamPromise가 values
라는 이름으로 wrapping됩니다. (values[0] 은 modelPromise임) 마지막으로 이제 webcam의 stream(videoRef.current)와 detection model(values[0]을 인자로 주어 detectFrame
을 실행합니다.
detectFrame = (video, model) => {
model.detect(video).then((predictions) => { //model.detect(video)의 return은 predictions으로 전달
this.renderPredictions(predictions);
requestAnimationFrame(() => { //지속적으로 detectFrame함수 을 실행시킴
this.detectFrame(video, model);
});
});
};
detectFrame
이라는 함수선언을 한것이고 (video, model)
이라는 이름의 2가지 인자를 받는다고 정의한 것이고 해당 인자들로 detect를 실행하고 반환값인 predictions
을 인자로 renderPredictions
함수를 호출하게 됩니다. 여기서 detect는 webcam의 실시간 frame image을 입력으로 ssd모델의 detection을 수행하는 것이죠. requestAnimationFrame()
을 통해 지속적으로 wrapping되어 있는 detectFrame
함수를 실행을 하게합니다.
renderPredictions = (predictions) => {
const ctx = this.canvasRef.current.getContext("2d");
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// 폰트 설정
const font = "16px sans-serif";
ctx.font = font;
ctx.textBaseline = "top";
predictions.forEach((prediction) => {
const x = prediction.bbox[0];
const y = prediction.bbox[1];
const width = prediction.bbox[2];
const height = prediction.bbox[3];
// bounding box 그리기
ctx.strokeStyle = "#0072B5";
ctx.lineWidth = 2;
ctx.strokeRect(x, y, width, height);
// label background 그리기
ctx.fillStyle = "#0072B5";
const textWidth = ctx.measureText(prediction.class).width;
const textHeight = parseInt(font, 10); // base 10
ctx.fillRect(x, y, textWidth + 4, textHeight + 4);
});
predictions.forEach((prediction) => {
const x = prediction.bbox[0];
const y = prediction.bbox[1];
// label text 그리기
ctx.fillStyle = "#000000";
ctx.fillText(prediction.class, x, y);
});
};
위에서 말씀드린 task B를 하도록 하기위해 canvasRef
를 여기서 사용하게 됩니다. predictions
이라는 인자를 받아 detection result을 그리는 renderPredictions
함수입니다. 한 frame image에 여러 detect결과값이 있을 수 있으므로 forEach
를 사용한것입니다.
- prediction.bbox: detection된 객체의 bounding box의 정보를 담고 있음
- prediction.bbox[0]: bounding box의 x축 좌표의 center값
- prediction.bbox[1]: bounding box의 y축 좌표의 center값
- prediction.bbox[2]: bounding box의 width(너비)
- prediction.bbox[3]: bounding box의 height(높이)
- prediction.class: bounding box의 클래스 이름
render() {
return (
<div>
<video
autoPlay
playsInline
muted
ref={this.videoRef}
width="600"
height="450"
/>
<canvas
className="tfjs_1_size"
ref={this.canvasRef}
width="600"
height="450"
/>
</div>
);
}
}
const rootElement = document.getElementById("inference");
ReactDOM.render(<App />, rootElement);
render()
함수를 통해 html에 랜더링하게 되는 부분입니다. webcam의 실시간 frame image을 담는 videoRef
는 ref인자로 들어가 video라는 DOM을 실행하게 되는것이고 detection result를 기록하는 canvasRef
는 canvas를 실행하게 됩니다.
inference라는 id를 가진 element(html skeleton에서 만든 element)를 가져와 rootElement
에 할당하고 해당 element를 대상으로 App라는 클래스의 render함수의 return값으로 html을 rendering시키는 것입니다. 이제 마지막으로 css부분만 작업하면 끝!!
.tfjs_1_size {
position: relative;
top: -450px;
left: 0;
}
css작업인데요. 위 canvas에서 className
과 동일한 이름으로 해당 css를 canvas에 적용하게됩니다. 블로그 특성상 video
라는 element밑에 canvas
가 위치하게되는데 이는 canvas와 video가 겹치게 보이지 않아 detection되지 않는 것처럼 보이게 되죠. 그래서 이를 위해 위의 css가 필요합니다. 그래서 video의 height만큼 canvas의 위치를 올려주면 완료되는 코드입니다.
이제 모든것이 완료되었습니다. 위에 설명드린 코드는 tfjs_tutorial 에 ssd_detection.html이라는 파일에 모아두었습니다.
3.3 detection 결과
webcam사용을 승낙하셧다면 아래에 detection결과가 짜짠!!!! (가끔 detection이 안될경우가 있는데 새로고침하시면 될거예요..ㅠ)
'AI Engineering > TensorFlow' 카테고리의 다른 글
TensorFlow.js (3) TensorFlow.js 변환 (0) | 2022.04.03 |
---|---|
TensorFlow.js (2) - WebGL 기반 hand pose detection (2) | 2022.03.23 |
Mediapipe (2) - custom segmentation model with mediapipe (0) | 2022.03.14 |
TFLite 뽀개기 (3) - Quantization (2) | 2022.03.09 |
TFLite 뽀개기 (2) - TFLite Inference (0) | 2022.03.09 |