Skip to content

samnammae/25_HC095

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

20 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

IncluKiosk: λͺ¨λ‘λ₯Ό μœ„ν•œ μ μ‘ν˜• 슀마트 ν‚€μ˜€μŠ€ν¬

μ•„ν‚€ν…μ²˜


πŸ’‘ 1. ν”„λ‘œμ νŠΈ κ°œμš”

1-1. ν”„λ‘œμ νŠΈ μ†Œκ°œ

  • ν”„λ‘œμ νŠΈ λͺ…: IncluKiosk
  • ν”„λ‘œμ νŠΈ μ •μ˜: AI 기술(컴퓨터 λΉ„μ „, μ‹œμ„  좔적, LLM)을 ν™œμš©ν•˜μ—¬ λͺ¨λ“  μ‚¬μš©μžλ₯Ό μœ„ν•œ λ§žμΆ€ν˜• λ©€ν‹°λͺ¨λ‹¬ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ œκ³΅ν•˜λŠ” 포용적(Inclusive) ν‚€μ˜€μŠ€ν¬ μ‹œμŠ€ν…œ

1-2. 개발 λ°°κ²½ 및 ν•„μš”μ„±

  • 문제 인식: ν‚€μ˜€μŠ€ν¬ λ³΄νŽΈν™”μ— λ”°λ₯Έ λ””μ§€ν„Έ 격차 심화
    • κ³ μ •λœ ν™”λ©΄ 높이와 ν„°μΉ˜ μ€‘μ‹¬μ˜ 단일 λ°©μ‹μœΌλ‘œ 인해 νŠΉμ • μ‚¬μš©μžμΈ΅(νœ μ²΄μ–΄ μ‚¬μš©μž, 노인, μ‹œκ°μž₯애인 λ“±)의 이용이 어렀움
  • μ‚¬νšŒμ  μš”κ΅¬: 포용적 기술과 보편적 λ””μžμΈμ— λŒ€ν•œ μ‚¬νšŒμ  ν•„μš”μ„± μ¦λŒ€
  • ν”„λ‘œμ νŠΈ λͺ©ν‘œ: λͺ¨λ“  μ‚¬μš©μžκ°€ 차별 없이 μ„œλΉ„μŠ€λ₯Ό μ΄μš©ν•˜λŠ” ν™˜κ²½μ„ μ‘°μ„±ν•˜μ—¬ λ””μ§€ν„Έ μ†Œμ™Έ 문제 ν•΄κ²°

1-3. ν”„λ‘œμ νŠΈ 특μž₯점

  • μ‚¬μš©μž μžλ™ 인식 및 ν™”λ©΄ μ΅œμ ν™”
    • 컴퓨터 λΉ„μ „μœΌλ‘œ μ‚¬μš©μž μ‹ μž₯을 인식, 졜적의 λ†’μ΄λ‘œ μžλ™ 쑰절
    • μ‚¬μš©μž 상황에 λ§žλŠ” λ§žμΆ€ν˜• UI 제곡
  • 닀쀑 μž…λ ₯ 방식을 μ§€μ›ν•˜λŠ” λ©€ν‹°λͺ¨λ‹¬ μΈν„°νŽ˜μ΄μŠ€
    • κΈ°λ³Έ ν„°μΉ˜ 방식에 μ‹œμ„  좔적(Eye-tracking) 및 μŒμ„± 인식 κΈ°λŠ₯ 톡합
    • μ‚¬μš©μžκ°€ μžμ‹ μ—κ²Œ κ°€μž₯ νŽΈλ¦¬ν•œ μž…λ ₯ 방식을 선택 κ°€λŠ₯
  • LLM 기반 μ§€λŠ₯ν˜• λŒ€ν™” μ‹œμŠ€ν…œ
    • λ‹¨μˆœ λͺ…λ Ήμ–΄λ₯Ό λ„˜μ–΄, λ¬Έλ§₯을 μ΄ν•΄ν•˜λŠ” μžμ—°μ–΄ 처리 λŠ₯λ ₯ 확보
    • 메뉴 μΆ”μ²œ, νŠΉμ • μ„±λΆ„ 문의 λ“± 볡합적인 μ§ˆλ¬Έμ— λŒ€ν•œ λŒ€ν™”ν˜• μ‘λŒ€ κ°€λŠ₯

1-4. μ£Όμš” κΈ°λŠ₯

  • μ μ‘ν˜• ν™”λ©΄ 높이 쑰절: AI 기반 μ‚¬μš©μž 인식 및 ν™”λ©΄ 높이 μžλ™ 쑰절
  • μ•„μ΄νŠΈλž˜ν‚Ή μΈν„°νŽ˜μ΄μŠ€: μ‹œμ„  μ›€μ§μž„μ„ ν†΅ν•œ 메뉴 선택 및 μ œμ–΄
  • μŒμ„± 챗봇: LLM 기반 μŒμ„± μ£Όλ¬Έ 및 λŒ€ν™”ν˜• μ§ˆμ˜μ‘λ‹΅
  • λ‹€κ΅­μ–΄ 지원: ν•œκ΅­μ–΄, μ˜μ–΄, 쀑ꡭ어 λ“± λ‹€κ΅­μ–΄ μΈν„°νŽ˜μ΄μŠ€ 제곡
  • 톡합 μ•„ν‚€ν…μ²˜: RESTful API 기반의 μΌκ΄€λœ μ‚¬μš©μž κ²½ν—˜

1-5. κΈ°λŒ€ 효과 및 ν™œμš© λΆ„μ•Ό

  • κΈ°λŒ€ 효과

    • 기술적 μΈ‘λ©΄: λ©€ν‹°λͺ¨λ‹¬ AI μΈν„°νŽ˜μ΄μŠ€λ‘œ κ³ λ„ν™”λœ μ‚¬μš©μž κ²½ν—˜ 제곡
    • μ‚¬νšŒμ  μΈ‘λ©΄: λ””μ§€ν„Έ μ•½μžμ˜ 정보 μ ‘κ·Όμ„± 및 μžλ¦½λ„ ν–₯상을 ν†΅ν•œ λ””μ§€ν„Έ ν¬μš©μ„± μ‹€ν˜„
    • μ‹œμž₯μ„±: κ³ λ Ήν™” 및 μž₯애인 ꢌ읡 ν™•λŒ€μ— λ”°λ₯Έ μ ‘κ·Όμ„± 쀑심 ν‚€μ˜€μŠ€ν¬ μˆ˜μš” μΆ©μ‘±
  • ν™œμš© λΆ„μ•Ό

    • 곡곡기관: 민원 μ ‘μˆ˜ 및 μ•ˆλ‚΄ μ‹œμŠ€ν…œ
    • μ˜λ£ŒκΈ°κ΄€: μ ‘μˆ˜, μˆ˜λ‚©, μ•ˆλ‚΄ μ‹œμŠ€ν…œ
    • κ΅ν†΅μ‹œμ„€: 발ꢌ 및 λ‹€κ΅­μ–΄ μ•ˆλ‚΄ μ‹œμŠ€ν…œ (곡항, 터미널 λ“±)
    • μƒμ—…μ‹œμ„€: μ£Όλ¬Έ μ‹œμŠ€ν…œ (카페, 식당 λ“±)
    • κ΅μœ‘Β·λ¬Έν™”μ‹œμ„€: μ•ˆλ‚΄ 및 μ˜ˆμ•½ μ‹œμŠ€ν…œ (λ„μ„œκ΄€, λ°•λ¬Όκ΄€ λ“±)

1-6. 기술 μŠ€νƒ

ꡬ뢄 기술
FE React.js, Next.js, TypeScript, react-query, zustand, Electron,
BE Java, Spring Boot, Gemini API
AI/ML MediaPipe, OpenCV, PyCoral, TensorFlowLite, MobileNet-V2
HW (IoT) Python, WebSockets
DB MongoDB, MySQL
Cloud AWS (EC2 Β· S3 Β· CloudFront Β· Route53)
HW 라즈베리파이 4 Model B (8GB RAM), 라즈베리파이 카메라λͺ¨λ“ˆ V2,
Seeed ReSpeaker Mic Array, 15.6인치 정전식 ν„°μΉ˜ λ””μŠ€ν”Œλ ˆμ΄,
λ¦¬λ‹ˆμ–΄ 앑좔에이터 및 TB6600 λͺ¨ν„° λ“œλΌμ΄λ²„

πŸ§‘πŸ»β€πŸ’» 2. νŒ€μ› μ†Œκ°œ

μ •ν•œμšΈ (@jho7535) 강은솑 (@kangeunsong) κΉ€λ„ν˜„ (@kdhqwe1030) κΉ€λ„μ˜
μ •ν•œμšΈ 강은솑 κΉ€λ„ν˜„ κΉ€λ„μ˜
β€’ λ°±μ—”λ“œ 개발
β€’ μ„œλ²„ 관리
β€’ ν•˜λ“œμ›¨μ–΄ μ œμ–΄
β€’ AI κΈ°λŠ₯ 개발
β€’ ν”„λ‘ νŠΈμ—”λ“œ 개발
β€’ UI/UX 섀계
β€’ ν”„λ‘œμ νŠΈ λ©˜ν† 
β€’ 기술 자문

πŸ’‘ 3. μ‹œμŠ€ν…œ ꡬ성도

πŸ—οΈ 전체 μ‹œμŠ€ν…œ 흐름도


🧩 μ‹œμŠ€ν…œ μ•„ν‚€ν…μ²˜


πŸ—‚οΈ ERD



πŸ“½οΈ 4. μž‘ν’ˆ μ†Œκ°œμ˜μƒ

ν•œμ΄μŒ λ“œλ¦Όμ—… ν”„λ‘œμ νŠΈ μ†Œκ°œ



πŸ’‘ 5. 핡심 μ†ŒμŠ€μ½”λ“œ

5-1. WebSocket 톡신

  • λΌμ¦ˆλ² λ¦¬νŒŒμ΄μ™€μ˜ WebSocket 톡신을 톡해 STT/TTS 흐름을 μ œμ–΄ν•˜κ³ , λ°±μ—”λ“œ REST API(ChatAPI) λ₯Ό 톡해 챗봇 λŒ€ν™”λ₯Ό μ²˜λ¦¬ν•˜λŠ” 핡심 λ‘œμ§μž…λ‹ˆλ‹€.
// [핡심 ν•¨μˆ˜] Chat.tsx
// - sendMessage(): ν”„λ‘ νŠΈ β†’ 라즈베리파이 λͺ…λ Ή 전솑
// - chatAPI.sendChat(): ν”„λ‘ νŠΈ β†’ λ°±μ—”λ“œ 챗봇 λŒ€ν™” μš”μ²­
// - case ꡬ문: 라즈베리파이 β†’ ν”„λ‘ νŠΈλ‘œ μˆ˜μ‹ λ˜λŠ” λ©”μ‹œμ§€ μ œμ–΄

useEffect(() => {
  if (!isConnected) return;

  const handle = async (msg: SocketMessage) => {
    switch (msg.type) {

      // 1️⃣ μ•ˆλ‚΄ μŒμ„± μ’…λ£Œ β†’ STT μ‹œμž‘ (λΌμ¦ˆλ² λ¦¬νŒŒμ΄λ‘œλΆ€ν„° μˆ˜μ‹ )
      case "END_GUIDE":
        sendMessage({ type: "STT_ON" }); // λΌμ¦ˆλ² λ¦¬νŒŒμ΄μ— μŒμ„±μΈμ‹ μ‹œμž‘ λͺ…λ Ή
        setIsListening(true);
        break;

      // 2️⃣ μŒμ„± 인식 μ™„λ£Œ(STT_OFF) β†’ λ°±μ—”λ“œλ‘œ μ‚¬μš©μž λ°œν™” 전달
      case "STT_OFF":
        setChatLogs(prev => […prev, { message: msg.message, isBot: false }]);

        const res = await chatAPI.sendChat(shopId, {
          sessionId,
          message: msg.message,
          storeId: Number(shopId),
          storeName: shopName,
        });

        const answer = res?.aiMessage || "μ£„μ†‘ν•©λ‹ˆλ‹€, 닡변을 λΆˆλŸ¬μ˜€μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€.";
        setChatLogs(prev => […prev, { message: answer, isBot: true }]);

        // 챗봇 응닡을 λΌμ¦ˆλ² λ¦¬νŒŒμ΄μ— 전달 β†’ μŒμ„± 좜λ ₯(TTS)
        sendMessage({ type: "TTS_ON", message: answer });
        break;

      // 3️⃣ μŒμ„± 좜λ ₯ μ’…λ£Œ(TTS_OFF) β†’ λ‹€μŒ λ°œν™” λŒ€κΈ°
      case "TTS_OFF":
        sendMessage({ type: "STT_ON" }); // λ‹€μŒ μŒμ„±μΈμ‹ μ‹œμž‘
        setIsListening(true);
        break;
    }
  };

  addOnMessage(handle);
  return () => removeOnMessage(handle);
}, [isConnected]);

