Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
dldltkdals committed Jul 28, 2023
2 parents 3705522 + 6a881da commit bd76bcf
Show file tree
Hide file tree
Showing 30 changed files with 365 additions and 125 deletions.
28 changes: 0 additions & 28 deletions .github/workflows/deploy_GPU1.yml

This file was deleted.

91 changes: 49 additions & 42 deletions GPUserver/model/inference_pipeline_with_Inpaint_Anything.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from model.img2img_model import StableDiffusionImg2Img
from model.segment_anything_model import SAM
from PIL import Image
from PIL import Image,ImageChops
import numpy as np
from model.utils import image_resize,image_segmentation,combine_image, check_crop_inference,crop_coord,img_padding
from model.utils import image_resize,image_segmentation,combine_image, check_size, mask_composit, mask_composit2
from model.Inpaint_Anything.lama_inpaint import inpaint_img_with_lama
from model.Inpaint_Anything.utils import save_array_to_img, dilate_mask

Expand All @@ -19,48 +19,55 @@ def __init__(self,check_point:str=check_point_dump_path):
def load_lora(self,check_point:str):
self.image_translation.load_Lora(check_point)
def pipe(self,image:Image.Image,input_bbox:np.array,prompt:str,negative_prompt:str,inference_steps:int=100,strength:float=0.6)->Image.Image:
#1.입력 이미지에서 마스크 추출
bbox_area = (input_bbox[2]-input_bbox[0])*(input_bbox[3]-input_bbox[1])
#1. 입력 이미지에서 마스크 추출
mask = self.segmentation.make_mask_with_bbox(image,input_bbox)

# crop을 할지 체크하는 함수
if check_crop_inference(image=image,bbox_area=bbox_area):
x1,y1,x2,y2,input_bbox = crop_coord(image=image,bbox=input_bbox)
cropped_img = image.crop((x1,y1,x2,y2))
cropped_mask = mask.crop((x1,y1,x2,y2))
segment_img = image_segmentation(cropped_img,cropped_mask)

target_image = self.image_translation.inpaint(prompt=prompt,negative_prompt=negative_prompt,image=segment_img,num_inference_steps=inference_steps,strength=strength)

background = self.outpaint(image,mask,15)
target_image = img_padding(image=image,target_image=target_image,coord = (x1,y1,x2,y2))

target_mask = self.segment_image(image = target_image,input_bbox=input_bbox)

#4 이미지를 합치기 전에 target_image의 mask를 통해 배경과 객체를 분리
target_image,target_background = image_segmentation(image=target_image,mask=target_mask,background=background)
#5. 4번 과정에서 얻은 결과를 통해 두이미지를 합친다.
result = combine_image(background=target_background,image=target_image)
else:

"""
1번을 수행한다면 아래 2번과 3번 과정은 비동기로 작성해도 가능할 것 같다.
추후 속도를 위해 개선할 예정!
"""

#2-1. 위에서 추출된 마스크 이외의 배경을 제거한 뒤, 이미지 변환
segment_img = image_segmentation(image,mask)
"""
1번을 수행한다면 아래 2번과 3번 과정은 비동기로 작성해도 가능할 것 같다.
추후 속도를 위해 개선할 예정!
"""
#2-1. 위에서 추출된 마스크 이외의 배경을 제거
segment_img = image_segmentation(image,mask)

target_image = self.image_translation.inpaint(prompt=prompt,negative_prompt=negative_prompt,image=segment_img,num_inference_steps=inference_steps,strength=strength)

background = self.outpaint(image,mask,15)
background = image_resize(image=target_image,background=background)

target_mask = self.segment_image(image = target_image,input_bbox=input_bbox)
#4 이미지를 합치기 전에 target_image의 mask를 통해 배경과 객체를 분리
target_image,target_background = image_segmentation(image=target_image,mask=target_mask,background=background)
#5. 4번 과정에서 얻은 결과를 통해 두이미지를 합친다.
result = combine_image(background=target_background,image=target_image)

#2-2. 선택된 객체만 crop
crop_img = segment_img.crop((input_bbox[0],input_bbox[1],input_bbox[2],input_bbox[3]))

#2-3. 선택된 객체 resize
resized_crop_img = check_size(crop_img)

#3. Resize한 객체 캐릭터 생성
resized_target_image = self.image_translation.inpaint(prompt=prompt,negative_prompt=negative_prompt,image=resized_crop_img,num_inference_steps=inference_steps,strength=strength)

#3-1. 크기 Resize한 객체 원본 크기로 돌리기
x = input_bbox[2] - input_bbox[0]
y = input_bbox[3] - input_bbox[1]
target_image = resized_target_image.resize((x, y),Image.ANTIALIAS)

#4. 캐릭터 마스크 재생성
x, y = target_image.size
bbox = np.array([0,0,x,y])

crop_mask = self.segmentation.make_mask_with_bbox(target_image, bbox)
# SAM에서 마스크의 테두리 부분이 잘 Seg 되지 않는 현상 때문에 미세하게 crop 진행
crop_mask = crop_mask.crop((4,4,x-4,y-4))

# 마스크 반전
crop_mask = ImageChops.invert(crop_mask)

#5. 애니 생성 후 원본 이미지 크기의 마스크 만들기
after_mask = mask_composit(image=image,crop_mask=crop_mask, input_bbox=input_bbox)

#6. 위에서 추출된 마스크 이외의 배경 제거
after_segment_img = mask_composit2(image, target_image, input_bbox)

#7. 원본 배경만 남기기
background = self.outpaint(image,mask,15)

#8. 이미지를 합치기 전에 target_image의 mask를 통해 배경과 객체를 분리
target_image,target_background = image_segmentation(image=after_segment_img,mask=after_mask,background=background)

#9. 8번 과정에서 얻은 결과를 통해 두이미지를 합친다.
result = combine_image(background=target_background,image=target_image)
return result

def outpaint(self,image:Image.Image,mask:Image.Image,dilate_kernel_size:int) -> Image.Image:
Expand Down
27 changes: 26 additions & 1 deletion GPUserver/model/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,29 @@ def combine_image(background:Image.Image,image:Image.Image) -> Image.Image:
background,image = np.array(background), np.array(image)
result_np = background+image
result_pil = Image.fromarray(result_np)
return result_pil
return result_pil

def check_size(img):
w,h = img.size
tmp = w if w > h else h
standard = 950

ratio = standard / tmp
img = img.resize((int(w * ratio), int(h * ratio)),Image.ANTIALIAS)
return img

def mask_composit(image:Image.Image,crop_img:Image.Image,input_bbox:np.array)->Image.Image:
x,y = image.size
board = np.zeros((y,x),dtype=bool)
board[input_bbox[1]+4:input_bbox[3]-4, input_bbox[0]+4:input_bbox[2]-4] = np.array(crop_img)

board = Image.fromarray(board)
return board

def mask_composit2(image:Image.Image,orig_img:Image.Image,input_bbox:np.array)->Image.Image:
x,y = image.size
board = np.zeros((y,x,3),dtype=np.uint8)
board[input_bbox[1]:input_bbox[3], input_bbox[0]:input_bbox[2]] = np.array(orig_img)
board = Image.fromarray(board)

return board
116 changes: 92 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,92 @@
### 서버 실행 순서

