사진 10장으로 나만의 AI 캐릭터·프로필 이미지 만들기

내 사진 10장으로 FLUX.1 dev + LoRA 학습 후 원하는 스타일의 캐릭터·프로필 이미지 무한 생성

2026.04.13

ImageGenerationLoRAFLUXComfyUIFinetuning

[미검증]

📌 0. 시리즈

응용편제목난이도핵심 기술
응용 1사진 10장으로 AI 캐릭터·프로필 이미지 만들기⭐⭐⭐FLUX.1 dev · LoRA · ComfyUI
응용 2내 목소리 AI 클론 — 유튜브 내레이터 자동화⭐⭐⭐F5-TTS · Kokoro · Sesame CSM-1B
응용 3상품 이미지 1장 → 15초 광고 영상 자동 생성⭐⭐⭐⭐Wan2.2 · HunyuanVideo 1.5 · LTX-2
응용 4공장 불량 자동 검사 — NG/OK 탐지 + 로봇 좌표 추출⭐⭐~⭐⭐⭐⭐⭐YOLOv12 · OpenCV · RealSense
응용 5영상 분위기 분석 → BGM 자동 생성 & 싱크⭐⭐⭐MusicGen · AudioCraft · Stable Audio Open
응용 6주제 한 줄 입력으로 유튜브 영상 완성 — AI 콘텐츠 자동화 파이프라인⭐⭐⭐⭐LangGraph · CrewAI · AutoGen 0.4
응용 7사진 보고 글 쓰는 AI — Vision LLM 상세페이지 자동 작성⭐⭐⭐⭐Qwen2.5-VL · InternVL3 · LLaVA-Next
응용 8내 PDF 문서를 AI가 읽는다 — 사내 지식 RAG 챗봇 구축⭐⭐⭐LlamaIndex · ChromaDB · Qdrant

📌 1. 들어가며

이 포스트에서 만들 것

이 포스트를 끝까지 따라하면 내 얼굴 사진 10~30장만으로 아래 결과물을 만들 수 있습니다.

입력: 내 셀카 20장
   ↓
LoRA 학습 (~30분)
   ↓
출력:
  ├─ 애니메이션 캐릭터 스타일 프로필 이미지
  ├─ 유화 스타일 초상화
  ├─ 사이버펑크 캐릭터
  └─ 버튜버용 일러스트 이미지

왜 LoRA인가 — DreamBooth · Full Fine-tuning과 차이

방식VRAM학습 시간저장 용량품질
Full Fine-tuning40GB+수 시간수 GB⭐⭐⭐⭐⭐
DreamBooth16GB+30~60분2~4GB⭐⭐⭐⭐
LoRA8GB+10~30분10~150MB⭐⭐⭐⭐

💡 LoRA (Low-Rank Adaptation) 는 모델 전체를 바꾸지 않고 작은 어댑터 레이어만 학습합니다. 원본 모델 가중치는 고정한 채, 변화량(ΔW)만 저장하므로 파일 크기가 극도로 작고 VRAM 소모도 적습니다. 결과물 품질은 Full Fine-tuning과 거의 동일하기 때문에 개인 프로젝트에서는 사실상 표준 방식입니다.


📌 2. 환경 준비

2-1. 하드웨어 요구사항

최소 사양:
  GPU VRAM: 8GB  (RTX 3070 / 4060 등)
  RAM: 16GB
  저장공간: 50GB 이상 (모델 가중치 포함)

권장 사양:
  GPU VRAM: 24GB (RTX 3090 / 4090 등)
  RAM: 32GB
  저장공간: 100GB 이상

VRAM 부족 시 대안:
  - gradient_checkpointing 활성화 → VRAM 30~40% 절약
  - bf16 혼합 정밀도 학습 사용
  - batch_size=1 + gradient_accumulation 으로 보완

2-2. 소프트웨어 설치

Python 환경 세팅:

bash# Python 3.10 가상환경 생성
python -m venv venv
source venv/bin/activate       # Linux/Mac
venv\Scripts\activate          # Windows PowerShell

# 핵심 라이브러리 설치
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
pip install diffusers transformers peft accelerate
pip install bitsandbytes xformers
pip install Pillow datasets

ComfyUI 설치:

bashgit clone https://github.com/comfyanonymous/ComfyUI
cd ComfyUI
pip install -r requirements.txt

ComfyUI 필수 노드 설치 (ComfyUI-Manager로 설치):

- ComfyUI-FLUX-LoRA
- ComfyUI_Kontext
- ComfyUI-WD14-Tagger  ← 캡션 자동 생성용

2-3. FLUX.1 dev 모델 다운로드

python# HuggingFace CLI 로그인
pip install huggingface_hub
huggingface-cli login
# → HuggingFace 계정 토큰 입력 (hf_xxxx)

