250915 [ Day 50 ] - OpenCV (6)

TaeHyun·2025년 9월 15일

TIL

목록 보기
52/185

시작하며

오늘은 리더님께서 몸이 안 좋으셔서 수업 대신 OpenCV의 얼굴 인식을 이용한 여러 실습 문제를 풀어보는 시간을 가졌다.

얼굴인식 (Haar Cascade)

실습1. 캠화면 얼굴 및 눈 인식

face_cascade = cv.CascadeClassifier(FACE_CASCADE)
eye_cascade = cv.CascadeClassifier(EYE_CASCADE)

cap = cv.VideoCapture()

SCALE = 1
THICKNESS = 2
COLOR = (255,255,255)

while cap.isOpened():
    ret, frame = cap.read()

    if not ret:
        break
    
    gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)

    faces = face_cascade.detectMultiScale(
        gray, scaleFactor=1.1, minNeighbors=10, minSize=(10,10)
    )   
    eyes = eye_cascade.detectMultiScale(
        gray, scaleFactor=1.1, minNeighbors=30, minSize=(30,30)
    )

    if len(faces):
        for face in faces:
            x, y, width, height = face
            cv.rectangle(frame, (x,y), (x+width, y+height), (0,0,255), 2, cv.LINE_AA)
            cv.putText(frame, "Face", (x,y), cv.FONT_HERSHEY_DUPLEX, SCALE, COLOR, THICKNESS)
    if len(eyes):
        for eye in eyes:
            x, y, width, height = eye
            cv.rectangle(frame, (x,y), (x+width, y+height), (0,255,0), 2, cv.LINE_AA)
            cv.putText(frame, "Eye", (x,y), cv.FONT_HERSHEY_DUPLEX, SCALE, COLOR, THICKNESS)

    cv.imshow("Camera", frame)

    if cv.waitKey(1) == ord("q"):
        break

cap.release()
cv.destroyAllWindows()
cv.waitKey(1)

실습2. 눈 덮어 씌우기

eye_cascade = cv.CascadeClassifier(EYE_CASCADE)

img = cv.imread(FACE)
eye_icon = cv.imread("../images/eye.png")

gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

eyes = eye_cascade.detectMultiScale(
    gray, scaleFactor=1.1, minNeighbors=15, minSize=(10,10)
)

if len(eyes):
    for eye in eyes:
        x, y, width, height = eye
        img[y:y+height, x:x+width] = cv.resize(eye_icon, (width, height))

cv.imshow("img", img)

cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(1)

배경 제거

cv2.createBackgroundSubtractorMOG2()

  • 웹캠이나 CCTV영상처럼 움직이는 물건(전경)과 고정된 배경을 자동으로 구분할때 사용
  • history=500 : 배경 학습에 사용할 프레임 수 / 값이 크면 배경 모델이 천천히 변함(안정적), 작으면 빠르게 변함(민감)
  • varThreshold=16 : 전경/배경 분류 임계값 / 값이 작으면 작은 변화에도 움직임으로 판단, 값이 크면 큰 변화가 있어야 움직임으로 판단
  • detectShadows=True : 그림자 검출 여부
import time
import os

# 폴더 생성
SAVE_DIR = "../output"

# 최소 영역
MIN_AREA = 1200
# 저장 간격(초단위)
COOLDOWN = 1.0

# 폴더가 없으면 생성
os.makedirs(SAVE_DIR, exist_ok=True)

cap = cv.VideoCapture(0)

if not cap.isOpened():
    raise RuntimeError("웹캠 오류")

# 배경제거
backsub = cv.createBackgroundSubtractorMOG2(history=200, varThreshold=25, detectShadows=True)

last_saved = 0.0

while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    # 전경 마스크 받아오기
    fg = backsub.apply(frame) # 현재 프레임에서 움직임 부분만 추출
    # 그림자 제거 : 200이상 부분만 남기고 나머지는 0으로 처리
    _, fg = cv.threshold(fg, 200, 255, cv.THRESH_BINARY)
    # 움직이는 영역을 키워서 구멍 메우기
    fg = cv.dilate(fg, None, iterations=2)

    # 움직임 영역 찾기
    contours, _ = cv.findContours(fg, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

    motion = False # 움직임이 있었는지 표시하는 변수

    for c in contours:
        # 너무 작은 영역 무시
        if cv.contourArea(c) < MIN_AREA:
            continue

        # 움직임이 있는 부분에 사각형 표시
        x, y, w, h = cv.boundingRect(c)
        cv.rectangle(frame, (x,y), (x+w, y+h), (0,255,0), 2)
        motion = True

    # 움직인 사진 저장
    now = time.time()
    if motion and (now - last_saved > COOLDOWN):
        filename = time.strftime("%Y%m%d_%H%M%S") + ".jpg" # 현재 시간으로 파일명 생성
        cv.imwrite(os.path.join(SAVE_DIR, filename), frame) # 현재 프레임 저장
        last_saved = now

    # 화면 상단에 "Motion : ON/OFF" 글자 표시
    cv.putText(frame, f"Motion : {'ON' if motion else 'OFF'}", (10, 30), cv.FONT_HERSHEY_DUPLEX, 1, (0,255,0) if motion else (0,0,255), 2)

    # 원본 영싱과 마스크 영상 출력
    cv.imshow("frame", frame)
    cv.imshow("mask", fg)

    # 종료
    if cv.waitKey(1) == ord("q"):
        break

# 카메라, 창 종료
cap.release()
cv.destroyAllWindows()
cv.waitKey(1)

마치며

여러 실습 문제들을 풀어보면서 OpenCV가 조금 더 익숙해진 것 같다. 그리고 배경 제거에 대한 내용도 꽤 유용해 보여서 더 연습해봐야 할 것 같다.

profile
Hello I'm TaeHyunAn, Currently Studying Data Analysis

0개의 댓글