[260721 16일차] YOLO 심화

2026. 7. 22. 16:46AI 기반 프론트엔드 웹개발자 양성과정

# YOLO 학습일지

[YOLO 심화] 히트맵·보안·카운팅
+ SAHI·속도추정·FastSAM

2026.07.21 · 학습 범위: YOLO 심화 02~19 (solutions · SAHI · FastSAM · OpenVINO)

📋 오늘 다룬 것들

어제 추적(Tracking)에 이어, 오늘은 YOLO를 감싼 ultralytics solutions 모듈을 파고들었다. 코드 몇 줄로 히트맵·보안 알람·블러·거리 측정·영역/출입 카운팅·속도 추정 같은 실전 기능이 뚝딱 나온다는 게 놀라웠다. 후반엔 작은 물체를 놓치지 않는 SAHI, 텍스트로 물체를 잘라내는 FastSAM까지 다뤘다. 라이브러리가 얼마나 많은 걸 대신해 주는지 체감한 하루였다.

실시간 분석
HeatmapSecurityAlarmObjectBlurrer
거리·영역
DistanceRegionCounterObjectCounter
작은 물체
SAHI슬라이싱
속도 · 분할
SpeedEstimatorFastSAM
환경
conda py39pip sahi
공통 패턴
solutions(frame).plot_im

🧩 ultralytics.solutions 클래스 모음

from ultralytics import solutions 하나로 실전 기능들이 클래스로 들어 있다. 오늘은 이 중 여러 개를 직접 써봤다.

Heatmap물체가 자주 나타난 곳을 열지도로 시각화 (오늘)
SecurityAlarm특정 클래스 감지 시 이메일 알림 (오늘)
ObjectBlurrer탐지 영역만 자동 블러 (오늘)
DistanceCalculation두 객체 중심점 사이 픽셀 거리 (오늘)
RegionCounter다각형 영역 안 객체 수 집계 (오늘)
ObjectCounter선을 넘는 IN/OUT 카운팅 (오늘)
SpeedEstimator차량 등 이동 속도(km/h) 추정 (오늘)
·AIGym운동 자세·반복 횟수 카운팅
·Analytics탐지 결과를 그래프로 통계화
·ObjectCropper탐지 영역만 잘라 저장
·ParkingManagement주차 구역 점유 관리
·QueueManager대기열 인원 관리
·TrackZone특정 구역 안에서만 추적
·VisionEye시점 기반 객체 매핑
·SearchApp / Inference이미지 검색 · Streamlit 추론 UI
공통 사용 패턴 — 오늘 계속 나온 형태 — solutions 객체는 일반 추론(model())과 조금 다르다. 프레임을 직접 넣어 호출하고(results = 객체(frame)), 결과가 그려진 이미지는 results[0].plot()이 아니라 results.plot_im으로 꺼낸다. show=True면 창을 알아서 띄우고, show=Falsecv2.imshow(..., results.plot_im)으로 직접 띄운다.

🎥 실시간 분석 솔루션

02 — 히트맵 (Heatmap)

물체가 자주 지나간 곳을 빨강(자주)~파랑(드묾) 열지도로 누적 시각화한다. 매장 동선 분석 등에 쓴다.

heatmap = solutions.Heatmap(
    model="yolo11n.pt",
    colormap=cv2.COLORMAP_MAGMA   # 색상 지도(MAGMA=보라~노랑)
)
results = heatmap(frame)
cv2.imshow("HEATMAP", results.plot_im)  # 누적 결과

03 — 보안 알람 (SecurityAlarm)

특정 클래스(예: 자동차)가 감지되면 이메일을 자동 발송한다. 탐지 → 추적 → 누적 카운트 → SMTP 메일 발송이 내부에서 한 번에 처리된다.

alarm = solutions.SecurityAlarm(
    model="yolo11n.pt", show=False,
    records=2,       # N회 감지 시 1회 메일(오탐 방지)
    classes=[2]      # 2 = car
)
alarm.authenticate(from_email, password, to_email)  # SMTP 로그인
results = alarm(frame)
Gmail 앱 비밀번호 필수 — 2단계 인증을 켠 뒤 앱 비밀번호(16자리)를 발급받아 password에 넣어야 한다. 일반 로그인 비밀번호로는 SMTP 인증이 막힌다. 실무에선 비밀번호를 코드에 직접 쓰지 말고 환경변수로 분리하는 게 안전하다.

