코드 : https://github.com/pchan1401-ICIL/Camera1_2_Compare



이번 글은 단순히 Camera2와 Camera API의 Fov를 비교하는 글이다.


최근에 Camera2를 사용하던 코드른 Camera API로 변환하는 과정에서 어떠한 값을 나타내는지 비교하는 것이다.


Camera 코드의 Fov를 구하는 코드는 많이 있으니 생략한다.


두 API의 차이점은 아래와 같다.


 - Camera : API가 이미 존재하여 값을 불러온다.

 - Camera2 : 센서의 크기를 이용하여 계산한다.


그리고 결과는 아래와 같다.


갤럭시탭 S3


한번 다른 기기를 이용해보았다


갤럭시 노트4




삼성기기만 그러한지는 모르겠으나 두 기기를 비교해보면 Camera와 Camera2의 FOV출력은 정확히 같은계산을 하는 것 같다.






기존의 Camera API는 카메라를 카메라로 보았다면


Android 5.0이상에서는 카메라는 센서처럼 취급한다.


camera2 api 강좌 : http://myandroidarchive.tistory.com/1


우선 카메라의 화각 계산 공식은 아래 사이트에서 볼 수 있다.


Calculation Field of View : http://www.bobatkins.com/photography/technical/field_of_view.html


이전 코드와 변경사항은 아래 코드가 추가되었다는 점이다.


float horizonalAngle;
float verticalAngle;

private void calculateFOV(CameraManager cManager) {
try {
for (final String cameraId : cManager.getCameraIdList()) {
CameraCharacteristics characteristics = cManager.getCameraCharacteristics(cameraId);
int cOrientation = characteristics.get(CameraCharacteristics.LENS_FACING);
if (cOrientation == CameraCharacteristics.LENS_FACING_BACK) {
float[] maxFocus = characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
SizeF size = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE);
float w = size.getWidth();
float h = size.getHeight();
horizonalAngle = (float) (2*Math.atan(w/(maxFocus[0]*2)));
verticalAngle = (float) (2*Math.atan(h/(maxFocus[0]*2)));
}
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

먼저, LENS_INFO_AVAILABLE_FOCUS_LENGTHS는 카메라가 사용가능한 렌즈의 초점거리를 얻기 위하여 사용하였다.


다음으로 SENSOR_INFO_PHYSICAL_SIZE는 센서의 크기를 불러오기 위해 사용하였다.


이 값을 이용하여 수직, 수평 FOV각도 값을 구할 수 있다.


FOV (rectilinear) =  2 * arctan (frame size/(focal length * 2))



GitHub : https://github.com/pchan1401-ICIL/Camera2FOV

안드로이드 5.0 이상의 버전은 Camera API 대신 Camera2 API를 사용할 수 있다. 


이 코드는 안드로이드 6.0에서도 사용이 가능하도록 하였다.

-------------------------------------------------------------------------------------------------------------------

public class MainActivity extends AppCompatActivity {
private TextureView mCameraTextureView;
private Preview mPreview;

Activity mainActivity = this;

private static final String TAG = "MAINACTIVITY";

static final int REQUEST_CAMERA = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mCameraTextureView = (TextureView) findViewById(R.id.cameraTextureView);
mPreview = new Preview(this, mCameraTextureView);

}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_CAMERA:
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
int grantResult = grantResults[i];
if (permission.equals(Manifest.permission.CAMERA)) {
if(grantResult == PackageManager.PERMISSION_GRANTED) {
mCameraTextureView = (TextureView) findViewById(R.id.cameraTextureView);
mPreview = new Preview(mainActivity, mCameraTextureView);
Log.d(TAG,"mPreview set");
} else {
Toast.makeText(this,"Should have camera permission to run", Toast.LENGTH_LONG).show();
finish();
}
}
}
break;
}
}

@Override
protected void onResume() {
super.onResume();
mPreview.onResume();
}

@Override
protected void onPause() {
super.onPause();
mPreview.onPause();
}
}

-----------------------------------------------------------------------------------------------------------------


위는 이코드의  MainActivity이다. 카메라의 화면을 보여주는 TextureView와 카메라의 기능을 하는 Preview를 선언하였다.


-----------------------------------------------------------------------------------------------------------------


public class Preview extends Thread {
private final static String TAG = "Preview : ";

private Size mPreviewSize;
private Context mContext;
private CameraDevice mCameraDevice;
private CaptureRequest.Builder mPreviewBuilder;
private CameraCaptureSession mPreviewSession;
private TextureView mTextureView;

public Preview(Context context, TextureView textureView) {
mContext = context;
mTextureView = textureView;
}

private String getBackFacingCameraId(CameraManager cManager) {
try {
for (final String cameraId : cManager.getCameraIdList()) {
CameraCharacteristics characteristics = cManager.getCameraCharacteristics(cameraId);
int cOrientation = characteristics.get(CameraCharacteristics.LENS_FACING);
if (cOrientation == CameraCharacteristics.LENS_FACING_BACK) return cameraId;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
return null;
}

public void openCamera() {
CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
Log.e(TAG, "openCamera E");
try {
String cameraId = getBackFacingCameraId(manager);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mPreviewSize = map.getOutputSizes(SurfaceTexture.class)[0];

int permissionCamera = ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA);
if(permissionCamera == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions((Activity) mContext, new String[]{Manifest.permission.CAMERA}, MainActivity.REQUEST_CAMERA);
} else {
manager.openCamera(cameraId, mStateCallback, null);
}
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.e(TAG, "openCamera X");
}

private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener(){

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface,
int width, int height) {
// TODO Auto-generated method stub
Log.e(TAG, "onSurfaceTextureAvailable, width="+width+",height="+height);
openCamera();
}

@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface,
int width, int height) {
// TODO Auto-generated method stub
Log.e(TAG, "onSurfaceTextureSizeChanged");
}

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
// TODO Auto-generated method stub
return false;
}

@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// TODO Auto-generated method stub
}
};

private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

@Override
public void onOpened(CameraDevice camera) {
// TODO Auto-generated method stub
Log.e(TAG, "onOpened");
mCameraDevice = camera;
startPreview();
}

@Override
public void onDisconnected(CameraDevice camera) {
// TODO Auto-generated method stub
Log.e(TAG, "onDisconnected");
}

@Override
public void onError(CameraDevice camera, int error) {
// TODO Auto-generated method stub
Log.e(TAG, "onError");
}

};

