최근 책을 300페이지 넘게 스캔할 일이 있어 vFlat을 실행하니 두 페이지 스캔이 유료화된 것을 알게되었다. 유료 결제도 고민해 보았지만 몇 번 사용하지 않는 기능 때문에 매달 결제해야 하는 것이 부담스러웠다. 그리고 이전에 두 페이지 스캔 기능을 사용해 보았지만 책을 넘겨감에 따라 중앙선(책이 접히는 부분)의 위치가 달라지기 때문에 책 위치를 여러번 조정해 주어야 하는데 그것이 매우 불편하였다. 이참에 중앙선을 알아서 찾아주고 페이지 분할해주는 프로그램을 만들어야겠다고 마음을 먹고 시작하게 되었다.

프로그램의 기능

  • 입력: vFlat(또는 다른 프로그램)으로 책을 스캔한 이미지 (책을 펼쳐서 1 페이지로 촬영한 이미지)
  • 가정:
    • 맥에서 작업한다. (파이썬 코드는 윈도우에서도 사용가능하지만 파인더(Finder) 기능 등은 윈도우에 맞는 방법으로 바꾸어야 한다.)
    • 책 외관선을 따라 크롭(crop)되어 있다. (vFlat 기본 설정으로 스캔한 이미지)
    • 책 표지 등 1 페이지를 촬영한 이미지가 포함되어 있어도 무방하다.
  • 출력: 책의 중앙선을 자동으로 찾아서 좌/우 페이지를 분할한 이미지 (이미지의 개수가 두 배가 된다)

vFlat 스캔

  • 먼저 vFlat에서 책을 스캔한 후 “공유하기 - 이미지 공유”를 눌러 모든 페이지를 컴퓨터로 옮긴다.
  • 맥북인 경우 AirDrop 기능을 이용하면 Downloads 폴더로 쉽게 공유할 수 있다.

하나의 폴더에 옮기기

먼저 vFlat에서 공유된 그림파일이 다른 파일들과 섞이지 않도록 하나의 폴더에 옮기는 작업을 한다. 폴더를 먼저 만들고 그 곳에 그림파일을 옮기는 방법도 가능하지만 맥OS 파인더(Finder)의 “New Folder with Selection”(저자의 맥OS 언어가 영어로 되어 있는 점을 양해해주시기 바란다.) 기능을 이용해보자.

  • 파인더에서 vFlat에서 공유된 파일을 모두 선택한다.
  • 마우스 오른쪽 버튼(또는 터치패널 두 손가락 탭)을 누르고 가장 위에 있는 “New Folder with Selection”을 누른다.
  • 폴더가 하나 생성되며 선택된 파일이 모두 생성된 폴더에 들어간다.
  • 폴더명을 적절히 입력한다.

파일명 바꾸기(rename)

맥OS의 파인더(Finder) 기능 중 또다른 편리한 기능은 rename 기능이다. 윈도우에서는 유틸리티 프로그램을 다운받아서 사용하던 기능이 기본 파일관리기에 있는 것이다.
vFlat에서 공유한 이미지들은 아래의 형식으로 저장되어 있을 것이다.

임시 - 1.jpg
임시 - 2.jpg
...
임시 - 10.jpg
...
임시 - 100.jpg
...

나중에 파이썬에서 파일명에 따라 정렬해줄 것이기 때문에 파일명이 일관되어야 한다. 아래와 같이 파일명을 수정해준다.

  • 파인더에서 vFlat의 공유 이미지 모두를 선택한다. (하나의 폴더 안에 있으므로 cmd + A 사용 가능)
  • 오른쪽 마우스를 눌러 “rename…“을 선택한다.
  • 왼쪽 위 풀다운 메뉴의 “Replace Text”, “Add Text”, “Format” 중에 “Replace Text”를 선택한다.
  • “Find”에 “임시 - “(마지막 공백을 반드시 추가), “Replace with”에 빈 칸을 입력하고 Rename 버튼을 누른다.

파일명이 다음과 같이 변경된다.

1.jpg
2.jpg
...
10.jpg
...
100.jpg
...
  • “1.jpg”부터 “9.jpg”까지 선택한 후 다시 “rename…“을 실행한다.
  • 이번에는 “Add Text”로 하고 “00”, “before name”을 입력/선택하고 Rename 버튼을 누른다.
  • “10.jpg”부터 “99.jpg”까지 선택한 후 같은 방법으로 “0”을 앞에 붙여준다.

최종적으로 다음과 같이 변경된다.

001.jpg
002.jpg
...
010.jpg
...
100.jpg
...

이미지 회전

vFlat으로 두 페이지를 촬영하면 글자를 기준으로 90도 회전되어 있는 경우가 있다. 이럴 때에도 역시 파인더의 이미지 회전 기능을 이용하면 편리하다. 모든 스캔 파일을 선택한 후 오른쪽 마우스를 누르고 “Quick Actions”에 마우스 포인터를 이동하면 오른쪽에 “Rotate Left” 메뉴를 발견할 수 있다. 이를 클릭하면 수백개의 이미지를 한 번에 회전시킬 수 있다.
이러한 방법으로 모든 이미지의 글자가 정방향이 되도록 회전시킨다.

두 페이지 스캔을 분할 저장하는 파이썬 코드

이미지 파일이 준비되면 아래의 파이썬 코드를 복사하거나 git clone하여 실행하면 두 페이지로 스캔된 파일을 좌우 페이지로 분할하여 각각의 파일로 저장해준다.

github repo link

ScanBookSplit.py 안에 구현되어 있는 ScanBookSplit 클래스를 간략히 설명하면 다음과 같다.

BookScanSplit 클래스는 이미지에서 텍스트를 검출하고, 이를 바탕으로 두 페이지로 나눈다. 이를 통해 각 이미지가 두 페이지로 나누어 저장된다.

1. 초기화 및 설정

class BookScanSplit:
    def __init__(self, input_folder, output_folder, debug_folder=None):
        # 초기 설정: 입력 폴더, 출력 폴더, 디버그 폴더 지정
        self.input_folder = input_folder
        self.output_folder = output_folder
        self.debug_folder = debug_folder

        # JPG 파일만 필터링해 불러옴
        filenames = sorted([f for f in os.listdir(self.input_folder) if f.lower().endswith('.jpg')])
        self.input_files = [os.path.join(self.input_folder, f) for f in filenames]
        self.output_files = [os.path.join(self.output_folder, f) for f in filenames]
        # 디버그 폴더 설정 (옵션)
        if self.debug_folder:
            self.debug_files = { ... }

        # 그리기 스타일 설정
        self.styles = { ... }

        # 중앙선 검출에 필요한 파라미터 설정
        self.params = { ... }

이 코드는 입력 이미지 목록을 생성하고, 디버그 모드일 경우, 히스토그램, 중앙선 등을 디버그 이미지로 저장할 경로를 설정한다. self.styles는 디버그용 이미지에 나타내는 박스나 선 등의 스타일(색, 선 두께)를 지정하며 self.params는 중앙선을 찾기 위한 파라메터를 저장한다.

2. 이미지 로드 및 텍스트 검출

각 이미지를 불러오고, 텍스트를 검출하는 함수는 아래와 같다.

def load_image(self, i):
    self.img = cv2.imread(self.input_files[i])

def detect_text(self, i):
    if self.img is None:
        raise(RuntimeError)
    self.text_data = pytesseract.image_to_data(self.img, output_type=Output.DICT)

여기서 pytesseract를 통해 텍스트 데이터를 검출하고 이를 self.text_data에 저장한다.

3. 중앙선 검출 방법

이미지를 두 페이지로 나누기 위해 중앙선을 검출하기 위하여 두 가지 방법을 사용한다. 히스토그램 기반과 Hough 변환을 통한 방법이 그것인데, 페이지 특성에 따라 더 잘 작동하는 방법이 있기 때문이다.

