fix: bone

This commit is contained in:
2025-12-24 14:43:34 +08:00
parent f16048247c
commit 9a16121610

View File

@ -41,7 +41,6 @@
v-model="backendUrl"
placeholder="http://192.168.2.184:8245"
/>
<!-- <button @click="testConnection" :disabled="isTesting">测试连接</button>-->
</div>
</div>
@ -92,6 +91,14 @@
<input type="checkbox" v-model="showKeypoints" />
关键点
</label>
<label>
<input type="checkbox" v-model="showHandBones" />
手部骨骼
</label>
<!-- <label>-->
<!-- <input type="checkbox" v-model="showFootBones" />-->
<!-- 脚部骨骼-->
<!-- </label>-->
</div>
</div>
@ -196,35 +203,152 @@ const originalImage = ref<HTMLImageElement | null>(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<number | null>(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()
// 组件挂载后的初始化
})
</script>
@ -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;