본문 바로가기

Project

Inceptionv3 모델을 활용한 체중관리서비스 Hungry Bear 프로젝트

인턴으로 근무하면서 업무로 부여받은 프로젝트 헝그리베어.

첫 프로젝트다보니 어렵고 난해한 부분이 많았지만 그래도 성공(?) 할 수 있었다.

  1. 요구사항 명세서.
  2. 설계사양서
  3. 개발
  4. 테스트

단계의 흐름을 밟아보도록 하겠다.

첫번째 단계로

요구사항 명세서를 작성해 보았다.

목차

 

1. 소개

1.1 SRS의 목적

1.2 산출물의 범위 (Scope of product)

1.3 참조 문서

1.4 SRS 개요

 

2. 일반적인 기술 사항

2.1 제품의 관점

2.2 제품의 기능

2.3 사용자 특성

2.4 제약사항

2.5 가정 및 의존성

 

3. 상세기능 요구사항

3.1 기능적 요구사항

3.2 비기능적 요구사항

3.3 외부 인터페이스 요구사항

 

4. 성능처리 요구사항

4.1 처리량

4.2 응답 시간

4.3 실패율

4.4 부팅시간

소개

 

1.1 SRS의 목적:

- 건강을 위한 웹페이지의 요구사항을 명확하게 정의하여 개발 과정에서의 목표를 제시

- 개발자와 고객 간의 의사소통을 원활하게 하고, 프로젝트의 범위와 제약사항을 명확히 함

- 개발자가 제품을 개발하고 테스트하기 위한 기준을 제공

 

1.2 산출물의 범위 (Scope of product):

- 웹페이지를 통해 사용자가 사진을 업로드하거나 웹캠을 통해 실시간으로 사진을 캡처할 수 있음

- AI 이미지 분류 기술을 사용하여 음식을 인식하고, 음식의 칼로리를 계산하여 표시함

- 음식 칼로리 데이터베이스를 구축하여 칼로리 정보를 제공함

- 사용자 계정 관리와 개인정보 보호 기능을 제공함 (선택 사항)

- BMI계산을 통한 기초대사량 파악 후 하루 총 섭취 칼로리와 비교하여 결과 도출

 

1.3 참조 문서:

- Food Images (Food-101)

https://www.kaggle.com/datasets/kmader/food41/code

 

1.4 SRS 개요:

- 소프트웨어 요구사항 명세서의 개요 및 구성 요소 설명

- 제품의 관점, 기능, 사용자 특성, 제약사항, 가정 및 의존성 등을 요약

- SRS 문서의 목적과 범위를 기술

2. 일반적인 기술사항

 

2.1 제품의 관점:

- 건강을 위한 웹페이지를 개발하여 사용자들이 음식의 칼로리를 계산할 수 있도록 함

- AI 이미지 분류 기술을 활용하여 음식 이미지를 분석하고 칼로리를 계산

 

2.2 제품의 기능:

- 이미지 업로드 또는 웹캠을 통해 사용자의 음식사진 제공 가능

- AI 이미지 분류 모델을 사용하여 음식 이미지 인식

- 음식 칼로리 데이터베이스를 통해 인식된 음식의 칼로리 검색 및 계산

- 칼로리 정보를 사용자에게 표시하여 식단 및 건강관리 가능

- BMI를 계산하여 하루 권장 칼로리를 넘어간다면 알려주는 시스템

 

2.3 사용자 특성:

- 건강을 위해 음식의 칼로리를 파악하고 싶어하는 일반 사용자

- 영양 관리에 관심이 있는 사람들

- 다양한 음식을 섭취하는 사람들

- 바디프로필을 준비하는 사람들

 

2.4 제약사항:

- AI 이미지 분류 모델의 정확성과 신뢰성에 대한 제한

- 데이터베이스에 없는 음식의 칼로리 계산의 한계

- 사용자가 제공하는 이미지 품질과 조명 조건의 영향

 

2.5 가정 및 의존성:

- AI 이미지 분류 모델의 학습 데이터셋의 다양성과 품질에 따라 인식 정확도가 달라질 수 있음

- 음식 칼로리 데이터베이스의 정확성과 최신 정보에 의존

- 웹페이지 사용을 위한 인터넷 연결 필요

3.1 기능적 요구사항

 

1. 사용자 인터페이스 기능:

- 사용자가 웹페이지에 접속하여 이미지 파일을 업로드하거나 웹캠을 통해 사진을 캡처할 수 있어야 함.

