# 原理

1664197622145

1664197768763

1664198117290

1664198135742

# 凸包算法

这里使用食指的闭合来做
实际测试中发现凸包算法不太行,当 8 和 6 在附近的时候,在检测的帧率较高且出错率较高的时候,对 食指伸出 这个检测结果暴增

比如当拳头向着摄像头的时候

# 距离算法

https://blog.csdn.net/qq_43550173/article/details/116273477

6 号点和 8 号点到 0 号点的距离来判断食指是否伸出还是闭合

# 凸包算法代码

调整了界面显示,加入控制摄像头打开和关闭的开关

<template>
    <div class="container">
        <el-card shadow="hover">
            <el-row>
                <el-col :span="24">
                    <div class="facedetect">
                        <!-- 注意下面的两个标签都设置镜像翻转 -->
                        <video ref="videoElement" style="transform:rotateY(180deg)"></video>
                        <canvas class="output_canvas" ref="canvasElement" :width="canvasWidth" :height="canvasHeight"
                            style="transform:rotateY(180deg)"></canvas>
                    </div>
                </el-col>
            </el-row>
            <el-row justify="space-around">
                <el-col :span="12" style="text-align:center;">
                    <el-button type="success" size="large" @click="startDetect">开始识别</el-button>
                </el-col>
                <el-col :span="12" style="text-align:center;">
                    <el-button type="primary" size="large" @click="stopDetect">停止识别</el-button>
                </el-col>
                <el-col style="text-align:center;">
                    <el-button type="info" size="large" @click="savePic">保存图片</el-button>
                </el-col>
            </el-row>
        </el-card>
    </div>
