신분증 OCR 모델 학습을 위한 가상 데이터 생성하기

2023. 12. 15.Tech

 

들어가며

안녕하세요! 케이뱅크 데이터서비스팀에서 AI 모델 개발을 하고 있는 김하영입니다.

이번 포스팅에서는 신분증 OCR PoC를 진행하면서 겪었던 데이터 수집의 어려움을 공유하고, 이를 극복하기 위해 어떤 방법들을 적용했는지 간단하게 소개하고자 합니다.

데이터 수집의 문제점

우수한 성능의 신분증 OCR 모델을 개발하려면 대량의 신분증 데이터셋이 필수적입니다. 그러나 데이터를 수집하기에는 현실적으로 다양한 어려움이 있습니다.

 

개인 정보 포함

여러분들도 아시다시피, 신분증에는 많은 개인 정보가 포함되어 있습니다. 먼저, 주민등록증에는 얼굴 사진, 이름, 주민등록번호, 주소 등 민감 정보가 존재합니다. 운전면허증에는 더불어 운전면허번호와 6자리 보안코드 등이 추가로 존재하지요. 이로 인해 대량의 원본 데이터를 획득하는 것은 법적, 윤리적 제약이 따릅니다. 데이터 수집에 성공하더라도 이를 원본 그대로 사용하는 것은 불가능하며, 개인 정보 보호를 위해 마스킹 등의 처리 후 사용해야 합니다. 

 

과도한 레이블링 비용

대량의 데이터를 확보했더라도, 이를 효과적으로 활용하기 위해서는 정확한 레이블링이 필요합니다. 신분증에는 이름, 주민등록번호, 주소와 같은 인식해야 할 항목이 많으며, 항목별로 텍스트와 텍스트의 좌표, 텍스트를 이루고 있는 문자, 문자별 좌표 등 여러 정보를 획득해야 합니다. 이 작업은 전문성이 요구되며, 시간과 비용이 굉장히 많이 소요되는 과정입니다. 그래서 대부분 데이터 전문 기업에게 아웃소싱하는 것을 고려합니다. 

 

위와 같이 여러 제약이 존재하는 원본 데이터를 수집하는 대신, 가상 데이터를 생성하여 학습 데이터로 활용하는 방법을 채택했습니다.

 

(모델 학습 목적으로) 직접 생성해봅시다.

우리는 앞서 언급한 여러가지 현실적인 제약을 극복하기 위해 모델 학습 목적으로 가상 신분증 데이터를 생성하는 것입니다.
따라서 이 가상 데이터는 합법적인 용도로만 사용되어야 하며, 어떤 경우에도 범죄 행위에 악용되어서는 안 됩니다.

 

가상으로 신분증을 만들려면 어떤 과정들을 거쳐야 하는지 생각해봅시다. 먼저, 신분증 고유의 패턴이 담긴 배경 이미지가 필요합니다. 그리고 그 배경 위에 이름, 주민등록번호, 주소 등 각 정보들을 해당 위치에 적절한 폰트와 크기, 색상으로 표시해야 합니다. 

  1. 신분증 템플릿 획득
    • 신분증 촬영
    • 신분증 영역 crop
    • 기존 텍스트 마스킹
    • 신분증 템플릿 획득
  2. 가상 정보 생성
    • 이름부터 발급기관까지 임의로 정보 생성
    • ChatGPT, 공공데이터 등 활용
  3. 신분증 템플릿에 가상 정보 합성
    • 다양한 폰트 다운로드
    • 항목별 정보 합성

 

그럼, 단계별로 하나씩 진행해 봅시다.

 

1단계 : 신분증 템플릿 획득

먼저, 신분증에서 텍스트 영역을 마스킹합니다. Image Inpainting 기술을 사용하여 신분증에서 기존 텍스트는 제거하고, 해당 영역을 주변의 배경으로 채우기 위함입니다.

 

Image Inpainting은 이미지에서 손상되거나 누락된 부분을 채워 완전한 이미지로 만드는 컴퓨터 비전 기술입니다.  Inpainting 기술에는 Generative Adversarial Networks(GANs)나 Autoencoders와 같은 구조를 사용하는 딥러닝 기반 방법, 템플릿 기반 방법, 편미분 방정식(Partial Differential Equations, PDE) 기반 방법, 텍스쳐 합성 방법 등이 있습니다.

 

여기에서는 PDE 기반의 PyInpaint를 사용하여  빠르게 처리해 보겠습니다. PyInpaint에 대한 자세한 과정은 PyInpaint Github을 참고하시기 바랍니다. 

 

