keypoint detection 모델을 이용한 운동 보조 웹 애플리케이션 (2022/12/05 ~ 2022/12/23, 2023/08/23 ~ 2023/09/01)
- 가끔 맨몸과 아령 1개로 홈트를 하는데 운동 사이클을 관리 해주는 무언가가 있으면 좋겠다는 생각을 하게 되었습니다.
- 그래서 홈트가 유행하고 있는 지금 시대에 keypoint detection을 활용해서 홈트를 하는데 편의성을 제공하고자 웹 서비스를 만들고자 했습니다.
- 여기서 편의성이란 몇 세트인지 한 세트에 몇개를 할 것인지 설정을 하고 1개를 할 때마다 카운팅 해주고 1세트가 끝날 때마다 휴식시간을 타이머 해주거나 크게 잘못된 자세를 지적해주거나 등입니다.(현재 미구현 상태)
- mediapipe pose(https://developers.google.com/mediapipe/solutions/vision/pose_landmarker/)
- keypoint detection을 사용하는 이유는 포즈를 추정하는데 좋기 때문입니다.
- 첫 번째 선택한 Openpose 모델 성능 테스트한 결과
- 탐지 정확도도 좀 떨어지고
- pose를 파악하기 위한 keypoint 개수도 적은 편이기 때문에
- 운동 보조를 위한 keypoint detection 모델에는 적합하지않다고 판단했습니다.
- 두 번째 선택한 mediapipe pose 모델 성능 테스트한 결과
- openpose보다 탐지가 훨씬 정확하고
- keypoint 개수도 많은 편이고
- keypoint마다 카메라와 신체 사이의 거리를 출력해주고
- 어떤 keypoint가 카메라에 보이는 상태인지 안보이는 상태인지 출력해주기 때문에
- 운동 보조를 위한 keypoint detection 모델로 적합하다고 판단해서 선택했습니다.
- Mediapipe pose 모델 관련 링크(https://developers.google.com/mediapipe/solutions/vision/pose_landmarker/)
- 아나콘다 가상환경에서 Django를 설치 후 healthweb 폴더에 Django 프로젝트 'health'와 앱 'web', 'board', 'common' 생성
- web: 운동 선택, 선택 후 운동 보조 기능을 위한 앱
- board: 게시판 기능을 위한 앱
- common: 로그인, 로그아웃, 회원가입 기능을 위한 앱
- 'health' 프로젝트 settings.py의 DATABASES
- mysql엔진을 사용하고 .env파일을 이용하여 깃허브에서 DB에 대한 정보를 볼 수 없게 했습니다.
- Models.py
- Exercises
- 보조할 운동에 대한 정보를 넣기 위한 모델
- id, 한글 이름, 영어 이름, 카메라 방향, 자극 부위에 대한 정보가 들어갑니다.
- django shell을 이용하여 임시로 사용할 2개의 데이터를 입력했습니다.
- Urls.py
- url과 view를 매칭시키는 역할을 합니다.
- Views.py
- 사용자의 요청을 받아 처리합니다.
- index
- 운동 선택 화면을 위한 뷰함수입니다.
- screen
- url에서 얻은 id 값에 매칭되는 데이터를 Exercises에서 가져와 html에 전달하고 처리 결과를 반환합니다.
- HTML
- 홈 화면(운동 선택 화면)
- Let’s Go 버튼 누를 시 이동할 url 형식
- 운동 선택 후 화면
- views.py에서 넘어온 exercise_data 이용
- mediapipe pose와 관련된 외부 라이브러리를 가져온 후 모델을 적용시키는 코드를 가져오고 카운팅 알고리즘을 추가한 mediapipe.js 파일을 로드
- Models.py
- Question
- 게시판에 등록되는 질문에 대한 정보를 위한 모델
- 작성자, 질문 제목, 질문 내용, 수정 날짜, 등록 날짜, 추천에 대한 정보가 들어갑니다.
- 작성자와 추천자는 장고 내장 모델인 User과 테이블 관계를 가집니다.
- User:작성자 -> 일대다
- User:추천자 -> 다대다
- 작성자 계정 삭제되면 해당 작성자가 작성한 질문도 삭제되도록 했습니다.
- Answer
- 질문에 등록되는 답변에 대한 정보를 위한 모델
- 작성자, 답변하는 질문, 답변 내용, 수정 날짜, 등록 날짜, 추천에 대한 정보가 들어갑니다.
- 답변하는 질문은 Question 모델과 테이블 관계를 가집니다.
- 질문:답변 -> 일대다
- 질문이 삭제되면 해당 질문에 대한 답변도 삭제되도록 했습니다.
- 작성자와 추천자는 장고 내장 모델인 User과 테이블 관계를 가집니다.
- 작성자:User -> 일대다
- 추천자:User -> 다대다
- 작성자 계정 삭제되면 해당 작성자가 작성한 질문도 삭제되도록 했습니다.
- Urls.py
- url과 view를 매칭시키는 역할을 합니다.
- Views
- 사용자의 요청을 받아 처리합니다.
- Base_views.py
- index
- 질문 목록을 위한 뷰함수입니다.
- GET 방식으로 동작하여 page는 기본값으로 1, kw는 기본값으로 ''을 넣습니다.
- question_list는 create_date를 기준으로 내림차순 즉 최신 글이 먼저 보이게끔 했습니다.
- 장고 내장 모듈인 Q를 이용하여 질문 제목, 질문 내용, 답변 내용, 질문 작성자, 답변 작성자에 kw이 포함되는 question_list를 가져오도록 해서 검색 기능을 구현했습니다.
- 장고 Paginator을 이용하여 한 페이지 당 10개의 질문 목록을 보여지게 했습니다.
- 페이지, 해당 페이지의 질문 목록들, 검색 키워드 데이터를 각각 page, page_obj, kw에 담고 질문 목록 화면에 전달합니다.
- detail
- 질문 클릭 후 질문 상세 화면을 위한 뷰함수입니다.
- GET 방식으로 동작합니다.
- question_id에 해당하는 질문 데이터를 question에 담고 질문 클릭 후 화면에 전달합니다.
- Question_views.py
- 장고의 login_required 데코레이터를 이용하여 질문 등록, 질문 수정, 질문 삭제, 질문 추천은 로그인이 필요하도록 했습니다.
- question_create
- 질문 목록 화면에서 질문 등록을 위한 뷰함수입니다.
- GET 방식으로 동작하면 질문 폼을 보여줍니다.
- POST 방식으로 동작하면 폼의 내용(질문 제목, 내용)과 함께 요청자는 작성자, 현재시간은 등록시간에 넣어 Question 모델에 저장한 후 질문 목록 화면으로 이동합니다.
- question_modify
- 질문 상세 화면에서 질문 수정을 위한 뷰함수입니다.
- question_id에 해당하는 질문 데이터를 question에 담습니다.
- 해당 질문의 작성자와 요청자가 다를 경우 에러 메세지를 발생시켜 해당 질문 상세 화면에 보냅니다.
- GET 방식으로 동작하면 질문 폼을 보여줍니다.
- POST 방식으로 동작하면 폼의 내용(질문 제목, 내용)과 함께 현재시간은 수정시간에 넣어 수정한 후 해당 질문 상세 화면으로 이동합니다.
- question_delete
- 질문 상세 화면에서 질문 삭제를 위한 뷰함수입니다.
- question_id에 해당하는 질문 데이터를 question에 담습니다.
- 해당 질문의 작성자와 요청자가 다를 경우 에러 메세지를 발생시켜 해당 질문 상세 화면에 보냅니다.
- question을 삭제한 후 질문 목록 화면으로 이동합니다.
- question_vote
- 질문 상세 화면에서 질문 추천을 위한 뷰함수입니다.
- question_id에 해당하는 질문 데이터를 question에 담습니다.
- 해당 질문의 작성자와 요청자가 같을 경우 에러 메세지를 발생시켜 해당 질문 상세 화면에 보냅니다.
- 해당 질문의 추천자에 요청자를 추가한 후 질문 상세 화면으로 이동합니다.
- answer_views.py
- 답변 등록, 답변 수정, 답변 삭제, 답변 추천은 로그인이 필요하도록 했습니다.
- answer_create
- 질문 상세 화면에서 답변 등록을 위한 뷰함수입니다.
- question_id에 해당하는 질문 데이터를 question에 담습니다.
- GET 방식으로 동작하면 답변 폼을 보여줍니다.
- POST 방식으로 동작하면 폼의 내용(답변 내용)을 포함하여 요청자는 작성자, 현재시간은 등록시간, 해당 질문은 답변하는 질문에 넣어 Answer 모델에 저장한 후 해당 질문 상세 화면으로 이동합니다.
- 답변 등록 후 앵커 태그를 이용하여 스크롤이 등록한 답변으로 이동합니다.
- answer_modify
- 질문 상세 화면에서 답변 수정을 위한 뷰함수입니다.
- answer_id에 해당하는 답변 데이터를 answer에 담습니다.
- 해당 답변의 작성자와 요청자가 다를 경우 에러 메세지를 발생시켜 해당 답변이 있는 질문 상세 화면으로 보냅니다.
- GET 방식으로 동작하면 답변 폼을 보여줍니다.
- POST 방식으로 동작하면 폼의 내용(답변 내용)을 포함하여 현재시간은 수정시간에 넣어 수정한 후 해당 답변이 있는 질문 상세 화면으로 이동합니다.
- 답변 수정 후 앵커 태그를 이용하여 스크롤이 수정한 답변으로 이동합니다.
- answer_delete
- 질문 상세 화면에서 답변 삭제 위한 뷰함수입니다.
- answer_id에 해당하는 답변 데이터를 answer에 담습니다.
- 해당 답변의 작성자와 요청자가 다를 경우 에러 메세지를 발생시켜 해당 답변이 있는 질문 상세 화면으로 보냅니다.
- answer을 삭제한 후 해당 답변이 있었던 질문 상세 화면으로 이동합니다.
- answer_vote
- 질문 상세 화면에서 답변 추천을 위한 뷰함수입니다.
- answer_id에 해당하는 답변 데이터를 answer에 담습니다.
- 해당 답변의 작성자와 요청자가 같을 경우 에러 메세지를 발생시킵니다.
- 해당 답변의 추천자에 요청자를 추가한 후 해당 답변이 있는 질문 상세 화면으로 이동합니다.
- 답변 추천 후 앵커 태그를 이용하여 스크롤이 추천한 답변으로 이동합니다.
- HTML
- 질문 목록 화면
- 글 클릭 후 질문 상세 화면
- 질문 등록 화면
- 검색 후 질문 목록 화면
- Model
-
장고 내장 모델인 User을 사용했습니다.
- Views.py
- 사용자의 요청을 받아 처리합니다.
- LoginView, LogoutVuew
- 장고 내장 기능인 로그인, 로그아웃 기능을 사용했습니다.
- signup
- 회원가입을 위한 뷰함수입니다.
- 장고 내장 함수 authenticate, login를 사용했습니다.
- GET 방식으로 동작하면 회원가입 폼을 보여줍니다.
- POST 방식으로 동작하면 form이 유효하다면 form 내용을 User에 저장하고 유저이름과 비밀번호를 가져와 authenticate로 인증하고 login로 로그인 시킵니다.
- Urls.py
- url과 view를 매칭시키는 역할을 합니다.
- HTML
- 로그인 화면
- 로그아웃 화면
- 회원가입 화면
- 로컬에서 만든 것을 깃허브에 올리고 AWS EC2에서 git clone하여 1차 배포했습니다.
- Django는 웹서버와 직접 통신할 수 없기 때문에 중간다리로 uWSGI python 패키지를 설치하여 Django와 연결했습니다.
- 웹서버 애플리케이션인 nginx를 설치하여 uWSGI와 연결했습니다.
- uwsgi.service 파일을 만들어 uWSGI를 백그라운드에서 실행이 되도록하여 2차 배포했습니다.
- 가비아에서 도메인을 구입하고 AWS Route 53을 통해 구매한 도메인과 EC2 인스턴스을 연결하여 최종 배포했습니다.
- 이 후, 개선한 부분이 있을 때마다 github에 재배포했습니다.
- http://myhealthweb.site
- 웹캠 기능은 다음 과정을 거친 후 사용가능합니다.
- 크롬 주소창에 chrome://flags을 입력합니다.
- 상단 search 입력란에 Insecure origins treated as secure을 입력합니다.
- Insecure origins treated as secure 아래에
http://myhealthweb.site
을 입력하고 Disabled를 Enabled로 바꿔줍니다.
- 서버가 1코어 1기가램이라 Django에서 mediapipe pose 모델을 적용시킨 웹캠이 프레임 드랍이 심해서 카운팅을 위한 알고리즘, 모델 사용을 CSR로 처리하여 개선
- sqlite3에서 mysql로 DB 교체
- block 기능을 이용하여 html 구조 개선
- 커뮤니티를 위한 게시판 추가
- 장고 내장 모델 user을 이용한 로그인, 로그아웃, 회원가입 기능 구현
- 집에서 가장 쉽게 할 수 있는 운동으로 맨몸 운동인 푸쉬업과 스쿼트가 떠올라서 보조할 운동으로 이 2가지를 선택했습니다.
- 옆모습을 찍은 youtube의 운동 영상에 mediapipe pose를 적용시켜서 keypoint가 어떻게 변하는지 눈으로 관찰했습니다.
- 가장 크게 변화를 보이는 keypoint의 변수값을 따로 print해서 다시 한번 관찰했습니다.
- 옆모습을 기준으로 카운팅을 하기 위한 크게 4가지의 운동 알고리즘을 구현했습니다.
- 인접한 keypoint들 간의 각도를 출력해주는 각도 계산 알고리즘
- 사용자가 현재 왼쪽 옆모습인지 오른쪽 옆모습인지 확인하는 방향 확인 알고리즘
- 각 운동 보조에 필요한 사용자의 keypoint가 카메라에 보이고 주요 keypoint들 간의 각도 값에 따른 상태를 확인하는 준비 확인 알고리즘
- 이전에 확인한 가장 크게 변화를 보이는 keypoint를 이용한 카운팅 알고리즘
- 각도 계산 알고리즘
- keypoint의 x, y, z 좌표 변수들을 이용했습니다.
- 인접한 keypoint들 간의 각도를 두 벡터의 사이각 공식으로 구하여 값을 리턴합니다.
- 방향 확인 알고리즘
- 카메라와 keypoint 간의 거리를 나타내는 z 변수를 이용했습니다.
- 좌측 어깨 끝이 더 가까우면 left, 우측 어깨 끝이 더 가까우면 right를 리턴합니다.
- 준비 확인 알고리즘
- 가시성을 나타내는 visibility 변수를 이용했습니다.
- 방향에 따라 운동 보조에 필요한 keypoint가 가시성 상태와 주요 keypoint들 간의 각도 상태에 따라 준비 상태를 리턴합니다.
- 카운팅 알고리즘
- 각 운동에 가장 크게 변하는 keypoint들 간의 각도를 이용했습니다.
- 각도가 일정 이상이 되면 up, 이하가되면 down이 되는 status 변수를 만들었습니다.
- status 변수를 이용하여 down에서 up에 되면 카운트 1 오르게 했습니다.
- health : django 프로젝트 폴더
- requirements.txt : 설치한 패키지들의 정보가 들어있는 파일
- 웹캠 화면에 keypoint detection 모델을 적용시켰습니다.
- 제가 만든 웹앱을 배포해보았습니다.
- keypoint detection 모델을 적용시킨 웹캠 화면의 심한 프레임 드랍을 SSR에서 CSR로 바꿔서 개선했습니다.
- 데이터가 2개뿐이지만 Django에서 DB를 이용해봤습니다.
- 미흡하지만 커뮤니케이션을 위한 게시판을 만들어 봤습니다.
- 더 좋은 서비스를 하려면 운동 종류 늘릴 필요가 있고 앞서 언급한 편의성에 대한 기능이 필요하다고 생각합니다.
- 좀 더 정확한 카운팅 알고리즘이 필요하다고 생각합니다.
- https를 적용한다면 웹캠 기능을 사용하기 위한 번거로운 작업을 할 필요가 없어질 것이라 생각합니다.
- 실서비스에서는 추후 개선을 위해 사용자 행동에 대한 로그 기능이 있으면 좋을 것 같습니다.