# FLUX.1 dev 다운로드 (약 23GB)
from huggingface_hub import snapshot_download

snapshot_download(
    repo_id="black-forest-labs/FLUX.1-dev",
    local_dir="./models/flux1-dev",
    ignore_patterns=["*.md", "*.txt"]
)

⚠️ FLUX.1 dev는 비상업용 라이선스입니다. 상업적 사용 시 FLUX.1 schnell (Apache 2.0) 을 사용하세요.


📌 3. 학습 데이터 준비

3-1. 사진 촬영 가이드

수량 기준:

최소: 10장  → 학습은 되지만 다양성 부족
권장: 20~30장 → 품질과 효율의 최적 균형
과잉: 50장+  → 효과 미미, 학습 시간만 증가

각도 분배 (20장 기준):

정면 바라보기:     6장  (30%)
좌측 45도:        4장  (20%)
우측 45도:        4장  (20%)
측면 (좌/우):     4장  (20%)
위에서 내려보기:   2장  (10%)

촬영 시 주의사항:

✅ 해야 할 것:
  - 자연광 또는 소프트박스 조명 사용
  - 다양한 표정 (미소, 무표정, 진지한 표정)
  - 다양한 의상
  - 배경은 단색 또는 아웃포커싱

❌ 하지 말 것:
  - 선글라스, 마스크 착용 사진 포함
  - 여러 사람이 함께 찍힌 사진
  - 얼굴이 너무 작게 나온 사진 (전신샷)
  - 흐릿하거나 역광 사진

3-2. 이미지 전처리

pythonfrom PIL import Image
import os

def preprocess_images(input_dir, output_dir, size=1024):
    os.makedirs(output_dir, exist_ok=True)
    
    for filename in os.listdir(input_dir):
        if filename.lower().endswith(('.jpg', '.jpeg', '.png')):
            img = Image.open(os.path.join(input_dir, filename))
            
            # 정사각형으로 중앙 크롭
            w, h = img.size
            min_side = min(w, h)
            left   = (w - min_side) // 2
            top    = (h - min_side) // 2
            img    = img.crop((left, top, left + min_side, top + min_side))
            
            # 리사이즈
            img = img.resize((size, size), Image.LANCZOS)
            
            # RGB 변환 (PNG 투명도 제거)
            img = img.convert("RGB")
            
            save_path = os.path.join(output_dir, filename.replace('.jpeg', '.jpg'))
            img.save(save_path, quality=95)
            print(f"처리 완료: {filename}")

preprocess_images("./raw_photos", "./dataset/images")

3-3. 캡션 작성법

트리거 워드 개념:

트리거 워드는 모델이 "이 사람이 나다"라고 인식하는 고유 키워드입니다. sks person 처럼 모델이 기존에 학습하지 않은 단어를 사용해야 충돌이 없습니다.

나쁜 예: "a photo of john"   → john이라는 개념이 이미 모델에 존재
좋은 예: "a photo of sks person"  → sks는 모델이 처음 보는 단어

캡션 파일 수동 작성 예시 (이미지당 .txt 파일 1개):

photo001.jpg  →  photo001.txt 내용:
"a photo of sks person, smiling, looking at camera, natural lighting"

photo002.jpg  →  photo002.txt 내용:
"a photo of sks person, serious expression, side view, outdoor background"

WD Tagger로 캡션 자동 생성:

python# ComfyUI WD14 Tagger 노드 사용 또는 아래 코드 활용
pip install wd14-tagger

from wd14_tagger import WD14Tagger

tagger = WD14Tagger()
tags = tagger.tag("./dataset/images/photo001.jpg", threshold=0.35)

# 자동 생성된 태그 앞에 트리거 워드 추가
trigger = "sks person"
caption = f"a photo of {trigger}, {', '.join(tags)}"
print(caption)
# 출력: "a photo of sks person, 1girl, solo, long hair, smile, ..."

📌 4. LoRA 학습

4-1. 학습 파라미터 설명

python# 핵심 파라미터 의미

learning_rate = 1e-4
# 한 스텝에서 가중치를 얼마나 크게 바꿀지
# 너무 크면 → 과적합 / 너무 작으면 → 학습 안됨
# FLUX LoRA 권장값: 1e-4 ~ 4e-4

rank = 16
# LoRA의 어댑터 행렬 크기
# 클수록 → 표현력↑ VRAM↑ 파일크기↑
# 작을수록 → 빠른학습 일반화↑
# 권장: 8 (가벼움) / 16 (균형) / 32 (고품질)

alpha = 16
# LoRA 학습의 스케일 조정값
# 보통 rank와 같은 값으로 설정
# alpha / rank 비율이 실제 학습 강도를 결정

# steps 수 기준
# 이미지 20장 기준:
#   500 steps  → 학습 부족, 특징 희미
#  1000 steps  → 균형 잡힌 결과 (권장)
#  2000 steps  → 고품질, 과적합 주의

