앱 연동 가이드
서버 연동은 REST API 호출 한 번이지만, 등록 UX(얼굴 각도 추적·자동촬영)는 온디바이스 구현이 필요합니다. 플랫폼별 데모앱과 소스코드를 그대로 받아 빌드하거나, 아래 공통 스펙대로 직접 구현하세요. 등록 방식은 5방향 정밀등록과 원형(Face ID식) 등록 두 가지가 제공되며, 둘 다 같은 5장을 만들어 같은 API 로 올립니다.
5방향 정밀등록 UX 스펙 · 플랫폼 공통
정면 + 네 모서리, 총 5장을 자동 촬영해 얼굴등록 API로 업로드합니다. 온디바이스 얼굴탐지기(ML Kit 등)에서 얼굴 각도(yaw·pitch)를 읽어 아래 조건을 만족하면 자동 촬영하고 다음 단계로 넘어갑니다.
right_up = 사진 속 얼굴이 오른쪽 위를 향한 상태.
전면 카메라 프리뷰는 거울처럼 좌우가 뒤집혀 보이므로,
사용자가 화면 왼쪽 마커를 볼 때 저장되는 사진은 얼굴이 오른쪽을 향합니다.
즉 화면 안내 문구(거울 기준)와 저장 라벨(사진 기준)은 좌우가 반대인 것이 정상입니다.
포즈 판정 기준 · 부호 규약: +yaw = 화면 왼쪽으로 고개 돌림, +pitch = 위로 듦 (기기·카메라에 따라 반전될 수 있으니 반전 플래그를 두세요)
| pose | yaw 조건 | pitch 조건 | 비고 |
|---|---|---|---|
| frontal | |yaw| ≤ 10° | |pitch| ≤ 12° | 중앙 응시 |
| right_up | yaw ≥ +7° | pitch ≥ +3.5° | 위쪽은 각도 추정치가 작게 나와 완화된 문턱 적용 |
| left_up | yaw ≤ −7° | pitch ≥ +3.5° | |
| right_down | yaw ≥ +11° | pitch ≤ −7° | 공통 상한: |yaw| ≤ 50°, |pitch| ≤ 45° (옆모습 제외) |
| left_down | yaw ≤ −11° | pitch ≤ −7° |
안정화 규칙 · 흔들림·오탐 방지 (실기기 튜닝값)
| 규칙 | 값 | 목적 |
|---|---|---|
| 홀드 프레임 | 포즈 충족 5프레임 연속 유지 시 촬영 | 순간 스침·흔들림 방지 |
| 시작 판정 보류 | 각 단계 시작 후 0.7초 | 이미 자세가 맞아도 즉시 찍히지 않게 |
| 촬영 확인 인터벌 | 촬영 직후 찍힌 사진을 1.1초 표시 | 사용자가 각 장을 눈으로 확인 |
| 얼굴 크기 게이트 | 얼굴 짧은변 / 프레임 짧은변 ≥ 0.26 | 너무 멀면 "가까이" 코칭 |
| 눈 감김 게이트 | 양쪽 눈 열림확률 < 0.2(정면) / 0.1(모서리)가 3프레임 연속일 때만 보류 | 감은 눈 등록 방지 · 분류기 오탐 무시 |
가이드 UI 권장
중앙 타원 가이드 + 목표 모서리에 점멸 마커(파랑→충족 시 초록 체크) + 타원에서 마커로 흐르는 점선 애니메이션. 문구만으로는 방향 전달이 어렵습니다 — 시각 타겟이 핵심입니다.
업로드
POST /api/register (multipart/form-data)
files=@frontal.jpg files=@left_up.jpg files=@right_up.jpg files=@left_down.jpg files=@right_down.jpg
poses=frontal,left_up,right_up,left_down,right_down
unique_key=member-1024 group_key=A extra='{"name":"홍길동"}'
업로드 전 이미지는 짧은변 720px · JPEG 품질 82 정도로 축소하면 전송이 빨라지고 인식 품질 손실이 없습니다. 상세 파라미터는 API 문서 참고.
원형 등록 UX · 아이폰 Face ID 식 · 선택
단계별로 한 방향씩 찍는 대신, 고개로 원을 한 바퀴 그리면서 대각 네 방향을 순서 없이 담는 방식입니다. Flutter·React Native 데모앱에 "원형 등록" 화면으로 함께 들어 있습니다. 업로드 결과(포즈 라벨 5종·5장)는 5방향 방식과 완전히 동일하므로 서버·API는 아무것도 바꿀 필요가 없고, 두 방식을 같은 조건에서 비교해 볼 수 있습니다.
takePicture/takePhoto 를 부르면 셔터음과 프리뷰 정지가 생겨 "원 그리기" 흐름이 끊깁니다.
레퍼런스는 얼굴 각도를 판정한 바로 그 프레임을 그대로 JPEG 로 저장합니다 — 카메라 스트림이 한 번도 멈추지 않고, 촬영 피드백은 햅틱으로 줍니다.
링 좌표 정규화 · 고개 방향을 링 위의 한 점으로 바꾸는 계산
// 부호 규약은 5방향과 동일 (+yaw = 화면 왼쪽, +pitch = 위)
upT = clamp(pitch / 3.5, 0, 1) // 위로 들수록 좌우 기준도 완화
nx = yaw / (11 - (11 - 7) * upT) // 11°=기본, 7°=위쪽 완화치
ny = pitch / (pitch >= 0 ? 3.5 : 7) // 위 3.5° / 아래 7°
mag = sqrt(nx*nx + ny*ny) // 1.0 = 링에 닿음
angle = atan2(ny, nx) * 180 / PI // 화면 좌표 = 중심 + (-cos, -sin)
구역·촬영 조건
| pose | 구역 중심각 | 촬영 조건 |
|---|---|---|
| frontal | — | 5방향과 동일 (|yaw| ≤ 10°, |pitch| ≤ 12°, 5프레임 유지) · 먼저 찍고 링 단계로 |
| right_up | 45° (화면 좌상) | 구역 반각 25° 안 & mag ≥ 1.15 & 4프레임 유지 (닿자마자 찍으면 덜 돌린 사진이 남습니다. 정확히 대각으로 돌리면 mag 1.4 근처) |
| left_up | 135° (화면 우상) | |
| left_down | 225° (화면 우하) | |
| right_down | 315° (화면 좌하) |
얼굴 크기 게이트·눈 감김 게이트·상한(|yaw| ≤ 50°, |pitch| ≤ 45°)은 5방향 스펙과 같은 값을 씁니다. 촬영 순서가 사용자마다 다르므로, 결과는 파일과 포즈 라벨을 쌍으로 넘겨 poses 파라미터 순서를 맞춰야 합니다.
가이드 UI 권장
중앙 정원 가이드(지름 280) + 그 바깥 링 24눈금(지나간 구간 점등) + 대각 4곳 타겟 마커(파랑 점멸 → 촬영되면 초록 체크) + 현재 고개 방향을 나타내는 점. 사용자는 이 점을 링 위로 몰고 다니게 됩니다 — 점이 없으면 "얼마나 돌려야 하는지"를 알 수 없어 방식 자체가 성립하지 않습니다.
프레임 저장 구현
| 플랫폼 | 방법 |
|---|---|
| Flutter | startImageStream 의 NV21(Android)/BGRA(iOS) 버퍼를 복사해 compute() 아이솔레이트에서 RGB 변환 → JPEG 인코딩. 버퍼는 콜백이 끝나면 재사용되므로 반드시 복사할 것 |
| React Native | 네이티브 프레임 프로세서 플러그인(SaveFramePlugin.kt)을 만들어 워크릿에서 동기 호출. 프레임은 워크릿 콜백이 끝나면 무효라, 포즈 판정도 워크릿 안에서 해야 같은 프레임을 저장할 수 있습니다 |
저장할 때 프레임 회전각(rotationDegrees)을 적용해 똑바로 세우고, 좌우반전은 하지 않습니다 — 등록 사진과 인식 사진의 좌우가 다르면 유사도가 떨어집니다. 참고 성능(스냅드래곤 8 Gen2): 얼굴검출 720p 프레임당 ~50ms(≈20fps), 프레임 저장 1280×720 ~50ms.
React Native 연동
데모앱 소스 ZIP은 그대로 빌드되는 완성 앱입니다(React Native 0.86 · TypeScript). 핵심 파일은 아래와 같고, 기존 앱에 이식할 때는 src/poses.ts · src/api.ts · src/FaceEnrollScreen.tsx 세 파일만 복사한 뒤 디자인을 맞추면 됩니다.
| 파일 | 내용 |
|---|---|
| src/FaceEnrollScreen.tsx | 5방향 정밀등록 화면 — 포즈 판정·모서리 마커·점선 가이드·자동촬영·눈감김 게이트 전부 포함 |
| src/poses.ts | 포즈 판정 로직·임계값 (실기기 튜닝값 · FLIP_YAW/FLIP_PITCH 부호 반전 플래그) |
| src/FaceEnrollRingScreen.tsx · ringPoses.ts | 원형 등록 화면 — 링·커서 UI, 구역 판정. 판정이 워크릿 안에서 도는 이유는 파일 상단 주석 참고 |
| src/saveFrame.ts android/…/SaveFramePlugin.kt | 원형 등록용 프레임 저장 플러그인(YUV→JPEG·회전 보정·햅틱). 등록 시 MainApplication.onCreate 에서 addFrameProcessorPlugin 필요 · iOS 미구현 |
| src/api.ts | REST 클라이언트 (등록·탐지·목록·상세·로그) |
| src/LiveAttendanceScreen.tsx | 라이브 얼굴인식(1:N) — 얼굴이 충분히 크면 자동 캡처·전송 |
| src/RegisterScreen.tsx · FacesScreen.tsx · LogsScreen.tsx | 등록 폼 · 등록 관리 · 매칭 로그 |
의존성
npm install react-native-vision-camera@^4 react-native-vision-camera-face-detector react-native-worklets-core
react-native-vision-camera 4.x의 프레임 프로세서로 매 프레임 얼굴 각도(yawAngle·pitchAngle)와 눈 열림 확률을 받아 위 공통 스펙대로 판정합니다. 카메라 권한(Android CAMERA, iOS NSCameraUsageDescription)과 babel 플러그인(react-native-worklets-core/plugin) 등록이 필요합니다.
빌드
npm install
cd android && ./gradlew assembleRelease # 데모앱과 동일한 APK 생성
⚠️ 프로젝트 경로에 공백/한글이 있으면 Metro 번들링이 실패합니다. 공백 없는 경로에서 빌드하세요. minSdk 26 이상 필요.
FLIP_YAW/FLIP_PITCH 플래그로 즉시 뒤집을 수 있게 해두었으니, 실기기에서 마커 반대쪽으로 돌려야 통과되면 플래그만 바꾸세요.Flutter 연동
데모 소스 ZIP은 그대로 빌드되는 완성 앱입니다. camera + google_mlkit_face_detection 기반이며, 핵심 파일은 아래와 같습니다. 기존 앱에 이식할 때는 lib/face_enroll_screen.dart · lib/api.dart 두 파일만 복사한 뒤 디자인을 맞추면 됩니다.
| 파일 | 내용 |
|---|---|
| lib/face_enroll_screen.dart | 5방향 정밀등록 화면 — 포즈 판정·모서리 마커·점선 가이드·자동촬영·눈감김 게이트 전부 포함 |
| lib/face_enroll_ring_screen.dart | 원형 등록 화면 — 링·커서 UI, 구역 판정, 셔터 없이 스트림 프레임 저장(compute 아이솔레이트). image 패키지 필요 |
| lib/api.dart | REST 클라이언트 (등록·탐지·목록·상세·로그) + 업로드 전 이미지 압축 |
| lib/live_attendance_screen.dart | 라이브 얼굴인식(1:N) — 얼굴이 충분히 크면 자동 캡처·전송 |
| lib/register_screen.dart · faces_screen.dart · logs_screen.dart | 등록 폼 · 등록 관리 · 매칭 로그 |
빌드
flutter pub get
flutter build apk --release # 데모앱과 동일한 APK 생성