iBetter Books
수정

이제 딥러닝 검출기로 넘어갑니다. 그런데 "딥러닝"이라고 하면 무거운 프레임워크 설치부터 떠올리기 쉽지만, 놀랍게도 첫 두 주자는 추가 라이브러리 없이 OpenCV만으로 돌아갑니다. 예전부터 있던 DNN 모듈의 SSD 검출기와, 비교적 최근 OpenCV에 정식 편입된 YuNet입니다. 둘 다 정면뿐 아니라 어느 정도 기울거나 옆으로 돌린 얼굴까지 잡아, 고전 검출기의 벽을 넘어섭니다.

OpenCV DNN이란

OpenCV의 DNN 모듈은 다른 프레임워크(Caffe, TensorFlow, ONNX 등)에서 학습된 딥러닝 모델을 불러와 실행해 주는 기능입니다. 즉 모델을 학습하지는 못해도, 누군가 학습해 둔 모델 파일만 있으면 OpenCV 안에서 바로 추론할 수 있습니다. 얼굴 검출에는 전통적으로 SSD(Single Shot Detector) 구조에 ResNet-10을 결합한 모델을 씁니다.

이 모델은 파일 두 개가 필요합니다. 신경망의 구조를 적은 deploy.prototxt와, 학습된 가중치인 res10_300x300_ssd_iter_140000.caffemodel입니다. 두 파일은 OpenCV의 공식 저장소나 모델 Zoo에서 내려받아 작업 폴더에 둡니다.

SSD 검출 코드

다음 내용을 dnn_detect.py로 저장합니다.

# 파일: dnn_detect.py"""OpenCV DNN(SSD ResNet-10)으로 얼굴을 검출한다."""import cv2# 모델 구조와 가중치 파일을 불러온다net = cv2.dnn.readNetFromCaffe(    "deploy.prototxt",    "res10_300x300_ssd_iter_140000.caffemodel",)img = cv2.imread("sample.jpg")h, w = img.shape[:2]# 입력 전처리: 300x300으로 맞추고 평균값을 빼서 정규화한다blob = cv2.dnn.blobFromImage(img, 1.0, (300, 300), (104.0, 177.0, 123.0))net.setInput(blob)detections = net.forward()   # shape: (1, 1, N, 7)CONF = 0.6   # 신뢰도 임계값for i in range(detections.shape[2]):    confidence = detections[0, 0, i, 2]    if confidence < CONF:        continue    # 박스 좌표는 0~1 비율로 나오므로 원본 크기를 곱해 픽셀로 바꾼다    x1 = int(detections[0, 0, i, 3] * w)    y1 = int(detections[0, 0, i, 4] * h)    x2 = int(detections[0, 0, i, 5] * w)    y2 = int(detections[0, 0, i, 6] * h)    cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)    cv2.putText(img, f"{confidence:.2f}", (x1, y1 - 6),                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1, cv2.LINE_AA)cv2.imwrite("dnn_result.jpg", img)

코드에서 두 가지가 새롭습니다. 첫째, blobFromImage는 이미지를 신경망이 기대하는 형태로 다듬는 전처리입니다. 300×300 크기로 맞추고 (104, 177, 123)이라는 평균값을 빼 주는데, 이 숫자는 모델이 학습될 때 쓰인 값이라 그대로 따라야 합니다. 둘째, 결과 박스 좌표가 0~1 사이의 비율로 나오므로, 원본 이미지의 너비·높이를 곱해 실제 픽셀 좌표로 되돌립니다.

YuNet — OpenCV에 내장된 최신 검출기

YuNet은 가볍고 정확한 얼굴 검출기로, 최근 OpenCV에 FaceDetectorYN이라는 정식 클래스로 편입되었습니다. SSD보다 인터페이스가 깔끔하고, 박스뿐 아니라 눈·코·입 같은 5개 랜드마크까지 한 번에 돌려줍니다. 모델 파일 face_detection_yunet_2023mar.onnx 하나만 OpenCV Zoo에서 내려받으면 됩니다.

# 파일: yunet_detect.py"""OpenCV 내장 YuNet(FaceDetectorYN)으로 얼굴과 랜드마크를 검출한다."""import cv2img = cv2.imread("sample.jpg")h, w = img.shape[:2]# 검출기 생성: 모델 경로, (빈 config), 입력 크기,# score_threshold(0.9), nms_threshold(0.3), top_k(5000) 순서로 넘긴다detector = cv2.FaceDetectorYN.create(    "face_detection_yunet_2023mar.onnx", "", (w, h), 0.9, 0.3, 5000,)detector.setInputSize((w, h))   # 입력 이미지 크기를 알려 준다# faces[i] = [x, y, w, h, 눈·코·입 5점(10개 값), score] → 길이 15retval, faces = detector.detect(img)if faces is not None:    for f in faces:        x, y, bw, bh = f[:4].astype(int)        score = f[-1]        cv2.rectangle(img, (x, y), (x + bw, y + bh), (0, 255, 0), 2)        # 5개 랜드마크 점 찍기 (오른눈, 왼눈, 코, 오른입꼬리, 왼입꼬리)        for j in range(4, 14, 2):            cx, cy = int(f[j]), int(f[j + 1])            cv2.circle(img, (cx, cy), 2, (0, 0, 255), 2)cv2.imwrite("yunet_result.jpg", img)

OpenCV DNN과 YuNet 검출 결과

YuNet의 detect가 돌려주는 faces는 한 줄이 길이 15인 배열입니다. 앞 4개가 박스 (x, y, w, h), 그다음 10개가 5개 랜드마크의 좌표, 마지막 하나가 신뢰도입니다. 이렇게 검출과 동시에 랜드마크를 얻을 수 있어서, 다음 PART의 정렬 작업으로 매끄럽게 이어집니다.

실무 팁. 둘 중 무엇을 쓸지 고민된다면 YuNet을 먼저 시도하세요. SSD ResNet-10 모델은 오래되어 작은 얼굴에 약한 편인데, YuNet은 더 가볍고 정확하며 랜드마크까지 주어 활용 폭이 넓습니다. SSD는 "OpenCV DNN으로 외부 모델을 불러오는 방식"을 익히는 예제로서 가치가 있습니다.

이 장에서 기억할 것

OpenCV만으로 돌아가는 두 딥러닝 검출기를 다뤘습니다. SSD ResNet-10은 cv2.dnn.readNetFromCaffe로 외부 모델을 불러와 blobFromImage 전처리 후 추론하며, 결과 박스는 비율 좌표라 원본 크기를 곱해 픽셀로 바꿉니다. YuNet은 OpenCV에 내장된 FaceDetectorYN으로 더 깔끔하고 랜드마크 5점까지 함께 줍니다. 둘 다 고전 검출기보다 다양한 각도에 강합니다. 다음 장에서는 실시간 영상에 특히 강한 경량 검출기, MediaPipe Face Detection을 다룹니다.