본문 바로가기

AI Inventors 개발 Story

AI Inventors History#2-4 "Personalcolor AI 개발 - Flask Framework 구현" 본문

IT 개발 프로젝트/Personalcolor AI 개발

AI Inventors History#2-4 "Personalcolor AI 개발 - Flask Framework 구현"

ai.inventors 2020. 8. 27. 16:22

안녕하세요 

AI Inventors 입니다 :)

 

오늘은 PersonalcolorAI 제작 과정 중 Flask 구현 및 Nginx 웹서버 연동 부분을 소개드리겠습니다.

저희 PersonalcolorAI는 Backend AI부분은 Python으로, Frontend는 HTML/CSS/Javascript로 작성했고, 

HTML 웹 페이지를 띄우기 위한 Framework으로 Flask를 사용했고,

상용 클라우드 서버에서 서비스를 Deploy하기 위해, 안정적인 웹서버 소프트웨어인 Nginx 웹서버와 연동하였습니다.

(Nginx 연동 부분은 추후 포스팅에서 다루겠습니다.)

 

 

Flask 기초 소개

Flask는 Micro 웹서버(WSGI Application) 구현을 위해 Python언어로 작성된 Framework입니다.

 

WSGI Python 표준(PEP-333)으로 HTTP를 통해 요청을 받아 응답하는 어플리케이션에 대한 명세로 이러한 명세를 만족시키는 클래스나 함수, (__call__을 통해 부를 수 있는)객체를 WSGI 어플리케이션이라고 합니다.

 

아래는, 아주 기초적인 flask 코드 예시입니다.

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run(port = "8000")

 

Flask 모듈을 import 한 후에, application 혹은 app 변수에 Flask 객체를 받아줍니다.

from flask import Flask
app = Flask(__name__)

 

app 객체를 통해, 라우팅 할 경로를 잡아주고, 해당 주소로 라우팅 요청이 들어왔을 때, 실행할 함수를 선언하여 작성해줍니다.

@app.route("/")
def hello():
    return "Hello World!"

 

마지막으로, Main module로 실행될 때, flask 서버가 구동되도록 코드를 작성해줍니다.

이때, port는 TCP Port 중 well-known port(0~1023) 영역을 제외한 포트번호 아무거나 잡아주시면 됩니다.

(저는, 개인적으로, 8000번, 8888번, 5000번, 6000번 대역 많이 사용합니다.)

 

또한, port번호 앞에 host ip를 넣을 수도 있는데요,

host ip를 넣지 않는다면 본인 로컬 PC의 로컬 IP(127.0.0.1) 가 그대로 지정되서 들어가며, 

저 처럼 상용 웹 서버로 Nginx를 연동하여 사용하고 싶으시다면, host = "0.0.0.0"을 넣어주시면 됩니다.

(0.0.0.0은 어느 IP에서 들어오더라도 허용해주겠다 라는 의미입니다.)

if __name__ == "__main__":
    app.run(port = "8000")

 

Jinja 탬플릿

 

세부 설정을 세세하게 바꿔줄 수 있는 Django Framework과 달리,

Flask Framework은 세세하게 바꿔줄만한 설정이 많지 않습니다.

 

그래서, 이런 간단한 어플리케이션에 잘 어울리기도 하고, 초보자분들이 사용하시기에도 편리합니다.

 

저희 역시도 Flask 설정 중 크게 변경시킨 부분은 없기 때문에, 위의 기본 예시와 크게 변화된 부분은 없습니다.

다만, 위 Flask 기본 예시와 달리 jinja Template을 사용하였는데요

또한,  CSS, JavaScript를 이용하기 위해, Static folder를 설정해주는 부분이 쪼금 추가되었습니다.

 

Jinja는 

Flask에 내장된 Template엔진으로, 단순히 static한 html 페이지를 띄워주는 것이 아닌,

동적인 웹 페이지 구성을 보여주기 위해 사용하는 라이브러리 입니다.

 

간단히 jinja Template의 문법을 소개하면 아래와 같습니다.

  • {{ ... }} : 변수나 표현식
  • {% ... %} : if나 for같은 제어문
  • {# ... #} : 주석

HTML 파일 내에 제가 사용하려는 변수명을,  {{ 변수명 }} 의 형태로 집어넣어주시면 됩니다.

 

JavaScript ES6 문법과 유사하다고 보시면 되는데요.

제가 퍼스널컬러AI를 만들면서는 대부분 첫 번째 문법만을 사용했었습니다.

사실, 제어문을 사용하려면 python 내부에서 사용하고, 이 결과 값을 flask로 리턴해주는 편이 사용이 더 편하더라구요

 

[기본 사용 예시]

 

[Python]

from flask import Flask, render_template

@app.route('/')
def index():
	hello = "안녕하세요"
    return render_template('index.html', hello)

아까, Flask 기본 예시와는 달라진 부분이 딱 두군데 있습니다.

우선, render_template 라이브러리를 import 해와야하구요!

두번째로는, 라우팅 밑에 정의된 함수의 return값에 render_template 모듈을 집어넣어주었습니다.

render_template 모듈 내에는 맨 첫 부분에 내가 이 인자값을 보내주고 싶은 html 페이지를 넣어줍니다.

html 페이지명 뒤에, 내가 실제로 보내고 싶은 변수명들을 쭉 나열해주면 끝!!

 

 

[HTML]

<head>
</head>
<body>
	<h2> {{ hello }} </h2>
</body>

HTML 웹페이지 내에서는 

내가 실제로 이 인자값을 배치하고싶은 부분에 jinja 문법에 맞게 변수명 혹은 인자값을 넣어주시면 됩니다.

 

위와 같이 작성해주시면, python 백엔드의 연산 값에 따라, HTML이 동적으로 변하도록 웹페이지를 구성하실 수 있습니다.

 

 

 퍼스널컬러AI 세부 구현 내역

 

위에서 말씀드렸던 것처럼, 저희 퍼스널컬러AI 구현 시에는 Flask 기본 예시, Jinja 기본예시와 크게 변화된 내역은 없습니다.

 

초기에 flask를 import 하는 부분입니다.

from flask import Flask, render_template, request, redirect

import에 보시면, request, redirect 모듈이 붙어있는데요.

저희 퍼스널컬러AI는 사진을 입력받아 이를 분석하고, 분석결과에 맞는 페이지로 라우팅을 해줘야 하기 때문에,

클라이언트(유저)가 브라우저로부터 이미지파일을 전송했을 때,

이를 인지하고 백엔드에서 처리할 수 있도록, request method를 정의하고자 request 모듈을 사용했습니다.

또한, 이미지를 전달받아 분석하고, 적절한 페이지로의 라우팅을 위해 redirect 모듈을 사용하였습니다.

 

TEMP_FOLDER = './temp'
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])

app = Flask(__name__,
            template_folder = "templates",
            static_folder = "static")
app.config['UPLOAD_FOLDER'] = TEMP_FOLDER

 

클라이언트가 제출한 파일을 분석을 위해 임시로 저장해놓는 폴더 공간을 정의하기 위해,

TEMP_FOLDER = './temp'의 형태로 정의했고, 

이를 app.config['UPLOAD_FOLDER'] 에 연결시켜주었습니다.

 

그리고, flask의 기본 app을 정의해주는 부분에서,

저희의 html page들이 저장될 공간을 template_folder에, css/js 등 부가적인 파일들이 들어갈 공간을 static_folder에 각각 맵핑하였습니다.

해당 폴더("templates", "static")들은 python 파일이 위치하는 하위 디렉토리에 정의되어있어야 합니다.

(ex. python 파일이  /user/pcolor_AI/ 디렉토리에 존재한다면, template, static은 /user/pcolor_AI/template/, /user/pcolor_AI/static/ 과 같이 위치해야 한다는 의미입니다.)

 

 

 

 

 

