이미지 생성 AI Stable Diffusion 활용하기

Somin Ji·2024년 5월 21일
1

졸업 프로젝트로 생성형 AI 기반 단체 추억 아카이빙 앱 서비스 Neverland를 기획했다.

Neverland는 크게 두 가지 기능을 가진다.
1) 공동 추억 기록 자동 생성
생성형 AI를 통해 각자 다르게 기억하는 추억을 모아 하나의 기록으로 자동 생성한다.
2) 단체의 추억을 효과적으로 아카이빙할 수 있도록 한다.
시간, 공간이라는 2가지 관점에서 단체의 공동 추억 기록을 모아볼 수 있도록 한다.

이번 글은 앱의 핵심 기능이라고 할 수 있는 공동 추억 기록 자동 생성을 주제로 포스팅 해보려 한다. 목차는 다음과 같다.

목차

이미지 생성 AI

  • 이미지 생성 AI 정의
  • 이미지 생성 AI 종류

Stable Diffusion 활용

  • 프로젝트 로직
  • Stable Diffusion API 적용
  • Stable Diffusion 활용한 이미지 생성 예시

References



이미지 생성 AI

'Neverland'는 단체 추억에 대해 각자가 기억하고 있는 바가 다르다는 점에 주목했다.
🤔 우리의 추억이긴 한데.. 이건 그냥 내가 쓴 글일 뿐이고 "우리"라는 게 와닿지 않는 것 같아
이를 해결하기 위해 'Neverland'는 여러 유저들의 기록을 모아 하나의 이미지로 생성하기로 했다. 새로운 이미지를 생성하는 이미지 생성 AI에 대해 알아보자!


이미지 생성 AI란?

인공지능을 활용해 텍스트 또는 이미지를 기반으로 새로운 이미지를 생성하는 기술

예를 들어, "공을 가지고 놀고 있는 고양이를 그려줘"라고 입력할 경우, 이미지 생성 AI가 해당 텍스트를 이해하고 그에 맞는 이미지를 생성해준다. 자세한 예시는 아래의 구현에서 다루도록 하겠다.


대표적으로 다음의 2가지 주요 아키텍처를 사용한다.

Transformers
텍스트를 이미지로 변환하는 데 사용되며, NLP에서 많이 사용되던 transformer 구조를 이미지 생성에 적용한 것이다.
ex) DALL-E

Diffusion Models
이미지를 점진적으로 생성하거나 복원하는 방식으로 작동한다. 노이즈가 있는 이미지에서 시작하여 점진적으로 더 나은 해상도의 이미지를 생성하는 방식이다.
ex) Stable Diffusion


이미지 생성 AI 종류

대표적인 이미지 생성 AI는 다음과 같다.

DALL-E
- Transformers 아키텍처 기반
- 사용자가 입력한 텍스트 설명을 이해하고 이를 시각적으로 표현하는 이미지를 생성함
- Chat-GPT와 연동되어 있어 전체 맥락에 대한 이해가 뛰어남

MidJourney
- Transformers와 Diffusion Models 아키텍처를 기반
- Transformers 모델을 사용하여 텍스트를 입력하면 그에 상응하는 이미지를 생성하고, Diffusion Models를 사용하여 디테일을 추가한 고품질의 이미지를 생성함
- 일러스트레이션 및 디자인 분야에 특화

Stable Diffusion
- Diffusion Models 아키텍처 기반
- 새로운 이미지를 생성할 때, 노이즈가 많은 상태에서 시작하여 점진적으로 노이즈를 제거하면서 선명한 이미지를 생성함
- 이미지와 텍스트를 동시에 프롬프트로 넣을 수 있음


🤔 그렇다면 이 중에서 어떤 이미지 생성 AI를 사용할까?

본인의 서비스 용도에 맞는 AI를 선택하면 된다.
우리 서비스에서는 최초 작성자의 이미지와 여러 유저의 텍스트를 프롬프트로 입력한다. 프롬프트 입력 방식과 기타 세부 조정의 유용함 때문에 Stable Diffusion을 사용하기로 했다!


Stable Diffusion 구현

프로젝트 로직

Neverland 프로젝트의 로직은 다음과 같다.