</template>
<script setup lang="ts" >
// 上面的 video 显示是摄像头的,canvas 是处理后的
import { Hands, HAND_CONNECTIONS, InputImage } from "@mediapipe/hands";
import { Camera } from "@mediapipe/camera_utils";
import { drawConnectors, drawLandmarks } from "@mediapipe/drawing_utils";
import { onMounted, ref, watch } from 'vue'
import { tr } from "element-plus/es/locale";
import axios from "axios";
import { useUsersStore } from '@/store/user';
import { useRouter } from "vue-router";
// 全局信息存储
const store = useUsersStore()
// 监听当前路由变化,关闭摄像头
const router = useRouter()
const videoElement = ref<HTMLVideoElement | null>(null)
const canvasElement = ref<HTMLCanvasElement | null>(null)
let camera: Camera
let canvasWidth = ref(500)
let canvasHeight = ref(500)
let preDetectTwo: boolean = false
// 检测是否在凸包点内的函数
function isInPolygon(checkPoint: number[], polygonPoints: number[][]): boolean {
    let counter = 0;
    let i: number;
    let xinters: number;
    let p1: number[], p2: number[];
    let pointCount = polygonPoints.length;
    p1 = polygonPoints[0];
    for (i = 1; i <= pointCount; i++) {
        p2 = polygonPoints[i % pointCount];
        if (
            checkPoint[0] > Math.min(p1[0], p2[0]) &&
            checkPoint[0] <= Math.max(p1[0], p2[0])
        ) {
            if (checkPoint[1] <= Math.max(p1[1], p2[1])) {
                if (p1[0] != p2[0]) {
                    xinters =
                        (checkPoint[0] - p1[0]) *
                        (p2[1] - p1[1]) /
                        (p2[0] - p1[0]) +
                        p1[1];
                    if (p1[1] == p2[1] || checkPoint[1] <= xinters) {
                        counter++;
                    }
                }
            }
        }
        p1 = p2;
    }
    if (counter % 2 == 0) {
        return false;
    } else {
        return true;
    }
}
onMounted(() => {
    const canvasCtx = canvasElement.value?.getContext("2d");
    function onResults(results: any) {
        canvasCtx?.save();
        canvasCtx?.clearRect(0, 0, canvasElement.value?.width as number, canvasElement.value?.height as number);
        canvasCtx?.drawImage(
            results?.image, 0, 0, canvasElement.value?.width as number, canvasElement.value?.height as number);
        if (results?.multiHandLandmarks) {
            // 这里的代码是同时绘制多只手的,到时候只绘制一只手吧
            for (const landmarks of results?.multiHandLandmarks) {
                if (landmarks == undefined || canvasCtx == undefined) {
                    continue
                }
                //console.log (landmarks);  // 数组,装着对象 {x, y, z}
                // console.log(results.image);
                // 下面添加检测手势的代码
                // 采集所有关键点的坐标
                let width = 1280, height = 720
                let list_lms: number[][] = []
                for (let i = 0; i < landmarks.length; i++) {
                    list_lms.push([landmarks[i]['x'] * canvasWidth.value, landmarks[i]['y'] * canvasHeight.value])
                }
                //console.log ("所有点为");
                // console.log(list_lms);
                // 凸包点
                let hull_list: number[][] = []
                let hull_index: number[] = [0, 1, 2, 3, 6, 10, 14, 19, 18, 17, 10]
                for (let i = 0; i < hull_index.length; i++) {
                    hull_list.push(list_lms[hull_index[i]])
                }
                // console.log("=======================\n");
                //console.log ("凸包点为");
                // console.log(hull_list);
                // 检测食指指尖是否在凸包内
                let res: boolean = isInPolygon(list_lms[8], hull_list)
                if (res == false && preDetectTwo == false) {
                    // 如果食指伸出,
                    preDetectTwo = true
                    console.log("食指伸出");
                } else if (res == false && preDetectTwo) {
                    // 食指继续伸出,不响应
                } else if (res == true) {
                    // 没有检测到,置为 false,无论上次是否检测到
                    preDetectTwo = false
                }
                // console.log(res);
                // if (res) {
                //     console.log ("在");
                // } else {
                //     console.log ("不在");
                // }
                // 上面是检测手势的代码
                drawConnectors(canvasCtx as CanvasRenderingContext2D, landmarks, HAND_CONNECTIONS,
                    { color: '#00FF00', lineWidth: 5 });
                drawLandmarks(canvasCtx as CanvasRenderingContext2D, landmarks, { color: '#FF0000', lineWidth: 2 });
            }
        }
        canvasCtx?.restore();
    }
    // 相机对象
    camera = new Camera(videoElement.value as HTMLVideoElement, {
        onFrame: async () => {
            await hands.send({ image: videoElement.value as InputImage });
        },
        width: canvasWidth.value,
        height: canvasHeight.value
    });
    const hands = new Hands({
        locateFile: (file) => {
            console.log("进入file");
            return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
        }
    });
    hands.setOptions({
        maxNumHands: 2,
        modelComplexity: 1,
        minDetectionConfidence: 0.5,
        minTrackingConfidence: 0.5
    });
    hands.onResults(onResults);
    // camera.start()
})
let isOn: boolean = false
const startDetect = () => {
    if (isOn) {
        return
    }
    isOn = true
    camera.start()
}
const stopDetect = () => {
    if (isOn == false) {
        return
    }
    isOn = false
    camera.stop()
}
watch(router.currentRoute, () => {
    if (isOn) {
        // 上面括号之前写的 camera
        camera.stop()
    }
})
const savePic = () => {
    // 要先判断 vedio 是否处于启动状态
    if (isOn) {
        let canvas = document.createElement('canvas')
        canvas.width = 500;
        canvas.height = 500;
        let ctx = canvas.getContext('2d');
        ctx?.drawImage(videoElement?.value as CanvasImageSource, 0, 0, canvas.width, canvas.height);
        // 生成图片 base64 编码
        let base64 = canvas.toDataURL('image/png')
        // console.log(base64);
    } else {
        // console.log("");
        alert("请先打开摄像头")
    }
}
</script>
<style scoped>
.container {
    display: flex;
    justify-content: center;
}
.facedetect {
    height: 500px;
    width: 1100px;
    display: flex;
    justify-content: space-around;
}
</style>

# 距离算法代码

