메뉴 닫기

파이썬 OpenCV 문서 스캔 모범 레시피 Canny 컨투어 사각형 투시보정 CLAHE

파이썬 OpenCV 문서 스캔 모범 레시피 Canny 컨투어 사각형 투시보정 CLAHE

🧭 흐릿한 촬영본을 또렷한 스캔본으로 바꾸는 한 방의 파이프라인을 소개합니다

휴대폰으로 찍은 계약서나 영수증이 기울어지고 테두리가 흐릿해 보이면 작업 흐름이 멈추기 마련입니다.
조명 반사 때문에 본문이 죽거나, 배경과 종이가 섞여 OCR이 실패한 경험도 낯설지 않죠.
이럴 때 가장 재현성 높은 길은 검증된 순서를 정해두고 그대로 따르는 것입니다.
이 글은 파이썬과 OpenCV를 사용해 에지 검출부터 컨투어 사각형 추출, 투시 보정, CLAHE 기반 대비 향상까지 이어지는 문서 스캔 파이프라인을 일관되게 적용하는 방법을 다룹니다.
사진의 왜곡과 그림자를 줄이고, 인쇄물의 잉크 질감을 해치지 않으면서 텍스트 가독성을 극대화하는 데 초점을 맞춥니다.
불필요한 시행착오는 덜어내고, 현장에서 바로 쓸 수 있는 기준값과 체크포인트를 중심으로 설명합니다.

핵심은 순서입니다.
먼저 Canny로 가장자리 정보를 안정적으로 얻고, 문서 외곽을 대표하는 사각형 컨투어를 근사합니다.
이어 사다리꼴 변형을 올바른 직사각형 평면으로 투시 보정한 다음, CLAHE로 지역 대비를 보강해 잉크 번짐 없이 글자를 선명하게 살립니다.
각 단계는 서로를 뒷받침하도록 설계되어 있으며, 입력 환경이 조금 달라져도 결과 품질이 흔들리지 않도록 파라미터의 의미와 조합을 풀어냅니다.
현업 자동화, 스캐너 대체, OCR 전처리 등 다양한 목적에 곧바로 연결할 수 있도록 안내합니다.



🚀 파이썬 OpenCV 문서 스캔 개요

이 파트는 문서 스캔 파이프라인의 큰 그림을 한 번에 잡도록 돕는 개요입니다.
핵심 흐름은 Canny → 컨투어 사각형 → 투시보정 → CLAHE 순서입니다.
이 순서가 중요한 이유는 각 단계가 바로 다음 단계의 품질을 좌우하기 때문입니다.
Canny가 안정적인 에지 맵을 만들어야 외곽 컨투어가 깔끔하게 잡히고, 외곽이 정확해야 투시보정 결과가 왜곡 없이 곧게 펴집니다.
마지막으로 CLAHE를 적용하면 전체 대비가 아니라 영역별 대비를 올려 잉크 텍스처는 유지하면서 글자 테두리를 또렷하게 강조할 수 있습니다.

현장 촬영본은 종이의 기울어짐, 배경의 잡색, 그림자, LED 깜빡임 등의 변수로 가득합니다.
그래서 파라미터를 외워두기보다 입력 표준화 → 에지 검출 → 도형 근사 → 기하 보정 → 대비 보강이라는 처리 철학을 갖추는 게 유리합니다.
여기서 입력 표준화는 해상도 리사이즈, 컬러 공간 변환(BGR→GRAY), 블러(예: Gaussian)로 노이즈를 줄여 에지 검출의 임계값이 흔들리지 않도록 돕습니다.
컨투어는 면적과 사각형성(approxPolyDP로 점 4개)으로 필터링하고, 좌표 정렬로 꼭짓점을 일관된 순서(좌상, 우상, 우하, 좌하)로 정리합니다.
투시보정은 getPerspectiveTransform/warpPerspective로 출력 크기를 명확히 정하는 것이 포인트이며, CLAHE는 타일 크기와 clipLimit을 통해 ‘선명하지만 과하지 않은’ 지점을 찾습니다.

  • 🛠️입력 이미지는 BGR→GRAYSCALE 변환과 가우시안 블러로 표준화합니다.
  • ⚙️Canny 임계값은 이미지 명암 대비에 맞춰 하향식으로 점검합니다.
  • 🧭가장 큰 사각형 컨투어를 찾고, 꼭짓점 순서를 좌상→우상→우하→좌하로 정렬합니다.
  • 🖼️출력 해상도(A4 등)와 DPI 목표를 정해 warpPerspective에 반영합니다.
  • CLAHE로 지역 대비를 끌어올리되, 과도한 노이즈 증폭은 피합니다.
