[Tech Series 05] Realays AI 활용 사례: FruitsFace & Dalendar
Realays가 실제로 FruitsFace와 Dalendar 서비스에 AI 기술을 어떻게 접목했는지, 기술적 해결책과 함께 공유합니다.
![[Tech Series 05] Realays AI 활용 사례: FruitsFace & Dalendar](/images/blog/fruitsface_concept.png)
Realays AI 활용 사례: FruitsFace & Dalendar
지금까지 우리는 Edge Intelligence와 Web ML 기술에 대해 탐구했습니다. 하지만 기술은 사용자에게 가치를 전달할 때만 의미가 있습니다. Realays는 앞서 언급한 최첨단 기술들을 실제 서비스에 접목하여, 사용자에게 새롭고 안전한 경험을 제공하고 있습니다.
1. FruitsFace: 프라이버시를 지키는 얼굴형 분석
서비스 개요
FruitsFace는 사용자의 얼굴형을 과일에 빗대어 재미있게 분석해주는 서비스입니다. 단순한 재미를 넘어, 핵심 가치는 완벽한 프라이버시 보호입니다.
핵심 특징:
- 📸 사진이 서버로 전송되지 않음
- 🔒 모든 AI 분석이 브라우저 내에서 완료
- ⚡ 실시간 결과 제공
- 🌐 인터넷 없이도 작동 (오프라인 모드)
기술적 구현
1단계: 얼굴 검출 (Face Detection)
// MediaPipe Face Detection 사용
import "@mediapipe/face_detection";
import { FaceDetection } from "@mediapipe/face_detection";
const faceDetection = new FaceDetection({
locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/face_detection/${file}`;
},
});
faceDetection.setOptions({
model: "short", // 근거리 얼굴 검출
minDetectionConfidence: 0.5,
});
faceDetection.onResults((results) => {
if (results.detections.length > 0) {
const detection = results.detections[0];
analyzeFaceShape(detection);
}
});
// 사용자 이미지 처리
await faceDetection.send({ image: imageElement });
2단계: 얼굴 랜드마크 추출
// 68개 얼굴 포인트 추출
import "@mediapipe/face_mesh";
const faceMesh = new FaceMesh({
locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/${file}`;
},
});
faceMesh.setOptions({
maxNumFaces: 1,
refineLandmarks: true,
minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5,
});
faceMesh.onResults((results) => {
const landmarks = results.multiFaceLandmarks[0];
const faceShape = calculateFaceShape(landmarks);
displayResult(faceShape);
});
3단계: 얼굴형 분류
얼굴 랜드마크를 바탕으로 주요 비율을 계산합니다:
function calculateFaceShape(landmarks) {
// 얼굴 너비 (178-234 인덱스)
const faceWidth = distance(landmarks[234], landmarks[454]);
// 얼굴 길이 (10-152 인덱스)
const faceLength = distance(landmarks[10], landmarks[152]);
// 광대뼈 너비 (123-352 인덱스)
const cheekWidth = distance(landmarks[123], landmarks[352]);
// 턱 너비 (172-397 인덱스)
const jawWidth = distance(landmarks[172], landmarks[397]);
// 비율 계산
const lengthToWidthRatio = faceLength / faceWidth;
const cheekToJawRatio = cheekWidth / jawWidth;
// 얼굴형 분류
if (lengthToWidthRatio > 1.3 && cheekToJawRatio < 1.1) {
return {
type: "banana", // 긴형
description: "세련되고 날씬한 인상",
recommendations: ["긴 헤어스타일", "둥근 선글라스"],
};
} else if (lengthToWidthRatio < 1.1 && cheekToJawRatio > 1.2) {
return {
type: "apple", // 둥근형
description: "부드럽고 친근한 인상",
recommendations: ["각진 안경", "측면 볼륨 헤어"],
};
}
// ... 나머지 과일 타입들
}
function distance(point1, point2) {
const dx = point1.x - point2.x;
const dy = point1.y - point2.y;
return Math.sqrt(dx * dx + dy * dy);
}
성능 최적화
모델 경량화
// WebGPU 백엔드 활성화 (가능한 경우)
if (navigator.gpu) {
await tf.setBackend("webgpu");
} else {
await tf.setBackend("webgl");
}
// 양자화된 모델 사용
const model = await tf.loadGraphModel("face_model_quantized/model.json");
프로그레시브 로딩
// 필수 모델만 먼저 로드
async function initializeApp() {
// 1단계: 얼굴 검출 모델 (2MB)
showProgress("얼굴 검출 모델 로딩 중...", 33);
await loadFaceDetection();
// 2단계: 랜드마크 모델 (8MB)
showProgress("상세 분석 모델 로딩 중...", 66);
await loadFaceMesh();
// 3단계: 분류 모델 (1MB)
showProgress("분류 모델 로딩 중...", 100);
await loadClassifier();
hideProgress();
enableCamera();
}
사용자 경험
Before (서버 기반):
- 사진 업로드 (프라이버시 우려)
- 서버 전송 대기 (~2초)
- AI 처리 대기 (~1초)
- 결과 수신 Total: ~3초 + 프라이버시 위험
After (Edge AI):
- 브라우저에서 즉시 처리
- 실시간 피드백 (<100ms) Total: ~0.1초 + 완벽한 프라이버시
2. Dalendar: 스마트 캘린더 with AI
서비스 개요
Dalendar는 한국의 음력/양력 시스템을 완벽하게 지원하는 스마트 캘린더 앱입니다.
AI 기능:
- 📅 자연어 일정 입력: “다음 주 화요일 오후 3시 회의”
- 🔍 스마트 검색: “지난달 치과 예약”
- 💡 일정 제안: 반복 패턴 감지 및 자동 제안
- 🌙 음력/양력 자동 변환
기술적 구현
자연어 처리 (NLP)
// MLC-LLM으로 경량 LLM 실행
import * as webllm from "@mlc-ai/web-llm";
const engine = await webllm.CreateMLCEngine("Phi-2-quantized", {
initProgressCallback: (progress) => {
console.log(`모델 로딩: ${progress.progress}%`);
},
});
async function parseNaturalLanguage(input) {
const prompt = `다음 문장에서 일정 정보를 JSON으로 추출해주세요:
"${input}"
형식:
{
"title": "일정 제목",
"date": "YYYY-MM-DD",
"time": "HH:MM",
"duration": "분 단위"
}`;
const reply = await engine.chat.completions.create({
messages: [{ role: "user", content: prompt }],
temperature: 0.1,
max_tokens: 150,
});
const jsonMatch = reply.choices[0].message.content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
}
return null;
}
// 사용 예시
const eventInfo = await parseNaturalLanguage(
"다음 주 금요일 저녁 7시 저녁 약속",
);
// 결과: { title: "저녁 약속", date: "2026-02-19", time: "19:00", duration: 60 }
패턴 인식 및 일정 제안
// 사용자의 일정 패턴 분석
function analyzeSchedulePatterns(events) {
const patterns = {};
events.forEach((event) => {
const key = `${event.title}_${event.dayOfWeek}_${event.time}`;
if (!patterns[key]) {
patterns[key] = { count: 0, lastDate: null, interval: [] };
}
patterns[key].count++;
if (patterns[key].lastDate) {
const daysSince = Math.floor(
(event.date - patterns[key].lastDate) / (1000 * 60 * 60 * 24),
);
patterns[key].interval.push(daysSince);
}
patterns[key].lastDate = event.date;
});
// 반복 패턴 감지 (예: 매주 월요일 10시 회의)
const suggestions = Object.entries(patterns)
.filter(([_, data]) => {
if (data.count < 3) return false; // 최소 3회 이상
const avgInterval =
data.interval.reduce((sum, i) => sum + i, 0) / data.interval.length;
const variance =
data.interval.reduce(
(sum, i) => sum + Math.pow(i - avgInterval, 2),
0,
) / data.interval.length;
// 일정한 간격 (분산이 작음)
return variance < 4 && avgInterval > 0;
})
.map(([key, data]) => {
const avgDays =
data.interval.reduce((sum, i) => sum + i, 0) / data.interval.length;
const nextDate = new Date(
data.lastDate.getTime() + avgDays * 24 * 60 * 60 * 1000,
);
return {
suggestion: `${key.split("_")[0]} 일정을 ${nextDate.toLocaleDateString()}에 추가하시겠습니까?`,
event: {
title: key.split("_")[0],
date: nextDate,
confidence: data.count / 10, // 신뢰도
},
};
});
return suggestions;
}
음력/양력 변환 (EAF 알고리즘)
// Euclidean Affine Functions로 초고속 날짜 계산
class LunarCalendar {
// Rata Die 기반 변환
static solarToLunar(year, month, day) {
const rataDie = this.ymdToRataDie(year, month, day);
// EAF 알고리즘으로 음력 계산
return this.rataDieToLunar(rataDie);
}
static ymdToRataDie(y, m, d) {
// 논문의 공식 직접 구현
const a = Math.floor((14 - m) / 12);
const yPrime = y + 4800 - a;
const mPrime = m + 12 * a - 3;
return (
d +
Math.floor((153 * mPrime + 2) / 5) +
365 * yPrime +
Math.floor(yPrime / 4) -
Math.floor(yPrime / 100) +
Math.floor(yPrime / 400) -
32045
);
}
}
// 성능: 전통적 방법 대비 2.75배 빠름
최적화 결과
| 메트릭 | Before | After | 개선 |
|---|---|---|---|
| 자연어 파싱 | 서버 API (~500ms) | 온디바이스 (~150ms) | 70% 빠름 |
| 프라이버시 | 서버 전송 | 완전 로컬 | 100% 안전 |
| 오프라인 | 불가능 | 완벽 지원 | 신규 기능 |
| 날짜 계산 | 245μs | 89μs | 175% 빠름 |
3. 공통 아키텍처 패턴
두 서비스 모두 동일한 Edge AI 아키텍처를 따릅니다:
[사용자 입력]
↓
[브라우저 내 전처리]
↓
[WebGPU/WebGL 가속 AI 모델]
↓
[브라우저 내 후처리]
↓
[실시간 결과 표시]
→ 서버 전송 없음
→ 완벽한 프라이버시
→ 초고속 응답
4. 학습과 개선
프라이버시를 지키며 모델 개선하기
사용자 데이터를 수집하지 않으면서도 모델을 개선하는 방법:
Federated Learning (연합 학습)
// 사용자 브라우저에서 모델 fine-tuning
async function localFineTuning(userFeedback) {
// 1. 사용자 피드백을 바탕으로 로컬에서 미세 조정
const localModel = await tf.loadLayersModel("indexeddb://local-model");
// 2. 업데이트된 가중치만 암호화하여 서버로 전송
const gradients = await localModel.getWeights();
const encryptedGradients = await encryptFederatedUpdate(gradients);
await fetch("/api/federated-update", {
method: "POST",
body: JSON.stringify(encryptedGradients),
});
// 3. 원본 데이터는 브라우저에만 존재
}
마무리
Realays의 두 서비스는 기술이 어떻게 실제 가치로 전환되는지 보여줍니다:
FruitsFace:
- Edge AI로 완벽한 프라이버시 보호
- 실시간 피드백으로 뛰어난 UX
- 재미와 실용성의 결합
Dalendar:
- 온디바이스 LLM으로 스마트한 일정 관리
- EAF 알고리즘으로 초고속 날짜 계산
- 한국 문화에 최적화된 기능
핵심 가치:
- 프라이버시가 선택이 아닌 기본값
- 네트워크 없이도 완벽한 기능
- 빠른 응답 시간으로 자연스러운 UX
- 사용자 경험을 해치지 않는 AI
다음 프로젝트에서 Edge Intelligence를 고려해보세요. 더 나은 세상을 만들 수 있습니다.

![[Tech Series 01] 웹 브라우저, AI의 새로운 무대가 되다: Edge Intelligence](/images/blog/edge_intelligence_concept.png)
![[Tech Series 02] TensorFlow.js에서 WebLLM까지: Web ML의 진화](/images/blog/web_ml_evolution.png)