5-2. EdgeTPU 기반 μ‹€μ‹œκ°„ μ‚¬μš©μž 좔적

  • EdgeTPU ν•˜λ“œμ›¨μ–΄ 가속과 2단계 폴백 μ „λž΅(μ–Όκ΅΄ β†’ μ‚¬λžŒ)을 톡해 μ‚¬μš©μž μœ„μΉ˜λ₯Ό μ‹€μ‹œκ°„μœΌλ‘œ μΆ”μ ν•˜κ³ , EMA ν•„ν„°λ§μœΌλ‘œ λ…Έμ΄μ¦ˆλ₯Ό μ œκ±°ν•˜μ—¬ λ¦¬λ‹ˆμ–΄ 앑좔에이터 μ œμ–΄ ν•¨μˆ˜(5-3)λ₯Ό ν˜ΈμΆœν•˜λŠ” 핡심 λ‘œμ§μž…λ‹ˆλ‹€.
# height_worker.py - EdgeTPU 가속 객체 감지

# 1️⃣ EdgeTPU λͺ¨λΈ λ‘œλ“œ (ν•˜λ“œμ›¨μ–΄ 가속)
face_interpreter = tflite.Interpreter(
    model_path=FACE_MODEL,
    experimental_delegates=[tflite.load_delegate('libedgetpu.so.1')]
)
person_interpreter = tflite.Interpreter(
    model_path=PERSON_MODEL,
    experimental_delegates=[tflite.load_delegate('libedgetpu.so.1')]
)

# 2️⃣ 2단계 폴백 μ „λž΅
def track_height():
    # μš°μ„ μˆœμœ„ 1: μ–Όκ΅΄ 감지 (μ •λ°€ μ œμ–΄)
    faces = detect_faces_ssd(face_interpreter, frame, MIN_DET_CONF)

    if faces:
        # μ–Όκ΅΄ 쀑심을 ν™”λ©΄ μ€‘μ•™μœΌλ‘œ
        y_center = (ymin + ymax) * 0.5
        ema_y = EMA_ALPHA * y_center + (1-EMA_ALPHA) * ema_y
        diff = ema_y - target_y

        if abs(diff) <= deadband:
            state = "center"  # βœ… μ•ˆμ •ν™”
        elif diff < 0:
            moveUp(WITH_FACE)    # λΉ λ₯΄κ²Œ 쑰절
        else:
            moveDown(WITH_FACE)

    else:
        # μš°μ„ μˆœμœ„ 2: μ‚¬λžŒ 전체 감지 (λŒ€λž΅μ  μœ„μΉ˜)
        person = detect_person_ssd(person_interpreter, frame)

        if person:
            if person[1] <= 0.05:      # ν™”λ©΄ 상단
                moveUp(WITHOUT_FACE)   # 천천히 쑰절
            elif person[1] >= 0.95:    # ν™”λ©΄ ν•˜λ‹¨
                moveDown(WITHOUT_FACE)

# 3️⃣ EMA ν•„ν„°λ‘œ λ…Έμ΄μ¦ˆ 제거
ema_y = 0.3 * new_value + 0.7 * ema_y  # λΆ€λ“œλŸ¬μš΄ μ›€μ§μž„

