YOLO 심화
탐지에서 추적·분석·배포까지
기준 모델: yolo11n.pt (작고 빠른 Nano 모델)
0. 시작 전 꼭 알아둘 것
YOLO 모델 이름 읽기
yolo11n.pt의 n은 Nano를 뜻한다. 일반적으로 n → s → m → l → x 순서로 모델이 커지고 정확도 잠재력은 높아지지만, 속도와 메모리 부담도 커진다.
| 모델 | 특징 | 초보자 권장 상황 |
| yolo11n.pt | 가장 작고 빠름 | CPU·저사양 PC·웹캠 실습 |
| yolo11s.pt | 속도와 정확도의 절충 | Nano 결과가 부족할 때 |
| yolo11m.pt 이상 | 더 무겁고 느릴 수 있음 | GPU가 있고 정확도가 더 중요할 때 |
주의 큰 모델이 항상 내 환경에서 더 좋은 것은 아니다. 먼저 yolo11n.pt로 전체 코드가 정상 작동하는지 확인한 뒤 모델만 바꾼다.
경로와 들여쓰기: 오늘 가장 많이 만난 오류
상대 경로는 현재 실행 폴더를 기준으로 찾는다. FileNotFoundError가 나면 os.path.abspath()로 실제 탐색 위치를 확인한다.
while문 안에서 반복할 코드는 모두 4칸 들여쓴다. 반복문 밖의 break는 SyntaxError: 'break' outside loop를 만든다.
함수 호출에는 괄호가 필요하다: cap.release()와 cv2.destroyAllWindows().
실행 위치가 달라도 안전한 경로 만들기
from pathlib import Path
base_dir = Path(__file__).resolve().parent
image_path = base_dir / "demo_data" / "image_1.png"
if not image_path.exists():
print("찾고 있는 위치:", image_path)
raise SystemExit
1. 객체 탐지와 객체 추적
| 구분 | 탐지(Detection) | 추적(Tracking) |
| 목적 | 현재 프레임의 물체 위치·종류 찾기 | 프레임 사이에서 같은 물체 ID 유지 |
| 상태 기억 | 없음: 매 프레임 독립 | 있음: 이전 프레임과 연결 |
| 대표 호출 | model(frame) | model.track(frame, persist=True) |
| 활용 | 사진 분석, 단일 장면 | 동선, 출입, 속도, 체류 분석 |
persist=True는 ‘이번 프레임이 이전 프레임과 이어진다’고 추적기에 알려 같은 객체의 ID를 유지하도록 돕는다.
기본 추적 코드
from ultralytics import YOLO
import cv2
model = YOLO("yolo11n.pt")
cap = cv2.VideoCapture(0)
while cap.isOpened():
success, frame = cap.read()
if not success:
break
results = model.track(frame, persist=True, conf=0.6, verbose=False)
annotated_frame = results[0].plot()
cv2.imshow("TRACK", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
초보자 체크 단일 이미지에는 지속적인 track ID의 의미가 없다. 연속 영상 프레임에서 사용할 때 추적의 장점이 생긴다.
2. from ultralytics import solutions란?
solutions는 YOLO의 탐지·추적 결과를 실제 업무 기능으로 묶어 둔 고수준 도구 모음이다. 직접 박스 좌표와 ID를 계산하는 코드 대신, 프레임을 솔루션 객체에 넣고 SolutionResults를 받는 방식으로 사용한다.
Solutions 공통 사용 형태
from ultralytics import solutions
tool = solutions.Heatmap(model="yolo11n.pt", show=False)
results = tool(frame)
processed_frame = results.plot_im
| 솔루션 | 무엇을 하는가 | 대표 결과 |
| Heatmap | 객체 위치를 누적해 활동 밀도 표시 | plot_im, total_tracks |
| ObjectCounter | 선 또는 영역을 통과한 IN/OUT 계산 | in_count, out_count |
| RegionCounter | 여러 구역 안의 객체 수 계산 | region_counts |
| SecurityAlarm | 탐지 수 조건 충족 시 이메일 알림 | email_sent |
| ObjectBlurrer | 탐지된 객체 영역 자동 블러 | plot_im |
| DistanceCalculation | 선택한 두 객체 사이 픽셀 거리 | pixels_distance |
| SpeedEstimator | 추적 이동량으로 속도 추정 | speed_dict |
| ObjectCropper | 탐지 객체를 이미지로 잘라 저장 | total_crop_objects |
| TrackZone / QueueManager | 구역 추적·대기열 분석 | plot_im, counts |
중요 일반 YOLO 결과는 results[0].plot()을 많이 쓰지만, Solutions 반환값은 보통 results.plot_im을 사용한다.
3. 모든 영상 솔루션의 기본 반복문
반복해서 재사용할 수 있는 골격
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print("영상을 열 수 없습니다.")
raise SystemExit
while cap.isOpened():
success, frame = cap.read()
if not success:
print("영상이 끝났거나 프레임 읽기에 실패했습니다.")
break
results = solution(frame) # 반드시 while 안쪽
cv2.imshow("RESULT", results.plot_im)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
cv2.VideoCapture(0): 기본 웹캠
cv2.VideoCapture('input.mp4'): 동영상 파일
cv2.VideoCapture(stream_url): CCTV/HLS 스트림. 주소가 종료되거나 차단되면 프레임을 읽지 못할 수 있다.
4. 히트맵(Heatmap)
히트맵은 추적 객체의 위치를 시간에 따라 누적하여 자주 등장하거나 오래 머문 영역을 색의 강도로 보여준다. 매장 동선, 진열대 관심도, 체류 구역, 입지 분석에 활용할 수 있다.
색 해석 색 의미는 선택한 colormap에 따라 달라진다. 일반적으로 밝고 뜨거운 색이 누적량이 큰 영역이지만, 반드시 사용한 색상표를 기준으로 해석한다.
사람만 누적하는 히트맵
from ultralytics import solutions
import cv2
video_path = "input.mp4"
cap = cv2.VideoCapture(video_path)
heatmap = solutions.Heatmap(
model="yolo11n.pt",
show=False,
classes=[0],
conf=0.5,
colormap=cv2.COLORMAP_MAGMA
)
while cap.isOpened():
success, frame = cap.read()
if not success:
break
results = heatmap(frame)
cv2.imshow("HEATMAP", results.plot_im)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
| 인자 | 의미 | 예 |
| model | 사용 모델 | yolo11n.pt |
| show | 솔루션 내부 창 표시 여부 | False이면 직접 imshow |
| classes | 분석할 COCO 클래스 번호 | [0]=사람, [2]=자동차 |
| conf | 최소 신뢰도 | 0.5 |
| colormap | 색상 지도 | MAGMA, JET, HOT, PARULA |
| region | 관심 선·다각형(선택) | [(x1,y1), ...] |
5. 보안 알람(SecurityAlarm)
SecurityAlarm은 탐지 개수가 records 이상이면 주석이 그려진 이미지를 첨부해 이메일을 한 번 전송하는 솔루션이다. 사람·차량 감지, 무단 침입 경보, 야간 감시에 활용할 수 있다.
Gmail 준비
Google 계정의 2단계 인증을 켠다.
앱 비밀번호에서 16자리 비밀번호를 발급한다.
일반 로그인 비밀번호 대신 앱 비밀번호를 사용한다.
보안 원칙 앱 비밀번호를 .py 파일에 직접 저장하거나 화면 캡처로 공유하지 않는다. 환경 변수로 전달하고, 설치된 ultralytics 소스 파일을 Ctrl+클릭해서 수정하지 않는다.
실행 전 환경 변수 설정
# Windows 명령 프롬프트에서 현재 창에만 설정
set GMAIL_FROM=sender@gmail.com
set GMAIL_TO=receiver@gmail.com
set GMAIL_APP_PASSWORD=발급받은16자리앱비밀번호
사람 감지 이메일 알람
from ultralytics import solutions
import cv2, os
cap = cv2.VideoCapture(0)
alarm = solutions.SecurityAlarm(
model="yolo11n.pt",
show=False,
classes=[0], # 사람만
conf=0.5,
records=1 # 1명 이상이면 알림
)
alarm.authenticate(
os.environ["GMAIL_FROM"],
os.environ["GMAIL_APP_PASSWORD"],
os.environ["GMAIL_TO"]
)
while cap.isOpened():
success, frame = cap.read()
if not success:
break
results = alarm(frame)
cv2.imshow("ALARM", results.plot_im)
print("이메일 발송 여부:", results.email_sent)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
알아둘 점 기본 구현은 한 이벤트에서 이메일을 반복 발송하지 않도록 email_sent 상태를 사용한다. 실제 서비스에서는 재알림 대기시간, 오탐 방지, 로그 저장을 추가한다.
6. 개인정보 보호 블러(ObjectBlurrer)
탐지된 객체의 박스 영역을 자동으로 흐리게 만들어 얼굴·차량 등 민감한 장면의 노출을 줄인다. 단, yolo11n.pt의 COCO 클래스는 ‘얼굴’ 전용 모델이 아니므로 사람 전체가 블러될 수 있다.
사람 영역 블러
blurrer = solutions.ObjectBlurrer(
model="yolo11n.pt",
show=False,
classes=[0],
blur_ratio=0.5
)
while cap.isOpened():
success, frame = cap.read()
if not success:
break
results = blurrer(frame)
cv2.imshow("BLUR", results.plot_im)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
7. 거리 계산(DistanceCalculation)
영상 창에서 탐지 박스 두 개를 왼쪽 클릭하면 두 객체 중심 사이의 픽셀 거리를 계산한다. 오른쪽 클릭은 선택 초기화다.
단위 주의 pixels_distance는 실제 미터가 아니라 화면상의 픽셀 거리다. 실제 거리로 바꾸려면 카메라 보정, 원근 보정, 기준 길이가 필요하다.
두 객체 거리와 상태 출력
distance = solutions.DistanceCalculation(
model="yolo11n.pt",
show=True
)
while cap.isOpened():
success, frame = cap.read()
if not success:
break
results = distance.process(frame)
d = results.pixels_distance
if d and d > 0:
status = "SAFE" if d >= 150 else "WARNING" if d >= 100 else "DANGER"
print(f"[거리] {d:.2f}px [상태] {status}")
if cv2.waitKey(1) & 0xFF == ord("q"):
break
NULL window 오류 OpenCV 버전 차이로 'Ultralytics Solutions' 창을 못 찾는다면 show=False로 바꾸고 cv2.namedWindow와 cv2.setMouseCallback을 직접 연결하는 우회가 필요할 수 있다.
8. 카운팅: 선, 영역, 여러 구역
ObjectCounter: 선 통과 IN/OUT
count_points = [(376, 222), (378, 268)]
counter = solutions.ObjectCounter(
model="yolo11n.pt",
show=False,
region=count_points
)
results = counter(frame)
print("IN:", results.in_count, "OUT:", results.out_count)
좌표 2개는 선을 만들고, 3개 이상은 다각형 영역을 만든다. 좌표는 실제 처리 프레임의 크기와 일치해야 한다. 프레임을 640×480으로 리사이즈했다면 좌표도 그 화면 기준이어야 한다.
RegionCounter: 여러 구역 현재 인원
딕셔너리의 각 구역 사이에는 쉼표가 필요하다
region_points = {
"region-01": [(391, 198), (395, 270), (484, 278), (481, 196)],
"region-02": [(113, 306), (116, 388), (185, 384), (170, 311)]
}
region_counter = solutions.RegionCounter(
model="yolo11n.pt",
show=False,
region=region_points,
conf=0.4
)
results = region_counter(frame)
print(results.region_counts)
| 목적 | 추천 솔루션 | 핵심 결과 |
| 문을 지나간 누적 출입 | ObjectCounter | in_count / out_count |
| 각 구역에 현재 몇 명인지 | RegionCounter | region_counts |
| 한 구역에서 추적만 | TrackZone | plot_im / total_tracks |
9. 속도 추정(SpeedEstimator)
추적 객체의 프레임 간 이동량과 meter_per_pixel을 이용해 속도를 추정한다. 카메라 각도와 실제 축척이 맞지 않으면 숫자는 참고값에 그친다.
speed = solutions.SpeedEstimator(
model="yolo11n.pt",
show=False,
classes=[2],
line_width=2,
max_speed=120,
meter_per_pixel=0.05
)
results = speed(frame)
print(results.speed_dict)
오타 주의 line_with가 아니라 line_width다. ValueError: ... is not a valid solution argument가 나오면 인자 철자를 먼저 확인한다.
10. SAHI 슬라이싱 추론
SAHI(Slicing Aided Hyper Inference)는 큰 이미지를 작은 조각으로 나누어 각 조각에서 탐지한 뒤 결과를 원본 좌표로 합친다.
멀리 있는 자동차, 드론·위성 이미지의 작은 객체처럼 전체 이미지를 축소할 때 사라지는 대상을 찾는 데 유리하다.
| 일반 YOLO | SAHI |
| 전체 이미지를 한 번에 리사이즈해 추론 | 여러 조각으로 나눠 확대된 상태로 추론 |
| 빠르고 단순 | 더 느리지만 작은 객체 탐지에 유리 |
| 중복 박스 병합 부담이 적음 | 겹침 비율로 경계 객체를 보완한 뒤 결과 병합 |
SAHI 기본 예제
from sahi import AutoDetectionModel
from sahi.predict import get_sliced_prediction
detection_model = AutoDetectionModel.from_pretrained(
model_type="ultralytics",
model_path="yolo11n.pt",
confidence_threshold=0.4,
device="cpu"
)
results = get_sliced_prediction(
"demo_data/image_1.png",
detection_model,
slice_width=256,
slice_height=256,
overlap_width_ratio=0.2,
overlap_height_ratio=0.2
)
results.export_visuals(export_dir="sahi/")
print("탐지 수:", len(results.object_prediction_list))
변수 저장 AutoDetectionModel.from_pretrained() 반환값을 detection_model에, get_sliced_prediction() 반환값을 results에 저장해야 NameError가 나지 않는다.
11. FastSAM: 이름·포인트로 영역 분할
FastSAM은 박스 탐지보다 더 세밀한 마스크 형태로 사물 영역을 분리한다. 전체 분할, 박스, 포인트, 텍스트 프롬프트를 지원한다.
from ultralytics import FastSAM
model = FastSAM("FastSAM-s.pt")
# 메모리를 아끼는 전체 분할
results = model(
"image.png",
imgsz=320,
retina_masks=False,
device="cpu"
)
# 특정 포인트의 대상만 선택하려면
# results = model("image.png", points=[[170, 480]], labels=[1], imgsz=320)
텍스트 프롬프트 texts='white tent'처럼 이름만으로 선택할 수 있지만 CLIP 모델이 추가로 필요해 다운로드·SSL 오류와 메모리 사용 증가가 생길 수 있다. 저사양 PC에서는 포인트 방식이 더 안정적이다.
12. 성능 확인과 OpenVINO
FPS 측정
start_time = time.perf_counter()
results = model(frame, verbose=False)
model_time = time.perf_counter() - start_time
fps = 1 / model_time if model_time > 0 else 0
annotated_frame = results[0].plot()
cv2.putText(
annotated_frame,
f"{fps:.1f} FPS",
(10, 30),
cv2.FONT_HERSHEY_SIMPLEX,
1,
(0, 255, 0),
2
)
해석 이 값은 모델 추론 중심의 순간 FPS다. 카메라 읽기·그리기·화면 표시까지 포함한 전체 처리 FPS와는 다를 수 있다.
OpenVINO 변환
Intel CPU 환경에서는 OpenVINO 형식이 추론 속도를 높일 수 있다. 변환은 한 번만 하고, 이후 생성된 폴더를 모델로 로드한다.
1단계: 변환
# 최초 1회만 실행
from ultralytics import YOLO
model = YOLO("yolo11n.pt")
model.export(format="openvino")
2단계: OpenVINO 모델 사용
# 이후 실행에서는 변환 폴더 로드
model = YOLO("yolo11n_openvino_model")
results = model(frame, verbose=False)
금지 model.export()를 while 프레임 반복문 안에 넣지 않는다. 매 프레임마다 모델을 변환하는 심각한 오류가 된다.
13. Streamlit으로 웹 UI 만들기
Streamlit은 Python 코드로 업로드 버튼, 슬라이더, 결과 이미지를 갖춘 간단한 웹 앱을 만든다.
모델 데모, 수업 결과 공유, 비개발자용 테스트 화면에 적합하다.
yolo_streamlit.py
import streamlit as st
from ultralytics import YOLO
from PIL import Image
st.title("YOLO 객체 탐지")
@st.cache_resource
def load_model():
return YOLO("yolo11n.pt")
model = load_model()
uploaded = st.file_uploader("이미지 선택", type=["jpg", "jpeg", "png"])
if uploaded:
image = Image.open(uploaded).convert("RGB")
results = model(image)
plotted = results[0].plot()[..., ::-1] # BGR → RGB
st.image(plotted, caption="탐지 결과", use_container_width=True)
실행 명령
# py39 환경이 활성화된 폴더에서 실행
python -m streamlit run yolo_streamlit.py
처음 실행 시 Email 입력 안내가 나오면 빈칸으로 Enter를 눌러도 된다.
Local URL: http://localhost:8501을 브라우저에서 연다.
cd yolo_streamlit.py는 잘못된 명령이다. cd는 폴더 이동에만 사용한다.
sreamit이 아니라 streamlit이다.
14. 자주 발생한 오류 빠른 해결표
| 오류·증상 | 원인 | 고칠 부분 |
| break outside loop | break가 while 밖에 있음 | 처리·imshow·키 입력 코드를 4칸 들여쓰기 |
| NameError | 변수 이름 오타 또는 반환값 미저장 | path/patn 확인, results = ... 저장 |
| FileNotFoundError | 실행 기준 폴더와 파일 위치 불일치 | Path(__file__).parent 또는 절대 경로 |
| NULL window | 마우스 콜백 전에 창이 없음/버전 차이 | show 설정 확인, 필요 시 namedWindow |
| invalid solution argument | 인자 철자 오류 | line_with → line_width 등 공식 이름 확인 |
| SSLError NOT_ENOUGH_DATA | CLIP 등 추가 모델 다운로드 실패 | texts 제거·네트워크 확인·포인트 방식 |
| 프레임 읽기 실패 | 경로·카메라·CCTV 주소 문제 | cap.isOpened() 먼저 확인 |
| 결과 이미지가 저장 안 됨 | cv2.imwrite 누락 또는 잘못된 폴더 | 폴더 생성 후 imwrite 반환값 확인 |
| Streamlit 명령을 못 찾음 | 오타 또는 미설치 | python -m streamlit run 파일.py |
# 문제를 좁히는 기본 점검
print("현재 작업 폴더:", os.getcwd())
print("파일 절대 경로:", os.path.abspath(input_path))
print("파일 존재:", os.path.exists(input_path))
print("OpenCV 버전:", cv2.__version__)
부록 A. 클래스 번호와 좌표
COCO 사전학습 모델에서 자주 쓰는 클래스 번호 예시다.
모델이나 사용자 데이터셋이 바뀌면 번호도 달라질 수 있으므로 model.names로 확인한다.
| 번호 | 클래스 | 사용 예 |
| 0 | person | classes=[0] |
| 1 | bicycle | classes=[1] |
| 2 | car | classes=[2] |
| 3 | motorcycle | classes=[3] |
| 5 | bus | classes=[5] |
| 7 | truck | classes=[7] |
현재 모델의 클래스 번호 확인
from ultralytics import YOLO
model = YOLO("yolo11n.pt")
print(model.names)
좌표는 (x, y)이며 왼쪽 위가 (0, 0)이다.
x는 오른쪽으로, y는 아래쪽으로 커진다. resize 이후에는 새 프레임 크기를 기준으로 좌표를 다시 잡는다.
부록 B. 공식 문서
Ultralytics Solutions: https://docs.ultralytics.com/solutions/
객체 추적: https://docs.ultralytics.com/modes/track/
Heatmap: https://docs.ultralytics.com/guides/heatmaps/
Security Alarm: https://docs.ultralytics.com/guides/security-alarm-system/
SAHI 슬라이싱 추론: https://docs.ultralytics.com/guides/sahi-tiled-inference/
'성남 프론트엔드' 카테고리의 다른 글
| 16일차_Hugging Face (0) | 2026.07.22 |
|---|---|
| 14일차_DATA 수집 ~ YOLO 객체 탐지 (0) | 2026.07.20 |
| 13일차_ Anaconda / Python문법 (0) | 2026.07.16 |
| 12일차_Vercel배포 (0) | 2026.07.15 |
| 12일차_Node·Express /POST 보내기/ (0) | 2026.07.14 |