7.3. Hello BlazeFace

BlazeFace는 Google의 MediaPipe 컬렉션에 속한 얼굴 검출 신경망입니다. 단 한 번의 추론 호출로 검출된 각 얼굴 주위의 경계 사각형과 함께 여섯 개의 얼굴 랜드마크(오른쪽 눈, 왼쪽 눈, 코, 입, 오른쪽 귀, 왼쪽 귀)를 반환합니다. 신경망 지원과 함께 출하되는 모든 OpenMV Cam은 플래시에 blazeface_front_128.tflite 모델을 탑재하고 있으므로, 엔드 투 엔드 얼굴 검출기를 실행하는 데는 몇 줄의 Python이면 됩니다.

7.3.1. 전체 스크립트

import csi
import ml
from ml.postprocessing.mediapipe import BlazeFace

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.VGA)
csi0.window((400, 400))

model = ml.Model("/rom/blazeface_front_128.tflite",
                 postprocess=BlazeFace(threshold=0.4))

while True:
    img = csi0.snapshot()
    for (x, y, w, h), score, keypoints in model.predict([img]):
        img.draw_rectangle((x, y, w, h), color=(0, 255, 0))
        ml.utils.draw_keypoints(img, keypoints, color=(255, 0, 0))

이것이 얼굴 검출기의 전부입니다. 그 외에는 아무것도 없습니다. 스크립트는 프레임을 캡처하여 모델에 전달하고, 반환된 검출 목록을 순회하며, 각 얼굴의 경계 사각형과 여섯 개의 랜드마크를 프레임에 다시 그립니다. IDE 미리보기는 박스와 랜드마크를 실시간으로 보여줍니다.

7.3.2. 각 줄이 하는 일

처음 세 줄은 스크립트에 필요한 모듈을 임포트합니다. csi는 카메라 센서 인터페이스이고, ml은 이 장의 나머지에서 다룰 머신러닝 모듈이며, BlazeFace는 BlazeFace의 원시 출력 텐서를 스크립트가 순회하는 경계 상자 및 랜드마크 목록으로 변환하는 후처리기입니다.

다음 다섯 줄은 센서를 구성합니다. 카메라를 알려진 상태로 리셋하고, RGB565 색상으로 설정하고, VGA 해상도로 설정한 다음, 400x400 정사각형으로 윈도잉(windowed)합니다. 이 윈도잉이 중요합니다. BlazeFace는 정사각형 크롭으로 학습되었으며, 정사각형 입력을 주면 네트워크가 기대하는 종횡비가 캡처된 프레임에서 보는 것과 맞춰지기 때문입니다.

모델 로딩 줄은 모델 파일을 엽니다:

model = ml.Model("/rom/blazeface_front_128.tflite",
                 postprocess=BlazeFace(threshold=0.4))

ml.Model은 주어진 경로의 파일을 읽고(/rom/은 나중에 다룰 플래시 상주 파일 시스템입니다), 스크립트가 추론을 실행할 모델 객체를 반환합니다. postprocess= 키워드는 BlazeFace 후처리기를 등록합니다. 이것이 없으면 predict는 네트워크의 원시 출력 텐서를 반환하고 애플리케이션이 직접 디코딩해야 합니다. 이것이 있으면 predict는 디코딩된 결과를 바로 반환합니다. 후처리기의 threshold=0.4 인수는 검출이 유지되기 전에 네트워크가 보고해야 하는 최소 신뢰도를 설정합니다. 값이 낮을수록 더 흐릿한 얼굴도 잡아내지만 그만큼 거짓 양성이 늘어납니다.

나머지 네 줄은 메인 루프입니다. 루프를 한 번 통과할 때마다 프레임 하나를 캡처하고 모델에게 무엇이 보이는지 묻습니다:

img = csi0.snapshot()
for (x, y, w, h), score, keypoints in model.predict([img]):
    img.draw_rectangle((x, y, w, h), color=(0, 255, 0))
    ml.utils.draw_keypoints(img, keypoints, color=(255, 0, 0))

predict()는 입력 목록(여기서는 캡처된 이미지 하나)을 받아 검출 튜플의 목록을 반환합니다. 각 튜플에는 경계 사각형 (x, y, w, h), 0과 1 사이의 신뢰도 score, 그리고 랜드마크 좌표를 담은 (6, 2) 형태의 ndarray가 들어 있습니다 – 순서대로 오른쪽 눈, 왼쪽 눈, 코, 입, 오른쪽 귀, 왼쪽 귀입니다. 그리기 호출은 draw_rectangle()를 사용하여 – 이미지 장에서 다룬 모든 고전적 검출기가 마지막에 사용했던 바로 그 기본 함수입니다 – 얼굴 윤곽을 그립니다. ml.utils.draw_keypoints()는 ml 유틸리티에 포함된 작은 헬퍼로, 각 키포인트를 해당 (x, y) 위치에 십자 표시로 나타냅니다.

7.3.3. 스크립트가 말하지 않는 것

스크립트는 임포트와 센서 설정을 제외하면 추론 작업으로 실행 가능한 일곱 줄이지만, 그 일곱 줄 안에서는 상당히 많은 산술 연산이 일어납니다. 캡처된 400x400 RGB565 프레임은 네트워크에 도달하기 전에 128x128 양자화된 8비트 텐서가 됩니다. 네트워크는 수만 개의 가중치에 대해 수백 번의 연산을 실행합니다. 그 결과로 나온 신뢰도 점수와 박스 오프셋 텐서는 predict가 반환되기 전에 랜드마크가 부착된, 서로 겹치지 않는 경계 상자의 순위 매겨진 목록이 됩니다. 이러한 변환 하나하나는 필요하다면 애플리케이션이 제어할 수 있는 대상이며, 그중 몇 가지는 기본값이 아닌 모델이라면 반드시 튜닝해야 합니다.

다음 네 개의 하위 절은 이러한 변환을 하나씩 펼쳐 봅니다. 순서대로:

  • ml 모듈 – 모델이 로드된 후 ml.Model이 노출하는 것, 그리고 모델 파일이 카메라의 실제로 어디에 위치하는지.

  • 추론 파이프라인 – 모든 predict() 호출의 네 단계.

  • 추론 엔진 – 네트워크의 산술 연산을 실행하는 CPU 및 NPU 경로.

  • 출력 디코딩 – 원시 출력 텐서를 이 스크립트가 순회한 검출로 변환하는 후처리기.

이 장의 끝에 이르면 독자는 카메라와 함께 출하되지 않은 모델에 대한 동등한 스크립트를 작성하고, 후처리기가 아직 존재하지 않는 텐서를 디코딩하며, 특정 모델이 어떤 카메라에서는 30 FPS로 실행되고 다른 카메라에서는 3 FPS로 실행되는 이유를 추론할 수 있게 됩니다.