diff --git a/src/components/PoseDetector.tsx b/src/components/PoseDetector.tsx index 181f0a5..98b4d06 100644 --- a/src/components/PoseDetector.tsx +++ b/src/components/PoseDetector.tsx @@ -1,6 +1,6 @@ import usePushNotification from "@/hooks/usePushNotification" import type { pose } from "@/utils/detector" -import { detectSlope, detectTextNeck } from "@/utils/detector" +import { detectHandOnChin, detectSlope, detectTextNeck } from "@/utils/detector" import { drawPose } from "@/utils/drawer" import { worker } from "@/utils/worker" import { useCallback, useEffect, useRef, useState } from "react" @@ -19,6 +19,7 @@ const PoseDetector: React.FC = () => { const [isScriptError, setIsScriptError] = useState(false) const [isTextNeck, setIsTextNeck] = useState(null) const [isShoulderTwist, setIsShoulderTwist] = useState(null) + const [isHandOnChin, setIsHandOnChin] = useState(null) const [isModelLoaded, setIsModelLoaded] = useState(false) const [isSnapSaved, setIsSnapSaved] = useState(false) const [isPopupVisible, setIsPopupVisible] = useState(false) @@ -28,7 +29,7 @@ const PoseDetector: React.FC = () => { const turtleNeckTimer = useRef(null) const shoulderTwistTimer = useRef(null) - // const chinUtpTimer = useRef(null) + const chinUtpTimer = useRef(null) // const tailboneSit = useRef(null) const canvasRef = useRef(null) @@ -89,12 +90,14 @@ const PoseDetector: React.FC = () => { if (snapRef.current) { const _isShoulderTwist = detectSlope(snapRef.current, results, false) const _isTextNeck = detectTextNeck(snapRef.current, results, true) + const _isHandOnChin = detectHandOnChin(results) if (_isShoulderTwist !== null) setIsShoulderTwist(_isShoulderTwist) if (_isTextNeck !== null) setIsTextNeck(_isTextNeck) + if (_isHandOnChin !== null) setIsHandOnChin(_isHandOnChin) } }, - [setIsShoulderTwist, setIsTextNeck, showNotification] + [setIsShoulderTwist, setIsTextNeck, setIsHandOnChin, showNotification] ) const detectStart = useCallback( @@ -139,8 +142,12 @@ const PoseDetector: React.FC = () => { } } - const getIsRight = (_isShoulderTwist: boolean | null, _isTextNeck: boolean | null): boolean => { - if (!_isShoulderTwist && !_isTextNeck) return true + const getIsRight = ( + _isShoulderTwist: boolean | null, + _isTextNeck: boolean | null, + _isHandOnChin: boolean | null + ): boolean => { + if (!_isShoulderTwist && !_isTextNeck && !_isHandOnChin) return true return false } @@ -166,6 +173,7 @@ const PoseDetector: React.FC = () => { usePoseTimer(isTextNeck, "TURTLE_NECK", turtleNeckTimer) usePoseTimer(isShoulderTwist, "SHOULDER_TWIST", shoulderTwistTimer) + usePoseTimer(isHandOnChin, "CHIN_UTP", chinUtpTimer) useEffect(() => { requestNotificationPermission() @@ -209,7 +217,7 @@ const PoseDetector: React.FC = () => {
{!isSnapSaved ? "바른 자세를 취한 후, 하단의 버튼을 눌러주세요." - : getIsRight(isShoulderTwist, isTextNeck) + : getIsRight(isShoulderTwist, isTextNeck, isHandOnChin) ? "올바른 자세입니다." : "올바르지 않은 자세입니다."}
diff --git a/src/components/Posture/PostrueCrew.tsx b/src/components/Posture/PostrueCrew.tsx index 13462a8..d5ba39d 100644 --- a/src/components/Posture/PostrueCrew.tsx +++ b/src/components/Posture/PostrueCrew.tsx @@ -4,6 +4,7 @@ import PostureGuide from "@assets/icons/posture-guide-button-icon.svg?react" import RankingGuideToolTip from "@assets/images/ranking-guide.png" import { useEffect, useState } from "react" import SelectBox from "@components/SelectBox" +import { useAuthStore } from "@/store" interface IPostureCrew { groupUserId: number @@ -19,15 +20,14 @@ interface PostureCrewProps { export default function PostrueCrew(props: PostureCrewProps) { const { toggleSidebar } = props + const accessToken = useAuthStore((state) => state.accessToken) const [crews, setCrews] = useState([]) const [isConnected, setIsConnected] = useState<"loading" | "success" | "disconnected">("loading") const [isEnabled, setIsEnabled] = useState(true) const [notiAlarmTime, setNotiAlarmTime] = useState("틀어진 즉시") useEffect(() => { - const token = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJoZXJvLWFsaWdubGFiLWFwaSIsImF1ZCI6Imhlcm8tYWxpZ25sYWItYXBpIiwiaWQiOjIwMDAwMSwidHlwZSI6ImFjY2Vzc1Rva2VuIiwiZXhwIjoxNzM1Mzk4MDAwfQ.pIl87yrMX4EVoLlBOG0A2X5AMRRUXalwMKnfH6cSDE8" - const socket = new WebSocket(`wss://api.alignlab.site/ws/v1/groups/1/users?X-HERO-AUTH-TOKEN=${token}`) + const socket = new WebSocket(`wss://api.alignlab.site/ws/v1/groups/1/users?X-HERO-AUTH-TOKEN=${accessToken}`) socket.onopen = () => { console.log("WebSocket connected") diff --git a/src/utils/detector.ts b/src/utils/detector.ts index daef4a8..4b72671 100644 --- a/src/utils/detector.ts +++ b/src/utils/detector.ts @@ -184,3 +184,47 @@ export const detectSlope = (refer: pose[], comp: pose[], isSnapShotMode = true): return true } } + +/** + * 손을 턱에 괴고 있는 자세를 감지하는 함수 + * @param poses 현재 포즈 데이터 배열 + * @returns 손을 턱에 대고 있으면 true, 아니면 false, 판단할 수 없으면 null + */ +export const detectHandOnChin = (poses: pose[]): boolean | null => { + if (!poses || poses.length === 0) return null + + // 필요한 키포인트 추출 + const nose = getXYfromPose(poses, "nose") + const leftEar = getXYfromPose(poses, "left_ear") + const rightEar = getXYfromPose(poses, "right_ear") + const leftWrist = getXYfromPose(poses, "left_wrist") + const rightWrist = getXYfromPose(poses, "right_wrist") + const leftShoulder = getXYfromPose(poses, "left_shoulder") + const rightShoulder = getXYfromPose(poses, "right_shoulder") + + // 키포인트가 없으면 null 반환 + if (!nose || !leftEar || !rightEar || !leftWrist || !rightWrist || !leftShoulder || !rightShoulder) return null + + // 턱의 위치를 추정 (코와 귀 중간점의 중간점) + const earMidpoint = getMidPoint(leftEar, rightEar) + const estimatedChin = getMidPoint(nose, earMidpoint) + + // 어깨 너비 계산 + const shoulderWidth = getDistance(leftShoulder, rightShoulder) + + // 턱 부근을 판단하기 위한 거리를 어깨 너비의 비율로 설정 + // 이 비율은 실제 테스트를 통해 조정이 필요할 수 있습니다. + const CHIN_PROXIMITY_RATIO = 0.5 // 어깨 너비의 25% + const chinProximityThreshold = shoulderWidth * CHIN_PROXIMITY_RATIO + + // 손목과 추정된 턱 위치 사이의 거리 계산 + const leftWristToChinDistance = getDistance(leftWrist, estimatedChin) + const rightWristToChinDistance = getDistance(rightWrist, estimatedChin) + + // 왼손이나 오른손 중 하나라도 턱 근처에 있으면 true 반환 + if (leftWristToChinDistance < chinProximityThreshold || rightWristToChinDistance < chinProximityThreshold) { + return true + } + + return false +}