지속해서 업데이트 됩니다. (11/13)
ctrl + d를 눌러 북마크를 추가할 수 있습니다. 
모든 질문은 커뮤니티 Q&A 게시판에서 받습니다.


C/C++

  

C#



Python



Java



Android



HTML/CSS



JavaScript



PHP



DataStructure



Algorithm



Database



Network





저번에 Tesseract와 OpenALPR을 이용하여 C# 프로그램을 만들었는데 이번에는 안드로이드에서 만들어보겠습니다!!


참고 ☞ C# 문자인식 프로그래밍


안드로이드에서 문자인식을 하기위해서는 기본적으로 OpenCV 라이브러리가 필요합니다.


참고 ☞ 안드로이드에서 OpenCV 사용하기


안드로이드에서는 API를 사용하지 않고 조금 원시적인(?) 방법으로 문자인식으로 해보려고 합니다.


※ 문자인식을 하기 위한 절차


1. 안드로이드와 카메라를 연동합니다.(CameraPreview)

2. 사진을 촬영하고 내부저장소에 저장합니다.(PNG)

3. 저장소에 저장된 이미지파일을 불러옵니다.(Bitmap)

4. 불러온 이미지 파일에 전처리 작업을 수행합니다.(GrayScale, ThresHold, 등)

5. 전체 이미지에서 번호판 영역을 관심영역으로 설정하고 따로 분류합니다.(ROI, contours)

6. 분류된 번호판 영역 이미지 파일에서 번호판 숫자 및 문자를 라벨링 처리합니다.

7. 라벨링 처리 된 숫자 및 문자들을 미리 준비한 템플릿과 비교합니다.

8. 비교한 결과 중 가장 매칭률이 높은 숫자 및 문자를 선별합니다.

9. 모든 숫자와 문자를 순서대로 조합하면 문자인식 결과입니다.


안드로이드에서도 물론 Tesseract API(tess-two)를 사용할 수 있습니다. (30줄 이내에...) 

하지만 이런 원시적인 방법을 통해 실제로 문자 및 패턴인식을 하는 원리를 파악할 수 있으며 가장 중요한것은 인식률이 상당히 높다는 것입니다!!! (Tesseract OCR이 한글 문자 인식률이 매우 낮은걸로 유명하죠... 하지만 워낙 라이브러리 자체가 유연해서 거의 모든 프로그래밍 언어를 지원하며 문자 인식 훈련을 통해 인식률을 높일수 있답니다.)


자 그럼 절차대로 하나하나씩 따라해봅시다.


1, 2. 안드로이드 카메라 연동 및 사진 저장 : 이미 블로그에 설명한 내용이 있으므로 링크로 대체합니다.


3. 저장된 이미지 파일 불러오기 : 내부 저장소에 저장된 이미지 파일을 불러오는 방법은 다음과 같이 간단합니다. PNG파일을 File 객체를 통해 지정하고 BitmapFactory.decodeFile을 통해 이미지를 비트맵 객체로 불러올 수 있습니다.


File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/OpenCVTest/sample.png");