5-3. λ¦¬λ‹ˆμ–΄ 앑좔에이터 μ •λ°€ μ œμ–΄ 및 닀쀑 μ•ˆμ „μž₯치

  • AI λΉ„μ „ μ‹œμŠ€ν…œ(5-2)의 νŒλ‹¨μ— 따라 GPIO νŽ„μŠ€ μ œμ–΄λ₯Ό 톡해 μŠ€ν… λͺ¨ν„°λ₯Ό μ •λ°€ν•˜κ²Œ κ΅¬λ™ν•˜κ³ , 닀쀑 ν•œκ³„ 검증과 μ‹€μ‹œκ°„ 높이 μ €μž₯을 톡해 ν•˜λ“œμ›¨μ–΄ μ•ˆμ „μ„±μ„ 보μž₯ν•˜λ©°, ν”„λ‘œκ·Έλž¨ μ’…λ£Œ μ‹œ μžλ™ 원점 볡귀λ₯Ό μˆ˜ν–‰ν•˜λŠ” 핡심 λ‘œμ§μž…λ‹ˆλ‹€.
# linear_actuator_controller.py - 앑좔에이터 μ•ˆμ „ μ œμ–΄

# 1️⃣ ν•œκ³„ 검증 (파일 기반 μƒνƒœ 관리)
def exceed_max_height() -> bool:
    """μƒν•œ 초과 μ—¬λΆ€ νŒλ‹¨"""
    global CUR_HEIGHT_STEP
    v = _read_height_from_file()  # current_height.txt 읽기

    if v is None:  # 파일 였λ₯˜/λ²”μœ„ μ™Έ
        print("[ACTUATOR] 🚫 높이 파일 였λ₯˜. 이동 차단")
        return True

    CUR_HEIGHT_STEP = v  # μ „μ—­ μƒνƒœμ™€ 동기화
    return CUR_HEIGHT_STEP >= HEIGHT_MAX  # 28000 step

def exceed_min_height() -> bool:
    """ν•˜ν•œ 초과(λ°”λ‹₯) μ—¬λΆ€ νŒλ‹¨"""
    global CUR_HEIGHT_STEP
    v = _read_height_from_file()

    if v is None:
        print("[ACTUATOR] 🚫 높이 파일 였λ₯˜. 이동 차단")
        return True

    CUR_HEIGHT_STEP = v
    return CUR_HEIGHT_STEP <= HEIGHT_MIN  # 0 step

# 2️⃣ μœ„λ‘œ 이동 (ν™•μž₯) - 닀쀑 검증
def moveUp(steps: int = DEFAULT_STEP):
    """앑좔에이터 μœ„λ‘œ 이동 (얼꡴이 ν™”λ©΄ μ•„λž˜μ— μžˆμ„ λ•Œ)"""
    global CUR_HEIGHT_STEP

    # 첫 번째 검증: 이동 μ‹œμž‘ μ „
    if exceed_max_height():
        print("[ACTUATOR] 🚫 μ΅œλŒ€ 높이 도달, 이동 쀑단")
        return

    GPIO.output(DIR, GPIO.LOW)   # μœ„μͺ½ λ°©ν–₯
    GPIO.output(ENA, GPIO.HIGH)  # λͺ¨ν„° ν™œμ„±ν™”
    print(f"[ACTUATOR] ↑ Move UP {steps} steps (Current: {CUR_HEIGHT_STEP}/{HEIGHT_MAX})")

    # λ§€ μŠ€ν…λ§ˆλ‹€ νŽ„μŠ€ + μž¬κ²€μ¦
    for _ in range(steps):
        # 두 번째 검증: 각 μŠ€ν…λ§ˆλ‹€
        if exceed_max_height():
            print("[ACTUATOR] 🚫 μ΅œλŒ€ 높이 도달, 이동 쀑단")
            break

        # νŽ„μŠ€ μ‹ ν˜Έ (μŠ€ν… λͺ¨ν„° ꡬ동)
        GPIO.output(PUL, GPIO.HIGH)
        sleep(STEP_DELAY)  # 0.0004초
        GPIO.output(PUL, GPIO.LOW)
        sleep(STEP_DELAY)

        # μ‹€μ‹œκ°„ 높이 좔적 및 μ €μž₯
        CUR_HEIGHT_STEP += 1
        _write_height_to_file(CUR_HEIGHT_STEP)

    print(f"[ACTUATOR] ↑ Current step: {CUR_HEIGHT_STEP}/{HEIGHT_MAX}")
    GPIO.output(ENA, GPIO.LOW)  # λͺ¨ν„° λΉ„ν™œμ„±ν™”

