이번 글의 목표인 original 스캔, 촬영된 사진을 제목에 적힌 기법들을 이용해서 글자랑 배경이랑 구분짓게 만들예정이다.
스캔이미지 다운로드
!wget https://raw.githubusercontent.com/rajeevratan84/ModernComputerVision/main/scan.jpeg
위의 original 이미지를 다운로드한다.
그리고 다운받은 이미지를 흑백(grayscale)으로 변환하고 출력한다.
image = cv2.imread('scan.jpeg',0)
imshow("Original", image)
해당 사진은 그림자진 부분, 밝은 부분 어두운부분 등 다양한 부분으로 이루어져있다.
이때 이걸 글자를 검은색(255), 글자를 제외한 부분은 흰색(0)로 나타내려고 하는데 아래의 내용을 보자.
Thresholding Methods
아래는 Thresholding 방법에 대한 내용을 나타내고 있는데,
https://docs.opencv.org/master/d7/d4d/tutorial_py_thresholding.html
그림으로만으론 이해하기 힘드니까 직접 cv2.threshold를 사용해서 이미지를 변환해보자.
아래는 original 이미지에 대해 Binary, Binary_inv, trunc, tozero, toezro_inv 총 5가지 방식을 적용했다.
# 127 미만의 값은 0 또는 검은색, 위의 모든 값은 255(흰색)로 이동합니다
ret,thresh1 = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
imshow('1 Threshold Binary @ 127', thresh1)
# 127 미만의 값은 255로 이동하고 127 이상의 값은 0으로 이동합니다(위의 역)
ret,thresh2 = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY_INV)
imshow('2 Threshold Binary Inverse @ 127', thresh2)
# 127을 초과하는 값은 127에서 잘림(보유)됩니다(255 인수는 사용되지 않음)
ret,thresh3 = cv2.threshold(image, 127, 255, cv2.THRESH_TRUNC)
imshow('3 THRESH TRUNC @ 127', thresh3)
# 127 미만의 값은 0으로 가고, 127 이상의 값은 변경되지 않습니다
ret,thresh4 = cv2.threshold(image, 127, 255, cv2.THRESH_TOZERO)
imshow('4 THRESH TOZERO @ 127', thresh4)
# 위의 역방향, 127 아래는 변경되지 않고 127 위는 0으로 이동합니다
ret,thresh5 = cv2.threshold(image, 127, 255, cv2.THRESH_TOZERO_INV)
imshow('5 THRESH TOZERO INV @ 127', thresh5)
총 5개의 이미지들이 나왔는데 inv는 반전한다는것을 알수있고, 변환된 이미지들을 봣을때 그림자진 부분은 Tozero INV를 사용한것이 잘 보이고, 글자에데한건 Trunk가 잘보인다.
변환한 이미지들중 잘보이는 부분만 추출해서 이미지를 합치면 처음에 봤던 결과처럼 될것같다.
Adaptive Thresholding
Threshold라는것이 번역하면 임계값이라고 하는데, 위의 코드들에 나와있는 127, 255와 같은 수치들이 임계값이다.
저 위의 변환한 이미지들은 고정적인 임계값을 적용했지만, 이미지마다 안보이는 부분, 잘보이는 부분, 다 다르고, 적용해야할 임계값도 다 다르다.
그래서 나온것이 위의 적응형 임계값을 추출하는 함수, 알고리즘인 Adaptive Thresholding이다.
# 127 미만의 값은 0으로 이동합니다(검은색, 위의 모든 것은 255(흰색)로 이동합니다)
ret,thresh1 = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
imshow('Threshold Binary', thresh1)
# 적응형 임계값 사용
thresh = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 3, 5)
imshow("Adaptive Mean Thresholding", thresh)
_, th2 = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
imshow("Otsu's Thresholding", th2)
blur = cv2.GaussianBlur(image, (5,5), 0)
_, th3 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
imshow("Guassian Otsu's Thresholding", th3)
아래는 아까 본 이미지 이고
적응형 임계값을 사용하여 이미지를 이진화한 이미지이다.
픽셀 주변의 작은 영역에 대해 각각 다른 임계값을 적용해서 확실이 깔끔해지긴 했으나 글자가 번져보이는 느낌이다.
오츠의 이진화를 사용하여 이미지를 이진화한것으로
이 메소드는 이미지의 히스토그램을 분석하여 임계값을 자동으로 결정
가우시안 필터링을 적용한 이미지에 대해 오츠의 이진화를 수행한것.
SkImage Threshold Local
SkImage 임계값 로컬이라고하는데 이건 위의 적응형 임계값 적용 알고리즘 중 훌륭한 성능을 가졌다.
https://scikit-image.org/docs/stable/auto_examples/applications/plot_thresholding.html
바로 적용한 코드를 보자.
from skimage.filters import threshold_local
image = cv2.imread('scan.jpeg')
# We get the Value component from the HSV color space
# then we apply adaptive thresholdingto
V = cv2.split(cv2.cvtColor(image, cv2.COLOR_BGR2HSV))[2]
T = threshold_local(V, 25, offset=15, method="gaussian")
# Apply the threshold operation
thresh = (V > T).astype("uint8") * 255
imshow("threshold_local", thresh)
우선 코드를 보면 COLOR_BGR2HSV로 BGR 사진을 HSV로 변환을 한다.
HSV에 대한내용을 다시 상기시켜보면
색상(Hue), 채도(Saturation), 명도(Value)로 이루어져있으며
이 색 공간은 특히 색상 정보에 더 강한 영향을 받는 작업에서 유용하다.
그럼 이 코드들이 어떤 역할을 하는지 확인하자.
두가지 내용을 갖고 적응형 임계함수인 threshold_local 함수를 이용하여 이미지를 처리했다.
결과를 보면 높은 퀄리티의 이미지를 얻어낼수 있다.