동물상 테스트 웹, 앱 만들기! (4편)

HyungJin Han·2022년 6월 20일
0

따라쟁이 코딩

목록 보기
4/4
post-thumbnail

실전 수익형 웹, 앱 서비스 동물상 테스트 만들기
3번쨰 영상을 통해 제작된 코드입니다.

해당 내용에 도움이 되는 부분은 링크를 걸어두었습니다!

그럼 시작하도록 하겠습니다!



저번 시간의 경우, Crawling에 대한 이해와 원하는 키워드에 맞춰

이미지 다운로드 자동화 등을 제작해보고 이해하는 포스팅을 했다.

이제 본격적으로 동물상 테스트를 위한 머신러닝을 시도해 볼 차례다.

결국에는 정말 훌륭하게 만들어진 머신러닝 사이트를 사용하여 큰 틀을 짜고
작은 부분들을 내 입맛에 맞게 고치는 작업이다...
머신러닝을 처음부터 끝까지 필자가 만든다?? 1년 밤새도 할 수 있을까 싶다...


1. Teachable Machine을 이용한 머신러닝

우선 머신러닝을 위한 사이트를 이용해야 한다.

위에서도 언급했듯이, 훌륭하게 만들어진 코드를 가져와

수정하는 작업을 거칠 것이기 때문이다.

구글에서 지원받아 운영하고 있는 Teachable Machine이라는 사이트가 있다.

이곳이 필자가 머신러닝을 위해 사용할 사이트이다.

사이트에 들어가보자.

사이트의 메인 화면에 보이는 시작하기를 클릭하면,

프로젝트를 생성할 수 있다.

필자의 작업은 저번 1편에서 다운받았던 동물상 별 연예인의

사진을 데이터로 사용해서 자신의 얼굴을 테스트하는 프로젝트이기 때문에

이미지 프로젝트로 생성했다.

프로젝트 생성 후의 모습이다.

우선 클래스의 이름을 정해야 한다.

클래스의 이름은 쉽게 말하자면 컴퓨터로 따지면 폴더이다.

필자는 클래스 추가를 통해 5개의 클래스를 만들고,

각 클래스에 강아지 상, 고양이 상, 곰 상, 공룡 상, 토끼 상

이렇게 지정을 했다.

그리고 조코딩님이 미리 정리해놓은 정보를 토대로

강아지 상 : 워너원 강다니엘, 엑소 백현, 박보검, 송중기

고양이 : 워너원 황민현, 엑소 시우민, 강동원, 이종석, 이준기

곰 : 마동석, 조진웅, 조세호, 안재홍

공룡 : 김우빈, 윤두준, 이민기, 육성재, 공유

토끼 : 방탄소년단 정국, 아이콘 바비, 워너원 박지훈, 엑소 수호

이렇게 정해주고 각 클래스에 사진을 넣어주었다.

이렇게 정리된 폴더를 통째로 넣으면 끝난다.

굉장히 편리한 사이트였다.

이렇게 각 클래스에 사진을 업로드를 했다면 자료를 다듬어야 한다.

즉, 데이터 클렌징이라는 작업을 시작할 것이다.

데이터 클렌징이란, 사진이나 기타 데이터가 될 자료들 중에

필요없거나, 있으면 오히려 정확도가 저하될만한 자료들을

삭제해 줌으로써 정확도의 향상을 위해 일종의 자료 필터링을 하는 것이다.

특히, 필자의 경우에는 BeautifulSoup를 통해 키워드에 맞춰 일괄적으로

이미지를 다운받았기 때문에 더더욱 필요한 작업이다.

위의 이미지에서 빨간 네모 부분에 있는 🗑️ 이런 모양의 휴지통을 클릭하고

이미지 확인 후, 삭제를 시작하면 된다.

필자는 삭제할 이미지의 기준을 잡을 때,

물건에 얼굴이 가려진 사진, 키워드와 맞지 않은 사진, 얼굴이 한 개 이상인 사진, 얼굴이 나오지 않은 사진

이렇게만 걸러도 나쁘지 않은 결과를 얻을 수 있을 것이다.

이렇게 데이터 클렌징 작업을 마치고 각 클래스에 담겨있는 사진들을

참고할 모델로써 학습을 시키도록 해보자.

모델 학습시키기를 클릭하면,

이렇게 학습 중...이라는 문구와 함께 열심히 학습을 한다.

