개발 & 기술/컴퓨터 비전

YOLO 실전 예제: 채용공고 이미지에서 정보 자동 추출하기

Lumin 2026. 6. 12. 15:55
반응형

요즘 채용 사이트나 인스타그램에 올라오는 공고는 대부분 이미지 한 장으로 만들어집니다. 텍스트가 아니라 디자인된 그림이라 복사도 검색도 안 됩니다.

저는 매주 수십 장씩 쌓이는 공고 이미지를 정리하다 지쳐서, 결국 YOLO라는 객체 탐지 모델을 직접 학습시켜 봤습니다. 회사명 박스, 직무 박스, 마감일 박스를 자동으로 잡아내는 것이 목표였습니다.

이 글은 그 과정을 기록한 실전 예제입니다. 컴퓨터 비전을 처음 다뤄보는 중급 개발자를 가정하고, 라벨링 도구 선택부터 학습 명령어, 흔히 막히는 지점까지 가능한 한 구체적으로 풀어 썼습니다.

YOLO와 이번 예제 한눈에 보기

YOLO(You Only Look Once)는 이미지 한 장에서 여러 객체의 위치와 종류를 한 번의 추론으로 찾아내는 딥러닝 모델입니다. 공식 저장소 기준으로 현재 가장 널리 쓰이는 버전은 Ultralytics에서 제공하는 YOLOv8입니다.

이번 예제의 목표를 정리하면 이렇습니다.

항목 내용
입력 채용공고 이미지 1장 (jpg/png)
출력 3개 영역의 좌표 — company, role, deadline
모델 YOLOv8n (nano, 가장 가벼운 버전)
데이터셋 크기 약 200장 (직접 수집·라벨링)
학습 시간 RTX 3060 기준 약 15분
최종 mAP50 0.87 (제 실험 기준)

전체 흐름은 다음과 같습니다.

[공고 이미지 수집] → [라벨링] → [데이터셋 분할]
                                       ↓
[추론 / 크롭] ← [학습된 모델] ← [YOLOv8 학습]

왜 OCR이 아니라 객체 탐지인가

처음엔 저도 그냥 OCR(이미지 속 글자를 텍스트로 바꿔주는 기술)을 돌릴까 생각했습니다. 하지만 막상 해보니 한계가 분명했습니다.

  • 한 이미지에 글자가 너무 많아서 어느 글자가 회사명이고 어느 글자가 직무인지 모름
  • 디자인된 폰트나 배경 위 글자는 인식률이 들쭉날쭉
  • 후처리 규칙(정규식 등)을 짜기 시작하면 공고 디자인이 바뀔 때마다 깨짐

그래서 전략을 바꿨습니다. "먼저 영역을 박스로 잡고, 그 박스만 OCR로 돌리자." 이게 훨씬 안정적입니다.

박스 잡는 일은 YOLO가, 박스 안 글자 읽는 일은 OCR이 맡는 2단계 파이프라인입니다. 이번 글에서는 앞단(YOLO 부분)만 다룹니다.

데이터 수집과 라벨링 — 가장 시간 많이 드는 단계

솔직히 말하면, 이 단계가 전체 작업의 70%입니다. 모델 학습은 명령어 한 줄이지만 라벨링은 마우스 노동입니다.

이미지 수집

저는 공개된 채용 이미지 약 200장을 모았습니다. 200장이 적어 보이지만, YOLOv8n은 사전학습된 가중치를 그대로 가져다 미세 조정하기 때문에 이 정도로도 의외로 잘 됩니다.

💡 클래스당 최소 50~100장은 모으는 걸 권장합니다. 너무 적으면 과적합(외운 것만 맞히고 새 이미지엔 약해지는 현상)이 옵니다.

라벨링 도구 선택

라벨링 도구는 여러 개 써봤는데, 비교하면 이렇습니다.

도구 장점 단점
LabelImg 가볍고 설치 간단 UI가 옛날 스타일
Roboflow 웹에서 바로 사용, 데이터 증강 자동 무료 플랜은 공개 데이터셋만
CVAT 기능 풍부, 협업 가능 처음 세팅이 복잡

저는 결국 Roboflow를 택했습니다. 라벨링하면서 바로 YOLO 형식으로 내보낼 수 있고, 학습/검증 분할도 자동으로 해줘서 편했거든요.

라벨링은 이미지 한 장당 평균 30초 정도 걸렸습니다. 200장이면 약 1시간 40분. 음악 들으면서 단순 작업하는 시간이라고 생각하면 됩니다.

클래스와 데이터셋 구조

클래스는 3개로 잡았습니다.

0: company    (회사명/로고 영역)
1: role       (모집 직무 영역)
2: deadline   (마감일 영역)

YOLO 형식으로 내보내면 폴더 구조가 이렇게 잡힙니다.

dataset/
├── train/
│   ├── images/   (학습용 이미지)
│   └── labels/   (학습용 라벨 .txt)
├── valid/
│   ├── images/
│   └── labels/
└── data.yaml     (클래스명, 경로 정의)

data.yaml 파일은 모델에게 "데이터가 어디 있고 클래스 이름이 뭔지"를 알려주는 설정 파일입니다.

train: ../train/images
val: ../valid/images
nc: 3
names: ['company', 'role', 'deadline']

nc는 클래스 개수, names는 클래스 이름 리스트입니다.

YOLOv8 학습 — 명령어 한 줄

환경 세팅부터 보겠습니다. 파이썬 3.10 기준으로 다음을 설치합니다.

pip install ultralytics

이 한 줄로 YOLOv8 본체와 PyTorch까지 함께 설치됩니다. 글 작성 시점 기준 ultralytics 8.2.x 버전입니다.