이미지 편집 소프트웨어를 사용하여 마스킹 후, 아래와 같이 마스킹 이미지를 저장합니다.

 

주민등록증_mask,png

 

그 다음 아래의 코드를 통해 Inpainting 기술을 적용합니다.

import os
import cv2
from pyinpaint import Inpaint

# 원본 신분증 이미지
img_path = 'data/주민등록증_origin.png'
# 마스킹 신분증 이미지
mask_path = 'data/주민등록증_mask.png'

img = cv2.imread(img_path)
mask_img = cv2.imread(mask_path)

# Inpainting 수행
inpaint = Inpaint(img_path, mask_path)
inpainted_img = inpaint()

result_img = inpainted_img * 255.0
result_img = res_img.astype('uint8')
result_img = cv2.cvtColor(res_img, cv2.COLOR_BGR2RGB)

 

 

획득한 신분증 템플릿 이미지입니다. 군데군데 텍스트가 지워진 흔적이 있지만, 그럼에도 불구하고 꽤 그럴싸한 신분증 배경을 얻을 수 있습니다.

Inpainting을 통해 획득한 신분증 템플릿 이미지

 

 

 

2단계 : 가상 정보 생성

이제, 신분증 배경에 합성할 가상 정보를 생성합니다. 주민등록증에는 약 6개의 항목이 있으며, 운전면허증에는 약 10개의 항목이 있습니다.

ChatGPT와 공공 데이터, 랜덤함수 등을 활용하여 임의로 정보를 생성하였으며, 항목별 txt 파일에 목록을 저장하였습니다.

  • 주민등록증
    • 이름(한글) : ChatGPT
    • 이름(한자) : ChatGPT
    • 주민등록번호 : 패턴 지정 및 랜덤함수
    • 주소 : 공공 데이터 
    • 발급일자 : 패턴 지정 및 랜덤함수
    • 발급기관 : ChatGPT, 공공 데이터
  • 운전면허증
    • 이름(한글) : ChatGPT
    • 운전면허번호 : 패턴 지정 및 랜덤함수
    • 주민등록번호 : 패턴 지정 및 랜덤함수
    • 면허 종류 : 랜덤함수
    • 주소 : 공공 데이터
    • 면허증 갱신 시작일자 : 패턴 지정 및 랜덤함수
    • 면허증 갱신 종료일자 : 패턴 지정 및 랜덤함수
    • 보안코드 : 패턴 지정 및 랜덤함수
    • 발급일자 : 패턴 지정 및 랜덤함수
    • 발급기관 : ChatGPT, 공공 데이터

 

3단계 : 신분증 템플릿에 가상 정보 합성

신분증 템플릿에 가상 정보를 합성해 봅시다. TextRecognitionDataGenerator는 OCR 모델을 학습하기 위한 데이터를 생성하는 데 많이 활용되는 오픈소스로, 다음와 같은 기능을 제공합니다.

  1. 텍스트 생성 : 다양한 폰트 및 크기, 스타일의 텍스트가 포함된 이미지를 생성할 수 있습니다.
  2. 이미지 생성 : 텍스트와 배경 이미지를 조합하여 실제 OCR 시나리오에 유사한 이미지를 생성할 수 있습니다.
  3. 레이블링 : 생성된 이미지에 있는 텍스트에 대한 레이블을 생성하고 얻을 수 있습니다.
  4. 다양성 추가 : 다양한 언어, 폰트, 배경, 조명, 노이즈, 왜곡, 블러 등을 고려하여 데이터셋에 다양성을 추가할 수 있습니다.

모든 작업은 trdg 디렉토리 하위에서 진행합니다.

 

먼저, 텍스트 생성에 사용할 폰트를 다운받아 fonts/ko 디렉토리에 위치시킵니다.

그리고 1단계에서 생성한 신분증 템플릿 이미지 파일을 images 디렉토리에 위치시킵니다.

2단계에서 생성한 가상 정보 텍스트 파일들은 texts 디렉토리에 저장합니다.

가상 정보 텍스트 파일 목록
name.txt 에시
address.txt 예시

 

1단계에서 획득한 신분증 템플릿에 가상 정보를 항목별로 하나씩 누적 합성하는 방식으로 진행할 예정입니다.