특정 추억을 함께한 모든 유저의 텍스트를 모아 Chat-GPT를 통해 하나의 프롬프트를 생성한다.
유저가 피드 이미지를 업로드했는지 여부에 따라, Stable Diffusion의 입력값과 호출되는 API에 차이가 있다. 최초 작성자가 이미지를 포함해 글을 작성했다면, 해당 이미지와 프롬프트를 입력값으로 하여 Image-to-Image with prompt API를 호출한다. 그러나 이미지 없이 작성된 피드의 경우, 프롬프트만을 입력값으로 하여 Text-to-Image API를 호출한다.


Stable Diffusion API 적용

이제 위의 로직을 구현해보자.
여러 유저들의 텍스트가 Chat-GPT를 거쳐 하나의 프롬프트로 병합되었음을 전제함


환경 설정

API key 발급
API 요청을 하기 위해서는 API key를 발급받아야 한다. 다음의 링크를 통해 쉽게 키를 생성할 수 있다.
🚨주의! 발급받은 API key는 절대 유출하면 안 된다.

https://platform.stability.ai/account/keys

STABILITY_API_KEY='발급 받은 API key'

생성된 API key는 환경 설정 파일에 둔다. .env 파일을 생성하여 API 키와 같은 중요한 설정 값을 저장한다. 이 파일을 통해 민감한 정보를 코드베이스로부터 분리하여 보안성을 높이기 위함이다!

API key 불러오기

import { STABILITY_API_KEY } from 'react-native-dotenv';

API를 불러오는 파일에서 react-native-dotenv 모듈을 사용하여 환경 변수 파일에 정의된 설정 값을 불러와 API를 사용한다.


Text-to-Image API 적용

React Native 환경에서 동작하기 때문에 Typescript를 사용했음


1. 이미지 생성 인터페이스 정의

interface generateImagesProps {
  text: string;
}

이미지 생성 함수인 generateImages에 전달되는 매개변수의 구조를 정의한다. Text-to-Image API의 경우, 이미지 없이 유저가 작성한 내용만으로 이미지를 생성하므로 text 필드만 둔다.

text: 생성할 이미지에 대한 텍스트 설명


2. 이미지 생성 함수 정의

const generateImages = async ({ text }: generateImagesProps) => {
  const engineId = 'stable-diffusion-v1-6';
  const apiHost = 'https://api.stability.ai';
  const apiKey = STABILITY_API_KEY;

  if (!apiKey) throw new Error('Missing Stability API key.');

이미지를 생성하는 비동기 함수를 정의한다. Stable Diffusion 모델의 API 정보를 포함한다.

engineId: 사용할 모델의 ID
apiHost: Stability AI API의 기본 URL
apiKey: 환경 변수에서 불러온 API key, 없을 시 오류 발생


3. API Request Body 정의

   const requestBody = {
    text_prompts: [
      {
        text: text,
      },
    ],
    samples: 1,
    cfg_scale: 7,
    steps: 30,
    style preset: 'cinematic'
  };

Text-to-Image의 경우, JSON 형식의 Request Body 사용한다. 이는 API의 요구사항 및 효율성 때문이다.
API 요구사항: 텍스트 프롬프트와 설정 값들이 단순한 key-value 쌍으로 이루어진 간단한 데이터 구조
효율성: JSON 형식은 간단하고 가볍기 때문에 텍스트 데이터와 설정 값을 전송하는 데 효율적임

text_prompts: 생성할 이미지에 대한 텍스트 설명(프롬프트 내용은 추후 삽입 예정)
samples: 생성할 이미지의 샘플 수(하나만 생성하므로 1로 설정)
cfg_scale : 텍스트 프롬프트가 생성할 이미지에 영향을 미치는 정도
steps: 생성 과정의 단계 수
style_preset: 생성할 이미지의 스타일(실사 느낌을 위해 cinematic 으로 설정)


4. API Request

  const response = await fetch(
    `${apiHost}/v1/generation/${engineId}/text-to-image`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify(requestBody),
    },
  );

fetch 함수를 사용하여 Text-to-Image API에 POST 요청을 보낸다.

HTTP method: POST
Request Header 설정
- Content-Type: 클라이언트가 JSON 형식의 데이터를 전송함
- Accept: 클라이언트가 JSON 형식의 응답을 기대함
- Authorization: 클라이언트의 인증 정보 확인
Request Body: 앞선 단계에서 정의한 JSON 형식의 Request Body로 포함시킨다.


5. Response 처리와 생성된 이미지 반환

  if (!response.ok) {
    throw new Error(`Non-200 response: ${await response.text()}`);
  }

  const responseJSON = await response.json();
  const generatedImages = {
    base64: `data:image/png;base64,${responseJSON.artifacts[0].base64}`,
  };
  return generatedImages;
};

API 응답을 처리하고 생성된 이미지를 반환한다.

Response 확인: 응답 상태 코드가 200이 아닌 경우, 오류 발생
JSON 파싱: Response를 JSON 형식으로 파싱하여 클라이언트는 이를 JavaScript 객체로 변환하여 사용
이미지 데이터 처리: Response에서 Base64 인코딩된 이미지를 추출하고, 이를 base64 형식의 이미지 데이터 URL로 변환


Image-to-Image API 적용

React Native 환경에서 동작하기 때문에 Typescript를 사용했음


1. 이미지 생성 인터페이스 정의

interface generateImagesProps {
  imageUri: string;
  text: string;
}

이미지 생성 함수인 generateImages에 전달되는 매개변수의 구조를 정의한다.

imageUri: 이미지 파일의 URI
text: 생성할 이미지에 대한 텍스트 설명

🚨주의! Text-to-Image와 다르게, imageUri를 포함해야 함
Image-to-Image API의 경우, 초기 이미지를 설정하여 해당 이미지를 바탕으로 새로운 이미지를 생성하기 때문이다.

2. 이미지 생성 함수 정의

const generateImages = async ({ imageUri, text }: generateImagesProps) => {
  const engineId = 'stable-diffusion-v1-6';
  const apiHost = 'https://api.stability.ai';
  const apiKey = STABILITY_API_KEY;

  if (!apiKey) throw new Error('Missing Stability API key.');

Text-to-Image API와 동일


3. API Request Body 정의

  const formData = new FormData();
  formData.append('init_image', {
    uri: imageUri,
    name: 'init_image.png',
    type: 'image/png',
  });
  formData.append('init_image_mode', 'IMAGE_STRENGTH');
  formData.append('image_strength', 0.35);
  formData.append('text_prompts[0][text]', text);
  formData.append('cfg_scale', 7);
  formData.append('samples', 1);
  formData.append('steps', 30);
  formData.append('style_preset', 'cinematic');

API 요청 본문을 구성할 FormData 객체를 생성한다. 초기 이미지와 텍스트 프롬프트를 모두 전송해야 하므로, Text-to-Image의 단순 JSON 형식이 아닌 멀티파트 형식인 FormData가 적합하다.

init_image: 초기 이미지 파일(유저가 업로드한 이미지)
image_strength: 초기 이미지가 생성할 이미지에 영향을 미치는 정도
text_prompts: 생성할 이미지에 대한 텍스트 설명
cfg_scale, samples, steps, style_preset: 이미지 생성 세부 설정값


4. API Request

  const response = await fetch(
    `${apiHost}/v1/generation/${engineId}/image-to-image`,
    {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
      body: formData,
    },
  );

fetch 함수를 사용하여 Image-to-Image API에 POST 요청을 보낸다.
HTTP method: POST
Request Header: Text-to-Image와 유사하나 Image-to-Image의 FormData는 브라우저나 환경에 의해 자동으로 Content-Type을 설정하기 때문에 따로 설정값을 포함하지 않음
Request Body: 앞선 단계에서 정의한 FormData 객체를 Request Body로 포함시킨다.


5. Response 처리와 생성된 이미지 반환

  if (!response.ok) {
    throw new Error(`Non-200 response: ${await response.text()}`);
  }

  const responseJSON = await response.json();
  const generatedImages = {
    base64: `data:image/png;base64,${responseJSON.artifacts[0].base64}`,
  };
  return generatedImages;
};

Text-to-Image와 동일


Stable Diffusion 전체 코드

Text-to-Image

// API key 
import { STABILITY_API_KEY } from 'react-native-dotenv';

// 이미지 생성 인터페이스
interface generateImagesProps {
  text: string;
  style?: string; // 유저가 직접 선택할 수 있도록 함 
}

