Skip to content
Snippets Groups Projects
Commit 7ff23b4e authored by Hyunseok_Sang's avatar Hyunseok_Sang
Browse files

Merge branch 'developing'

parents 22ed2dca 37d7a618
No related branches found
No related tags found
No related merge requests found
Showing
with 120 additions and 26 deletions
...@@ -8,4 +8,4 @@ config.json ...@@ -8,4 +8,4 @@ config.json
*.jpg *.jpg
test.py test.py
ssfshop/ ssfshop/
image_processing_app/etc/secrets/google_key.json image_processing_app/etc/secrets/
\ No newline at end of file
.env
*.png
*.jpg
test.py
etc/secrets/google_key.json
# 기본 이미지 설정 # 기본 이미지 설정
FROM python:3.9-slim-buster FROM python:3.11.4-slim-bullseye
# 작업 디렉토리 설정 # 작업 디렉토리 설정
WORKDIR /app WORKDIR /app
...@@ -7,10 +7,16 @@ WORKDIR /app ...@@ -7,10 +7,16 @@ WORKDIR /app
# 의존성 설치 # 의존성 설치
COPY requirements.txt requirements.txt COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
RUN apt-get update && apt-get install -y \
libgl1-mesa-glx
RUN apt-get update && apt-get install -y \
libgl1-mesa-glx \
libglib2.0-0
# 소스코드 복사 # 소스코드 복사
COPY . . COPY . .
WORKDIR /app/src WORKDIR /app/src
# Flask 명령으로 앱 실행 # Flask 명령으로 앱 실행
CMD [ "python", "-m" , "app", "run", "--host=0.0.0.0"] #CMD [ "python", "-m" , "app", "run", "--host=0.0.0.0"]
\ No newline at end of file CMD [ "python", "app.py"]
\ No newline at end of file
# 이미지 자동 처리 # 이미지 프로세싱 앱
\ No newline at end of file # 소개
이미지 자동 Crop 앱에 사용될 이미지를 처리하고 객체 인식 데이터를 제공하는 로컬 서버입니다.
![Flask App 모식도](./docs/images/flask.png)
할 줄 아는 것이 Python 뿐이었던 저는 Python으로 이미지 처리 개발을 시작습니다. 그런데 서버를 Spring으로 구축해야 하는 상황이 오고 만 것입니다. java로 모조리 새로 구현하기는 싫었던 저는, 기존 코드를 기반으로 로컬 서버를 구축하고, Spring 서버가 API를 통해 이 것과 통신을 하도록 했습니다. 이런 것을 마이크로 서비스 아키텍처(Micro Service Architecture)라고 하더군요.
아무튼, 해당 서비스는 이미지 자동 Crop 앱을 위한 로컬 마이크로 서비스입니다.
## Requirements
작업 디렉토리를 `image_processing_app`로 변경하고 아래 명령어를 실행합니다.
```bash
pip install -r requirements.txt
```
## 실행 방법
작업 디렉토리를 `image_processing_app/src`로 변경하고 아래 명령어를 실행합니다. 하지만 실행에 앞서 .env를 통해 환경 변수를 등록해야 합니다. 아래를 참고하세요.
```Bash
python app.py
```
## Docker를 이용한 실행
Docker를 이용하면, 별도의 패키지 설치나 작업 디렉토리 변경없이 서버를 실행할 수 있습니다. 하지만 실행에 앞서 .env를 통해 환경 변수를 등록해야 합니다. 아래를 참고하세요.
```bash
docker pull henry914/image-processing-flask-app:0.1
docker run -p 5000:5000 --env-file .env henry914/image-processing-flask-app:0.1
```
## 환경 변수 설정법
`.env` 파일을 생성하고 OpenAI API의 key를 입력해야 합니다.
```
DallE_API_KEY=<Dall-E 서비스 키 json의 위치>
GOOGLE_APPLICATION_CREDENTIALS=../etc/secrets/google_key.json
```
`.env` 파일을 생성했다면 다음의 과정에 따라 배치합니다.
### `python app.py`을 통해 실행하는 경우
작업 디렉토리 `image_processing_app/src``.env` 파일을 배치합니다.
### Docker 이미지를 pull 해서 실행하는 경우
`docker run`을 실행하는 작업 디렉토리에 `.env` 파일을 배치합니다.
## Google API키 등록 방법
위의 과정을 전부 따라도 FaceDetection API를 사용하는 것은 불가능합니다. Google API service key를 발급받고, 이를 배치해야 하기 때문입니다. `https://console.cloud.google.com/`에서 서비스를 등록하고, service key를 발급받을 수 있습니다.
### `python app.py`을 통해 실행하는 경우
다음 명령어를 실행해줍니다.
```
export GOOGLE_APPLICATION_CREDENTIALS=../etc/secrets/google_key.json
```
그리고 `image_processing_app/etc/secrets` 경로를 만들고, service_key를 배치합니다
### Docker 이미지를 pull 해서 실행하는 경우
`docker run`을 실행할 때, `-v` 옵션을 통해 호스트의 파일을 Docker 컨테이너에 전달하면 됩니다.
```
docker run -p 5000:5000 -v /host/(호스트의 service key 위치):/container/app/etc/secrets/google_key.json --env-file .env henry914/image-processing-flask-app:0.1
```
\ No newline at end of file
onnxruntime onnxruntime
opencv-python opencv-python
google-cloud-vision google-cloud-vision
python-dotenv
openai openai
retina-face retina-face
flask
python-dotenv
\ No newline at end of file
...@@ -5,4 +5,4 @@ app = Flask(__name__) ...@@ -5,4 +5,4 @@ app = Flask(__name__)
app = create_router(app) app = create_router(app)
if __name__ == '__main__': if __name__ == '__main__':
app.run() app.run(host='0.0.0.0')
import json from dotenv import load_dotenv
import os
load_dotenv()
def load_config(file_path): dallE_api_key = os.getenv('DallE_API_KEY')
with open(file_path, 'r') as file: \ No newline at end of file
config_data = json.load(file)
return config_data
config = load_config('../conf/config.json')
unet_model_location = config["unet_model_location"]
dallE_api_key = config["dallE_api_key"]
\ No newline at end of file
...@@ -2,6 +2,7 @@ from flask import Blueprint, request, jsonify ...@@ -2,6 +2,7 @@ from flask import Blueprint, request, jsonify
from services.aggregated_services.image_processing_service import ImageProcessingService from services.aggregated_services.image_processing_service import ImageProcessingService
from utils.image.image_converter import ImageConverter from utils.image.image_converter import ImageConverter
from utils.image.image_encoder import ImageEncoder
image_controller = Blueprint('image', __name__) image_controller = Blueprint('image', __name__)
...@@ -12,6 +13,10 @@ def process_image(): ...@@ -12,6 +13,10 @@ def process_image():
chop_image = request.args.get('chop_image', 'True').lower() == 'true' chop_image = request.args.get('chop_image', 'True').lower() == 'true'
outpainting_size = int(request.args.get('outpainting_size', 1024)) outpainting_size = int(request.args.get('outpainting_size', 1024))
removed_border_pixel = int(request.args.get('removed_border_pixel', 2)) removed_border_pixel = int(request.args.get('removed_border_pixel', 2))
image_bytes = request.files['image_data'].read() image_bytes = request.files['image_data'].read()
np_image = ImageConverter().convert_to_np_image(image_bytes) np_image = ImageConverter().convert_to_np_image(image_bytes)
processed_np_image, x_offset, y_offset = ImageProcessingService().process(np_image, ratio, recover_size, chop_image, outpainting_size, removed_border_pixel) result = ImageProcessingService().process(np_image, ratio, recover_size, chop_image, outpainting_size, removed_border_pixel)
\ No newline at end of file
result["result_image"]["image_data"] = ImageEncoder().encode_np_image_base64(result["result_image"]["image_data"])
return jsonify(result)
\ No newline at end of file
...@@ -54,3 +54,10 @@ class Mask(Image): ...@@ -54,3 +54,10 @@ class Mask(Image):
else: else:
self.touch_border_bottom = False self.touch_border_bottom = False
def relocate_smallest_box(self, reduction_ratio, x_offset, y_offset):
self.smallest_box_x = int(reduction_ratio * self.smallest_box_x)
self.smallest_box_y = int(reduction_ratio * self.smallest_box_y)
self.smallest_box_width = int(reduction_ratio * self.smallest_box_width)
self.smallest_box_height = int(reduction_ratio * self.smallest_box_height)
self.smallest_box_x += x_offset
self.smallest_box_y += y_offset
\ No newline at end of file
...@@ -22,16 +22,35 @@ class ImageProcessingService: ...@@ -22,16 +22,35 @@ class ImageProcessingService:
outer_width = compsited_image.outer_image.shape[1] outer_width = compsited_image.outer_image.shape[1]
outer_height = compsited_image.outer_image.shape[0] outer_height = compsited_image.outer_image.shape[0]
outpainted_image = ImageResizer().resize(outpainted_image, outer_width, outer_height) outpainted_image = ImageResizer().resize(outpainted_image, outer_width, outer_height)
reduction_ratio = 1
x_offset = compsited_image.x_offset x_offset = compsited_image.x_offset
y_offset = compsited_image.y_offset y_offset = compsited_image.y_offset
outpainted_image = ImageCompister().composite(outpainted_image, np_image, x_offset, y_offset) outpainted_image = ImageCompister().composite(outpainted_image, np_image, x_offset, y_offset)
else: else:
reduction_ratio = outpainting_size * compsited_image.outer_image.shape[0] reduction_ratio = outpainting_size / compsited_image.outer_image.shape[0]
x_offset = int(compsited_image.x_offset * reduction_ratio) x_offset = int(compsited_image.x_offset * reduction_ratio)
y_offset = int(compsited_image.y_offset * reduction_ratio) y_offset = int(compsited_image.y_offset * reduction_ratio)
############################################################# #############################################################
if chop_image is True: if chop_image is True:
processed_image, x_offset, y_offset = ImageChoppingService().chop_according_to_mask(outpainted_image, mask, x_offset, y_offset) chopped_image, x_offset, y_offset = ImageChoppingService().chop_according_to_mask(outpainted_image, mask, x_offset, y_offset)
return processed_image mask.relocate_smallest_box(reduction_ratio, x_offset, y_offset)
result = {
"result_image": {
"image_data": chopped_image,
"width": chopped_image.shape[1],
"height": chopped_image.shape[0]
},
"original_x_offset": x_offset / chopped_image.shape[1],
"original_y_offset": y_offset / chopped_image.shape[0],
"mask": {
"smallest_box_x": mask.smallest_box_x / chopped_image.shape[1],
"smallest_box_y": mask.smallest_box_y / chopped_image.shape[0],
"smallest_box_width": mask.smallest_box_width / chopped_image.shape[1],
"smallest_box_height": mask.smallest_box_height / chopped_image.shape[0]
}
}
return result
\ No newline at end of file
import cv2
import base64
class ImageEncoder:
@staticmethod
def encode_np_image_base64(image):
_, img_encoded = cv2.imencode('.jpg', image)
img_string = base64.b64encode(img_encoded).decode('utf-8')
return img_string
\ No newline at end of file
...@@ -2,11 +2,9 @@ import onnxruntime ...@@ -2,11 +2,9 @@ import onnxruntime
import cv2 import cv2
import numpy as np import numpy as np
from config import unet_model_location
class MaskDetector: class MaskDetector:
def detect_mask(self, np_image): def detect_mask(self, np_image):
model = onnxruntime.InferenceSession(unet_model_location) model = onnxruntime.InferenceSession("../static/model_weight/unet.onnx")
mask = cv2.resize(np_image, (320, 320)) mask = cv2.resize(np_image, (320, 320))
mask = mask.transpose((2, 0, 1)) # 채널 순서 변경 mask = mask.transpose((2, 0, 1)) # 채널 순서 변경
mask = mask.astype(np.float32) / 255.0 # 정규화 mask = mask.astype(np.float32) / 255.0 # 정규화
......
ssfshop/beanpole/남녀공용 베이직 피케 티셔츠 - 블랙/DallE.png

3 MiB

ssfshop/beanpole/남녀공용 베이직 피케 티셔츠 - 블랙/original.jpg

41.9 KiB

0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment