신나서 쓰는 캡스톤 일지(밀린 포스팅은 담에 ㅎ)
다음 주 대면 회의 전까지 각자 미디어파이프 이용한 마우스 클릭 기능과 모델학습 해오기로 했다. 미디어파이프가 안되는 날 위해 현지가 오늘 데이터셋도 만들어주고.. 클릭 이벤트도 성공해따~.~ 우리 캡스톤은 미디어파이프랑 robotjs가 다하는듯
이전에 미디어파이프 포인트에 마우스 커서 연결한거 포스팅 👉🏻
[캡스톤 일지] ~ 10.16 robotjs 이용한 마우스 커서 이동 https://ijo0r98.github.io/posts/capstone5/
미디어파이프 랜드마크를 이용한 클릭 모션 정의는 현지 코드를 사용했고 나는 거기에 마우스 커서를 연결하고 클릭 모션을 취하면 클릭되도록 구현하였다.
전체 코드 위에서부터 차례대로 살펴보자
1. 랜드마크와 마우스 커서 연결
원래는 검지 손가락으로 마우스 커서를 조작한다는 의미로 landmark[8] 좌표를 이용하였으나, 모션을 취하면 검지손가락의 좌표값이 심하게 흔들려서 현지 의견대로 손바닥 위 랜드마크로 변경하였다. 어차피 미디어파이프 결과 캔버스는 짱 크고 손모양 없이 메뉴판만 보이기 때문에 어디에 커서가 붙던 주문자가 사용할 때 크게 상관없을 것 같다.
1
2
3
4
5
6
7
8
if (results.multiHandLandmarks) {
for (const landmarks of results.multiHandLandmarks) {
var x = width - width * landmarks[9]['x'];
var y = height * landmarks[9]['y'];
...
}
이렇게 손바닥 가운데에 위차한 9번 랜드마크를 이용하였고 x좌표의 경우 좌우반전때문에 전체 크기에서 해당 값을 빼주었다.
(이전 포스팅에도 적었는지 기억 안나는디) 미디어파이프는 랜드마크의 x, y값을 전체 캔버스에 대한 비율값으로 주기 때문에 x, y 각각 width와 height를 곱하였다. 하지만 여전히 내가 원하는 랜드마크와 내가 조작한 x, y의 좌표가 정확히 일치하지 않는 문제가 있어 차차 해결해봐야겠다ㅠ
여기서 width와 height는 전체 윈도우의 높이와 넓이이다.
1
2
const width = $(window).width();
const height = $(window).height();
2. 손가락 펼침 여부 확인
이전에 작성한 포스팅 👉🏻 [캡스톤일지] 09.24 서비스 기획과 미디어파이프 테스트 https://ijo0r98.github.io/posts/capstone1/
캡스톤 초기 찾아서 참고한 코드를 여전히 잘 활용하고있다.. 사실 그게 다임
1
2
const compareIndex = [[18, 4], [6, 8], [10, 12], [14, 16], [18, 20]];
var open = [false, false, false, false, false];
1
2
3
4
5
6
7
8
9
10
11
if (results.multiHandLandmarks) {
for (const landmarks of results.multiHandLandmarks) {
for (var i=0; i<5; i++) {
// true 펼쳐짐, false 접힘
open[i] = dist(landmarks[0]['x'], landmarks[0]['y'], landmarks[compareIndex[i][0]]['x'], landmarks[compareIndex[i][0]]['y'])
< dist(landmarks[0]['x'], landmarks[0]['y'], landmarks[compareIndex[i][1]]['x'], landmarks[compareIndex[i][1]]['y'])
}
...
}
이전 포스팅에 적은것처럼 손가락마다 두 포인트를 정해 손바닥과 각 포인트까지 거리를 구하고 비교하여 손가락이 접혔는지 펴졌는지 확인한다.
3. 지정된 제스처인지 확인
1
const gesture = [true, true, false, false, false, "index"];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (results.multiHandLandmarks) {
for (const landmarks of results.multiHandLandmarks) {
...
var flag = true;
for (var i=1; i<5; i++) {
if (gesture[i] != open[i]) {
flag = false
}
}
...
}
이 부분도 위의 포스팅과 마찬가지로 지정된 gesture와 비교하여 해당 포즈인지 아닌지 확인한다.
여기서 엄지손가락(open[0]) 부터 비교하지 않고 검지손가락(open[1])부터 비교한 이유는 엄지손가락의 펼침 여부는 2번의 코드로 잘 판단되지 않음 + 검지손가락 이용 시 엄지를 피고 이용하는 사용자도 있을 것으로 예상되기 때문이다.
중요한 것은 검지손가락만 펼쳐지고 엄지 제외 나머지 손가락들이 접혔는지! 근데 이 정의는 향후 서비스를 좀 더 다듬으면서 수정될 듯 하당
암튼 지정된 제스처와 다른 손가락이 있다면 flag가 false가 되며 지정된 제스처가 아니라고 알려줌
4. 마우스 커서 조작과 클릭
📄 order.html (미디어파이프 관련 스크립트는 모두 html에 포함되어있음, 클라이언트)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (results.multiHandLandmarks) {
for (const landmarks of results.multiHandLandmarks) {
...
if (flag == true) {
// 검지가 위를 향하고 있을 때만 동작하게 하여 검지가 아래를 향하고 있을 때는 동작하지 않게
if(getAngle(landmarks[8]['x'], landmarks[8]['y'],landmarks[6]['x'], landmarks[6]['y'])>0){
// 검지 끝이 검지 두 번째 마디보다 내려갔을 때(클릭 모션을 취했을 때)
if(getAngle(landmarks[7]['x'], landmarks[7]['y'],landmarks[8]['x'], landmarks[8]['y'])>0){
socket.emit('click', true); // 클릭 발생!
}
}
}
socket.emit('location', [x, y]); // 마우스 커서 이동
}
}
1
2
3
4
function getAngle(x1, y1, x2, y2){ // 두 점 사이의 각도를 반환
var rad = Math.atan2(y2-y1, x2-x1);
return (rad*180 / Math.PI);
}
검지손가락에 있는 랜드마크 6, 7, 8번들 사이 각도를 이용하여 접힘 여부를 손가락의 접힘 여부를 판단한다.
접힘이 판단되면 소켓으로 접혔다는 신호를 보낸다 (그냥 간단히 click이라는 이름의 boolean 값을 보냈다.. 완전 대충..)
📄 app.js (서버)
1
2
3
4
5
6
7
8
9
10
socket.on('location', (msg) => { // 마우스 커서 이동
var x = msg[0];
var y = msg[1];
robot.moveMouse(x, y);
});
socket.on('click', (flg) => { // 마우스 왼쪽 클릭
console.log('click!');
robot.mouseClick("left");
});
위에서 전달된 접힘 여부가 있으면 서버에서 rogotjs 모듈을 이용해 마우스 왼쪽 클릭을 발생시킨다.
전체 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function onResults(results) {
canvasCtx.save();
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
// canvasCtx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height);
if (results.multiHandLandmarks) {
for (const landmarks of results.multiHandLandmarks) {
var x = width - width * landmarks[9]['x'];
var y = height * landmarks[9]['y'];
for (var i=0; i<5; i++) {
open[i] = dist(landmarks[0]['x'], landmarks[0]['y'], landmarks[compareIndex[i][0]]['x'], landmarks[compareIndex[i][0]]['y'])
< dist(landmarks[0]['x'], landmarks[0]['y'], landmarks[compareIndex[i][1]]['x'], landmarks[compareIndex[i][1]]['y'])
}
var flag = true;
for (var i=1; i<5; i++) {
if (gesture[i] != open[i]) {
flag = false
}
}
if (flag == true) {
if(getAngle(landmarks[8]['x'], landmarks[8]['y'],landmarks[6]['x'], landmarks[6]['y'])>0){
if(getAngle(landmarks[7]['x'], landmarks[7]['y'],landmarks[8]['x'], landmarks[8]['y'])>0){
socket.emit('click', true);
// sleep(300);
}
}
}
socket.emit('location', [x, y]);
}
}
canvasCtx.restore();
}
나머지 코드도 확인하고 싶다면.. 우리의 깃을 확인해주세욥(11월 15일 아직 반영은 안되어있음)
📍 [GIT] web/public/order/order.ejs https://github.com/ijo0r98/capstone/tree/master/web
두둥 그냥 조작하는것처럼 보여도 .. 나름 마우스 하나도 안쓰고 전부 비접촉으로 허공에 손을 움직여 조작한거다!!!!!!
아직 클릭 모션에 대한 정의가 잘 내려지지 않아 클릭하는데 좀 어려움이 있지만 이는 모델학습과 새로운 코드로 수정하면 될 것 같다.
씐난당 우리에게 가장 중요했던 마우스 커서 이동과 클릭 이벤트를 모두 해결하여 기분이 너무 좋다 ~.~
이제 가벼운 마음으로 주간보고서 쓰러 가야쥐