AWS 기술 블로그

Amazon Bedrock Gallery: AI가 그리는 당신의 전생 이야기

AWS Seoul Summit 2024은 올해로 10주년을 맞이한 국내 최대 규모의 IT 컨퍼런스입니다. 이번 Summit에서는 생성형 AI와 AWS 서비스를 활용해 새로운 아이디어를 탐색하고 이를 신속하고 효율적으로 실행하는 방법을 소개하는 다양한 콘텐츠가 준비되었으며, 많은 고객들이 참여했습니다. 이 블로그에서는 AWS 서비스를 직접 체험해 볼 수 있는 Expo 현장에서 참가자들에게 특별하고 재미있는 경험을 제공했던 “Amazon Bedrock Gallery” 데모의 아키텍처 구현 방법과 전시 과정에서 얻은 인사이트를 공유하고자 합니다. 또한 이 블로그의 구현 코드도  오픈소스로 공개 합니다.

Amazon Bedrock Gallery 프로젝트 소개

Amazon Bedrock은 AI21 Labs, Anthropic, Cohere, Meta, Mistral AI, Stability AI 및 Amazon과 같은 선도적인 AI 회사의 다양한 고성능 파운데이션 모델(FM)을 단일 API를 통해 제공하는 완전 관리형 서비스입니다. 사용자들은 Amazon Bedrock의 다양한 모델을 활용하여 다양한 유스케이스에 생성형 AI 애플리케이션을 만들 수 있습니다.

Amazon Bedrock Gallery는 Amazon Bedrock 서비스를 소개하고, 참여자들에게 특별하고 재미있는 경험을 제공하기 위해 기획되었습니다. 특히 이미지를 생성하고 재미있는 이야기를 더해, 참여자들에게 더욱 몰입감 있는 경험을 선사하고자 했습니다. 이를 위해 “전생 이야기”라는 컨셉으로 다양한 시대의 이미지와 이야기를 기획했습니다. 또한, 이미지는 실제와 유사한 “실사”, 페인트로 그린 듯한 “유화”, 세계를 여행하는 “여행자”, 애니메이션 스타일의 “애니메이션” 등 네 가지 테마로 생성하기로 했습니다.

Amazon Bedrock Gallery 프로젝트 고려사항

Summit의 Expo 현장은 수많은 참여자들이 AWS 서비스를 체험하기 위해 방문하는 공간입니다. 따라서 Expo의 모든 데모는 참여자가 원활하게 체험할 수 있도록 동선, 체험 시간, 사용자의 개인정보 사용 여부 및 동의 절차 등을 꼼꼼하게 확인해야 합니다. 아래는 Amazon Bedrock Gallery 데모를 위해 고려한 사항들입니다.

  • 전시 공간의 디자인 및 크기
  • 전시 참여자의 동선 및 최대 체험 시간
  • 전시 참여자 안내 가이드
  • 전시 참여자의 개인정보 사용 동의 및 폐기 방안
  • 전시 현황 및 디버깅을 위한 대시보드

특히, 전시 참여자의 동선 및 최대 체험 시간은 가장 중요한 요소라고 판단했습니다.

먼저, 전시에 참여한 사람이 입장부터 체험을 마치고 퇴장하기까지의 총 체험 시간을 관리하는 것이 중요했습니다. GenAI 모델의 성능이 향상되면서 추론에 소요되는 시간이 줄어들었지만, 이미지와 전생 스토리를 생성하고 이를 사용자의 사진과 합성하는 데 걸리는 시간이 1분을 넘기면 사용자가 지루함을 느낄 가능성이 높다고 판단했습니다. 따라서 대규모 사용자가 체험하는 공간에서는 빠르고 인상적인 경험을 제공하기 위해, 전생 이미지와 스토리를 미리 준비해 두는 것이 필요했습니다. 즉, 현장에서는 사용자가 자신의 사진만 촬영하고, 이를 준비된 전생 이미지와 합성하는 과정만 거치면 전체 체험 시간을 1분 이내로 줄일 수 있었습니다. 아래 이미지는 사용자의 경험 전체 흐름을 보여줍니다.

참여자의 동선 관리도 원활한 데모 운영에 있어 매우 중요한 요소였습니다. 전시 공간 내에서 전생 이미지를 생성하고, 사용자의 사진을 촬영한 후 결과물을 합성하는 과정은 최대한 짧은 시간 안에 이루어져야 했으며, 동시에 여러 참여자가 병렬로 체험할 수 있도록 물리적인 요소 배치 또한 신중히 고려되었습니다. 이를 위해 8대의 대형 LED 스크린을 좌우로 비스듬히 배치하여, 정면에서 봤을 때 웅장한 느낌을 전달하는 동시에, 8명의 참여자가 각자 병렬로 전시를 체험할 수 있도록 공간을 설계했습니다.

Amazon Bedrock Gallery 솔루션 소개

전체 아키텍처

Amazon Bedrock Gallery는 개발 시 프론트 엔드부터 백엔드 서비스까지 Serverless 서비스를 활용하여 인프라 구성 및 운영 요소를 최소화하고 개발에 집중할 수 있었습니다. 대략적인 아키텍처는 위와 같으며 크게 3단계로 구분됩니다.

  1. 사용자에게 보여지는 디스플레이 및 캡처 이미지 업로드
  2. 얼굴 인식 및 face crop
  3. Image Generator Model로 사전 생성된 이미지와 2번 결과물을 합성

먼저 1과정AWS Amplify의 강력한 프론트엔드 개발 기능을 활용하여 사용자가 첫 화면에 진입 시 촬영을 위한 UI를 제공합니다. 그리고 사용자가 이미지를 캡쳐하면 Amazon Simple Storage Service(S3)

에 원본 이미지가 저장되고 S3 Event에 Trigger되어 2과정이 진행됩니다. 이때 얼굴 부분만 추출하기 위해 Amazon Rekognition 서비스를 활용하며 얼굴 부분의 좌표값을 얻어 잘린 이미지를 다시 S3에 저장하게 되고 마찬가지로 S3 Event가 Trigger되며 최종적으로 이미지 합성을 위한 3과정이 진행됩니다. 이미지 합성 시, 실시간 합성을 위한 Amazon SageMaker Realtime Inference를 활용하였으며 약 40초의 합성 시간을 거쳐 최종적으로 디스플레이에 보여지게 됩니다.

이제 본격적으로 각 단계별로 사용된 서비스와 구체적인 구성 과정, 그리고 사용된 코드를 살펴보도록 하겠습니다.

1. 개발 환경 및 배포 자동화 구성하기

이 프로젝트는 여러 사람들이 참여해서 개발하기 때문에 프로젝트 관리 환경이 필요했습니다. 먼저 소스 저장소가 필요하고 간단한 이슈 트래커 및 배포 파이프라인을 구성해야 했습니다. 이를 위해 Amazon CodeCatalyst를 사용했습니다.

Amazon CodeCatalyst는 개발 라이프사이클의 생산성을 강화하는 통합 도구로 Git 저장소를 제공하고 CICD Workflow를 구성할 수 있으며 작업을 관리할 수 있는 환경 등을 제공합니다. Amazon CodeCatalyst 에서 제공하는 Dev Environment 기능은 개인 사용자 별로 AWS에 개발 환경 및 연동 리소스를 구성해서 실제 AWS에서 운용되는 환경과 동일한 환경에서 개발하고 테스트 할 수 있는 경험을 제공합니다.

그림. Amazon CodeCatalyst에서 구성한 CICD Workflow

Amazon CodeCatalyst를 사용해서 Git 저장소를 생성했고 Issues를 통해 작업을 관리했습니다. Issues에서는 Version 라벨을 만들고 각 버전 별로 기능 배포를 계획한 후 이에 따라 개발을 진행하며 각 버전을 릴리즈 했습니다. 코드를 작업할 때는 branch를 만들어서 작업을 하고 Pull Request를 보낸 후 필요에 따라 코드 리뷰도 진행했습니다.

또한 CICD Workflow를 구성할 때는 develop branch에 코드가 올라오면 개발 환경으로 자동 배포되게 하고, main branch로 소스가 통합된 후, 배포 파이프라인을 시작하면 production 환경으로 배포될 수 있게 구성했습니다.

이렇게 초기 개발 환경 및 배포 자동화 구성이 되었습니다. 팀 구성원들은 Amazon CodeCatalyst에서 생성한 Github에서 코드를 받아서 작업하고 커밋하고 필요할 때 리뷰하고, 이 안에서 작업계획을 하고, 개발환경으로 그리고 프로덕션 환경으로 자동으로 배포하는 환경을 구성했습니다.

2. Frontend 구성하기

프론트 엔드 애플리케이션은 사진 촬영 애플리케이션과 디스플레이 애플리케이션으로 구성됩니다. 두 애플리케이션 모두 React 라이브러리를 사용하여 개발되었으며, Amazon CodeCatalyst로 빌드 및 배포 워크플로우를 구성하여 AWS Amplify Hosting을 통해 배포하였습니다.

사진 촬영 애플리케이션

사진 촬영 애플리케이션에서는 참여자가 태블릿을 직접 조작하여 본인의 사진을 촬영하고, 이를 백앤드의 Amazon S3 버킷에 업로드하게 됩니다. 전시에서는 총 8대의 디스플레이와 8대의 태블릿이 활용되었기 때문에, 사진을 업로드하는 과정에서 각 태블릿이 어떠한 디스플레이와 매핑이 되어 있는지에 대한 정보도 함께 전달해야 했습니다.

이는 AWS Amplify의 Auth 기능을 추가하여 Amazon Cognito UserPool을 생성하고, 태블릿마다 별도의 사용자 계정으로 로그인하는 방식으로 해결하였습니다. 이 때, 로그인을 위한 UI는 Amplify의 Authenticator UI Component를 활용하여 복잡한 코드 작성 없이 쉽게 추가할 수 있었습니다.

디스플레이 애플리케이션

디스플레이 애플리케이션은 합성된 사진을 LED 스크린에 표시하고, 참여자들이 모바일로 자신의 사진을 볼 수 있는 두 가지 주요 기능을 제공합니다. 8개의 LED 스크린이 하나의 애플리케이션을 미러링하는 방식이었기 때문에, 단일 애플리케이션에서 8개의 이미지를 모두 관리하도록 구현하였습니다.

