Last active
September 13, 2021 08:59
-
-
Save misshannah/f9495bee77c80ce97ec05723f892c9f2 to your computer and use it in GitHub Desktop.
Camera setting orientation using sensor events
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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