- 업로드된 이미지 또는 캡처된 사진이 적절한 이미지 파일인지 확인해야 함.

 

2. 이미지 분류 및 인식 기능:

- AI 이미지 분류 모델을 활용하여 음식 이미지를 인식해야 함.

- 이미지 분류 결과를 통해 음식 종류를 식별해야 함.

 

3. 음식 칼로리 계산 기능:

- 인식된 음식에 대한 칼로리 정보를 데이터베이스에서 검색하여 제공해야 함.

- 칼로리 정보를 사용자에게 표시해야 함.

 

4. 에러 핸들링 기능:

- 이미지 분류 과정에서 오류가 발생한 경우 적절한 에러 메시지를 표시해야 함.

- 데이터베이스에서 해당 음식의 칼로리 정보를 찾을 수 없는 경우에 대한 예외 처리가 필요함.

 

5. 데이터베이스 관리 기능:

- 음식 칼로리 데이터베이스를 관리하고 업데이트할 수 있는 기능이 필요함.

- 새로운 음식의 칼로리 정보를 추가하거나 기존 정보를 수정할 수 있어야 함.

 

6. 프로세스 흐름:

3.2 비기능적 요구사항:

1. 정확성:

- AI 이미지 분류 모델의 정확성과 신뢰성이 요구됨.

- 음식 칼로리 데이터베이스의 정확성과 최신 정보 유지가 필요함.

 

2. 보안:

- 사용자가 업로드한 이미지나 계정 정보를 안전하게 처리해야 함.

- 개인정보 보호를 위한 적절한 보안 기능이 구현되어야 함.

 

3. 성능:

- 이미지 분류 및 칼로리 계산 속도가 빠르고 효율적이어야 함.

- 웹페이지의 응답 속도가 빠르고 사용자 경험이 좋아야 함.

 

4. 사용성:

- 사용자가 웹페이지를 쉽게 이해하고 사용할 수 있도록 직관적인 인터페이스가 필요함.

- 모바일 기기 및 다양한 화면 크기에 대한 반응형 디자인이 요구됨.

 

5. 신뢰성:

- 웹페이지의 안정성과 오류 처리가 잘 되어야 함.

- 데이터베이스 관리와 백업 시스템이 신뢰성 있게 동작해야 함.

3.3 외부 인터페이스 요구사항:

4. 성능처리 요구사항

 

4.1 처리량(Throughput):

- 이미지 파일을 처리하는 것이기 때문에 최대 20mbytes/sec의 처리량을 충족시켜야 함

 

4.2 동시 접속 수(Concurrent Session):

- 네트워크 연결이 필요 없기 때문에 동시 접속 수는 1대로 고정

 

4.3 응답 시간(Response Time):

- 태그 검색 탭에서 검색 결과를 화면에 보여주는 데에 5초 이내의 시간이 걸려야 함

- 만약 실제로 테스트하고 3초 이내이지만 사용자가 불편하다고 느낀다면 요구치 수준을 높여서 충족시킬 수 있도록 함

 

4.4 리소스 사용량(Memory/CPU/Disk/Network Usage):

- 용량이 크지 않은 웹이고, 네트워크를 사용하지 않을 것이기 때문에 리소스 사용량은 10% 미만으로 예측

 

4.5 실패율(Fail Rate):

- 사진에 찍힌 음식의 정확도가 90% 이상이어야 함

 

4.6 부팅 시간(Boot Time):

- 웹에 접속 후 5초 이내로 실행 화면이 뜰 수 있도록 해야 함

 

 

1. 요구사항 명세서.

 

2. 설계사양서

 

3. 개발

 

4 .테스트

두번째 단계는 설계에 들어가게 되었다.

설계단계에서 UML다이어그램 시퀀스다이어그램 등 그릴때 상당히 고전하였다.

그래도 포기하지않고 끝까지 준비 해보았다.

목차

 

 

1. 개요

1.1 목적

1.2 범위

1.3 용어 정의 및 약어

1.4 참고 문헌

 

2. 시스템 설명

2.1 시스템 상세 설계

2.1.1 클래스 다이어그램

시퀀스 다이어그램

 

3. 추진 일정

개요

 

 

1.1 목적

 

사용자가 업로드한 음식 이미지를 인식하고, 해당 음식의 칼로리 정보와 BMI, 기초대사량을 출력하는 웹페이지를 제작한다. 이를 통해 사용자는 영양 정보를 쉽고 빠르게 확인할 수 있으며, 건강한 식습관 형성 및 다이어트 동기부여에 도움을 받을 수 있다.

 

 

