Last active
June 26, 2025 09:53
-
-
Save lqez/e893cd19f5ec19a3d0d68e0c03cdc286 to your computer and use it in GitHub Desktop.
파이콘 한국 2025 심각한 중독입니다
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
model: https://huggingface.co/kitsumed/yolov8m_seg-speech-bubble