1. [GCS에서 버킷 만들기](https://soundprovider.tistory.com/entry/GCP-Python%EC%97%90%EC%84%9C-GCP-Cloud-Storage-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0)
2. [redislab 접속해서 redis-server cloud 만들기](https://inpa.tistory.com/entry/REDIS-%F0%9F%93%9A-Redis%EB%A5%BC-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90-Redislabs?category=918728)
3. key.json 파일 만들고 설정
```
{
"GOOGLE_APPLICATION_CREDENTIALS" : "JSON 경로",
"BUCKET_NAME" : "자신이 설정한 BUCKET 이름",
"REDIS_HOST":"REDIS IP주소",
"REDIS_PASSWORD" : "REDIS API 키"
}
```
4. 터미널에서 streamlit 실행
```
streamlit run frontend.py
```
5. 터미널에서 fastapi backend 실행
```
python __main__.py
```

### 구현 내용
- 프론트에서 이미지 받아서 gcs 에 crop 이미지 저장하고 redis message queue에 이미지 고유 id push 까지 구현
# 인물 『 애니메이션 』 화 프로젝트

### 앗, 『이 세계』로부터의 손님이 내게 찾아왔다!?

<center>

| Before || After |
|--------|---|-------|
| ![Before](./src/cat1.jpg) || ![After](./src/cat1.jpg) |
| ![Before](./src/cat2.jpg) || ![After](./src/cat2.jpg) |
| ![Before](./src/cat3.jpg) || ![After](./src/cat3.jpg) |

</center>

## Visits

<p align="center">
<a href="https://count.getloli.com/"><img src="https://count.getloli.com/get/@Boostcamp5-CV-16-최『AI』?theme=rule34"/></a>
</p>

## Team Members

| [김지현](https://github.com/codehyunn) | [박상필](https://github.com/SangphilPark) | [오동혁](https://github.com/97DongHyeokOH) | [이상민](https://github.com/dldltkdals) | [이태순](https://github.com/LTSGOD) |
| :--------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQrscwx3lsb0twVlYNjri57vfLQ2R_c6ABDmA&usqp=CAU" alt="대체 텍스트" width="100" height="100"> | <img src="./src/T5082.jpg" alt="대체 텍스트" width="100" height="100"> | <img src="./src/T5124.jpg" alt="대체 텍스트" width="100" height="100"> | <img src="./src/T5141.png" alt="대체 텍스트" width="100" height="100"> | <img src="./src/T5165.jpg" alt="대체 텍스트" width="100" height="100"> |
| 모델 엔지니어링 및 학습 | 모델 엔지니어링 및 학습 | 서비스 파이프라인 구축 | 모델 파이프라인 구축 | 웹 서비스, 클라우드 구축 |

## Introduction

### 프로젝트 동기
- 애니메이션과 웹툰 매니아 층은 정식으로 제공되는 컨텐츠를 넘어 부가적인 창작물을 소비
- CV 분야의 생성 기술을 **애니메이션 및 웹툰**에 적용하여 **자신만의 콘텐츠 제작**을 돕고자 함
- 전체적인 이미지를 변경해주는 기존 서비스들과 달리 **인물만 골라서 원하는 캐릭터로 변환**해주는 서비스 기획

### 기대효과
- **애니메이션 및 웹툰 플랫폼**에서 활용 가능
- Instagram, TikTok, YouTube 등 **소셜 미디어의 파급효과** 기대
- **디지털 콘텐츠 창작 혁명**의 시작 기능

## Dataset
- (512, 512) 크기의 원하는 캐릭터 사진 8~10장
- 준비된 데이터 셋으로 Pretrained된 모델에 학습을 진행하면 해당 캐릭터 스타일로 변환해주는 Fine-tuning된 모델이 만들어짐

## Model
- Model Pipeline

![모델 파이프라인](./src/Model_Pipeline.png)

- Used Model

| ![Segmentation Model](./src/SAM.png) | ![Inpainting Model](./src/LAMA.png) | ![Stable Diffusion](./src/Stable%20diffusion.png) |
|:--------------------------:|:--------------------------:|:--------------------------:|
| SAM | LAMA | Stable Diffusion |

- SAM
- 인물 추출에 사용
- 사용자의 입력을 받아 원하는 객체만 segmentation
- 다른 segmentation 모델과 비교했을 때 성능이 좋음

- LAMA
- 인물이 제거된 배경 생성에 사용
- 기존 stable diffusion의 inpaint 파이프라인을 사용하면 신체 일부분을 놓치는 점을 확인
- 인물과 배경을 분리해 캐릭터를 생성하고 배경의 인물 영역을 inpainting하고 캐릭터를 넣어줌
- LAMA 사용 전/후

<center>

| LAMA 사용 전 | LAMA 사용 후 |
|--------------|--------------|
| ![사용 전](./src/before_lama.png) | ![사용 후](./src/after_lama.png) |

</center>


- Stable Diffusion
- 캐릭터 생성에 사용
- 쉽고 빠른 학습 -> DreamBooth를 통해 10장 이하의 사진과 한 시간 이내의 시간으로도 스타일 학습이 가능
- 좋은 캐릭터화 성능


## Product Serving
![Product Serving](./src/Service%20Pipeline.png)

## Demo

![Demo](https://github.com/boostcampaitech5/level3_cv_finalproject-cv-16/assets/64296314/baa60c7a-605f-463f-b1fe-dd782670242f)

## Future Works
- 모델 경량화를 통한 서비스 시간 단축
- 고품질 서비스 배포를 위한 Super Resolution 과정 추가
- 사용자가 원하는 캐릭터 자동 학습 기능 구축
- 카카오톡 등의 SNS를 통한 결과 공유 기능 추가
50 changes: 50 additions & 0 deletions app/frontend.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@font-face {
font-family: 'twaysky';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/[email protected]/twaysky.woff') format('woff');
font-weight: normal;
font-style: normal;
}

@font-face {
font-family: 'Pretendard-Regular';
src: url('https://cdn.jsdelivr.net/gh/Project-Noonnu/[email protected]/Pretendard-Regular.woff') format('woff');
font-style: normal;
}

.css-10trblm {
/* Main title */
font-family : 'twaysky';
text-align: left;
}

.css-1dp5vir{
/* line on top */
background-image : none ;
background-color : #f06292;
}


.css-vk3wp9{
/* side bar */
background-color: #fce4ec;
}

.css-nziaof{
/* chosen side bar */
background-color: #f8bbd0;
}

.css-content-new-font{
font-family: 'Pretendard-Regular';
font-weight:400;
}

.css-title-new-font{
font-family: 'Pretendard-Regular';
font-weight: 900;

}

.css-5rimss{
font-family : 'Pretendard-Regular';
}
Loading

0 comments on commit bd76bcf

Please sign in to comment.