Skip to content

Instantly share code, notes, and snippets.

@haandol
Created June 12, 2025 05:58
Show Gist options
  • Save haandol/bf07d733e4950ec314594e40f5b2115e to your computer and use it in GitHub Desktop.
Save haandol/bf07d733e4950ec314594e40f5b2115e to your computer and use it in GitHub Desktop.
vertical image split
#!/usr/bin/env python3
"""
이미지 분할 도구
위아래로 긴 이미지에서 중간 여백을 찾아 여러 개의 이미지로 분할합니다.
"""
import cv2
import numpy as np
import os
import argparse
from typing import List, Tuple
import matplotlib.pyplot as plt
class ImageSplitter:
def __init__(self, threshold: int = 250, min_gap_height: int = 100, min_content_height: int = 50):
"""
이미지 분할기 초기화
Args:
threshold: 여백으로 판단할 픽셀 밝기 임계값 (0-255, 기본값: 250 - 흰색에 가까운 영역)
min_gap_height: 여백으로 인정할 최소 높이 (픽셀, 기본값: 100 - 100픽셀 이상 연속된 흰색 줄)
min_content_height: 분할된 이미지의 최소 높이 (픽셀)
"""
self.threshold = threshold
self.min_gap_height = min_gap_height
self.min_content_height = min_content_height
def load_image(self, image_path: str) -> np.ndarray:
"""이미지 파일을 로드합니다."""
if not os.path.exists(image_path):
raise FileNotFoundError(f"이미지 파일을 찾을 수 없습니다: {image_path}")
image = cv2.imread(image_path)
if image is None:
raise ValueError(f"이미지를 읽을 수 없습니다: {image_path}")
return image
def detect_horizontal_gaps(self, image: np.ndarray) -> List[Tuple[int, int]]:
"""
이미지에서 수평 여백 영역을 감지합니다.
100픽셀 이상 연속된 흰색 줄을 찾아 분할 지점으로 사용합니다.
Args:
image: 입력 이미지 (BGR 형식)
Returns:
여백 영역의 (시작_y, 끝_y) 좌표 리스트
"""
# 그레이스케일로 변환
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
height, width = gray.shape
# 각 행의 평균 밝기 계산
row_means = np.mean(gray, axis=1)
# 여백 행 찾기 (임계값보다 밝은 행 - 흰색에 가까운 행)
gap_rows = row_means > self.threshold
# 연속된 여백 영역 찾기
gaps = []
in_gap = False
gap_start = 0
for i, is_gap in enumerate(gap_rows):
if is_gap and not in_gap:
# 여백 시작
gap_start = i
in_gap = True
elif not is_gap and in_gap:
# 여백 끝
gap_height = i - gap_start
if gap_height >= self.min_gap_height: # 100픽셀 이상인 경우만
gaps.append((gap_start, i))
in_gap = False
# 마지막 여백 처리
if in_gap:
gap_height = height - gap_start
if gap_height >= self.min_gap_height: # 100픽셀 이상인 경우만
gaps.append((gap_start, height))
return gaps
def split_image_by_gaps(self, image: np.ndarray, gaps: List[Tuple[int, int]]) -> List[np.ndarray]:
"""
여백을 기준으로 이미지를 분할합니다.
Args:
image: 원본 이미지
gaps: 여백 영역 리스트
Returns:
분할된 이미지 리스트
"""
if not gaps:
return [image]
height = image.shape[0]
split_images = []
# 첫 번째 이미지 (시작부터 첫 번째 여백까지)
if gaps[0][0] > 0:
first_img = image[0:gaps[0][0]]
if first_img.shape[0] >= self.min_content_height:
split_images.append(first_img)
# 중간 이미지들 (여백 사이의 내용들)
for i in range(len(gaps) - 1):
start_y = gaps[i][1] # 현재 여백의 끝
end_y = gaps[i + 1][0] # 다음 여백의 시작
if end_y > start_y:
img_segment = image[start_y:end_y]
if img_segment.shape[0] >= self.min_content_height:
split_images.append(img_segment)
# 마지막 이미지 (마지막 여백부터 끝까지)
if gaps[-1][1] < height:
last_img = image[gaps[-1][1]:height]
if last_img.shape[0] >= self.min_content_height:
split_images.append(last_img)
return split_images
def visualize_gaps(self, image: np.ndarray, gaps: List[Tuple[int, int]], save_path: str = None):
"""
감지된 여백을 시각화합니다.
Args:
image: 원본 이미지
gaps: 여백 영역 리스트
save_path: 저장할 경로 (선택사항)
"""
# 그레이스케일로 변환
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
height, width = gray.shape
# 각 행의 평균 밝기 계산
row_means = np.mean(gray, axis=1)
# 시각화
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 8))
# 원본 이미지 표시
ax1.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
ax1.set_title('원본 이미지')
ax1.set_xlabel('Width')
ax1.set_ylabel('Height')
# 여백 영역 표시
for gap_start, gap_end in gaps:
ax1.axhspan(gap_start, gap_end, alpha=0.3, color='red', label='여백 영역')
# 행별 밝기 그래프
ax2.plot(row_means, range(height))
ax2.axvline(x=self.threshold, color='red', linestyle='--', label=f'임계값 ({self.threshold})')
ax2.set_title('행별 평균 밝기')
ax2.set_xlabel('평균 밝기')
ax2.set_ylabel('Height')
ax2.invert_yaxis()
ax2.legend()
# 여백 영역 표시
for gap_start, gap_end in gaps:
ax2.axhspan(gap_start, gap_end, alpha=0.3, color='red')
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=150, bbox_inches='tight')
print(f"시각화 결과가 저장되었습니다: {save_path}")
plt.show()
def save_split_images(self, split_images: List[np.ndarray], output_dir: str, base_name: str):
"""
분할된 이미지들을 저장합니다.
Args:
split_images: 분할된 이미지 리스트
output_dir: 출력 디렉토리
base_name: 기본 파일명
"""
os.makedirs(output_dir, exist_ok=True)
saved_files = []
for i, img in enumerate(split_images):
filename = f"{base_name}_part_{i+1:03d}.png"
filepath = os.path.join(output_dir, filename)
cv2.imwrite(filepath, img)
saved_files.append(filepath)
print(f"저장됨: {filepath} (크기: {img.shape[1]}x{img.shape[0]})")
return saved_files
def process_image(self, image_path: str, output_dir: str = None, visualize: bool = False) -> List[str]:
"""
이미지를 처리하여 분할합니다.
Args:
image_path: 입력 이미지 경로
output_dir: 출력 디렉토리 (기본값: 입력 파일과 같은 디렉토리)
visualize: 여백 감지 결과 시각화 여부
Returns:
저장된 파일 경로 리스트
"""
# 이미지 로드
print(f"이미지 로딩 중: {image_path}")
image = self.load_image(image_path)
print(f"이미지 크기: {image.shape[1]}x{image.shape[0]}")
# 여백 감지
print("여백 영역 감지 중...")
gaps = self.detect_horizontal_gaps(image)
print(f"감지된 여백 영역 수: {len(gaps)}")
for i, (start, end) in enumerate(gaps):
print(f" 여백 {i+1}: y={start}~{end} (높이: {end-start}px)")
# 시각화 (선택사항)
if visualize:
base_name = os.path.splitext(os.path.basename(image_path))[0]
viz_path = os.path.join(os.path.dirname(image_path), f"{base_name}_gaps_visualization.png")
self.visualize_gaps(image, gaps, viz_path)
# 이미지 분할
print("이미지 분할 중...")
split_images = self.split_image_by_gaps(image, gaps)
print(f"분할된 이미지 수: {len(split_images)}")
# 출력 디렉토리 설정
if output_dir is None:
output_dir = os.path.join(os.path.dirname(image_path), "split_images")
# 분할된 이미지 저장
base_name = os.path.splitext(os.path.basename(image_path))[0]
saved_files = self.save_split_images(split_images, output_dir, base_name)
print(f"\n처리 완료! {len(saved_files)}개의 이미지가 저장되었습니다.")
print(f"출력 디렉토리: {output_dir}")
return saved_files
def main():
parser = argparse.ArgumentParser(description="이미지를 여백 기준으로 분할합니다. (100픽셀 이상 연속된 흰색 줄에서 자름)")
parser.add_argument("input_image", help="입력 이미지 파일 경로")
parser.add_argument("-o", "--output", help="출력 디렉토리 (기본값: 입력 파일과 같은 디렉토리의 split_images 폴더)")
parser.add_argument("-t", "--threshold", type=int, default=250, help="여백 판단 임계값 (0-255, 기본값: 250 - 흰색에 가까운 영역)")
parser.add_argument("--min-gap-height", type=int, default=100, help="여백으로 인정할 최소 높이 (기본값: 100px - 100픽셀 이상 연속된 흰색 줄)")
parser.add_argument("--min-content-height", type=int, default=50, help="분할된 이미지의 최소 높이 (기본값: 50px)")
parser.add_argument("-v", "--visualize", action="store_true", help="여백 감지 결과 시각화")
args = parser.parse_args()
# 이미지 분할기 생성
splitter = ImageSplitter(
threshold=args.threshold,
min_gap_height=args.min_gap_height,
min_content_height=args.min_content_height
)
try:
# 이미지 처리
saved_files = splitter.process_image(
image_path=args.input_image,
output_dir=args.output,
visualize=args.visualize
)
print(f"\n성공적으로 완료되었습니다!")
except Exception as e:
print(f"오류 발생: {e}")
return 1
return 0
if __name__ == "__main__":
exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment