▪︎ 얼굴인식 (Haar Cascade)
▫︎ Haar 특징
- 밝고 어두운 영역 간의 차이를 활용해 객체를 감지하는 규칙
- 알고리즘이 단계별(계단식) 구조로 작동
- 특정 크기(예: 24x24 픽셀)의 창을 이미지 위에서 이동시키면서 이미지를 검사
- 각 창에서 Haar 특징(밝고 어두운 패턴)을 비교
- 창 안의 밝기 차이가 특정 조건을 만족하면 "여기에 얼굴이 있을 가능성이 있다"고 판단
- 얼굴일 가능성이 높은 위치를 Cascade 구조를 통해 더 정밀하게 검사
▫︎ cv2.detectMultiScale
scaleFactor : 이미지 크기를 얼마나 줄여가며 검출을 진행할지를 결정, 기본적으로 1.1로 많이 사용
minNeighbors : 검출된 객체 주변에 얼마나 많은 "이웃" 검출이 있어야 최종적으로 객체로 간주할지 결정
minSize / maxSize : 검출할 객체의 최소 / 최대 크기, 해당 사이즈보다 작거나 큰 객체는 제외
# 얼굴인식
face_cascade = cv.CascadeClassifier(FACE_CASCADE)
img = cv.imread(FACE)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(
gray, scaleFactor=1.1, minNeighbors=10, minSize=(10,10)
)
if len(faces):
for face in faces:
x, y, width, height = face
cv.rectangle(img, (x,y), (x+width, y+height), (0,255,0), 2, cv.LINE_AA)
cv.imshow("img", img)
cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(1)
# 눈인식
eye_cascade = cv.CascadeClassifier(EYE_CASCADE)
img = cv.imread(FACE)
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
cv.rectangle(img, (x,y), (x+width, y+height), (0,255,0), 2, cv.LINE_AA)
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)
▪︎ YOLO