4-2. 학습 실행 코드

학습 설정 파일 (train_config.yaml):

yamlmodel:
  base_model: "./models/flux1-dev"
  model_type: "flux"

dataset:
  image_dir: "./dataset/images"
  caption_dir: "./dataset/images"   # 같은 폴더에 txt 파일
  resolution: 1024
  batch_size: 1

lora:
  rank: 16
  alpha: 16
  target_modules:
    - "to_q"
    - "to_k"
    - "to_v"
    - "to_out.0"

training:
  learning_rate: 1e-4
  max_train_steps: 1000
  lr_scheduler: "cosine"
  warmup_steps: 100
  gradient_checkpointing: true
  mixed_precision: "bf16"
  save_every_n_steps: 250

output:
  save_dir: "./output/lora"
  lora_name: "my_character_v1"

학습 실행:

pythonfrom diffusers import FluxPipeline
from peft import LoraConfig, get_peft_model
from accelerate import Accelerator
from torch.utils.data import DataLoader, Dataset
from PIL import Image
import torch, os

class CharacterDataset(Dataset):
    def __init__(self, image_dir, caption_dir, size=1024):
        self.image_dir   = image_dir
        self.caption_dir = caption_dir
        self.size        = size
        self.images      = [f for f in os.listdir(image_dir) if f.endswith('.jpg')]

    def __len__(self):
        return len(self.images)

    def __getitem__(self, idx):
        img_name = self.images[idx]
        img_path = os.path.join(self.image_dir, img_name)
        txt_path = os.path.join(self.caption_dir, img_name.replace('.jpg', '.txt'))

        image   = Image.open(img_path).convert("RGB").resize((self.size, self.size))
        caption = open(txt_path).read().strip()

        return {"image": image, "caption": caption}

# LoRA 설정 적용
lora_config = LoraConfig(
    r=16,
    lora_alpha=16,
    target_modules=["to_q", "to_k", "to_v", "to_out.0"],
    lora_dropout=0.1,
    bias="none"
)

# 학습 루프 (간략 버전)
accelerator  = Accelerator(mixed_precision="bf16")
pipeline     = FluxPipeline.from_pretrained("./models/flux1-dev", torch_dtype=torch.bfloat16)
model        = get_peft_model(pipeline.transformer, lora_config)
dataset      = CharacterDataset("./dataset/images", "./dataset/images")
dataloader   = DataLoader(dataset, batch_size=1, shuffle=True)
optimizer    = torch.optim.AdamW(model.parameters(), lr=1e-4)

model, optimizer, dataloader = accelerator.prepare(model, optimizer, dataloader)

for step, batch in enumerate(dataloader):
    if step >= 1000:
        break
    optimizer.zero_grad()
    # ... 노이즈 예측 학습 ...
    accelerator.backward(loss)
    optimizer.step()

    if step % 250 == 0:
        pipeline.transformer.save_pretrained(f"./output/lora/checkpoint-{step}")
        print(f"Step {step} 저장 완료")

💡 더 쉬운 방법: kohya-ss/sd-scripts GUI 툴인 Kohya GUI를 사용하면 위 코드 없이도 설정값만 입력해서 학습할 수 있습니다. FLUX LoRA를 지원하며 초보자에게 권장합니다.

4-3. 학습 중 VRAM 관리 팁

python# VRAM 절약 설정 모음

# 1. gradient checkpointing (VRAM ~35% 절약, 속도 20% 감소)
model.enable_gradient_checkpointing()

# 2. 8bit Adam 옵티마이저 (VRAM ~20% 절약)
from bitsandbytes.optim import AdamW8bit
optimizer = AdamW8bit(model.parameters(), lr=1e-4)

# 3. xformers attention (VRAM ~15% 절약)
pipeline.enable_xformers_memory_efficient_attention()

# 4. 실시간 VRAM 모니터링
import subprocess
result = subprocess.run(
    ['nvidia-smi', '--query-gpu=memory.used,memory.total',
     '--format=csv,noheader,nounits'],
    capture_output=True, text=True
)
used, total = result.stdout.strip().split(', ')
print(f"VRAM: {used}MB / {total}MB ({int(used)/int(total)*100:.1f}%)")

📌 5. 이미지 생성

5-1. ComfyUI에서 LoRA 적용법

ComfyUI 기본 FLUX 워크플로우에서:

1. Load Checkpoint 노드 → FLUX.1 dev 선택
2. [추가] Load LoRA 노드 연결
   - lora_name: my_character_v1.safetensors
   - strength_model: 0.8  ← 0.6~1.0 사이 조정
   - strength_clip: 0.8
3. CLIP Text Encode 노드에 프롬프트 입력
4. KSampler → VAE Decode → 이미지 출력

LoRA strength 가이드:

