Skip to content

Instantly share code, notes, and snippets.

@misshannah
Last active September 13, 2021 08:59
Show Gist options
  • Save misshannah/f9495bee77c80ce97ec05723f892c9f2 to your computer and use it in GitHub Desktop.
Save misshannah/f9495bee77c80ce97ec05723f892c9f2 to your computer and use it in GitHub Desktop.
Camera setting orientation using sensor events
class CameraConnectionFragment
private constructor(
private val cameraConnectionCallback: ConnectionCallback,
private val imageListener: OnImageAvailableListener,
private val layout: Int,
private val inputSize: Size
) : Fragment(), SensorEventListener {
//SensorEvent Variables
var degree_count = 0
var Secdeg_Value = FloatArray(3)
var Secdeg_to_Int = IntArray(3)
var Fixed_Degree = 0f
var degree = 0
private val fuseTimer = Timer()
private var timestamp = 0f
private var initState = true
private val gyro = FloatArray(3)
private var gyroMatrix = FloatArray(9)
private val gyroOrientation = FloatArray(3)
private val magnet = FloatArray(3)
private val accel = FloatArray(3)
private val accMagOrientation: FloatArray = FloatArray(3)
private val fusedOrientation = FloatArray(3)
private val rotationMatrix = FloatArray(9)
private var mSensorManager: SensorManager? = null
var handler = Handler()
var runnable: Runnable? = null
var delay = 10
companion object {
private const val NS2S = 1.0f / 1000000000.0f
const val EPSILON = 0.000000001f
const val FILTER_COEFFICIENT = 0.98f
var handler1 = Handler()
var runnable1: Runnable? = null
var accuracy = true
//Camera Fragment variables
private const val MINIMUM_PREVIEW_SIZE = 320
private val ORIENTATIONS = SparseIntArray()
protected fun chooseOptimalSize(choices: Array<Size>, width: Int, height: Int): Size {
val minSize = Math.max(Math.min(width, height), MINIMUM_PREVIEW_SIZE)
val desiredSize = Size(width, height)
var exactSizeFound = false
val bigEnough: MutableList<Size> = ArrayList()
val tooSmall: MutableList<Size> = ArrayList()
for (option in choices) {
if (option == desiredSize) {
exactSizeFound = true
}
if (option.height >= minSize && option.width >= minSize) {
bigEnough.add(option)
} else {
tooSmall.add(option)
}
}
if (exactSizeFound) {
return desiredSize
}
return if (bigEnough.size > 0) {
Collections.min(bigEnough, CompareSizesByArea())
} else {
choices[0]
}
}
fun newInstance(
callback: ConnectionCallback,
imageListener: OnImageAvailableListener,
layout: Int,
inputSize: Size
): CameraConnectionFragment {
return CameraConnectionFragment(callback, imageListener, layout, inputSize)
}
init {
ORIENTATIONS.append(Surface.ROTATION_0, 90)
ORIENTATIONS.append(Surface.ROTATION_90, 0)
ORIENTATIONS.append(Surface.ROTATION_180, 270)
ORIENTATIONS.append(Surface.ROTATION_270, 180)
}
}
private val cameraOpenCloseLock = Semaphore(1)
private val captureCallback: CaptureCallback = object : CaptureCallback() {
override fun onCaptureProgressed(
session: CameraCaptureSession,
request: CaptureRequest,
partialResult: CaptureResult
) {
}
override fun onCaptureCompleted(
session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult
) {
}
}
private var cameraId: String? = null
private var textureView: AutoFitTextureView? = null
private var captureSession: CameraCaptureSession? = null
private var cameraDevice: CameraDevice? = null
private var sensorOrientation: Int? = null
private var previewSize: Size? = null
private var backgroundThread: HandlerThread? = null
private var backgroundHandler: Handler? = null
var cameraOrientationView_on: View? = null
var cameraOrientationView_off: View? = null
var i = 0
var orientation_status = true
private val surfaceTextureListener: SurfaceTextureListener = object : SurfaceTextureListener {
override fun onSurfaceTextureAvailable(
texture: SurfaceTexture, width: Int, height: Int
) {
openCamera(width, height)
}
override fun onSurfaceTextureSizeChanged(
texture: SurfaceTexture, width: Int, height: Int
) {
configureTransform(width, height)
}
override fun onSurfaceTextureDestroyed(texture: SurfaceTexture): Boolean {
return true
}
override fun onSurfaceTextureUpdated(texture: SurfaceTexture) {}
}
private var previewReader: ImageReader? = null
private var previewRequestBuilder: CaptureRequest.Builder? = null
private var previewRequest: CaptureRequest? = null
private val stateCallback: CameraDevice.StateCallback = object : CameraDevice.StateCallback() {
override fun onOpened(cd: CameraDevice) {
cameraOpenCloseLock.release()
cameraDevice = cd
createCameraPreviewSession()
}
override fun onDisconnected(cd: CameraDevice) {
cameraOpenCloseLock.release()
cd.close()
cameraDevice = null
}
override fun onError(cd: CameraDevice, error: Int) {
cameraOpenCloseLock.release()
cd.close()
cameraDevice = null
val activity: Activity? = activity
activity?.finish()
}
}
private fun showToast(text: String) {
val activity: Activity? = activity
activity?.runOnUiThread { Toast.makeText(activity, text, Toast.LENGTH_SHORT).show() }
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
return inflater.inflate(layout, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
textureView = view.findViewById(R.id.texture)
cameraOrientationView_on = view.findViewById(R.id.camera_orientation_indicator_on)
cameraOrientationView_off = view.findViewById(R.id.camera_orientation_indicator_off)
mSensorManager = activity!!.getSystemService(Context.SENSOR_SERVICE) as SensorManager
initListeners()
handler.postDelayed(Runnable {
handler.postDelayed(runnable, delay.toLong())
call_Elevation_Sensor()
}.also { runnable = it }, delay.toLong())
fuseTimer.scheduleAtFixedRate(
calculateFusedOrientationTask(),
1000, 20
)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
}
override fun onResume() {
super.onResume()
startBackgroundThread()
if (textureView.isAvailable()) {
openCamera(textureView.getWidth(), textureView.getHeight())
} else {
textureView.setSurfaceTextureListener(surfaceTextureListener)
}
}
override fun onPause() {
closeCamera()
stopBackgroundThread()
super.onPause()
}
fun setCamera(cameraId: String?) {
this.cameraId = cameraId
}
private fun setUpCameraOutputs() {
val activity: Activity? = activity
val manager = activity!!.getSystemService(Context.CAMERA_SERVICE) as CameraManager
try {
val characteristics = manager.getCameraCharacteristics(cameraId!!)
val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)
previewSize = chooseOptimalSize(
map!!.getOutputSizes(SurfaceTexture::class.java),
1280,
640
)
// We fit the aspect ratio of TextureView to the size of preview we picked.
val orientation = resources.configuration.orientation
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
textureView.setAspectRatio(previewSize!!.width, previewSize!!.height)
} else {
textureView.setAspectRatio(previewSize!!.height, previewSize!!.width)
}
} catch (e: CameraAccessException) {
} catch (e: NullPointerException) {
}
cameraConnectionCallback.onPreviewSizeChosen(previewSize, sensorOrientation!!)
}
@SuppressLint("MissingPermission")
private fun openCamera(width: Int, height: Int) {
setUpCameraOutputs()
configureTransform(width, height)
val activity: Activity? = activity
val manager = activity!!.getSystemService(Context.CAMERA_SERVICE) as CameraManager
try {
if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw RuntimeException("Time out waiting to lock camera opening.")
}
if (ActivityCompat.checkSelfPermission(
activity,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED
) {
return
}
manager.openCamera(cameraId!!, stateCallback, backgroundHandler)
} catch (e: CameraAccessException) {
} catch (e: InterruptedException) {
throw RuntimeException("Interrupted while trying to lock camera opening.", e)
}
}
/**
* Closes the current [CameraDevice].
*/
private fun closeCamera() {
try {
cameraOpenCloseLock.acquire()
if (null != captureSession) {
captureSession!!.close()
captureSession = null
}
if (null != cameraDevice) {
cameraDevice!!.close()
cameraDevice = null
}
if (null != previewReader) {
previewReader!!.close()
previewReader = null
}
} catch (e: InterruptedException) {
throw RuntimeException("Interrupted while trying to lock camera closing.", e)
} finally {
cameraOpenCloseLock.release()
}
}
private fun startBackgroundThread() {
backgroundThread = HandlerThread("ImageListener")
backgroundThread!!.start()
backgroundHandler = Handler(backgroundThread!!.looper)
}
private fun stopBackgroundThread() {
backgroundThread!!.quitSafely()
try {
backgroundThread!!.join()
backgroundThread = null
backgroundHandler = null
} catch (e: InterruptedException) {
}
}
/**
* Creates a new [CameraCaptureSession] for camera preview.
*/
private fun createCameraPreviewSession() {
try {
val texture: SurfaceTexture = textureView.getSurfaceTexture()!!
val surface = Surface(texture)
previewRequestBuilder =
cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
previewRequestBuilder!!.addTarget(surface)
previewReader = ImageReader.newInstance(
previewSize!!.width, previewSize!!.height, ImageFormat.YUV_420_888, 2
)
previewReader.setOnImageAvailableListener(imageListener, backgroundHandler)
previewRequestBuilder!!.addTarget(previewReader.getSurface())
cameraDevice!!.createCaptureSession(
Arrays.asList(surface, previewReader.getSurface()),
object : CameraCaptureSession.StateCallback() {
override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
if (null == cameraDevice) {
return
}
captureSession = cameraCaptureSession
try {
previewRequestBuilder!!.set(
CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
)
previewRequestBuilder!!.set(
CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
)
previewRequest = previewRequestBuilder!!.build()
captureSession!!.setRepeatingRequest(
previewRequest!!, captureCallback, backgroundHandler
)
} catch (e: CameraAccessException) {
}
}
override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) {
showToast("Failed")
}
},
null
)
} catch (e: CameraAccessException) {
}
}
private fun configureTransform(viewWidth: Int, viewHeight: Int) {
val activity: Activity? = activity
if (null == textureView || null == previewSize || null == activity) {
return
}
val rotation = activity.windowManager.defaultDisplay.rotation
val matrix = Matrix()
val viewRect = RectF(0, 0, viewWidth.toFloat(), viewHeight.toFloat())
val bufferRect = RectF(
0, 0, previewSize!!.height.toFloat(),
previewSize!!.width.toFloat()
)
val centerX = viewRect.centerX()
val centerY = viewRect.centerY()
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY())
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL)
val scale = Math.max(
viewHeight.toFloat() / previewSize!!.height,
viewWidth.toFloat() / previewSize!!.width
)
matrix.postScale(scale, scale, centerX, centerY)
matrix.postRotate((90 * (rotation - 2)).toFloat(), centerX, centerY)
} else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180f, centerX, centerY)
}
textureView.setTransform(matrix)
}
interface ConnectionCallback {
fun onPreviewSizeChosen(size: Size?, cameraRotation: Int)
}
internal class CompareSizesByArea : Comparator<Size> {
override fun compare(lhs: Size, rhs: Size): Int {
return java.lang.Long.signum(
lhs.width.toLong() * lhs.height - rhs.width.toLong() * rhs.height
)
}
}
fun call_Elevation_Sensor() {
Secdeg_Value[degree_count] = (fusedOrientation[1] * 180 / Math.PI).toFloat()
Secdeg_to_Int[degree_count] = Secdeg_Value[degree_count].toInt()
degree_count = degree_count + 1
when (degree_count) {
1 -> {
}
2 -> {
}
3 -> {
if (Secdeg_to_Int[0] == Secdeg_to_Int[1] && Secdeg_to_Int[0] == Secdeg_to_Int[2]) {
degree = 1
Fixed_Degree = (Secdeg_Value[0] + Secdeg_Value[1] + Secdeg_Value[2]) / 3
degree_count = 0
} else {
degree_count = 0
degree = 0
}
}
}
if (degree == 1) {
if (Fixed_Degree < 0) {
Fixed_Degree = Fixed_Degree * -1
}
// Log.d("Main Degree", String.valueOf(Fixed_Degree));
if (Fixed_Degree < 10 && Fixed_Degree > -1) {
cameraOrientationView_on!!.visibility = View.VISIBLE
cameraOrientationView_off!!.visibility = View.GONE
orientation_status = true
}
if (Fixed_Degree > 10) {
cameraOrientationView_on!!.visibility = View.GONE
cameraOrientationView_off!!.visibility = View.VISIBLE
orientation_status = false
}
}
}
override fun onSensorChanged(event: SensorEvent) {
when (event.sensor.type) {
Sensor.TYPE_ACCELEROMETER -> {
System.arraycopy(event.values, 0, accel, 0, 3)
calculateAccMagOrientation()
}
Sensor.TYPE_GYROSCOPE -> // process gyro data
gyroFunction(event)
Sensor.TYPE_MAGNETIC_FIELD -> // copy new magnetometer data into magnet array
System.arraycopy(event.values, 0, magnet, 0, 3)
}
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
fun gyroFunction(event: SensorEvent) {
if (accMagOrientation == null) return
if (initState) {
var initMatrix = FloatArray(9)
initMatrix = getRotationMatrixFromOrientation(accMagOrientation)
val test = FloatArray(3)
SensorManager.getOrientation(initMatrix, test)
gyroMatrix = matrixMultiplication(gyroMatrix, initMatrix)
initState = false
}
val deltaVector = FloatArray(4)
if (timestamp != 0f) {
val dT = (event.timestamp - timestamp) * NS2S
System.arraycopy(event.values, 0, gyro, 0, 3)
getRotationVectorFromGyro(gyro, deltaVector, dT / 2.0f)
}
timestamp = event.timestamp.toFloat()
val deltaMatrix = FloatArray(9)
SensorManager.getRotationMatrixFromVector(deltaMatrix, deltaVector)
gyroMatrix = matrixMultiplication(gyroMatrix, deltaMatrix)
SensorManager.getOrientation(gyroMatrix, gyroOrientation)
}
private fun getRotationVectorFromGyro(
gyroValues: FloatArray,
deltaRotationVector: FloatArray,
timeFactor: Float
) {
val normValues = FloatArray(3)
val omegaMagnitude =
Math.sqrt((gyroValues[0] * gyroValues[0] + gyroValues[1] * gyroValues[1] + gyroValues[2] * gyroValues[2]).toDouble())
.toFloat()
if (omegaMagnitude > EPSILON) {
normValues[0] = gyroValues[0] / omegaMagnitude
normValues[1] = gyroValues[1] / omegaMagnitude
normValues[2] = gyroValues[2] / omegaMagnitude
}
val thetaOverTwo = omegaMagnitude * timeFactor
val sinThetaOverTwo = Math.sin(thetaOverTwo.toDouble()).toFloat()
val cosThetaOverTwo = Math.cos(thetaOverTwo.toDouble()).toFloat()
deltaRotationVector[0] = sinThetaOverTwo * normValues[0]
deltaRotationVector[1] = sinThetaOverTwo * normValues[1]
deltaRotationVector[2] = sinThetaOverTwo * normValues[2]
deltaRotationVector[3] = cosThetaOverTwo
}
fun initListeners() {
mSensorManager!!.registerListener(
this,
mSensorManager!!.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_FASTEST
)
mSensorManager!!.registerListener(
this,
mSensorManager!!.getDefaultSensor(Sensor.TYPE_GYROSCOPE),
SensorManager.SENSOR_DELAY_FASTEST
)
mSensorManager!!.registerListener(
this,
mSensorManager!!.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
SensorManager.SENSOR_DELAY_FASTEST
)
}
fun calculateAccMagOrientation() {
if (SensorManager.getRotationMatrix(rotationMatrix, null, accel, magnet)) {
SensorManager.getOrientation(rotationMatrix, accMagOrientation)
}
}
internal inner class calculateFusedOrientationTask : TimerTask() {
override fun run() {
val oneMinusCoeff = 1.0f - FILTER_COEFFICIENT
fusedOrientation[0] = (FILTER_COEFFICIENT * gyroOrientation[0]
+ oneMinusCoeff * accMagOrientation!![0])
fusedOrientation[1] = (FILTER_COEFFICIENT * gyroOrientation[1]
+ oneMinusCoeff * accMagOrientation[1])
fusedOrientation[2] = (FILTER_COEFFICIENT * gyroOrientation[2]
+ oneMinusCoeff * accMagOrientation[2])
gyroMatrix = getRotationMatrixFromOrientation(fusedOrientation)
System.arraycopy(fusedOrientation, 0, gyroOrientation, 0, 3)
}
}
private fun getRotationMatrixFromOrientation(o: FloatArray): FloatArray {
val xM = FloatArray(9)
val yM = FloatArray(9)
val zM = FloatArray(9)
val sinX = Math.sin(o[1].toDouble()).toFloat()
val cosX = Math.cos(o[1].toDouble()).toFloat()
val sinY = Math.sin(o[2].toDouble()).toFloat()
val cosY = Math.cos(o[2].toDouble()).toFloat()
val sinZ = Math.sin(o[0].toDouble()).toFloat()
val cosZ = Math.cos(o[0].toDouble()).toFloat()
// rotation about x-axis (pitch)
xM[0] = 1.0f
xM[1] = 0.0f
xM[2] = 0.0f
xM[3] = 0.0f
xM[4] = cosX
xM[5] = sinX
xM[6] = 0.0f
xM[7] = -sinX
xM[8] = cosX
// rotation about y-axis (roll)
yM[0] = cosY
yM[1] = 0.0f
yM[2] = sinY
yM[3] = 0.0f
yM[4] = 1.0f
yM[5] = 0.0f
yM[6] = -sinY
yM[7] = 0.0f
yM[8] = cosY
// rotation about z-axis (azimuth)
zM[0] = cosZ
zM[1] = sinZ
zM[2] = 0.0f
zM[3] = -sinZ
zM[4] = cosZ
zM[5] = 0.0f
zM[6] = 0.0f
zM[7] = 0.0f
zM[8] = 1.0f
// rotation order is y, x, z (roll, pitch, azimuth)
var resultMatrix = matrixMultiplication(xM, yM)
resultMatrix = matrixMultiplication(zM, resultMatrix)
return resultMatrix
}
private fun matrixMultiplication(A: FloatArray, B: FloatArray): FloatArray {
val result = FloatArray(9)
result[0] = A[0] * B[0] + A[1] * B[3] + A[2] * B[6]
result[1] = A[0] * B[1] + A[1] * B[4] + A[2] * B[7]
result[2] = A[0] * B[2] + A[1] * B[5] + A[2] * B[8]
result[3] = A[3] * B[0] + A[4] * B[3] + A[5] * B[6]
result[4] = A[3] * B[1] + A[4] * B[4] + A[5] * B[7]
result[5] = A[3] * B[2] + A[4] * B[5] + A[5] * B[8]
result[6] = A[6] * B[0] + A[7] * B[3] + A[8] * B[6]
result[7] = A[6] * B[1] + A[7] * B[4] + A[8] * B[7]
result[8] = A[6] * B[2] + A[7] * B[5] + A[8] * B[8]
return result
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment