728x90
728x90
문제인식
태블릿 PC를 구매하게 되었고 도서를 스캔하며 공부할 계획을 세웠다.
vFlat으로 스캔을 해보았지만 문제가 있었다. (물론 내 잘못)
한 번에 두 페이지를 스캔했기 때문에 각 페이지를 개별적으로 분할해야 했다.
물론 vFlat에는 두 페이지를 동시에 촬영하고 한 장씩 저장하는 기능이 있었지만, 유료 서비스였다.
매달 결제하고 싶지 않았기에 Python 코딩으로 해결하고자 했다.
code
마음에 들지는 않았지만, 그래도 그럭저럭 볼 만하게 분할되었다.
시간이 걸리더라도 한 번에 두 장씩 말고 한 장씩 스캔하는 것이 좋을 듯하다.
import os
from tkinter import Tk
from tkinter import filedialog
import cv2
import numpy as np
def split_scanned_pages(image_paths):
for image_path in image_paths:
# 이미지 읽기
normalized_path = os.path.normpath(image_path)
image = cv2.imread(normalized_path)
if image is None:
print(f"이미지를 불러올 수 없습니다: {normalized_path}")
continue
# 이미지 크기 얻기
height, width, _ = image.shape
# 가로 세로 비율을 기반으로 한 페이지 여부 판단
aspect_ratio = width / height
if aspect_ratio < 1.2:
print(f"한 페이지로 간주됨, 분할하지 않습니다: {normalized_path}")
# 한 페이지로 간주되면 복사하여 저장
dir_name, file_name = os.path.split(image_path)
left_page_path = os.path.join(dir_name, f"split_{os.path.splitext(file_name)[0]}_1{os.path.splitext(file_name)[1]}")
cv2.imwrite(left_page_path, image)
print(f"이미지 저장 완료: {left_page_path}")
continue
# 그레이스케일로 변환
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 가우시안 블러 적용 (노이즈 제거)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# Adaptive Threshold 적용 (그림자 및 텍스트 영역 분리)
adaptive_thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
# Sobel Edge Detection 적용 (가장자리 검출)
sobelx = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=5)
sobely = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=5)
edges = cv2.magnitude(sobelx, sobely)
# Morphological Closing 적용 (가장자리 연결 강화)
kernel = np.ones((5, 5), np.uint8)
edges = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
# 세로 히스토그램 계산 (가장자리 합계 및 Adaptive Threshold 결과를 결합하여 수직 분포 확인)
combined = np.sum(edges, axis=0) + np.sum(adaptive_thresh, axis=0)
# 1D 가우시안 스무딩 적용 (히스토그램 스무딩)
smoothed_combined = cv2.GaussianBlur(combined.reshape(1, -1), (1, 21), 0).flatten()
# 중앙에서 밀도가 가장 낮은 지점을 찾기 위한 탐색 범위 설정 (47.5% ~ 52.5%)
search_start = int(width * 0.475)
search_end = int(width * 0.525)
split_x = search_start
min_density = float('inf')
# 중앙 근처에서 밀도가 가장 낮은 지점 찾기
for i in range(search_start, search_end):
if smoothed_combined[i] < min_density:
min_density = smoothed_combined[i]
split_x = i
# 컨투어 기반 추가 검토 (가장 큰 컨투어의 중심을 분할 지점으로 설정)
contours, _ = cv2.findContours(adaptive_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
largest_contour = max(contours, key=cv2.contourArea)
x, y, w, h = cv2.boundingRect(largest_contour)
contour_center = x + w // 2
if search_start < contour_center < search_end:
split_x = contour_center
# 파일명 및 확장자 분리
dir_name, file_name = os.path.split(image_path)
base_name, ext = os.path.splitext(file_name)
# 이미지를 두 개로 나누기
left_page = image[:, :split_x]
right_page = image[:, split_x:]
# 분할된 이미지가 비어 있는지 확인
if left_page.size == 0 or right_page.size == 0:
print(f"분할된 이미지가 비어 있습니다. 분할을 건너뜁니다: {normalized_path}")
continue
# 저장할 파일명 설정
left_page_path = os.path.join(dir_name, f"split_{base_name}_1{ext}")
right_page_path = os.path.join(dir_name, f"split_{base_name}_2{ext}")
# 결과 저장
cv2.imwrite(left_page_path, left_page)
cv2.imwrite(right_page_path, right_page)
print(f"이미지 저장 완료: {left_page_path}, {right_page_path}")
# 파일 선택창 열기
Tk().withdraw() # Tkinter 기본 창 숨기기
file_paths = filedialog.askopenfilenames(initialdir='D:/', title='이미지 파일 선택', filetypes=[("Image Files", "*.jpg *.jpeg *.png *.bmp")])
# 선택된 파일 처리
if file_paths:
split_scanned_pages(file_paths)
else:
print("파일이 선택되지 않았습니다.")
728x90
728x90
'etc.' 카테고리의 다른 글
PDF 처리와 활용: Python을 사용한 PDF 조작 방법 (0) | 2024.11.11 |
---|---|
스크린 캡처 방지 가이드: 안드로이드, iOS, 그리고 윈도우에서의 구현 방법 (0) | 2024.11.08 |
스캔한 도서를 pdf로 만들어 보자 (image to pdf) (0) | 2024.10.29 |
라즈베리파이 공유기 만들기 (Routed Wireless Access Point) (0) | 2022.08.08 |
지그재그 지그비 zigbee (0) | 2022.07.10 |