1.2 범위

 

“AI기반 식습관 개선 프로그램” 결과물인 “헝그리베어” 시스템의 개발 기술을 상세히 기술한다.

 

 

1.3 용어 정의 및 약어

DB : 데이터베이스

CNN : convolutional neural network의 약자, 인공신경망

BMI : 체질량 지수

 

 

 

1.4 참고 문헌

- Food Images (Food-101)

https://www.kaggle.com/datasets/kmader/food41/code

 

 

 

 

 

 

2 시스템 설명

 

 

2.1 시스템 상세 설계

 

 

2.1.1 클래스 다이어그램

 

클래스 다이어그램

“AI기반 식습관 개선 프로그램” 결과물인 “헝그리베어” 시스템의 주요 핵심 클래스들에 대한 다이어그램은 다음과 같다.

이미지 입력을 담당하는 Picture, 이미지가 무엇이지 판단하는 AI model, BMI를 기반으로 음식을 조절 할 수 있게 메시지를 전달하는 Main, 사용자가 볼 수 있는 Interface, 음식과 칼로리정보를 저장하는 DB, 사용자의 정보를 입력받는 Costomer로 이루어져 있다.

클래스다이어그램

2.1.2 시퀀스 다이어그램

 

시퀀스 다이어그램

“AI기반 식습관 개선 프로그램” 결과물인 “헝그리베어” 시스템의 시퀀스 다이어그램은 그림과 같다. 유저가 기본정보를 입력하면 BMI와 기초대사량을 제공한다.

식사를 하면서 음식 이미지를 입력하면 CNN모델에서 무슨 음식인지 예측한다. 예측한 음식을 DB에서 선택한 후 칼로리 정보를 가져온다. 가져온 칼로리 정보를 Main으로 입력 후 음식과 칼로리를 제공한다. Main에서 기존에 파악했던 기초대사량과 BMI를 바탕으로 하루에 먹은 음식을 계산하여 먹으면 될지 안될지를 파악해 유저에게 메시지로 전달한다.

시퀀스다이어그램

3. 추진일정

아직 많이 부족하지만 설계서를 완성해 보았다. 부족한 부분이 있으면 피드백이있있었으면 좋겠다.

 

1. 요구사항 명세서.

 

 

2. 설계사양서

 

 

3. 개발

 

 

4 .테스트

개발에 앞서, 요구사항명세서와 설계사양서를 보면서 백앤드, 프론트앤드, db, ai모델을 구축해 보았다.

우선 AI모델을 개발하는 과정이다.

food101데이터를 활용, 일전에 학습 할 수 있게 가이드가 제공된 Colab 코드를 통해 진행해서 AI를 학습시켜 보았다.

하지만 아직 학생이고, 노트북을 사용하는 관계로 사양상 11가지정도의 음식을 분류할 수 있는 모델을 개발하였다.

https://colab.research.google.com/drive/1Z5-LqlJxNslHcCzM9jChsNhGi9Y7HSC4

다음 코드를 참고하여 우리 입맛에 맞게 개발하였다. 라벨링이 미리 되어있어 간편하게 사용 할 수 있었고, 이미지 노이즈를 자체적으로 준 사진들이 포함되어있기 때문에 학습은 효율적이였다.

모델 생성 후 DB를 구축해보았다.

ER다이어그램

다음과같이 ER다이어그램을 간단하게 설계 해보았고, create, insert하는 SQL문을 작성하였다.

-- hungrybear 초기화 후 데이터베이스 생성

IF EXISTS(SELECT name FROM sys.databases WHERE (name = 'Hungrybear') OR (name = 'hungrybear'))

DROP DATABASE [Hungrybear];

CREATE DATABASE Hungrybear

--테이블 생성 (food, dailycal, customer)

USE [hungrybear]

--음식 테이블

CREATE TABLE food (

foodID INT PRIMARY KEY,

foodname VARCHAR(20),

foodcal int,

);

-- 고객 테이블

CREATE TABLE customer (

customerID INT PRIMARY KEY,

name VARCHAR(20),

weight int,

high int,

age int,

gender VARCHAR(20),

);

--음식 칼로리를 가져오기위한 테이블

CREATE TABLE dailycal (

dailycalID INT PRIMARY KEY,

foodID INT REFERENCES food(foodID),

customerID INT REFERENCES customer(customerID),

eat_food INT NULL

);

INSERT INTO food VALUES (1, 'beef_tartare', 400);

INSERT INTO food VALUES (2, 'chicken_curry', 500);

INSERT INTO food VALUES (3, 'chocolate_mousse', 400);

INSERT INTO food VALUES (4, 'french_toast', 400);

INSERT INTO food VALUES (5, 'fried_rice', 400);

INSERT INTO food VALUES (6, 'hot_dog', 300);

INSERT INTO food VALUES (7, 'ice_cream', 300);

INSERT INTO food VALUES (8, 'lasagna', 200);

INSERT INTO food VALUES (9, 'oysters', 200);

INSERT INTO food VALUES (10, 'pizza', 500);

INSERT INTO food VALUES (11, 'takoyaki', 400);

추가적으로 백앤드 자체에서 추가한 코드 ---------------

import pymysql

#추가적으로 변경한 sql문

# 데이터베이스 연결

db = pymysql.connect(

host="localhost",

user="root",

password="****",

database="hungrybear",

charset="utf8"

)

try:

# 데이터베이스 커서 생성

cursor = db.cursor()

# 열 추가 쿼리 실행

sql = "ALTER TABLE customer ADD food_calories INT NULL"

cursor.execute(sql)

# 데이터베이스에 변경 사항 저장

db.commit()

print("열 추가가 성공적으로 완료되었습니다.")

except Exception as e:

# 예외가 발생한 경우 롤백 수행

db.rollback()

print("열 추가 중 오류가 발생했습니다:", str(e))

finally:

# 데이터베이스 연결 종료

db.close()

이렇게 해서 데이터베이스를 개발 한 후에 데이터베이스 연결상태를 확인 해본다.

잘 생성이 되었다. 추가적으로 확인하고싶으면 SQL문을 활용하여 확인해보면 된다.

이제 다음에 프론트 앤드와 백앤드 개발과정이 남았다.

 

프론트 앤드를 개발 해 볼 것이다.

처음 접하는 프론트앤드다보니 chat gpt활용과 구글링이 필수적이였다.

프론트앤드는 같이하던 팀원이 그래도 많이 만들어줘서 어렵지 않게 수행 할 수 있었다.

첫번째 페이지 코드

<head>

<title>시작페이지</title>

<link rel="preconnect" href="https://fonts.googleapis.com">

<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

<link href="https://fonts.googleapis.com/css2?family=Bagel+Fat+One&display=swap" rel="stylesheet">

</head>

<style>

html, body {

height: 100vh;

width: 100vw;

margin: 0;

display: flex;

justify-content: center;

align-items: center;

background: url("/static/img/bg.png");

background-repeat: no-repeat;

background-size: cover;

background-position: center;

}

.image-container {

text-align: center;

position: relative;

}

 

.image-container img {

max-width: 100%;

max-height: 100%;

width: 400px;

height: 400px;

margin-bottom: -50px;

}

h1 {

margin-top: 0;

margin-bottom: 60px;

font-size: 100px;

font-family: 'Bagel Fat One', cursive;

color: rgb(184, 213, 190);

text-shadow: 8px 8px 0px rgb(100, 93, 93);

}

.start-button {

background-color: #e3ea19;

color: #ffffff;

padding: 20px 40px;

border: none;

border-radius: 5px;

font-size: 36px;

font-family: 'Bagel Fat One', cursive;

cursor: pointer;

}

.start-button:hover {

background-color: #0036cc;

}

 

</style>

 

<div class="image-container">

<img src="/static/img/teddybear.png" alt="헝그리베어 이미지">

<h1>헝그리베어</h1>

<a href="{{ url_for('test2') }}" class="start-button">시작하기</a>

</div>

귀여운 헝그리베어 1 페이지

2번째 페이지 코드

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>프로필입력</title>

<link rel="preconnect" href="https://fonts.googleapis.com">

<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

<link href="https://fonts.googleapis.com/css2?family=Bagel+Fat+One&display=swap" rel="stylesheet">

<style>

html, body {

height: 100%;

margin: 0;

display: flex;

justify-content: left;

align-items: center;

background: url("static/img/bg.png") no-repeat center center fixed;

background-size: cover;

}

 

.image-container {

text-align: right;

position: relative;

right: -350px;

}

 

.image-container img {

max-width: 100%;

max-height: 100%;

width: 600px;

height: 600px;

margin-bottom: -50px;

}

form {

background-color: rgba(255, 255, 255, 0.8);

padding: 30px 110px;

border-radius: 20px;

text-align: center;

position: relative;

left: 150px;

}

h1 {

font-size: 50px;

font-family: 'Bagel Fat One', cursive;

color: #1c2775;

margin-bottom: 10px;

}

label {

display: block;

margin-top: 10px;

font-size: 20px;

font-weight: bolder;

color: #060606;

}

input, select {

width: 100%;

padding: 10px;

border: none;

border-radius: 5px;

font-size: 16px;

margin-top: 5px;

}

button {

background-color: #e3ea19;

color: #ffffff;

padding: 15px 40px;

border: none;

border-radius: 5px;

font-size: 30px;

font-family: 'Bagel Fat One', cursive;

margin-top: 20px;

cursor: pointer;

}

button:hover {

background-color: #0fa5f0;

}

</style>

<script>

function goToThirdPage() {

var name = document.getElementById("name").value;

var height = parseFloat(document.getElementById("height").value);

var weight = parseFloat(document.getElementById("weight").value);

var age = parseInt(document.getElementById("age").value);

var gender = document.querySelector('input[name="gender"]:checked').value;

localStorage.setItem("name", nickname);

localStorage.setItem("height", height);

localStorage.setItem("weight", weight);

localStorage.setItem("age", age);

localStorage.setItem("gender", gender);

window.location.href = "test3.html";

}

</script>

</head>

<body>

<form action="{{ url_for('test2') }}" method="POST">

<h1>프로필 설정</h1>

<label for="name">닉네임</label>

<input type="text" id="name" name="name" required>

 

<label for="height">키 (cm)</label>

<input type="number" id="height" name="height" required>

 

<label for="weight">몸무게 (kg)</label>

<input type="number" id="weight" name="weight" required>

 

<label for="age">나이</label>

<input type="number" id="age" name="age" required>

 

<label for="gender">성별</label>

<select id="gender" name="gender" required>

<option value="male">남성</option>

<option value="female">여성</option>

</select>

<button type="submit"><a>저장</a></button>

</form>

</body>

<div class="image-container">

<img src="static/img/teddybear.png" alt="헝그리베어 이미지">

</div>

</html>

두번째 페이지 화면

3번째 페이지 코드

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>세 번째 페이지</title>

<link rel="preconnect" href="https://fonts.googleapis.com">

<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

<link href="https://fonts.googleapis.com/css2?family=Bagel+Fat+One&family=Gaegu&display=swap" rel="stylesheet">

<style>

html, body {

height: 100%;

margin: 0;

display: flex;

justify-content: center;

align-items: center;

background: url("static/img/bg.png") no-repeat center center fixed;

background-size: cover;

}

.left-section {

width: 0px;

padding: 0px;

}

.bmi-box {

background-color: rgba(255, 255, 255, 0.8);

width: 450px;

padding: 0px 15px 20px;

border-radius: 10px;

text-align: center;

position: relative;

top: -50px;

right: 320px;

font-size: 18px;

font-weight: bolder;

}

h2 {

font-size: 50px;

font-family: 'Bagel Fat One', cursive;

color: #1c2775;

}

.upload-box {

background-color: rgba(255, 255, 255, 0.8);

width: 450px;

height: 200px;

padding: 5px 15px 100px;

border-radius: 10px;

text-align: center;

position: relative;

top: 0px;

right: 320px;

flex-direction: column;

align-items: center;

justify-content: center;

}

.upload-box h3 {

margin-bottom: 10px;

}

#preview-image {

max-width: 100%;

max-height: 100%;

width: auto;

height: auto;

object-fit: contain;

}

#preview-message {

margin-top: 20px;

font-size: 16px;

color: #999;

}

.remove-button {

background-color: #729dd9;

color: #fff;

width: 450px;

height: 50px;

padding: 10px 10px;

border: none;

border-radius: 5px;

position: relative;

top: 0px;

right: 300px;

font-size: 16px;

font-weight: bolder;

cursor: pointer;

margin-top: 20px;

display: flex;

justify-content: center;

}

.remove-button:hover {

background-color: #ff0000;

}

.right-section {

width: 0px;

padding: 10px;

}

.message-bubble1 {

background-color: rgba(255, 255, 255, 1);

width: 280px;

height: 160px;

padding: 10px 20px 30px;

border-radius: 60px;

text-align: center;

font-size: 30px;

font-family: 'Gaegu', cursive;

font-weight: bolder;

position: relative;

top: -110px;

right: -480px;

margin-top: 0px;

}

.message-bubble2 {

background-color: rgba(255, 255, 255, 1);

width: 240px;

height: 160px;

padding: 20px;

border-radius: 60px;

text-align: center;

font-size: 32px;

font-family: 'Gaegu', cursive;

position: relative;

top: 20px;

right: -780px;

margin-top: 20px;

}

