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

저는 매주 수십 장씩 쌓이는 공고 이미지를 정리하다 지쳐서, 결국 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)
이 코드가 하는 일을 풀어 쓰면 이렇습니다.
- 학습한
best.pt모델을 메모리에 올린다 - 새 공고 이미지를 모델에 통과시켜 박스 좌표를 받는다
- 받은 좌표대로 원본 이미지에서 영역을 잘라 별도 파일로 저장한다
이렇게 잘라낸 작은 이미지들을 OCR(저는 Tesseract와 PaddleOCR을 둘 다 써봤는데 한글은 PaddleOCR이 훨씬 정확했습니다)에 넘기면, 마침내 이미지 → 구조화된 텍스트 파이프라인이 완성됩니다.
자주 막히는 부분
직접 해보면서 한참 헤맸던 지점들을 모아봤습니다.
CUDA 관련 에러로 학습이 안 시작될 때 GPU 드라이버와 PyTorch 버전이 안 맞을 때 자주 납니다. pip install torch --index-url https://download.pytorch.org/whl/cu121 식으로 본인 CUDA 버전에 맞춰 PyTorch를 재설치하면 풀립니다.
라벨링한 박스가 추론 결과에서 엉뚱한 곳에 잡힐 때 대부분 클래스 ID 순서가 꼬인 경우입니다. data.yaml의 names 순서와 라벨링 도구에서 정한 순서가 다르면 학습은 되지만 결과가 뒤섞입니다.
과적합 의심 — 학습 데이터엔 잘 맞는데 새 이미지엔 못 맞힐 때 데이터 증강(augmentation)을 켜야 합니다. Ultralytics는 기본적으로 켜져 있지만, 회전·색조 변화 강도를 더 올리거나 데이터 자체를 더 모으는 게 정답입니다.
박스가 너무 빽빽하게 겹쳐서 잡힐 때 conf 임계값을 올리세요. 추론 시 model('img.jpg', conf=0.5) 식으로 신뢰도 50% 이상만 남기면 깔끔해집니다.
마무리
처음 YOLO를 만졌을 때 가장 놀란 건, "정말 명령어 한 줄로 학습이 된다"는 점이었습니다. 어려운 건 모델이 아니라 데이터였습니다.
이번 예제에서 핵심만 추리면, 200장 정도의 직접 라벨링한 데이터로도 YOLOv8n은 실용적인 정확도(mAP50 0.87)를 냅니다. 그리고 OCR 앞단에 객체 탐지를 붙이는 2단계 파이프라인은, 디자인된 이미지에서 정보를 뽑을 때 가장 안정적인 접근입니다.
다음으로 시도해볼 만한 것들도 적어둡니다. 추출된 영역을 PaddleOCR에 연결해 텍스트까지 뽑는 파이프라인 완성, 더 가벼운 YOLOv8n 대신 정확도 높은 YOLOv8s/m으로 비교 실험, 그리고 FastAPI로 감싸 웹 서비스로 띄워보는 것까지. 데이터만 본인 도메인에 맞게 바꾸면, 이 흐름은 명함·영수증·간판 등 거의 모든 "이미지 속 영역 추출" 문제에 그대로 통합니다.
'개발 & 기술 > 컴퓨터 비전' 카테고리의 다른 글
| AI 사진 복원으로 오래된 가족사진 화질 살리는 법 (0) | 2026.06.19 |
|---|---|
| 스마트폰 카메라로 OCR 만들기 — 명함·영수증 1초 인식 (0) | 2026.06.19 |
| 한국어 OCR 비교 — Tesseract·PaddleOCR·GPT-4V 실전 후기 (0) | 2026.06.06 |
| 이미지 배경 제거 AI 비교 — Remove.bg·Photoroom·SAM (0) | 2026.06.05 |
| 실시간 얼굴 인식 만들기 — 웹캠으로 30분 완성 (0) | 2026.05.26 |