<template>
    <div class="container">
        <el-card shadow="hover">
            <el-row>
                <el-col :span="24">
                    <div class="facedetect">
                        <!-- 注意下面的两个标签都设置镜像翻转 -->
                        <video ref="videoElement" style="transform:rotateY(180deg)"></video>
                        <canvas class="output_canvas" ref="canvasElement" :width="canvasWidth" :height="canvasHeight"
                            style="transform:rotateY(180deg)"></canvas>
                    </div>
                </el-col>
            </el-row>
            <el-row justify="space-around">
                <el-col :span="12" style="text-align:center;">
                    <el-button type="success" size="large" @click="startDetect">开始识别</el-button>
                </el-col>
                <el-col :span="12" style="text-align:center;">
                    <el-button type="primary" size="large" @click="stopDetect">停止识别</el-button>
                </el-col>
                <el-col style="text-align:center;">
                    <el-button type="info" size="large" @click="savePic">保存图片</el-button>
                </el-col>
            </el-row>
        </el-card>
    </div>
</template>
<script setup lang="ts" >
// 上面的 video 显示是摄像头的,canvas 是处理后的
import { Hands, HAND_CONNECTIONS, InputImage } from "@mediapipe/hands";
import { Camera } from "@mediapipe/camera_utils";
import { drawConnectors, drawLandmarks } from "@mediapipe/drawing_utils";
import { onMounted, ref, watch } from 'vue'
import { tr } from "element-plus/es/locale";
import axios from "axios";
import { useUsersStore } from '@/store/user';
import { useRouter } from "vue-router";
// 全局信息存储
const store = useUsersStore()
// 监听当前路由变化,关闭摄像头
const router = useRouter()
const videoElement = ref<HTMLVideoElement | null>(null)
const canvasElement = ref<HTMLCanvasElement | null>(null)
let camera: Camera
let canvasWidth = ref(500)
let canvasHeight = ref(500)
let preDetectTwo: boolean = false
let pre_one_finger: boolean = false
let pre_two_finger: boolean = false
// 检测是否在凸包点内的函数
function isInPolygon(checkPoint: number[], polygonPoints: number[][]): boolean {
    let counter = 0;
    let i: number;
    let xinters: number;
    let p1: number[], p2: number[];
    let pointCount = polygonPoints.length;
    p1 = polygonPoints[0];
    for (i = 1; i <= pointCount; i++) {
        p2 = polygonPoints[i % pointCount];
        if (
            checkPoint[0] > Math.min(p1[0], p2[0]) &&
            checkPoint[0] <= Math.max(p1[0], p2[0])
        ) {
            if (checkPoint[1] <= Math.max(p1[1], p2[1])) {
                if (p1[0] != p2[0]) {
                    xinters =
                        (checkPoint[0] - p1[0]) *
                        (p2[1] - p1[1]) /
                        (p2[0] - p1[0]) +
                        p1[1];
                    if (p1[1] == p2[1] || checkPoint[1] <= xinters) {
                        counter++;
                    }
                }
            }
        }
        p1 = p2;
    }
    if (counter % 2 == 0) {
        return false;
    } else {
        return true;
    }
}
onMounted(() => {
    const canvasCtx = canvasElement.value?.getContext("2d");
    function onResults(results: any) {
        canvasCtx?.save();
        canvasCtx?.clearRect(0, 0, canvasElement.value?.width as number, canvasElement.value?.height as number);
        canvasCtx?.drawImage(
            results?.image, 0, 0, canvasElement.value?.width as number, canvasElement.value?.height as number);
        if (results?.multiHandLandmarks) {
            // 这里的代码是同时绘制多只手的,到时候只绘制一只手吧
            for (const landmarks of results?.multiHandLandmarks) {
                if (landmarks == undefined || canvasCtx == undefined) {
                    continue
                }
                // 画关键点和连接线
                drawConnectors(canvasCtx as CanvasRenderingContext2D, landmarks, HAND_CONNECTIONS,
                    { color: '#00FF00', lineWidth: 5 });
                drawLandmarks(canvasCtx as CanvasRenderingContext2D, landmarks, { color: '#FF0000', lineWidth: 2 });
                //console.log (landmarks);  // 数组,装着对象 {x, y, z}
                // console.log(results.image);
                // 下面添加检测手势的代码
                // 采集所有关键点的坐标
                let list_lms: number[][] = []
                for (let i = 0; i < landmarks.length; i++) {
                    list_lms.push([landmarks[i]['x'] * canvasWidth.value, landmarks[i]['y'] * canvasHeight.value])
                }
                //console.log ("所有点为");
                // console.log(list_lms);
                let x0 = list_lms[0][0], y0 = list_lms[0][1]
                let x6 = list_lms[6][0], y6 = list_lms[6][1]
                let x8 = list_lms[8][0], y8 = list_lms[8][1]
                let x10 = list_lms[8][0], y10 = list_lms[8][1]
                let x12 = list_lms[12][0], y12 = list_lms[12][1]
                let dis06 = Math.hypot(x6 - x0, y6 - y0)
                let dis08 = Math.hypot(x8 - x0, y8 - y0)
                //console.log ("0 到 6 的距离是", dis06);
                //console.log ("0 到 8 的距离是", dis08);
                let two_out = dis06 < dis08
                let dis0_10 = Math.hypot(x10 - x0, y10 - y0)
                let dis0_12 = Math.hypot(x12 - x0, y12 - y0)
                let three_out = dis0_10 < dis0_12
                // console.log(pre_one_finger, two_out, three_out);
                if (pre_one_finger == false && two_out == true && three_out == false) {
                    pre_one_finger = true
                    pre_two_finger = false
                    console.log("伸出食指");
                    // 检测到一根手指
                    return
                    // 要不要设置 pre_two_finger 为 false 呢
                }
                else if (pre_one_finger == true && two_out == true && three_out == false) {
                    // 上次检测到一根手指,现在还是检测到一根手指,直接返回
                    return
                }
                else if (pre_two_finger == false && two_out == true && three_out == true) {
                    // 首次检测到两根手指
                    pre_one_finger = false
                    pre_two_finger = true
                    console.log("伸出食指和中指");
                    return
                } else if(pre_two_finger == true && two_out == true && three_out == true) {
                    // 上次检测到两根手指,现在还是检测到两根手指,直接返回
                    return
                }
                // if (res == false && preDetectTwo == false) {
                //     // 如果食指伸出,且上一次检测检测时食指未伸出
                //     preDetectTwo = true
                //     console.log ("食指伸出");
                // } else if (res == false && preDetectTwo) {
                //     // 食指继续伸出,不响应
                // } else if (res == true) {
                //     // 没有检测到,置为 false,无论上次是否检测到
                //     preDetectTwo = false
                // }
                // 下面的代码的凸包算法
                // 凸包点
                // let hull_list: number[][] = []
                // let hull_index: number[] = [0, 1, 2, 3, 6, 10, 14, 19, 18, 17, 10]
                // for (let i = 0; i < hull_index.length; i++) {
                //     hull_list.push(list_lms[hull_index[i]])
                // }
                // console.log("=======================\n");
                //console.log ("凸包点为");
                // console.log(hull_list);
                // 检测食指指尖是否在凸包内
                // let res: boolean = isInPolygon(list_lms[8], hull_list)
                // if (res == false && preDetectTwo == false) {
                //     // 如果食指伸出,且上一次检测检测时食指未伸出
                //     preDetectTwo = true
                //     console.log ("食指伸出");
                // } else if (res == false && preDetectTwo) {
                //     // 食指继续伸出,不响应
                // } else if (res == true) {
                //     // 没有检测到,置为 false,无论上次是否检测到
                //     preDetectTwo = false
                // }
                // console.log(res);
                // if (res) {
                //     console.log ("在");
                // } else {
                //     console.log ("不在");
                // }
                // 上面的代码的凸包算法
                // 上面是检测手势的代码
            }
        }
        canvasCtx?.restore();
    }
    // 相机对象
    camera = new Camera(videoElement.value as HTMLVideoElement, {
        onFrame: async () => {
            await hands.send({ image: videoElement.value as InputImage });
        },
        width: canvasWidth.value,
        height: canvasHeight.value
    });
    const hands = new Hands({
        locateFile: (file) => {
            console.log("进入file");
            return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
        }
    });
    hands.setOptions({
        maxNumHands: 2,
        modelComplexity: 1,
        minDetectionConfidence: 0.5,
        minTrackingConfidence: 0.5
    });
    hands.onResults(onResults);
    // camera.start()
})
let isOn: boolean = false
const startDetect = () => {
    if (isOn) {
        return
    }
    isOn = true
    camera.start()
}
const stopDetect = () => {
    if (isOn == false) {
        return
    }
    isOn = false
    camera.stop()
}
watch(router.currentRoute, () => {
    if (isOn) {
        // 上面括号之前写的 camera
        camera.stop()
    }
})
const savePic = () => {
    // 要先判断 vedio 是否处于启动状态
    if (isOn) {
        let canvas = document.createElement('canvas')
        canvas.width = 500;
        canvas.height = 500;
        let ctx = canvas.getContext('2d');
        ctx?.drawImage(videoElement?.value as CanvasImageSource, 0, 0, canvas.width, canvas.height);
        // 生成图片 base64 编码
        let base64 = canvas.toDataURL('image/png')
        // console.log(base64);
    } else {
        // console.log("");
        alert("请先打开摄像头")
    }
}
</script>
<style scoped>
.container {
    display: flex;
    justify-content: center;
}
.facedetect {
    height: 500px;
    width: 1100px;
    display: flex;
    justify-content: space-around;
}
</style>

# 距离算法 食指 四指

<template>
    <div class="container">
        <el-card shadow="hover">
            <el-row>
                <el-col :span="24">
                    <div class="facedetect">
                        <!-- 注意下面的两个标签都设置镜像翻转 -->
                        <video ref="videoElement" style="transform:rotateY(180deg)"></video>
                        <canvas class="output_canvas" ref="canvasElement" :width="canvasWidth" :height="canvasHeight"
                            style="transform:rotateY(180deg)"></canvas>
                    </div>
                </el-col>
            </el-row>
            <el-row justify="space-around">
                <el-col :span="12" style="text-align:center;">
                    <el-button type="success" size="large" @click="startDetect">开始识别</el-button>
                </el-col>
                <el-col :span="12" style="text-align:center;">
                    <el-button type="primary" size="large" @click="stopDetect">停止识别</el-button>
                </el-col>
                <el-col style="text-align:center;">
                    <el-button type="info" size="large" @click="savePic">保存图片</el-button>
                </el-col>
            </el-row>
            <el-row justify="space-around">
                <el-col :span="12" style="text-align:center;">
                    <!-- <el-button ref="leftbtn" type="success" size="large" @click="leftbtnclick"> 左按钮 & lt;/el-button> -->
                    <button ref="leftbtn" @click="leftbtnclick">左按钮</button>
                </el-col>
                <el-col :span="12" style="text-align:center;">
                    <!-- <el-button ref="rightbtn" type="primary" size="large" @click="rightbtnclick"> 右按钮 & lt;/el-button> -->
                    <button ref="rightbtn" @click="rightbtnclick">左按钮</button>
                </el-col>
            </el-row>
        </el-card>
    </div>
</template>
<script setup lang="ts" >
// 上面的 video 显示是摄像头的,canvas 是处理后的
import { Hands, HAND_CONNECTIONS, InputImage } from "@mediapipe/hands";
import { Camera } from "@mediapipe/camera_utils";
import { drawConnectors, drawLandmarks } from "@mediapipe/drawing_utils";
import { onMounted, ref, watch } from 'vue'
import { fa, tr } from "element-plus/es/locale";
import axios from "axios";
import { useUsersStore } from '@/store/user';
import { useRouter } from "vue-router";
/////////////////////////////////
const leftbtn = ref<HTMLButtonElement | null>(null)
const rightbtn = ref<HTMLButtonElement | null>(null)
const leftbtnclick = () => {
    console.log("左按钮被点击");
}
const rightbtnclick = () => {
    console.log("右按钮被点击");
}
////////////////////////////////
// 全局信息存储
const store = useUsersStore()
// 监听当前路由变化,关闭摄像头
const router = useRouter()
const videoElement = ref<HTMLVideoElement | null>(null)
const canvasElement = ref<HTMLCanvasElement | null>(null)
let camera: Camera
let canvasWidth = ref(500)
let canvasHeight = ref(500)
let preDetectTwo: boolean = false
let pre_one_finger: boolean = false
let pre_two_finger: boolean = false
let pre_four_finger: boolean = false
let exeBefore: boolean = false
// 检测是否在凸包点内的函数
function isInPolygon(checkPoint: number[], polygonPoints: number[][]): boolean {
    let counter = 0;
    let i: number;
    let xinters: number;
    let p1: number[], p2: number[];
    let pointCount = polygonPoints.length;
    p1 = polygonPoints[0];
    for (i = 1; i <= pointCount; i++) {
        p2 = polygonPoints[i % pointCount];
        if (
            checkPoint[0] > Math.min(p1[0], p2[0]) &&
            checkPoint[0] <= Math.max(p1[0], p2[0])
        ) {
            if (checkPoint[1] <= Math.max(p1[1], p2[1])) {
                if (p1[0] != p2[0]) {
                    xinters =
                        (checkPoint[0] - p1[0]) *
                        (p2[1] - p1[1]) /
                        (p2[0] - p1[0]) +
                        p1[1];
                    if (p1[1] == p2[1] || checkPoint[1] <= xinters) {
                        counter++;
                    }
                }
            }
        }
        p1 = p2;
    }
    if (counter % 2 == 0) {
        return false;
    } else {
        return true;
    }
}
onMounted(() => {
    const canvasCtx = canvasElement.value?.getContext("2d");
    function onResults(results: any) {
        canvasCtx?.save();
        canvasCtx?.clearRect(0, 0, canvasElement.value?.width as number, canvasElement.value?.height as number);
        canvasCtx?.drawImage(
            results?.image, 0, 0, canvasElement.value?.width as number, canvasElement.value?.height as number);
        if (results?.multiHandLandmarks) {
            // 这里的代码是同时绘制多只手的,到时候只绘制一只手吧
            for (const landmarks of results?.multiHandLandmarks) {
                if (landmarks == undefined || canvasCtx == undefined) {
                    continue
                }
                // 画关键点和连接线
                drawConnectors(canvasCtx as CanvasRenderingContext2D, landmarks, HAND_CONNECTIONS,
                    { color: '#00FF00', lineWidth: 5 });
                drawLandmarks(canvasCtx as CanvasRenderingContext2D, landmarks, { color: '#FF0000', lineWidth: 2 });
                //console.log (landmarks);  // 数组,装着对象 {x, y, z}
                // console.log(results.image);
                // 下面添加检测手势的代码
                // 采集所有关键点的坐标
                let list_lms: number[][] = []
                for (let i = 0; i < landmarks.length; i++) {
                    list_lms.push([landmarks[i]['x'] * canvasWidth.value, landmarks[i]['y'] * canvasHeight.value])
                }
                //console.log ("所有点为");
                // console.log(list_lms);
                let x0 = list_lms[0][0], y0 = list_lms[0][1]
                let x6 = list_lms[6][0], y6 = list_lms[6][1]
                let x8 = list_lms[8][0], y8 = list_lms[8][1]
                let x10 = list_lms[10][0], y10 = list_lms[10][1]
                let x12 = list_lms[12][0], y12 = list_lms[12][1]
                let x14 = list_lms[14][0], y14 = list_lms[14][1]
                let x16 = list_lms[16][0], y16 = list_lms[16][1]
                let x18 = list_lms[18][0], y18 = list_lms[18][1]
                let x20 = list_lms[20][0], y20 = list_lms[20][1]
                let dis06 = Math.hypot(x6 - x0, y6 - y0)
                let dis08 = Math.hypot(x8 - x0, y8 - y0)
                //console.log ("0 到 6 的距离是", dis06);
                //console.log ("0 到 8 的距离是", dis08);
                let two_out = dis06 < dis08
                let dis0_10 = Math.hypot(x10 - x0, y10 - y0)
                let dis0_12 = Math.hypot(x12 - x0, y12 - y0)
                let three_out = dis0_10 < dis0_12
                let dis0_14 = Math.hypot(x14 - x0, y14 - y0)
                let dis0_16 = Math.hypot(x16 - x0, y16 - y0)
                let dis0_18 = Math.hypot(x18 - x0, y18 - y0)
                let dis0_20 = Math.hypot(x20 - x0, y20 - y0)
                let one_finger = dis06 < dis08 && dis0_10 > dis0_12 && dis0_14 > dis0_16 && dis0_18 > dis0_20
                let four_finger = dis06 < dis08 && dis0_10 < dis0_12 && dis0_14 < dis0_16 && dis0_18 < dis0_20
                console.log(pre_one_finger, pre_four_finger, one_finger, four_finger);
                /**
                 * 在实际测试中发现,当上一步是伸出食指和中指,然后缩回来的过程中
                 * 发现会检测到食指伸出,因此下面加一个判断,当上一步是伸出食指和中指且
                 * 这次是伸出食指的时候直接返回,且要保证只执行一次
                 */
                // if(pre_one_finger == false && one_finger == true) {
                //     console.log ("伸出食指");
                //     pre_one_finger = true
                //     pre_four_finger = false
                //     return
                // }else if(pre_four_finger == false && four_finger == true) {
                //     console.log ("伸出四指");
                //     pre_four_finger = true
                //     pre_one_finger = false
                // }
                // else {
                //     // 这里有问题,
                //     pre_one_finger = false
                //     pre_four_finger = false
                // }
                
                // 食指
                if (pre_one_finger == false && one_finger == true && four_finger == false) {
                    console.log("伸出食指");
                    pre_one_finger = true
                    pre_four_finger = false
                    return
                } else if (pre_one_finger == true && one_finger == true) {
                    // return
                } else if(one_finger == false) {
                    pre_one_finger = false
                }
                // 四指
                if (pre_four_finger == false && four_finger == true && one_finger == false) {
                    console.log("伸出四指");
                    pre_four_finger = true
                    pre_one_finger = false
                    return
                } else if (pre_four_finger == true && four_finger == true) {
                    // return
                } else if(four_finger == false) {
                    pre_four_finger = false
                }
                /*
                if (pre_one_finger == false && two_out == true && three_out == false) {
                    pre_one_finger = true
                    pre_two_finger = false
                    console.log ("伸出食指");
                    console.log (leftbtn.value);
                    if (leftbtn.value instanceof HTMLButtonElement) {
                        leftbtn.value.click ()
                    }
                    // 检测到一根手指
                    return
                    // 要不要设置 pre_two_finger 为 false 呢
                }
                else if (pre_one_finger == true && two_out == true && three_out == false) {
                    // 上次检测到一根手指,现在还是检测到一根手指,直接返回
                    return
                }
                else if (pre_two_finger == false && two_out == true && three_out == true) {
                    // 首次检测到两根手指
                    pre_one_finger = false
                    pre_two_finger = true
                    //exeBefore = true
                    if (rightbtn.value instanceof HTMLButtonElement) {
                        rightbtn.value.click ()
                    }
                    console.log ("伸出食指和中指");
                    return
                } else if (pre_two_finger == true && two_out == true && three_out == true) {
                    // 上次检测到两根手指,现在还是检测到两根手指,直接返回
                    
                    return
                } else {
                    // 检测到其他手势,将 pre_one_finger 和 pre_two_finger 置为 false
                    pre_one_finger = false
                }
                */
                // if (res == false && preDetectTwo == false) {
                //     // 如果食指伸出,且上一次检测检测时食指未伸出
                //     preDetectTwo = true
                //     console.log ("食指伸出");
                // } else if (res == false && preDetectTwo) {
                //     // 食指继续伸出,不响应
                // } else if (res == true) {
                //     // 没有检测到,置为 false,无论上次是否检测到
                //     preDetectTwo = false
                // }
            }
        }
        canvasCtx?.restore();
    }
    // 相机对象
    camera = new Camera(videoElement.value as HTMLVideoElement, {
        onFrame: async () => {
            await hands.send({ image: videoElement.value as InputImage });
        },
        width: canvasWidth.value,
        height: canvasHeight.value
    });
    const hands = new Hands({
        locateFile: (file) => {
            console.log("进入file");
            return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
        }
    });
    hands.setOptions({
        maxNumHands: 2,
        modelComplexity: 1,
        minDetectionConfidence: 0.5,
        minTrackingConfidence: 0.5
    });
    hands.onResults(onResults);
    // camera.start()
})
let isOn: boolean = false
const startDetect = () => {
    if (isOn) {
        return
    }
    isOn = true
    camera.start()
}
const stopDetect = () => {
    if (isOn == false) {
        return
    }
    isOn = false
    camera.stop()
}
watch(router.currentRoute, () => {
    if (isOn) {
        // 上面括号之前写的 camera
        camera.stop()
    }
})
const savePic = () => {
    // 要先判断 vedio 是否处于启动状态
    if (isOn) {
        let canvas = document.createElement('canvas')
        canvas.width = 500;
        canvas.height = 500;
        let ctx = canvas.getContext('2d');
        ctx?.drawImage(videoElement?.value as CanvasImageSource, 0, 0, canvas.width, canvas.height);
        // 生成图片 base64 编码
        let base64 = canvas.toDataURL('image/png')
        // console.log(base64);
    } else {
        // console.log("");
        alert("请先打开摄像头")
    }
}
</script>
<style scoped>
.container {
    display: flex;
    justify-content: center;
}
.facedetect {
    height: 500px;
    width: 1100px;
    display: flex;
    justify-content: space-around;
}
</style>

# 预览 pdf

<!-- <div style="width:1000px; height:1000px;background-color:pink;margin: auto;">
        <!-- <pdf :src="url">         -->
        <!-- </pdf> -->
        <object data="http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf" type="application/pdf" width=800 height=800>
            This browser does not support PDFs. Please download the PDF to view it: <a href="unix test.pdf">Download
                PDF</a>
        </object>
        <!-- <iframe src="http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf" style="width:1000px; height:1000px;background-color:pink;margin: auto;" frameborder="0"></iframe> -->
    </div> -->

https://www.zhangxinxu.com/wordpress/2015/11/html-input-type-file/

文件转 base64

https://blog.csdn.net/qq_34664239/article/details/94595508?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-2-94595508-blog-108580573.pc_relevant_vip_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-2-94595508-blog-108580573.pc_relevant_vip_default&utm_relevant_index=3

data:application/pdf;base64, 28 个字符

翻页效果实现

<template>
    <div id="container">
        <button @click="nextPage">按钮</button>
         / 
        <pdf src="http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf"
            :page="currentPage"></pdf>
    </div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import pdf from 'vue3-pdf'
let currentPage = ref(1)
let pageCount = ref(0)
const nextPage = () => {
    currentPage.value += 1
}
</script>
<style scoped>
#container {
    height: 60%;
    width: 60%;
    border: 20px;
    margin: auto;
}
</style>

# 将文件转为 base64,直接填在 src 中

<template>
    <div id="container">
        <input type="file" ref="fileinput" @change="uploadFile" />
        <button @click="nextPage">按钮</button>
         / 
        <pdf :src="url" :page="currentPage"></pdf>
    </div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import pdf from 'vue3-pdf'
let currentPage = ref(1)
let pageCount = ref(0)
let url = ref('http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf')
const nextPage = () => {
    currentPage.value += 1
}
let fileinput = ref<HTMLInputElement | null>(null)
const uploadFile = () => {
    let inputDom = fileinput.value; // 拿到 dom 元素
    console.log(inputDom);
    if (inputDom != null && inputDom.files != null) {
        let file = inputDom.files[0];
        console.log(file);
        console.log(file.name);
        // let url = URL.createObjectURL(file)
        let reader = new FileReader();
        reader.readAsDataURL(file);
        let data
        reader.onload = function () {
            data = reader.result
            console.log(reader.result); //base64
            //console.log ("拿到 data");
            url.value = (data as string)
            console.log(typeof data);
        }
    }
}
</script>
<style scoped>
#container {
    height: 60%;
    width: 60%;
    border: 20px;
    margin: auto;
}
</style>