주민등록증을 기준으로 설명드리면,

  1.  신분증 템플릿에 '주민등록증' 텍스트를 합성하여 결과물인  합성 이미지 파일(.jpg)과 레이블 텍스트 파일(.txt)을 생성합니다.
  2. 주민등록증' 합성 이미지에 '이름(한글)' 텍스트를 누적 합성하여 다시 합성 이미지 파일(.jpg)과 레이블 텍스트 파일(.txt)을 생성합니다.
  3. 이름(한글)' 합성 이미지에 '이름(한자)' 텍스트를 누적 합성하여 다시 합성 이미지 파일(.jpg)과 레이블 텍스트 파일(.txt)을 생성합니다.
  4. 이러한 방식으로 '발급 기관'까지 모든 항목에 대해 위 과정을 반복 수행합니다.
  5. 이를 통해 최종 결과로 각 항목이 누적된 합성 이미지 파일과 레이블 텍스트 파일이 생성됩니다.

 

위 방식으로 진행하기 위해서는 data_genererator.py에서 사용자가 지정한 영역에 텍스트를 합성할 수 있도록 코드를 수정해야 합니다.

...
##########################
# Create picture of text #
##########################
font_size = 44
text_left_origin = 456 # 텍스트 시작 위치의 x 좌표
text_top_origin = 66 # 텍스트 시작 위치의 x 좌표

image_font = ImageFont.truetype(font=font, size=font_size)

space_width = int(get_text_width(image_font, " ") * space_width)
space_width += 0

if word_split:
    splitted_text = []
    for w in text.split(" "):
        splitted_text.append(w)
        splitted_text.append(" ")
    splitted_text.pop()
else:
    splitted_text = text
    
...

            # 기존 이미지의 gt(text) 파일 내용 복사
            # before_gt_words = []
            # with open(os.path.join(image_dir, background_gt_name), "r") as f:
            #     lines = f.readlines()
            #     before_gt_words  = [line.strip() for line in lines]
            
            
            # Save the image
            if out_dir is not None:
                final_image.save(os.path.join(out_dir, image_name))
                # 단어별 bbox 좌표 저장 (gt)
                with open(os.path.join(out_dir, box_name), "w") as f:
                    
                    # if len(before_gt_words) > 0: # 기존 생성한 단어부터 저장
                    #     for gt_word in before_gt_words:
                    #         f.write(gt_word+'\n')
                    
                    f.write("{},{},{},{},{},{},{},{},{}\n".format(text_left, text_top, text_right, text_top, text_right, text_bottom, text_left, text_bottom, text))
                if output_mask == 1:
                    final_mask.save(os.path.join(out_dir, mask_name))
                if output_bboxes == 1:
                    bboxes = mask_to_bboxes(final_mask)
                    with open(os.path.join(out_dir, box_name), "w") as f:
                        for bbox in bboxes:
                            f.write(" ".join([str(v) for v in bbox]) + "\n")
                if output_bboxes == 2:
                    bboxes = mask_to_bboxes(final_mask, tess=True)
                    with open(os.path.join(out_dir, tess_box_name), "w") as f:
                        for bbox, char in zip(bboxes, text):
                            f.write(
                                " ".join([char] + [str(v) for v in bbox] + ["0"]) + "\n"
                            )

 

이후 run.py 파일을 실행하면 되며, 실행 시 사용되는 주요 인자들은 다음과 같습니다.

  • —output_dir : 결과 파일을 저장할 경로
  • —language : 생성 언어 선택
  • —background : 배경 이미지 사용 여부
  • —width: 생성 이미지의 너비
  • —format : 생성 이미지의 높이
  • —font_dir : 사용할 폰트 경로
  • —image_dir : 사용할 배경 이미지 경로

그 외에도 여러가지 지정할수 있는 변수들이 많으며, 이를 조절하여 다양한 조건의 신분증 이미지를 생성할 수 있습니다.

 

생성된 가상 신분증과 레이블 예시입니다.

가상 주민등록증 이미지
주민등록증 항목별 레이블
가상 운전면허증 이미지
운전면허증 항목별 레이블

 

 

항목별로 텍스트 길이나 형태 패턴만 맞추었기 때문에 주소와 발급기관이 일치하지 않거나 적성검사 기간에 오류가 있는 등 무효 값이 많지만, 텍스트 검출 및 인식 모델 학습을 위한 용도이므로 이러한 불일치나 오류는 크게 고려하지 않아도 무방합니다.

 

 

마치며

이번 포스팅에서는 개인 정보를 포함하는 데이터 수집의 현실적인 제약을 해결하기 위해 실제 데이터 대신, 가상으로 이미지를 생성하고 동시에 레이블 데이터를 함께 획득하는 방법에 소개해드렸습니다. 이 방법은 신분증 외에도 신용카드나 계약서, 영수증, 간판 등 여러분들이 원하는 어떤 대상에든 적용할 수 있습니다. 소개해드린 방법을 통해 텍스트 검출 및 인식 모델을 학습하는 데 조금이나마 도움이 되기를 바랍니다.