CODE BLOCK
import cv2
import numpy as np

img = cv2.imread("scan_input.jpg")
orig = img.copy()

# 1) 표준화
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)

# 2) Canny
edges = cv2.Canny(blur, 75, 200)

# 3) 컨투어 사각형
cnts, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)

doc = None
for c in cnts:
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    if len(approx) == 4:
        doc = approx
        break

# 좌표 정렬
def order_pts(pts):
    pts = pts.reshape(4, 2)
    s = pts.sum(axis=1); diff = np.diff(pts, axis=1)
    ordered = np.zeros((4, 2), dtype="float32")
    ordered[0] = pts[np.argmin(s)]   # tl
    ordered[2] = pts[np.argmax(s)]   # br
    ordered[1] = pts[np.argmin(diff)]# tr
    ordered[3] = pts[np.argmax(diff)]# bl
    return ordered

rect = order_pts(doc)
(w, h) = (1200, 1700)  # 출력 목표 해상도 예시(A4 비율 근사)
dst = np.array([[0,0],[w-1,0],[w-1,h-1],[0,h-1]], dtype="float32")

# 4) 투시보정
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(orig, M, (w, h))

# 5) CLAHE
lab = cv2.cvtColor(warped, cv2.COLOR_BGR2LAB)
L, A, B = cv2.split(lab)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
L2 = clahe.apply(L)
lab2 = cv2.merge([L2, A, B])
enhanced = cv2.cvtColor(lab2, cv2.COLOR_LAB2BGR)

cv2.imwrite("scan_output.jpg", enhanced)

💡 TIP: Canny 임계값(예: 75, 200)은 입력 대비에 따라 달라질 수 있습니다.
대략적인 기준을 잡고, 블러 강도, 임계값 상·하한을 묶어 조정하면 재현성이 높아집니다.

⚠️ 주의: CLAHE를 그레이스케일에 직접 적용하면 컬러 문서에서 색 균형이 깨질 수 있습니다.
가능하면 LAB의 L 채널에만 적용한 뒤 다시 합성하세요.

항목1 항목2
단계 목적
Canny 문서 외곽을 포함한 선명한 에지 맵 생성
컨투어 사각형 문서 경계를 4점 다각형으로 근사
투시보정 사다리꼴을 평평한 직사각형으로 펴기
CLAHE 지역 대비 향상으로 텍스트 가독성 강화

💎 핵심 포인트:

문서 스캔 품질은 정확한 외곽 검출기하 보정에서 사실상 승부가 납니다.
CLAHE는 마지막 다듬기 단계로 생각하고, 과도한 샤프닝보다 노이즈 억제와 대비 균형에 신경 쓰면 성능과 자연스러움을 함께 얻을 수 있습니다.

🔍 Canny 에지 검출 최적값 설정

Canny 알고리즘은 이미지에서 경계를 찾는 가장 보편적이고 안정적인 도구입니다.
문서 스캔에서는 특히 종이와 배경의 구분, 글자와 종이의 대비를 살려주는 데 핵심 역할을 합니다.
하지만 임계값을 잘못 잡으면 전체 외곽이 끊기거나 불필요한 잡음이 따라 들어와 이후 단계가 흔들리게 됩니다.

Canny는 두 개의 임계값 lowThreshold, highThreshold를 필요로 합니다.
일반적으로 high를 low의 2배~3배로 잡으면 안정적입니다.
예를 들어 low=50, high=150 또는 low=75, high=200 같은 조합이 자주 쓰입니다.
하지만 이미지 해상도, 조명, 배경 대비에 따라 달라지므로, 샘플 이미지를 몇 장 뽑아 두고 가변적으로 적용하는 것이 좋습니다.
가우시안 블러를 선행 처리하면 작은 잡음이 줄어 경계선이 더 매끄럽게 추출됩니다.

📌 파라미터 튜닝 전략