학습을 끝낸 후의 모습이다.

우측을 qhaus Webcam 부분을 파일로 바꿔주어야 테스트를 진행할 때

이미지 업로드를 통해 테스트를 할 수 있다.

대표적인 고양이 상인 가수 GD의 테스트 결과이다.

비교적 사진이 잘 나와서 그런지 고양이 상이 83%로 높게 나왔다.

다음으로는 곰 상으로 알려진 배우 곽도원의 테스트 결과이다.

그냥 곰 상으로 나와버렸다.

어? 그럼 무난하게 성공한거네? 라고 생각하고 정말 대표적인 공룡 상,

가수 TOP를 넣어봤다.

??????

아...아닌가?

그럼 더 공룡 상으로 알려진 배우 류준열로 해보았다.


이게 무슨 일이지....

사실 오류가 아니고 동물 상이라는 것은 지극히 주관적이기도 하고,

헤어스타일, 옷차림, 조명, 표정 등을 학습하게 되면서

수많은 변수에 따라 결과가 정확하지 않을 수 밖에 없다.

어찌보면 당연하다.

해당 편의 영상에서 조코딩님의 설명을 덧붙이자면,

사진의 색을 전부 흑백으로 처리하고, 얼굴 부분만을 학습시킨다면

보다 정확한 결과가 나올 것이라고 한다.

하지만 그렇게까지는 하지 않을 예정이다.

추후에 다른 주제로 해볼법 한 작업임은 틀림없다.

이제 이렇게 머신러닝은 끝났고, 이제 해당 링크와 코드를 불러와야 한다.

위의 이미지처럼 모델 업로드를 클릭하고,

모델 업로드가 끝나면 링크와 함께 JavaScript 코드가 나온다.

위의 링크의 경우 타인이 언제든지 이 링크를 들어와서

필자가 만든 동물상 테스트를 할 수 있는 링크이다.

동물상 테스트 사이트 👈 여기로 들어가면 된다.

하지만 필자는 그 밑의 JavaScript 코드를 수정해서 웹을 만들 예정이다.

위의 이미지에서 빨간 네모 부분의 복사📃를 클릭해 코드를 복사하고,

미리 만들어둔 HTML 파일에 에디터를 통해 <body> 태그 안에 붙여넣어 준다.

필자의 경우, HTML이 너무 복잡해질 것 같아서 js 파일을 따로 만들고

<script> 부분을 js 파일에 따로 넣어주었다.

그 후에 저장을 하고 실행을 시키면?

초라하지만 일단 성공은 한 셈이다.

Start를 클릭하면 학습한 모델을 불러오면서 웹캠이 켜지고,

웹캠에 비춰지는 얼굴에 따라 동물상을 수치로 나타내는 웹이다.

하지만 필자는 웹캠이 없어서 그런지 반응을 하지 않았다.

뭐 문제될 것은 없는 것이 웹캠은 쓰지 않을 예정이고,

이미지 업로드를 하고 그에 맞춰서 테스트를 하는 것에만 신경쓰자!


2. 웹캠에서 이미지 업로드 형식으로

앞서 설명했듯이 웹캠을 사용하지 않고 이미지 업로드를 통해

동물상 테스트를 할 예정이다.

이미지 업로드를 위한 코드를 작성해야 하지만...

구글링을 통해 템플릿을 찾아보도록 했다.

2-1. 이미지 업로드 템플릿 사용

우선 키워드는 image upload template로 검색했다.

그렇게 뜨는 사이트인 colorlibFile Upload Input를 들어가봤다.

이런 식으로 간단한 프리뷰가 가능한 사이트가 나왔으며,

이미지의 빨간 네모로 체크된 곳을 클릭해서 각 HTML 코드와

CSS 코드, JS 코드 등을 복사해서 사용하면 된다.

만약에 이 코드를 사용하여 수익을 창출한다고 하면,

쭉 스크롤을 내려서 License를 유심히 주의 깊게 봐야 한다.

해당 코드의 License를 보자면,

