2017년 10월 15일 일요일

Android Camera+Compass (나침반)


Android Camera+Compass (나침반)


준비

미리 학습이 필요한 내용은 아래와 같습니다.

나침반
https://swlock.blogspot.com/2017/10/android-compass.html

카메라
https://swlock.blogspot.com/2017/10/transparent-canvas-in-android-camera.html

카메라 위에 그림그리기
https://swlock.blogspot.com/2017/10/transparent-canvas-in-android-camera.html


작업 방법

카메라 overlay 예제에서 CustomView 안에 sensor manager를 넣고 onDraw시 화면과 방위각 자표를 구하면 됩니다.

구현 소스

SensorManager 준비한다.
CustomView.java
    public CustomView(Camera2BasicFragment context) {
        super(context.getActivity().getBaseContext());
        mAzimutArray = new float[COM_DATA_SIZE];
        this.context = context.getActivity().getBaseContext();
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setTextSize(30);
        setBackgroundColor(Color.TRANSPARENT);

        // initialize your android device sensor capabilities
        mSensorManager = (SensorManager) context.getActivity().getBaseContext().getSystemService(SENSOR_SERVICE);
        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mMagneticField = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
    }

Activity에서는 OnResume, OnPause가 있었지만 CustomView에서는 따로 없기 때문에 만들어줍니다.

CustomView.java 메소드
    public void resume() {
        mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
        mSensorManager.registerListener(this, mMagneticField, SensorManager.SENSOR_DELAY_NORMAL);
    }
    public void pause() {
        mSensorManager.unregisterListener(this);
    }

onResume 내부에서 resume을 onPause에서 pause를 호출 해줍니다.
Camera2BasicFragment.java
    @Override
    public void onResume() {
        super.onResume();
        startBackgroundThread();

        // When the screen is turned off and turned back on, the SurfaceTexture is already
        // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
        // a camera and start preview from here (otherwise, we wait until the surface is ready in
        // the SurfaceTextureListener).
        if (mTextureView.isAvailable()) {
            openCamera(mTextureView.getWidth(), mTextureView.getHeight());
        } else {
            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        }
        if(mCustomView!=null)
            mCustomView.resume();
    }

    @Override
    public void onPause() {
        closeCamera();
        stopBackgroundThread();
        if(mCustomView!=null)
            mCustomView.pause();
        super.onPause();
    }

출력 onDraw내부
"N" "E" "W" "S" Text를 각각 출력시켜주며 getChangedAzimut()함수로 방위각 정보를 가져옵니다.

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if( mOutput==null )
            return;

        paint.setColor(Color.WHITE);
        paint.setTextSize(30);
        paint.setStrokeWidth(2);
        canvas.drawText(mOutput,100,100,paint);

        int width = getWidth();
        int height = getHeight();
        int centerx = width / 2;
        int centery = height / 2;
        float radius;

        if (centerx > centery) {
            radius = (float) (centery * 0.7);
        } else {
            radius = (float) (centerx * 0.7);
        }

        paint.setColor(Color.YELLOW);
        paint.setTextSize(100);
        canvas.drawText(String.valueOf((int)getChangedAzimut()),centerx-40,centery,paint);
        //
        canvas.rotate(-getChangedAzimut(), centerx, centery);
        paint.setColor(Color.RED);
        paint.setStrokeWidth(1);
        paint.setTextSize(100);
        canvas.drawCircle(centerx, centery, (float)(radius*1.1), paint);
        canvas.drawCircle(centerx, centery, (float)(radius*0.5), paint);
        paint.setStrokeWidth(10);
        canvas.drawText("N", centerx-30, centery - radius+30, paint);
        canvas.drawLine(centerx, centery - radius, centerx, centery, paint);
        paint.setColor(Color.BLUE);
        canvas.drawText("W", centerx - radius-30, centery+30, paint);
        canvas.drawLine(centerx, centery, centerx - radius, centery, paint);
        canvas.drawText("E", centerx + radius-30, centery+30, paint);
        canvas.drawLine(centerx + radius, centery , centerx, centery, paint);
        canvas.drawText("S", centerx-30, centery + radius+30, paint);
        canvas.drawLine(centerx, centery, centerx, centery + radius, paint);

    }

방위값의 민감도 처리

방위각정보가 이전에는 변수로 되어있었는데요. 문제점은 센서에 올라오는값이 너무 민감해서 값 흔들림이 심하게 됩니다.
평균을 구하면 되는데... 이 부분에서 많은 시간을 소모 하였습니다.
-180~+180 => 0~360 으로 변환했던 변환하지 않았던지, 값이 변화하는 구간에서 문제가 발생합니다. 0~360인 경우를 예로 들어보겠습니다.
아래의 구간이 있다고 가정할때 평균을 구하면
359,0,1 => (359+1)/3 => 120 이 됩니다.
우리가 원하는 기대값은 0 정도 나오길 바랍니다. 왜냐하면 359도, 0, 1도는 모두 0도 근처이기 때문입니다.
-180~+180 이런 구간을 사용하더라도 -180과 +180 구간에서 문제가 발생합니다.

