64비트 컴퓨터에 OpenCV를 설치할 때 MSVCP100D.dll 과 MSVCR100D.dll이 없어 프로그램을 시작할수 없습니다. 라는 경고문구가 뜰때가 있다.

이럴때는 아래 파일을 다운받아 압축을 해제한 뒤 해당 dll파일을 SysWOW64폴더(C:\Windows\SysWOW64)에 넣어주면 된다.


msvcp100d.zip


msvcr100d.zip


지금까지 이미지 출력과 동영상파일 출력을 알아보았는데, 이번 시간에는 웹캠으로부터 받은 영상을 띄우는 방법에 대해 알아보도록 하겠다. 동영상파일 출력까지 해보았다면 카메라(웹캠)영상은 쉽게 띄울수 있을것이다.


먼저 간단한 코드는 다음과 같다.

#include <opencv\highgui.h>

int main() {
	IplImage *frame;
	
	CvCapture* capture = cvCaptureFromCAM(0);

	cvNamedWindow("Test",1);
	
	while(capture) {
		frame = cvQueryFrame(capture);
		cvShowImage("Test",frame);

		if(cvWaitKey(33) >= 27)
			break;
	}
	cvReleaseCapture(&capture);
	cvDestroyWindow("Test");

	return 0;
}

전체적으로 동영상을 출력할 때와 비슷하다. 동영상 출력과 다른점은 cvCaptureFromCAM()부분이다. cvCaptureFromCAM()함수를 이용하여 웹캠으로부터 영상을 받아온다. 인자로는 숫자를 입력하는데, 0을 넣을경우 가장 기본으로 연결되어 있는 웹캠으로부터 영상을 받아온다. 노트북의 경우 내장된 웹캠이 0번이고 추가적으로 연결을 한 웹캠이 1번으로 되어있을 것이다. PC의 경우 내장된 웹캠이 없으므로 연결한 웹캠이 0번이 된다. 

영상처리를 위한 코드는 while문 안의 cvQueryFrame()뒤에 넣어주면 된다.


실행화면 :



[영상처리] 이미지 파일 출력


[영상처리] 동영상파일(AVI) 출력

영상처리를 하기 위해 미리 녹화된 동영상을 쓰는 경우가 있다. 이번시간에는 동영상을 띄우는 방법에 대해 알아보도록 하겠다.

동영상 파일을 여는 일은 이미지 파일을 출력했던것 만큼 쉽다. 하지만 방법이 약간 다르다.

이미지 파일은 단순히 한장의 영상이지만, 동영상은 이미지 파일의 집합이라고 볼 수 있다. 여러개의 사진이 계속해서 바뀌는 개념이다. 그렇기 때문에 매 프레임마다 화면을 바꿔준다고 생각하면 될 것이다.


코드는 다음과 같다.

#include <opencv\highgui.h>

int main() {
	IplImage *frame;
	
	CvCapture* capture = cvCaptureFromFile("testAVI.avi");
	//CvCapture* capture = cvCreateFileCapture("testAVI.avi");
	//CvCapture* capture = cvCaptureFromAVI("testAVI.avi");

	cvNamedWindow("Test",1);
	
	while(capture) {
		frame = cvQueryFrame(capture);
		cvShowImage("Test",frame);

		if(cvWaitKey(33) == 27)
			break;
	}
	cvReleaseCapture(&capture);
	cvDestroyWindow("Test");

	return 0;
}

CvCapture형식의 capture에 영상을 저장한다.

동영상 파일을 불러올 때는 cvCaptureFromFile() 이나 cvCreateFileCapture() 또는 cvCaptureFromAVI()중 하나를 선택하여 사용한다. 각 함수의 차이점은 크게 없어보인다. 

인자로는 파일이름, 파일형식을 넣어준다. 

파일 형식은 avi, wmv, mp4, 3gp, mkv 등등이 되는 것 같다. 아마 왠만한 파일 형식은 로드가 될 것이지만 avi파일을 로드하는 것을 추천한다.

반복문이 시작하기 전에 cvNamedWindow()를 이용하여 영상을 띄울 윈도우를 생성한다.

while문을 사용하여 영상을 띄워준다. capture가 null이 아닐경우 반복문을 실행한다.

IplImage형식의 frame이라는 변수에 capture를 통해 얻어온 이미지를 cvQueryFrame()을 이용하여 입혀준다. 