if(file.exists()){

Bitmap myBitmap = BitmapFactory.decodeFile(file.getAbsolutePath());


4. 이미지 전처리 작업 : 기본적으로 GrayScale(흑백) 및 ThresHold(이진화) 처리를 수행합니다.


Bitmap image1;

OpenCVLoader.initDebug();


Mat img1=new Mat();

Utils.bitmapToMat(myBitmap ,img1);


Mat imageGray1 = new Mat();

Mat imageCny1 = new Mat();


Imgproc.cvtColor(img1, imageGray1, Imgproc.COLOR_BGR2GRAY);

Imgproc.threshold(imageGray1, imageCny1, 160, 255, Imgproc.THRESH_BINARY);


5. 이미지 관심영역 추출 : 링크


6, 7, 8, 9. 번호판 숫자 인식 : 

코드가 약간 복잡합니다. 간단히 설명드리면 다음 절차와 같이 수행합니다.

1. 전체사진 불러오기 -> 전처리 -> 번호판만 추출

2. 번호판사진 불러오기 -> 흑백 반전(검은바탕에 흰색 숫자가 되도록) -> 숫자 추출

3. 추출된 숫자와 템플릿 비교

4. 결과 출력


* 신형 번호판의 숫자와 문자는 00가 0000 이런식으로 숫자6개 문자1개 총 7개의 라벨이 생깁니다. 하지만 'ㄱ ㅏ' 라는 문자의 경우 'ㄱ'과 'ㅏ'를 따로 라벨링 처리하기 때문에 총 8개의 문자가 생길때도 있습니다. 이를 처리하기위해 라벨이 7개, 8개인 경우를 각각 조건문으로 처리했습니다.


int[][] listarr = new int[8][4];

List<MatOfPoint> contoursROI = new ArrayList<>();

Mat hierarchyROI = new Mat();

Mat matROI = new Mat();

Mat ResultROI = new Mat();

Utils.bitmapToMat(roi, matROI);

Imgproc.cvtColor(matROI, ResultROI, Imgproc.COLOR_BGR2GRAY, 1);

Imgproc.findContours(ResultROI, contoursROI, hierarchyROI, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

int count = 0;

for(int idx = 0; idx >= 0; idx = (int) hierarchyROI.get(0, idx)[0]) {

    if(count>7) break;

    MatOfPoint matOfPoint = contoursROI.get(idx);

    Rect rect = Imgproc.boundingRect(matOfPoint);

    // 해상도별로 조절하기.

    if(rect.x < roi.getWidth()/20 || rect.x > roi.getWidth()-(roi.getWidth()/10) ||

            rect.y < roi.getHeight()/10 || rect.y > roi.getHeight()-(roi.getHeight()/10) ||

            rect.width < roi.getWidth()/50 || rect.width > roi.getWidth()/8 ||

            rect.height <= roi.getHeight()/10 ) continue;


    Log.d("RECT : ", "x : " + rect.x + ", y : " + rect.y + ", w :" + rect.width + ", h : " + rect.height);

    Imgproc.rectangle(matROI, rect.tl(), rect.br(), new Scalar(255, 0, 0, 255), 1);


    listarr[count][0] = rect.x;

    listarr[count][1] = rect.y;

    listarr[count][2] = rect.width;

    listarr[count][3] = rect.height;

    count++;

}

if(count == 7) {

    // 오름차순 정렬

    for(int i = 0; i < 7; i++) {

        for(int j = i; j < 7; j++) {

            if(listarr[i][0] > listarr[j][0]) {

                // 스왑

                int[] temp = new int[4];

                temp[0] = listarr[i][0];

                temp[1] = listarr[i][1];

                temp[2] = listarr[i][2];

                temp[3] = listarr[i][3];

                listarr[i][0] = listarr[j][0];

                listarr[i][1] = listarr[j][1];

                listarr[i][2] = listarr[j][2];

                listarr[i][3] = listarr[j][3];

                listarr[j][0] = temp[0];

                listarr[j][1] = temp[1];

                listarr[j][2] = temp[2];

                listarr[j][3] = temp[3];

            }

        }

    }


    for(int i = 0; i < 7; i++) {

        Bitmap tempBitmap = Bitmap.createBitmap(roi, listarr[i][0], listarr[i][1], listarr[i][2], listarr[i][3]);

        tempBitmap = Bitmap.createScaledBitmap(tempBitmap, 10, 15, true);

        if(i == 0)

            imageViewROI1.setImageBitmap(tempBitmap);

        else if(i == 1)

            imageViewROI2.setImageBitmap(tempBitmap);

        else if(i == 2)

            imageViewROI3.setImageBitmap(tempBitmap);

        else if(i == 3)

            imageViewROI4.setImageBitmap(tempBitmap);

        else if(i == 4)

            imageViewROI5.setImageBitmap(tempBitmap);

        else if(i == 5)

            imageViewROI6.setImageBitmap(tempBitmap);

        else if(i == 6)

            imageViewROI7.setImageBitmap(tempBitmap);


        int num_count = 0;

        double max = 0.0;

        int num = 0;

        if(i==2) { // 문자

            for(int n = 0; n < 10; n++) {

                for(int j = 0; j < 15; j++) {

                    for(int k = 0; k < 10; k++) {

                        if(ones[n][j][k] == tempBitmap.getPixel(k, j))

                            num_count++;

                    }

                }

                double result = (double)num_count/proones[n]*100;

                if(result > 100) result = 100;

                if(result > max) {

                    max = result;

                    num = n;

                }

                num_count=0;

            }

        }

        else { // 숫자

            for(int n = 0; n < 10; n++) {

                for(int j = 0; j < 15; j++) {

                    for(int k = 0; k < 10; k++) {

                        if(nums[n][j][k] == tempBitmap.getPixel(k, j))

                            num_count++;

                    }

                }

                double result = (double)num_count/pronums[n]*100;

                if(result > 100) result = 100;

                if(result > max) {

                    max = result;

                    num = n;

                }

                num_count=0;

            }

        }


        avg+=max;

        String floatnum = String.format("%.2f", max);


        if(i == 2) { // 한글 처리

            // 중략


            }


            int a = 0, b = 0, c = 0;

            for(int x=0; x<tempBitmap.getHeight(); x++) {

                for (int y = 0; y < tempBitmap.getWidth(); y++) {

                    if (tempBitmap.getPixel(y, x) == -1) {

                        Log.d("arr", "ones[5][" + a + "][" + b + "] = " + tempBitmap.getPixel(y, x) + ";");

                        c++;

                    }

                    b++;

                }

                b = 0;

                a++;

            }

            Log.d("count" , ""+c);

        }

        else { // 숫자 처리

            textViewResult.append(i + "번째 분석 결과 : " + num + " / 정확도 : " + floatnum +"%\n");

            result += Integer.toString(num);

        }

    }

}

else if(count == 8) {

    // 오름차순 정렬

    for(int i = 0; i < 8; i++) {

        for(int j = i; j < 8; j++) {

            if(listarr[i][0] > listarr[j][0]) {

                // 스왑

                int[] temp = new int[4];

                temp[0] = listarr[i][0];

                temp[1] = listarr[i][1];

                temp[2] = listarr[i][2];

                temp[3] = listarr[i][3];

                listarr[i][0] = listarr[j][0];

                listarr[i][1] = listarr[j][1];

                listarr[i][2] = listarr[j][2];

                listarr[i][3] = listarr[j][3];

                listarr[j][0] = temp[0];

                listarr[j][1] = temp[1];

                listarr[j][2] = temp[2];

                listarr[j][3] = temp[3];

            }

        }

    }

    // 한글인 경우

    if(abs(listarr[2][0]-listarr[3][0]) < 20) {

        if(listarr[2][1] > listarr[3][1]) {

            // 스왑

            int[] temp = new int[4];

            temp[0] = listarr[2][0];

            temp[1] = listarr[2][1];

            temp[2] = listarr[2][2];

            temp[3] = listarr[2][3];

            listarr[2][0] = listarr[3][0];

            listarr[2][1] = listarr[3][1];

            listarr[2][2] = listarr[3][2];

            listarr[2][3] = listarr[3][3];

            listarr[3][0] = temp[0];

            listarr[3][1] = temp[1];

            listarr[3][2] = temp[2];

            listarr[3][3] = temp[3];

        }

    }


    for(int i = 0; i < 8; i++) {

        Bitmap tempBitmap = Bitmap.createBitmap(roi, listarr[i][0], listarr[i][1], listarr[i][2], listarr[i][3]);

        tempBitmap = Bitmap.createScaledBitmap(tempBitmap, 10, 15, true);

        if(i == 0)

            imageViewROI1.setImageBitmap(tempBitmap);

        else if(i == 1)

            imageViewROI2.setImageBitmap(tempBitmap);

        else if(i == 2)

            imageViewROI3.setImageBitmap(tempBitmap);

        else if(i == 3)

            imageViewROI4.setImageBitmap(tempBitmap);

        else if(i == 4)

            imageViewROI5.setImageBitmap(tempBitmap);

        else if(i == 5)

            imageViewROI6.setImageBitmap(tempBitmap);

        else if(i == 6)

            imageViewROI7.setImageBitmap(tempBitmap);

        else if(i == 7)

            imageViewROI8.setImageBitmap(tempBitmap);


        int num_count = 0;

        double max = 0.0;

        int num = 0;

        // 자음

        if(i==2) {

            for(int n = 0; n < 10; n++) {

                for(int j = 0; j < 15; j++) {

                    for(int k = 0; k < 10; k++) {

                        if(cons[n][j][k] == tempBitmap.getPixel(k, j))

                            num_count++;

                    }

                }

                double result = (double)num_count/procons[n]*100;

                if(result > 100) result = 100;

                if(result > max) {

                    max = result;

                    num = n;

                }

                num_count=0;

            }

        }


        // 모음

        else if(i==3) {

            for(int n = 0; n < 4; n++) {

                for(int j = 0; j < 15; j++) {

                    for(int k = 0; k < 10; k++) {

                        if(vocs[n][j][k] == tempBitmap.getPixel(k, j))

                            num_count++;

                    }

                }

                double result = (double)num_count/provocs[n]*100;

                if(result > 100) result = 100;

                if(result > max) {

                    max = result;

                    num = n;

                }

                num_count=0;

            }

        }


        // 숫자

        else {

            for(int n = 0; n < 10; n++) {

                for(int j = 0; j < 15; j++) {

                    for(int k = 0; k < 10; k++) {

                        if(nums[n][j][k] == tempBitmap.getPixel(k, j))

                            num_count++;

                    }

                }

                double result = (double)num_count/pronums[n]*100;

                if(result > 100) result = 100;

                if(result > max) {

                    max = result;

                    num = n;

                }

                num_count=0;

            }

        }


        avg+=max;

        String floatnum = String.format("%.2f", max);


        // 자음

        if(i==2) {

            // 중략

        }


        // 모음

        else if(i==3) {

            // 중략

        }


        // 숫자

        else {

            textViewResult.append(i + "번째 분석 결과 : " + num + " / 정확도 : " + floatnum +"%\n");

            result += Integer.toString(num);

        }

    }

}

else {

    Toast.makeText(context, "숫자 생성 실패", Toast.LENGTH_SHORT).show();

}


Utils.matToBitmap(matROI, roi2);


imageViewROI = (ImageView)findViewById(R.id.image_result_ROI);

imageViewROI.setImageBitmap(roi2);


String resultnum = String.format("%.2f", avg/count);

textViewResult.append("\n\n분석 결과 : " + result + " / 정확도 : " + resultnum + "%");

}


중간에 문자 비교 코드는 너무 길어서 중략 처리했습니다.

다음은 미리 저장해둔 문자 템플릿입니다.


nums = new int[10][15][10];

pronums = new int[10];


cons = new int[10][15][10];

procons = new int[10];


vocs = new int[4][15][10];

provocs = new int[4];


ones = new int[10][15][10];

proones = new int[10];


// 픽셀 개수

pronums[0] = 50;

pronums[1] = 40;

pronums[2] = 40;

pronums[3] = 45;

pronums[4] = 50;

pronums[5] = 55;

pronums[6] = 50;

pronums[7] = 37;

pronums[8] = 50;

pronums[9] = 40;


// 0

nums[0][0][3] = -1; nums[0][0][4] = -1; nums[0][0][5] = -1; nums[0][0][6] = -1;

nums[0][1][2] = -1; nums[0][1][3] = -1; nums[0][1][6] = -1; nums[0][1][7] = -1;

nums[0][2][0] = -1; nums[0][2][1] = -1; nums[0][2][8] = -1; nums[0][2][9] = -1;

nums[0][3][0] = -1; nums[0][3][1] = -1; nums[0][3][8] = -1; nums[0][3][9] = -1;

nums[0][4][0] = -1; nums[0][4][1] = -1; nums[0][4][8] = -1; nums[0][4][9] = -1;

nums[0][5][0] = -1; nums[0][5][1] = -1; nums[0][5][8] = -1; nums[0][5][9] = -1;

nums[0][6][0] = -1; nums[0][6][1] = -1; nums[0][6][8] = -1; nums[0][6][9] = -1;

nums[0][7][0] = -1; nums[0][7][1] = -1; nums[0][7][8] = -1; nums[0][7][9] = -1;

nums[0][8][0] = -1; nums[0][8][1] = -1; nums[0][8][8] = -1; nums[0][8][9] = -1;

nums[0][9][0] = -1; nums[0][9][1] = -1; nums[0][9][8] = -1; nums[0][9][9] = -1;

nums[0][10][0] = -1; nums[0][10][1] = -1; nums[0][10][8] = -1; nums[0][10][9] = -1;

nums[0][11][0] = -1; nums[0][11][1] = -1; nums[0][11][8] = -1; nums[0][11][9] = -1;