@app.route('/', methods =['GET', 'POST'])
def index():
	
    if request.method == 'GET':
        intro1 = "파일 선택 후 제출 버튼을 눌러주세요"
        intro2 = "AI가 당신을 위한 퍼스널컬러 분석을 시작합니다"

        return render_template('index.html', 
                            intro1 = intro1, intro2 = intro2,
                            celeb_img_1 = celeb_img_1,
                            celeb_img_2 = celeb_img_2,
                            celeb_img_3 = celeb_img_3)
 	    

이후, 위와 같이 routing 디렉토리와, 명령을 실행할 함수를 정의하게 됩니다.

 

앞서 말씀드렸던 것처럼, 클라이언트가 보낸 request를 받아 python이 연산을 진행하기 때문에,

methods 에 List형태로 ['GET', 'POST']를 정의한 모습을 보실 수 있습니다.

 

또한, 클라이언트가 단순히 'GET'의 형태로,

웹사이트에 접속만 한다면 초기 intro 페이지를 보여주기 위해,

if request.method == 'GET'의 제어문을 통해, 초기 페이지를 보여주는 내역입니다.

 

    elif request.method == 'POST':
        intro1 = "재 분석을 원하시면 다시 파일 선택 후 제출 버튼을 눌러주세요"
        
        f = request.files['file']
        name = f.filename

        f.save('./temp/' + str(secure_filename(name)))
        file_path = os.path.join('./temp', name )
        
        try:
            df_mean = [0, 0, 0, 0, 0, 0]
            df_std = [1, 1, 1, 1, 1, 1]
            raw_data = pcolor_analysis(file_path)
            feature_1, feature_2, feature_3, feature_4, feature_5, feature_6= (raw_data - np.array(df_mean)) / np.array(df_std)

			#### 생략(Deep Learning 적용) #####


            if fin_result == '봄웜라이트':    
                return redirect('/result_spring_light/')
                
            elif fin_result == '봄웜브라이트':                
                return redirect('/result_spring_bright/')

            elif fin_result == '여름쿨라이트':                
                return redirect('/result_summer_light/')

            elif fin_result == '여름쿨뮤트':
                return redirect('/result_summer_mute/')

            elif fin_result == '가을웜뮤트':
                return redirect('/result_fall_mute/')

            elif fin_result == '가을웜딥':
                return redirect('/result_fall_deep/')

            elif fin_result == '겨울쿨브라이트':                
                return redirect('/result_winter_bright/')

            elif fin_result == '겨울쿨딥':
                return redirect('/result_winter_deep/')
            
            
            if os.path.isfile(file_path):
                os.remove(file_path)
                
         except:

            if os.path.isfile(file_path):
                os.remove(file_path)
            return redirect('/result_fail/')
                

또한, elif request.method == 'POST': 문을 통해,

클라이언트가 파일을 전송했을 때, 하위 함수들이 실행되도록 코드를 작성하였습니다.

 

flask에서는 클라이언트가 전달한 파일을 넘겨받기 위해서는 아래와 같이, request.file['file'].filename 메쏘드를 통해 file명을 받은 후, 이를 저장하고, 이 저장받은 파일을 가져와서 가공하고 분석하는 방법을 사용해야합니다.

        f = request.files['file']
        name = f.filename

        f.save('./uploads/' + str(secure_filename(name)))
        file_path = os.path.join('./uploads', name )

 

그리고, 파일을 입력받은 후에, pcolor_analysis라는 컬러톤 분석값 추출용 함수에 집어넣어, 분석값들을 뽑아옵오고, standardization을 적용해 주면, 딥러닝에 넣을 전처리단계가 완성됩니다.

 

이후, 딥러닝에 적용한 후에, 최종적으로 결과값을 뽑아오고,

최종 결과값이 저장된 fin_result에 따라, 해당 페이지로 redirect를 해주면 처리는 끝나게 됩니다.

 

 

 

오늘은 Flask/Jinja 프레임워크 구현 부분을 포스팅해봤습니다.

다음번에는 Linux 서버 구축과 Nginx 연동 부분을 포스팅해보겠습니다.

 

감사합니다.

AI Inventors :)

Comments