Copyright (c) 2022 by Aaron Vanston (https://codepen.io/aaronvanston/pen/yNYOXR)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

영어라 보기 힘들다면 번역기를 통해 좀 더 쉽게 알아보자!

Copyright (c) 2022 by Aaron Vanston (https://codepen.io/aaronvanston/pen/yNYOXR)

이에 따라 이 소프트웨어 및 관련 문서 파일("소프트웨어")의 사본을 입수한 사람에게 사용, 복사, 수정, 병합 권한을 포함하되 이에 국한되지 않는 제한 없이 소프트웨어를 취급할 수 있는 권한이 무료로 부여됩니다. 다음 조건에 따라 소프트웨어 사본을 게시, 배포, 재라이센스 부여 및/또는 판매하고 소프트웨어가 제공된 사람에게 그렇게 하도록 허용합니다.

위의 저작권 표시 및 이 허가 표시는 소프트웨어의 모든 사본 또는 상당 부분에 포함되어야 합니다.

소프트웨어는 상품성, 특정 목적에의 적합성 및 비침해에 대한 보증을 포함하되 이에 국한되지 않는 어떠한 종류의 명시적 또는 묵시적 보증 없이 "있는 그대로" 제공됩니다. 어떤 경우에도 저자 또는 저작권자는 계약, 불법 행위 또는 기타의 행위로 인해 소프트웨어 또는 다른 거래와 관련하여 발생하는 청구, 손해 또는 기타 책임에 대해 책임을 지지 않습니다. 소프트웨어.

제한 없이 소프트웨어를 취급할 수 있는 권한이 무료로 부여됩니다.

제한없이 사용은 가능하다는 뜻인데,

소프트웨어 사본을 게시, 배포, 재라이센스 부여 및/또는 판매하고 소프트웨어가 제공된 사람에게 그렇게 하도록 허용합니다.

위의 저작권 표시 및 이 허가 표시는 소프트웨어의 모든 사본 또는 상당 부분에 포함되어야 합니다.

위의 문구를 보면 위의 저작권 표시 및 이 허가 표시는 소프트웨어의 모든 사본 또는 상당 부분에 포함

이 부분이 눈에 띈다.

결국 이 코드를 사용하기 위해서는 저 License 전체를

코드에 삽입해야 한다는 의미이다.

그렇다면

위의 이미지에 있는 빨간 네모 부분의 Copy를 눌러 복사 후,

사용할 코드의 HTML 부분에 <!-- --> 이렇게 주석처리를 통해

어딘가에 붙여넣어주면 된다.

꼭 보이는 화면에 출력될 필요는 없기에 주석을 사용한 것이다.

이제 웹을 열어보면?

아주 수월하게 진행되고 있는 느낌이 든다.

당연하게 수월한 것이, 다른 사람들이 만들어 놓은 코드를 어떻게 보면
짜집기를 통해서 만들고 있기 때문에 수월할 수 밖에 없다는 걸
지금 블로그에 포스팅을 하면서 뼈저리게 깨닫게 된다.
하지만 이러한 과정도 성장의 일부라고 생각하고 열심히 해보자!

자! 그럼 여기서 코드의 중간점검을 할 시간이다.

<!-- HTML 코드 -->

<html lang="kor">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="style.css">
    <title>Animal Test</title>
</head>
<body>
  	<!-- Teachable Machine Start 버튼 -->
    <div>Teachable Machine Image Model</div>
    <button type="button" onclick="init()">Start</button>
  
  	<!-- 이미지 업로드 코드 -->
    <script class="jsbin" src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
    <div class="file-upload">
        <button class="file-upload-btn" type="button" onclick="$('.file-upload-input').trigger( 'click' )">AddImage</button>
        <div class="image-upload-wrap">
            <input class="file-upload-input" type='file' onchange="readURL(this);" accept="image/*" />
            <div class="drag-text">
                <h3>Drag and drop a file or select add Image</h3>
            </div>
        </div>
        <div class="file-upload-content">
            <!-- <img class="file-upload-image" src="#" alt="your image" /> -->
            <img class="file-upload-image" id="upload-image" src="#" alt="your image" />
            <div class="image-title-wrap">
                <button type="button" onclick="removeUpload()" class="remove-image">Remove <span class="image-title">Uploaded Image</span></button>
            </div>
        </div>
    </div>
  
	<!-- 웹캠, 테스트 결과 창 -->
    <div id="webcam-container"></div>
    <div id="label-container"></div>
	
  	<!-- jsdelivr의 src -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.3.1/dist/tf.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@teachablemachine/image@0.8/dist/teachablemachine-image.min.js"></script>
  
  	<!-- 따로 생성한 js 파일의 src -->
    <script src="scripts.js"></script>
</body>

<!-- Copyright (c) 2022 by Aaron Vanston (https://codepen.io/aaronvanston/pen/yNYOXR)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -->

</html>

/* CSS 파일 */

body {
    font-family: sans-serif;
    background-color: #eeeeee;
}

.file-upload {
    background-color: #ffffff;
    width: 600px;
    margin: 0 auto;
    padding: 20px;
}

.file-upload-btn {
    width: 100%;
    margin: 0;
    color: #fff;
    background: #1FB264;
    border: none;
    padding: 10px;
    border-radius: 4px;
    border-bottom: 4px solid #15824B;
    transition: all .2s ease;
    outline: none;
    text-transform: uppercase;
    font-weight: 700;
}

.file-upload-btn:hover {
    background: #1AA059;
    color: #ffffff;
    transition: all .2s ease;
    cursor: pointer;
}

.file-upload-btn:active {
    border: 0;
    transition: all .2s ease;
}

.file-upload-content {
    display: none;
    text-align: center;
}

.file-upload-input {
    position: absolute;
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100%;
    outline: none;
    opacity: 0;
    cursor: pointer;
}

.image-upload-wrap {
    margin-top: 20px;
    border: 4px dashed #1FB264;
    position: relative;
}

.image-dropping,
.image-upload-wrap:hover {
    background-color: #1FB264;
    border: 4px dashed #ffffff;
}

.image-title-wrap {
    padding: 0 15px 15px 15px;
    color: #222;
}

.drag-text {
    text-align: center;
}

.drag-text h3 {
    font-weight: 100;
    text-transform: uppercase;
    color: #15824B;
    padding: 60px 0;
}

.file-upload-image {
    max-height: 200px;
    max-width: 200px;
    margin: auto;
    padding: 20px;
}

.remove-image {
    width: 200px;
    margin: 0;
    color: #fff;
    background: #cd4535;
    border: none;
    padding: 10px;
    border-radius: 4px;
    border-bottom: 4px solid #b02818;
    transition: all .2s ease;
    outline: none;
    text-transform: uppercase;
    font-weight: 700;
}

.remove-image:hover {
    background: #c13b2a;
    color: #ffffff;
    transition: all .2s ease;
    cursor: pointer;
}

.remove-image:active {
    border: 0;
    transition: all .2s ease;
}

// JavaScript 파일

// Teachable Machine 코드
// More API functions here:
// https://github.com/googlecreativelab/teachablemachine-community/tree/master/libraries/image
// the link to your model provided by Teachable Machine export panel
const URL = "https://teachablemachine.withgoogle.com/models/WXmTZBNlH/";

let model, webcam, labelContainer, maxPredictions;

// Load the image model and setup the webcam
async function init() {
        const modelURL = URL + "model.json";
const metadataURL = URL + "metadata.json";

// load the model and metadata
// Refer to tmImage.loadFromFiles() in the API to support files from a file picker
// or files from your local hard drive
// Note: the pose library adds "tmImage" object to your window (window.tmImage)
model = await tmImage.load(modelURL, metadataURL);
maxPredictions = model.getTotalClasses();

// Convenience function to setup a webcam
const flip = true; // whether to flip the webcam
webcam = new tmImage.Webcam(200, 200, flip); // width, height, flip
await webcam.setup(); // request access to the webcam
await webcam.play();
window.requestAnimationFrame(loop);

// append elements to the DOM
document.getElementById("webcam-container").appendChild(webcam.canvas);
labelContainer = document.getElementById("label-container");
for (let i = 0; i < maxPredictions; i++) { // and class labels
    labelContainer.appendChild(document.createElement("div"));
        }
    }

async function loop() {
    webcam.update(); // update the webcam frame
await predict();
window.requestAnimationFrame(loop);
    }

// run the webcam image through the image model
async function predict() {
        // predict can take in an image, video or canvas html element
        const prediction = await model.predict(webcam.canvas);
for (let i = 0; i < maxPredictions; i++) {
            const classPrediction =
prediction[i].className + ": " + prediction[i].probability.toFixed(2);
labelContainer.childNodes[i].innerHTML = classPrediction;
        }
    }

// 이미지 업로드 JavaScript 코드
function readFile(input) {
    if (input.files && input.files[0]) {
        var reader = new FileReader();

        reader.onload = function (e) {
            var htmlPreview =
                '<img width="200" src="' + e.target.result + '" />' +
                '<p>' + input.files[0].name + '</p>';
            var wrapperZone = $(input).parent();
            var previewZone = $(input).parent().parent().find('.preview-zone');
            var boxZone = $(input).parent().parent().find('.preview-zone').find('.box').find('.box-body');

            wrapperZone.removeClass('dragover');
            previewZone.removeClass('hidden');
            boxZone.empty();
            boxZone.append(htmlPreview);
        };

        reader.readAsDataURL(input.files[0]);
    }
}

function reset(e) {
    e.wrap('<form>').closest('form').get(0).reset();
    e.unwrap();
}

$(".dropzone").change(function () {
    readFile(this);
});

$('.dropzone-wrapper').on('dragover', function (e) {
    e.preventDefault();
    e.stopPropagation();
    $(this).addClass('dragover');
});

$('.dropzone-wrapper').on('dragleave', function (e) {
    e.preventDefault();
    e.stopPropagation();
    $(this).removeClass('dragover');
});

$('.remove-preview').on('click', function () {
    var boxZone = $(this).parents('.preview-zone').find('.box-body');
    var previewZone = $(this).parents('.preview-zone');
    var dropzone = $(this).parents('.form-group').find('.dropzone');
    boxZone.empty();
    previewZone.addClass('hidden');
    reset(dropzone);
});

function readURL(input) {
    if (input.files && input.files[0]) {
        var reader = new FileReader();
        reader.onload = function (e) {
            $('.image-upload-wrap').hide();
            $('.file-upload-image').attr('src', e.target.result);
            $('.file-upload-content').show();
            $('.image-title').html(input.files[0].name);
        };
        reader.readAsDataURL(input.files[0]);
    } else {
        removeUpload();
    }
}

function readURL(input) {
    if (input.files && input.files[0]) {

        var reader = new FileReader();

        reader.onload = function (e) {
            $('.image-upload-wrap').hide();

            $('.file-upload-image').attr('src', e.target.result);
            $('.file-upload-content').show();

            $('.image-title').html(input.files[0].name);
        };

        reader.readAsDataURL(input.files[0]);

    } else {
        removeUpload();
    }
}

function removeUpload() {
    $('.file-upload-input').replaceWith($('.file-upload-input').clone());
    $('.file-upload-content').hide();
    $('.image-upload-wrap').show();
}
$('.image-upload-wrap').bind('dragover', function () {
    $('.image-upload-wrap').addClass('image-dropping');
});
$('.image-upload-wrap').bind('dragleave', function () {
    $('.image-upload-wrap').removeClass('image-dropping');
});

2-2. 전체적인 코드 리뷰

앞서 템플릿을 통해 이미지 업로드까지 구현했다.

이제는 업로드 된 이미지를 동물상 테스트에 적용해야 한다.

우선 코드에 사용된 웹캠의 잔재를 전부 없애야 한다.

그럼 간단히 코드 리뷰를 해야 하는데,

<button type="button" onclick="init()">Start</button>

위의 코드는 index 파일의 HTML 코드의 일부분이다.

작동되는 순서에 따라 리뷰를 하자면, 우선 만들어진 웹의 Start 버튼을

클릭하게 되면, 임의의 함수인 init 함수가 실행된다.

init 함수는 JavaScript 코드의 함수이기에

JavaScript 파일로 넘어가도록 한다.

async function init() {
        const modelURL = URL + "model.json";
const metadataURL = URL + "metadata.json";

우선 위의 코드는 Start 버튼을 클릭하면서 init 함수가 실행되고,

Teachable Machine에서 학습시켰던 model.jsonmetadata.json

초기값으로 불러오게 된다.

init 자체가 초기화라는 의미로 곧 잘 사용되는 것을 보면,
임의의 함수를 만드는 작업에 쓰이는 init은 초기값을 의미하는 함수로 보인다.
예를 들면, 에디터 툴과 Git의 연동 작업에서 git init 명령어를 통해
GitHub의 로컬 저장소를 초기화할 때 사용하는 단어가 바로 init의 경우다.

여담은 이제 그만하도록 하고, 다음 코드를 보겠다.

model = await tmImage.load(modelURL, metadataURL);
maxPredictions = model.getTotalClasses();

한 줄씩 리뷰를 하자면,

init 함수를 실행시키면서 model.jsonmetadata.jsonload하는 작업을

model이라는 변수에 담아두고, model에 해당하는 앞서 지정한 모든 Class들을

예측할 수 있도록 불러오는 작업을 하고,

const flip = true;
webcam = new tmImage.Webcam(200, 200, flip);
await webcam.setup();
await webcam.play();
window.requestAnimationFrame(loop);

웹캠을 좌우 반전시키고 넓이 등을 설정, 웹캠에 접근해서 실행시키도록 하고,

웹캠이 실행되면서 loop 반복문을 실행시키는 코드로 이해했다.

document.getElementById("webcam-container").appendChild(webcam.canvas);
labelContainer = document.getElementById("label-container");
for (let i = 0; i < maxPredictions; i++) {
    labelContainer.appendChild(document.createElement("div"));
        }
    }

다음 위의 코드를 간단하게 보자면,

웹캠을 출력할 container를 만들어주고, 자식요소로 웹캠을 띄울 canvas

생성해준다!!..라고 이해했다...

그럼 웹캠의 실행으로 작동하는 loop 반복문 부분을 보자면,

async function loop() {
    webcam.update();
await predict();
window.requestAnimationFrame(loop);
    }

웹캠이 업데이트가 되면 반복해서 동물상 예측을 하고,

웹캠의 움직임에 따라 계속해서 loop 함수를 실행하는 코드이다.

그럼 여기서 나오는 예측이라는 predict는 무엇인가?

다음 코드에서 등장한다.

async function predict() {
        const prediction = await model.predict(webcam.canvas);
for (let i = 0; i < maxPredictions; i++) {
            const classPrediction =
prediction[i].className + ": " + prediction[i].probability.toFixed(2);
labelContainer.childNodes[i].innerHTML = classPrediction;
        }
    }

위의 코드는 predict라는 동물상 예측 함수이다.

이해한 바로 간단하게 리뷰해 보자면,

하나의 영상이라는 차원의 webcam.canvas를 이미지로써 예측하도록 지정하고,

앞서 model.jsonmetadata.json을 모든 클래스에서 예측하도록 지정해 둔

maxPredictionsfor문을 사용하여 예측을 반복해서 결과를

도출할 수 있도록 코드가 짜여져 있다.

그렇게 나온 동물상 예측 결과를

<예측 결과의 className> : <예측 결과의 확률> 형식으로

HTML에 출력하면서 결과가 나온다.

예시로 들자면, 곰 상 : 0.845 이런 식이다.

2-3. 업로드된 이미지 적용하기

본격적으로 앞서 리뷰했던 코드에서 웹캠 부분을 이미지로 변경하거나,

아예 사용되지 않는 웹캠은 삭제하는 작업을 하도록 하겠다.

일단 간단하게 생각할 수 있는 부분은 바로 반복문의 필요성이다.

웹캠의 경우 계속해서 움직이는 모습을 반복적으로 예측을 해야 하기 때문에

반복문을 사용해서 끊임없이 값이 변동한다.

하지만 이미지는?

그런거 없다. 깔끔하게 멈춰있다.

그럼 당연히! 반복문은 OUT!

당연히 웹캠이라는 단어도 전부 지워보도록 하겠다.

const URL = "https://teachablemachine.withgoogle.com/models/WXmTZBNlH/";

let model, webcam, labelContainer, maxPredictions;

async function init() {
        const modelURL = URL + "model.json";
const metadataURL = URL + "metadata.json";
  
model = await tmImage.load(modelURL, metadataURL);
maxPredictions = model.getTotalClasses();

// 웹캠, loop 부분 삭제
/*const flip = true;
webcam = new tmImage.Webcam(200, 200, flip);
await webcam.setup();
await webcam.play();
window.requestAnimationFrame(loop);*/

// 웹캠 부분 삭제
// document.getElementById("webcam-container").appendChild(webcam.canvas);
labelContainer = document.getElementById("label-container");
for (let i = 0; i < maxPredictions; i++) {
    labelContainer.appendChild(document.createElement("div"));
        }
    }

// 반복문 삭제
/*async function loop() {
    webcam.update();
await predict();
window.requestAnimationFrame(loop);
    }*/

// 웹캠 부분 삭제
async function predict() {
        // const prediction = await model.predict(webcam.canvas);
  		// 이 부분에 이미지를 예측하도록 수정한다.
for (let i = 0; i < maxPredictions; i++) {
            const classPrediction =
prediction[i].className + ": " + prediction[i].probability.toFixed(2);
labelContainer.childNodes[i].innerHTML = classPrediction;
        }
    }

위의 코드에서 이제 해야 할 것은 이미지 업로드!

우선 웹캠의 id값을 getElementById로 불러와서 예측했다는 것을

위의 코드에서 삭제한 웹캠 부분을 통해서 볼 수 있다.

document.getElementById("webcam-container").appendChild(webcam.canvas);

바로 이 부분이다!

웹캠은 자식요소로 canvasHTML에 띄워주는 작업을 했지만,

이미지는 그런거 필요 없다.

자식요소 생성의 appendChild부분은 과감하게 없애주도록 하고,

이미지를 사용할 수 있는 코드를 구글링으로 찾아보도록 하자!

전체적으로 코드를 쭉 보다가 주석에 GitHub 주소를 찾았다.

teachable machine GitHub 이 링크이며,

Teachable Machine 공식 GitHub이다.

사용 설명서와 같은 느낌으로 작성되어 있어서 쭉 내리며 찾던 중,

model.predict(
  image: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap,
  flipped = false
)

이러한 코드를 찾았다.

내용은 model.predict()의 인자로 이미지나, 캔ㄴ버스, 비디오 등이 들어갈 수 있고,

좌, 우 반전을 할지의 여부를 인자로 입력하면 된다고 한다.

model.predict(image, false);

이렇게 말이다.

웹캠 대신, 이미지를 넣기는 했지만, 이미지의 해당 id를 불러도록 id값을 찾는

작업을 시작하도록 한다.

간단하다. 아주.

개발자 메뉴를 통해 이미지의 id값은 upload-image라는 것을 알아냈다.

정리해서 코드를 보자면,

async function predict() {
 	 	var image = document.getElementById("upload-image");
  		// 해당 이미지의 id 가져오기
        const prediction = await model.predict(image, false);
  		// 이미지로써의 예측을 실행하도록 수정
for (let i = 0; i < maxPredictions; i++) {
            const classPrediction =
prediction[i].className + ": " + prediction[i].probability.toFixed(2);
labelContainer.childNodes[i].innerHTML = classPrediction;
        }
    }

일단 필자가 이해한 바로만 주절주절 설명했다.

기타 CSS, 이미지 템플릿 관련된 사항은 생략하도록 하겠다...

마지막으로 웹캠을 이용할 시에 자동으로 예측되는 것을

사진을 올리고, 버튼을 클릭하는 방식으로 바꿔야 한다.

다시 HTML 코드로 돌아와서 Start 버튼 옆에

<button type="button" onclick="predict()">Predict</button>

이렇게 predict() 함수를 실행시키는 버튼을 만들어

Start 버튼 클릭 → 얼굴 사진 올리기 → Predict 버튼으로 결과 출력

이러한 순서로 동물상 테스트를 할 수 있는 웹을 만들었다.


필자는 공룡 상 98%가 나왔다.

난생 처음 들어봤다... 공룡 상...

뭐... 결과는 나왔다! 아마 데이터로써의 자료가 부족했던 것 같다.

안일하게 너무 적은 데이터를 사용하긴 했다.

여러가지로 어려운 시간이었다.

물론 프로그래밍 코드를 짠 것에 대한 것보다도, 이해하고 해석하는 부분이
굉장히 어려운 부분이 많았다.
하나씩 차근차근 영어 뜻 생각하며 해석을 해보고 이해하려고 노력은 했다...노력은...
사실 위의 내용이 맞는 지도 작성하면서도 긴가민가한 부분이 많았다.
추후에 다른 수업을 통해 늘도록 열심히 하는 수 밖에...


마치며...

우선 위의 과정을 거치며 만들어진 웹을 보고 느낀점은...

프로젝트 구상을 처음부터 다시 해서 다른 데이터를 통해 다른 결과를

도출하는, 코드는 같을 수 있지만, 학습된 데이터를 다르게 하고

다른 결과물이 나오는 음...예를 들면 동안 테스트? 이런 것을 다시 만들어 보고 싶다.

아니 만들어야겠다.

이렇게 쭉 이어간다면 충분히 가능할 것 같다.

일단 인터페이스를 좀 꾸미고 편의성을 갖춘 완성된 웹을 만들고

추후에 도전해봐야겠다.

다음 시간에는 인터페이스, 편의성을 고려해서 웹을 좀 꾸밀 예정이다.

profile
토끼보다는 거북이처럼 꾸준하게

0개의 댓글