@@ -24,22 +24,24 @@ class PoseEstimator(
2424 }
2525
2626 companion object {
27- // xywh(4) + conf(1) + keypoints(17*3=51) = 56
28- private const val OUTPUT_FEATURES = 56
29- private const val KEYPOINTS_COUNT = 17
27+ private const val BOX_CONF_FEATURES = 5 // Normal pose prefix: box x/y/w/h plus confidence.
28+ private const val KEYPOINT_FEATURES = 3 // One keypoint triplet: x, y, and confidence.
29+ private const val END_TO_END_MAX_ROWS = 300 // Ultralytics default max_det for capped end-to-end outputs.
3030 }
3131
3232 // Reusable float input for the CompiledModel input buffer.
3333 private lateinit var floatInput: FloatArray
3434 private lateinit var inputBitmap: Bitmap
3535 private lateinit var intValues: IntArray
36-
36+
3737 // Reuse output arrays to reduce allocations
3838 private var outDim2 = 0
39-
39+
4040 // Output dimensions
4141 private var batchSize = 0
4242 private var numAnchors = 0
43+ private var keypointStart = BOX_CONF_FEATURES
44+ private var keypointCount = 0
4345 private var isEndToEnd = false
4446 private lateinit var outputLayout: OutputLayout
4547
@@ -61,13 +63,22 @@ class PoseEstimator(
6163 modelInputSize = Pair (inWidth, inHeight)
6264
6365 val outputShape = rtModel.outputDims.getOrNull(0 ) ? : IntArray (0 )
64- batchSize = if (outputShape.isNotEmpty()) outputShape[0 ] else 1
65- isEndToEnd = outputShape[2 ] < outputShape[1 ] && outputShape[2 ] >= 6
66+ require(outputShape.size >= 3 ) {
67+ " Unexpected pose output shape. Expected [batch, features, anchors] or [batch, anchors, features], Actual=${outputShape.contentToString()} "
68+ }
69+
70+ batchSize = outputShape[0 ]
71+ val featuresFirstPose = isPoseFeatureCount(outputShape[1 ]) && outputShape[2 ] > END_TO_END_MAX_ROWS
72+ val anchorsFirstPose = isPoseFeatureCount(outputShape[2 ]) && outputShape[1 ] > END_TO_END_MAX_ROWS
73+ isEndToEnd = ! featuresFirstPose &&
74+ ! anchorsFirstPose &&
75+ outputShape[1 ] <= END_TO_END_MAX_ROWS &&
76+ isEndToEndFeatureCount(outputShape[2 ])
6677 outputLayout = when {
67- outputShape[ 1 ] == OUTPUT_FEATURES -> OutputLayout .FEATURES_FIRST
68- outputShape[ 2 ] == OUTPUT_FEATURES || isEndToEnd -> OutputLayout .ANCHORS_FIRST
78+ featuresFirstPose -> OutputLayout .FEATURES_FIRST
79+ isEndToEnd || anchorsFirstPose -> OutputLayout .ANCHORS_FIRST
6980 else -> throw IllegalArgumentException (
70- " Unexpected output feature size. Expected $OUTPUT_FEATURES or end-to-end rows, Actual=${outputShape.contentToString()} "
81+ " Unexpected pose output feature size. Expected 5 + keypoints * 3 features or end-to-end rows, Actual=${outputShape.contentToString()} "
7182 )
7283 }
7384
@@ -83,6 +94,8 @@ class PoseEstimator(
8394 }
8495 }
8596
97+ keypointStart = if (isEndToEnd && (outFeatures - 6 ) % 3 == 0 ) 6 else BOX_CONF_FEATURES
98+ keypointCount = keypointCountFromFeatureCount(outFeatures, keypointStart)
8699 outDim2 = outputShape[2 ]
87100
88101 floatInput = FloatArray (inWidth * inHeight * 3 )
@@ -122,7 +135,7 @@ class PoseEstimator(
122135
123136 // Apply numItemsThreshold limit
124137 val limitedDetections = rawDetections.take(numItemsThreshold)
125-
138+
126139 val boxes = limitedDetections.map { it.box }
127140 val keypointsList = limitedDetections.map { it.keypoints }
128141
@@ -181,10 +194,10 @@ class PoseEstimator(
181194
182195 val kpArray = mutableListOf<Pair <Float , Float >>()
183196 val kpConfArray = mutableListOf<Float >()
184- for (k in 0 until KEYPOINTS_COUNT ) {
185- val rawKx = featureValue(flat, 5 + k * 3 , j)
186- val rawKy = featureValue(flat, 5 + k * 3 + 1 , j)
187- val kpC = featureValue(flat, 5 + k * 3 + 2 , j)
197+ for (k in 0 until keypointCount ) {
198+ val rawKx = featureValue(flat, BOX_CONF_FEATURES + k * 3 , j)
199+ val rawKy = featureValue(flat, BOX_CONF_FEATURES + k * 3 + 1 , j)
200+ val kpC = featureValue(flat, BOX_CONF_FEATURES + k * 3 + 2 , j)
188201
189202 val (finalKx, finalKy) = inputPointFromOutputPoint(
190203 rawKx,
@@ -202,13 +215,13 @@ class PoseEstimator(
202215 (fx / origWidth) to (fy / origHeight)
203216 }
204217 val boxObj = Box (0 , labelName(0 ), conf, rectF, normBox)
205-
218+
206219 val keypoints = Keypoints (
207220 xyn = xynList,
208221 xy = kpArray,
209222 conf = kpConfArray
210223 )
211-
224+
212225 detections.add(
213226 PoseDetection (
214227 box = boxObj,
@@ -228,9 +241,6 @@ class PoseEstimator(
228241 origHeight : Int
229242 ): List <PoseDetection > {
230243 val detections = mutableListOf<PoseDetection >()
231- val fieldCount = outDim2
232- val keypointStart = if ((fieldCount - 6 ) % 3 == 0 ) 6 else 5
233- val keypointCount = (fieldCount - keypointStart) / 3
234244
235245 for (j in 0 until numAnchors) {
236246 val conf = featureValue(flat, 4 , j)
@@ -295,18 +305,34 @@ class PoseEstimator(
295305 }
296306 }
297307
308+ private fun isPoseFeatureCount (featureCount : Int ): Boolean {
309+ return featureCount >= BOX_CONF_FEATURES + KEYPOINT_FEATURES &&
310+ (featureCount - BOX_CONF_FEATURES ) % KEYPOINT_FEATURES == 0
311+ }
312+
313+ private fun isEndToEndFeatureCount (featureCount : Int ): Boolean {
314+ return isPoseFeatureCount(featureCount) ||
315+ (featureCount >= 6 + KEYPOINT_FEATURES && (featureCount - 6 ) % KEYPOINT_FEATURES == 0 )
316+ }
317+
318+ private fun keypointCountFromFeatureCount (featureCount : Int , keypointStart : Int = BOX_CONF_FEATURES ): Int {
319+ require(featureCount >= keypointStart && (featureCount - keypointStart) % KEYPOINT_FEATURES == 0 ) {
320+ " Unexpected pose output feature size. Expected keypoint triplets after $keypointStart box fields, Actual=$featureCount "
321+ }
322+ return (featureCount - keypointStart) / KEYPOINT_FEATURES
323+ }
298324
299325 private fun nmsPoseDetections (
300326 detections : List <PoseDetection >,
301327 iouThreshold : Float
302328 ): List <PoseDetection > {
303329 val confidenceThreshold = 0.25f // Hardcoded second-pass threshold
304330 val filteredDetections = detections.filter { it.box.conf >= confidenceThreshold }
305-
331+
306332 if (filteredDetections.size <= 1 ) {
307333 return filteredDetections
308334 }
309-
335+
310336 val sorted = filteredDetections.sortedByDescending { it.box.conf }
311337 val picked = mutableListOf<PoseDetection >()
312338 val used = BooleanArray (sorted.size)
@@ -340,7 +366,6 @@ class PoseEstimator(
340366 return if (unionArea <= 0f ) 0f else (interArea / unionArea)
341367 }
342368
343-
344369 override fun setConfidenceThreshold (conf : Double ) {
345370 confidenceThreshold = conf.toFloat()
346371 super .setConfidenceThreshold(conf)
@@ -350,7 +375,7 @@ class PoseEstimator(
350375 iouThreshold = iou.toFloat()
351376 super .setIouThreshold(iou)
352377 }
353-
378+
354379 override fun setNumItemsThreshold (n : Int ) {
355380 numItemsThreshold = n
356381 super .setNumItemsThreshold(n)
@@ -368,5 +393,4 @@ class PoseEstimator(
368393 val box : Box ,
369394 val keypoints : Keypoints
370395 )
371-
372396}
0 commit comments