protected void startPreview() {
// TODO Auto-generated method stub
if(null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
Log.e(TAG, "startPreview fail, return");
}

SurfaceTexture texture = mTextureView.getSurfaceTexture();
if(null == texture) {
Log.e(TAG,"texture is null, return");
return;
}

texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Surface surface = new Surface(texture);

try {
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mPreviewBuilder.addTarget(surface);

try {
mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {

@Override
public void onConfigured(CameraCaptureSession session) {
// TODO Auto-generated method stub
mPreviewSession = session;
updatePreview();
}

@Override
public void onConfigureFailed(CameraCaptureSession session) {
// TODO Auto-generated method stub
Toast.makeText(mContext, "onConfigureFailed", Toast.LENGTH_LONG).show();
}
}, null);
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

protected void updatePreview() {
// TODO Auto-generated method stub
if(null == mCameraDevice) {
Log.e(TAG, "updatePreview error, return");
}

mPreviewBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
HandlerThread thread = new HandlerThread("CameraPreview");
thread.start();
Handler backgroundHandler = new Handler(thread.getLooper());

try {
mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

public void setSurfaceTextureListener()
{
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}

public void onResume() {
Log.d(TAG, "onResume");
setSurfaceTextureListener();
}

private Semaphore mCameraOpenCloseLock = new Semaphore(1);

public void onPause() {
// TODO Auto-generated method stub
Log.d(TAG, "onPause");
try {
mCameraOpenCloseLock.acquire();
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
Log.d(TAG, "CameraDevice Close");
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera closing.");
} finally {
mCameraOpenCloseLock.release();
}
}
}


----------------------------------------------------------------------------------------------------------------------


Camera2 API는 기존의 Camera API와 다르게 선언해야하는 여러가지 Camera 클래스들이 있다.


CameraDevice는 카메라 기기를 나타내고 CameraManager는 카메라의 기능을 시키는 요소이다.


카메라를 켜기위해 openCamera를 사용하였는데


이 함수는 SurfaceTextureListener내부의 onSurfaceTextureAvailable에 위치한다


TextureView가 사용이 가능하다면 카메라를 켜는 기능이다.


----------------------------------------------------------------------------------------------------------------------


public void openCamera() {
CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
Log.e(TAG, "openCamera E");
try {
String cameraId = getBackFacingCameraId(manager);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mPreviewSize = map.getOutputSizes(SurfaceTexture.class)[0];

int permissionCamera = ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA);
if(permissionCamera == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions((Activity) mContext, new String[]{Manifest.permission.CAMERA}, MainActivity.REQUEST_CAMERA);
} else {
manager.openCamera(cameraId, mStateCallback, null);
}
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.e(TAG, "openCamera X");
}


----------------------------------------------------------------------------------------------------------------------


또한 카메라를 다시 켜고 싶다면, SurfaceTextureListener만 다시 선언해 주면 된다.


----------------------------------------------------------------------------------------------------------------------

public void setSurfaceTextureListener()
{
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}

----------------------------------------------------------------------------------------------------------------------


카메라가 켜진다면 카메라 상태 콜백함수가 실행된다.


----------------------------------------------------------------------------------------------------------------------

private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

@Override
public void onOpened(CameraDevice camera) {
// TODO Auto-generated method stub
Log.e(TAG, "onOpened");
mCameraDevice = camera;
startPreview();
}

@Override
public void onDisconnected(CameraDevice camera) {
// TODO Auto-generated method stub
Log.e(TAG, "onDisconnected");
}

@Override
public void onError(CameraDevice camera, int error) {
// TODO Auto-generated method stub
Log.e(TAG, "onError");
}

};

----------------------------------------------------------------------------------------------------------------------


이 함수 내부에 onOpened함수가 있는데 카메라가 켜지면 자동적으로 실행된다. 


여기서 실질적으로 하는 함수는 startPreview함수이다.


----------------------------------------------------------------------------------------------------------------------


protected void startPreview() {
// TODO Auto-generated method stub
if(null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
Log.e(TAG, "startPreview fail, return");
}

SurfaceTexture texture = mTextureView.getSurfaceTexture();
if(null == texture) {
Log.e(TAG,"texture is null, return");
return;
}

texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Surface surface = new Surface(texture);

try {
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mPreviewBuilder.addTarget(surface);

try {
mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {

@Override
public void onConfigured(CameraCaptureSession session) {
// TODO Auto-generated method stub
mPreviewSession = session;
updatePreview();
}

@Override
public void onConfigureFailed(CameraCaptureSession session) {
// TODO Auto-generated method stub
Toast.makeText(mContext, "onConfigureFailed", Toast.LENGTH_LONG).show();
}
}, null);
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

-----------------------------------------------------------------------------------------------------------------------


startPreview함수는 위와 같다. 여기서는 카메라의 출력을 어디로 보낼 것인지 설정한다.


출력은 최대 3개까지 지정할 수 있다.


여기서는 카메라 프리뷰만 설정하였는데, 사진촬영 동영상촬영은 이 함수 내에 코드를 몇개만 바꿔서 사용할 수 있다.


카메라의 출력은 surface로 저장되어 mPreviewBuilder에 들어가게 된다. 


-----------------------------------------------------------------------------------------------------------------------


protected void updatePreview() {
// TODO Auto-generated method stub
if(null == mCameraDevice) {
Log.e(TAG, "updatePreview error, return");
}

mPreviewBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
HandlerThread thread = new HandlerThread("CameraPreview");
thread.start();
Handler backgroundHandler = new Handler(thread.getLooper());

try {
mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

-----------------------------------------------------------------------------------------------------------------------


마지막으로 updatePreview함수가 실행된다.


이 함수는 화면의 프리뷰를 계속 변경시켜주는 Thread를 실행시키는 함수이다.


이것으로 카메라의 프리뷰가 실행된다.



아래는 Camera2 API를 이해하기 위해 코드이다.


Camera2Raw : https://github.com/googlesamples/android-Camera2Raw


예제로 사용한 코드는 GitHub에 업로드 하였다.


mapp : https://github.com/pchan1401-ICIL/mapp









+ Recent posts