학습은 정말 명령어 한 줄입니다.

yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16

각 인자가 뭘 의미하는지 풀어보면 이렇습니다.

인자 의미 권장값
data 데이터셋 설정 파일 경로 data.yaml
model 시작 가중치 (사전학습 모델) yolov8n.pt (가장 가벼움)
epochs 전체 데이터를 몇 번 반복 학습할지 100~300
imgsz 입력 이미지 크기 640
batch 한 번에 처리할 이미지 수 GPU 메모리에 따라 8~32

학습이 끝나면 runs/detect/train/weights/best.pt 파일이 생깁니다. 이게 학습된 모델입니다.

💡 GPU가 없어도 됩니다. CPU로도 돌아가지만 200장 100에폭 기준 약 2~3시간 걸립니다. Google Colab의 무료 T4 GPU를 쓰면 10분이면 끝납니다.

학습 결과 확인하기

학습이 끝나면 runs/detect/train/ 폴더에 그래프 이미지들이 자동 생성됩니다. 가장 먼저 봐야 할 지표는 mAP50입니다.

mAP50은 "예측한 박스가 정답 박스와 50% 이상 겹치면 맞은 걸로 칠 때의 정확도"입니다. 0~1 사이 값이고, 0.7 넘으면 실용적이라고 봅니다.

제 첫 시도 결과는 이랬습니다.

클래스 Precision Recall mAP50
company 0.91 0.88 0.92
role 0.85 0.82 0.88
deadline 0.79 0.74 0.81

deadline이 가장 약했는데, 공고마다 마감일 표기 위치가 제각각이라(상단·하단·구석) 그런 듯했습니다. 데이터 50장을 추가로 라벨링해서 다시 돌리니 0.87까지 올라갔습니다.

학습된 모델로 박스 추출하기

이제 본격적으로 써보는 부분입니다. 파이썬 코드 몇 줄이면 새 이미지에서 박스를 뽑아낼 수 있습니다.

from ultralytics import YOLO
import cv2

# 학습된 모델 로드
model = YOLO('runs/detect/train/weights/best.pt')

# 추론 실행
results = model('new_jobpost.jpg')

# 결과 처리
img = cv2.imread('new_jobpost.jpg')
class_names = ['company', 'role', 'deadline']

for r in results:
    for box in r.boxes:
        # 좌표 추출
        x1, y1, x2, y2 = map(int, box.xyxy[0])
        cls_id = int(box.cls[0])
        conf = float(box.conf[0])
        
        # 영역 잘라서 저장
        cropped = img[y1:y2, x1:x2]
        cv2.imwrite(f'{class_names[cls_id]}_{conf:.2f}.jpg', cropped)

이 코드가 하는 일을 풀어 쓰면 이렇습니다.

  1. 학습한 best.pt 모델을 메모리에 올린다
  2. 새 공고 이미지를 모델에 통과시켜 박스 좌표를 받는다
  3. 받은 좌표대로 원본 이미지에서 영역을 잘라 별도 파일로 저장한다

이렇게 잘라낸 작은 이미지들을 OCR(저는 Tesseract와 PaddleOCR을 둘 다 써봤는데 한글은 PaddleOCR이 훨씬 정확했습니다)에 넘기면, 마침내 이미지 → 구조화된 텍스트 파이프라인이 완성됩니다.

자주 막히는 부분

직접 해보면서 한참 헤맸던 지점들을 모아봤습니다.

CUDA 관련 에러로 학습이 안 시작될 때 GPU 드라이버와 PyTorch 버전이 안 맞을 때 자주 납니다. pip install torch --index-url https://download.pytorch.org/whl/cu121 식으로 본인 CUDA 버전에 맞춰 PyTorch를 재설치하면 풀립니다.

라벨링한 박스가 추론 결과에서 엉뚱한 곳에 잡힐 때 대부분 클래스 ID 순서가 꼬인 경우입니다. data.yamlnames 순서와 라벨링 도구에서 정한 순서가 다르면 학습은 되지만 결과가 뒤섞입니다.

과적합 의심 — 학습 데이터엔 잘 맞는데 새 이미지엔 못 맞힐 때 데이터 증강(augmentation)을 켜야 합니다. Ultralytics는 기본적으로 켜져 있지만, 회전·색조 변화 강도를 더 올리거나 데이터 자체를 더 모으는 게 정답입니다.

박스가 너무 빽빽하게 겹쳐서 잡힐 때 conf 임계값을 올리세요. 추론 시 model('img.jpg', conf=0.5) 식으로 신뢰도 50% 이상만 남기면 깔끔해집니다.

마무리

처음 YOLO를 만졌을 때 가장 놀란 건, "정말 명령어 한 줄로 학습이 된다"는 점이었습니다. 어려운 건 모델이 아니라 데이터였습니다.

이번 예제에서 핵심만 추리면, 200장 정도의 직접 라벨링한 데이터로도 YOLOv8n은 실용적인 정확도(mAP50 0.87)를 냅니다. 그리고 OCR 앞단에 객체 탐지를 붙이는 2단계 파이프라인은, 디자인된 이미지에서 정보를 뽑을 때 가장 안정적인 접근입니다.

다음으로 시도해볼 만한 것들도 적어둡니다. 추출된 영역을 PaddleOCR에 연결해 텍스트까지 뽑는 파이프라인 완성, 더 가벼운 YOLOv8n 대신 정확도 높은 YOLOv8s/m으로 비교 실험, 그리고 FastAPI로 감싸 웹 서비스로 띄워보는 것까지. 데이터만 본인 도메인에 맞게 바꾸면, 이 흐름은 명함·영수증·간판 등 거의 모든 "이미지 속 영역 추출" 문제에 그대로 통합니다.

반응형