alpha
Login
or
Join now
nateholland.bsky.social
/
PoseDetection
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
This repository has no description
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
feat: specify object for each usecase
author
nathan holland
date
11 months ago
(Jul 27, 2025, 11:32 PM +0100)
commit
64b4b87a
64b4b87a3ea28137c7a0204c8115c404595c3acd
parent
1c61763f
1c61763f5f29f55ecf22d5612bdcaea10283abc5
+84
-79
10 changed files
Expand all
Collapse all
Unified
Split
posedetection
src
androidMain
kotlin
com
performancecoachlab
posedetection
recording
InputFrame.android.kt
com.performancecoachlab
posedetection
camera
CameraView.android.kt
commonMain
kotlin
com
performancecoachlab
posedetection
camera
CameraView.kt
custom
CustomObjectModel.kt
recording
InputFrame.kt
iosMain
kotlin
com
performancecoachlab
posedetection
camera
CameraEngine.kt
CameraView.ios.kt
FrameProcessor.kt
recording
InputFrame.ios.kt
sample
composeApp
src
commonMain
kotlin
com
nate
posedetection
App.kt
+11
-4
posedetection/src/androidMain/kotlin/com.performancecoachlab/posedetection/camera/CameraView.android.kt
Reviewed
···
47
47
import androidx.compose.runtime.rememberCoroutineScope
48
48
import androidx.compose.ui.geometry.Rect
49
49
import com.google.mlkit.vision.pose.PoseDetector
50
50
-
import com.performancecoachlab.posedetection.custom.CustomObjectDetectorModels
51
50
import com.performancecoachlab.posedetection.custom.CustomObjectRespository
51
51
+
import com.performancecoachlab.posedetection.custom.ObjectModel
52
52
import com.performancecoachlab.posedetection.recording.AnalysisObject
53
53
import com.performancecoachlab.posedetection.recording.AnalysisResult
54
54
import kotlinx.coroutines.launch
···
61
61
skeletonRepository: SkeletonRepository,
62
62
customObjectRepository: CustomObjectRespository,
63
63
drawSkeleton: Boolean,
64
64
+
objectModel: ObjectModel?,
64
65
drawObjects: Boolean,
65
66
modifier: Modifier,
66
67
frontCamera: Boolean,
···
77
78
val lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current
78
79
val previewView: PreviewView = remember { PreviewView(context) }
79
80
val executor = remember { Executors.newSingleThreadExecutor() }
80
80
-
val objDetector = CustomObjectDetectorModels.getInstance().model
81
81
val scope = rememberCoroutineScope()
82
82
var firstFrameTimestamp: Long? = null
83
83
-
var focus by remember { mutableStateOf<Rect?>(focusArea) }
83
83
+
var focus by remember { mutableStateOf(focusArea) }
84
84
+
var objectDetector by remember { mutableStateOf(objectModel) }
84
85
85
86
// Update focus when focusArea changes
86
87
LaunchedEffect(focusArea) {
87
88
focus = focusArea
89
89
+
}
90
90
+
91
91
+
// Update object model when it changes
92
92
+
LaunchedEffect(objectModel) {
93
93
+
objectDetector = objectModel
88
94
}
89
95
90
96
// Video recording state
···
144
150
analysis.setAnalyzer(executor) { imageProxy ->
145
151
val timestamp = System.currentTimeMillis()
146
152
val area = focus
153
153
+
val currentObjectDetector = objectDetector?.getDetector()
147
154
imageProxy.process(
148
148
-
objDetector?.getDetector(), poseDetector, timestamp, area
155
155
+
currentObjectDetector, poseDetector, timestamp, area
149
156
){
150
157
customObjectRepository.updateCustomObject(it.objects)
151
158
it.skeleton?.let { skel ->
+3
-3
posedetection/src/androidMain/kotlin/com/performancecoachlab/posedetection/recording/InputFrame.android.kt
Reviewed
···
9
9
import com.performancecoachlab.posedetection.camera.drawAnalysisResults
10
10
import com.performancecoachlab.posedetection.camera.drawSkeleton
11
11
import com.performancecoachlab.posedetection.camera.process
12
12
-
import com.performancecoachlab.posedetection.custom.CustomObjectDetectorModels
12
12
+
import com.performancecoachlab.posedetection.custom.ObjectModel
13
13
import com.performancecoachlab.posedetection.skeleton.Skeleton
14
14
import kotlinx.coroutines.suspendCancellableCoroutine
15
15
import kotlin.coroutines.resume
···
28
28
}
29
29
}
30
30
31
31
-
actual class FrameAnalyser actual constructor() {
31
31
+
actual class FrameAnalyser actual constructor(val model: ObjectModel?) {
32
32
private val options =
33
33
PoseDetectorOptions.Builder().setDetectorMode(PoseDetectorOptions.STREAM_MODE).build()
34
34
private val poseDetector = PoseDetection.getClient(options)
35
35
-
private val objDetector = CustomObjectDetectorModels.getInstance().model?.getDetector()
35
35
+
private val objDetector = model?.getDetector()
36
36
actual suspend fun analyseFrame(inputFrame: InputFrame, focusArea: Rect?): AnalysisResult =
37
37
suspendCancellableCoroutine { continuation ->
38
38
inputFrame.bitmap.process(
+2
posedetection/src/commonMain/kotlin/com/performancecoachlab/posedetection/camera/CameraView.kt
Reviewed
···
5
5
import androidx.compose.ui.Modifier
6
6
import androidx.compose.ui.geometry.Rect
7
7
import com.performancecoachlab.posedetection.custom.CustomObjectRespository
8
8
+
import com.performancecoachlab.posedetection.custom.ObjectModel
8
9
import com.performancecoachlab.posedetection.skeleton.SkeletonRepository
9
10
10
11
@Composable
···
12
13
skeletonRepository: SkeletonRepository,
13
14
customObjectRepository: CustomObjectRespository,
14
15
drawSkeleton: Boolean = true,
16
16
+
objectModel: ObjectModel? = null,
15
17
drawObjects: Boolean = true,
16
18
modifier: Modifier = Modifier.fillMaxSize(),
17
19
frontCamera: Boolean = true,
-18
posedetection/src/commonMain/kotlin/com/performancecoachlab/posedetection/custom/CustomObjectModel.kt
Reviewed
···
8
8
val iosModelPath: String? = null,
9
9
)
10
10
11
11
-
class CustomObjectDetectorModels private constructor(
12
12
-
val model: ObjectModel?
13
13
-
) {
14
14
-
companion object {
15
15
-
@Volatile
16
16
-
private var instance = CustomObjectDetectorModels(null)
17
17
-
18
18
-
@Composable
19
19
-
fun init(path: ModelPath) {
20
20
-
val model = initialiseObjectModel(path)
21
21
-
instance = CustomObjectDetectorModels(model = model)
22
22
-
}
23
23
-
24
24
-
fun getInstance(): CustomObjectDetectorModels = instance
25
25
-
}
26
26
-
}
27
27
-
28
11
@Composable
29
12
expect fun initialiseObjectModel(modelPath: ModelPath): ObjectModel
30
13
31
14
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
32
15
expect class ObjectModel {
33
33
-
34
16
}
35
17
+2
-1
posedetection/src/commonMain/kotlin/com/performancecoachlab/posedetection/recording/InputFrame.kt
Reviewed
···
2
2
3
3
import androidx.compose.ui.geometry.Rect
4
4
import androidx.compose.ui.graphics.ImageBitmap
5
5
+
import com.performancecoachlab.posedetection.custom.ObjectModel
5
6
import com.performancecoachlab.posedetection.skeleton.Skeleton
6
7
7
8
expect class InputFrame {
···
12
13
val timestamp: Long
13
14
}
14
15
15
15
-
expect class FrameAnalyser() {
16
16
+
expect class FrameAnalyser(model: ObjectModel?) {
16
17
suspend fun analyseFrame(inputFrame: InputFrame, focusArea: Rect? = null): AnalysisResult
17
18
}
18
19
+10
-17
posedetection/src/iosMain/kotlin/com/performancecoachlab/posedetection/camera/CameraEngine.kt
Reviewed
···
10
10
import androidx.compose.ui.graphics.toComposeImageBitmap
11
11
import androidx.compose.ui.unit.Density
12
12
import androidx.compose.ui.unit.LayoutDirection
13
13
-
import com.performancecoachlab.posedetection.custom.CustomObjectDetectorModels
14
13
import com.performancecoachlab.posedetection.custom.CustomObjectRespository
14
14
+
import com.performancecoachlab.posedetection.custom.ObjectModel
15
15
import com.performancecoachlab.posedetection.recording.AnalysisObject
16
16
import com.performancecoachlab.posedetection.recording.AnalysisResult
17
17
import com.performancecoachlab.posedetection.skeleton.Skeleton
···
227
227
fun setFocusArea(focusArea: Rect?) {
228
228
cameraController.setFocusArea(focusArea)
229
229
}
230
230
+
231
231
+
fun setObjectModel(objectModel: ObjectModel?){
232
232
+
cameraController.setObjectModel(objectModel)
233
233
+
}
230
234
}
231
235
232
236
class CameraController : NSObject(), AVCaptureVideoDataOutputSampleBufferDelegateProtocol,
···
293
297
frameProcessor.setFocusArea(focusArea)
294
298
}
295
299
300
300
+
fun setObjectModel(objectModel: ObjectModel?){
301
301
+
frameProcessor.setObjectModel(objectModel)
302
302
+
}
303
303
+
296
304
fun setupSession() {
297
305
try {
298
306
captureSession = AVCaptureSession()
···
460
468
}
461
469
}
462
470
463
463
-
private val objectDetector = CustomObjectDetectorModels.getInstance().model?.getModel()
464
464
-
private val frameProcessor = FrameProcessor(objectDetector)
471
471
+
private val frameProcessor = FrameProcessor(null)
465
472
466
473
@OptIn(ExperimentalForeignApi::class, NativeRuntimeApi::class)
467
474
override fun captureOutput(
···
481
488
frameProcessor.analyseBufferForAll(
482
489
CMSampleBufferGetImageBuffer(didOutputSampleBuffer),
483
490
timestamp,
484
484
-
mapPoint = { point: Skeleton.SkeletonCoordinate ->
485
485
-
val normalizedPoint = CGPointMake(
486
486
-
point.x.toDouble() / 480f, point.y.toDouble() / 360f
487
487
-
)
488
488
-
cameraPreviewLayer?.let { preview ->
489
489
-
val screenPoint =
490
490
-
preview.pointForCaptureDevicePointOfInterest(
491
491
-
normalizedPoint
492
492
-
)
493
493
-
Skeleton.SkeletonCoordinate(
494
494
-
screenPoint.useContents { x.toFloat() },
495
495
-
screenPoint.useContents { y.toFloat() })
496
496
-
}?:point
497
497
-
},
498
491
preview = cameraPreviewLayer,
499
492
onSkeletonProcessed = { skeleton ->
500
493
skeleton?.also {
+13
posedetection/src/iosMain/kotlin/com/performancecoachlab/posedetection/camera/CameraView.ios.kt
Reviewed
···
9
9
import androidx.compose.runtime.getValue
10
10
import androidx.compose.runtime.mutableStateOf
11
11
import androidx.compose.runtime.remember
12
12
+
import androidx.compose.runtime.rememberCoroutineScope
12
13
import androidx.compose.runtime.setValue
13
14
import androidx.compose.ui.Modifier
14
15
import androidx.compose.ui.geometry.Rect
15
16
import androidx.compose.ui.layout.ContentScale
16
17
import com.performancecoachlab.posedetection.custom.CustomObjectRespository
18
18
+
import com.performancecoachlab.posedetection.custom.ObjectModel
17
19
import com.performancecoachlab.posedetection.skeleton.SkeletonRepository
18
20
import kotlinx.cinterop.BetaInteropApi
19
21
import kotlinx.cinterop.autoreleasepool
22
22
+
import kotlinx.coroutines.delay
20
23
import kotlin.native.runtime.NativeRuntimeApi
21
24
22
25
@OptIn(NativeRuntimeApi::class, BetaInteropApi::class)
···
25
28
skeletonRepository: SkeletonRepository,
26
29
customObjectRepository:CustomObjectRespository,
27
30
drawSkeleton: Boolean,
31
31
+
objectModel: ObjectModel?,
28
32
drawObjects: Boolean,
29
33
modifier: Modifier,
30
34
frontCamera: Boolean,
···
54
58
}
55
59
LaunchedEffect(focusArea){
56
60
cameraEngine.value?.setFocusArea(focusArea)
61
61
+
}
62
62
+
LaunchedEffect(objectModel) {
63
63
+
cameraEngine.value?.setObjectModel(objectModel)
64
64
+
}
65
65
+
LaunchedEffect(Unit){
66
66
+
delay(1000L)
67
67
+
cameraEngine.value?.setObjectModel(objectModel)
68
68
+
cameraEngine.value?.setFocusArea(focusArea)
69
69
+
println("model set")
57
70
}
58
71
Box(modifier = Modifier.fillMaxSize()) {
59
72
CameraPreview(
+18
-13
posedetection/src/iosMain/kotlin/com/performancecoachlab/posedetection/camera/FrameProcessor.kt
Reviewed
···
2
2
3
3
import androidx.compose.ui.geometry.Offset
4
4
import androidx.compose.ui.geometry.Rect
5
5
+
import com.performancecoachlab.posedetection.custom.ObjectModel
5
6
import com.performancecoachlab.posedetection.recording.AnalysisObject
6
7
import com.performancecoachlab.posedetection.recording.Label
7
8
import com.performancecoachlab.posedetection.skeleton.Skeleton
···
127
128
}
128
129
129
130
@OptIn(ExperimentalForeignApi::class)
130
130
-
class FrameProcessor(val modelObj: VNCoreMLModel?) {
131
131
+
class FrameProcessor(var modelObj: VNCoreMLModel?) {
131
132
private val skelBuffer = SkelBuffer(maxSize = 10)
132
133
private var regionOfInterest = CGRectMake(0.0, 0.0, 1.0, 1.0)
133
134
private var requests = mutableListOf<VNRequest>()
134
135
val objectRecognition = setUpRecognition()
135
136
private var focusArea: Rect? = null
137
137
+
private var path = ""
136
138
137
139
fun setFocusArea(focusArea: Rect?) {
138
140
this.focusArea = focusArea
141
141
+
}
142
142
+
143
143
+
fun setObjectModel(objectModel: ObjectModel?){
144
144
+
modelObj = objectModel?.getModel()
145
145
+
setUpRecognition()
139
146
}
140
147
141
148
private fun setUpRecognition() {
142
142
-
if (modelObj == null) {
143
143
-
return
144
144
-
}
145
145
-
try {
146
146
-
val objectRecognition = VNCoreMLRequest(modelObj) { request, error ->
147
147
-
val results = request?.results as? List<*> ?: return@VNCoreMLRequest
148
148
-
val recognized = results.filterIsInstance<VNRecognizedObjectObservation>()
149
149
-
processResults(recognized)
149
149
+
modelObj?.let {
150
150
+
try {
151
151
+
val objectRecognition = VNCoreMLRequest(it) { request, error ->
152
152
+
val results = request?.results as? List<*> ?: return@VNCoreMLRequest
153
153
+
val recognized = results.filterIsInstance<VNRecognizedObjectObservation>()
154
154
+
processResults(recognized)
155
155
+
}
156
156
+
requests = mutableListOf(objectRecognition)
157
157
+
} catch (e: Throwable) {
158
158
+
println("Vision setup error: ${e.message}")
150
159
}
151
151
-
requests = mutableListOf(objectRecognition)
152
152
-
} catch (e: Throwable) {
153
153
-
println("Vision setup error: ${e.message}")
154
160
}
155
161
}
156
162
···
346
352
fun analyseBufferForAll(
347
353
buffer: CVImageBufferRef?,
348
354
timestamp: Long,
349
349
-
mapPoint: (skeleton: Skeleton.SkeletonCoordinate) -> Skeleton.SkeletonCoordinate,
350
355
preview: AVCaptureVideoPreviewLayer?,
351
356
onObjectsProcessed: (List<AnalysisObject>) -> Unit,
352
357
onSkeletonProcessed: (Skeleton?) -> Unit
+4
-4
posedetection/src/iosMain/kotlin/com/performancecoachlab/posedetection/recording/InputFrame.ios.kt
Reviewed
···
6
6
import com.performancecoachlab.posedetection.camera.drawAnalysisResults
7
7
import com.performancecoachlab.posedetection.camera.drawSkeleton
8
8
import com.performancecoachlab.posedetection.camera.toImageBitmap
9
9
-
import com.performancecoachlab.posedetection.custom.CustomObjectDetectorModels
9
9
+
import com.performancecoachlab.posedetection.custom.ObjectModel
10
10
import com.performancecoachlab.posedetection.skeleton.Skeleton
11
11
import kotlinx.cinterop.ExperimentalForeignApi
12
12
import kotlinx.coroutines.suspendCancellableCoroutine
···
35
35
}
36
36
}
37
37
38
38
-
actual class FrameAnalyser actual constructor() {
39
39
-
private val modelObj = CustomObjectDetectorModels.getInstance().model?.getModel()
38
38
+
actual class FrameAnalyser actual constructor(val model: ObjectModel?) {
39
39
+
private val modelObj = model?.getModel()
40
40
private val frameProcessor = FrameProcessor(modelObj)
41
41
42
42
@OptIn(ExperimentalForeignApi::class)
···
54
54
continuation.resume(AnalysisResult(poseResult, objectResults))
55
55
}
56
56
}
57
57
-
}
57
57
+
}
+21
-19
sample/composeApp/src/commonMain/kotlin/com/nate/posedetection/App.kt
Reviewed
···
1
1
package com.nate.posedetection
2
2
3
3
import androidx.compose.animation.AnimatedContent
4
4
-
import androidx.compose.animation.AnimatedVisibility
5
4
import androidx.compose.foundation.Image
6
5
import androidx.compose.foundation.layout.Box
7
6
import androidx.compose.foundation.layout.Column
8
7
import androidx.compose.foundation.layout.fillMaxSize
9
8
import androidx.compose.foundation.layout.imePadding
10
9
import androidx.compose.foundation.layout.padding
11
11
-
import androidx.compose.foundation.layout.requiredHeight
12
12
-
import androidx.compose.foundation.layout.requiredWidth
13
13
-
import androidx.compose.foundation.layout.safeDrawing
14
10
import androidx.compose.material3.Button
15
11
import androidx.compose.material3.CircularProgressIndicator
16
12
import androidx.compose.material3.Tab
···
27
23
import androidx.compose.runtime.setValue
28
24
import androidx.compose.ui.Alignment
29
25
import androidx.compose.ui.Modifier
30
30
-
import androidx.compose.ui.geometry.Rect
31
26
import androidx.compose.ui.graphics.ImageBitmap
32
27
import androidx.compose.ui.layout.ContentScale
33
28
import androidx.compose.ui.unit.dp
34
29
import androidx.compose.ui.unit.sp
35
30
import chaintech.videoplayer.host.MediaPlayerHost
36
36
-
import chaintech.videoplayer.model.PlayerSpeed
37
31
import chaintech.videoplayer.model.ScreenResize
38
32
import chaintech.videoplayer.model.VideoPlayerConfig
39
33
import chaintech.videoplayer.ui.video.VideoPlayerComposable
40
34
import chaintech.videoplayer.util.RetrieveMediaDuration
41
35
import com.nate.posedetection.theme.AppTheme
42
36
import com.performancecoachlab.posedetection.camera.CameraView
43
43
-
import com.performancecoachlab.posedetection.camera.drawSkeleton
44
44
-
import com.performancecoachlab.posedetection.custom.CustomObjectDetectorModels
45
37
import com.performancecoachlab.posedetection.custom.CustomObjectRespository
46
38
import com.performancecoachlab.posedetection.custom.ModelPath
39
39
+
import com.performancecoachlab.posedetection.custom.initialiseObjectModel
47
40
import com.performancecoachlab.posedetection.encoding.VideoBuilder
48
41
import com.performancecoachlab.posedetection.encoding.createVideoBuilder
49
42
import com.performancecoachlab.posedetection.permissions.PermissionProvider
···
62
55
import io.github.vinceglb.filekit.filesDir
63
56
import io.github.vinceglb.filekit.path
64
57
import kotlinx.coroutines.Job
65
65
-
import kotlinx.coroutines.delay
66
58
import kotlinx.coroutines.launch
67
59
import kotlin.math.roundToLong
68
60
69
61
@Composable
70
62
internal fun App() = AppTheme {
71
71
-
var selectedTabIndex by remember { mutableStateOf(0) }
63
63
+
var selectedTabIndex by remember { mutableStateOf(1) }
72
64
val tabs = listOf("Camera Feed", "Recorded Video")
73
73
-
CustomObjectDetectorModels.init(
74
74
-
ModelPath("lite-model_efficientdet_lite2_detection_metadata_1.tflite", "YOLOv3FP16")
75
75
-
)
76
65
Column {
77
66
TabRow(selectedTabIndex = selectedTabIndex) {
78
67
tabs.forEachIndexed { index, title ->
···
140
129
var image by remember { mutableStateOf<InputFrame?>(null) }
141
130
val timeRange = Pair(0L, duration)
142
131
var frame by remember { mutableStateOf(timeRange.first) }
143
143
-
//val frameAnalyser = FrameAnalyser("4.tflite")
144
144
-
val frameAnalyser by remember { mutableStateOf(FrameAnalyser())}
132
132
+
val generalModel = initialiseObjectModel(
133
133
+
ModelPath(
134
134
+
"lite-model_efficientdet_lite2_detection_metadata_1.tflite",
135
135
+
"YOLOv3FP16"
136
136
+
)
137
137
+
)
138
138
+
val frameAnalyser by remember { mutableStateOf(FrameAnalyser(generalModel)) }
145
139
var bitmap by remember { mutableStateOf<ImageBitmap?>(null) }
146
140
val videoBuilder = remember { mutableStateOf<VideoBuilder?>(null) }
147
141
var isRecording by remember { mutableStateOf(false) }
···
204
198
startRecording()
205
199
}
206
200
if (firstFrameTimestamp == null) firstFrameTimestamp = frame.timestamp
207
207
-
val relativeTimestamp = frame.timestamp - (firstFrameTimestamp ?: frame.timestamp)
201
201
+
val relativeTimestamp =
202
202
+
frame.timestamp - (firstFrameTimestamp ?: frame.timestamp)
208
203
videoBuilder.value?.addFrame(it, relativeTimestamp)
209
204
bitmap = it
210
205
}
···
285
280
var permissionGranted by remember { mutableStateOf(false) }
286
281
var isRecording by remember { mutableStateOf(false) }
287
282
var path by remember { mutableStateOf("") }
283
283
+
val generalModel = initialiseObjectModel(
284
284
+
ModelPath(
285
285
+
"lite-model_efficientdet_lite2_detection_metadata_1.tflite",
286
286
+
"YOLOv3FP16"
287
287
+
)
288
288
+
)
288
289
PermissionProvider().apply {
289
290
if (!hasCameraPermission()) RequestCameraPermission(onGranted = {
290
291
permissionGranted = true
···
292
293
}
293
294
if (permissionGranted) {
294
295
Box(modifier = Modifier.fillMaxSize()) {
295
295
-
Column (modifier = Modifier.fillMaxSize()){
296
296
+
Column(modifier = Modifier.fillMaxSize()) {
296
297
androidx.compose.animation.AnimatedVisibility(path.isNotBlank()) {
297
298
val playerHost = remember(path) {
298
299
MediaPlayerHost(
···
323
324
skeletonRepository = skeletonRepository,
324
325
customObjectRepository = customObjectRespository,
325
326
drawSkeleton = true,
327
327
+
objectModel = generalModel,
326
328
drawObjects = true,
327
329
modifier = Modifier.weight(1f),
328
328
-
frontCamera = true,
330
330
+
frontCamera = false,
329
331
isRecording = isRecording,
330
332
onRecordToggled = { isRecording = it },
331
333
onVideoSaved = { path = it },
···
366
368
if (it.isNotEmpty()) {
367
369
it.forEach { obj ->
368
370
val l = "${obj.labels.maxByOrNull { it.confidence }?.text}"
369
369
-
println("Detected Objects: ${obj.labels}")
371
371
+
//println("Detected Objects: ${obj.labels}")
370
372
}
371
373
}
372
374
}