달력

3

« 2024/3 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
2012. 8. 23. 20:44

안드로이드 - 카메라 안드로이드 이야기2012. 8. 23. 20:44

728x90
반응형

안드로이드 - 카메라

원본:http://www.winapi.co.kr/android/annex/18-4.htm

렌즈로부터 영상을 얻는 카메라는 이제 스마트폰에 빠질 수 없는 필수 기능이며 실제로도 카메라가 없는 폰을 보기 힘들다. 사진을 찍는 본질적인 용도는 물론이고 이제는 영상 통화까지도 기본 기능으로 정착되었으며 바코드 스캔이나 문자 인식 등의 최신 기술에도 카메라가 두루 사용된다. 특히 최근에 화두로 떠오르는 증강 현실은 카메라로부터 입수한 영상에 가상의 정보를 가미해 상황에 맞는 최적의 정보를 제공한다.

카메라의 물리적인 성능은 장비마다 천차 만별로 다르다. 각 제조사들은 하드웨어의 모든 기능을 아낌없이 발휘하고 타사 제품과의 차별화를 위해 고유의 기능을 확장해서 적용한다. 카메라의 성능이 곧 폰의 성능으로 인식되며 선택의 중요한 기준으로 작용한다. 그래서 카메라 앱은 하드웨어에 강하게 의존적이며 제조사가 제공한 기본 카메라가 해당 장비에서는 가장 좋은 성능을 낼 수밖에 없다.

모든 장비에 두루 사용할 수 있는 앱을 제작하려면 공통적인 기본 기능만 사용하거나 특정 기능의 존재 유무를 확인한 후 하드웨어의 능력치를 최대한 활용해야 한다. 안드로이드의 카메라 서비스는 복잡도에 비해 굉장히 깔끔하게 잘 정비되어 있으며 신뢰성도 높아서 기본 API만 잘 숙지해도 원하는 품질의 영상을 쉽게 얻을 수 있다. 카메라 기능을 사용하려면 매니페스트에 다음 선언문을 작성한다.

<uses-permission android:name="android.permission.CAMERA" />

<uses-feature android:name="android.hardware.camera" />

<uses-feature android:name="android.hardware.camera.autofocus" />

물리적인 하드웨어를 사용하는 것이므로 카메라를 사용하겠다는 퍼미션이 필요하며 사용자에게 허가를 받아야 한다. 퍼미션 외에도 uses-feature 엘리먼트로 카메라 하드웨어와 오토 포커스 기능을 사용한다는 것을 알려 해당 기능이 없는 장비에 앱이 설치되는 것을 방지할 필요가 있다. 카메라 기능은 운영체제의 서비스 형태로 제공되며 서비스와 앱 사이를 연결해 주는 것이 바로 Camera 클래스이다. 별도의 생성자는 없으므로 다음 메서드로 생성 및 파괴한다.

static Camera open ()

void release ()

정적 메서드로 카메라 객체를 생성하고 다 사용한 후 release 메서드로 해제한다. 카메라는 입수된 영상의 미리 보기를 표시하기 위해 표면 객체를 요구하므로 객체 생성 후 미리 보기 표면을 제공해야 한다. 미리보기 표면은 렌즈로부터 영상을 공급받으므로 별도의 버퍼를 가질 필요는 없으며 그래서 타입을 SURFACE_TYPE_PUSH_BUFFERS 로 설정한다. 다음 메서드로 미리 보기 표면과 방향을 지정한다.

void setPreviewDisplay (SurfaceHolder holder)

void setDisplayOrientation (int degrees)

미리보기는 기본적으로 가로 방향으로 표시되는데 대부분의 사진이 가로 방향으로 촬영되며 동영상도 세로로 된 것은 없기 때문이다. 그래서 카메라는 장비의 방향에 상관없이 항상 가로 전용으로 실행된다. 촬영시에 장비를 많이 움직이는데 이때 화면이 전환되면 오히려 불편하므로 일반적으로 카메라는 방향 전환을 하지 않는다. 하지만 카메라를 특수한 용도로 사용하는 앱에서는 세로로 촬영해야 하는 경우도 있어 2.2 버전부터는 미리보기의 방향을 변경하는 메서드가 추가되었다. 0, 90, 180, 270 식으로 미리보기의 각도를 지정한다.

표면을 지정한 후 카메라의 동작 방식이나 여러 가지 옵션을 지정하는 파라미터를 전달한다. 파라미터는 Camera의 내부 클래스인 Parameters 클래스로 표현되며 다음 메서드로 조사 및 변경한다. get 메서드로 현재 파라미터를 얻고 원하는 값을 수정한 후 set 메서드로 편집한 파라미터를 다시 전달하면 이후부터 카메라는 수정된 파라미터대로 동작한다. 실행중에라도 파라미터는 언제든지 수정할 수 있다.

