성남 프론트엔드

15일차_YOLO 심화

sojsoj 2026. 7. 21. 16:49

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/

FastSAM: https://docs.ultralytics.com/models/fast-sam/

'성남 프론트엔드' 카테고리의 다른 글

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