# 3️⃣ μ•„λž˜λ‘œ 이동 (μˆ˜μΆ•) - 닀쀑 검증
def moveDown(steps: int = DEFAULT_STEP):
    """앑좔에이터 μ•„λž˜λ‘œ 이동 (얼꡴이 ν™”λ©΄ μœ„μ— μžˆμ„ λ•Œ)"""
    global CUR_HEIGHT_STEP

    # 첫 번째 검증: 이동 μ‹œμž‘ μ „
    if exceed_min_height():
        print("[ACTUATOR] 🚫 μ΅œμ†Œ 높이 도달, 이동 쀑단")
        return

    GPIO.output(DIR, GPIO.HIGH)  # μ•„λž˜μͺ½ λ°©ν–₯
    GPIO.output(ENA, GPIO.HIGH)
    print(f"[ACTUATOR] ↓ Move DOWN {steps} steps (Current: {CUR_HEIGHT_STEP}/{HEIGHT_MAX})")

    for _ in range(steps):
        # 두 번째 검증: 각 μŠ€ν…λ§ˆλ‹€
        if exceed_min_height():
            print("[ACTUATOR] 🚫 μ΅œμ†Œ 높이 도달, 이동 쀑단")
            break

        GPIO.output(PUL, GPIO.HIGH)
        sleep(STEP_DELAY)
        GPIO.output(PUL, GPIO.LOW)
        sleep(STEP_DELAY)

        CUR_HEIGHT_STEP -= 1
        CUR_HEIGHT_STEP = max(HEIGHT_MIN, CUR_HEIGHT_STEP)
        _write_height_to_file(CUR_HEIGHT_STEP)

    print(f"[ACTUATOR] ↓ Current step: {CUR_HEIGHT_STEP}/{HEIGHT_MAX}")
    GPIO.output(ENA, GPIO.LOW)

# 4️⃣ ν”„λ‘œκ·Έλž¨ μ’…λ£Œ μ‹œ μžλ™ 원점 볡귀
def return_to_start():
    """ν˜„μž¬ μœ„μΉ˜λ§ŒνΌ ν•˜κ°•ν•˜μ—¬ 기계 원점(0) 볡귀"""
    global CUR_HEIGHT_STEP

    if CUR_HEIGHT_STEP <= 0:
        print("[ACTUATOR] Already at home position (0 step)")
        return

    print(f"[ACTUATOR] 🏁 Returning to home: {CUR_HEIGHT_STEP} steps down...")
    GPIO.output(ENA, GPIO.HIGH)
    GPIO.output(DIR, GPIO.HIGH)  # ν•˜κ°• λ°©ν–₯

    for i in range(CUR_HEIGHT_STEP):
        GPIO.output(PUL, GPIO.HIGH)
        sleep(STEP_DELAY)
        GPIO.output(PUL, GPIO.LOW)
        sleep(STEP_DELAY)
        _write_height_to_file(CUR_HEIGHT_STEP - i - 1)

        if i % 100 == 0 and i > 0:
            print(f"   ↓ μ§„ν–‰: {i}/{CUR_HEIGHT_STEP}")

    CUR_HEIGHT_STEP = 0
    _write_height_to_file(CUR_HEIGHT_STEP)
    GPIO.output(ENA, GPIO.LOW)
    print("[ACTUATOR] βœ… Returned to home position (step=0)")

def on_shutdown():
    """비정상 μ’…λ£Œ μ‹œμ—λ„ 원점 볡귀 보μž₯"""
    print("[SYSTEM] πŸ”» Returning actuator to 0...")
    return_to_start()
    GPIO.cleanup()
    print("[SYSTEM] βœ… Actuator returned to home position.")

atexit.register(on_shutdown)  # μ’…λ£Œ ν›… 등둝

5-4. PCA 기반 3차원 μ‹œμ„  좔적

  • PCA(μ£Όμ„±λΆ„ 뢄석)λ₯Ό 톡해 μ–Όκ΅΄μ˜ 3D μ’Œν‘œκ³„λ₯Ό μƒμ„±ν•˜κ³ , μ–‘μ•ˆ μ‹œμ„  벑터λ₯Ό μœ΅ν•©ν•˜μ—¬ ν™”λ©΄ μ’Œν‘œλ‘œ λ³€ν™˜ν•¨μœΌλ‘œμ¨ 고개 νšŒμ „μ—λ„ μ •ν™•ν•œ μ‹œμ„  좔적을 μˆ˜ν–‰ν•˜λŠ” 핡심 λ‘œμ§μž…λ‹ˆλ‹€.
  • μ°Έκ³  μ˜€ν”ˆμ†ŒμŠ€: https://github.com/JEOresearch/EyeTracker
# eye_tracking_worker.py - 3D κΈ°ν•˜ν•™ 기반 μ‹œμ„  좔적

# 1️⃣ PCA둜 μ–Όκ΅΄ 3D μ’Œν‘œκ³„ 생성
def compute_coordinate_box(face_landmarks, indices, w, h):
    # 23개 μ½” λžœλ“œλ§ˆν¬λ‘œ μ’Œν‘œκ³„ ꡬ성
    points_3d = np.array([
        [face_landmarks[i].x * w,
         face_landmarks[i].y * h,
         face_landmarks[i].z * w]
        for i in nose_indices  # 23개 점
    ])

    # μ£Όμ„±λΆ„ λΆ„μ„μœΌλ‘œ μ–Όκ΅΄ νšŒμ „ 계산
    center = np.mean(points_3d, axis=0)
    cov = np.cov((points_3d - center).T)
    eigvals, eigvecs = np.linalg.eigh(cov)

    # νšŒμ „ ν–‰λ ¬ 생성 (μ–Όκ΅΄μ˜ λ°©ν–₯)
    R_final = Rscipy.from_matrix(eigvecs).as_matrix()
    return center, R_final, points_3d

# 2️⃣ μ–‘μ•ˆ μ‹œμ„  벑터 μœ΅ν•©
def track_gaze():
    # 쒌우 눈의 μ‹œμ„  λ°©ν–₯ 계산
    left_gaze_dir = iris_3d_left - sphere_world_l
    right_gaze_dir = iris_3d_right - sphere_world_r

    # 두 눈의 ν‰κ· μœΌλ‘œ μ΅œμ’… μ‹œμ„  κ²°μ •
    combined_direction = (left_gaze_dir + right_gaze_dir) / 2
    combined_direction /= np.linalg.norm(combined_direction)

    # μ‹œμ„  λ°©ν–₯ β†’ ν™”λ©΄ μ’Œν‘œ λ³€ν™˜
    screen_x, screen_y = convert_gaze_to_screen_coordinates(
        combined_direction,
        calibration_offset_yaw,   # μ‚¬μš©μžλ³„ 보정값
        calibration_offset_pitch
    )

# 3️⃣ μ‚¬μš©μžλ³„ μΊ˜λ¦¬λΈŒλ ˆμ΄μ…˜
def calibrate():
    # ν˜„μž¬ 눈 μœ„μΉ˜λ₯Ό κΈ°μ€€μ μœΌλ‘œ μ €μž₯
    left_sphere_local_offset = R_final.T @ (iris - head_center)
    left_sphere_local_offset += base_radius * camera_dir_local

    # ν™”λ©΄ 쀑앙을 보고 μžˆλ‹€κ³  κ°€μ •ν•˜κ³  μ˜€ν”„μ…‹ 계산
    calibration_offset_yaw = -raw_yaw
    calibration_offset_pitch = -raw_pitch

5-5. LLM 기반 챗봇 응닡 처리

  • μ‚¬μš©μž λ°œν™”λ₯Ό λ°›μ•„ Gemini LLM을 톡해 응닡을 μƒμ„±ν•˜κ³ , μ£Όλ¬Έ μš”μ²­κ³Ό 일반 λŒ€ν™”λ₯Ό κ΅¬λΆ„ν•˜μ—¬ μ²˜λ¦¬ν•˜λŠ” λ°±μ—”λ“œ 챗봇 μ„œλΉ„μŠ€μ˜ 핡심 λ‘œμ§μž…λ‹ˆλ‹€. MSA ꡬ쑰에 따라 μ£Όλ¬Έ λ°œμƒ μ‹œ Order Service와 ν†΅μ‹ ν•©λ‹ˆλ‹€.