.image-container1 {

text-align: right;

position: relative;

right: -300px;

top: 10px;

}

 

.image-container1 img {

max-width: 100%;

max-height: 100%;

width: 400px;

height: 400px;

margin-bottom: -50px;

}

.image-container2 {

text-align: right;

position: relative;

right: -350px;

}

 

.image-container2 img {

max-width: 100%;

max-height: 100%;

width: 300px;

height: 300px;

margin-bottom: -50px;

}

</style>

</head>

<body>

<script>

function showLoading() {

document.getElementById("loading-message").style.display = "block";

}

function hideLoading() {

document.getElementById("loading-message").style.display = "none";

}

// Function to handle image preview

function previewImage(event) {

var input = event.target;

var preview = document.getElementById("preview-image");

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

var reader = new FileReader();

reader.onload = function (e) {

preview.setAttribute("src", e.target.result);

};

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

document.getElementById("preview-message").style.display = "none";

} else {

preview.setAttribute("src", "");

document.getElementById("preview-message").style.display = "block";

}

}

</script>

<div class="left-section">

<div class="bmi-box">

<h2>BMI 결과</h2>

<div id="bmi-result"> bmi기준!</div>

<div id="bmi-result"> 저체중(18~23) 정상체중(18~23) 과체중(23~25)</div>

<div id="bmi-result"> 경증비만(25~30) 중등도비만(30~35) 고도비만(35~)</div>

<div id="bmi-result"> ------------------------------</div>

<div id="bmi-result"> {{name}}님! 당신의 BMI는 {{bmi}}입니다.</div>

<div id="bmr-result"> 기초대사량은 {{bmr}} kcal/day입니다.</div>

</div>

<div class="upload-box">

<h3>이미지</h3>

<p id="preview-message">사진을 입력하세요</p>

<form action="/test3" method="POST" enctype="multipart/form-data" onsubmit="showLoading()">

<input type="file" name="image" onchange="previewImage(event)">

<input type="submit" value="업로드">

</form>

<img id="preview-image" src="" alt="미리보기 이미지">

</div>

 

<div class="group-button">

<button class="remove-button" id="save-button">먹음</button>

<button class="remove-button">안먹음</button>

</div>

</div>

<div class="right-section">

<div class="message-bubble1">

{% if predicted_food %}

<p>FOOD: {{ predicted_food }}</p>

{% if calories %}

<p>Calories: {{ calories }}</p>

{% endif %}

{% else %}

<p id="loading-message" style="display: none;">당신이 먹을 음식 칼로리는?! (분석중...)</p>

{% endif %}

</div>

<div class="message-bubble2">

{% if bmi < 18 %}

<p>{{ bmi_messages['underweight'] }}</p>

{% elif bmi >= 18 and bmi < 23 %}

<p>{{ bmi_messages['normal'] }}</p>

{% elif bmi >= 23 and bmi < 25 %}

<p>{{ bmi_messages['overweight'] }}</p>

{% elif bmi >= 25 and bmi < 30 %}

<p>{{ bmi_messages['mild_obesity'] }}</p>

{% elif bmi >= 30 and bmi < 35 %}

<p>{{ bmi_messages['moderate_obesity'] }}</p>

{% else %}

<p>{{ bmi_messages['severe_obesity'] }}</p>

{% endif %}

</div>

</div>

<div class="image-container1">

<img src="static/img/teddybear.png" alt="베어 이미지1">

</div>

<div class="image-container2">

<img src="static/img/bear1.png" alt="베어 이미지2">

</div>

</body>

</html>

아직 백앤드가 적용되지않고, 이미지가 적용되지 않은 3페이지

이제 백앤드를 개발하여 적용해보고, 테스트 단계에 들어갈 것이다.

 

이제 백앤드 개발이 남았다. 백앤드 프로그램 언어는 Python을 활용하였다.

페이지별로 구분해서 개발 하였고, AI모델과 연결하는 과정에서도 많이 막혔다.

from flask import Flask, render_template, request, redirect

import os

import pymysql

import tensorflow as tf

import keras

import numpy as np

from PIL import Image

from flask_sqlalchemy import SQLAlchemy

import urllib.parse

from flask import jsonify

app = Flask(__name__)

필요한 라이브러리를 import 하였다.

# ------------------------------------------------------------------------------------------------------------------------------------

# page 1

@app.route('/')

def test():

return render_template('test.html')

첫 페이지를 개발하는 코드이다.

클릭만 하면 되기에 따로 코드는 필요하지 않았다.

# ------------------------------------------------------------------------------------------------------------------------------------

# page 2 - DB 연결 및 사용자 정보 입력

@app.route('/test2')

def test2():

return render_template('test2.html')

# 데이터베이스 연결 (완)

db = pymysql.connect(

host="localhost",

user="root",

password="****",

database="hungrybear",

charset="utf8"

)

# 음식 정보 조회 함수

def get_food_info(food_name):

cursor = db.cursor(pymysql.cursors.DictCursor)

query = f"SELECT foodcal FROM food WHERE foodname = '{food_name}'"

cursor.execute(query)

result = cursor.fetchone()

if result:

return result['foodcal']

else:

return None

# 유저 정보

def get_user_info():

name = request.form.get("name")

height = float(request.form.get("height"))

weight = float(request.form.get("weight"))

age = int(request.form.get("age"))

gender = request.form.get("gender")

 

# BMI 계산

height_m = height / 100 # cm를 m로 변환

bmi = round(weight / (height_m ** 2), 2)

# BMR 계산

if gender == "male":

bmr = round(66 + (13.75 * weight) + (5 * height) - (6.75 * age), 2)

elif gender == "female":

bmr = round(655 + (9.56 * weight) + (1.85 * height) - (4.68 * age), 2)

 

return name, age, gender, height, weight, bmi, bmr

@app.route('/test2', methods=['GET', 'POST'])

def profile():

if request.method == 'POST':

# 사용자 정보 입력 받기

name, age, gender, height, weight, bmi, bmr = get_user_info()

# 커서 객체 생성 (완)

cursor = db.cursor(pymysql.cursors.DictCursor)

alter_query = "ALTER TABLE customer MODIFY customerID INT AUTO_INCREMENT"

cursor.execute(alter_query)

# DB에 유저 정보 삽입

query = "INSERT INTO customer (name, age, gender, height, weight, bmi, bmr) VALUES (%s, %s, %s, %s, %s, %s, %s)"

values = (name, age, gender, height, weight, bmi, bmr)

try:

cursor.execute(query, values)

db.commit()

print("사용자 정보가 성공적으로 저장되었습니다.")

except Exception as e:

db.rollback()

print("사용자 정보 저장 중에 오류가 발생했습니다:", e)

cursor.close() # 커서 닫기

return redirect('/test3') # 프로필 정보 입력 후 test3 페이지로 리다이렉트

return render_template('test3.html')

투번째 페이지의 코드이다.

데이터베이스를 연결 한 후 유저정보, 음식정보를 어떻게 가져올지와, BMI와 기초대사량을 구하는 계산식을 작성 하였고, 입력을 받으면 DB에 저장. DB에 유저정보까지 기록하도록 설정 하였다.

# ------------------------------------------------------------------------------------------------------------------------------------

#누적된 칼로리.

@app.route('/save_food', methods=['POST'])

def save_food():

data = request.json

calories = data['calories']

customer_id = data['customerId']

# 사용자의 음식 칼로리 누적

update_query = "UPDATE customer SET food_calories = IFNULL(food_calories, 0) + %s WHERE customerID = %s"

update_values = (calories, customer_id)

cursor = db.cursor()

cursor.execute(update_query, update_values)

db.commit()

return jsonify({'message': '음식 칼로리가 성공적으로 누적되었습니다.'})

다음 코드까지 작성해보고싶었으나 오류가 자꾸 발생하여 칼로리 누적은 제외하도록 했다.

# ------------------------------------------------------------------------------------------------------------------------------------

#page 3.

upload_folder = 'static/uploads' # 이미지가 저장될 폴더 경로

app.config['UPLOAD_FOLDER'] = upload_folder

@app.route('/test3', methods=['GET', 'POST'])

def test3():

# BMI에 따른 메세지 정의

bmi_messages = {

'underweight': '저체중입니다! 많이드세요!',

'normal': '정상체중입니다!',

'overweight': '과체중입니다! 적절히 관리하세요!',

'mild_obesity': '경증비만입니다! 관리가 필요합니다!',

'moderate_obesity': '중등도비만입니다 식단조절과 운동이 필요합니다!',

'severe_obesity': '고도비만입니다! 전문가의 도움이 필요합니다!'

}

# 사용자의 bmi와 bmr 가져오기

cursor = db.cursor(pymysql.cursors.DictCursor)

query = "SELECT name, bmi, bmr, IFNULL(food_calories, 0) AS food_calories FROM customer ORDER BY customerID DESC LIMIT 1"