cvWaitKey()를 이용하여 ESC를 입력하였을 때 종료하도록 한다.

cvWaitKey(33)에서 33은 프레임을 받아오는 속도이다. 단위는 미리 세컨드이다(1/1000초). 값이 높을수록 프레임이 느리다. 33을 입력하면 33/1000 이므로 대략 초당 30프레임정도를 받아올 것이다.


영상처리를 위한 코드는 while문 안에 cvQueryFrame()을 수행한 뒤에 넣어주면 된다.


실행화면 : 


capture의 값만 cvCaptureFromCAM()으로 변경해 준다면 웹캠영상도 띄울수 있다.


[영상처리] 이미지 파일 출력


[영상처리] 카메라(웹캠)영상 출력

OpenCV의 HighGUI라이브러리에는 다양한 포맷의 영상 파일을 불러올 수 있는 기능이 정의되어 있다.

HighGUI 라이브러리를 이용하여 영상을 불러와서 화면에 출력하는 예제이다.

#include <opencv\highgui.h>

int main() {
	IplImage *image = cvLoadImage("lena.jpg");

	cvNamedWindow("Test",1);
	cvShowImage("Test",image);
	cvWaitKey(0);

	cvReleaseImage(&image);
	cvDestroyWindow("Test");

	return 0;
}

cvLoadImage()는 영상 데이터의 구조체 포인터를 반환한다. 인자 값으로 파일명과 형식을 넣어준다.

구조체의 이름은 IplImage이며, 단일 채널, 다중 채널, 정수형, 실수형 등 모든 형태의 영상 데이터를 IplImage로 표현할 수 있다. 또한 BMP, DIB, JPEG, JPE, PNG, PBM, PGM, PPM, SR, RAS, TIFF 등의 영상 포맷을 지원한다.

*출력할 영상은 프로젝트 폴더내에 존재하여야 한다.


cvNamedWindow()는 영상을 표시할 윈도우를 하나 만드는 일을 한다.

첫 번째 인자는 윈도우의 이름으로 등록된다.

두 번째 인자는 윈도우의 속성을 지정한다. 여기에는 0(기본값) 또는 1이 들어갈 수가 있다.

인자값이 0으로 지정되면 불러오는 영상의 크기에 상관없이 윈도우 크기는 일정하게 고정이 되고, 영상이 윈도우의 크기에 맞게 확대 또는 축소되어 나타난다. 만약 1(CV_WINDOW_AUTOSIZE)이면 불러오는 영상의 실제 크기에 맞게 윈도우의 크기가 자동으로 조절된다.


cvShowImage()함수는 IplImage* 타입으로 생성된 영상을 해당 제목을 갖은 윈도우에 영상을 출력한다.

첫번째 인자는 윈도우의 이름이고, 두번째 인자는 IplImage이다.


cvWaitKey()함수는 프로그램의 동작을 잠시 멈추고 사용자로부터 키 입력을 기다린다.

양의 정수일 경우 밀리초(millisecond) 단위로 지정한 시간동안 대기한다.

인자값이 0이나 음수일 경우 키가 눌려질 때 까지 무한정 기다린다.


cvReleaseImage()함수는 할당된 메모리 공간을 해제한다. 

인자로 IplImage*의 주소값을 전달해준다. 이 함수가 실행된 후 해당 IplImage는 NULL로 설정된다.


cvDestroyWindow()함수는 윈도우를 닫고, 윈도우를 위해 동적할당된 메모리 공간을 모두 해제한다. 

cvDestroyAllWindows()함수를 이용하여 한번에 모든 창을 닫을 수도 있다.


간단한 프로그램의 경우 프로그램이 종료할 때 운영체제에 의해 할당된 모든 리소스들이 자동으로 반환되지만 cvReleaseImage()함수와 cvDestroyWindow()함수를 직접 호출하여 리소스를 반환하는것이 좋다.





이미지 파일을 출력하는 또 다른 방법이 있다. 다음과 같이 소스를 작성했을 경우 명령프롬프트 창을 이용하여 실행해야한다.

#include <opencv\highgui.h>

int main(int argc, char** argv) {
	IplImage *image = cvLoadImage( argv[1] );

	cvNamedWindow("Test",1);
	cvShowImage("Test",image);
	cvWaitKey(0);

	cvReleaseImage(&image);
	cvDestroyWindow("Test");

	return 0;
}

