From 9a16121610c5f5cf3b5704d4118de6e62b2aa67a Mon Sep 17 00:00:00 2001 From: laoboli <1293528695@qq.com> Date: Wed, 24 Dec 2025 14:43:34 +0800 Subject: [PATCH] fix: bone --- src/components/Pose.vue | 286 ++++++++++++++++++++++++++++------------ 1 file changed, 201 insertions(+), 85 deletions(-) diff --git a/src/components/Pose.vue b/src/components/Pose.vue index f7e7815..8d70855 100644 --- a/src/components/Pose.vue +++ b/src/components/Pose.vue @@ -41,7 +41,6 @@ v-model="backendUrl" placeholder="http://192.168.2.184:8245" /> - @@ -92,6 +91,14 @@ 关键点 + + + + + @@ -196,35 +203,152 @@ const originalImage = ref(null) const imageWidth = ref(0) const imageHeight = ref(0) const averageConfidence = ref(0) -const backendUrl = ref('http://192.168.2.184:8245') +const backendUrl = ref('https://api.pose.weihua-iot.cn') const canvasWidth = ref(800) const canvasHeight = ref(600) const showBoundingBox = ref(true) const showSkeleton = ref(true) const showKeypoints = ref(true) +const showHandBones = ref(false) +const showFootBones = ref(false) const confidenceThreshold = ref(0.3) const selectColorIndex = ref(null) -const personColors = ref(['#FF5252', '#4CAF50', '#2196F3', '#FF9800', '#9C27B0', '#00BCD4']) +const personColors = ref(['#1389CA', '#E74C3C', '#2ECC71', '#F39C12', '#9B59B6', '#1ABC9C']) -// 骨骼连接关系(COCO-WholeBody格式) -const skeletonConnections = [ - [0, 1], [0, 2], [1, 3], [2, 4], [5, 6], [5, 7], [6, 8], [7, 9], [8, 10], - [11, 12], [5, 11], [6, 12], [11, 13], [13, 15], [12, 14], [14, 16], - [17, 18], [18, 19], [19, 20], [20, 21], [22, 23], [23, 24], [24, 25], [25, 26], - [27, 28], [28, 29], [29, 30], [31, 32], [32, 33], [33, 34], [34, 35], - [36, 37], [37, 38], [38, 39], [39, 40], [41, 42], [42, 43], [43, 44], [44, 45], - [46, 47], [47, 48], [48, 49], [49, 50], [51, 52], [52, 53], [53, 54] +// 颜色定义 +const COLOR_SPINE = '#8AC926' // 绿色,脊椎和头部 +const COLOR_ARMS = '#FFCA3A' // 黄色,手臂和肩膀 +const COLOR_LEGS = '#1982C4' // 蓝色,腿部和臀部 +const COLOR_FINGERS = '#FF595E' // 红色,手指 +const COLOR_FACE = '#FFCA3A' // 黄色,面部 +const COLOR_FOOT = '#FF924C' // 橙色,脚部 +const COLOR_HEAD = '#8AC926' // 绿色,头部 + +// 根据Python代码修复骨骼连接关系 +// 注意:Python代码中使用的是1-based索引,但JavaScript数组是0-based +// 所以需要将Python中的索引减1 + +// 身体骨骼连接(基于Python中的body_bones定义) +const bodyBones = [ + // 腿部 + { start: 15, end: 13, color: COLOR_LEGS, name: "左胫骨" }, // left_tibia + { start: 13, end: 11, color: COLOR_LEGS, name: "左股骨" }, // left_femur + { start: 16, end: 14, color: COLOR_LEGS, name: "右胫骨" }, // right_tibia + { start: 14, end: 12, color: COLOR_LEGS, name: "右股骨" }, // right_femur + { start: 11, end: 12, color: COLOR_LEGS, name: "骨盆" }, // pelvis + + // 躯干 + { start: 5, end: 11, color: COLOR_SPINE, name: "左侧躯干轮廓" }, // left_contour + { start: 6, end: 12, color: COLOR_SPINE, name: "右侧躯干轮廓" }, // right_contour + { start: 5, end: 6, color: COLOR_SPINE, name: "锁骨" }, // clavicle + + // 手臂 + { start: 5, end: 7, color: COLOR_ARMS, name: "左肱骨" }, // left_humerus + { start: 7, end: 9, color: COLOR_ARMS, name: "左桡骨" }, // left_radius + { start: 6, end: 8, color: COLOR_ARMS, name: "右肱骨" }, // right_humerus + { start: 8, end: 10, color: COLOR_ARMS, name: "右桡骨" }, // right_radius + + // 头部 + { start: 0, end: 1, color: COLOR_HEAD, name: "鼻子-左眼" }, // 头部连接 + { start: 0, end: 2, color: COLOR_HEAD, name: "鼻子-右眼" }, // 头部连接 + { start: 1, end: 3, color: COLOR_HEAD, name: "左眼-左耳" }, // 左耳 + { start: 2, end: 4, color: COLOR_HEAD, name: "右眼-右耳" }, // 右耳 + { start: 1, end: 2, color: COLOR_HEAD, name: "左眼-右眼" }, // 双眼连接 + + // 脚部(基于Python中的body_bones定义,但注释中显示是foot_landmarks的连接) + { start: 15, end: 17, color: COLOR_FOOT, name: "左脚-大脚趾" }, // left foot toe + { start: 15, end: 18, color: COLOR_FOOT, name: "左脚-小脚趾" }, // left foot small toe + { start: 15, end: 19, color: COLOR_FOOT, name: "左脚-脚后跟" }, // left foot heel + { start: 16, end: 20, color: COLOR_FOOT, name: "右脚-大脚趾" }, // right foot toe + { start: 16, end: 21, color: COLOR_FOOT, name: "右脚-小脚趾" }, // right foot small toe + { start: 16, end: 22, color: COLOR_FOOT, name: "右脚-脚后跟" } // right foot heel ] -// 观察置信度阈值变化 -watch(confidenceThreshold, () => { - if (originalImage.value && poseData.value) { - drawPoseResults(originalImage.value) - } -}) +// 手部骨骼连接(基于Python中的hand_bones定义) +const handBones = [ + // 右手拇指 + { start: 91, end: 92, color: COLOR_FINGERS, name: "右手拇指掌骨" }, + { start: 92, end: 93, color: COLOR_FINGERS, name: "右手拇指近节指骨" }, + { start: 93, end: 94, color: COLOR_FINGERS, name: "右手拇指远节指骨" }, + { start: 94, end: 95, color: COLOR_FINGERS, name: "右手拇指指尖" }, -// 观察显示选项变化 -watch([showBoundingBox, showSkeleton, showKeypoints], () => { + // 右手食指 + { start: 91, end: 96, color: COLOR_FINGERS, name: "右手食指掌骨" }, + { start: 96, end: 97, color: COLOR_FINGERS, name: "右手食指近节指骨" }, + { start: 97, end: 98, color: COLOR_FINGERS, name: "右手食指中节指骨" }, + { start: 98, end: 99, color: COLOR_FINGERS, name: "右手食指远节指骨" }, + { start: 99, end: 100, color: COLOR_FINGERS, name: "右手食指指尖" }, + + // 右手中指 + { start: 91, end: 100, color: COLOR_FINGERS, name: "右手中指掌骨" }, + { start: 100, end: 101, color: COLOR_FINGERS, name: "右手中指近节指骨" }, + { start: 101, end: 102, color: COLOR_FINGERS, name: "右手中指中节指骨" }, + { start: 102, end: 103, color: COLOR_FINGERS, name: "右手中指远节指骨" }, + { start: 103, end: 104, color: COLOR_FINGERS, name: "右手中指指尖" }, + + // 右手无名指 + { start: 91, end: 104, color: COLOR_FINGERS, name: "右手无名指掌骨" }, + { start: 104, end: 105, color: COLOR_FINGERS, name: "右手无名指近节指骨" }, + { start: 105, end: 106, color: COLOR_FINGERS, name: "右手无名指中节指骨" }, + { start: 106, end: 107, color: COLOR_FINGERS, name: "右手无名指远节指骨" }, + { start: 107, end: 108, color: COLOR_FINGERS, name: "右手无名指指尖" }, + + // 右手小指 + { start: 91, end: 108, color: COLOR_FINGERS, name: "右手小指掌骨" }, + { start: 108, end: 109, color: COLOR_FINGERS, name: "右手小指近节指骨" }, + { start: 109, end: 110, color: COLOR_FINGERS, name: "右手小指中节指骨" }, + { start: 110, end: 111, color: COLOR_FINGERS, name: "右手小指远节指骨" }, + { start: 111, end: 112, color: COLOR_FINGERS, name: "右手小指指尖" }, + + // 左手拇指 + { start: 112, end: 113, color: COLOR_FINGERS, name: "左手拇指掌骨" }, + { start: 113, end: 114, color: COLOR_FINGERS, name: "左手拇指近节指骨" }, + { start: 114, end: 115, color: COLOR_FINGERS, name: "左手拇指远节指骨" }, + { start: 115, end: 116, color: COLOR_FINGERS, name: "左手拇指指尖" }, + + // 左手食指 + { start: 112, end: 117, color: COLOR_FINGERS, name: "左手食指掌骨" }, + { start: 117, end: 118, color: COLOR_FINGERS, name: "左手食指近节指骨" }, + { start: 118, end: 119, color: COLOR_FINGERS, name: "左手食指中节指骨" }, + { start: 119, end: 120, color: COLOR_FINGERS, name: "左手食指远节指骨" }, + { start: 120, end: 121, color: COLOR_FINGERS, name: "左手食指指尖" }, + + // 左手中指 + { start: 112, end: 121, color: COLOR_FINGERS, name: "左手中指掌骨" }, + { start: 121, end: 122, color: COLOR_FINGERS, name: "左手中指近节指骨" }, + { start: 122, end: 123, color: COLOR_FINGERS, name: "左手中指中节指骨" }, + { start: 123, end: 124, color: COLOR_FINGERS, name: "左手中指远节指骨" }, + { start: 124, end: 125, color: COLOR_FINGERS, name: "左手中指指尖" }, + + // 左手无名指 + { start: 112, end: 125, color: COLOR_FINGERS, name: "左手无名指掌骨" }, + { start: 125, end: 126, color: COLOR_FINGERS, name: "左手无名指近节指骨" }, + { start: 126, end: 127, color: COLOR_FINGERS, name: "左手无名指中节指骨" }, + { start: 127, end: 128, color: COLOR_FINGERS, name: "左手无名指远节指骨" }, + { start: 128, end: 129, color: COLOR_FINGERS, name: "左手无名指指尖" }, + + // 左手小指 + { start: 112, end: 129, color: COLOR_FINGERS, name: "左手小指掌骨" }, + { start: 129, end: 130, color: COLOR_FINGERS, name: "左手小指近节指骨" }, + { start: 130, end: 131, color: COLOR_FINGERS, name: "左手小指中节指骨" }, + { start: 131, end: 132, color: COLOR_FINGERS, name: "左手小指远节指骨" }, + { start: 132, end: 133, color: COLOR_FINGERS, name: "左手小指指尖" } +] + +// 合并所有骨骼连接 +const getAllBones = () => { + const bones = [...bodyBones] + if (showFootBones.value) { + // 脚部骨骼已经在bodyBones中包含 + } + if (showHandBones.value) { + bones.push(...handBones) + } + return bones +} + +// 观察设置变化 +watch([confidenceThreshold, showBoundingBox, showSkeleton, showKeypoints, showHandBones, showFootBones], () => { if (originalImage.value && poseData.value) { drawPoseResults(originalImage.value) } @@ -263,38 +387,6 @@ const handleDrop = async (e: DragEvent) => { } } -// 测试后端连接 -const testConnection = async () => { - isTesting.value = true - connectionStatus.value = null - error.value = '' - - try { - const response = await fetch(backendUrl.value + '/hpe', { - method: 'OPTIONS' // 先发送OPTIONS请求检查CORS - }) - - if (response.ok) { - connectionStatus.value = { - type: 'success', - message: '后端连接成功!' - } - } else { - connectionStatus.value = { - type: 'error', - message: '后端连接失败,状态码:' + response.status - } - } - } catch (err) { - connectionStatus.value = { - type: 'error', - message: '无法连接到后端:' + (err as Error).message - } - } finally { - isTesting.value = false - } -} - // 处理图片分析 const processImage = async (file: File) => { // 验证文件类型 @@ -349,7 +441,7 @@ const processImage = async (file: File) => { const response = await fetch(`${backendUrl.value}/hpe`, { method: 'POST', headers: { - 'Content-Type': file.type // 设置正确的Content-Type + 'Content-Type': file.type }, body: arrayBuffer }) @@ -369,7 +461,6 @@ const processImage = async (file: File) => { } } - // 检查是否有检测结果 const contentLength = response.headers.get('content-length') if (response.status === 200 && contentLength !== '0' && parseInt(contentLength || '0') > 0) { @@ -437,21 +528,29 @@ const drawPoseResults = (img: HTMLImageElement) => { const keypoints = poseData.value!.keypoints[personIndex] const confidences = poseData.value!.keypoints_confidence?.[personIndex] || [] + // 获取所有要绘制的骨骼 + const bonesToDraw = getAllBones() + // 绘制骨骼连接 if (showSkeleton.value) { - ctx.strokeStyle = color - ctx.lineWidth = 2 + bonesToDraw.forEach(bone => { + if (bone.start < keypoints.length && bone.end < keypoints.length) { + const startPoint = keypoints[bone.start] + const endPoint = keypoints[bone.end] - skeletonConnections.forEach(([start, end]) => { - if (start < keypoints.length && end < keypoints.length) { - const startPoint = keypoints[start] - const endPoint = keypoints[end] + // 检查关键点是否存在 + if (!startPoint || !endPoint || startPoint.length < 2 || endPoint.length < 2) { + return + } // 检查关键点置信度 - const startConf = confidences[start] || 1 - const endConf = confidences[end] || 1 + const startConf = confidences[bone.start] || 1 + const endConf = confidences[bone.end] || 1 if (startConf > confidenceThreshold.value && endConf > confidenceThreshold.value) { + ctx.strokeStyle = bone.color + ctx.lineWidth = 2 + ctx.lineCap = 'round' ctx.beginPath() ctx.moveTo(startPoint[0] * scaleX, startPoint[1] * scaleY) ctx.lineTo(endPoint[0] * scaleX, endPoint[1] * scaleY) @@ -464,12 +563,50 @@ const drawPoseResults = (img: HTMLImageElement) => { // 绘制关键点 if (showKeypoints.value) { keypoints.forEach((point, index) => { + if (!point || point.length < 2) return + const confidence = confidences[index] || 1 if (confidence > confidenceThreshold.value) { - ctx.fillStyle = color + // 根据身体部位设置关键点颜色 + let pointColor = color! + + // 身体关键点(0-16) + if (index <= 16) { + // 头部和脊椎 + if (index <= 4) { + pointColor = COLOR_HEAD + } + // 手臂 + else if (index <= 10) { + pointColor = COLOR_ARMS + } + // 腿部 + else { + pointColor = COLOR_LEGS + } + } + // 脚部关键点(17-22) + else if (index <= 22) { + pointColor = COLOR_FOOT + } + // 手部关键点(92-132) + else if (index >= 91 && index <= 132) { + pointColor = COLOR_FINGERS + } + // 脸部关键点(23-90) + else { + pointColor = COLOR_FACE + } + + ctx.fillStyle = pointColor ctx.beginPath() - ctx.arc(point[0] * scaleX, point[1] * scaleY, 1, 0, Math.PI * 2) + ctx.arc(point[0] * scaleX, point[1] * scaleY, 2, 0, Math.PI * 2) ctx.fill() + + // 绘制关键点轮廓 + ctx.strokeStyle = '#000000' + ctx.lineWidth = 1 + ctx.stroke() } }) } @@ -488,8 +625,7 @@ const downloadResult = () => { } onMounted(() => { - // 初始化时测试后端连接 - // testConnection() + // 组件挂载后的初始化 }) @@ -587,26 +723,6 @@ h1 { box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1); } -.config-item button { - padding: 8px 20px; - background: #667eea; - color: white; - border: none; - border-radius: 5px; - cursor: pointer; - transition: background 0.3s; - font-weight: 500; -} - -.config-item button:hover:not(:disabled) { - background: #5a67d8; -} - -.config-item button:disabled { - opacity: 0.6; - cursor: not-allowed; -} - .loading { text-align: center; padding: 40px;