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.
Files changed (150) hide show
  1. package/.gitattributes +1 -0
  2. package/README.md +202 -0
  3. package/dist/core/base.d.ts +20 -0
  4. package/dist/core/base.d.ts.map +1 -0
  5. package/dist/core/base.js +40 -0
  6. package/dist/core/file.d.ts +11 -0
  7. package/dist/core/file.d.ts.map +1 -0
  8. package/dist/core/file.js +111 -0
  9. package/dist/core/modelCache.d.ts +35 -0
  10. package/dist/core/modelCache.d.ts.map +1 -0
  11. package/dist/core/modelCache.js +161 -0
  12. package/dist/core/posePostprocessing.d.ts +12 -0
  13. package/dist/core/posePostprocessing.d.ts.map +1 -0
  14. package/dist/core/posePostprocessing.js +76 -0
  15. package/dist/core/postprocessing.d.ts +10 -0
  16. package/dist/core/postprocessing.d.ts.map +1 -0
  17. package/dist/core/postprocessing.js +70 -0
  18. package/dist/core/preprocessing.d.ts +14 -0
  19. package/dist/core/preprocessing.d.ts.map +1 -0
  20. package/dist/core/preprocessing.js +79 -0
  21. package/dist/index.d.ts +27 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +31 -0
  24. package/dist/models/rtmpose.d.ts +25 -0
  25. package/dist/models/rtmpose.d.ts.map +1 -0
  26. package/dist/models/rtmpose.js +185 -0
  27. package/dist/models/rtmpose3d.d.ts +28 -0
  28. package/dist/models/rtmpose3d.d.ts.map +1 -0
  29. package/dist/models/rtmpose3d.js +184 -0
  30. package/dist/models/yolo12.d.ts +23 -0
  31. package/dist/models/yolo12.d.ts.map +1 -0
  32. package/dist/models/yolo12.js +165 -0
  33. package/dist/models/yolox.d.ts +18 -0
  34. package/dist/models/yolox.d.ts.map +1 -0
  35. package/dist/models/yolox.js +167 -0
  36. package/dist/solution/animalDetector.d.ts +229 -0
  37. package/dist/solution/animalDetector.d.ts.map +1 -0
  38. package/dist/solution/animalDetector.js +663 -0
  39. package/dist/solution/body.d.ts +16 -0
  40. package/dist/solution/body.d.ts.map +1 -0
  41. package/dist/solution/body.js +52 -0
  42. package/dist/solution/bodyWithFeet.d.ts +16 -0
  43. package/dist/solution/bodyWithFeet.d.ts.map +1 -0
  44. package/dist/solution/bodyWithFeet.js +52 -0
  45. package/dist/solution/customDetector.d.ts +137 -0
  46. package/dist/solution/customDetector.d.ts.map +1 -0
  47. package/dist/solution/customDetector.js +342 -0
  48. package/dist/solution/hand.d.ts +14 -0
  49. package/dist/solution/hand.d.ts.map +1 -0
  50. package/dist/solution/hand.js +20 -0
  51. package/dist/solution/index.d.ts +10 -0
  52. package/dist/solution/index.d.ts.map +1 -0
  53. package/dist/solution/index.js +9 -0
  54. package/dist/solution/objectDetector.d.ts +172 -0
  55. package/dist/solution/objectDetector.d.ts.map +1 -0
  56. package/dist/solution/objectDetector.js +606 -0
  57. package/dist/solution/pose3dDetector.d.ts +145 -0
  58. package/dist/solution/pose3dDetector.d.ts.map +1 -0
  59. package/dist/solution/pose3dDetector.js +611 -0
  60. package/dist/solution/poseDetector.d.ts +198 -0
  61. package/dist/solution/poseDetector.d.ts.map +1 -0
  62. package/dist/solution/poseDetector.js +622 -0
  63. package/dist/solution/poseTracker.d.ts +22 -0
  64. package/dist/solution/poseTracker.d.ts.map +1 -0
  65. package/dist/solution/poseTracker.js +106 -0
  66. package/dist/solution/wholebody.d.ts +19 -0
  67. package/dist/solution/wholebody.d.ts.map +1 -0
  68. package/dist/solution/wholebody.js +82 -0
  69. package/dist/solution/wholebody3d.d.ts +22 -0
  70. package/dist/solution/wholebody3d.d.ts.map +1 -0
  71. package/dist/solution/wholebody3d.js +75 -0
  72. package/dist/types/index.d.ts +52 -0
  73. package/dist/types/index.d.ts.map +1 -0
  74. package/dist/types/index.js +5 -0
  75. package/dist/visualization/draw.d.ts +57 -0
  76. package/dist/visualization/draw.d.ts.map +1 -0
  77. package/dist/visualization/draw.js +400 -0
  78. package/dist/visualization/skeleton/coco133.d.ts +350 -0
  79. package/dist/visualization/skeleton/coco133.d.ts.map +1 -0
  80. package/dist/visualization/skeleton/coco133.js +120 -0
  81. package/dist/visualization/skeleton/coco17.d.ts +180 -0
  82. package/dist/visualization/skeleton/coco17.d.ts.map +1 -0
  83. package/dist/visualization/skeleton/coco17.js +48 -0
  84. package/dist/visualization/skeleton/halpe26.d.ts +278 -0
  85. package/dist/visualization/skeleton/halpe26.d.ts.map +1 -0
  86. package/dist/visualization/skeleton/halpe26.js +70 -0
  87. package/dist/visualization/skeleton/hand21.d.ts +196 -0
  88. package/dist/visualization/skeleton/hand21.d.ts.map +1 -0
  89. package/dist/visualization/skeleton/hand21.js +51 -0
  90. package/dist/visualization/skeleton/index.d.ts +10 -0
  91. package/dist/visualization/skeleton/index.d.ts.map +1 -0
  92. package/dist/visualization/skeleton/index.js +9 -0
  93. package/dist/visualization/skeleton/openpose134.d.ts +357 -0
  94. package/dist/visualization/skeleton/openpose134.d.ts.map +1 -0
  95. package/dist/visualization/skeleton/openpose134.js +116 -0
  96. package/dist/visualization/skeleton/openpose18.d.ts +177 -0
  97. package/dist/visualization/skeleton/openpose18.d.ts.map +1 -0
  98. package/dist/visualization/skeleton/openpose18.js +47 -0
  99. package/docs/ANIMAL_DETECTOR.md +450 -0
  100. package/docs/CUSTOM_DETECTOR.md +568 -0
  101. package/docs/OBJECT_DETECTOR.md +373 -0
  102. package/docs/POSE3D_DETECTOR.md +458 -0
  103. package/docs/POSE_DETECTOR.md +442 -0
  104. package/examples/README.md +119 -0
  105. package/examples/index.html +746 -0
  106. package/package.json +51 -0
  107. package/playground/README.md +114 -0
  108. package/playground/app/favicon.ico +0 -0
  109. package/playground/app/globals.css +17 -0
  110. package/playground/app/layout.tsx +19 -0
  111. package/playground/app/page.tsx +1338 -0
  112. package/playground/eslint.config.mjs +18 -0
  113. package/playground/next.config.ts +34 -0
  114. package/playground/package-lock.json +6723 -0
  115. package/playground/package.json +27 -0
  116. package/playground/postcss.config.mjs +7 -0
  117. package/playground/tsconfig.json +34 -0
  118. package/src/core/base.ts +66 -0
  119. package/src/core/file.ts +141 -0
  120. package/src/core/modelCache.ts +189 -0
  121. package/src/core/posePostprocessing.ts +91 -0
  122. package/src/core/postprocessing.ts +93 -0
  123. package/src/core/preprocessing.ts +127 -0
  124. package/src/index.ts +69 -0
  125. package/src/models/rtmpose.ts +265 -0
  126. package/src/models/rtmpose3d.ts +289 -0
  127. package/src/models/yolo12.ts +220 -0
  128. package/src/models/yolox.ts +214 -0
  129. package/src/solution/animalDetector.ts +955 -0
  130. package/src/solution/body.ts +89 -0
  131. package/src/solution/bodyWithFeet.ts +89 -0
  132. package/src/solution/customDetector.ts +474 -0
  133. package/src/solution/hand.ts +52 -0
  134. package/src/solution/index.ts +10 -0
  135. package/src/solution/objectDetector.ts +816 -0
  136. package/src/solution/pose3dDetector.ts +890 -0
  137. package/src/solution/poseDetector.ts +892 -0
  138. package/src/solution/poseTracker.ts +172 -0
  139. package/src/solution/wholebody.ts +130 -0
  140. package/src/solution/wholebody3d.ts +125 -0
  141. package/src/types/index.ts +62 -0
  142. package/src/visualization/draw.ts +543 -0
  143. package/src/visualization/skeleton/coco133.ts +131 -0
  144. package/src/visualization/skeleton/coco17.ts +49 -0
  145. package/src/visualization/skeleton/halpe26.ts +71 -0
  146. package/src/visualization/skeleton/hand21.ts +52 -0
  147. package/src/visualization/skeleton/index.ts +10 -0
  148. package/src/visualization/skeleton/openpose134.ts +125 -0
  149. package/src/visualization/skeleton/openpose18.ts +48 -0
  150. 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
+ }