main함수에 인자가 있는 경우인데, 코드를 작성한뒤 디버깅을 해야 코드가 적용된다. 코드 작성후 디버깅을 한뒤 명령프롬프트 창에서 명령어를 입력해준다.

이때 출력할 영상파일이 같은 폴더에 존재하여야 한다. 


프로젝트폴더\Debug>프로젝트이름.exe 이미지파일.파일형식



다음과 같이 입력하면 영상이 출력되는 모습을 볼 수 있다.


[영상처리] 동영상파일(AVI) 출력


[영상처리] 카메라(웹캠)영상 출력

영어의 경우 평균 필기 속도는 초당 1.5~2.5 문자이고 최고속도는 l을 연속해서 쓸 경우 초당 5~10문자 정도가 된다.

한글의 경우 평균 필기 속도는 초당 0.5~1.5 문자이고 최고속도는 이 를 연속해서 쓸 경우 초당 2~3문자 정도가 된다.


테블릿의 경우 x,y축 방향에 0.1~0.5인치 간격으로 격자 형태의 도체가 있다.

정상 필기 속도를 감당하기 위해서는 1인치당 최소한 200개의 점을 표현할 수 있는 해상도를 가져야하며, 초당 최소한 100개의 점의 위치를 추출할 수 있어야 한다.


영어는 26개의 알파벳으로 구성되어 있는데, 각각 대문자와 소문자가 있다. 또 필기 방법에는 정차게와 흘림체가 있고, 한 단어에 5개의 문자가 포함된다. 한 문자는 대문자의 경우 평균 2개의 획으로, 소문자의 경우 평균 1개의 획으로 구성된다.

(획 = 펜을 누른 상태로 부터 뗄 때까지의 펜의 이동 자취)

영어에서는 문자의 위치와 크기가 중요하다. 대문자는 크고 바닥선(Baseline)에 위치하는 반면, 소문자는 작고 대부분 대문자 크기의 절반 정도이다.


한글은 초성 19개, 중성 21개, 종성 28개의 기본 자소가 규칙적으로 결합하여 하나의 문자를 생성한다.필기 방법에는 영어와 마찬가지로 정자체와 흘림체가 가능하고 한 문자는 평균 2~6개의 획으로 이루어진다.


비슷한 문자 문제

영어의 U-V, C-L, a-d, n-h, O-o, I(아이)-1, 1-l(엘), Z-2, S-5, G-6 등이 유사하다.

O-o와 I(아이)-1은 똑같이 필기 되기도 하기 때문에 문맥적 정보에 의해서만 구분이 가능하다.

대소문자가 형태가 같은경우 C-c, K-k, O-o, P-p, Y-y, Z-z 등이 있는데 이러한 문자는 크기로 구분 가능하고, P-p, Y-y의 경우 바닥선에 대한 문자의 위치에 의해 구별될 수 있다.


전처리(Preprocessing)후 정합(Matching)을 하게 된다.


전처리

외부 분리

잡음 제거

정규화


전처리는 각종 잡영을 줄여주는 신호처리 과정이다. 대표적인 잡영으로는 하드웨어 결함이나 필기자의 부주의로 나타나는 난폭점과 획의 처음과 끝에 빈발하는 삐침을 들 수 있다. 이러한 잡영을 없애는 일반적인 방법은 존재하지 않으며, 대부분 적절한 휴리스틱을 사용하여 해결한다. 

(ex. 따로 떨어져 있는 점이나, 획 중간에 갑자기 튄 점 등은 난폭점으로 간주하며, 획의 처음 또는 끝에 예리한 각도로 짧게 나타나는 세그먼트는 불필요한 삐침일 가능성이 높다고 판단하여 제거)

Visual Studio 2010에 OpenCV 2.4.8버전을 설치해 보겠다. Visual 6.0 + OpenCV 1.0 버전부터 Visual Studio2012까지 OpenCV를 설치해 보았지만 OpenCV를 설치하는 일이란 참 귀찮다.. 이번엔 Visual Studio 2010에 설치를 할텐데, Visual Studio 2012에 설치하는 방법이랑 별 다른 차이는 없을듯 하다.

1. OpenCV 다운받기

다운받으러 가기

