Skip to content

Commit

Permalink
Merge pull request #15 from DDD-Community/feat#14
Browse files Browse the repository at this point in the history
[Feat#14] 스냅샷 가이드라인 기능 추가
  • Loading branch information
lkhoony authored Jul 29, 2024
2 parents 894e9dd + a543cc2 commit 7b717c1
Show file tree
Hide file tree
Showing 5 changed files with 365 additions and 9 deletions.
70 changes: 64 additions & 6 deletions src/components/PoseDetector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@ const PoseDetector: React.FC = () => {
const [isTextNeck, setIsTextNeck] = useState<boolean | null>(null)
const [isModelLoaded, setIsModelLoaded] = useState<boolean>(false)
const [mode, setMode] = useState<string>("snapshot")

const [canInit, setCanInit] = useState<boolean>(false)
const [isSnapSaved, setIsSnapSaved] = useState<boolean>(false);
const modelRef = useRef<any>(null)
const snapRef = useRef<pose[] | null>(null)
const resultRef = useRef<pose[] | null>(null)
const textNeckStartTime = useRef<number | null>(null)
const timer = useRef<any>(null)
const canvasRef = useRef<HTMLCanvasElement>(null)

const dx = useRef<number>(0)
const dy = useRef<number>(0)
const scale = useRef<number>(1)

const { requestNotificationPermission, showNotification } = usePushNotification()

const requestApi = (delay: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, delay))
Expand Down Expand Up @@ -64,10 +69,23 @@ const PoseDetector: React.FC = () => {
await window.ml5.setBackend("webgl")
}

const canInitCallback = (canInit : boolean) => {
setCanInit(canInit)
}

const detect = useCallback(
(results: pose[]): void => {
resultRef.current = results
if (canvasRef.current) drawPose(results, canvasRef.current)
if (canvasRef.current) {
drawPose(
results,
canvasRef.current,
dx.current,
dy.current,
scale.current,
canInitCallback,
!!snapRef.current)
}
if (snapRef.current) {
const _slope = detectSlope(snapRef.current, results, mode === "snapshot")
const _isTextNeck = detectTextNeck(snapRef.current, results, mode === "snapshot")
Expand Down Expand Up @@ -108,7 +126,10 @@ const PoseDetector: React.FC = () => {
)

const getInitSnap = (): void => {
if (modelRef && modelRef.current) snapRef.current = resultRef.current
if (modelRef && modelRef.current) {
snapRef.current = resultRef.current
setIsSnapSaved(true)
}
}

useEffect(() => {
Expand All @@ -129,6 +150,8 @@ const PoseDetector: React.FC = () => {
setIsTextNeck(null)
setSlope(null)
snapRef.current = null
setIsSnapSaved(false)
setCanInit(false)
}

const onChangeMode = (e: React.ChangeEvent<HTMLSelectElement>) => {
Expand All @@ -138,6 +161,25 @@ const PoseDetector: React.FC = () => {
}
}

const onChangeTranslation = (e: React.ChangeEvent<HTMLInputElement>) => {
const id = e.target.id ;
if(e.target.value){
const value = Number.parseInt(e.target.value)
switch(id){
case 'vertical' :
dy.current = value
return
case 'horizontal' :
dx.current = value
return
case 'scale' :
scale.current = value / 100 * 2
return
default :
}
}
}

const onCancelAutoPoseMonitoring = () => {
initializePoseMonitoring()
}
Expand All @@ -164,6 +206,14 @@ const PoseDetector: React.FC = () => {
/>
{isModelLoaded && (
<>
<div>
<div>좌우 이동</div>
<input id="horizontal" type="range" min={-100} max={100} onChange={onChangeTranslation}></input>
<div>상하 이동</div>
<input id="vertical" type="range" min={-100} max={100} onChange={onChangeTranslation}></input>
<div>크기 변경</div>
<input id="scale" type="range" min={0} max={100} onChange={onChangeTranslation}></input>
</div>
<div className="font-bold text-red-500">본 화면은 좌우가 반대로 보이고 있으니 주의하세요!</div>
<div>
<select className="rounded border border-gray-400 bg-white p-2" onChange={onChangeMode}>
Expand All @@ -177,9 +227,17 @@ const PoseDetector: React.FC = () => {
스냅샷 모드입니다. 올바른 자세를 하신 후에, 버튼을 눌러 촬영을 하면 해당 자세를 기준으로 부적절한
자세를 추적합니다!
</div>
<button className="rounded bg-blue-500 px-4 py-2 font-bold text-white" onClick={getInitSnap}>
올바른 자세를 촬영한 후 자세 측정 시작!
</button>
{
canInit ?
isSnapSaved ?
<button className="rounded bg-blue-500 px-4 py-2 font-bold text-white" onClick={initializePoseMonitoring}>
스냅샷 다시 찍기
</button> :
<button className="rounded bg-blue-500 px-4 py-2 font-bold text-white" onClick={getInitSnap}>
올바른 자세를 촬영한 후 자세 측정 시작!
</button> :
<div className="font-bold text-red-500">스냅샷을 찍을 수 없습니다. 가이드 라인에 맞게 자세를 잡아주세요.</div>
}
</>
)}
{mode === "skeleton" && (
Expand Down
67 changes: 67 additions & 0 deletions src/utils/calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,70 @@ export const getMidPoint = (p1: point, p2: point): point => {

return { x, y };
};

/**
* x=axisX를 기준으로 point를 대칭 이동 시킴
*
* @param {point} point
* @param {number} axisX
*
*/
export const getSymmetricPointFromX = (point : point, axisX : number) => {
return {
x: axisX + (axisX - point.x),
y: point.y
};
}

/**
* y=axisY를 기준으로 point를 대칭 이동 시킴
*
* @param {point} point
* @param {number} axisY
*
*/
export const getSymmetricPointFromY = (point : point, axisY : number) => {
return {
x: point.x,
y: axisY + (axisY -point.y)
};
}

/**
* dx, dy 만큼 point를 이동 시킴
*
* @param {point} point
* @param {number} dx
* @param {number} dy
*
*/
export const getTranslatedPoint = (point : point, dx : number, dy : number) => {
return {
x: point.x + dx,
y: point.y + dy
}
}

/**
* cx, cy를 기준으로 scaleFactor 만큼 point의 스케일을 변경
*
* @param {point} point
* @param {number} cx
* @param {number} cy
* @param {number} scaleFactor
*
*/
export const getScaledPoint = (point : point, cx : number, cy : number, scaleFactor : number) => {
const x = point.x
const y = point.y

// 기준점으로부터의 상대적인 거리 계산
const dx = x - cx
const dy = y - cy

// 스케일 팩터를 적용한 새로운 거리 계산
const scaledX = cx + dx * scaleFactor
const scaledY = cy + dy * scaleFactor

return { x: scaledX, y: scaledY }
}
42 changes: 39 additions & 3 deletions src/utils/drawer.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,51 @@
import type { pose } from "@/utils/detector"
import { getSymmetricPointFromY, getTranslatedPoint, getScaledPoint } from "./calculator";
import { guideLinePoints } from "./guideLine";

export const drawPose = (poses: pose[], canvas: HTMLCanvasElement, dx : number = 0, dy : number = 0, scale : number = 1, canInitCallback : (canInit : boolean) => void, isInit : boolean) : void => {

export const drawPose = (poses: pose[], canvas: HTMLCanvasElement): void => {
const ctx = canvas.getContext("2d")

if (ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height)

const guideLine = new Path2D()
const origin = {x : canvas.width / 2, y : canvas.height}

// canavs 좌표계 기준으로 가이드 라인 정렬 (가이드 라인의 기준은 (canvas.width / 2, canvas.height))
let _guideLinePoints = guideLinePoints.map(point => getSymmetricPointFromY(point,canvas.height / 2.))
if(dx!==0 || dy!==0) _guideLinePoints = _guideLinePoints.map(point => getTranslatedPoint(point,dx,dy))
if(scale!==1) _guideLinePoints = _guideLinePoints.map(point=>getScaledPoint(point, origin.x, origin.y, scale))
guideLine.moveTo(_guideLinePoints[0].x, _guideLinePoints[0].y)
for (let i = 1; i < _guideLinePoints.length; i++) {
guideLine.lineTo(_guideLinePoints[i].x, _guideLinePoints[i].y)
}
guideLine.closePath()
ctx.strokeStyle = 'blue'
ctx.fillStyle = 'rgba(0, 0, 255, 0.3)';
ctx.stroke(guideLine)
ctx.fill(guideLine)

poses.forEach((pose) => {
// 왼쪽과 오른쪽 어깨 이어주는 선 그리기
const leftShoulder = pose.keypoints.find((kp) => kp.name === "left_shoulder")
const rightShoulder = pose.keypoints.find((kp) => kp.name === "right_shoulder")
const leftEar = pose.keypoints.find((kp) => kp.name === "left_ear")
const rightEar = pose.keypoints.find((kp) => kp.name === "right_ear")

//
if(leftShoulder && rightShoulder && leftEar && rightEar){
if(!isInit){
if(
ctx.isPointInPath(guideLine,leftShoulder?.x, leftShoulder?.y) &&
ctx.isPointInPath(guideLine,rightShoulder?.x, rightShoulder?.y) &&
ctx.isPointInPath(guideLine,leftEar?.x, leftEar?.y) &&
ctx.isPointInPath(guideLine,rightEar?.x, rightEar?.y)
) canInitCallback(true)
else canInitCallback(false)
}
}

// 왼쪽과 오른쪽 어깨 이어주는 선 그리기
if (leftShoulder && rightShoulder && leftShoulder.confidence > 0.2 && rightShoulder.confidence > 0.2) {
ctx.beginPath()
ctx.moveTo(leftShoulder.x, leftShoulder.y)
Expand All @@ -29,4 +65,4 @@ export const drawPose = (poses: pose[], canvas: HTMLCanvasElement): void => {
})
})
}
}
}
Loading

0 comments on commit 7b717c1

Please sign in to comment.