구현 복잡도를 줄이기 위해서 10초마다 백엔드 서버에 API 요청을 보내는 폴링 방식을 채택했습니다. 서버로부터 각 디스플레이에 해당하는 이미지의 presigned URL, UUID, 그리고 관련 스토리를 응답받아 변경 사항이 있는 경우 업데이트되도록 구성하였습니다. 또한 이미지의 UUID를 이용해 QR 코드를 생성하여, 관람객들이 모바일 환경에서 자신의 사진을 쉽게 확인할 수 있도록 했습니다.

실제 전시 환경에서 발생할 수 있는 네트워크 장애나 백엔드 API 오류 등 다양한 문제 상황에 대비하기 위해 몇 가지 안전장치를 마련했습니다. 예를 들어, 연속 5회 이상 백엔드 오류 응답을 받으면 자동으로 디폴트 이미지로 전환되도록 하였습니다. 또한, 관리자용 컨트롤 패널을 추가하여 개별 또는 전체 이미지를 수동으로 디폴트 이미지로 변경하거나 API를 강제로 호출할 수 있게 했습니다.

3. Backend 구성하기

디스플레이에서 촬영한 이미지는 합성을 위해 Amazon S3 Bucket에 업로드 되며 아래 단계별 가공을 거쳐 합성된 최종 이미지가 생성됩니다. 먼저 원본 이미지에서 사용자의 얼굴을 추출하는 Mask Image 처리를 살펴보겠습니다.

Mask Image 처리

처음 촬영한 이미지는 S3의 face Image 디렉터리에 저장됩니다. fase Image에는 사용자 뿐만 아니라 뒷 배경의 대기 인원, 사물 등 다양한 요소가 포함되어 있기 때문에 사람의 얼굴이 어떤 영역에 있는지 추출해야 합니다. 따라서 face Image 버킷에 이미지가 업로드되면 Lambda를 Trigger하여 사용자 얼굴을 추출하기 위해 Amazon Rekognition 서비스의  DetectFace API를 호출합니다. 이때 DetectFace API의 응답에서 감지된 얼굴 영역은 BoundingBox의 Height, Left, Top, Width 값으로 알 수 있으며 다음과 같은 Lambda 코드로 구현 되었습니다.

def lambda_handler(event, context):
    s3 = boto3.client('s3')
    bucket_name = event['Records'][0]['s3']['bucket']['name']
    key = event['Records'][0]['s3']['object']['key']
    
    # S3에서 이미지 파일을 로드
    response = s3.get_object(Bucket=bucket_name, Key=key)
    image = Image.open(response['Body'])
    if image.mode == 'RGBA':
        image = image.convert('RGB')
    
    # Rekognition으로 가장 큰 얼굴 영역 추출
    cropped_image, width, height, f_left, f_top, f_width, f_height, _ = show_faces(image, padding_ratio=0.5)
    
    if cropped_image is not None:
        # 추출한 얼굴 영역을 새로운 이미지로 저장
        cropped_image = cropped_image.crop((f_left, f_top, f_left + f_width, f_top + f_height))
        
        # 새로운 S3 버킷과 키 지정
        new_bucket_name = '[bucke_name]'
        new_key = 'images/mask-image/' + key.split('/')[-1]
        
        # 추출한 이미지를 S3에 저장
        buffered = BytesIO()
        cropped_image.save(buffered, format="png")
        image_bytes = buffered.getvalue()
        s3.put_object(Bucket=new_bucket_name, Key=new_key, Body=image_bytes)
        
        return {
            'statusCode': 200,
            'body': json.dumps('Cropped face image saved successfully!')
        }
    else:
        return {
            'statusCode': 200,
            'body': json.dumps('No faces detected in the image.')
        }

이때 여러 인물 중, 사진을 촬영한 인물을 선택하기 위해 show_faces 함수를 정의했습니다. 감지된 인물 중 카메라 앞에 위치한 인물의 BoundingBox가 가장 클 것으로 판단하기 위해 각 BoundingBox의 크기를 비교하고 가장 큰 영역을 가진 얼굴을 합성할 대상으로 지정했습니다. DetectFace API가 추출한 영역은 사용자의 얼굴 영역에 FIT하게 추출되며 추후 이미지 합성 시 간헐적으로 얼굴을 찾지 못하는 이슈가 발생했습니다. 따라서 추출된 영역에 여유 공간(padding_ratio)을 조정하여 이미지 합성 모델이 얼굴을 잘 인식할 수 있도록 개선하였습니다.

def show_faces(image, target_region='us-west-2', padding_ratio=0.5):
    client = boto3.client('rekognition', region_name=target_region)
    
    imgWidth, imgHeight = image.size
    ori_image = copy.deepcopy(image)
    
    buffer = BytesIO()
    image.save(buffer, format='jpeg')
    val = buffer.getvalue()
    
    response = client.detect_faces(Image={'Bytes': val}, Attributes=['ALL'])
    
    largest_area = 0
    largest_face_box = None
    
    for faceDetail in response['FaceDetails']:
        box = faceDetail['BoundingBox']
        left = imgWidth * box['Left']
        top = imgHeight * box['Top']
        width = imgWidth * box['Width']
        height = imgHeight * box['Height']
        
        current_area = width * height
        
        if current_area > largest_area:
            largest_area = current_area
            largest_face_box = (left, top, width, height)
    
    if largest_face_box:
        left, top, width, height = largest_face_box
        # 여유 공간 추가
        padding_width = width * padding_ratio
        padding_height = height * padding_ratio
        padded_left = max(0, left - padding_width)
        padded_top = max(0, top - padding_height)
        padded_right = min(imgWidth, left + width + padding_width)
        padded_bottom = min(imgHeight, top + height + padding_height)
        
        return ori_image, imgWidth, imgHeight, int(padded_left), int(padded_top), int(padded_right - padded_left), int(padded_bottom - padded_top), response
    else:
        return None, None, None, None, None, None, None, None

여기까지 합성에 필요한 이미지를 가공하는 단계를 알아보았습니다. 이제 Amazon Bedrock Gallery의 핵심인 얼굴 합성 처리 방법에 대해 알아보겠습니다.

얼굴 합성 처리

얼굴 합성 처리는 크게 다음과 같습니다.

– 1/ 얼굴 인식 및 스왑 단계

– 2/ 얼굴 복원(Restoration) 단계로 진행됩니다.

이 과정은 사전 학습된 모델을 이용해서 각각 수행됩니다. 이 모든 과정은 SageMaker의 BYOC(Bring Your Own Container) 기능을 활용합니다. 이 기능을 사용하기 위해서는 Building your own algorithm 문서를 참고하여 사용자가 정해진 구조에 맞춰야 합니다. 예를 들어, 아래는 gfpgan 모델의 추론을 위해 BYOC를 활용하기 위한 기본 디렉토리 구조입니다.

.
├── Dockerfile
├── build_and_push.sh
└── src
    ├── nginx.conf
    ├── predictor.py
    ├── serve
    ├── train
    └── wsgi.py

이 중에서 추가적인 수정이 필요한 파일은 아래와 같습니다.

  • Dockerfile : 사전 학습된 모델을 설정하고, ENTRYPOINT 실행을 위한 컨테이너 이미지를 만듭니다.
  • build_and_push.sh : Dockerfile 변경 시점에 새롭게 빌드하고, 레지스트리에 업로드합니다.
  • predictor.py : Flask 웹 서버와 모델의 예측을 실제로 구현하는 프로그램입니다. 실제 예측 부분을 귀하의 애플리케이션에 맞게 사용자 정의해야 합니다.

그렇다면 Dockerfile과 predictor.py을 자세히 살펴보겠습니다. 먼저 Dockerfile을 살펴보겠습니다.

# Base image
ARG ECR_REGISTRY
ARG ECR_REPOSITORY
ARG IMAGE_TAG
FROM ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}