에지 검출은 단순히 “경계가 잘 보이게 하는 것”이 목표가 아니라, 문서 외곽이 한 번에 잡히도록 만드는 것이 관건입니다.
글자 내부의 세세한 획은 과감히 희생하더라도, 종이의 테두리가 분명히 드러나야 컨투어 단계에서 안정적으로 사각형을 추출할 수 있습니다.

  • 🔧가우시안 블러 (5×5, sigma=0)를 선행 적용합니다.
  • 📊lowThreshold는 배경 노이즈를 제거할 수 있을 만큼 높여 잡습니다.
  • ⚖️highThreshold는 low의 2~3배로 설정해 주요 경계만 남깁니다.
  • 🧪테스트 이미지를 최소 5장 이상 두고 공통적으로 잘 동작하는 구간을 찾습니다.
CODE BLOCK
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)

# Canny 에지 검출
edges = cv2.Canny(blur, 75, 200)

cv2.imshow("edges", edges)
cv2.waitKey(0)

💬 에지 맵이 너무 복잡하면 사각형이 아닌 다각형이 추출될 수 있습니다.
이 경우 low/high 임계값을 점진적으로 올려 불필요한 내부 경계를 제거하세요.

💡 TIP: 밝기 불균형이 심한 경우 단순 Canny보다 적응형 임계값(Adaptive Threshold)을 활용해 전처리를 보완한 뒤 Canny를 적용하면 안정성이 높습니다.



🧩 컨투어 사각형 추출과 정밀 근사

Canny 에지 검출로 얻은 이진 이미지를 기반으로 문서의 외곽선을 추출하기 위해 컨투어(Contour) 탐색을 진행합니다.
컨투어는 이미지의 연속된 곡선을 따라 경계선을 벡터 형태로 표현한 것이며, OpenCV의 cv2.findContours() 함수를 통해 쉽게 구할 수 있습니다.
문서 스캔에서는 가장 큰 사각형 모양의 컨투어를 찾아내는 것이 목표입니다.

일반적으로 여러 개의 컨투어가 검출되기 때문에, 면적 기준으로 정렬 후 가장 큰 컨투어를 우선 검사합니다.
이때 cv2.approxPolyDP() 함수를 사용해 곡선을 다각형으로 근사하면 문서의 사각형 외곽을 네 꼭짓점으로 단순화할 수 있습니다.
네 개의 꼭짓점이 검출되면 문서의 외곽으로 판단하고 이후 투시 보정 단계로 이어갑니다.

📌 컨투어 정제 과정

사각형 외곽을 제대로 잡으려면 몇 가지 정제 과정이 필요합니다.
단순히 넓이가 큰 컨투어만 고르면 배경의 그림자나 책상 모서리가 선택될 수 있으므로 다음 규칙을 함께 적용합니다.

  • 📐컨투어 면적이 전체 이미지의 일정 비율(예: 20% 이상)에 해당하는 것만 선택합니다.
  • 📏approxPolyDP로 근사했을 때 꼭짓점이 정확히 4개인 컨투어만 통과시킵니다.
  • 🔄꼭짓점 좌표를 좌상-우상-우하-좌하 순으로 정렬하여 투시 보정에 일관되게 사용합니다.
CODE BLOCK
cnts, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)

for c in cnts:
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    if len(approx) == 4:
        screenCnt = approx
        break

# 꼭짓점 정렬 함수
def order_points(pts):
    rect = np.zeros((4, 2), dtype="float32")
    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]
    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]
    return rect

💬 컨투어 탐색 후 근사 과정에서 epsilon 값을 0.01~0.05 사이로 조정하면 직선과 곡선의 근사 정확도를 제어할 수 있습니다.
너무 작으면 잡음이 많아지고, 너무 크면 사각형이 찌그러지므로 적절한 균형이 필요합니다.

💎 핵심 포인트:
컨투어는 단순히 가장 큰 사각형을 찾는 것이 아니라, 문서 테두리를 대표할 수 있는 네 꼭짓점을 안정적으로 확보하는 과정입니다.
이 단계가 흔들리면 투시 보정 결과도 불안정해지므로, 면적·꼭짓점 수·좌표 정렬 세 가지 기준을 모두 충족해야 합니다.

🖼️ 투시 보정과 해상도 설정

컨투어 단계에서 확보한 네 꼭짓점을 활용하면 기울어진 문서를 곧게 펴는 투시 보정(Perspective Transform)을 수행할 수 있습니다.
OpenCV에서는 cv2.getPerspectiveTransform()cv2.warpPerspective() 함수가 이 역할을 담당합니다.
사다리꼴 형태의 입력 이미지를 직사각형 평면으로 맵핑해 출력하면, 카메라 각도 때문에 비뚤어진 문서가 마치 스캐너로 스캔한 것처럼 곧게 펴집니다.

투시 보정의 핵심은 출력 해상도를 올바르게 지정하는 것입니다.
출력 크기를 지나치게 낮추면 글자가 흐려지고, 지나치게 높이면 연산 속도가 저하되거나 불필요한 용량을 차지합니다.
따라서 일반 문서는 A4 비율(1:1.414)을 기준으로 삼고, 1200×1700 픽셀 같은 표준 크기를 설정하는 것이 실용적입니다.
OCR과 연계하려는 경우 최소 300DPI 이상의 해상도를 맞추는 것이 권장됩니다.

📌 투시 보정 구현

투시 보정을 구현할 때는 네 꼭짓점을 항상 좌상-우상-우하-좌하 순으로 정렬해야 왜곡 없는 결과를 얻을 수 있습니다.
이후 원하는 출력 해상도 좌표계를 정의한 뒤 변환 행렬을 계산해 warpPerspective로 적용하면 됩니다.

CODE BLOCK
# rect: 정렬된 문서 네 꼭짓점
(w, h) = (1200, 1700)  # A4 비율 해상도 예시
dst = np.array([
    [0, 0],
    [w - 1, 0],
    [w - 1, h - 1],
    [0, h - 1]], dtype="float32")

M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(orig, M, (w, h))

cv2.imwrite("scan_warped.jpg", warped)

💬 출력 해상도를 A4 기준 픽셀 값으로 정하면, 프린트나 PDF 저장 시 표준 규격과 쉽게 매칭됩니다.

⚠️ 주의: 네 꼭짓점이 잘못 정렬되면 문서가 뒤집히거나 비정상적으로 찌그러질 수 있습니다.
항상 좌표 순서를 정렬하는 함수를 적용한 뒤 변환을 진행하세요.

💎 핵심 포인트:
투시 보정은 단순히 이미지를 펴는 과정이 아니라, 출력 품질을 결정짓는 핵심 단계입니다.
표준 해상도와 DPI를 고려해 설정하면 OCR, PDF 저장, 프린트 등 다양한 후속 작업에서 안정적인 결과를 얻을 수 있습니다.



🎛️ CLAHE로 대비 향상과 노이즈 케어

투시 보정으로 문서를 곧게 편 뒤에도 여전히 그림자, 밝기 불균형, 글자 대비 부족 문제가 남아 있을 수 있습니다.
이때 유용한 도구가 CLAHE (Contrast Limited Adaptive Histogram Equalization)입니다.
기존의 단순 히스토그램 평활화가 이미지 전체의 명암을 균일하게 조정하는 데 비해, CLAHE는 이미지를 작은 구역으로 나누어 각각 대비를 조절합니다.
그 결과 종이의 텍스처나 글자 번짐은 억제하면서 텍스트 선명도는 크게 높일 수 있습니다.

CLAHE 적용은 보통 LAB 색 공간에서 L 채널(밝기)에만 국한하는 것이 가장 효과적입니다.
컬러 문서의 경우 RGB 전체에 CLAHE를 적용하면 색 왜곡이 심해지므로 주의해야 합니다.
파라미터 중 clipLimittileGridSize는 품질을 좌우하는 핵심으로, clipLimit은 대비 증폭 한도를, tileGridSize는 국소 영역 크기를 의미합니다.

📌 CLAHE 적용 절차

  • 🎨이미지를 BGR → LAB으로 변환합니다.
  • 💡L 채널만 분리해 CLAHE를 적용합니다.
  • ⚙️일반적으로 clipLimit=2.0, tileGridSize=(8,8) 조합이 권장됩니다.
  • 📈CLAHE 적용 후 다시 LAB을 합쳐 BGR로 변환합니다.
CODE BLOCK
# warpPerspective로 얻은 보정 이미지를 대상으로 CLAHE 적용
lab = cv2.cvtColor(warped, cv2.COLOR_BGR2LAB)
L, A, B = cv2.split(lab)

clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
L2 = clahe.apply(L)

lab2 = cv2.merge([L2, A, B])
enhanced = cv2.cvtColor(lab2, cv2.COLOR_LAB2BGR)