04 — 블러 (ObjectBlurrer)

방송의 모자이크처럼, 탐지된 물체 영역만 골라 자동으로 흐리게 처리한다. 좌표를 직접 계산할 필요가 없다.

blurrer = solutions.ObjectBlurrer(
    model="yolo11n.pt", show=False,
    blur_ratio=0.5   # 0.1~1.0, 높을수록 더 흐림
)
results = blurrer(frame)
cv2.imshow("BLUR", results.plot_im)

📏 거리 · 영역 · 출입

05 — 거리 계산 (DistanceCalculation)

영상 위에서 마우스로 객체 2개를 클릭하면 두 중심점 사이의 픽셀 거리를 실시간으로 잰다.

distance = solutions.DistanceCalculation(model="yolo11n.pt", show=True)
distance(frame)  # 좌클릭 2회로 대상 선택 → 거리 표시

06 — 거리 상태 분류 (실습)

거리 값을 꺼내 신호등처럼 안전/경고/위험으로 분류하는 실습. 여기서 solutions 객체를 process()로 호출하면 결과 객체가 나오고, 거기서 pixels_distance를 꺼낼 수 있다는 걸 배웠다.

results = distance.process(frame)         # 결과 객체 반환
d = results.pixels_distance               # 두 점 사이 픽셀 거리

if d is None or d == 0:
    print("입력 안됨"); continue   # 아직 선택 전
elif d >= 150: status = "Safe"
elif d >= 100: status = "Warning"
else:          status = "Danger"
distance(frame) vs distance.process(frame) — 앞엣것은 화면에 그려 주기만 하고, 뒤엣것은 결과 객체를 돌려줘 pixels_distance 같은 값을 코드로 꺼내 조건 분기에 쓸 수 있다. 상태를 판단해야 하니 process()를 썼다. 임계값(150·100)은 카메라 위치·줌에 따라 달라져 직접 보정해야 한다.

07 — 영역 카운팅 (RegionCounter)

다각형 영역(4점)을 정의하고, 그 안에 들어온 객체만 센다. 좌표는 화면 해상도에 맞춰야 해서 프레임을 640×480으로 통일했다.

region_points = { "region-01": [(179,114), (173,466), (636,359), (217,133)] }
region = solutions.RegionCounter(
    model="yolo11n.pt", show=False,
    region=region_points, conf=0.4   # CCTV 저화질 대응
)
re_frame = cv2.resize(frame, (640, 480))  # 좌표계 통일 필수
results = region(re_frame)

08 — 마우스로 영역 좌표 추출 (get_region)

영역 좌표를 코드에 하드코딩하지 않고, 화면을 클릭해서 좌표를 뽑는 방법. setMouseCallback에 콜백을 연결해 좌클릭한 (x, y)를 리스트에 모은다.