// [핡심 둜직] ChatService.processChat()
// - conversationRepository: μ„Έμ…˜λ³„ λŒ€ν™” 기둝 쑰회 및 μ €μž₯
// - geminiPromptService: 메뉴 정보, λŒ€ν™” νžˆμŠ€ν† λ¦¬ 등을 μ‘°ν•©ν•˜μ—¬ LLM ν”„λ‘¬ν”„νŠΈ 생성
// - geminiClient: Google Gemini API 호좜
// - orderServiceClient: μ£Όλ¬Έ μš”μ²­ λ°œμƒ μ‹œ μ™ΈλΆ€ Order Service API 호좜

@Transactional
public ChatResponse processChat(Long storeId, String sessionId, String userMessage, String managedStoreIds, String storeName) {

    // 1️⃣ λŒ€ν™” 기둝 쑰회 λ˜λŠ” 생성 (μ„Έμ…˜ 기반 λŒ€ν™” 관리)
    Conversation conversation = conversationRepository.findBySessionId(sessionId)
            .orElseGet(() -> new Conversation(sessionId));

    // 2️⃣ ν˜„μž¬ μ‚¬μš©μž λ©”μ‹œμ§€λ₯Ό λŒ€ν™” 기둝에 μΆ”κ°€
    conversation.addMessage(Message.of("USER", userMessage));

    // 3️⃣ Gemini에 보낼 ν”„λ‘¬ν”„νŠΈ 생성 (μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ + 메뉴 데이터 + λŒ€ν™” νžˆμŠ€ν† λ¦¬)
    String prompt = geminiPromptService.createPrompt(storeId, conversation, managedStoreIds);

    // 4️⃣ Gemini API ν˜ΈμΆœν•˜μ—¬ AI의 원본 응닡 λ°›κΈ°
    GeminiResponse geminiResponse = geminiClient.call(new GeminiRequest(prompt));
    String aiRawResponse = geminiResponse.extractText();

    // 5️⃣ AI 응닡 뢄석 ν›„ μ΅œμ’… λ©”μ‹œμ§€ κ²°μ •
    String finalAiMessage;
    Optional<OrderRequestDto> orderRequestOpt = parseOrderAction(aiRawResponse, storeId, storeName);

    if (orderRequestOpt.isPresent()) {
        // 5-1. μ£Όλ¬Έ μš”μ²­μΈ 경우: Order Service 호좜
        OrderRequestDto orderRequest = orderRequestOpt.get();

        try {
            var orderApiResponse = orderServiceClient.placeOrder(orderRequest);
            finalAiMessage = "주문이 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ£Όλ¬Έλ²ˆν˜ΈλŠ” " + orderApiResponse.getData().getOrderNumber() + "μž…λ‹ˆλ‹€.";
        } catch (Exception e) {
            finalAiMessage = "μ£Όλ¬Έ 처리 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. λ‹€μ‹œ μ‹œλ„ν•΄ μ£Όμ„Έμš”.";
        }
    } else {
        // 5-2. 일반 λŒ€ν™”μΈ 경우: Gemini 응닡 κ·ΈλŒ€λ‘œ μ‚¬μš©
        finalAiMessage = aiRawResponse;
    }

    // 6️⃣ μ΅œμ’… AI 응닡을 λŒ€ν™” 기둝에 μ €μž₯
    conversation.addMessage(Message.of("AI", finalAiMessage));
    conversationRepository.save(conversation);

    // 7️⃣ ν΄λΌμ΄μ–ΈνŠΈμ— 전달할 μ΅œμ’… 응닡 생성
    return new ChatResponse(conversation.getSessionId(), finalAiMessage);
}

πŸ“Š 6. λ°œν‘œμžλ£Œ

πŸ“„ IncluKiosk_Presentation.pdf

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors