반응형

저번편에는 API 연동 및 STT, TTS 를 사용해 gpt3.5 와 대화하는 것까지 구현을 진행했다.

하지만, 곰곰히 생각해보니 이 기능만으로는 부족하다는 생각이 들었고 추가 기능을 생각해보기로 했다.

추가 기능 구현

노인을 위한 이미지 편집 기능

보통 어르신들이 친구들과 카카오톡 대화를 할 때 카카오톡 이모티콘 만큼 자주 사용하는 사진들이 있다.

 

 

바로 요런 사진들!

나는 이미지를 Stable-diffusion 을 이용해 생성을 한 후 사용자가 원하는 글귀를 이미지에 삽입하는 기능을 구상했다.

위의 기능을 구현하기 위해 생각한 흐름을 한 번 보자면

 

알고리즘

  1. 이미지로 만들고 싶은 카테고리를 선택한다.
  2. 원하는 글귀를 선택한다.
  3. 이미지를 생성한다.
  4. 원하는 글귀를 이미지에 넣는다.

이미지생성

이미지 생성은 원래 로컬서버에 저장 후 사용하는 방식을 썼는데 그렇게하면 나중에 서버에 여러 이미지가 쌓여 넘칠게 뻔하기 때문에 API 를 이용해 보기로 했다.

async function query() {
        console.log("Send Query")
        setImageURL('');
        var data = { "inputs": props.inputValue,"use_cache":false }
        const response = await fetch(
            "<https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-2-1>",
            {
                headers: { Authorization: "Bearer API_KEY" },
                method: "POST",
                body: JSON.stringify(data),
            }
        );
        var formData = new FormData();
        await response.blob().then((res)=>{
            formData.append('image',res);
            formData.append('textPrompt', props.textPrompt)
            
            axios.post("/api/imgEdit",formData).then((res) => {
                console.log("api img edit response", res.data);
                setImageURL(res.data.slice(2,-1))
                props.setLoading(false)  
              });
        })
    }

frontend 에서 이미지를 받아올 것이기 때문에 js 코드로 함수를 구현했다.

huggingface API 를 사용하면 이미지 데이터는 blob 형식으로 받게 된다. 해당 데이터를 서버로 보내 처리하기 위해서는 formdata 에 저장 후 보내야한다.

formData.append(’image’,res) 를 사용해 blob 데이터를 저장한다. 이제 여기서 blob 데이터를 이미지로 출력하면 생성된 이미지가 화면에 나오게 된다.

나는 여기에 텍스트를 편집할것 이기 때문에 서버로 blob 과 텍스트 데이터를 전송하겠다.

이제 서버에 전달받은 데이터를 파이썬 코드를 실행하기 위해 다음과 같은 코드를 작성했다.

async function imgEdit (imgBlob, text) {
    return new Promise((resolve, reject) =>{
        let dataToSend;
        const msg = text;
        console.log("img edit : ",text, "img URL : ", imgBlob.path);
        const python = spawn(desiredPath, [ msg, imgBlob.path]);

        python.stdout.on('data', async function(data){
            dataToSend = data.toString();
            console.log("imgEdit start", dataToSend);
            resolve(dataToSend)
        });

        python.stderr.on("data", async function(data) {
            console.error("Python Error: ", data.toString())
            reject(data.toString())
        });

        python.on('error', (error) => {
            console.error("Spawn Error: ", error);
            reject(error);
        });
    })
}

Promise 객체를 생성 후 spawn 을 이용해 데이터를 파이썬 코드로 보낸다. 파이썬에서 처리를 한 데이터를 resolve 를 통해 받아 frontend 로 전달한다.

파이썬 코드는 다음과 같다.

import numpy as np
from PIL import ImageFont, ImageDraw, Image
import cv2
import sys
import base64
from io import BytesIO

def calculate_average_color(image):
    # 이미지의 RGB값을 계산
    r, g, b = 0, 0, 0
    pixels = image.load()
    width, height = image.size
    for y in range(height):
        for x in range(width):
            _r, _g, _b = pixels[x, y]
            r += _r
            g += _g
            b += _b
    # 평균값을 계산
    num_pixels = width * height
    r //= num_pixels
    g //= num_pixels
    b //= num_pixels
    return b,g,r

def get_complementary_color(b,g,r):
    # 보색을 계산
    r, g, b = b,g,r
    return 255 - r, 255 - g, 255 - b

try:    
    image = cv2.imread(sys.argv[2], cv2.IMREAD_ANYCOLOR)
    overlay = image.copy()
except Exception as e:
    print("error", e)

imgForColor = Image.open(sys.argv[2])

avgB, avgG,avgR = calculate_average_color(imgForColor)
comB,comG,comR = get_complementary_color(avgB,avgG,avgR)
b,g,r= 0,0,0

if((avgB+avgG+avgR)/3 >=128):
    b,g,r = 0,0,0
else:
    b,g,r = 255,255,255

font_size = 80
h,w,c = image.shape

line_w = w/font_size

height = str(h)

test_str = sys.argv[1]
max_line = (int)(len(test_str)/line_w)+1

str_slice = test_str.split(" ")
exp_str = []
str_token = ""

font = ImageFont.truetype("arial.ttf", font_size)

i = 0
while i != len(str_slice):
    if len(str_token) + len(str_slice[i]) + 1 <= line_w:
        str_token += " " + str_slice[i]
        i += 1
    else:
        exp_str.append(str_token.strip())  # str_token의 앞뒤 공백 제거 후 추가
        str_token = ""  # str_token 초기화
    if i == len(str_slice) and str_token:  # 마지막 요소이고, str_token이 빈 문자열이 아닌 경우
        exp_str.append(str_token.strip()) 

background_h = []

center_w = []
for i in range(len(exp_str)):
    left,top,right,bottom = font.getbbox(exp_str[i])
    center_w.append(right)

for i in range (len(exp_str)):
    background_h.append((int)((h/2) + (i - len(exp_str)/2) * font_size))

cv2.rectangle(overlay, (0, background_h[0]), (w, background_h[len(exp_str)-1]+font_size), (comB, comG, comR), -1)
alpha = 0.7  # 투명도 설정 (0은 완전 투명, 1은 완전 불투명)
image = cv2.addWeighted(overlay, alpha, image, 1 - alpha, 0)

img = np.zeros((200,400,3),np.uint8)
#b,g,r,a = 0,0,0,0
fontpath = "fonts/batang.ttc"
font = ImageFont.truetype(fontpath, font_size)
img_pil = Image.fromarray(image)
draw = ImageDraw.Draw(img_pil)

for i in range (len(exp_str)):
    ex = (int)((w-center_w[i])/2)-50   
    draw.text((ex,(int)((h/2) + (i - len(exp_str)/2) * font_size)), exp_str[i],font=font,fill=(b,g,r))
    
img = np.array(img_pil)

# 임의의 행렬을 생성
matrix = np.array([...])  # 여기에 실제 행렬을 입력

# 행렬을 이미지로 변환
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
resultImg = Image.fromarray(img)

# 이미지를 base64 문자열로 변환
buffered = BytesIO()
resultImg.save(buffered, format="JPEG")
img_str = base64.b64encode(buffered.getvalue())

print(str(img_str))

cv2.waitKey()
cv2.destroyAllWindows()

텍스트와 이미지를 받아 중앙정렬 후 텍스트를 돋보이기 위한 상자를 배치한다.

정말 가독성이 1도 없는 코드라 바꾸고 싶지만 그럴 시간이 있을까?

위의 프로세스를 수행하면 다음과 같은 그림이 생성된다.

 

 

짜란 정말 아무도 안쓸것같은 이미지 완성!

 

반응형

'ChatGPT > 챗봇프로젝트' 카테고리의 다른 글

[프로젝트] 노인을 위한 스마트챗봇 -1-  (2) 2023.03.21

+ Recent posts