3.1 히스토그램 기반 중앙선 검출

히스토그램을 통해 텍스트 좌표 분포를 분석하고 가장 적은 텍스트가 있는 위치를 중앙선으로 선택한다.

def find_center_line_by_hist(self, k, n_bin=27):
    ...
    # 히스토그램 생성
    hist, bin_edges = np.histogram(x_centers, bins=np.arange(0, width + bin_size, bin_size))
    ...
    # 텍스트가 가장 적은 부분을 중앙선으로 선택
    min_bin_index = np.argmin(hist[n_exclude:-n_exclude]) + n_exclude
    center_line = bin_edges[min_bin_index] + int(bin_size / 2)

3.2 Hough 변환을 통한 수직선 검출

이미지에서 수직선을 감지해 중앙선을 찾는다.

def find_center_line_by_Hough(self, k):
    ...
    lines = cv2.HoughLinesP(central_region, rho=1, theta=np.pi/180, threshold=100, ...)
    ...
    # 수직선 중 가장 중앙에 가까운 선을 중앙선으로 선택
    fold_position = x1 + central_region_left

두 가지 방법을 모두 이용하여 중앙선을 찾은 후 페이지의 정중앙에 가까운 선을 중앙선으로 선택한다.

4. 페이지 추출 및 저장

중앙선을 기준으로 좌우 페이지를 나눈다. 나눠진 각 페이지는 별도의 이미지로 저장된다.

def extract_pages(self, k, xc):
    ...
    detected_textboxes_left = np.array(detected_textboxes_left)
    detected_textboxes_right = np.array(detected_textboxes_right)
    
    # 좌우 페이지 추출
    page_box_left = self._get_page_box(detected_textboxes_left, default_page_left, ...)
    page_box_right = self._get_page_box(detected_textboxes_right, default_page_right, ...)
    
    # 추출된 이미지 저장
    cv2.imwrite(filename_left, extracted_page_left)
    cv2.imwrite(filename_right, extracted_page_right)

ScanBookSplit 클래스를 사용하여 실제로 스캔한 이미지에 적용하는 방법은 아래의 샘플 코드에 나타내었다. 스캔한 이미지는 input_folder_path에 저장되어 있어야 하고 분할된 이미지는 output_folder_path에, 디버그용 이미지는 debug_folder_path에 저장된다. output, debug 폴더는 미리 생성되어 있어야 한다.
BookScanSplit 클래스 생성 시 debug 폴더를 지정하지 않으면 디버그 이미지가 생성되지 않는다.

input_folder_path = '/Users/.../sample-book/input/'
output_folder_path = '/Users/.../sample-book/output/'
debug_folder_path = '/Users/.../sample-book/debug/'

bss = BookScanSplit(input_folder_path, output_folder_path, debug_folder_path)
bss.clear_output_folders()
bss.split()

pdf 생성하기

자 이제 마지막 단계이다. 분할 이미지가 아래와 같이 저장되어 있을 것이다.

001.jpg
002-1.jpg
002-2.jpg
003-1.jpg
...

“-1”이 붙어있는 파일이 좌측, “-2”가 우측 페이지를 저장한 파일이다. “001.jpg”는 표지이므로 한 페이지로 스캔되어 분할되지 않았다.

마지막에도 파인더의 기능을 사용한다. (파인더가 정말 유용하지 않은가?) 모든 출력파일을 선택한 후 오른쪽 마우스를 누르고 “Quick Actions” - “Create PDF”를 선택하면 모든 이미지를 합쳐 하나의 pdf 파일로 만들어준다.

TIP

맥에서 무료 프로그램인 Lightweight PDF 프로그램을 사용하면 용량이 큰 pdf 파일의 화질은 크게 저하되지 않으면서 용량을 대폭 줄여준다. 한 번 사용해보길 바란다.


책을 무단으로 스캔하여 공개하는 행위는 불법이므로 반드시 법과 규정이 허용하는 범위에서 활용하시기 바랍니다.