본문 바로가기
웹개발/DIOS

[웹개발 - DIOS] Teachable Machine 를 이용한 운동 횟수 세는 페이지 / Teachable Machine / 티처블머신 / 머신러닝

by 오엥?은 2023. 2. 23.
반응형

 

⌦  Teachable Machine 를 이용한 운동 횟수 세는 페이지

 


☑︎  Teachable Machine

https://teachablemachine.withgoogle.com/

 

Teachable Machine

Train a computer to recognize your own images, sounds, & poses. A fast, easy way to create machine learning models for your sites, apps, and more – no expertise or coding required.

teachablemachine.withgoogle.com

이 프로젝트를 기획하게 된 계기가 Teachable Machine 이다. Teachable Machine은 구글에서 만든 웹기반 노코드 인공지능 학습 툴이다. 이미지, 사운드, 자세를 인식하도록 컴퓨터를 학습시켜서 사이트, 앱 등에 사용할 수 있는 머신러닝 모델을 쉽고 빠르게 만들 수 있다.

 

머신러닝에 대해 잘 몰랐고 써본 경험도 없지만 머신러닝 입문용으로 사용할 만한 것 같았고, 잘 활용하면 재밌는 웹 서비스를 만들 수 있을 것 같다는 생각이 들어서 사용법을 찾아가며 써 보았다.

 

이 프로젝트는 운동 개수를 세는 것이기 때문에 포즈 프로젝트를 사용했다.

 

들어가면 이런 화면이 나오는데, 이제 사진을 찍거나 업로드하여 학습시키면 된다. 이 프로젝트에서는 스쿼트, 런지, 팔굽혀펴기 세가지의 운동 종류를 학습시켰는데, 방법은 세가지 모두 유사하다.

 

스쿼트를 예로 들면 Class 1 에는 정자세로 서 있는 사진을 웹캠으로 찍어서 학습시켰다. 사용자가 어떤 자세로 시작할 것인지 모르기 때문에 서 있는 것도 여러 방향으로 촬영했다. 밝은 곳 어두운 곳도 구분하여 촬영했다!! 그래서 1000장 정도 찍었던 것 같다.

Class 2 에는 우리가 알고있는 스쿼트 자세로 사진을 찍어 학습시켰다. 마찬가지로 여러 방향으로 촬영했고, 1000장 정도 찍었다.

 

사진 촬영이 끝나면 모델 학습시키기를 누른다. 생각보다 짧은 시간에 학습이 끝난다. 학습이 끝나면 모델을 미리보기 할 수 있다.

 

Class 1 : 정자세 / Class 2 : 스쿼트 자세 

정자세로 서 있을 때는 Class 1 이 100% 로 출력되고, 스쿼트 자세를 했을 때는 Class 2 가 100% 로 출력된다. 100% 정확하진 않고, 엄청 섬세하진 않지만 이렇게만 해도 원하는 방향으로 서비스를 개발할 수 있을 것 같다.

 

 

미리보기에서 모델이 잘 학습되었으면 모델을 내보내야 하는데, 내보내는 방법은 업로드와 다운로드가 있다. 나는 업로드를 사용했다. 모델 업로드를 누르고 조금만 기다리면 밑 javascript 코드에 상수 URL이 생긴다. 그거 확인하고 코드 전체를 복사하여 프로젝트에 사용하면 된다.

 

 

- JavaScript

Teachable Machine 에서 만들어 준 코드를 그대로 사용해도 문제는 없지만, 내가 원하는 기능을 추가하기 위해 대략적인 코드 내용을 알아야 했다. 이리저리 바꿔보고 또 추가해보고 에러 300번 실패 200번 내면서 만들었다.

 

prediction[0] 에는 stand 자세가 학습되어있는 Class 1 정보가 들어있다. prediction[1] 에는 squat 자세가 학습되어있는 Class 2 정보가 들어있다. 그래서 각각 상수의 이름을 stand, squat 로 바꿔주었다. toFixed(2) 는 소숫점자리수를 2개로 제한한다는 뜻이다.

 

