Skip to content

Instantly share code, notes, and snippets.

@lqez
Last active June 26, 2025 09:53
Show Gist options
  • Save lqez/e893cd19f5ec19a3d0d68e0c03cdc286 to your computer and use it in GitHub Desktop.
Save lqez/e893cd19f5ec19a3d0d68e0c03cdc286 to your computer and use it in GitHub Desktop.
파이콘 한국 2025 심각한 중독입니다
from ultralytics import YOLO
from PIL import Image, ImageFont, ImageDraw
import numpy as np
import cv2
import textwrap
# 입력 이미지 및 모델
image_path = "img800.jpg"
model = YOLO("yolov8m-seg-speech-bubble.pt")
font_path = "ko.otf"
text_to_insert = [
"흥, 웃기는 소리. 파이콘 한국 2025 pycon.kr 8월 15일부터 17일까지 동국대학교 서울캠퍼스 신공학관에서",
"치료가 필요할 정도로 심각한 파이썬 중독입니다",
]
# 이미지 불러오기
image = Image.open(image_path).convert("RGB")
image_np = np.array(image)
h, w = image_np.shape[:2]
def fits_in_contour(lines, font, contour, origin):
draw = ImageDraw.Draw(Image.new("RGB", (1,1)))
bbox = draw.multiline_textbbox((0, 0), "\n".join(lines), font=font)
text_w, text_h = bbox[2] - bbox[0], bbox[3] - bbox[1]
tx, ty = origin[0] - text_w // 2, origin[1] - text_h // 2
for dy in range(0, text_h, 2):
for dx in range(0, text_w, 2):
px, py = float(tx + dx), float(ty + dy)
if cv2.pointPolygonTest(contour, (px, py), False) < 0:
return False
return True
def find_best_wrapped_text(text, font_path, contour, max_font_size):
M = cv2.moments(contour)
if M["m00"] == 0:
return None
center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
best_result = None
for font_size in range(max_font_size, 10, -1):
font = ImageFont.truetype(font_path, font_size)
for width in range(5, len(text) + 1): # 최대 너비
wrapped = textwrap.fill(text, width=width)
lines = wrapped.splitlines()
if fits_in_contour(lines, font, contour, center):
best_result = (lines, font, center)
break
if best_result:
break
return best_result # (lines, font, center)
# 예측 실행
results = model.predict(source=image_path, save=False, imgsz=max(h, w))
for r in results:
if r.masks is None:
continue
class_names = model.names
target_class_name = "speech bubble"
# 마스크, 클래스 ID, confidence 모두 순회
for i, (mask, cls_id, conf, box) in enumerate(zip(r.masks.data, r.boxes.cls, r.boxes.conf, r.boxes.xyxy)):
class_id = int(cls_id.item())
if class_names[class_id] != target_class_name:
continue
# 박스
x1, y1, x2, y2 = map(int, box.tolist())
cv2.rectangle(image_np, (x1, y1), (x2, y2), (0, 255, 0), 2)
label = f"{class_names[class_id]} {conf.item():.2f}"
cv2.putText(image_np, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 128, 255), 1)
# 마스크 윤곽선 추출
mask_np = mask.cpu().numpy()
mask_resized = cv2.resize(mask_np, (w, h), interpolation=cv2.INTER_NEAREST)
binary = (mask_resized > 0.5).astype(np.uint8)
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
M = cv2.moments(contour)
if M["m00"] == 0:
continue
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
contour = contour.astype(np.float32)
contour -= [cx, cy]
contour *= 0.95
contour += [cx, cy]
contour = contour.astype(np.int32)
# 하얀색으로 채움 (지우기)
cv2.drawContours(image_np, [contour], -1, (255, 255, 255), thickness=cv2.FILLED)
# 텍스트 그릴 공간 계산
result = find_best_wrapped_text(text_to_insert[i], font_path, contour, 40)
if result:
lines, font, center = result
pil_img = Image.fromarray(image_np)
draw = ImageDraw.Draw(pil_img)
draw.multiline_text(center, "\n".join(lines), font=font, fill=(0, 0, 0), anchor="mm", align="center")
image_np = np.array(pil_img)
# 저장 및 표시
Image.fromarray(image_np).show()
@lqez
Copy link
Author

lqez commented Jun 26, 2025

@lqez
Copy link
Author

lqez commented Jun 26, 2025

tmp3lbdi09r

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment