Add functionality to request camera data #6

closed
opened by nateholland.bsky.social targeting master from camera-data
Changed files
+63 -5
gradle
posedetection
src
androidMain
kotlin
com.performancecoachlab
posedetection
commonMain
kotlin
com
performancecoachlab
posedetection
camera
iosMain
kotlin
com
performancecoachlab
posedetection
sample
composeApp
src
commonMain
kotlin
com
nate
posedetection
+1
gradle/wrapper/gradle-wrapper.properties
···
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+
org.gradle.jvmargs=-Xmx4G
+15 -1
posedetection/src/androidMain/kotlin/com.performancecoachlab/posedetection/camera/CameraView.android.kt
···
import androidx.camera.core.Preview
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
+
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.geometry.Rect
import com.google.mlkit.vision.pose.PoseDetector
···
frontCamera: Boolean,
recordingId: String?,
focusArea: Rect?,
+
controller: CameraViewController?,
onRecordToggled: (Boolean) -> Unit,
onVideoSaved: (String, String) -> Unit,
) {
···
bitmap =
imageProxy.toBitmap().rotate(imageProxy.imageInfo.rotationDegrees.toFloat())
.asImageBitmap().let { inbmp ->
+
controller?.setRequestDataProvider {
+
CameraViewData(
+
width = inbmp.width.toFloat(),
+
height = inbmp.height.toFloat(),
+
rotation = when (imageProxy.imageInfo.rotationDegrees) {
+
0 -> SensorRotation.ROTATION_0
+
90 -> SensorRotation.ROTATION_90
+
180 -> SensorRotation.ROTATION_180
+
270 -> SensorRotation.ROTATION_270
+
else -> SensorRotation.ROTATION_0
+
}
+
)
+
}
addFrameToActiveRecordings(inbmp, timestamp)
inbmp.drawResults(if(drawSkeleton) it.skeleton else null, drawObjects?.invoke(it.objects)?: emptyList())
}
···
)
previewView.scaleType = PreviewView.ScaleType.FIT_CENTER
}
-
Box(
modifier = modifier
) {
+27
posedetection/src/commonMain/kotlin/com/performancecoachlab/posedetection/camera/CameraView.kt
···
frontCamera: Boolean = true,
recordingId: String? = null,
focusArea: Rect? = null,
+
controller: CameraViewController? = null,
onRecordToggled: (Boolean) -> Unit = {},
onVideoSaved: (String, String) -> Unit
)
···
enum class DrawableShape {
OVAL,RECTANGLE
}
+
+
data class CameraViewData(
+
val width: Float,
+
val height: Float,
+
val rotation: SensorRotation,
+
)
+
+
enum class SensorRotation {
+
ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270
+
}
+
+
+
interface CameraViewController {
+
fun requestData(onResult: (CameraViewData) -> Unit)
+
fun setRequestDataProvider(provider: (() -> CameraViewData)?)
+
}
+
+
class CameraViewControllerImpl : CameraViewController {
+
private var dataProvider: (() -> CameraViewData)? = null
+
override fun requestData(onResult: (CameraViewData) -> Unit) {
+
dataProvider?.let { onResult(it()) }
+
}
+
override fun setRequestDataProvider(provider: (() -> CameraViewData)?) {
+
dataProvider = provider
+
}
+
}
+2
posedetection/src/iosMain/kotlin/com/performancecoachlab/posedetection/camera/CameraView.ios.kt
···
frontCamera: Boolean,
recordingId: String?,
focusArea: Rect?,
+
controller: CameraViewController?,
onRecordToggled: (Boolean) -> Unit,
onVideoSaved: (String, String) -> Unit,
) {
···
cameraEngine.value?.apply {
addSkeletonRepository(skeletonRepository)
addCustomObjectRepository(customObjectRepository)
+
addCameraViewController(controller)
addFrameListener(frameListener)
setOnVideoSavedCallback(recordingDone)
setDrawOptions(
+10 -4
sample/composeApp/src/commonMain/kotlin/com/nate/posedetection/App.kt
···
import chaintech.videoplayer.model.VideoPlayerConfig
import chaintech.videoplayer.ui.video.VideoPlayerComposable
import chaintech.videoplayer.util.RetrieveMediaDuration
+
import co.touchlab.kermit.Logger
import com.nate.posedetection.theme.AppTheme
import com.performancecoachlab.posedetection.camera.CameraView
+
import com.performancecoachlab.posedetection.camera.CameraViewControllerImpl
import com.performancecoachlab.posedetection.camera.DetectMode
import com.performancecoachlab.posedetection.camera.DrawableObject
import com.performancecoachlab.posedetection.camera.DrawableShape
···
@Composable
internal fun App() = AppTheme {
-
var selectedTabIndex by remember { mutableStateOf(1) }
+
var selectedTabIndex by remember { mutableStateOf(0) }
val tabs = listOf("Camera Feed", "Recorded Video")
Column {
TabRow(selectedTabIndex = selectedTabIndex) {
···
"YOLOv3FP16"
)
)
+
val controller = remember { CameraViewControllerImpl() }
PermissionProvider().apply {
if (!hasCameraPermission()) RequestCameraPermission(onGranted = {
permissionGranted = true
···
CameraView(
skeletonRepository = skeletonRepository,
customObjectRepository = customObjectRespository,
-
detectMode = DetectMode.BOTH,
+
detectMode = DetectMode.NONE,
drawSkeleton = true,
drawObjects = { obj ->
-
obj.map {
DrawableObject(
obj = it,
···
modifier = Modifier.weight(1f),
frontCamera = false,
recordingId = recordingId,
+
controller = controller,
onVideoSaved = {id,url -> path = url },
)
}
Button(
onClick = {
-
recordingId = "${Clock.System.now().epochSeconds}"
+
//recordingId = "${Clock.System.now().epochSeconds}"
+
controller.requestData { data ->
+
Logger.d("CameraViewData: $data")
+
}
},
modifier = Modifier.imePadding().padding(16.dp).align(Alignment.TopStart)
) {
+1
sample/gradle.properties
···
#Android
android.useAndroidX=true
android.nonTransitiveRClass=true
+
android.enableJetifier=true
+3
settings.gradle.kts
···
mavenCentral()
}
}
+
plugins {
+
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
+
}
dependencyResolutionManagement {
repositories {
+4
posedetection/src/iosMain/kotlin/com/performancecoachlab/posedetection/camera/FrameProcessor.kt
···
import kotlinx.cinterop.value
import platform.AVFoundation.AVCaptureVideoOrientationLandscapeRight
import platform.AVFoundation.AVCaptureVideoPreviewLayer
+
import platform.CoreFoundation.CFRelease
+
import platform.CoreFoundation.CFRetain
import platform.CoreGraphics.CGImageGetHeight
import platform.CoreGraphics.CGImageGetWidth
import platform.CoreGraphics.CGImageRef
···
onSkeletonProcessed(null)
return
}
+
val retainedBuffer = CFRetain(buffer)
val width = 480uL // You may want to get actual width from buffer if needed
val height = 360uL // You may want to get actual height from buffer if needed
memScoped {
···
handler.performRequests(
listOfNotNull(requestForObjects, requestForSkeleton), errorPtr.ptr
)
+
CFRelease(retainedBuffer)
if (errorPtr.value != null) {
//println("Error performing object detection request: ${errorPtr.value}")
onObjectsProcessed(emptyList())