// 이미지 생성 함수
const generateImages = async ({ text, style }: generateImagesProps) => {
  const engineId = 'stable-diffusion-v1-6';
  const apiHost = 'https://api.stability.ai';
  const apiKey = STABILITY_API_KEY;

  if (!apiKey) throw new Error('Missing Stability API key.');

  // Request Body 작성 - JSON 형식
  const requestBody = {
    text_prompts: [
      {
        text: text,
      },
    ],
    cfg_scale: 7,
    steps: 30,
    samples: 1,
    style_preset: style
  };

  // API 요청 보내기
  const response = await fetch(
    `${apiHost}/v1/generation/${engineId}/text-to-image`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify(requestBody),
    }
  );

  // 응답 상태 코드 확인
  if (!response.ok) {
    throw new Error(`Non-200 response: ${await response.text()}`);
  }

 const responseJSON = await response.json();

  // 이미지 데이터 추출 및 변환
  const generatedImageBase64 = responseJSON.artifacts[0].base64;
  const generatedImageURL = `data:image/png;base64,${generatedImageBase64}`;

  return generatedImageURL;
};

export default generateImages;

Image-to-Image

// API key 
import { STABILITY_API_KEY } from 'react-native-dotenv';

// 이미지 생성 인터페이스
interface generateImagesProps {
  imageUri: string;
  text: string;
  style?: string; // 유저가 직접 선택할 수 있도록 함 
}

// 이미지 생성 함수
const generateImages = async ({ imageUri, text, style }: generateImagesProps) => {
  const engineId = 'stable-diffusion-v1-6';
  const apiHost = 'https://api.stability.ai';
  const apiKey = STABILITY_API_KEY;

  if (!apiKey) throw new Error('Missing Stability API key.');
  
  // Request Body 작성 - Formdata 형식
  const formData = new FormData();
  formData.append('init_image', {
    uri: imageUri,
    name: 'init_image.png',
    type: 'image/png',
  });
  formData.append('init_image_mode', 'IMAGE_STRENGTH');
  formData.append('image_strength', 0.35);
  formData.append('text_prompts[0][text]', text);
  formData.append('cfg_scale', 7);
  formData.append('samples', 1);
  formData.append('steps', 30);
  style && formData.append('style_preset', style);

  // API 요청 보내기
  const response = await fetch(
    `${apiHost}/v1/generation/${engineId}/image-to-image`,
    {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
      body: formData,
    },
  );

  // 응답 상태 코드 확인
  if (!response.ok) {
    throw new Error(`Non-200 response: ${await response.text()}`);
  }

  //이미지 데이터 추출 및 반환
  const responseJSON = await response.json();
  const generatedImages = {
    base64: `data:image/png;base64,${responseJSON.artifacts[0].base64}`,
  };
  return generatedImages;
};

export default generateImages;

Stable Diffusion 활용한 이미지 생성 예시

실제로 프롬프트를 입력하여 새로운 사진을 생성해보자!

interface GeneratedImages {
  base64: string;
}
  const text =
    '작년 여름에 우리 둘이 제주도 여행 간 거 기억 나? 맛집도 가고 한림 해수욕장에서 수영도 했었잖아.. 저녁에 산책하면서 봤던 핑크 노을이 진짜 예술이었지~ 이 기억이 너무 소중하다 또 가고싶어!!';
  const createImage = async () => {
    try {
      const images = await generateImages({ imageUri, text, style });
      console.log(images.base64);
      setGeneratedImages({ base64: images.base64 });
      if (!complete) setRealComplete(true);
    } catch (err) {
      console.error(err);
    }
  };

작년 여름 제주 여행을 테마로 한 프롬프트와 아래의 사진을 전달하면


다음과 같이 Stable Diffusion이 업로드한 사진에 프롬프트를 결합한 새로운 이미지를 만들어준다.

초기 이미지의 분홍빛 하늘과 프롬프트의 "두 명", "노을"과 같은 구체적인 내용이 잘 반영된 사진이 생성되었음을 확인할 수 있다!




이번 포스팅에서는 이미지 생성 AI를 알아보았다. 특히 Stable Diffusion의 활용법을 실습하면서 프롬프트를 통해 실제 이미지를 생성해보았다.

🤔 What's next ?
점점 졸업 프로젝트의 끝이 보인다. 프로젝트 회고로 돌아올 것 같다!


References

이미지 생성 AI 소개
https://futures-studies.tistory.com/entry/%EC%83%9D%EC%84%B1AI-%EC%9D%B4%EA%B2%83%EB%A7%8C-%EC%95%8C%EC%9E%903-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%83%9D%EC%84%B1%ED%98%95-AI-%EC%86%8C%EA%B0%9C

Stable Diffusion API 문서
https://platform.stability.ai/docs/api-reference

0개의 댓글

관련 채용 정보