Camera.Parameters getParameters ()

void setParameters (Camera.Parameters params)

파라미터로 조정할 수 있는 값은 해상도, 이미지 품질, 미리보기의 크기, 장면, 효과, 줌, 포커스, 플래시, 화이트 밸런스, 회전 모드 등등 아주 다양하다. 일반적인 디지털 카메라가 지원하는 옵션들이 대부분 지원된다. 그러나 모든 옵션이 항상 다 지원되는 것은 아니며 카메라의 물리적인 능력치를 초과할 수는 없다. 또 제조사별로 노출 시간, ISO, 손떨림 보정, GPS 좌표 기록 등의 고급 옵션들을 지원하기도 하며 얼굴 인식, 웃는 표정 인식 등 표준에 없는 커스텀 기능을 지원하는 모델도 있다.

따라서 파라미터를 조정할 때는 항상 장비의 능력치를 먼저 조사해 보고 사용할 수 있는 파라미터인지를 점검한 후 적용해야 한다. 플래시가 없는 카메라에 적목 감소 기능을 적용한다거나 300만 화소 카메라에 1000만 화소를 지정하는 것은 아무 의미가 없다. Parameters 클래스는 능력치를 조사하는 메서드와 옵션을 변경하는 메서드가 같이 제공된다. 다음은 미리 보기 영역의 크기 목록을 조사하고 설정하는 메서드이다.

List<Camera.Size> Camera.Parameters.getSupportedPreviewSizes ()

void Camera.Parameters.setPreviewSize (int width, int height)

카메라가 지원하는 미리 보기 크기의 목록을 먼저 구하고 표현하고자 하는 미리 보기와 비교하여 종횡비가 가장 근접하고 가급적이면 비슷한 크기로 미리 보기를 표시해야 한다. 다음은 사진의 해상도를 조사 및 지정하는데 방식은 동일하다.

List<Camera.Size> getSupportedPictureSizes ()

void setPictureSize (int width, int height)

해상도 목록으로 1600*1200, 2560*1920 등의 사용 가능한 크기가 조사된다. 이 목록을 사용자에게 보여주고 사용자가 선택한 해상도를 선택하면 이후 사진이 이 크기대로 촬영된다. 파라미터로 옵션을 설정했으면 다음 메서드로 미리 보기를 표시한다.

void startPreview ()

void stopPreview ()

startPreview를 호출하면 지정한 표면에 카메라 렌즈로부터 입수된 영상이 반복적으로 출력된다. 미리 보기의 프레임 비율이나 포맷도 파라미터로 지정할 수 있다. 미리 보기까지 나왔으면 언제든지 사진을 촬영할 수 있지만 좀 더 질좋은 이미지를 얻기 위해 오토 포커싱 과정을 거쳐야 한다.

void autoFocus (Camera.AutoFocusCallback cb)

void AutoFocusCallback.onAutoFocus (boolean success, Camera camera)

void cancelAutoFocus ()

autoFocus 메서드는 카메라와 영상간의 거리를 자동 판별하여 초점을 조절한다. 렌즈의 모터를 동작시켜 움직여야 하므로 다소 시간이 걸리며 그래서 이 메서드는 비동기적으로 동작한다. 오토 포커싱 콜백을 등록해 놓으면 포커싱 완료 후에 콜백이 호출되며 이때 인수로 포커싱 성공 여부가 전달된다. 대개의 경우 성공하겠지만 너무 근접한 거리에서는 실패할 수도 있다. 포커싱 중에 사용자의 다른 요청이 들어왔다면 중간에 취소할 수도 있다.

만약 장비가 오토 포커싱을 지원하지 않으면 콜백이 즉시 호출되며 이때 포커싱은 성공한 것으로 가정한다. 따라서 오토 포커싱은 기능의 제공 여부를 조사할 필요없이 무조건 호출해도 상관없다. 대상에 대해 초점을 정확하게 잡았으면 이제 사진을 찍을 차례이다. 이때는 다음 메서드를 호출한다.

void takePicture (Camera.ShutterCallback shutter, Camera.PictureCallback raw, [Camera.PictureCallback postview, ] Camera.PictureCallback jpeg)

사진 한장을 촬영하는데는 굉장히 많은 절차를 거쳐야 하는데다 대용량의 데이터를 조작해야 한다. 입수된 영상을 해상도에 맞게 축소하고 효과를 입히고 Jpeg 포맷으로 변환 및 압축까지 해야 하므로 상당한 시간이 걸린다. 그래서 takePicture 메서드는 카메라 서비스에게 사진 촬영 명령만 내린 후 즉시 리턴하되 각 단계마다 호출될 콜백 메서드를 전달해 놓는다. 필요치 않은 콜백은 null로 지정하여 생략할 수 있다.

셔터 콜백은 셔터를 닫을 때 호출되는데 보통 이 시점에서 찰칵 하는 셔터음을 낸다. 이 콜백에서 아무 것도 하지 않으면 소리가 안나야 정상이지만 무음 촬영이 법적으로 금지되어 있는 경우에는 강제로 소리가 나기도 한다. 우리 나라의 경우도 그런데 심지어 진동 모드인 상태에서도 촬영음이 들린다. PictureCallback 인터페이스에는 다음 콜백 메서드가 정의되어 있는데 각 단계의 이미지를 얻을 때마다 이 메서드가 호출된다.

void onPictureTaken (byte[] data, Camera camera)

data 인수는 이미지의 래스터 정보이다. 로(raw) 콜백으로는 압축하기 전의 원본 이미지 데이터가 전달되는데 용량이 대단히 크다. 바코드 리더기나 OCR처럼 손실없는 영상이 필요할 때 이 콜백을 처리하되 메모리가 충분하지 않을 경우는 원본 영상이 전달되지 않는다. 포스트 뷰 콜백은 촬영 후 액정으로 보여줄 이미지가 크기 조정이 완료된 상태로 전달된다.

카메라의 경우 가장 중요한 콜백은 이미지 데이터를 압축해서 전달하는 Jpeg 콜백이다. 미리 정한 포맷으로 압축된 이미지가 전달되는데 통상 JPEG 포맷이며 이 데이터를 파일로 저장하면 촬영된 사진 파일이 되는 것이다. 이미지를 저장할 때 GPS 좌표나 화면 방향 등의 상세 정보를 Exif 헤더에 저장할 수도 있다. 또 새 이미지가 추가되었으므로 미디어 DB에게 신호를 보내는 처리도 필요하다.

takePicture 메서드는 촬영전에 미리보기를 자동으로 중지하므로 stopPreview를 호출할 필요는 없다. 렌즈로부터 촬영 영상을 읽어들이는 동안에는 미리보기가 강제로 중지된다. 그러나 촬영이 끝난 후 미리보기를 자동으로 재시작하지는 않으므로 startPreview는 직접 호출해야 한다. 이 처리는 보통 Jpeg 콜백에서 파일 저장까지 완료한 후에 수행한다. 미리보기가 다시 나오면 다음 촬영을 계속할 수 있다.

2.간단한 카메라

앞 항에서 Camera 클래스를 프로그래밍하는 대략적인 절차에 대해 소개했는데 좀 길기는 하지만 지극히 상식적이고 작업 과정이 직선적이어서 이해하기는 쉽다. 카메라 열고 미리 보기 지정한 후 옵션 설정하고 찍으면 되는 것이다. 그러나 미리 보기 표면을 따로 준비해야 하는데다 오토 포커싱과 촬영 과정이 모두 비동기적으로 수행되므로 언제 어떤 메서드를 순서에 맞게 호출해야 하는지를 파악하는 것이 다소 어렵고 필요한 콜백을 선정하기도 쉽지 않다.

렌즈로부터 외부의 영상을 받아 파일 형태로 저장하기까지의 과정이 다소 복잡한 편이다. 이런 실습을 할 때는 고급 기능은 죄다 무시하고 가장 기본적인 촬영까지만 해 보는 간단한 예제가 도움이 된다. 다음 예제로 일단 카메라 프로그래밍의 기본을 익혀 보자. 짧지만 포커싱, 촬영, 저장 정도의 기능을 제공한다. 이 정도 길이의 소스로 카메라가 구현된다는 것이 놀랍다.

mm_Camera

public class mm_Camera extends Activity {

MyCameraSurface mSurface;

Button mShutter;

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.mm_camera);

mSurface = (MyCameraSurface)findViewById(R.id.preview);

// 오토 포커스 시작

findViewById(R.id.focus).setOnClickListener(new Button.OnClickListener() {

public void onClick(View v) {

mShutter.setEnabled(false);

mSurface.mCamera.autoFocus(mAutoFocus);

}

});

// 사진 촬영

mShutter = (Button)findViewById(R.id.shutter);

mShutter.setOnClickListener(new Button.OnClickListener() {

public void onClick(View v) {

mSurface.mCamera.takePicture(null, null, mPicture);

}

});

}

// 포커싱 성공하면 촬영 허가

AutoFocusCallback mAutoFocus = new AutoFocusCallback() {

public void onAutoFocus(boolean success, Camera camera) {

mShutter.setEnabled(success);

}

};

// 사진 저장.

PictureCallback mPicture = new PictureCallback() {

public void onPictureTaken(byte[] data, Camera camera) {

String path = "/sdcard/cameratest.jpg";

File file = new File(path);

try {

FileOutputStream fos = new FileOutputStream(file);

fos.write(data);

fos.flush();

fos.close();

} catch (Exception e) {

Toast.makeText(mm_Camera.this, "파일 저장 중 에러 발생 : " +

e.getMessage(), 0).show();

return;

}

Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);

Uri uri = Uri.parse("file://" + path);

intent.setData(uri);

sendBroadcast(intent);

Toast.makeText(mm_Camera.this, "사진 저장 완료 : " + path, 0).show();

mSurface.mCamera.startPreview();

}

};

}

// 미리보기 표면 클래스

class MyCameraSurface extends SurfaceView implements SurfaceHolder.Callback {

SurfaceHolder mHolder;

Camera mCamera;

public MyCameraSurface(Context context, AttributeSet attrs) {

super(context, attrs);

mHolder = getHolder();

mHolder.addCallback(this);

mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

}

// 표면 생성시 카메라 오픈하고 미리보기 설정

public void surfaceCreated(SurfaceHolder holder) {

mCamera = Camera.open();

try {

mCamera.setPreviewDisplay(mHolder);

} catch (IOException e) {

mCamera.release();

mCamera = null;

}

}

// 표면 파괴시 카메라도 파괴한다.

public void surfaceDestroyed(SurfaceHolder holder) {

if (mCamera != null) {

mCamera.stopPreview();

mCamera.release();

mCamera = null;

}

}

// 표면의 크기가 결정될 때 최적의 미리보기 크기를 구해 설정한다.

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

Camera.Parameters params = mCamera.getParameters();

List<Size> arSize = params.getSupportedPreviewSizes();

if (arSize == null) {

params.setPreviewSize(width, height);

} else {

int diff = 10000;

Size opti = null;

for (Size s : arSize) {

if (Math.abs(s.height - height) < diff) {

diff = Math.abs(s.height - height);

opti = s;

}

}

params.setPreviewSize(opti.width, opti.height);

}

mCamera.setParameters(params);

mCamera.startPreview();

}

}

레이아웃에는 포커싱과 촬영을 위한 버튼 두개와 미리 보기 표면이 배치되어 있다. 버튼 영역에 118dip 폭을 할당하고 미리 보기가 나머지 262dip 폭을 차지하는데 이는 일반적으로 많이 촬영되는 해상도의 4:3 비율을 에뮬레이터 환경에 맞춘 것이다. 와이드 해상도를 지원하는 장비는 사용자가 선택한 해상도의 종횡비와 장비의 실제 해상도에 맞게 미리 보기의 크기를 동적으로 조정해야 한다.

액티비티는 가로 전용으로 설정하여 방향 전환을 금지했다. 세로 촬영도 가능하기는 하지만 아직 2.2 이상의 장비가 일반화되지 않아 호환성 확보를 위해 방향을 고정하는 것이 좋다. 사실 카메라는 촬영중에 방향이 함부로 전환되면 오히려 불편하므로 가로든, 세로든 방향을 고정하는 것이 현실적으로 합당하다. 다음은 에뮬레이터에서 실행한 모습이다.

에뮬레이터는 렌즈가 없으므로 실제 미리 보기 영상이 나타나지 않는다. 대신 체크 무늬위를 움직이는 사각형을 반복적으로 보여줌으로써 미리보기를 흉내낸다. 에뮬레이터답게 미리보기 영상까지도 그럴듯하게 흉내를 내 주는 것이다. 이 애니메이션이 실장비에서는 렌즈로부터 입수된 영상으로 대체된다. 렌즈가 없으므로 오토 포커싱도 지원되지 않지만 항상 성공하는 것으로 가정하므로 사진 촬영은 가능하다.

에뮬레이터에서 촬영을 하면 더미 이미지 하나가 생성되는데 이 역시 가짜 이미지이다. 사진이 제대로 촬영되는지 확인해 보려면 렌즈를 가진 실장비가 필요하다. 다음은 실장비에서 이 예제를 실행한 모습이다. 미리 보기가 나타나 움직이며 Focus 버튼을 누르면 초점을 잡고 Shutter 버튼을 누르면 실제 촬영도 잘 된다. UI가 극단적으로 촌스럽기는 하지만 촬영은 훌륭하게 잘 수행된다.