0.4 ~ 0.6  → 닮음 낮음, 스타일 변환 자연스러움
0.7 ~ 0.8  → 균형 잡힌 닮음 (권장)
0.9 ~ 1.0  → 닮음 높음, 다양성 낮아짐
1.0 초과   → 과적합 증상 (얼굴 왜곡 가능)

5-2. 프롬프트 작성법

기본 구조:

[트리거 워드], [스타일], [품질 키워드], [구도], [조명]

스타일별 프롬프트 예시:

# 애니메이션 캐릭터
"a photo of sks person, anime style, 2D illustration,
 vibrant colors, clean lineart, studio ghibli inspired,
 masterpiece, best quality"

# 유화 초상화
"a photo of sks person, oil painting portrait,
 renaissance style, dramatic lighting, textured brushstrokes,
 museum quality, 8k resolution"

# 사이버펑크
"a photo of sks person, cyberpunk style, neon lights,
 futuristic city background, holographic elements,
 cinematic lighting, ultra detailed"

# 버튜버 일러스트
"a photo of sks person, vtuber avatar style,
 cute anime illustration, pastel colors,
 big eyes, chibi proportions, digital art"

네거티브 프롬프트 (FLUX는 선택사항):

"blurry, low quality, deformed face, extra fingers,
 bad anatomy, watermark, text, signature"

5-3. FLUX.1 Kontext dev로 이미지 편집

Kontext는 레퍼런스 이미지를 보고 동일한 사람을 다른 상황에서 그려주는 기능입니다.

pythonfrom diffusers import FluxKontextPipeline
import torch
from PIL import Image

# Kontext dev 로드
pipe = FluxKontextPipeline.from_pretrained(
    "black-forest-labs/FLUX.1-Kontext-dev",
    torch_dtype=torch.bfloat16
).to("cuda")

# LoRA 적용
pipe.load_lora_weights("./output/lora/my_character_v1.safetensors")

# 레퍼런스 이미지 불러오기
reference_image = Image.open("./best_result.jpg")

# 이미지 편집 (배경·옷만 바꾸고 얼굴 유지)
result = pipe(
    image=reference_image,
    prompt="sks person wearing a business suit, office background, professional photo",
    num_inference_steps=28,
    guidance_scale=2.5,
).images[0]

result.save("./output/edited_result.jpg")

Kontext 활용 시나리오:

원본 레퍼런스 이미지 1장
   ↓
├─ 배경만 변경: "sks person, same pose, beach background"
├─ 의상만 변경: "sks person, wearing kimono"
├─ 계절 변경:   "sks person, winter coat, snowing"
└─ 연령 변경:   "sks person as an elderly person"

📌 6. 결과 확인 & 트러블슈팅

얼굴 유사도가 낮을 때

원인 1: 학습 steps 부족
  → max_train_steps 500→1000으로 증가

원인 2: 트리거 워드 누락
  → 프롬프트에 "sks person" 반드시 포함 확인

원인 3: LoRA strength가 너무 낮음
  → ComfyUI Load LoRA 노드에서 0.8→0.9로 증가

원인 4: 학습 데이터 품질 문제
  → 흐릿하거나 얼굴이 작은 사진 제거 후 재학습

과적합(Overfitting) 증상과 해결법

증상:
  - 트리거 워드 없이도 내 얼굴이 나옴
  - 다른 스타일 프롬프트 입력해도 항상 비슷한 결과
  - 얼굴 이외 부분이 학습 사진과 동일하게 고정됨

해결법:
  1. steps 줄이기: 2000→1000
  2. learning_rate 낮추기: 1e-4 → 5e-5
  3. rank 줄이기: 32 → 16
  4. 중간 체크포인트 (500 steps) 사용해보기

VRAM OOM (Out of Memory) 오류 해결

python# OOM 발생 시 순서대로 적용

# 1단계: 해상도 낮추기
resolution: 1024512

# 2단계: gradient checkpointing 활성화
model.enable_gradient_checkpointing()

# 3단계: 8bit 옵티마이저
from bitsandbytes.optim import AdamW8bit

# 4단계: xformers 활성화
pipeline.enable_xformers_memory_efficient_attention()

# 5단계: 캐시 비우기
import torch, gc
gc.collect()
torch.cuda.empty_cache()

# 6단계 (최후): batch_size=1 + gradient_accumulation_steps=4
# → 실질적으로 batch_size=4 효과를 VRAM 부담 없이 구현

완성 체크리스트

  • 사진 20~30장 준비 및 전처리 완료
  • 캡션 파일 생성 (트리거 워드 포함)
  • LoRA 학습 1000 steps 완료
  • ComfyUI에서 LoRA 로드 후 이미지 생성 확인
  • 다양한 스타일 프롬프트로 결과물 테스트
  • FLUX.1 Kontext dev로 이미지 편집 적용