구현

일단 올라오는 모든 방위값을 저장합니다.
들어오는 방위값 정보가 -180~+180 사이이므로 0~360도 사이로 변환해서 저장합니다.
방위값 저장
    private void changeAzimut(float mAzimut) {
        // mAzimut -180 ~ +180
        if( mAzimut < 0 ) mAzimut=mAzimut+360.0f;
        mAzimutArray[comindex++]=mAzimut;
        if( COM_DATA_SIZE <= comindex ){
            comindex = 0;
            full = true;
        }
    }

방위값을 mAzimutArray[] 변수에 COM_DATA_SIZE 갯수 만큼 넣게 되는데 만약 해당 변수에 가득 차지 않으면 full 변수는 false가 됩니다. full이 되어야만 평균을 내도록 합니다.

아래 부분은 0번째 값을 기준으로 다른 항목들의 차이를 구한후 차이가 180보다 크면 360을 빼거나 더하는 방법을 사용합니다.
즉,
350, 10, 30, 355 일때와 10, 350, 355, 20 일때 어떤 코드에 의해서 동작이 되는지 살펴보면 아래와 같습니다.

     350   :   10     30     355
차이       :   340    320      5
180보다큼  :   예     예     아니오
180크면+360: 10+360 30+360   355
최종       :   370    390    355
350,370,390.355
                if (firstValue>mAzimutArray[i]) {
                    if(firstValue-mAzimutArray[i]>180){
                        arr[i]=mAzimutArray[i]+360;
                    }else{
                        arr[i]=mAzimutArray[i];
                    }
                }

     10        350    355     20
차이       :   340    345     10
180보다큼  :   예     예     아니오
180크면-360: 350-360 355-360  20
최종       :   -10    -15     20

10,-10,-15,20
                }else{
                    if(mAzimutArray[i]-firstValue>180){
                        arr[i]=mAzimutArray[i]-360;
                    }else{
                        arr[i]=mAzimutArray[i];
                    }
                }
결과로 나오는 350,370,390.355 또는 10,-10,-15,20 이 값을 arr[] 배열에 넣어서 평균을 구하게 되는데 단순하게 평균을 구하지는 않습니다.

방법은 저장된 변수에서 최대값, 최소값 하나씩을 제외하고 평균을 내는 방식입니다.
아래 부분이 최대값 최소값의 인덱스를 저장하는 부분입니다.
            for (i = 0; i < COM_DATA_SIZE; i++) {
                if (arr[i] < min) {
                    min = arr[i];
                    mini = i;
                }
                if (arr[i] > max) {
                    max = arr[i];
                    maxi = i;
                }
            }
최종 방위값 범위로 만들기는 아래 코드에 의해 0~360값 사이로 정규화 됩니다.
            float dir = sum/count;
            for(;;) {
                if (dir>=0 && dir<=360) break;
                if (dir < 0) dir += 360;
                if (dir > 360) dir -= 360;
            }

방위값을 평균내기
    private float getChangedAzimut() {
        if( full ) {
            int i;
            float min = 1000, max = -1000;
            int mini = -1,maxi = -1,count = 0;
            float sum = 0.0f;
            float arr []=new float[COM_DATA_SIZE];

            float firstValue = mAzimutArray[0];
            arr[0] = mAzimutArray[0];

            for (i = 1; i < COM_DATA_SIZE; i++) {
                if (firstValue>mAzimutArray[i]) {
                    if(firstValue-mAzimutArray[i]>180){
                        arr[i]=mAzimutArray[i]+360;
                    }else{
                        arr[i]=mAzimutArray[i];
                    }
                }else{
                    if(mAzimutArray[i]-firstValue>180){
                        arr[i]=mAzimutArray[i]-360;
                    }else{
                        arr[i]=mAzimutArray[i];
                    }
                }

            }

            for (i = 0; i < COM_DATA_SIZE; i++) {
                if (arr[i] < min) {
                    min = arr[i];
                    mini = i;
                }
                if (arr[i] > max) {
                    max = arr[i];
                    maxi = i;
                }
            }
            for (i = 0; i < COM_DATA_SIZE; i++) {
                if( mini == i || maxi == i ) continue;
                count ++;
                sum += arr[i];
            }
            float dir = sum/count;
            for(;;) {
                if (dir>=0 && dir<=360) break;
                if (dir < 0) dir += 360;
                if (dir > 360) dir -= 360;
            }
            return dir;
        }else{
            return mAzimutArray[comindex];
        }
    }

전체 소스

https://drive.google.com/open?id=0B9vAKDzHthQIeGhaZXFveE1JMXc

동작화면







댓글 없음:

댓글 쓰기