1
- import { useState , useEffect , useRef } from "react"
1
+ import { useState , useEffect , useRef , useCallback } from "react"
2
2
import { Camera } from "."
3
3
import type { pose } from "@/utils/detector"
4
4
import { detectSlope , detectTextNeck } from "@/utils/detector"
5
5
import { drawPose } from "@/utils/drawer"
6
6
import usePushNotification from "@/hooks/usePushNotification"
7
7
import { worker } from "@/utils/worker"
8
8
9
- declare let ml5 : any
10
-
11
9
const PoseDetector : React . FC = ( ) => {
12
10
const [ isScriptLoaded , setIsScriptLoaded ] = useState < boolean > ( false )
13
11
const [ isScriptError , setIsScriptError ] = useState < boolean > ( false )
@@ -28,7 +26,7 @@ const PoseDetector: React.FC = () => {
28
26
const requestApi = ( delay : number ) : Promise < void > => new Promise ( ( resolve ) => setTimeout ( resolve , delay ) )
29
27
30
28
const setup = async ( ) : Promise < void > => {
31
- ml5 . bodyPose (
29
+ window . ml5 . bodyPose (
32
30
"MoveNet" ,
33
31
{
34
32
modelType : "SINGLEPOSE_THUNDER" ,
@@ -63,45 +61,51 @@ const PoseDetector: React.FC = () => {
63
61
64
62
// webgl설정
65
63
const initializeBackend = async ( ) : Promise < void > => {
66
- await ml5 . setBackend ( "webgl" )
64
+ await window . ml5 . setBackend ( "webgl" )
67
65
}
68
66
69
- const detect = ( results : pose [ ] ) : void => {
70
- resultRef . current = results
71
- if ( canvasRef . current ) drawPose ( results , canvasRef . current )
72
- if ( snapRef . current ) {
73
- const _slope = detectSlope ( snapRef . current , results , mode === "snapshot" )
74
- const _isTextNeck = detectTextNeck ( snapRef . current , results )
75
- if ( _slope !== null ) setSlope ( _slope )
76
- if ( _isTextNeck !== null ) setIsTextNeck ( _isTextNeck )
77
-
78
- if ( _isTextNeck ) {
79
- if ( ! textNeckStartTime || ! textNeckStartTime . current ) {
80
- textNeckStartTime . current = Date . now ( )
81
- // 거북목 자세 3초 유지 시, api 요청을 보내게 (콘솔 로그에서 확인)
82
- } else if ( Date . now ( ) - textNeckStartTime . current >= 3000 ) {
83
- if ( ! timer . current ) {
84
- timer . current = setInterval ( ( ) => {
85
- requestApi ( 1000 ) . then ( ( ) => console . log ( "api request" ) )
86
- showNotification ( )
87
- } , 2000 )
67
+ const detect = useCallback (
68
+ ( results : pose [ ] ) : void => {
69
+ resultRef . current = results
70
+ if ( canvasRef . current ) drawPose ( results , canvasRef . current )
71
+ if ( snapRef . current ) {
72
+ const _slope = detectSlope ( snapRef . current , results , mode === "snapshot" )
73
+ const _isTextNeck = detectTextNeck ( snapRef . current , results , mode === "snapshot" )
74
+ if ( _slope !== null ) setSlope ( _slope )
75
+ if ( _isTextNeck !== null ) setIsTextNeck ( _isTextNeck )
76
+
77
+ if ( _isTextNeck ) {
78
+ if ( ! textNeckStartTime || ! textNeckStartTime . current ) {
79
+ textNeckStartTime . current = Date . now ( )
80
+ // 거북목 자세 3초 유지 시, api 요청을 보내게 (콘솔 로그에서 확인)
81
+ } else if ( Date . now ( ) - textNeckStartTime . current >= 3000 ) {
82
+ if ( ! timer . current ) {
83
+ timer . current = setInterval ( ( ) => {
84
+ requestApi ( 1000 ) . then ( ( ) => console . log ( "api request" ) )
85
+ showNotification ( )
86
+ } , 2000 )
87
+ }
88
88
}
89
+ } else {
90
+ clearInterval ( timer . current )
91
+ timer . current = null
92
+ textNeckStartTime . current = null
89
93
}
90
- } else {
91
- clearInterval ( timer . current )
92
- timer . current = null
93
- textNeckStartTime . current = null
94
94
}
95
- }
96
- }
95
+ } ,
96
+ [ mode , setSlope , setIsTextNeck , showNotification ]
97
+ )
97
98
98
- const detectStart = async ( video : HTMLVideoElement ) : Promise < void > => {
99
- worker . onmessage = ( { } : any ) => {
100
- if ( modelRef . current ) {
101
- modelRef . current . detect ( video , detect )
99
+ const detectStart = useCallback (
100
+ async ( video : HTMLVideoElement ) : Promise < void > => {
101
+ worker . onmessage = ( { data } : any ) => {
102
+ if ( modelRef . current ) {
103
+ modelRef . current . detect ( video , detect )
104
+ }
102
105
}
103
- }
104
- }
106
+ } ,
107
+ [ detect ]
108
+ )
105
109
106
110
const getInitSnap = ( ) : void => {
107
111
if ( modelRef && modelRef . current ) snapRef . current = resultRef . current
@@ -112,12 +116,32 @@ const PoseDetector: React.FC = () => {
112
116
getScript ( )
113
117
} , [ ] )
114
118
119
+ useEffect ( ( ) => {
120
+ if ( isModelLoaded ) {
121
+ const video = document . querySelector ( "video" )
122
+ if ( video ) {
123
+ detectStart ( video )
124
+ }
125
+ }
126
+ } , [ isModelLoaded , detectStart ] )
127
+
128
+ const initializePoseMonitoring = ( ) => {
129
+ setIsTextNeck ( null )
130
+ setSlope ( null )
131
+ snapRef . current = null
132
+ }
133
+
115
134
const onChangeMode = ( e : React . ChangeEvent < HTMLSelectElement > ) => {
116
135
if ( e . target . value ) {
117
136
setMode ( e . target . value )
137
+ initializePoseMonitoring ( )
118
138
}
119
139
}
120
140
141
+ const onCancelAutoPoseMonitoring = ( ) => {
142
+ initializePoseMonitoring ( )
143
+ }
144
+
121
145
return (
122
146
< div >
123
147
{ isScriptError ? (
@@ -141,53 +165,56 @@ const PoseDetector: React.FC = () => {
141
165
{ isModelLoaded && (
142
166
< >
143
167
< div className = "font-bold text-red-500" > 본 화면은 좌우가 반대로 보이고 있으니 주의하세요!</ div >
144
- < div className = "w-1/2" >
145
- < select
146
- className = "w-full appearance-none rounded border border-gray-400 bg-white p-2"
147
- onChange = { onChangeMode }
148
- >
168
+ < div >
169
+ < select className = "rounded border border-gray-400 bg-white p-2" onChange = { onChangeMode } >
149
170
< option value = { "snapshot" } > 스냅샷 모드 (올바른 자세 촬영 후, 해당 자세 기준으로 측정)</ option >
150
- < option value = { "skeleton" } > 스켈레톤 모드 (올바른 자세 제시 후, 해당 자세 기준으로 측정)</ option >
171
+ < option value = { "skeleton" } > 자동 모드 (올바른 자세 기준으로 측정)</ option >
151
172
</ select >
152
173
</ div >
153
174
{ mode === "snapshot" && (
154
175
< >
155
- < button
156
- className = "rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700"
157
- onClick = { getInitSnap }
158
- >
176
+ < div className = "p-10 font-bold" >
177
+ 스냅샷 모드입니다. 올바른 자세를 하신 후에, 버튼을 눌러 촬영을 하면 해당 자세를 기준으로 부적절한
178
+ 자세를 추적합니다!
179
+ </ div >
180
+ < button className = "rounded bg-blue-500 px-4 py-2 font-bold text-white" onClick = { getInitSnap } >
159
181
올바른 자세를 촬영한 후 자세 측정 시작!
160
182
</ button >
161
- < div >
162
- 거북목 상태:
163
- { isTextNeck === null
164
- ? "상태를 확인할 수 없습니다."
165
- : isTextNeck
166
- ? "거북목 상태 입니다"
167
- : "정상적인 자세 입니다" }
168
- </ div >
169
- < div > 어깨 기울기: { slope === null ? "상태를 확인할 수 없습니다." : slope } </ div >
170
183
</ >
171
184
) }
172
185
{ mode === "skeleton" && (
173
186
< >
174
- < button
175
- className = "rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700"
176
- onClick = { getInitSnap }
177
- >
178
- 기준 그림에 몸을 맞춘 후 측정 시작!
179
- </ button >
180
- < div >
181
- 거북목 상태:
182
- { isTextNeck === null
183
- ? "상태를 확인할 수 없습니다."
184
- : isTextNeck
185
- ? "거북목 상태 입니다"
186
- : "정상적인 자세 입니다" }
187
+ < div className = "p-10 font-bold" > 자동 모드입니다. 자동으로 부적절한 자세를 추적합니다.</ div >
188
+ < div className = "flex gap-10" >
189
+ < button
190
+ className = "rounded bg-blue-500 px-4 py-2 font-bold text-white disabled:bg-gray-400"
191
+ onClick = { getInitSnap }
192
+ // disabled={snapShoptPose.length > 0}
193
+ >
194
+ 자세 모니터링 시작!
195
+ </ button >
196
+ < button
197
+ className = "rounded bg-red-500 px-4 py-2 font-bold text-white disabled:bg-gray-400"
198
+ onClick = { onCancelAutoPoseMonitoring }
199
+ // disabled={snapShoptPose.length === 0}
200
+ >
201
+ 자세 모니터링 취소
202
+ </ button >
187
203
</ div >
188
- < div > 어깨 기울기: { slope === null ? "상태를 확인할 수 없습니다." : slope } </ div >
189
204
</ >
190
205
) }
206
+
207
+ < div >
208
+ 거북목 상태:
209
+ { isTextNeck === null ? (
210
+ "상태를 확인할 수 없습니다."
211
+ ) : isTextNeck ? (
212
+ < span className = "font-extrabold text-red-500" > "거북목 상태 입니다"</ span >
213
+ ) : (
214
+ "정상적인 자세 입니다"
215
+ ) }
216
+ </ div >
217
+ < div > 어깨 기울기: { slope === null ? "상태를 확인할 수 없습니다." : slope } </ div >
191
218
</ >
192
219
) }
193
220
</ >
0 commit comments