rtmlib-ts 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +1 -0
- package/README.md +202 -0
- package/dist/core/base.d.ts +20 -0
- package/dist/core/base.d.ts.map +1 -0
- package/dist/core/base.js +40 -0
- package/dist/core/file.d.ts +11 -0
- package/dist/core/file.d.ts.map +1 -0
- package/dist/core/file.js +111 -0
- package/dist/core/modelCache.d.ts +35 -0
- package/dist/core/modelCache.d.ts.map +1 -0
- package/dist/core/modelCache.js +161 -0
- package/dist/core/posePostprocessing.d.ts +12 -0
- package/dist/core/posePostprocessing.d.ts.map +1 -0
- package/dist/core/posePostprocessing.js +76 -0
- package/dist/core/postprocessing.d.ts +10 -0
- package/dist/core/postprocessing.d.ts.map +1 -0
- package/dist/core/postprocessing.js +70 -0
- package/dist/core/preprocessing.d.ts +14 -0
- package/dist/core/preprocessing.d.ts.map +1 -0
- package/dist/core/preprocessing.js +79 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/models/rtmpose.d.ts +25 -0
- package/dist/models/rtmpose.d.ts.map +1 -0
- package/dist/models/rtmpose.js +185 -0
- package/dist/models/rtmpose3d.d.ts +28 -0
- package/dist/models/rtmpose3d.d.ts.map +1 -0
- package/dist/models/rtmpose3d.js +184 -0
- package/dist/models/yolo12.d.ts +23 -0
- package/dist/models/yolo12.d.ts.map +1 -0
- package/dist/models/yolo12.js +165 -0
- package/dist/models/yolox.d.ts +18 -0
- package/dist/models/yolox.d.ts.map +1 -0
- package/dist/models/yolox.js +167 -0
- package/dist/solution/animalDetector.d.ts +229 -0
- package/dist/solution/animalDetector.d.ts.map +1 -0
- package/dist/solution/animalDetector.js +663 -0
- package/dist/solution/body.d.ts +16 -0
- package/dist/solution/body.d.ts.map +1 -0
- package/dist/solution/body.js +52 -0
- package/dist/solution/bodyWithFeet.d.ts +16 -0
- package/dist/solution/bodyWithFeet.d.ts.map +1 -0
- package/dist/solution/bodyWithFeet.js +52 -0
- package/dist/solution/customDetector.d.ts +137 -0
- package/dist/solution/customDetector.d.ts.map +1 -0
- package/dist/solution/customDetector.js +342 -0
- package/dist/solution/hand.d.ts +14 -0
- package/dist/solution/hand.d.ts.map +1 -0
- package/dist/solution/hand.js +20 -0
- package/dist/solution/index.d.ts +10 -0
- package/dist/solution/index.d.ts.map +1 -0
- package/dist/solution/index.js +9 -0
- package/dist/solution/objectDetector.d.ts +172 -0
- package/dist/solution/objectDetector.d.ts.map +1 -0
- package/dist/solution/objectDetector.js +606 -0
- package/dist/solution/pose3dDetector.d.ts +145 -0
- package/dist/solution/pose3dDetector.d.ts.map +1 -0
- package/dist/solution/pose3dDetector.js +611 -0
- package/dist/solution/poseDetector.d.ts +198 -0
- package/dist/solution/poseDetector.d.ts.map +1 -0
- package/dist/solution/poseDetector.js +622 -0
- package/dist/solution/poseTracker.d.ts +22 -0
- package/dist/solution/poseTracker.d.ts.map +1 -0
- package/dist/solution/poseTracker.js +106 -0
- package/dist/solution/wholebody.d.ts +19 -0
- package/dist/solution/wholebody.d.ts.map +1 -0
- package/dist/solution/wholebody.js +82 -0
- package/dist/solution/wholebody3d.d.ts +22 -0
- package/dist/solution/wholebody3d.d.ts.map +1 -0
- package/dist/solution/wholebody3d.js +75 -0
- package/dist/types/index.d.ts +52 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/visualization/draw.d.ts +57 -0
- package/dist/visualization/draw.d.ts.map +1 -0
- package/dist/visualization/draw.js +400 -0
- package/dist/visualization/skeleton/coco133.d.ts +350 -0
- package/dist/visualization/skeleton/coco133.d.ts.map +1 -0
- package/dist/visualization/skeleton/coco133.js +120 -0
- package/dist/visualization/skeleton/coco17.d.ts +180 -0
- package/dist/visualization/skeleton/coco17.d.ts.map +1 -0
- package/dist/visualization/skeleton/coco17.js +48 -0
- package/dist/visualization/skeleton/halpe26.d.ts +278 -0
- package/dist/visualization/skeleton/halpe26.d.ts.map +1 -0
- package/dist/visualization/skeleton/halpe26.js +70 -0
- package/dist/visualization/skeleton/hand21.d.ts +196 -0
- package/dist/visualization/skeleton/hand21.d.ts.map +1 -0
- package/dist/visualization/skeleton/hand21.js +51 -0
- package/dist/visualization/skeleton/index.d.ts +10 -0
- package/dist/visualization/skeleton/index.d.ts.map +1 -0
- package/dist/visualization/skeleton/index.js +9 -0
- package/dist/visualization/skeleton/openpose134.d.ts +357 -0
- package/dist/visualization/skeleton/openpose134.d.ts.map +1 -0
- package/dist/visualization/skeleton/openpose134.js +116 -0
- package/dist/visualization/skeleton/openpose18.d.ts +177 -0
- package/dist/visualization/skeleton/openpose18.d.ts.map +1 -0
- package/dist/visualization/skeleton/openpose18.js +47 -0
- package/docs/ANIMAL_DETECTOR.md +450 -0
- package/docs/CUSTOM_DETECTOR.md +568 -0
- package/docs/OBJECT_DETECTOR.md +373 -0
- package/docs/POSE3D_DETECTOR.md +458 -0
- package/docs/POSE_DETECTOR.md +442 -0
- package/examples/README.md +119 -0
- package/examples/index.html +746 -0
- package/package.json +51 -0
- package/playground/README.md +114 -0
- package/playground/app/favicon.ico +0 -0
- package/playground/app/globals.css +17 -0
- package/playground/app/layout.tsx +19 -0
- package/playground/app/page.tsx +1338 -0
- package/playground/eslint.config.mjs +18 -0
- package/playground/next.config.ts +34 -0
- package/playground/package-lock.json +6723 -0
- package/playground/package.json +27 -0
- package/playground/postcss.config.mjs +7 -0
- package/playground/tsconfig.json +34 -0
- package/src/core/base.ts +66 -0
- package/src/core/file.ts +141 -0
- package/src/core/modelCache.ts +189 -0
- package/src/core/posePostprocessing.ts +91 -0
- package/src/core/postprocessing.ts +93 -0
- package/src/core/preprocessing.ts +127 -0
- package/src/index.ts +69 -0
- package/src/models/rtmpose.ts +265 -0
- package/src/models/rtmpose3d.ts +289 -0
- package/src/models/yolo12.ts +220 -0
- package/src/models/yolox.ts +214 -0
- package/src/solution/animalDetector.ts +955 -0
- package/src/solution/body.ts +89 -0
- package/src/solution/bodyWithFeet.ts +89 -0
- package/src/solution/customDetector.ts +474 -0
- package/src/solution/hand.ts +52 -0
- package/src/solution/index.ts +10 -0
- package/src/solution/objectDetector.ts +816 -0
- package/src/solution/pose3dDetector.ts +890 -0
- package/src/solution/poseDetector.ts +892 -0
- package/src/solution/poseTracker.ts +172 -0
- package/src/solution/wholebody.ts +130 -0
- package/src/solution/wholebody3d.ts +125 -0
- package/src/types/index.ts +62 -0
- package/src/visualization/draw.ts +543 -0
- package/src/visualization/skeleton/coco133.ts +131 -0
- package/src/visualization/skeleton/coco17.ts +49 -0
- package/src/visualization/skeleton/halpe26.ts +71 -0
- package/src/visualization/skeleton/hand21.ts +52 -0
- package/src/visualization/skeleton/index.ts +10 -0
- package/src/visualization/skeleton/openpose134.ts +125 -0
- package/src/visualization/skeleton/openpose18.ts +48 -0
- package/tsconfig.json +32 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drawing utilities for visualization
|
|
3
|
+
* Based on rtmlib Python library
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Draw bounding boxes on image
|
|
7
|
+
*/
|
|
8
|
+
export function drawBbox(img, width, height, bboxes, color = [0, 255, 0]) {
|
|
9
|
+
const result = new Uint8Array(img);
|
|
10
|
+
for (const bbox of bboxes) {
|
|
11
|
+
const [x1, y1, x2, y2] = bbox;
|
|
12
|
+
// Draw top and bottom horizontal lines
|
|
13
|
+
for (let x = Math.floor(x1); x < Math.floor(x2); x++) {
|
|
14
|
+
for (let t = 0; t < 2; t++) {
|
|
15
|
+
const yTop = Math.floor(y1) + t;
|
|
16
|
+
const yBottom = Math.floor(y2) - t;
|
|
17
|
+
if (yTop >= 0 && yTop < height && x >= 0 && x < width) {
|
|
18
|
+
const idx = (yTop * width + x) * 3;
|
|
19
|
+
result[idx] = color[2];
|
|
20
|
+
result[idx + 1] = color[1];
|
|
21
|
+
result[idx + 2] = color[0];
|
|
22
|
+
}
|
|
23
|
+
if (yBottom >= 0 && yBottom < height && x >= 0 && x < width) {
|
|
24
|
+
const idx = (yBottom * width + x) * 3;
|
|
25
|
+
result[idx] = color[2];
|
|
26
|
+
result[idx + 1] = color[1];
|
|
27
|
+
result[idx + 2] = color[0];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Draw left and right vertical lines
|
|
32
|
+
for (let y = Math.floor(y1); y < Math.floor(y2); y++) {
|
|
33
|
+
for (let t = 0; t < 2; t++) {
|
|
34
|
+
const xLeft = Math.floor(x1) + t;
|
|
35
|
+
const xRight = Math.floor(x2) - t;
|
|
36
|
+
if (y >= 0 && y < height && xLeft >= 0 && xLeft < width) {
|
|
37
|
+
const idx = (y * width + xLeft) * 3;
|
|
38
|
+
result[idx] = color[2];
|
|
39
|
+
result[idx + 1] = color[1];
|
|
40
|
+
result[idx + 2] = color[0];
|
|
41
|
+
}
|
|
42
|
+
if (y >= 0 && y < height && xRight >= 0 && xRight < width) {
|
|
43
|
+
const idx = (y * width + xRight) * 3;
|
|
44
|
+
result[idx] = color[2];
|
|
45
|
+
result[idx + 1] = color[1];
|
|
46
|
+
result[idx + 2] = color[0];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Draw skeleton on image
|
|
55
|
+
*/
|
|
56
|
+
export function drawSkeleton(img, width, height, keypoints, scores, openposeSkeleton = false, kptThr = 0.5, radius = 2, lineWidth = 2) {
|
|
57
|
+
const numKeypoints = keypoints.length;
|
|
58
|
+
// Handle empty keypoints - return a copy of the image
|
|
59
|
+
if (numKeypoints === 0) {
|
|
60
|
+
console.log('No keypoints to draw');
|
|
61
|
+
return new Uint8Array(img);
|
|
62
|
+
}
|
|
63
|
+
let skeletonName;
|
|
64
|
+
if (openposeSkeleton) {
|
|
65
|
+
if (numKeypoints === 18) {
|
|
66
|
+
skeletonName = 'openpose18';
|
|
67
|
+
}
|
|
68
|
+
else if (numKeypoints === 134 || numKeypoints === 133) {
|
|
69
|
+
skeletonName = 'openpose134';
|
|
70
|
+
}
|
|
71
|
+
else if (numKeypoints === 26) {
|
|
72
|
+
skeletonName = 'halpe26';
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
throw new Error(`Unsupported openpose skeleton with ${numKeypoints} keypoints`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
if (numKeypoints === 17) {
|
|
80
|
+
skeletonName = 'coco17';
|
|
81
|
+
}
|
|
82
|
+
else if (numKeypoints === 133 || numKeypoints === 134) {
|
|
83
|
+
skeletonName = 'coco133';
|
|
84
|
+
}
|
|
85
|
+
else if (numKeypoints === 21) {
|
|
86
|
+
skeletonName = 'hand21';
|
|
87
|
+
}
|
|
88
|
+
else if (numKeypoints === 26) {
|
|
89
|
+
skeletonName = 'halpe26';
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
throw new Error(`Unsupported mmpose skeleton with ${numKeypoints} keypoints`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const skeletonDict = getSkeletonDict(skeletonName);
|
|
96
|
+
// Single instance - keypoints is 2D array [N, 2]
|
|
97
|
+
img = drawMmpose(img, width, height, keypoints, scores, skeletonDict.keypoint_info, skeletonDict.skeleton_info, kptThr, radius, lineWidth);
|
|
98
|
+
return img;
|
|
99
|
+
}
|
|
100
|
+
function getSkeletonDict(name) {
|
|
101
|
+
// Import skeleton configs dynamically
|
|
102
|
+
switch (name) {
|
|
103
|
+
case 'coco17':
|
|
104
|
+
return {
|
|
105
|
+
keypoint_info: {
|
|
106
|
+
0: { name: 'nose', id: 0, color: [51, 255, 255] },
|
|
107
|
+
1: { name: 'left_eye', id: 1, color: [51, 255, 255] },
|
|
108
|
+
2: { name: 'right_eye', id: 2, color: [51, 255, 255] },
|
|
109
|
+
3: { name: 'left_ear', id: 3, color: [51, 255, 255] },
|
|
110
|
+
4: { name: 'right_ear', id: 4, color: [51, 255, 255] },
|
|
111
|
+
5: { name: 'left_shoulder', id: 5, color: [255, 51, 255] },
|
|
112
|
+
6: { name: 'right_shoulder', id: 6, color: [255, 51, 255] },
|
|
113
|
+
7: { name: 'left_elbow', id: 7, color: [255, 51, 255] },
|
|
114
|
+
8: { name: 'right_elbow', id: 8, color: [255, 51, 255] },
|
|
115
|
+
9: { name: 'left_wrist', id: 9, color: [255, 51, 255] },
|
|
116
|
+
10: { name: 'right_wrist', id: 10, color: [255, 51, 255] },
|
|
117
|
+
11: { name: 'left_hip', id: 11, color: [255, 255, 51] },
|
|
118
|
+
12: { name: 'right_hip', id: 12, color: [255, 255, 51] },
|
|
119
|
+
13: { name: 'left_knee', id: 13, color: [255, 255, 51] },
|
|
120
|
+
14: { name: 'right_knee', id: 14, color: [255, 255, 51] },
|
|
121
|
+
15: { name: 'left_ankle', id: 15, color: [255, 255, 51] },
|
|
122
|
+
16: { name: 'right_ankle', id: 16, color: [255, 255, 51] },
|
|
123
|
+
},
|
|
124
|
+
skeleton_info: {
|
|
125
|
+
0: { link: ['left_ankle', 'left_knee'], color: [255, 51, 255] },
|
|
126
|
+
1: { link: ['left_knee', 'left_hip'], color: [255, 51, 255] },
|
|
127
|
+
2: { link: ['left_hip', 'right_hip'], color: [255, 255, 51] },
|
|
128
|
+
3: { link: ['right_hip', 'right_knee'], color: [255, 51, 255] },
|
|
129
|
+
4: { link: ['right_knee', 'right_ankle'], color: [255, 51, 255] },
|
|
130
|
+
5: { link: ['left_hip', 'left_shoulder'], color: [255, 255, 51] },
|
|
131
|
+
6: { link: ['left_shoulder', 'left_elbow'], color: [255, 255, 51] },
|
|
132
|
+
7: { link: ['left_elbow', 'left_wrist'], color: [255, 255, 51] },
|
|
133
|
+
8: { link: ['left_hip', 'right_shoulder'], color: [255, 255, 51] },
|
|
134
|
+
9: { link: ['right_shoulder', 'right_elbow'], color: [255, 255, 51] },
|
|
135
|
+
10: { link: ['right_elbow', 'right_wrist'], color: [255, 255, 51] },
|
|
136
|
+
11: { link: ['left_shoulder', 'right_shoulder'], color: [255, 255, 51] },
|
|
137
|
+
12: { link: ['nose', 'left_shoulder'], color: [255, 255, 51] },
|
|
138
|
+
13: { link: ['nose', 'right_shoulder'], color: [255, 255, 51] },
|
|
139
|
+
14: { link: ['nose', 'left_eye'], color: [255, 255, 51] },
|
|
140
|
+
15: { link: ['left_eye', 'right_eye'], color: [255, 255, 51] },
|
|
141
|
+
16: { link: ['right_eye', 'nose'], color: [255, 255, 51] },
|
|
142
|
+
17: { link: ['left_eye', 'left_ear'], color: [255, 255, 51] },
|
|
143
|
+
18: { link: ['right_eye', 'right_ear'], color: [255, 255, 51] },
|
|
144
|
+
19: { link: ['left_ear', 'left_shoulder'], color: [255, 255, 51] },
|
|
145
|
+
20: { link: ['right_ear', 'right_shoulder'], color: [255, 255, 51] },
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
case 'coco133':
|
|
149
|
+
// For 133 keypoints, use simplified body skeleton
|
|
150
|
+
return {
|
|
151
|
+
keypoint_info: Object.fromEntries(Array.from({ length: 133 }, (_, i) => [
|
|
152
|
+
i,
|
|
153
|
+
{ name: `kp_${i}`, id: i, color: [255, 255, 255] }
|
|
154
|
+
])),
|
|
155
|
+
skeleton_info: {
|
|
156
|
+
0: { link: ['kp_15', 'kp_13'], color: [255, 51, 255] },
|
|
157
|
+
1: { link: ['kp_13', 'kp_11'], color: [255, 51, 255] },
|
|
158
|
+
2: { link: ['kp_11', 'kp_12'], color: [255, 255, 51] },
|
|
159
|
+
3: { link: ['kp_12', 'kp_14'], color: [255, 51, 255] },
|
|
160
|
+
4: { link: ['kp_14', 'kp_16'], color: [255, 51, 255] },
|
|
161
|
+
5: { link: ['kp_11', 'kp_5'], color: [255, 255, 51] },
|
|
162
|
+
6: { link: ['kp_5', 'kp_7'], color: [255, 255, 51] },
|
|
163
|
+
7: { link: ['kp_7', 'kp_9'], color: [255, 255, 51] },
|
|
164
|
+
8: { link: ['kp_12', 'kp_6'], color: [255, 255, 51] },
|
|
165
|
+
9: { link: ['kp_6', 'kp_8'], color: [255, 255, 51] },
|
|
166
|
+
10: { link: ['kp_8', 'kp_10'], color: [255, 255, 51] },
|
|
167
|
+
11: { link: ['kp_5', 'kp_6'], color: [255, 255, 51] },
|
|
168
|
+
12: { link: ['kp_0', 'kp_5'], color: [255, 255, 51] },
|
|
169
|
+
13: { link: ['kp_0', 'kp_6'], color: [255, 255, 51] },
|
|
170
|
+
14: { link: ['kp_0', 'kp_1'], color: [255, 255, 51] },
|
|
171
|
+
15: { link: ['kp_1', 'kp_2'], color: [255, 255, 51] },
|
|
172
|
+
16: { link: ['kp_2', 'kp_0'], color: [255, 255, 51] },
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
default:
|
|
176
|
+
throw new Error(`Unknown skeleton type: ${name}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Draw MMPose-style skeleton
|
|
181
|
+
*/
|
|
182
|
+
function drawMmpose(img, width, height, keypoints, scores, keypointInfo, skeletonInfo, kptThr = 0.5, radius = 2, lineWidth = 2) {
|
|
183
|
+
const result = new Uint8Array(img);
|
|
184
|
+
const visKpt = scores.map((s) => s >= kptThr);
|
|
185
|
+
// Build keypoint name to id mapping
|
|
186
|
+
const linkDict = {};
|
|
187
|
+
// Draw keypoints
|
|
188
|
+
for (const [idStr, kptInfo] of Object.entries(keypointInfo)) {
|
|
189
|
+
const id = parseInt(idStr);
|
|
190
|
+
const kptColor = kptInfo.color;
|
|
191
|
+
linkDict[kptInfo.name] = kptInfo.id;
|
|
192
|
+
if (id >= keypoints.length)
|
|
193
|
+
continue;
|
|
194
|
+
const kpt = keypoints[id];
|
|
195
|
+
if (visKpt[id]) {
|
|
196
|
+
drawCircle(result, width, height, kpt[0], kpt[1], radius, kptColor);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Draw skeleton links
|
|
200
|
+
for (const skeInfo of Object.values(skeletonInfo)) {
|
|
201
|
+
const [link0, link1] = skeInfo.link;
|
|
202
|
+
const pt0 = linkDict[link0];
|
|
203
|
+
const pt1 = linkDict[link1];
|
|
204
|
+
if (pt0 === undefined || pt1 === undefined)
|
|
205
|
+
continue;
|
|
206
|
+
if (!visKpt[pt0] || !visKpt[pt1])
|
|
207
|
+
continue;
|
|
208
|
+
const linkColor = skeInfo.color;
|
|
209
|
+
const kpt0 = keypoints[pt0];
|
|
210
|
+
const kpt1 = keypoints[pt1];
|
|
211
|
+
drawLine(result, width, height, kpt0[0], kpt0[1], kpt1[0], kpt1[1], linkColor, lineWidth);
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Draw a circle on the image
|
|
217
|
+
*/
|
|
218
|
+
function drawCircle(img, width, height, cx, cy, radius, color) {
|
|
219
|
+
const x0 = Math.max(0, Math.floor(cx - radius));
|
|
220
|
+
const x1 = Math.min(width, Math.ceil(cx + radius));
|
|
221
|
+
const y0 = Math.max(0, Math.floor(cy - radius));
|
|
222
|
+
const y1 = Math.min(height, Math.ceil(cy + radius));
|
|
223
|
+
const rSquared = radius * radius;
|
|
224
|
+
for (let y = y0; y < y1; y++) {
|
|
225
|
+
for (let x = x0; x < x1; x++) {
|
|
226
|
+
const dx = x - cx;
|
|
227
|
+
const dy = y - cy;
|
|
228
|
+
if (dx * dx + dy * dy <= rSquared) {
|
|
229
|
+
const idx = (y * width + x) * 3;
|
|
230
|
+
img[idx] = color[2];
|
|
231
|
+
img[idx + 1] = color[1];
|
|
232
|
+
img[idx + 2] = color[0];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Draw a line on the image using Bresenham's algorithm
|
|
239
|
+
*/
|
|
240
|
+
function drawLine(img, width, height, x0, y0, x1, y1, color, thickness = 2) {
|
|
241
|
+
let x0i = Math.round(x0);
|
|
242
|
+
let y0i = Math.round(y0);
|
|
243
|
+
const x1i = Math.round(x1);
|
|
244
|
+
const y1i = Math.round(y1);
|
|
245
|
+
const dx = Math.abs(x1i - x0i);
|
|
246
|
+
const dy = Math.abs(y1i - y0i);
|
|
247
|
+
const sx = x0i < x1i ? 1 : -1;
|
|
248
|
+
const sy = y0i < y1i ? 1 : -1;
|
|
249
|
+
let err = dx - dy;
|
|
250
|
+
// Draw with thickness
|
|
251
|
+
const halfThickness = Math.floor(thickness / 2);
|
|
252
|
+
while (true) {
|
|
253
|
+
for (let dy_t = -halfThickness; dy_t <= halfThickness; dy_t++) {
|
|
254
|
+
for (let dx_t = -halfThickness; dx_t <= halfThickness; dx_t++) {
|
|
255
|
+
const x = x0i + dx_t;
|
|
256
|
+
const y = y0i + dy_t;
|
|
257
|
+
if (x >= 0 && x < width && y >= 0 && y < height) {
|
|
258
|
+
const idx = (y * width + x) * 3;
|
|
259
|
+
img[idx] = color[2];
|
|
260
|
+
img[idx + 1] = color[1];
|
|
261
|
+
img[idx + 2] = color[0];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (x0i === x1i && y0i === y1i)
|
|
266
|
+
break;
|
|
267
|
+
const e2 = 2 * err;
|
|
268
|
+
if (e2 > -dy) {
|
|
269
|
+
err -= dy;
|
|
270
|
+
x0i += sx;
|
|
271
|
+
}
|
|
272
|
+
if (e2 < dx) {
|
|
273
|
+
err += dx;
|
|
274
|
+
y0i += sy;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Draw detections on HTML Canvas
|
|
280
|
+
* @param ctx - Canvas 2D context
|
|
281
|
+
* @param detections - Array of detected objects
|
|
282
|
+
* @param color - Base color for boxes (default: green)
|
|
283
|
+
*/
|
|
284
|
+
export function drawDetectionsOnCanvas(ctx, detections, color = '#00ff00') {
|
|
285
|
+
detections.forEach((det, idx) => {
|
|
286
|
+
const { bbox } = det;
|
|
287
|
+
const label = det.className ? `${det.className} ${(bbox.confidence * 100).toFixed(0)}%` : `${(bbox.confidence * 100).toFixed(0)}%`;
|
|
288
|
+
const boxColor = Array.isArray(color) ? color : color;
|
|
289
|
+
const hueColor = typeof color === 'string' && color.startsWith('hsl') ? color : `hsl(${idx * 60}, 80%, 50%)`;
|
|
290
|
+
// Draw bounding box
|
|
291
|
+
ctx.strokeStyle = hueColor;
|
|
292
|
+
ctx.lineWidth = 2;
|
|
293
|
+
ctx.strokeRect(bbox.x1, bbox.y1, bbox.x2 - bbox.x1, bbox.y2 - bbox.y1);
|
|
294
|
+
// Draw label background
|
|
295
|
+
ctx.font = 'bold 12px sans-serif';
|
|
296
|
+
const textWidth = ctx.measureText(label).width;
|
|
297
|
+
ctx.fillStyle = hueColor;
|
|
298
|
+
ctx.fillRect(bbox.x1, bbox.y1 - 20, textWidth + 8, 20);
|
|
299
|
+
// Draw label text
|
|
300
|
+
ctx.fillStyle = '#000';
|
|
301
|
+
ctx.fillText(label, bbox.x1 + 4, bbox.y1 - 5);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Draw pose skeleton on HTML Canvas
|
|
306
|
+
* @param ctx - Canvas 2D context
|
|
307
|
+
* @param people - Array of people with keypoints
|
|
308
|
+
* @param confidenceThreshold - Minimum keypoint confidence to draw (default: 0.3)
|
|
309
|
+
*/
|
|
310
|
+
export function drawPoseOnCanvas(ctx, people, confidenceThreshold = 0.3) {
|
|
311
|
+
// COCO17 skeleton connections (correct MMPose format)
|
|
312
|
+
// Keypoint order: 0=nose, 1=left_eye, 2=right_eye, 3=left_ear, 4=right_ear,
|
|
313
|
+
// 5=left_shoulder, 6=right_shoulder, 7=left_elbow, 8=right_elbow,
|
|
314
|
+
// 9=left_wrist, 10=right_wrist, 11=left_hip, 12=right_hip,
|
|
315
|
+
// 13=left_knee, 14=right_knee, 15=left_ankle, 16=right_ankle
|
|
316
|
+
const skeleton = [
|
|
317
|
+
[0, 1], [0, 2], // nose to eyes
|
|
318
|
+
[1, 3], [2, 4], // eyes to ears
|
|
319
|
+
[5, 6], // shoulders
|
|
320
|
+
[5, 7], [7, 9], // left arm
|
|
321
|
+
[6, 8], [8, 10], // right arm
|
|
322
|
+
[5, 11], [6, 12], // shoulders to hips
|
|
323
|
+
[11, 12], // hips
|
|
324
|
+
[11, 13], [13, 15], // left leg
|
|
325
|
+
[12, 14], [14, 16], // right leg
|
|
326
|
+
];
|
|
327
|
+
const keypointColors = [
|
|
328
|
+
'#FF0000', '#FF0000', '#FF0000', '#FF0000', '#FF0000', // Head
|
|
329
|
+
'#00FF00', '#00FF00', // Shoulders
|
|
330
|
+
'#00FF00', '#00FF00', '#00FF00', // Left arm
|
|
331
|
+
'#00FF00', '#00FF00', '#00FF00', // Right arm
|
|
332
|
+
'#0000FF', '#0000FF', // Torso
|
|
333
|
+
'#0000FF', // Hips
|
|
334
|
+
'#0000FF', '#0000FF', '#0000FF', // Left leg
|
|
335
|
+
'#0000FF', '#0000FF', '#0000FF', // Right leg
|
|
336
|
+
];
|
|
337
|
+
const skeletonColors = [
|
|
338
|
+
'#FF0000', '#FF0000', '#FF0000', '#FF0000', // Head
|
|
339
|
+
'#00FF00', // Shoulders
|
|
340
|
+
'#00FF00', '#00FF00', // Left arm
|
|
341
|
+
'#00FF00', '#00FF00', // Right arm
|
|
342
|
+
'#0000FF', '#0000FF', // Torso
|
|
343
|
+
'#0000FF', // Hips
|
|
344
|
+
'#0000FF', '#0000FF', '#0000FF', // Left leg
|
|
345
|
+
'#0000FF', '#0000FF', '#0000FF', // Right leg
|
|
346
|
+
];
|
|
347
|
+
people.forEach((person, personIdx) => {
|
|
348
|
+
const baseColor = `hsl(${personIdx * 60}, 80%, 50%)`;
|
|
349
|
+
const { bbox, keypoints } = person;
|
|
350
|
+
// Draw bounding box
|
|
351
|
+
ctx.strokeStyle = baseColor;
|
|
352
|
+
ctx.lineWidth = 2;
|
|
353
|
+
ctx.strokeRect(bbox.x1, bbox.y1, bbox.x2 - bbox.x1, bbox.y2 - bbox.y1);
|
|
354
|
+
// Draw label
|
|
355
|
+
const label = `Person ${personIdx + 1} ${(bbox.confidence * 100).toFixed(0)}%`;
|
|
356
|
+
ctx.font = 'bold 12px sans-serif';
|
|
357
|
+
const textWidth = ctx.measureText(label).width;
|
|
358
|
+
ctx.fillStyle = baseColor;
|
|
359
|
+
ctx.fillRect(bbox.x1, bbox.y1 - 20, textWidth + 8, 20);
|
|
360
|
+
ctx.fillStyle = '#000';
|
|
361
|
+
ctx.fillText(label, bbox.x1 + 4, bbox.y1 - 5);
|
|
362
|
+
// Draw skeleton lines
|
|
363
|
+
skeleton.forEach((link, linkIdx) => {
|
|
364
|
+
const [k1, k2] = link;
|
|
365
|
+
const kp1 = keypoints[k1];
|
|
366
|
+
const kp2 = keypoints[k2];
|
|
367
|
+
if (kp1 && kp2 && kp1.visible && kp2.visible) {
|
|
368
|
+
ctx.strokeStyle = skeletonColors[linkIdx] || baseColor;
|
|
369
|
+
ctx.lineWidth = 2;
|
|
370
|
+
ctx.beginPath();
|
|
371
|
+
ctx.moveTo(kp1.x, kp1.y);
|
|
372
|
+
ctx.lineTo(kp2.x, kp2.y);
|
|
373
|
+
ctx.stroke();
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
// Draw keypoints
|
|
377
|
+
keypoints.forEach((kp, kpIdx) => {
|
|
378
|
+
if (kp.visible) {
|
|
379
|
+
ctx.fillStyle = keypointColors[kpIdx] || baseColor;
|
|
380
|
+
ctx.beginPath();
|
|
381
|
+
ctx.arc(kp.x, kp.y, 4, 0, Math.PI * 2);
|
|
382
|
+
ctx.fill();
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Draw both detections and pose on canvas (convenience method)
|
|
389
|
+
* @param ctx - Canvas 2D context
|
|
390
|
+
* @param results - Detection or pose results
|
|
391
|
+
* @param mode - 'object' or 'pose'
|
|
392
|
+
*/
|
|
393
|
+
export function drawResultsOnCanvas(ctx, results, mode = 'object') {
|
|
394
|
+
if (mode === 'object') {
|
|
395
|
+
drawDetectionsOnCanvas(ctx, results);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
drawPoseOnCanvas(ctx, results);
|
|
399
|
+
}
|
|
400
|
+
}
|