# Install necessary packages
RUN apt-get update && \
    apt-get install -y sudo wget git xz-utils nginx ca-certificates libsm6 libxext6 ffmpeg libfontconfig1 libxrender1 libgl1-mesa-glx && \
    rm -rf /var/lib/apt/lists/*

RUN wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && \
    pip install opencv-python-headless opencv-contrib-python flask gevent gunicorn boto3 && \
    rm -rf /root/.cache

# Clone the GFPGAN repository to the home directory and install requirements
RUN mkdir -p /opt/program/GFPGAN && \
    git clone https://github.com/TencentARC/GFPGAN.git /opt/program/GFPGAN && \
    cd /opt/program/GFPGAN && \
    pip install basicsr && \
    pip install facexlib && \
    pip install realesrgan && \
    pip install -r requirements.txt --no-cache-dir && \
    sed -i 's/from torchvision.transforms.functional_tensor/from torchvision.transforms.functional/g' /opt/conda/lib/python3.11/site-packages/basicsr/data/degradations.py
    
# Set environment variables
ENV PYTHONUNBUFFERED=TRUE
ENV PYTHONDONTWRITEBYTECODE=TRUE
ENV PATH="/opt/program:${PATH}"
COPY src /opt/program
WORKDIR /opt/program

# 스크립트에 실행 권한 부여
RUN chmod +x /opt/program/serve

# SageMaker는 기본적으로 이 포트를 사용합니다
EXPOSE 8080

# serve 스크립트를 실행하는 엔트리포인트 설정
ENTRYPOINT ["serve"]

다음은 predictor.py 입니다. /invocations로 요청이 들어왔을 때 실제 추론에 필요한 로직을 구현합니다. 아래처럼 추론 요청을 받으면 S3에서 필요한 이미지를 다운로드 받고, 프로세싱을 처리하여 다시 S3에 업로드하는 로직을 구현합니다.

@app.route('/invocations', methods=['POST'])
def invocations():
    input_data = request.get_json(force=True)

    uuid = input_data['uuid']
    bucket = input_data['bucket']
    source_object_key = input_data['source']
    output_object_key = input_data['output']
    source_path = f"/opt/workspace/source/{uuid}.png"
    output_path = f"/opt/workspace/output"
    output_file_path = f"/opt/workspace/output/restored_imgs/{uuid}.png"

    fetch_images(bucket, source_object_key, source_path)

    process_images(source_path, output_path)

    s3_client.upload_file(output_file_path, bucket, output_object_key)

    remove_all_files(source_path, output_file_path)

    return jsonify(input_data)

4. 이미지 합성 파이프라인 구성하기

이제 위 단계에서 생성된 이미지와 텍스트를 참여자의 얼굴과 합성하기 위해 Amazon Lambda

를 생성합니다. Lambda는 Rekognition의로부터 전달받은 Cropped image가 S3 Bucket에 이미지가 업로드될 때 트리거 되는 형태입니다. 트리거가 되면 Lambda 함수가 호출되며, 이 Lambda 함수에서 기존에 SageMaker BYOC 기능으로 생성했던 사전 학습된 모델의 SageMaker Endpoint 로 호출하여 합성이 완료된 이미지는 디스플레이에 전시됩니다.

5. 전생 이미지 생성하기

이미지 생성

Amazon Bedrock Gallery에는 4가지의 테마가 준비되었습니다. 테마의 첫번째는 전생 사진, 두번째는 전생 초상화, 세번째는 해외여행, 네번째는 애니메이션이었습니다. (각각의 사진 샘플을 추가)

가상의 전생, 여행, 애니메이션 이미지 생성에는 AWS의 생성형 AI 서비스인 Amazon Bedrock이 사용되었습니다. Amazon Bedrock은 AI21 Labs, Anthropic, Cohere, Meta, Mistral AI, Stability AI 및 Amazon과 같은 선도적인 AI 회사의 다양한 고성능 파운데이션 모델(FM)을 단일 API를 통해 제공하는 완전 관리형 서비스입니다.

Amazon Bedrock을 사용해 Image 생성을 위해서는 Stability AI의 Stable Diffusion 모델과 Amazon의 자체 파운데이션 모델인 Amazon Titan Image Generator을 선택할 수 있습니다.

Amazon Bedrock Gallery의 이미지 생성을 위한 모델로는 여러 테스트를 거쳐 Amazon Titan Image Generator를 선택했습니다. 갤러리 전시에서는 실시간으로 사용자의 이미지를 받아 미리 만들어둔 이미지를 사용하면 요청할 때마다 새로운 이미지를 생성하지 않고 더 빠르게 서비스를 할 수 있습니다. 아래는 제시된 테마의 프롬프트에 맞춰 국가, 시대, 직업, 나이, 성별에 따라 다양한 이미지를 미리 만들어 S3에 업로드를 하는 Python 코드 예제입니다.

def generate_image(prompt_template, model_id, db_connection, env, theme):
    
    for country, era, occupation, age, gender in list(itertools.product(countries, eras, occupations, ages, genders)):
        try: 
            prompt = prompt_template.format(age=age,gender=gender, occupation=occupation, era=era, country=country)
            
            logger.info("Generating image with model %s", model_id)
            logger.info("The prompt is : %s", prompt)
        
            body = None
            seed_number = random.choice(seed_numbers)
            
            # 모델 선택에 따른 request body 생성
            if model_id=='stability.stable-diffusion-xl-v1':
                body=json.dumps({
                    "text_prompts": [
                        {
                            "text": prompt, "weight": 1.0
                        }
                    ] + [{"text": negprompt, "weight": -1.0} for negprompt in negative_prompts]
                    ,
                "cfg_scale": 30,
                "height": 1536,
                "width": 640,
                "seed": seed_number,
                "steps": 50,
                "samples" : 1,
                "style_preset" : "photographic"
            
                })
            elif model_id=='amazon.titan-image-generator-v1':
                body = json.dumps(
                    {
                        "taskType": "TEXT_IMAGE",
                        "textToImageParams": {
                            "text": prompt,   # 필수
                          "negativeText": "ugly, ugly hands"  # 선택 사항
                        },
                        "imageGenerationConfig": {
                            "numberOfImages": 1,   # 범위: 1~5 
                            "quality": "premium",  # 옵션: standard 또는 premium
                            "height": 1408,         # 문서에서 지원되는 높이 목록 
                            "width": 640,         # 문서에서 지원되는 너비 목록
                            "cfgScale": 7.5,       # 범위: 1.0(제외)~10.0
                            "seed": random.randint(0, 214783647),             # 범위: 0~214783647
                        }
                    }
                )
        
            accept = "application/json"
            content_type = "application/json"
        
            # 선택한 모델에 프롬프트 전달
            response = bedrock.invoke_model(
                body=body, modelId=model_id, accept=accept, contentType=content_type
            )
            
            response_body = json.loads(response.get("body").read())
            
            image = None
            if model_id=='stability.stable-diffusion-xl-v1':
                print(response_body['result'])
                base64_image = response_body.get("artifacts")[0].get("base64")
                base64_bytes = base64_image.encode('ascii')
                image_bytes = base64.b64decode(base64_bytes)
                image = Image.open(io.BytesIO(image_bytes))
            elif model_id=='amazon.titan-image-generator-v1':
                images = [Image.open(BytesIO(base64.b64decode(base64_image))) for base64_image in response_body.get("images")]
                image = images[0]
        except Exception as e:
            print(f"Error occurred: {e}")
            continue
        
        # 이미지 파일을 Amazon S3에 업로드
        bucket = 'bedrock-x'
        file_name = f"/home/ec2-user/environment/ImageGenerator/{image_path}"
        image_file = image.save(file_name)
        s3_client.upload_file(file_name, bucket, image_path)

위 코드 에서와 같이 우리는 모델 아이디와 모델에 맞는 파라메터만 전달하면 쉽게 원하는 모델을 테스트 해볼 수 있습니다.
함수의 인자 중 prompt_template는 main함수에서 인자로 받은 테마의 프롬프트를 활용합니다. 아래는 각 테마의 프롬프트 예시입니다.

    if theme=='portrait_realistic':
        prompt_template="Portrait photography photo of a {age} {gender}, working as a {occupation} lives in {country}, fully immersed in the cultural and historical essence of the {era}. They are not only enjoying activities in famous places but also experiencing the lifestyle, fashion, and local customs of the {era}, dressed in authentic attire from the period, which adds a timeless charm to their journey."
    elif theme=='portrait_travel':
        prompt_template="A {age} {gender}, working as a {occupation}, traveling around {country}, fully immersed in the cultural and historical essence of the {era}. They are not only enjoying activities in famous places but also experiencing the lifestyle, fashion, and local customs of the {era}, dressed in authentic attire from the period, which adds a timeless charm to their journey."
    elif theme=='portrait_painting':
        prompt_template="Portrait {country} style oil painting of a {age} year old {gender} {occupation} looking straight ahead alone, set in the {era} in {country}."
    elif theme=='portrait_animation':
        prompt_template="Fun, Cute, Portrait, {occupation}, aesthetic, {gender}, Anime, {country} background, {age} year old. aesthetic elements (line, shape, tone, color, pattern, texture, form), missing or absent digits, extremely detailed."

아래는 위의 코드로 생성된 각 테마의 샘플 이미지 입니다.

한국 양반집 전생 이미지 프랑스 귀족 전생 초상화
애니메이션 스페인 여행

이미지 생성과정은 프롬프트의 영향이 컸기 때문에 전시를 위해 인물이 앞을 보도록 하거나 시대나 국가의 특징을 잘 살리도록 여러 번 프롬프트를 수정해야 했습니다. 생성된 이미지들 중에는 생성형 이미지의 특징인 손모양이나 눈동자의 문제 그리고 앞서 전시를 위한 테마를 잘 살리는지 여부를 위해 이 글의 후반에 설명되는 이미지 검수를 통해 전시할 것들을 추가로 선정했습니다.

전생 스토리 텍스트 생성

테마에 대한 이미지를 설명하기 전생 혹은 여행에 대한 스토리 또한 Amazon Bedrock의 Text 생성 모델(Anthropic Claude 3 Sonnet)을 사용해 만들어졌습니다. 아래 이미지의 설명 글은 해당 이미지를 생성한 국가(프랑스)와 시기(500년대), 성별(여성), 직업(기사) 등의 정보를 기반으로 전생 스토리를 만들어달라는 프롬프트의 결과 입니다.

아래는 Amazon Bedrock의 Claude 3 Sonnet을 사용해 위의 스토리를 만들어내기 위한 코드입니다.
이미지 생성과 마찬가지로 모델 아이디만 변경하면 다른 Text 생성 모델로 변환이 가능합니다.

def invoke_claude_3_with_text(prompt):
    """
    Invokes Anthropic Claude 3 Sonnet to run an inference using the input
    provided in the request body.

    :param prompt: The prompt that you want Claude 3 to complete.
    :return: Inference response from the model.
    """

    # Invoke Claude 3 with the text prompt
    model_id = "anthropic.claude-3-sonnet-20240229-v1:0"

    try:
        response = bedrock.invoke_model(
            modelId=model_id,
            body=json.dumps(
                {
                    "anthropic_version": "bedrock-2023-05-31",
                    "max_tokens": 1024,
                    "messages": [
                        {
                            "role": "user",
                            "content": [{"type": "text", "text": prompt}],
                        }
                    ],
                }
            ),
        )

        # Process and print the response
        result = json.loads(response.get("body").read())
        input_tokens = result["usage"]["input_tokens"]
        output_tokens = result["usage"]["output_tokens"]
        output_list = result.get("content", [])

        print("Invocation details:")
        print(f"- The input length is {input_tokens} tokens.")
        print(f"- The output length is {output_tokens} tokens.")

        print(f"- The model returned {len(output_list)} response(s):")
        for output in output_list:
            print(output["text"])

        return output_list[0]["text"]

    except ClientError as err:
        logger.error(
            "Couldn't invoke Claude 3 Sonnet. Here's why: %s: %s",
            err.response["Error"]["Code"],
            err.response["Error"]["Message"],
        )
        raise

위의 함수에 아래와 같은 프롬프트를 전달해주면 위와 같은 스토리가 완성됩니다.
prompt=f”‘{image_prompt}’라는 프롬프트로 생성된 이미지에 대해서 참신한 전생 스토리를 150자 정도로 들려줘. ‘당신은’으로 시작하고 스토리만 말해줘.”

Amazon Bedrock Gallery 프러덕션 (운영) 준비를 위한 요소

이미지 검수

Amazon Bedrock을 통해 생성된 이미지를 실제 서비스에 제공하기 전, 적합하지 않은 이미지를 제거하는 작업을 진행합니다. 촬영된 사진과 생성된 이미지 합성에 적합하지 않은 이미지를 필터링하도록, 별도 이미지 리뷰 툴을 사용하여 이미지 검수를 수행합니다. 여기에서 적합하다고 확인된 이미지들만 최종 서비스의 Base Image에 활용되게 됩니다. 예를 들어, 인물의 얼굴이 측면이거나, 인물이 너무 작아서 합성된 이미지를 보여주기 어려운 경우 필터링합니다.

부하 테스트

Amazon Bedrock Gallery는 AWS의 큰 이벤트인 AWS Summit Seoul 2024 에서 활용될 예정이기에 부하/가용성 테스트가 필요했습니다. 전국민을 대상으로 하는 서비스는 아니었지만, 이벤트 부스에 대규모 인원이 지속적으로 몰릴 것에 대비해야 했습니다. 따라서 서비스의 안정성, 응답 시간, 그리고 예기치 못한 이슈 발생 여부 등을 철저히 검증하기 위한 테스트가 필수적이었습니다.

간단한 스크립트를 만들어서 평상시, 사람이 몰렸을 때를 시뮬레이션 했고 이에 따른 서비스 품질, 성능 등에 대한 데이터를 확보했습니다. 또한 예상 최대치의 2-3배의 요청이 몰렸을 때도 서비스가 어떻게 동작하는지 테스트 해서 시스템 운용에 대한 인사이트를 얻을 수 있었습니다. 이를 통해 실제 트래픽 증가 시 발생할 수 있는 문제점을 사전에 파악하고 대비할 수 있게 되었습니다. 이렇게 모의 테스트를 통해 확보한 데이터와 경험은 향후 시스템 최적화와 안정적인 운영을 위한 중요한 기반이 되었습니다.

슬랙 알림: 이미지 합성 완료 여부 확인

이미지가 합성되기 까지는 최소 40초에서 최대 1분까지 소요됩니다. 현장에서 체험하는 사용자 입장에서 현재 디스플레이에 보여진 이미지가 본인인지 이전 사람인지 구분하기 위해 합성이 완료된 이미지가 S3 버킷에 업로드되면 웹훅 설정을 통해 슬랙 알림을 전달하도록 구성하였습니다. 알림 메시지에는 어떤 display에 작업이 완료되었는지, 파일명으로부터 시간을 알 수 있기 때문에 현장에서 사용자들에게 현재 디스플레이 상태를 전달할 수 있었습니다.

문제 발생 여부 모니터링

양일간 진행되는 이벤트에는 문제 발생 시 실시간으로 대응이 필요했습니다. 이를 모니터링하기 위해 CloudWatch Dashboard를 구성하고 각 지표들에 알람을 설정하였습니다. 대표적으로 이미지 생성 시간이 1분 이상 걸리는지 확인하기 위해 Elastic Load Balacner(ELB)의 TargetResponseTime이 1분을 초과하지 않았는지, 이미지 합성 시 모델 서빙 서버의 CPU, Memory 사용률, Lambda Throttling 지표가 있습니다.

그리고 이미지 합성 시 순간적인 트래픽에 영향으로 에러 로그가 발생하는 경우, 정상적인 디스플레이 표현에 실패할 수 있습니다. 따라서 EC2 인스턴스에 CloudwatchLogs Agent를 설치하고 Error 로그 패턴을 카운트하여 슬랙 알림 설정을 진행하였습니다.

첫째 날 행사가 마무리되고 대시보드와 슬랙 알람으로 보았을 때 장애 없이 안정적인 서비스를 제공할 수 있었던 점을 대시보드를 통해 알 수 있었습니다. 이는 행사 전, 충분한 부하테스트를 거쳐 서버 EC2RDSLambda리소스를 조정하고 Lambda의 프로비저닝된 동시성 설정으로 안정적인 서비스 제공이 가능했습니다.

마무리: 서울 서밋 결과

AWS Seoul Summit 2024 전시회에서 운영된 ‘AWS Experience Live Demos’ 부스 중 하나인 Amazon Bedrock Gallery에는 총 1,848명의 관람객이 방문하여 실제 서비스를 체험할 수 있었습니다. 참가자들은 자신의 사진을 다양한 시대와 문화권의 분위기를 느낄 수 있는 생동감 넘치는 이미지에 합성하여 인물화하고, 그에 맞는 스토리를 경험하며 신선한 반응을 보였습니다. 또한 생성된 이미지의 품질과 스토리의 창의성에 대해서도 높이 평가했습니다.

하지만 일부 참가자들은 이미지 합성 시간이 다소 길었다는 점과 결과물의 완성도에 아쉬움을 토로하기도 했고, 더욱 다양한 테마와 옵션을 제공했으면 좋겠다는 의견도 있었습니다. 이에 따라 저희는 결과물 품질 향상과 테마 다양화 측면에서 서비스를 업그레이드하여 AWS AI Day: InnovationAWS Public Sector Day 2024, BDA(빅데이터 학회) x AWS 페스티벌, Amazon UK & Ireland 올핸즈 이벤트, AWS 생성형 AI 로프트 : 서울SK AI Summit 2024 등에서 선보였습니다.

저희는 이 프로젝트를 오픈소스로 전환하여 많은 분들이 프로젝트에 기여하고 활용할 수 있도록 하였습니다. 앞으로는 전생 스토리를 텍스트뿐 아니라 오디오, 비디오 형식으로도 제공하여 더 많은 분들이 쉽게 접할 수 있도록 하고, 보다 몰입감 있는 스토리 경험을 선사하고자 합니다. 다양한 미디어를 활용해 생동감 있고 실감나는 체험을 제공하는 것이 목표입니다.

이 프로젝트를 진행하며 저희가 얻은 가장 큰 교훈은 최신 AI 기술을 활용해 참신한 아이디어를 실현하는 과정에서 사용자 경험을 최우선으로 고려해야 한다는 점입니다. 준비 기간 동안 생성된 이미지와 스토리의 일치성 정도, 이미지와 사진의 합성 매끄러움 등을 지속적으로 검수하며 결과물의 퀄리티를 높였습니다. 이를 통해 긍정적인 사용자 피드백을 이끌어낼 수 있었고, 지속적인 사용자 의견 수렴과 제품 반영의 중요성을 실감할 수 있었습니다.

또한 대규모 이벤트에서 AI 서비스를 제공할 때는 서버 리소스 관리, 부하 테스트, 모니터링 등 운영 측면의 준비가 필수적임을 깨달었습니다. 실시간 AI 추론 요청에 실패 없이 대응하기 위해서는 사전 부하 테스트를 통한 최적의 아키텍처 구성과 모니터링/알람 체계 구축이 매우 중요한 요소였습니다.

이번 프로젝트는 AWS의 다양한 최신 서비스를 통합적으로 활용했다는 점에서도 큰 의미가 있었습니다. 여러 팀원들이 협업하며 개발, 인프라, 데이터 처리, AI 모델 활용 등 다양한 측면에서 AWS 기술을 실전에 적용해볼 수 있었습니다. 이를 통해 실무에서 발생할 수 있는 다양한 이슈 상황을 미리 경험하고 해결책을 모색해볼 수 있었습니다. 이러한 경험들이 향후 보다 안정적이고 사용자 중심의 AI 서비스를 개발하는 데 큰 자산이 될 것입니다.

Jungseob Shin

Jungseob Shin

신정섭 Solutions Architect는 다양한 분야의 Backend 서버 개발 경험을 바탕으로 Startup 고객이 비즈니스 목표를 달성하도록 AWS 서비스의 효율적인 사용을 위한 아키텍처 설계 가이드와 기술을 지원하는 역할을 수행하고 있습니다. 컨테이너와 서버리스 기술에 관심이 많습니다.

Chulwoo Choi

Chulwoo Choi

최철우 Solutions Architect는 여러 서비스 개발 경험을 바탕으로 스타트업들이 AWS 클라우드를 효과적으로 활용하며 성장할 수 있도록 돕는 일을 하고 있습니다.

Jinwoo Park

Jinwoo Park

박진우 시니어 솔루션즈 아키텍트는 AWS 고객들의 클라우드 여정을 성공적으로 돕기위해 기술 지원을 제공하고 있습니다. 그는 고객이 보다 안전하고 효율적이며 비용 최적화된 아키텍처와 솔루션을 구축하도록 돕고 모범사례와 워크숍을 제공합니다.

Jisoo Min

Jisoo Min

Jisoo Min is a Startup Solutions Architect at Amazon Web Services (AWS). She loves working with startups and data analytics services.

Hyeryeong Joo

Hyeryeong Joo

주혜령 솔루션즈 아키텍트는 대규모 엔터프라이즈 시스템 개발과 운영 경험을 바탕으로, 현재는 ISV 고객들이 AWS 클라우드에 안정적이고 효율적인 아키텍처를 구성할 수 있게 돕고 있습니다.

Kihoon Kwon

Kihoon Kwon

권기훈 스타트업 솔루션즈 아키텍트는 스타트업 고객들이 AWS에서 성공적인 비즈니스를 달성할 수 있도록 함께 고민하고 지원하는 역할을 하고 있습니다.

Gonsoo Moon

Gonsoo Moon

AWS AIML 스페셜리스트 솔루션즈 아키텍트로 일하고 있습니다. AI/ML 의 다양한 유스케이스 및 프러뎍션 경험을 바탕으로 고객의 AI/ML 문제를 해결하기 위해 고객과 함께 고민하고 협업하는 일을 주로 하고 있습니다. AI/ML 기술을 데이터 과학자, 개발자, 분석가 분에게 전파하여, 글로벌 및 한국 사회가 발전될 수 있게 기여를 하고자 합니다.

Seongjin Ahn

Seongjin Ahn

안성진 Solutions Architect는 다양한 고객 인프라 운영 경험을 바탕으로 AWS 서비스의 효율적인 사용을 위해 Startup 고객에게 기술을 지원하는 역할을 합니다.