cursor.execute(query)

result = cursor.fetchone()

name = result['name']

bmi = result['bmi']

bmr = result['bmr']

food_calories = result['food_calories']

predicted_food = None # predicted_food 변수 초기화

image = None # image 변수 초기화

calories = None # calories 변수 초기값 설정

file_path = None # file_path 변수 초기화

# 이미지 업로드 처리

if 'image' in request.files:

image = request.files['image']

 

if image.filename != '':

# 이미지 파일 수 카운트

file_count = len(os.listdir(upload_folder))

# 파일 이름 변경

filename = f"{file_count + 1}.jpg"

file_path = os.path.join(upload_folder, filename)

# 파일 저장

image.save(file_path)

print("이미지 파일이 성공적으로 저장되었습니다.")

# 이미지 분류 처리

model_path = 'C:\\Users\\lily1\\OneDrive\\바탕 화면\\아이와즈_인턴십\\bestmodel_11class.hdf5'

new_model = tf.keras.models.load_model(model_path)

class_labels = ['beef_tartare',

'chicken_curry',

'chocolate_mousse',

'french_toast',

'fried_rice',

'hot_dog',

'ice_cream',

'lasagna',

'oysters',

'pizza',

'takoyaki']

def preprocess_image(image): # 이미지 전처리

image = image.resize((224, 224))

image = image.convert('RGB')

image = np.array(image) / 255.0

image = np.expand_dims(image, axis=0)

return image

def classify_food_photo(image_path):

image = Image.open(image_path)

preprocessed_image = preprocess_image(image)

predictions = new_model.predict(preprocessed_image)

predicted_class_index = np.argmax(predictions)

predicted_class = class_labels[predicted_class_index]

return predicted_class

predicted_food = classify_food_photo(file_path)

calories = get_food_info(predicted_food)

print("Predicted Food:", predicted_food, "의 칼로리", calories)

return render_template('test3.html', name=name, bmi=bmi, bmr=bmr, file_path=file_path, predicted_food=predicted_food, calories=calories, result=result, bmi_messages=bmi_messages)

 

if __name__ == '__main__':

app.run(debug=True)

세번째 페이지 코드이다.

본격적으로 서비스를 진행하는 부분이며, 사용자의 BMI BMR을 계산하여 몸무게 상태를 체크해준다.

또한, 음식 이미지를 사용자가 입력하였을때, AI가 음식의 칼로리를 예측하여 알려준다.

이제 테스트 단계에 들어갈 것이다.

 

 

1. 요구사항 명세서.

 

 

2. 설계사양서

 

 

3. 개발

 

 

4 .테스트

이제 마지막으로 테스트를 해볼것이다.

run을 시키면 다음과 같이 페이지 정보가 나온다.

페이지에 들어가면 1페이지가 나온다. 시작하기를 누르면 2페이지로 넘어간다.

다음과같이 user정보를 입력하고 저장을 누른다.

다음과같이 bmi가 나오고 기초대사량을 확인시켜준다.

다음과 같이 저장이 된 것을 확인 할 수 있다.

(76은 처절한 테스트의 흔적...)

댜음과 같이 이미지를 넣고 업로드를 클릭한다.

다음과 같이 분석을 시작한다.

(개발을 하는 과정에서 모델을 잘 못 생성한건지, 아니면 백앤드에서 복잡하게 구현을 해서 그런지 모르겠는데 분석이 굉장히 오래 걸린다. 약 20초? 해결해야하는 부분이다.)

정상적으로 음식의 칼로리를 예측 할 수 있었다.

summary,

첫 프로젝트다보니 부족하고 어려운 부분이 정말 많았다.

지금 봐도 요구사항대로 완성하기 어려웠던 것 같다.

그렇지만 여러가지를 배울 수 있었다.

절대 포기하지말자. 끝까지 부딪혀보자. 모르는건 여기저기 자문을 구하면서 해결해보자. 그렇게 하다보니 하나씩 해결 할 수 있었고, 1~2주 정도 진행한 프로젝트라는 것과 처음 해본 프로젝트라는 부분에서 그래도 해결 할 수 있는 부분이 많았다고 생각한다.

또한, 함께해준 팀원들이 없었다면 여기까지 오긴 정말 어려웠을거라고 생각한다. 모두 고생해주셔서 너무 감사하다.

끝까지 최선을 다 했고, 더 성장하고싶다. 앞으로 더 많이 배워서 좋은 개발자가 되고싶다.