소스를 분석해 보자. 미리 보기는 카메라의 필수 요소이므로 아래쪽에 미리 보기를 위한 표면인 MyCameraSurface 클래스를 정의했다. 생성자에서 콜백을 스스로 처리함을 명시했으며 버퍼 타입을 설정한다. 표면과 카메라는 생명 주기가 일치하며 표면이 먼저 준비되어야 카메라가 동작할 수 있다. 그래서 카메라의 초기화와 해제 코드는 표면의 콜백 메서드에 작성된다. 표면이 생성될 때 카메라를 생성하고 자신을 미리 보기 영역으로 제공한다.

표면이 변경될 때, 즉 표면의 크기가 처음 결정될 때 미리 보기의 크기를 결정하는데 이 코드는 다소 복잡하다. 장비가 지원하는 미리 보기 영역의 목록을 파라미터에서 구하고 그 중 표면의 실제 크기와 가장 근접한 크기를 선택한다. 만약 지원 목록을 구할 수 없으면 어쩔 수 없이 표면의 크기를 그대로 전달한다. 크기를 정한 후 startPreview를 호출하면 표면에 미리 보기가 출력된다. 표면이 파괴될 때는 미리 보기를 끝내고 카메라도 해제한다.

표면의 콜백만 제대로 처리해도 미리 보기까지는 잘 나오며 이 상태에서 바로 촬영할 수 있다. Focus 버튼을 누르면 셔터 버튼을 잠시 사용 금지시키고 autoFocus 메서드를 호출하여 초점을 잡는다. 포커스 콜백에서 포커싱이 성공하면 셔터 버튼을 허가한다. 포커싱과 촬영 두 과정을 명확하게 보이기 위해 의도적으로 버튼을 따로 두었는데 버튼 하나로 두 작업을 순차 처리할 수도 있다. 다만 중간에 버튼을 놓으면 취소하는 과정이 좀 복잡해진다.

촬영 버튼을 누르면 takePicture 메서드로 촬영을 한다. 카메라는 결국 사진을 얻는 것이 궁극의 목적이므로 다른 콜백은 무시하고 Jpeg 콜백만 처리했다. 파일에 래스터 데이터를 저장하고 새로 촬영된 이미지를 DB에 추가하기 위해 SCAN_FILE 방송을 보낸다. 좀 더 신속하게 삽입하려면 방송을 하는 대신 미디어 DB에 직접 레코드를 삽입하는 방법도 가능하다. 촬영 후 갤러리를 열어 보면 새로 찍은 사진이 보일 것이다.

이 예제는 비록 짧지만 카메라 제작의 기본 뼈대를 잘 보여준다. 디자인을 좀 더 쌈박하게 장식하고 해상도 변경이나 타이머 정도의 기능만 넣어도 실용성이 훨씬 더 개선될 것이다. mm_SHCamera 예제는 기본 기능에 약간의 추가 기능을 더해 실용적으로도 쓸만하게 만들어 본 것이되 이 정도 되면 예제로서의 가치는 떨어지므로 소스 분석은 생략하기로 한다. 원고 집필에 시달리다 보니 요즘은 다음 간식을 종종 먹어야 한다.

앞 예제와는 다소 달라졌는데 주로 오른손으로 촬영을 하므로 버튼들을 오른쪽으로 옮겼으며 몇 가지 기능을 더 넣었다. 촬영 버튼을 누르면 포커스 잡고 바로 촬영을 하는데 화면 터치 시점과 촬영 시점을 분리하여 떨림을 방지하자는 의도이다. 간단한 아이디어지만 터치폰에서는 화질 향상에 꽤 도움이 된다. 해상도 선택 기능과 접사 모드 정도를 추가했고 최후로 찍은 사진이 제대로 찍혔는지 확인하는 리뷰 기능도 제공한다.

이외에도 아주 많은 기능들을 더 넣을 수 있지만 그다지 실용성은 없어 보인다. 장면 모드니 반전, 세피아 같은 효과들은 찍은 후에 적용하는 것이 더 품질이 좋고 디지털 줌은 있으나 마나한 기능이다. 가장 빈번하게 사용하는 기능이 싱글 샷이므로 이 기능에만 집중해서 간단하게 만들어 본 것이며 개인적으로 유용하게 잘 쓰고 있다. 여러분들도 자신만의 카메라를 만들어 사용해 보기 바란다.

728x90
반응형
:
Posted by mapagilove