② 스쿼트의 count 가 올라가려면 stand ➡️ squat ➡️ stand 세 과정이 완료되어야 count 가 1이 올라간다. 처음엔 stand ➡️ squat 두 과정 만으로 count 를 올리는 게 아닌가 생각했는데 실험 해보니까 당연히 다시 stand 하는 과정 까지가 "1" 이겠다고 생각했다.

 

운동 start 버튼을 누르고 카메라를 켠 뒤, 운동을 시작할 때의 상태는 stand 이다. 그 다음 parseInt(squat) 가 1 (100%) 이면 predict 함수 밖에 선언해 준 변수 status 의 값이 "stand" 에서 "squat" 로 바뀌게 된다. 

 

그리고 다시 stand 가 0.98(98%) 초과인 상황에서 status 가 "squat" 인 상태라면 stand ➡️ squat ➡️ stand 의 과정이 이루어졌다는 소리가 된다. ( ( stand > 0.98 ) 이라는 조건을 준 이유는 stand 자세가 학습된 모델과 100% 일치했을 때만 운동 개수를 세어주는 것 보다,  98% 정도만 일치해도 개수를 세어주게 하는 것이 낫겠다는 생각이 들어서다. 2% 차이지만 분명한 차이가 있었다! 진짜다. ) 그렇기 때문에 count++ 를 해준다. 그와 동시에 우측에 만들어 둔 원형 progress bar 도 움직여야 한다. 이 모든 과정이 끝났을 때의 사용자의 자세는 stand 이므로 status 를 "stand" 로 다시 바꾼다.

 

원형 progress bar 는 count 가 올라감과 동시에 변할 수 있도록 위치한다.

진행 정도(%) / 현재 개수 / 목표 개수

- Controller

@RequestMapping(value = "squat",
            method = RequestMethod.GET)
public ModelAndView getSquat(@SessionAttribute(value = "user", required = false) UserEntity user) {

    ModelAndView modelAndView;
    if (user == null) {
        modelAndView = new ModelAndView("redirect:/dios/login");
    } else {
        modelAndView = new ModelAndView("records/squat");

        int goal = this.recordService.readCount(user);
        modelAndView.addObject("goal", goal);
    }
    return modelAndView;
}

squat 페이지도 session에 user가 null 이라면 당장 로그인 화면으로 돌아가게했다.

목표 개수를 user email 을 기준으로 select 하여 받아와서 modelAndView 에 "goal" 이 키인 값으로 넣어준다.

 

- Service

public int readCount(UserEntity user) {

    UserEntity existingUser = this.memberMapper.selectUserByEmail(user.getEmail());

    return existingUser.getGoalCount();
}

- Mapper(interface), xml

UserEntity selectUserByEmail(@Param(value = "email") String email);
<select id="selectUserByEmail"
        resultType="com.blackgreen.dios.entities.member.UserEntity">
    SELECT `email`             AS `email`,
           `goal_count`        AS `goalCount`
    FROM `dios_member`.`users`
    WHERE BINARY `email` = #{email}
    LIMIT 1
</select>

 

 

목표 개수를 modelAndView 에 넣어뒀으니 HTML에서 타임리프를 사용하여 th:value="${goal}" 의 형태로 받아올 수 있다. 이것을 javascript의 변수 value 로 받아와서 squat 페이지에 출력한다. 현재 개수는 변수 count 를 사용하여 계속 올라가는 것을 출력하면 된다. 그러면 진행 정도(%)는 Math.round((count / value) * 100) 로 표현할 수 있다.

 

 

count(현재 개수)와 goal(목표 개수)이 같아지면 측정을 멈춰야 한다.

JavaScript predict 함수 위에 작성되어야 함

predict 함수는 loop 함수 안에서 실행되는데 loop 함수는 밖에서 선언해주었던 변수 breakLoop 가 false 일 때만 실행된다. 그러므로 count === goal 일 때 breakLoop 를 true 로 바꿔준다면, 운동 측정 화면이 멈추게 된다.

 

✔️ requestAnimationFrame 은 재귀함수의 흐름과 같다. 한 번 실행한 함수 안에서 자신을 다시 불러 실행하고, 그게 반복된다.

 

 

반응형