points = []
def mouse_callback(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN:   # 좌클릭 감지
        points.append((x, y))
        print(f"클릭 좌표: {x, y}")

cv2.namedWindow("GET_X_Y", cv2.WINDOW_NORMAL)
cv2.setMouseCallback("GET_X_Y", mouse_callback)
# 좌상단→좌하단→우하단→우상단 순서로 4점 클릭 → region_points로 복사
콜백 인자 5개는 순서 고정event(무슨 일)·x, y(어디)·flags(같이 누른 키)·param(밖에서 넘긴 값). 안 써도 자리는 비워두면 안 된다(OpenCV 호출 규칙). 그리고 좌표 측정 창과 실제 카운팅 코드의 resize 크기가 같아야(640×480) 영역이 안 어긋난다.

09 — 출입 카운팅 (ObjectCounter)

화면에 선(2점)을 긋고 그 선을 넘는 사람·차를 세어 IN/OUT을 집계한다. 4점을 주면 RegionCounter처럼, 2점을 주면 선 카운터로 동작한다.

count_points = [(231,465), (637,418)]  # 점 2개 = 선 1개
counter = solutions.ObjectCounter(
    model="yolo11n.pt", show=False,
    region=count_points
)
results = counter(re_frame)  # 탐지+추적+선통과+IN/OUT 한 번에
선은 사람 다니는 길에 직각으로 그어야 잘 세고, IN/OUT이 반대로 나오면 두 점의 순서만 바꾸면 된다. 같은 사람은 처음 넘는 순간 한 번만 카운트된다(내부에서 ID로 잠금).

🔍 SAHI · 속도 · FastSAM

환경 준비 — SAHI는 별도 가상환경에서 실습했다. conda activate py39로 Python 3.9 환경에 들어가 pip install sahi로 설치. (버전 호환 때문에 전용 환경을 쓰는 게 편했다.)

10 — SAHI 데이터 준비

멀리 있는 작은 차·사람은 일반 YOLO가 잘 못 본다. SAHI로 해결하는데, 먼저 테스트용 이미지를 내려받았다.

from sahi.utils.file import download_from_url

download_from_url(
    ".../demo_data/small-vehicles1.jpeg",  # 작은 차량
    "demo_data/small-vehicles1.jpeg",
)

11 — SAHI 기본 추론 (비교용)

사진을 통째로 한 번 추론해 탐지 개수를 세어 둔다. 이 숫자를 다음 슬라이싱 결과와 비교하면 SAHI 효과가 한눈에 보인다.

model = YOLO("yolo11n.pt")
results = model("demo_data/image_1.png")
detected = len(results[0].boxes)   # 탐지 수 기억!
print(f"탐지수 : {detected}")

12 — SAHI 슬라이싱 추론

사진을 작은 타일(200×200)로 잘라 각 조각마다 추론한 뒤 결과를 합친다. 큰 그림을 돋보기로 한 칸씩 보는 셈이라 작은 물체 탐지가 확 늘어난다.

from sahi.predict import get_sliced_prediction
from sahi import AutoDetectionModel

det_model = AutoDetectionModel.from_pretrained(
    model_type="ultralytics", model_path="yolo11n.pt",
    confidence_threshold=0.4
)
results = get_sliced_prediction(
    "demo_data/image_1.png", det_model,
    slice_width=200, slice_height=200,      # 타일 크기
    overlap_width_ratio=0.1, overlap_height_ratio=0.1  # 경계 10% 겹침
)
results.export_visuals(export_dir="sahi/")
타일을 작게 할수록 더 작은 물체를 잡지만 타일 수가 늘어 처리 시간이 증가한다(트레이드오프). 경계에 걸친 물체를 놓치지 않도록 overlap을 10~20% 준다.

13 — 속도 추정 (SpeedEstimator)

실시간 CCTV에서 차량 속도를 km/h로 추정한다. 핵심은 픽셀을 실제 거리로 바꾸는 meter_per_pixel 보정값.

speed = solutions.SpeedEstimator(
    model="yolo11n.pt", show=False,
    classes=[2],            # car만
    max_speed=120,          # 상한 km/h
    meter_per_pixel=0.5     # 1픽셀 = 0.5m (환경별 보정 필수)
)
results = speed(frame)
cv2.imshow("Speed", results.plot_im)
meter_per_pixel은 화면 1픽셀이 실제 몇 m인지의 환산 계수다. 도로 점선 한 칸(3m)이 화면에서 6픽셀이면 3 ÷ 6 = 0.5. 카메라 높이·각도·렌즈마다 달라서 직접 측정해야 하고, 원근 왜곡 때문에 도로 면 근처에서 재야 정확하다.

15 — FastSAM (텍스트로 잘라내기)

SAM(Segment Anything)은 학습 없이 모든 물체를 분할하지만 무겁다. FastSAM은 그걸 YOLO 골격으로 갈아 끼워 훨씬 빠르게 만들고, "cat"처럼 텍스트 한 단어로 원하는 물체만 콕 집어 잘라낸다.

from ultralytics import FastSAM

model = FastSAM("FastSAM-s.pt")          # s=소형·빠름
results = model("img_cat.jpg", texts="cat") # "cat"만 분할
cv2.imwrite("result.jpg", results[0].plot())
SAM은 "모든 물체"를 다 자르고, FastSAM은 "말로 시킨 것만" 빠르게 자른다. (실습 땐 인증서 오류가 나서 ssl·CA_BUNDLE 관련 설정으로 다운로드를 우회했다.)

⚡ 모델 최적화 · 웹 UI

16 — OpenVINO 변환

OpenVINO는 Intel이 만든 CPU 전용 부스터. PyTorch .pt 모델을 변환하면 GPU 없이 CPU만으로도 2~3배 빠른 추론이 가능하다.

model = YOLO("yolo11n.pt")
model.export(format="openvino")   # → yolo11n_openvino_model/ 폴더 생성
format"openvino" 외에 "onnx"·"tensorrt" 등도 된다. GPU가 없는 노트북에서 빠르게 돌리고 싶을 때 변환이 유용하다.

17 — PT vs OpenVINO FPS 비교

FPS(초당 프레임 수)로 원본과 변환본의 속도를 겨뤄봤다. 한 프레임 추론에 걸린 시간을 재서 1초로 나누면 FPS가 나온다.

start = time.time()
results = model(frame, verbose=False)
fps = 1 / (time.time() - start)   # 1초 ÷ 1장 걸린 시간
cv2.putText(annotated, f"{fps:.1f} FPS", (10,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 2)
모델만 yolo11n.ptyolo11n_openvino_model/로 바꿔 같은 코드로 비교한다. CPU 환경일수록 OpenVINO 효과가 커서 보통 2~3배 빨라진다. verbose=False는 콘솔 로그를 꺼 측정을 깔끔하게 하려는 용도.

18 — 이미지 검색 (SearchApp)

"내 사진첩 전용 구글". 폴더를 주면 "cat" 같은 단어로 비슷한 이미지를 찾아주는 웹앱이다. 내부에 CLIP 임베딩 + FAISS 인덱스가 들어 있어 "비슷한 그림" 찾기가 된다.

app = solutions.SearchApp(
    data="demo_data",   # 검색 대상 이미지 폴더
    device="cpu"          # GPU면 "cuda"
)
app.run(debug=False)     # 브라우저에서 자동 오픈
Flask 앱이라 python 파일.py로 실행한다(streamlit run 쓰면 오류). 여기서도 HuggingFace 모델 다운로드 인증서 문제로 ssl·CA_BUNDLE을 우회 설정했다.

19 — Streamlit 추론 UI

코드 두 줄로 뜨는 YOLO 대시보드. 모델 선택·웹캠 입력·결과 표시를 solutions.Inference가 알아서 그려준다.

inf = solutions.Inference(model="yolo11n.pt")
inf.inference()   # 실행: streamlit run 파일.py
STEP 18 SearchApp이 "이미지 검색"이라면, Inference는 "실시간 객체 탐지 UI". 실행 명령이 python이 아니라 streamlit run이라는 점만 주의하면 된다.

✍️ 마치며

어제까지는 "YOLO로 박스 그리기"였는데, 오늘 solutions를 보니 히트맵·알람·카운팅·속도까지 실제 서비스에 쓸 법한 기능이 이미 클래스로 다 만들어져 있었다. 결국 중요한 건 각 클래스가 어떤 입력(영역 좌표·이메일·보정값)을 필요로 하는지, 그리고 결과를 results.plot_im·process()로 어떻게 꺼내 쓰는지를 아는 것이었다. SAHI의 "잘라서 보기"나 FastSAM의 "말로 자르기"처럼, 문제를 바라보는 관점 자체가 새로워서 재미있었다. 마지막엔 OpenVINO로 속도를 끌어올리고 SearchApp·Streamlit으로 웹 UI까지 붙여봤다. 다음엔 SAM 본체와 직접 모은 데이터로 커스텀 학습을 이어서 해볼 예정이다.

🏷️ YOLO  |  solutions  |  SAHI  |  OpenCV  |  AI  |  학습일지