<

/>

imageocr

최소 비용과 기본 기능으로 신분증 OCR 알고리즘 구현하기

CLOVA OCR 기본 기능으로 원하는 데이터를 추출하는 과정과 후기

2024년 06월 30일

개요

신분증 OCR

신분증 OCR

여러 금융 어플에서 OCR 기능을 사용하는 것에 흥미가 생겨, 직접 구현해 보면서 겪었던 문제점과 해결 방법을 공유하려고 합니다.

이 글은 CLOVA OCR 기본 사용법이나 프론트엔드 코드에 관한 글이 아닌, 필요한 데이터의 특징을 파악하는법에 관한 글입니다.

CLOVA OCR

악필이어도 글자를 정확히 인식하는 CLOVA OCR

악필이어도 글자를 정확히 인식하는 CLOVA OCR

Naver Cloud CLOVA OCR 공식문서

다양한 OCR 서비스중에서 CLOVA OCR을 선택한 이유는, 한글 문자 인식에 있어서 매우 정확했기 때문입니다.
구글의 클라우드 비전또한 괜찮은 선택지이지만, 한글 필기체 인식이 아쉬웠습니다.

CLOVA OCR이 제공하는 3가지의 OCR은 각자 기능과 요금이 다릅니다.

General OCRTemplate OCRDocument OCR
과금 기준호출수 당호출수 당,월 요금제호출수 당,월 요금제
과금 구간100회 초과10,000건 초과3,000건 초과
호출수 당3원60원80원
월 기본료(standard)x35,000원180,000원
기능기본 기능사진에서 필드 영역 지정 가능영수증,신용카드,신분증 등 특화 모델 제공

제가 구현하려는 OCR은 영수증과 신용카드이기 때문에 Document OCR이 좋은 선택지가 될것이지만,
월 기본료를 내지 않고 적은 요금으로 구현하기 위해서 General OCR을 사용하여 구현할것입니다.

다음은 기본 기능으로 신분증과 신용카드 데이터를 가져오는 방법입니다.

추출 로직 구상하기

신분증(이름, 주민등록번호)

실제로 신분증 OCR을 사용할때는 사진을 포함 사진속의 많은 데이터들이 필요하겠지만,
저의 OCR에서는 신분증 고유의 값인 이름과 주민번호만 추출할것입니다.

CLOVA OCR 신분증 인식 결과

CLOVA OCR 신분증 인식 결과

신분증 사진을 General OCR 돌린 결과물입니다.
return 배열의 순서는 사진기준 위 부터 아래로, y축이 같다면 좌에서 우로 순서대로 채워집니다.

주민등록증의 형태는 변하지 않기에, 예제 사진과 다른 주민등록증 사진으로 테스트 하더라도,
배열에서 1번과 3번값을 가져온다면 이름과 주민등록번호를 추출할 수 있을것입니다.

재외국민 주민등록증

재외국민 주민등록증

하지만 재외국민 주민등록증의 경우도 있기 때문에 배열의 index만으로 값을 추출하는건 오류가 생길것입니다.
필요한 데이터의 특징을 파악하여 추출하는 방식으로 방법을 바꿔야 합니다.

이름 field 특성 파악

이름 field 특성 파악

이름 field 다음인 한자 field 에는 항상 "()" 괄호가 있기 때문에
배열에서 "()" 의 index의 전 field가 이름 field인걸 알수있습니다.

하지만 "(재외국민)" 과 같이 괄호가 포함된 field도 존재합니다.
그러나 "(재외국민)" 은 고정된 값이기 때문에 제외 목록을 만들어서 검증을 하면 이름 field를 찾을 수 있습니다.

주민등록번호 field 특성 파악

주민등록번호 field 특성 파악

주민등록번호는 "-" 문자가 존재하기 때문에 "-"가 포함된 field를 찾는다면 가능할것같지만,
"412-3번지" 와 같이 주소에도 "-" 문자가 포함된 경우가 있습니다.

그렇다면, 주민등록번호 field 만의 또다른 특징을 찾아야합니다. 그 특징은 다음과 같습니다.

  1. "-" 문자를 제외하면 숫자로만 이루어져있다.
  2. "-" 문자 기준 앞부분은 6자리, 뒷부분 7자리이다.

이 특징들을 이용한다면 주민등록번호 field를 정확히 찾아낼수있습니다.
이제 이 특징들을 이용해 코드를 작성하겠습니다.