nums[0][12][0] = -1; nums[0][12][1] = -1; nums[0][12][8] = -1; nums[0][12][9] = -1;

nums[0][13][2] = -1; nums[0][13][3] = -1; nums[0][13][6] = -1; nums[0][13][7] = -1;

nums[0][14][3] = -1; nums[0][14][4] = -1; nums[0][14][5] = -1; nums[0][14][6] = -1;


// 1

nums[1][0][7] = -1; nums[1][0][8] = -1;

nums[1][1][6] = -1; nums[1][1][7] = -1; nums[1][1][8] = -1;

nums[1][2][1] = -1; nums[1][2][2] = -1; nums[1][2][3] = -1; nums[1][2][4] = -1; nums[1][2][5] = -1; nums[1][2][6] = -1; nums[1][2][7] = -1; nums[1][2][8] = -1;

nums[1][3][0] = -1; nums[1][3][1] = -1; nums[1][3][2] = -1; nums[1][3][3] = -1; nums[1][3][4] = -1; nums[1][3][5] = -1; nums[1][3][6] = -1; nums[1][3][7] = -1; nums[1][3][8] = -1;

nums[1][4][6] = -1; nums[1][4][7] = -1; nums[1][4][8] = -1;

nums[1][5][7] = -1; nums[1][5][8] = -1;

nums[1][6][7] = -1; nums[1][6][8] = -1;

nums[1][7][7] = -1; nums[1][7][8] = -1;

nums[1][8][7] = -1; nums[1][8][8] = -1;

nums[1][9][7] = -1; nums[1][9][8] = -1;

nums[1][10][7] = -1; nums[1][10][8] = -1;

nums[1][11][7] = -1; nums[1][11][8] = -1;

nums[1][12][7] = -1; nums[1][12][8] = -1;

nums[1][13][7] = -1; nums[1][13][8] = -1;

nums[1][14][7] = -1; nums[1][14][8] = -1;


// 2

nums[2][0][3] = -1; nums[2][0][4] = -1;

nums[2][1][1] = -1; nums[2][1][2] = -1; nums[2][1][5] = -1; nums[2][1][6] = -1;

nums[2][2][0] = -1; nums[2][2][1] = -1; nums[2][2][7] = -1;

nums[2][3][0] = -1; nums[2][3][1] = -1; nums[2][3][7] = -1; nums[2][3][8] = -1;

nums[2][4][7] = -1; nums[2][4][8] = -1;

nums[2][5][7] = -1; nums[2][5][8] = -1;

nums[2][6][6] = -1; nums[2][6][7] = -1;

nums[2][7][6] = -1; nums[2][7][7] = -1;

nums[2][8][5] = -1; nums[2][8][6] = -1;

nums[2][9][4] = -1; nums[2][9][5] = -1;

nums[2][10][3] = -1; nums[2][10][4] = -1;

nums[2][11][3] = -1; nums[2][11][4] = -1;

nums[2][12][2] = -1; nums[2][12][3] = -1;

nums[2][13][1] = -1; nums[2][13][2] = -1; nums[2][13][3] = -1;

nums[2][14][1] = -1; nums[2][14][2] = -1; nums[2][14][3] = -1; nums[2][14][4] = -1; nums[2][14][5] = -1; nums[2][14][6] = -1; nums[2][14][7] = -1; nums[2][14][8] = -1; nums[2][14][9] = -1;


// 3

nums[3][0][2] = -1; nums[3][0][3] = -1; nums[3][0][4] = -1; nums[3][0][5] = -1; nums[3][0][6] = -1; nums[3][0][7] = -1; nums[3][0][8] = -1; nums[3][0][9] = -1;

nums[3][1][3] = -1; nums[3][1][4] = -1; nums[3][1][5] = -1; nums[3][1][6] = -1; nums[3][1][7] = -1; nums[3][1][8] = -1; nums[3][1][9] = -1;

nums[3][2][7] = -1; nums[3][2][8] = -1;

nums[3][3][6] = -1; nums[3][3][7] = -1;

nums[3][4][5] = -1; nums[3][4][6] = -1;

nums[3][5][4] = -1; nums[3][5][5] = -1; nums[3][5][6] = -1;

nums[3][6][5] = -1; nums[3][6][6] = -1; nums[3][6][7] = -1;

nums[3][7][7] = -1;

nums[3][8][7] = -1; nums[3][8][8] = -1;

nums[3][9][7] = -1; nums[3][9][8] = -1;

nums[3][10][0] = -1; nums[3][10][1] = -1; nums[3][10][7] = -1;

nums[3][11][0] = -1; nums[3][11][1] = -1; nums[3][11][7] = -1;

nums[3][12][0] = -1; nums[3][12][1] = -1; nums[3][12][6] = -1;

nums[3][13][1] = -1; nums[3][13][2] = -1; nums[3][13][3] = -1; nums[3][13][4] = -1; nums[3][13][5] = -1; nums[3][13][6] = -1;

nums[3][14][2] = -1; nums[3][14][3] = -1; nums[3][14][4] = -1;


// 4

nums[4][0][6] = -1; nums[4][0][7] = -1;

nums[4][1][6] = -1; nums[4][1][7] = -1;

nums[4][2][5] = -1; nums[4][2][6] = -1; nums[4][2][7] = -1;

nums[4][3][4] = -1; nums[4][3][5] = -1; nums[4][3][6] = -1; nums[4][3][7] = -1;

nums[4][4][3] = -1; nums[4][4][4] = -1; nums[4][4][6] = -1; nums[4][4][7] = -1;

nums[4][5][3] = -1; nums[4][5][6] = -1; nums[4][5][7] = -1;

nums[4][6][2] = -1; nums[4][6][3] = -1; nums[4][6][6] = -1; nums[4][6][7] = -1;

nums[4][7][2] = -1; nums[4][7][6] = -1; nums[4][7][7] = -1;

nums[4][8][1] = -1; nums[4][8][2] = -1; nums[4][8][6] = -1; nums[4][8][7] = -1;

nums[4][9][1] = -1; nums[4][9][6] = -1; nums[4][9][7] = -1;

nums[4][10][0] = -1; nums[4][10][1] = -1; nums[4][10][2] = -1; nums[4][10][3] = -1; nums[4][10][4] = -1; nums[4][10][5] = -1; nums[4][10][6] = -1; nums[4][10][7] = -1; nums[4][10][8] = -1; nums[4][10][9] = -1;

nums[4][11][0] = -1; nums[4][11][1] = -1; nums[4][11][2] = -1; nums[4][11][3] = -1; nums[4][11][4] = -1; nums[4][11][5] = -1; nums[4][11][6] = -1; nums[4][11][7] = -1; nums[4][11][8] = -1; nums[4][11][9] = -1;

nums[4][12][6] = -1; nums[4][12][7] = -1;

nums[4][13][6] = -1; nums[4][13][7] = -1;

nums[4][14][6] = -1; nums[4][14][7] = -1;


// 5

nums[5][0][2] = -1; nums[5][0][3] = -1; nums[5][0][4] = -1; nums[5][0][5] = -1; nums[5][0][6] = -1; nums[5][0][7] = -1; nums[5][0][8] = -1;

nums[5][1][1] = -1;  nums[5][1][2] = -1; nums[5][1][3] = -1; nums[5][1][4] = -1; nums[5][1][5] = -1; nums[5][1][6] = -1; nums[5][1][7] = -1;

nums[5][2][1] = -1; nums[5][2][2] = -1;

nums[5][3][1] = -1; nums[5][3][2] = -1;

nums[5][4][1] = -1; nums[5][4][2] = -1;

nums[5][5][1] = -1; nums[5][5][2] = -1;

nums[5][6][1] = -1; nums[5][6][2] = -1; nums[5][6][3] = -1; nums[5][6][4] = -1; nums[5][6][5] = -1; nums[5][6][6] = -1; nums[5][6][7] = -1;

nums[5][7][1] = -1; nums[5][7][2] = -1; nums[5][7][3] = -1; nums[5][7][4] = -1; nums[5][7][5] = -1; nums[5][7][6] = -1; nums[5][7][7] = -1; nums[5][7][8] = -1;

nums[5][8][8] = -1; nums[5][8][9] = -1;

nums[5][9][8] = -1; nums[5][9][9] = -1;

nums[5][10][8] = -1; nums[5][10][9] = -1;

nums[5][11][0] = -1; nums[5][11][1] = -1; nums[5][11][8] = -1; nums[5][11][9] = -1;

nums[5][12][0] = -1; nums[5][12][1] = -1; nums[5][12][7] = -1; nums[5][12][8] = -1;

nums[5][13][1] = -1; nums[5][13][2] = -1; nums[5][13][3] = -1; nums[5][13][4] = -1; nums[5][13][5] = -1; nums[5][13][6] = -1; nums[5][13][7] = -1;

nums[5][14][2] = -1; nums[5][14][3] = -1; nums[5][14][4] = -1; nums[5][14][5] = -1; nums[5][14][6] = -1;


// 6

nums[6][0][6] = -1; nums[6][0][7] = -1;

nums[6][1][6] = -1;

nums[6][2][5] = -1; nums[6][2][6] = -1;

nums[6][3][4] = -1; nums[6][3][5] = -1;

nums[6][4][4] = -1;

nums[6][5][3] = -1; nums[6][5][4] = -1;

nums[6][6][2] = -1; nums[6][6][3] = -1; nums[6][6][4] = -1; nums[6][6][5] = -1; nums[6][6][6] = -1;

nums[6][7][2] = -1; nums[6][7][3] = -1; nums[6][7][4] = -1; nums[6][7][5] = -1; nums[6][7][6] = -1; nums[6][7][7] = -1; nums[6][7][8] = -1;

nums[6][8][1] = -1; nums[6][8][2] = -1; nums[6][8][7] = -1; nums[6][8][8] = -1;

nums[6][9][0] = -1; nums[6][9][1] = -1; nums[6][9][8] = -1; nums[6][9][9] = -1;

nums[6][10][0] = -1; nums[6][10][1] = -1; nums[6][10][8] = -1; nums[6][10][9] = -1;

nums[6][11][0] = -1; nums[6][11][1] = -1; nums[6][11][8] = -1; nums[6][11][9] = -1;

nums[6][12][0] = -1; nums[6][12][1] = -1; nums[6][12][2] = -1; nums[6][12][7] = -1; nums[6][12][8] = -1;

nums[6][13][1] = -1; nums[6][13][2] = -1; nums[6][13][3] = -1; nums[6][13][4] = -1; nums[6][13][5] = -1; nums[6][13][6] = -1; nums[6][13][7] = -1;

nums[6][14][3] = -1; nums[6][14][4] = -1; nums[6][14][5] = -1;


// 7

nums[7][0][0] = -1; nums[7][0][1] = -1; nums[7][0][2] = -1; nums[7][0][3] = -1; nums[7][0][4] = -1; nums[7][0][5] = -1; nums[7][0][6] = -1; nums[7][0][7] = -1; nums[7][0][8] = -1; nums[7][0][9] = -1;

nums[7][1][0] = -1; nums[7][1][1] = -1; nums[7][1][2] = -1; nums[7][1][6] = -1; nums[7][1][7] = -1; nums[7][1][8] = -1; nums[7][1][9] = -1;

nums[7][2][0] = -1; nums[7][2][1] = -1; nums[7][2][7] = -1; nums[7][2][8] = -1;

nums[7][3][7] = -1;

nums[7][4][7] = -1;

nums[7][5][6] = -1;

nums[7][6][5] = -1; nums[7][6][6] = -1;

nums[7][7][5] = -1;

nums[7][8][5] = -1;

nums[7][9][4] = -1; nums[7][9][5] = -1;

nums[7][10][4] = -1;

nums[7][11][3] = -1; nums[7][11][4] = -1;

nums[7][12][3] = -1;

nums[7][13][2] = -1; nums[7][13][3] = -1;

nums[7][14][2] = -1;


// 8

nums[8][0][4] = -1; nums[8][0][5] = -1;

nums[8][1][2] = -1; nums[8][1][3] = -1; nums[8][1][4] = -1; nums[8][1][5] = -1; nums[8][1][6] = -1; nums[8][1][7] = -1;

nums[8][2][1] = -1; nums[8][2][2] = -1; nums[8][2][7] = -1; nums[8][2][8] = -1;

nums[8][3][1] = -1; nums[8][3][8] = -1;

nums[8][4][0] = -1; nums[8][4][1] = -1; nums[8][4][8] = -1;

nums[8][5][1] = -1; nums[8][5][8] = -1;

nums[8][6][2] = -1; nums[8][6][3] = -1; nums[8][6][4] = -1; nums[8][6][5] = -1; nums[8][6][6] = -1; nums[8][6][7] = -1;

nums[8][7][2] = -1; nums[8][7][3] = -1; nums[8][7][4] = -1; nums[8][7][5] = -1; nums[8][7][6] = -1; nums[8][7][7] = -1;

nums[8][8][1] = -1; nums[8][8][2] = -1; nums[8][8][7] = -1; nums[8][8][8] = -1;

nums[8][9][0] = -1; nums[8][9][1] = -1; nums[8][9][8] = -1; nums[8][9][9] = -1;

nums[8][10][0] = -1; nums[8][10][1] = -1; nums[8][10][8] = -1; nums[8][10][9] = -1;

nums[8][11][0] = -1; nums[8][11][1] = -1; nums[8][11][8] = -1; nums[8][11][9] = -1;

nums[8][12][1] = -1; nums[8][12][2] = -1; nums[8][12][7] = -1; nums[8][12][8] = -1;

nums[8][13][1] = -1; nums[8][13][2] = -1; nums[8][13][3] = -1; nums[8][13][6] = -1; nums[8][13][7] = -1;

nums[8][14][3] = -1; nums[8][14][4] = -1; nums[8][14][5] = -1; nums[8][14][6] = -1;


// 9

nums[9][0][3] = -1; nums[9][0][4] = -1; nums[9][0][5] = -1;

nums[9][1][1] = -1; nums[9][1][2] = -1; nums[9][1][3] = -1; nums[9][1][4] = -1; nums[9][1][5] = -1; nums[9][1][6] = -1; nums[9][1][7] = -1;

nums[9][2][0] = -1; nums[9][2][1] = -1; nums[9][2][7] = -1; nums[9][2][8] = -1;

nums[9][3][0] = -1; nums[9][3][1] = -1; nums[9][3][7] = -1; nums[9][3][8] = -1;

nums[9][4][0] = -1; nums[9][4][1] = -1; nums[9][4][7] = -1; nums[9][4][8] = -1;

nums[9][5][1] = -1; nums[9][5][2] = -1; nums[9][5][8] = -1; nums[9][5][9] = -1;

nums[9][6][1] = -1; nums[9][6][2] = -1; nums[9][6][3] = -1; nums[9][6][8] = -1; nums[9][6][9] = -1;

nums[9][7][3] = -1; nums[9][7][4] = -1; nums[9][7][5] = -1; nums[9][7][6] = -1; nums[9][7][7] = -1; nums[9][7][8] = -1;

nums[9][8][5] = -1; nums[9][8][6] = -1; nums[9][8][7] = -1;

nums[9][9][6] = -1; nums[9][9][7] = -1;

nums[9][10][6] = -1;

nums[9][11][6] = -1; nums[9][11][7] = -1;

nums[9][12][5] = -1;

nums[9][13][4] = -1; nums[9][13][5] = -1;


// 자음 및 모음 중략...


이런식으로 3차원 배열에 이미지 템플릿(자음, 모음, 숫자)에 해당하는 픽셀값을 미리 설정해 두고 추출된 이미지와 1:1 비교해서 일치하는 값만큼 숫자를 증가시켜 그 숫자의 비율을 정확도로 칭하고 그 정확도가 가장 높은 숫자를 인식된 숫자라고 판별합니다.


다음은 실행 결과입니다.





프로그램 코드가 너무 긴 관계로 일부 코드를 생략했습니다. 무조건 복사 붙여넣기 한다고 실행 되지 않을것입니다. 프로그램 원리나 소스코드에 대해 궁금하신분은 댓글 또는 방명록에 남겨주시면 상세하게 답변해드리겠습니다. 

공감♡ 버튼을 눌러주시면 더욱 유용하고 좋은 포스팅으로 찾아뵙겠습니다.









안드로이드 프로그래밍 중 텍스트를 출력시키고 싶다면 TextView를 사용해보세요.

TextView는 View클래스를 상속받아 만들어진 클래스입니다.


※ 참고

만약 유저가 수정할 수 있는 텍스트란을 원하신다면 EditText를 사용해보세요.


XML Layout에서 기본적인 사용법입니다.


<TextView

android:layout_height="wrap_content"

android:layout_width="wrap_content"

android:id="@+id/text_view_id"

android:text="Hello World!" />


layout_height와 width는 텍스트뷰의 크기를 지정해 줍니다. wrap_content는 텍스트 뷰 내용에 맞게 맞춰진다는 뜻입니다.

id는 현재 텍스트뷰에 이름을 붙여준다고 생각합시다.(다른 누군가가 부를때 이름이 없으면 부를수가 없겠죠?)

text는 텍스트뷰에 표시 될 텍스트(문구)를 적습니다.


실행한다면 화면 좌측 상단에 Hello World!라고 작게 찍히는 모습을 확인 할 수 있습니다.


MainActivity로 가서 onCreate 함수 안에 다음과 같이 작성해봅시다.


TextView textView = (TextView) findViewById(R.id.text_view_id);

textView.setText("World Hello!");


TextView의 객체를 선언해주고 값을 findViewById 함수를 통해 XML에서 설정해주는 id(텍스트뷰의 이름)로 정의합니다.

그리고 setText함수를 통해 출력 될 텍스트를 설정하거나 바꿀 수 있습니다.


다음은 XML TextView attributes(속성)입니다.


autoText : 스펠링 검사를 자동으로 수행합니다.

cursorVisible : 텍스트 뷰의 커서를 보일지 설정합니다.

digits : 텍스트뷰에 숫자만 입력하도록 설정합니다.

editable : 텍스트뷰의 텍스트를 수정할 수 있는지 설정합니다.

ems : 텍스트뷰의 기본 길이를 설정합니다.

gravity : 텍스뷰의 텍스트가 나타날 위치를 지정합니다.

height : 텍스트뷰의 높이를 설정합니다.

hint : 텍스트 뷰가 비어있을 때 나타날 문자를 설정합니다.

inputType : 텍스트 뷰에 입력 될 텍스트의 입력 타입을 설정합니다.

- numeric : 숫자만 입력합니다.

- password : 입력된 숫자를 *로 표기합니다.

- phoneNumber : 휴대폰 번호 입력.

lines : 텍스트 뷰의 줄(라인)을 설정합니다.

maxEms : ems의 최대 길이를 설정합니다.

maxHeight : 텍스트 뷰의 최대 높이를 설정합니다.

maxLength : 텍스트 뷰에 입력 될 텍스트의 최대 길이를 설정합니다.

maxLines : 텍스트 뷰의 최대 줄 수를 설정합니다.

maxWidth : 텍스트 뷰의 최대 너비를 설정합니다.

※ min은 모두 최소값을 설정하는 속성입니다.

textColor : 텍스트 뷰에 입력 될 텍스트의 색깔을 설정합니다.

textSize : 텍스트 뷰에 입력 될 텍스트의 크기를 설정합니다.


다음은 Acitivity(Java class)에서 사용되는 TextView Class Public Methods(함수)입니다. 

getHint() : 텍스트 뷰에 설정된 hint 내용을 반환합니다.

setHint("hint") : 텍스트 뷰에 "hint"를 설정합니다.

getInputType() : 텍스트 뷰에 설정된 InputType을 반환합니다.

setInputType(int) : 텍스트 뷰의 입력 타입을 설정합니다. (type은 EditorInfo.inputType에 정의되어 있습니다.)

getLineCount() : 현재 입력된 텍스트 뷰의 라인 수를 반환합니다.

getMaxEms() : 텍스트 뷰의 최대 ems를 반환합니다.

setMaxEms(int) : 텍스트 뷰의 최대 ems를 설정합니다.

getMaxHeight() : 텍스트 뷰의 최대 높이를 반환합니다.

setMaxHeight(int) : 텍스트 뷰의 최대 높이를 설정합니다.

getMaxLines() : 텍스트 뷰의 최대 라인 수를 반환합니다.

setMaxLines(int) : 텍스트 뷰의 최대 라인 수를 설정합니다.

getMaxWidth() : 텍스트 뷰의 최대 너비를 반환합니다.

setMaxWidth(int) : 텍스트 뷰의 최대 너비를 설정합니다.

getText() : 텍스트 뷰에 입력된 텍스트를 반환합니다.

setText("text") : 텍스트 뷰에 "text"를 출력하도록 설정합니다.

append("text") : 텍스트 뷰에 "text"를 덧붙여 씁니다. (기존에 존재하던 텍스트 바로 뒤에 붙여서 입력됩니다.)

getTextSize() : 텍스트 뷰의 텍스트 크기를 반환합니다.

setTextSize(int) : 텍스트 뷰의 텍스트 크기를 설정합니다.


TextView에 대해 더 궁금한 사항이 있으시면 여기를 확인하세요.


2018/08/20 - [Language/Android] - 안드로이드 EditText 사용법 정리

2018/08/20 - [Language/Android] - [Android] 안드로이드 키보드 입력 창 올리기/ 내리기



정보가 유익하셨다면 아래 공감버튼 눌러주시면 감사하겠습니다.

질문사항은 댓글로 달아주시면 성의껏 답변해드리겠습니다.




어플리케이션 제작 중에 SurfaceView에 CameraPreview를 이용하여 카메라를 제어하는 프로그래밍 중

실시간으로 SurfaceView를 캡쳐해야하는 코드가 필요해 처리하는 과정 등을 정리하여 포스팅합니다.

SurfaceView는 일반적인 함수로 캡쳐하면 검은 화면만 출력되기 때문에...

여러가지 찾아본 후 적용되는 코드로 설명하곘습니다.

CameraPreview로 SurfaceView에 카메라 화면을 출력할 때, 실시간으로 사진파일이 필요할 때 사용합니다.


[CameraPreview.java]


public class CameraPreview extends ViewGroup implements SurfaceHolder.Callback {

... 중략 ...

@Override

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

... 중략 ...

mCamera.setPreviewDisplay(mHolder);

mCamera.setPreviewCallback(new Camera.PreviewCallback(){

@Override

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

// 현재 SurfaceView를 JPEG Format으로 변경

Camera.Parameters parameters = camera.getParameters();

int w = parameters.getPreviewSize().width;

int h = parameters.getPreviewSize().height;

int format = parameters.getPreviewFormat();

YuvImage image = new YuvImage(data, format, w, h, null);

ByteArrayOutputStream out = new ByteArrayOutputStream();

Rect area = new Rect(0, 0, w, h);

image.compressToJpeg(area, 100, out);

Bitmap bm = BitmapFactory.decodeByteArray(out.toByteArray(), 0, out.size());

byte[] currentData = out.toByteArray();         

                   

... 중략 ...

}

}

}


기본 코드입니다.

CameraView class에서 SurfaceHolder.Callback을 implements하고

surfaceChanged() 에서 mCamera.setPreviewCallback을 호출합니다.

onPreviewFrame 메소드는 CameraPreview에서 실시간으로 호출되는 함수입니다.

위 코드는 현재 SurfaceView 화면을 캡쳐해서 JPEG Format으로 만들어주는 코드입니다.


여기서 JPEG 파일을 PNG 파일로 바꾸는 방법입니다.


... 위 코드로 부터 계속 ...

int orientation = calculatePreviewOrientation(mCameraInfo, mDisplayOrientation);

Matrix matrix = new Matrix();

matrix.postRotate(orientation);


BitmapFactory.Options options = new BitmapFactory.Options();

options.inSampleSize = 1 // 이미지 사이즈 리스케일(2의 지수 승으로 해야 처리속도 빠름; 1,2,4,8,16...; 필요시)

options.inPreferredConfig = Bitmap.Config.ARGB_8888;

// RGB_565 : 16bit Bitmap, ARGB_8888 : 32bit Bitmap, 4444는 저품질이라 추천안함


Bitmap bitmap = BitmapFactory.decodeByteArray(currentData, 0, currentData.length, options);

bitmap =  Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true);

// bitmap src, start x, start y, width, height, 회전, Flag


 // Bitmap 이미지를 surfaceView 좌표로 변환

bitmap = GetBinaryBitmap(bitmap); // bitmap 이진화 처리


//bitmap을 byte array로 변환

 ByteArrayOutputStream stream = new ByteArrayOutputStream();

bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); // 100 수치 변경해서 품질(Quality) 변경

currentData = stream.toByteArray();


// 이후 해당 Byte Array로 파일 저장 및 처리...

... 중략 ....

}

}

}


조금 복잡할 수도 있는 코드입니다.

한줄씩 주석 포함해서 해석하면서 코드 작성하시면 쉽게 처리 가능할 것 같습니다.


PNG(Bitmap으로) 파일 저장하는 방법은 Google에 많이 있고 또 예제코드가 있기 때문에 생략하겠습니다.


이상 CameraPreview에서 카메라 화면을 SurfaceView에 출력하고 그 화면을 캡쳐하는 방법에 대해 알아보았습니다.

질문 사항은 댓글로 남겨주세요!!


* 참고 글 : 

SurfaceView CameraPreview 연동

Bitmap 이진화 처리



Android 내부 저장소에 텍스트 파일(.txt) 읽기 및 쓰기 방법입니다.

/////////////////////// 파일 쓰기 ///////////////////////
String str = input_text.getText().toString();
// 파일 생성
File saveFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/camdata"); // 저장 경로
// 폴더 생성
if(!saveFile.exists()){ // 폴더 없을 경우
saveFile.mkdir(); // 폴더 생성
}
try {
long now = System.currentTimeMillis(); // 현재시간 받아오기
Date date = new Date(now); // Date 객체 생성
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowTime = sdf.format(date);

BufferedWriter buf = new BufferedWriter(new FileWriter(saveFile+"/CarnumData.txt", true));
buf.append(nowTime + " "); // 날짜 쓰기
buf.append(str); // 파일 쓰기
buf.newLine(); // 개행
buf.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
1) File 객체를 생성합니다.

2) Environment.getExternalStorageDirectory().getAbsolutePath() 까지 현재 경로이며 사용할 폴더를 하나 생성해줍니다.

3) 폴더가 없을 경우 mkdir()을 통해 생성해줍니다.

4) BufferedWriter를 통해 파일을 엽니다. 두번째 parameter를 true로 함으로써 파일을 이어서 쓰도록 합니다.

5) 사용 후 close()로 객체를 꼭 닫아줍니다.

/////////////////////// 파일 읽기 ///////////////////////
// 파일 생성
String line = null; // 한줄씩 읽기
File saveFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/camdata"); // 저장 경로
// 폴더 생성
if(!saveFile.exists()){ // 폴더 없을 경우
saveFile.mkdir(); // 폴더 생성
}
try {
BufferedReader buf = new BufferedReader(new FileReader(saveFile+"/CarnumData.txt"));
while((line=buf.readLine())!=null){
tv.append(line);
tv.append("\n");
}
buf.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

1) File 객체를 생성합니다. 2) Environment.getExternalStorageDirectory().getAbsolutePath() 까지 현재 경로이며 사용할 폴더를 하나 생성해줍니다. 3) 폴더가 없을 경우 mkdir()을 통해 생성해줍니다. 4) BufferedReader 를 통해 파일을 엽니다.


5) while문을 이용하여 파일을 한줄씩 읽어옵니다. 5) 사용 후 close()로 객체를 꼭 닫아줍니다.







정보가 유익하셨다면 아래 공감버튼 눌러주시면 감사하겠습니다.

질문사항은 댓글로 달아주시면 성의껏 답변해드리겠습니다.







to Top