설치하기 위해 OpenCV 최신버전을 받는다. 현재(2014.1.5) 최신버전은 2.4.8버전이다.

다운받은후 실행하여 압축을 해제한다. 난 C드라이브에 압축해제 하였다. 알아서 opencv라는 폴더가 생성이 될 것이다.



2. 환경변수 설정

내컴퓨터 - 속성 - 고급 시스템 설정 - 환경변수 - 시스템변수 - Path - 편집

이전 환경변수를 ;(세미콜론)으로 구분지어주고
(Opencv설치폴더)\build\x86\vc10\bin; 입력 후 확인

ex) C:\opencv\build\x86\vc10\bin;

Visual Studio 2010 의 경우 vc10
Visual Studio 2008 의 경우 vc9 를 선택하면 된다.

x86은 32bit
x64는 64bit 환경이다.

재부팅을 해야 환경변수가 적용이 된다. 재부팅을 하고 계속 진행을 하자


3. 속성 설정

Visual Studio 2010 실행 후 Win32 콘솔 응용 프로그램으로 프로젝트를 생성한다. 


빈 프로젝트로 생성한다.


프로젝트 생성후 속성 관리자에서 새 프로젝트 속성 시트를 추가한다.
Debug|Win32 - 마우스 오른쪽 클릭 - 새 프로젝트 속성 시트 추가


OpenCV_Debug로 생성된 속성을 열어 수정한다.
OpenCV_Debug - 마우스 오른쪽 클릭 - 속성

공용 속성 - C/C++ - 일반 - 추가 포함 디렉터리를 추가한다.
(OpenCV 설치 폴더)\build\include

ex) C:\opencv\build\include

공용 속성 - 링커 - 일반 - 추가 라이브러리 디렉터리를 추가한다.
(OpenCV 설치 폴더)\build\x86\vb10\lib

ex) C:\opencv\build\x86\vc10\lib

공용 속성 - 링커 - 입력 - 추가 종속성 을 추가한다.
필요한 것들만 추가해도 된다. 하나를 추가 할 때 마다 ;(세미콜론)으로 구분지어 줘야한다.

opencv_calib3d248d.lib
opencv_contrib248d.lib
opencv_core248d.lib
opencv_features2d248d.lib
opencv_flann248d.lib
opencv_gpu248d.lib
opencv_highgui248d.lib
opencv_imgproc248d.lib
opencv_legacy248d.lib
opencv_ml248d.lib
opencv_nonfree248d.lib
opencv_objdetect248d.lib
opencv_ocl248d.lib
opencv_photo248d.lib
opencv_stitching248d.lib
opencv_superres248d.lib
opencv_ts248d.lib
opencv_video248d.lib
opencv_videostab248d.lib

(추가 해주는 이름뒤에 숫자는(248) 2.4.8 버전 이라는 뜻이므로 다른 버전을 설치한다면 숫자를 바꿔줘야한다.
또한 Debug모드이기 때문에 맨 뒤에 d가 들어가는데 Release모드라면 d를 빼고 입력해주면 된다.)


4. 테스트 하기

모든 설정이 끝났기 때문에 테스트를 해보겠다. 소스파일을 하나 생성한다. 

소스 파일 생성후 다음과 같은 코드를 입력한다.

#include <opencv\cv.h>
#include <opencv\highgui.h>

int main() {
	IplImage *image = cvLoadImage("test.jpg");

	cvShowImage("Test",image);
	cvWaitKey(0);

	cvReleaseImage(&image);
}

출력할 이미지인 test.jpg 파일을 프로젝트 폴더안에 넣어두고 실행을 한다.
test.jpg의 이미지가 나온다면 힘겨웠던 opencv 설치가 완료된것이다.^^

앞으로 새로운 프로젝트를 생성했을시 지금 만들었던 속성을 불러와서 사용하면 된다.

힘겨운 Open CV 설치 과정이 끝났다. 잘 따라했는데 에러가 난다면 오타가 있거나 중간에 틀린 부분이 있을것이다.

그러나 잘 따라 했다고 해도 에러가 나는 경우가 있다 64비트 컴퓨터에 그러한 경우가 발생할 텐데 아래 글을 읽고 해결하도록 하자.

2014/01/25 - [Programming/영상처리] - [영상처리] 64bit에 OpenCV 설치 에러 해결방법



 안드로이드에서 음성인식 기능을 구현하기 위해서는 구글의 Speech-To-Text(이하 STT)기능을 사용하면 됩니다. SST기능을 사용하는 방법은 2가지가 있습니다. 구글에서 지원하는 UI를 사용하거나, 나만의 커스텀 UI를 만드는 방법이 있습니다. 이 두가지 방법을 사용하는 예제는 인터넷에서 충분히 구할 수 있으나 이 두가지 방법은 제 맘에 드는 방법이 아니었습니다.

 저는 현재 음성녹음 시작과 함께 음성인식(STT)이 가능한 앱을 개발해야 했는데, 저 두가지 방법은 액티비티 전환이 필요하기 때문에 액티비티 전환 없이 음성인식을 하도록 만들어 보았습니다. 음성녹음의 경우 어렵지 않게 예제를 보고 구현할 수 있기 때문에 음성녹음 시작시 액티비티 전환 없이 음성인식을 하는 기능만 구현하면 되겠거니 생각해서 만들어 보았습니다.


액티비티(Activity)전환 없이 음성인식(구글 STT) 사용하기

 일단 프로젝트를 하나 생성한다. 그리고 가장 처음에 퍼미션부터 등록하도록 하겠다. 이 부분은 자칫하면 깜빡하고 넘어가기 쉽기 때문에(글쓴이 같은 경우..) 가장 먼저 등록을 해놓고 시작하겠다.

AndroidManifest.xml

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

 소리를 입력받기 위해 RECORD_AUDIO 권한이 필요하고, 음성인식을 하기위해 인터넷에 접속해야 하기 떄문에 INTERNET 권한이 필요하다.

 음성 인식 시작을 위한 버튼하나정도는 필요할 것이다. 버튼 하나와 결과를 출력할 텍스트뷰를 만들어준다.

 그리고 액티비티가 최초 생성될 때 onCreate시 음성인식을 위한 준비를 한다.

 MainActivity.java

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		
		i = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
		i.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, getPackageName());
		i.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "ko-KR");
	}

i는 전역변수로 설정해주었다. (Intent i)
마지막 줄인 EXTRA_LANGUAGE는 인식할 언어를 설정해주는 코드이다. 글쓴이는 한글을 인식하였다. 영어를 인식하기 위해서는 "ko-KR" 대신 "en-US"를 써주면 된다.

("ko-KR"로 설정해도 abc라고 발음했을 경우 "에이비씨" 보다 "abc"로 나오는 경우가 많은것 같다.)

다음은 버튼 클릭시 작동하는 코드이다.

MainActivity.java

			mRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
			mRecognizer.setRecognitionListener(listener);
			mRecognizer.startListening(i);

mRecognizer역시 전역변수로 설정 해 주었다. (SpeechRecognizer mRecognizer)
위의 두줄은 onCreate에 넣어줘도 상관은 없을것 같다. 마지막 줄은 음성인식 리스너를 불러오기 때문에 버튼 클릭시 실행하도록 한다.

다음은 리스너를 작성하자. 몇줄만 넣으면 자동완성으로 리스너 안의 메소드가 작성된다.

MainActivity.java