신분증 예제코드
const extractNameAndId = (ocrResults) => {
  let name = "";
  let id = "";
 
  // 제외 목록
  const excludedKeywords = ["주민등록증", "(재외국민)"];
 
  // 이름 추출
  for (let i = 0; i < ocrResults.length; i++) {
    const currentText = ocrResults[i].inferText;
    // "(" 를 포함하는지 확인 && 제외 목록에 포함되지 않는지 확인
    if (currentText.includes("(") && !excludedKeywords.includes(currentText)) {
      name = ocrResults[i - 1].inferText;
      break;
    }
  }
 
  // 주민등록번호 추출
  for (let i = 0; i < ocrResults.length; i++) {
    const currentText = ocrResults[i].inferText;
    // "-" 를 포함하는지 확인
    if (!currentText.includes("-")) {
      continue;
    }
 
    const [front, behind] = currentText.split("-");
 
    // 자릿수 및 isNaN 체크
    if (
      front.length === 6 &&
      behind.length === 7 &&
      !isNaN(Number(front)) &&
      !isNaN(Number(behind))
    ) {
      id = currentText;
      break;
    }
  }
 
  if (name && id) {
    return { name, id };
  } else {
    return null;
  }
};
 
// 예제 OCR 결과
const ocrResults = [
  { inferText: "주민등록증" },
  { inferText: "둘리" },
  { inferText: "(杜里)" },
  { inferText: "830422-1185600" },
  { inferText: "부천시 원미구 상1동" },
  { inferText: "412-3번지" },
  { inferText: "둘리의 거리" },
  { inferText: "2003.4.22" },
  { inferText: "경기도 부천시장" },
];
 
const nameAndId = extractNameAndId(ocrResults);
 
if (nameAndId) {
  console.log(`이름: ${nameAndId.name} / 주민등록번호: ${nameAndId.id}`);
  // 이름 : 둘리 / 주민등록번호 : 830422-1185600
} else {
  console.log("이름과 주민등록번호를 찾을 수 없습니다.");
}

원하는 값인 이름과 주민등록번호를 성공적으로 얻을 수 있었습니다.
다음은 신용카드 데이터를 추출할것입니다.

신용카드(카드번호, 유효기간)

신용카드 OCR

신용카드 OCR

실제로 신용카드 OCR을 사용할때는 사진을 포함 사진속의 많은 데이터들이 필요하겠지만,
저의 OCR에서는 신용카드 고유의 값인 카드번호와 유효기간만 추출할것입니다.

카드마다 카드번호와 유효기간의 위치가 다르기때문에 index기준으로 찾는것은 어렵습니다.
그렇기에 카드번호와 유효기간의 특징을 파악하여 추출해야합니다.

유효기간 특징

유효기간 특징

유효기간은 모든 field중 유일하게 "/" 를 포함한것을 확인할 수 있습니다.
그렇다면 모든 field중 "/" 문자를 포함한 field를 찾는다면 유효기간을 찾을 수 있습니다.
더욱 확실하게 추출하려면 다음과 같은 특징을 추가하여 찾는것도 좋습니다.

  1. "/" 문자를 제외하면 숫자로만 이루어져있다.
  2. "/" 문자 기준 앞부분은 2자리, 뒷부분 2자리이다.
카드번호 2가지 특징

카드번호 2가지 특징

사진과 같이 카드번호는 2가지 특징이있습니다.

  1. 4개의 필드가 y축 위치가 동일하여야한다.
  2. 그 4개의 field가 width가 같아야 한다.

CLOVA OCR은 필드마다 x,y축 값을 제공하니 동일한 y축을 4개를 찾는것은 간단합니다.
다만 width 값을 따로 제공하지 않으니 직접 계산해야합니다.

x축 y축

x축 y축

x축 시작점과 끝점의 값을 계산하면 field의 width를 알수있습니다.
사진속 카드번호 4000 field의 width는 147 - 72 = 75로 75인것을 알수있습니다.
이제 다른 필드들도 75의 width를 가졌는지 검증하면 카드번호 4개의 field를 알수있습니다.

이제 이 특징들을 이용하여 코드 로직을 작성하겠습니다.

카드번호와 유효기간 추출

카드번호와 유효기간 추출

코드 흐름은 간단하지만, 내용이 너무 길기에 알고리즘 순서도로 올렸습니다. 코드는 github 에서 확인 가능합니다.

마치며

OCR 결과물

OCR 결과물

PLAYGROUND 링크를 통해 결과물을 확인하실 수 있습니다.

많은 사용자들을 유지하는 서비스라면, 비용이 비쌀지라도 머신러닝을 통해 학습시킨 Template, Document OCR을 사용하는 것이 더 좋은 선택지가 될 수 있습니다.

하지만 필요한 데이터의 특징을 파악하여 추출할 수 있는 논리적인 방법이 있다면, General OCR의 제한된 기능만으로도 충분히 원하는 데이터를 구할 수 있습니다.

2024﹒©

 OU9999

Powered by Next.js﹒Vercel