MediaPipe의 Hair Segmenter를 활용하면 이미지 속 머리카락을 감지하여 자연스러운 색상 변경 효과를 구현할 수 있습니다.
이 글에서는 머리카락을 분리한 뒤 3가지 방식으로 염색 효과를 테스트하며, 가장 자연스럽고 실용적인 리컬러링 기법을 비교합니다.
MediaPipe Hair Segmenter로 머리카락 염색 효과 테스트
목차
1. Hair Segmenter 개요
머리카락 염색 효과를 시뮬레이션하려면, 먼저 머리카락 영역을 정확하게 분리해야 합니다.
MediaPipe Hair Segmenter는 정적 이미지 또는 실시간 영상에서 머리카락만을 픽셀 단위로 탐지해주는 경량 세그멘테이션 모델입니다.
출력 마스크 설명
마스크 종류 | 데이터 형태 | 값 범위 | 활용 |
category_mask | np.uint8 (0 또는 1) | 머리카락 = 1 | 시각화용 이진 마스크 |
confidence_mask | np.float32 (0.0 ~ 1.0) | 확률 기반 | 블렌딩, 경계 부드럽게 적용 시 |
confidence_mask는 두 개의 클래스에 대한 확률 정보를 담고 있는 배열입니다.
confidence_masks[0] → 배경(background) 확률 마스크
confidence_masks[1] → 머리카락(hair) 확률 마스크
아래 이미지는 Hair Segmenter의 출력 결과인 category_mask(이진 마스크)와 confidence_masks[1](머리카락 확률 마스크)를 시각화한 것입니다.
2. LUT란 무엇인가?
머리카락 색을 바꾸려면 단순히 RGB 값을 바꾸는 것보다 더 정밀하고 직관적인 색상 제어가 필요합니다.
여기서 사용되는 기술이 바로 LUT(Look-Up Table)입니다. 이 LUT는 주로 HSV 색공간 기반으로 설정됩니다.
◆ LUT(Look-Up Table)란?
UT는 색상 스타일을 정해놓은 사전 정의된 색상 매핑 테이블입니다.
여기서는 HSV(Hue, Saturation, Value) 색공간의 값으로 구성된 딕셔너리 형태의 스타일 설정을 의미합니다.
pink_lut = {
'hue': 160, # 색조 (Hue) : 핑크 계열
'sat': 180, # 채도 (Saturation) : 강한 색
'val': 1.2 # 밝기 (Value) : 기존보다 20% 밝게
}
◆ 왜 RGB가 아닌 HSV를 사용하는가?
RGB는 인간 눈의 인식과는 다소 다른 구조입니다.
HSV는 인간이 인지하는 색의 구조와 더 유사하며, 다음과 같은 특징을 가집니다:
색상 제어가 중요한 컴퓨터 비전 작업에서 RGB보다 훨씬 직관적이고 유연한 선택입니다.
구성요소 | 의미 | 설명 |
Hue | 색상 | 빨강(0), 노랑(30), 초록(60), 파랑(120), 핑크(160~170) 등 |
Saturation | 채도 | 색의 강도 (선명함). 낮을수록 회색에 가까움 |
Value | 명도 | 밝기. 1.0은 원래 밝기, 1.2는 더 밝게 표현 |
HSV를 사용하면 다음과 같은 제어가 가능합니다:
- 색상은 그대로 유지하고 채도만 낮춰서 은은한 톤 만들기
- 명도를 높여서 염색 후 밝아진 느낌 표현하기
- 동일한 hue에서 채도·밝기를 바꿔서 스타일 변형 가능
◆ LUT 적용 흐름
1. 이미지 → HSV 색공간 변환
2. 마스크가 적용된 영역에 LUT 값 덮어쓰기
3. 다시 RGB 또는 BGR로 변환
# HSV 변환
image_hsv = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2HSV).astype(np.float32)
# LUT 적용
image_hsv[..., 0] = lut['hue']
image_hsv[..., 1] = lut['sat']
image_hsv[..., 2] *= lut['val']
# 다시 RGB로 변환
styled_rgb = cv2.cvtColor(np.clip(image_hsv, 0, 255).astype(np.uint8), cv2.COLOR_HSV2RGB)
3. Hair Segmentation 설정 및 실행
머리카락 염색을 적용하기 위해선 먼저 Hair Segmenter를 로드하고 이미지에서 머리카락 영역을 추출해야 합니다.
이 단계는 모든 리컬러링 기법에 공통으로 사용되며, 다음 순서로 이루어집니다
1. 모델 로드 및 옵션 설정
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
import cv2
import numpy as np
import os
# 모델 경로
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
model_path = os.path.join(BASE_DIR, "models/hair_segmenter.tflite")
# 옵션 설정
base_options = python.BaseOptions(model_asset_path=model_path, delegate="CPU")
options = vision.ImageSegmenterOptions(
base_options=base_options,
running_mode=vision.RunningMode.IMAGE,
output_category_mask=True,
output_confidence_masks=True
)
#LUT 설정
pink_lut = {
'hue': 160, # 0~179 범위 (OpenCV HSV 기준), 보통 핑크는 160~170
'sat': 180, # 적당히 강한 채도
'val': 1.0 # 원래 밝기 유지 (혹은 1.1~1.2 로 부스트 가능)
}
2. 입력 이미지 로드
# 테스트 이미지 경로
image_path = os.path.join(BASE_DIR, "images/ssa5.jpg")
# MediaPipe용 이미지 객체 생성 (RGB 포맷)
mp_image = mp.Image.create_from_file(image_path)
3. 세그멘테이션 실행 및 마스크 추출
# 세그멘터 실행 및 마스크 추출
with vision.ImageSegmenter.create_from_options(options) as segmenter:
segmentation_result = segmenter.segment(mp_image)
# Category Mask (0 또는 1) - 흑백 마스크
category_mask = segmentation_result.category_mask.numpy_view()
# Confidence Mask (float, 0.0 ~ 1.0) - 머리카락 확률 마스크
confidence_mask = segmentation_result.confidence_masks[1].numpy_view()
4. 세그멘테이션 영역 단순 칼라 지정하기
confidence_mask는 각 픽셀이 머리카락일 확률을 나타냅니다.
이 값을 투명도(알파값)처럼 사용하면, 머리카락일수록 지정한 색이 더 많이 섞이고,
배경일수록 원본 이미지가 그대로 유지됩니다.
색상은 BGR 형식으로 지정하며, 원하는 컬러로 쉽게 바꿀 수 있습니다.
def apply_hair_color(mp_image, confidence_mask, hair_color=(255, 102, 204)):
# 1. 이미지 BGR로 변환
image = cv2.cvtColor(mp_image.numpy_view().copy(), cv2.COLOR_RGB2BGR)
# 2. 지정 색상으로 채워진 이미지 생성
hair_color_img = np.ones_like(image, dtype=np.uint8)
hair_color_img[:, :] = hair_color # 예: 분홍색
# 3. 마스크를 3채널로 확장
confidence_mask_3ch = np.repeat(confidence_mask[:, :, np.newaxis], 3, axis=2)
# 4. 알파 블렌딩 수행
blended = (image * (1.0 - confidence_mask_3ch) + hair_color_img * confidence_mask_3ch).astype(np.uint8)
# 5. 결과 표시
cv2.imshow("Apply hair color", blended)
4. 하드 마스크 기반 HSV 색상 변경
이 기법은 Confidence Mask의 값이 0.5를 초과하는 픽셀만 머리카락으로 간주하여,
해당 영역에 LUT에서 정의한 색상(Hue), 채도(Saturation), 밝기(Value)를 강제로 덮어씌우는 방식입니다.
def recolor_hair_area_hard_mask(mp_image, confidence_mask, lut_map):
"""
▶ 경계가 날카로운 하드 마스크 방식
▶ hair_mask > 0.5 조건으로 색상 강제 적용
"""
image_rgb = mp_image.numpy_view().copy().astype(np.uint8)
hair_mask = confidence_mask.copy().astype(np.float32)
image_hsv = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2HSV).astype(np.float32)
# LUT 맵에서 색상 정보 가져오기
hue, sat, val = lut_map['hue'], lut_map['sat'], lut_map['val']
# 마스크가 50% 이상인 영역만 색상 변경
image_hsv[..., 0] = np.where(hair_mask > 0.5, hue, image_hsv[..., 0])
image_hsv[..., 1] = np.where(hair_mask > 0.5, sat, image_hsv[..., 1])
image_hsv[..., 2] = np.where(hair_mask > 0.5, image_hsv[..., 2] * val, image_hsv[..., 2])
# 값 클리핑 및 타입 변환, 각 픽셀 값을 0과 255 사이로 잘라냅니다.
image_hsv = np.clip(image_hsv, 0, 255).astype(np.uint8)
result = cv2.cvtColor(cv2.cvtColor(image_hsv, cv2.COLOR_HSV2RGB), cv2.COLOR_RGB2BGR)
cv2.imshow("Hard Mask", result)
cv2.waitKey(0)
5. RGB 소프트 블렌딩 (마스크 그대로 사용)
이 기법은 confidence_mask를 그대로 투명도(알파값)처럼 사용하여, 머리카락 부분에만 색이 자연스럽게 섞이도록 처리합니다.
먼저 이미지의 색조(Hue), 채도(Saturation), 밝기(Value)를 HSV 색공간에서 변경한 후,다시 RGB로 변환한 이미지를 원본 이미지와 부드럽게 혼합합니다.
마스크를 그대로 사용하기 때문에 구현은 간단하면서도,자연스럽고 은은한 염색 효과를 만들 수 있는 것이 특징입니다.
def recolor_hair_area_rgb_soft_blend(mp_image, confidence_mask, lut_map):
"""
▶ RGB 공간에서 Soft Mask Blending
▶ 머리카락 전체에 부드럽게 색 적용
"""
image_rgb = mp_image.numpy_view().copy().astype(np.uint8)
hair_mask = confidence_mask.copy().astype(np.float32)
image_hsv = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2HSV).astype(np.float32)
hue, sat, val = lut_map['hue'], lut_map['sat'], lut_map['val']
styled_hsv = image_hsv.copy()
styled_hsv[..., 0] = hue
styled_hsv[..., 1] = sat
styled_hsv[..., 2] *= val
styled_rgb = cv2.cvtColor(np.clip(styled_hsv, 0, 255).astype(np.uint8), cv2.COLOR_HSV2RGB)
# 소프트 마스크 알파 블렌딩 적용 로직
alpha = hair_mask[..., np.newaxis] # (H, W, 1)로 확장
#픽셀 단위로 원본 이미지와 스타일 이미지(RGB)를 부드럽게 섞습니다.
blended_rgb = (1 - alpha) * image_rgb + alpha * styled_rgb
result = cv2.cvtColor(np.clip(blended_rgb, 0, 255).astype(np.uint8), cv2.COLOR_RGB2BGR)
cv2.imshow("Soft RGB Blend", result)
cv2.waitKey(0)
6. RGB 소프트 블렌딩 + 마스크 Blur
이 기법은 "5. RGB 소프트 블렌딩" 기법과 비슷하지만, confidence_mask에 Gaussian Blur(가우시안 블러)를 적용하여 머리카락 경계를 더욱 부드럽게 만듭니다.
이로 인해 색상이 경계 없이 자연스럽게 스며들듯 표현되며, 머리카락 윤곽이 흐릿하거나 명확하지 않은 이미지에서도 매우 자연스러운 염색 효과를 얻을 수 있습니다.
def recolor_hair_area_rgb_soft_blend_blur(mp_image, confidence_mask, lut_map):
"""
▶ RGB 블렌딩 + 마스크 Gaussian Blur 적용
▶ 경계가 더 부드럽고 자연스러움
"""
image_rgb = mp_image.numpy_view().copy().astype(np.uint8)
hair_mask = confidence_mask.copy().astype(np.float32)
# 마스크에 Gaussian Blur 적용 (경계 부드럽게)
hair_mask = cv2.GaussianBlur(hair_mask, (15, 15), 0)
# 너무 낮은 마스크 제거 (불필요한 배경 경계 제거)
hair_mask[hair_mask < 0.1] = 0.0
image_hsv = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2HSV).astype(np.float32)
hue, sat, val = lut_map['hue'], lut_map['sat'], lut_map['val']
styled_hsv = image_hsv.copy()
styled_hsv[..., 0] = hue
styled_hsv[..., 1] = sat
styled_hsv[..., 2] *= val
styled_rgb = cv2.cvtColor(np.clip(styled_hsv, 0, 255).astype(np.uint8), cv2.COLOR_HSV2RGB)
alpha = hair_mask[..., np.newaxis]
blended_rgb = (1 - alpha) * image_rgb + alpha * styled_rgb
result = cv2.cvtColor(np.clip(blended_rgb, 0, 255).astype(np.uint8), cv2.COLOR_RGB2BGR)
cv2.imshow("Soft RGB Blend + Blur", result)
cv2.waitKey(0)
관련 글 링크
[2.인공지능/MediaPipe] - 4.MediaPipe Image Segmentation 사용하기 : 이미지 분할
4.MediaPipe Image Segmentation 사용하기 : 이미지 분할
MediaPipe Image Segmenter의 주요 기능과 실행 모드를 중심으로 이미지 세그멘테이션의 개념과 활용법을 소개합니다.SelfieSegmenter, HairSegmenter, SelfieMulticlass, DeepLab-v3 등 다양한 모델 비교와 Python 코드 예
quadcube.tistory.com
'2.인공지능 > MediaPipe' 카테고리의 다른 글
4.MediaPipe Image Segmentation 사용하기 : 이미지 분할 (0) | 2025.04.28 |
---|---|
3.MediaPipe Image Classification 사용하기 : 이미지 분류 (0) | 2025.04.24 |
2.MediaPipe Object Detector 사용하기: 객체탐지 (0) | 2025.04.22 |
1.Google MediaPipe 솔루션 이해하기 (0) | 2025.04.15 |