private RecognitionListener listener = new RecognitionListener() {
		
		@Override
		public void onRmsChanged(float rmsdB) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onResults(Bundle results) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onReadyForSpeech(Bundle params) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onPartialResults(Bundle partialResults) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onEvent(int eventType, Bundle params) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onError(int error) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onEndOfSpeech() {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onBufferReceived(byte[] buffer) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onBeginningOfSpeech() {
			// TODO Auto-generated method stub
			
		}
	};

각 메소드의 이름만 보면 언제 동작할 것인지 이해할것이라 생각하고 음성인식 결과를 텍스트로 출력하는 부분만 작성하겠다. 

참고로 onRmsChanged 메소드는 입력받는 소리의 크기를 나타내주는데, 소리 크기값인 rmsDb는 디폴트 값이 -2 또는 -2.12 인거 같다. 한번 음성입력을 받고 일정 시간동안 입력이 없으면(rmsDb값이 -2 또는 -2.12일 때) 자동으로 종료되는듯 하다.

음성인식 결과는 onResult 메소드에서 얻을 수 있다.

MainActivity.java

@Override
public void onResults(Bundle results) {
	String key = "";
	key = SpeechRecognizer.RESULTS_RECOGNITION;
	ArrayList<String> mResult = results.getStringArrayList(key);
	String[] rs = new String[mResult.size()];
	mResult.toArray(rs);
	tv.setText(""+rs[0]);		
}

tv는 텍스트뷰이다. tv역시 전역변수로 설정하였다.(TextView tv)
results라는 결과값을 mResult라는 ArrayList에 저장한다. 
rs라는 String배열을 만들어 mResult의 크기만큼 배열을 초기화 하고 그 rs라는 배열에 mResult값을 넣는다.
for루프를 돌려서 인식한 결과값을 모두 볼 수도 있고, rs[0]으로 첫번째 나온 값만 받을수 있다.

액티비티 전환없이 음성인식을해서 결과를 받아왔다. 음성인식 시작코드만 음성녹음 시작시에 실행하도록 하면 음성녹음과 동시에 음성인식을 할 수 있을것 같다. 추가적으로 입맛에 맞게 코드 수정도 자유롭게 할 수 있을것 같다.



제가 설명이 부족한 부분이 있을텐데 댓글로 질문해주시면 감사하겠습니다.


 요즘 많은 어플들이 뒤로가기 버튼을 두번 눌러 앱을 종료시킨다. 
 종료하시겠습니까? 라는 확인창이 뜨고 확인을 눌러야 되는 번거로움이 없고, 잘못 종료버튼을 눌렀을 경우에도 토스트 알림창만 뜰뿐 별다른 방해요소가 없어서 참 좋은것 같다. 요즘 대부분의 앱이 이러한 종료 방식을 선택하고 있기때문에 한번 구현을 해보자.



 구현방법은 간단하다 뒤로가기 버튼 클릭시 현재시간을 저장하며 토스트 알림창을 띄워주고 한번더 눌렀을때 일정시간(예를들면 2초)가 지났는지 확인하고 일정시간이 지나지 않았을 경우 종료, 지났을 경우 알림창을 다시 띄워주면 된다.

0.    시간을 저장하는 변수(t) = 0;
1.    뒤로가기 버튼 (처음)클릭시 시간을 저장하는 변수(t) + 2000(2초)가 현재 시간보다 작다.
2-1. 알림창을 띄운다.('뒤로'버튼을 한번 더 누르시면 종료됩니다.)
2-2. 시간을 저장하는 변수(t)에 현재 시간을 저장한다.
3.    뒤로가기 버튼을 한번더 클릭
4-1. 현재 시간이 변수t + 2000보다 작으면 앱 종료
4-2. 현재 시간이 변수t + 2000보다 크면 t에 현재시간을 저장하고 알림창을 띄운다.


글로 보면 어렵더라도 소스를 보면 아마 바로 이해가 갈 것이다.

먼저 뒤로가기 버튼의 이벤트를 담당하는 클래스를 하나 만들었다.

BackPressCloseHandler.java

import android.app.Activity;
import android.widget.Toast;

public class BackPressCloseHandler {

	private long backKeyPressedTime = 0;
	private Toast toast;

	private Activity activity;

	public BackPressCloseHandler(Activity context) {
		this.activity = context;
	}

	public void onBackPressed() {
		if (System.currentTimeMillis() > backKeyPressedTime + 2000) {
			backKeyPressedTime = System.currentTimeMillis();
			showGuide();
			return;
		}
		if (System.currentTimeMillis() <= backKeyPressedTime + 2000) {
			activity.finish();
			toast.cancel();
		}
	}

	public void showGuide() {
		toast = Toast.makeText(activity,
				"\'뒤로\'버튼을 한번 더 누르시면 종료됩니다.", Toast.LENGTH_SHORT);
		toast.show();
	}
}


다음은 이 기능을 사용하려는 액티비티에서 넣어줘야 되는 소스이다. 사용하려는 액티비티에 onBackPressed()메소드를 생성하고 BackPressCloseHandler 클래스의 onBackPressed()메소드를 불러와주면 된다.

MainActivity.java

public class MainActivity extends Activity {

	private BackPressCloseHandler backPressCloseHandler;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		
		backPressCloseHandler = new BackPressCloseHandler(this);
	}

	@Override
	public void onBackPressed() {
		//super.onBackPressed();
		backPressCloseHandler.onBackPressed();
	}
}

+ Recent posts