cv2.imwrite("scan_enhanced.jpg", enhanced)

💬 clipLimit 값을 높일수록 대비가 강하게 살아나지만 노이즈가 과장될 수 있습니다.
실제 스캔본처럼 보이려면 2.0~3.0 범위가 가장 자연스럽습니다.

💎 핵심 포인트:
CLAHE는 문서 이미지에서 글자의 가독성을 높이는 마지막 다듬기 단계입니다.
글자가 번지지 않도록 L 채널만 보정하고, 적절한 clipLimit과 tileGridSize를 찾는 것이 성능과 자연스러움의 균형을 맞추는 열쇠입니다.

자주 묻는 질문 (FAQ)

Canny 임계값은 고정해도 되나요?
환경마다 조명과 대비가 달라지므로 완전히 고정하기보다는 기준값을 두고 상황에 맞게 조정하는 것이 더 안정적입니다.
컨투어에서 꼭짓점이 4개가 안 나오면 어떻게 하나요?
epsilon 값을 줄여 근사 정확도를 높이거나, 작은 컨투어를 무시하는 필터를 강화해 문서 외곽을 강조하는 방법을 시도할 수 있습니다.
투시 보정 후 문서가 뒤집혀 보입니다
꼭짓점 순서가 맞지 않으면 이런 현상이 발생합니다. 항상 좌상, 우상, 우하, 좌하 순서대로 정렬한 뒤 보정을 적용하세요.
OCR과 함께 쓰려면 해상도를 얼마나 설정해야 하나요?
일반적으로 300DPI 이상, A4 문서 기준 1200×1700 픽셀 정도면 OCR 엔진에서 높은 인식률을 보장합니다.
CLAHE 대신 단순 히스토그램 평활화를 써도 되나요?
단순 평활화는 전체 밝기 대비만 올리기 때문에 그림자와 하이라이트가 과도하게 강조될 수 있습니다. CLAHE가 문서 스캔에는 더 적합합니다.
컬러 문서도 같은 방법을 쓰면 되나요?
가능합니다. 다만 CLAHE는 LAB 색 공간에서 L 채널에만 적용해야 색 왜곡을 방지할 수 있습니다.
문서의 그림자를 줄이려면 어떻게 하나요?
전처리 단계에서 가우시안 블러와 어댑티브 스레시홀드를 병행하거나, CLAHE 후에 노이즈 억제 필터를 추가하면 효과적입니다.
실시간 촬영 영상에서도 적용할 수 있나요?
가능합니다. 단, 프레임별 연산 부하가 크기 때문에 해상도를 낮추거나 GPU 가속을 병행하면 더 원활하게 동작합니다.

📝 문서 스캔 자동화를 위한 최적 파이프라인 정리

파이썬 OpenCV를 활용한 문서 스캔은 단순히 사진을 보정하는 단계를 넘어, 실무와 연구 모두에서 활용 가능한 강력한 자동화 파이프라인입니다.
핵심은 Canny 에지 검출 → 컨투어 사각형 추출 → 투시 보정 → CLAHE 대비 향상이라는 순서를 확립하는 것입니다.
이 과정을 거치면 단순한 촬영본도 스캐너에 버금가는 품질로 변환할 수 있습니다.

Canny 단계에서 안정적인 에지 맵을 얻고, 컨투어에서 외곽을 정확히 잡으며, 투시 보정으로 문서를 바르게 펼친 뒤 CLAHE로 글자 가독성을 최종적으로 강화하는 구조는 범용성과 재현성이 뛰어납니다.
OCR, 아카이빙, PDF 저장, 모바일 앱 등 다양한 곳에서 동일한 흐름을 적용할 수 있으며, 파라미터만 조금씩 조정하면 조명이나 배경이 달라져도 흔들리지 않는 결과를 얻을 수 있습니다.

이 파이프라인은 현장에서 빠른 스캔이 필요할 때, 혹은 대량의 문서를 자동 처리할 때 특히 강점을 발휘합니다.
표준화된 처리 순서를 그대로 따라가면 누구나 쉽게 구현할 수 있으며, 실무에서는 정확성과 일관성을 동시에 보장합니다.


🏷️ 관련 태그 : OpenCV, 파이썬문서스캔, Canny에지검출, 컨투어사각형, 투시보정, CLAHE, OCR전처리, 이미지프로세싱, 자동화파이프라인, 프로그래밍