이전 글
임계값
이전의 글에서 Trackbar를 이용해 임계값의 변화에 따라 변하는 이미지를 확인하는 과정에서 흑백처리를 했었다. 그렇다면 흑백처리 된 이미지에서 이진화할 때 흑과 백을 나누는 임계값은 무엇일까. 이를 알아보기 위해 아래와 같은 이미지를 준비한다.
위 이미지는 왼쪽에서 부터 차례대로 RGB값이 0인 검은색, 127, 195, 255인 흰색을 각각 가지고 있다. 이 이미지를 통해 임계값에 대해 알 수 있다.
이 전에 trackbar를 추가해 이진화 했던 코드에서 위 이미지를 사용한다.
import cv2
def empty(pos):
pass
img = cv2.imread("threshold.png", cv2.IMREAD_GRAYSCALE)
name = "Trackbar"
cv2.namedWindow(name)
cv2.createTrackbar("threshold", name, 127, 255, empty)
while True:
thresh = cv2.getTrackbarPos("threshold", name)
ret, binary = cv2.threshold(img, thresh, 255, cv2.THRESH_BINARY)
if not ret:
break
# 비교를 위해 원본 사진을 함께 출력한다.
cv2.imshow(name, binary)
cv2.imshow("image", img)
if cv2.waitKey(1) == ord("q"):
break
cv2.destroyAllWindows()
원본 이미지와 비교했을 때 임계값(threshold)이 127일 때는 왼쪽의 두 칸이 검은색이고, 오른쪽 2칸이 흰색으로 나타난다.
thershold < 127
임계값이 127보다 작은 경우엔 왼쪽의 한 칸만이 검정색이며, 나머지 3칸이 흰색으로 나타난다. 즉 threshold값이 0부터 126일 때 RGB가 127보다 작으면 검은색으로 표시되어 나타남을 의미한다.
127 ≤ thershold < 195
임계값이 127일 때부턴 왼쪽에서부터 두 칸이 검정색으로 나타나고 나머지 두 칸은 흰색으로 나타난다. 이는 threshold값이 127 이상 195 미만일 때 RGB가 195보다 작으면 검은색으로 표시되어 나타남을 의미한다.
195 ≤ thershold < 255
임계값이 195일 때부턴 왼쪽에서부터 세 칸이 검정색으로 나타나고 나머지 한 칸은 흰색으로 나타난다. 이는 threshold값이 195 이상 255 미만일 때 RGB가 255보다 작으면 검은색으로 표시되어 나타남을 의미한다.
마지막으로 임계값(threshold)이 255일때는 모든 칸이 검은색으로 나타난다.
Adaptive Threshold
# 원본 이미지
import cv2
img = cv2.imread("book.jpg")
cv2.imshow("image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
이 전부터 사용한 원본 이미지를 보면 빛에 의해 밝게 표현된 부분이 있는가 하면 상대적으로 어둡게 나타나는 부분이 있어 이미지를 이진화했을 때 같은 값으로 이진화하기 때문에 모든 글씨를 알아보기가 어렵다. 이 처럼 특정영역마다 다른 임계치를 대입하기 위해서는 일반 Threshold를 사용하는 것이 아닌 이미지를 작은 영역으로 나누어 임계치를 적용하는 Adaptive Threshold를 사용한다.
Adaptive Threshold를 사용하기 위해서는 cv2.adaptiveThreshold() 함수를 사용한다.
cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
adaptiveMethod에는 세가지 연산방법이 있다.
cv2.ADAPTIVE_THRESH_MEAN_C | 주변영역의 평균값으로 결정 |
cv2.ADAPTIVE_THRESH_GAUSSIAN_C | 중심점으로부터의 거리에 대한 가우시안 가중치를 적용 |
cv2.ADAPTIVE_THRESH_MEAN_C | cv2.ADAPTIVE_THRESH_GAUSSIAN_C | 평균 가중치와 가우신안 가중치를 혼합해 사용 |
평균 가중치를 적용하는 cv2.ADAPTIVE_THRESH_MEAN_C와 가우시안 가중치를 적용하는 cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 그리고 이 두 방법을 혼합해 사용하는 방법이 있다.
blockSize는 thresholding을 적용할 영역의 사이즈이며 C는 평균이나 가중평균에서 차감할 값을 의미한다. 여기서 blockSize와 C를 사용자가 최적의 값을 찾기 위해 Trackbar를 사용해 나타낼 수 있다.
import cv2
def empty(pos):
pass
img = cv2.imread("book.jpg", cv2.IMREAD_GRAYSCALE)
# Track bar
name = "Trackbar"
cv2.namedWindow(name)
# 1. 영역을 세분화하기 위한 블록 크기 정의(1보다 큰 홀수만 가능)
cv2.createTrackbar("block_size", name, 25, 100, empty)
# 2. 일반 상수 (일반적으로 양수의 값을 사용)
cv2.createTrackbar("c", name, 3, 10, empty)
while True:
block_size = cv2.getTrackbarPos("block_size", name)
c = cv2.getTrackbarPos("c", name)
# blockSize Track bar 조건 정의
# 1보다 큰 수
if block_size <= 1:
block_size = 3
# 짝수인 경우 + 1
if block_size % 2 ==0:
block_size += 1
# daptiveThreshold의 경우 하나의 값만 반환한다.
binary = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, block_size, c)
cv2.imshow("image", img)
cv2.imshow(name, binary)
if cv2.waitKey(1) == ord("q"):
break
cv2.destroyAllWindows()
완벽하게 글씨가 인식되는 것은 아니지만 원본 이미지에서 빛에 의해 밝게 보여 텍스트가 인식이 되지 않던 부분도 임계치가 조정되어 이미지가 더 선명하게 보이는 것을 알 수 있다.
Otsu Algorithm Threshold
Threshold 값을 이용해 이진화를 할 때 사용자가 Trackbar를 움직여 최적의 값을 찾아야 하는 불편함이 있다. 이러한 점을 해결하기 위해 오츠가 개발한 최적의 임계치 값을 찾아주는 알고리즘이 있다. otsu 알고리즘을 사용하는 방법은 cv2.threshold함수의 flag에서 추가로 cv2.THRESH_OTSU를 적용하면 된다. 이때 임계값은 0으로 전달하면된다.
import cv2
img = cv2.imread("book.jpg", cv2.IMREAD_GRAYSCALE)
ret, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, otsu = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
print("otsu threshold ", ret)
cv2.imshow("binarization", binary)
cv2.imshow("otsu", otsu)
cv2.waitKey(0)
cv2.destroyAllWindows()
otsu threshold 138.0
otsu 알고리즘을 적용했을 때 위 이미지의 최적의 임계치값은 138임을 알 수 있다. 또한 otsu 알고리즘을 적용한 이미지가 기본 이진화를 적용한 이미지와 큰 차이가 없음을 확인할 수 있다. 이러한 결과가 나오는 이유는 otsu알고리즘의 경우 모든 이미지에 대해서 최적을 임계치값을 찾을 수 있는 것이 아닌 bimodal image에서 최적의 임계치 값을 찾아낼 수 있기 때문이다. bimodal image는 히스토그램에 두 개의 뚜렷한 피크가 있는 이미지로, 두 개의 뚜렷한 밝기 값 범위를 나타낸다. 이러한 이미지에는 어두운 영역과 밝은 영역이 모두 있을 수 있으며 처리하거나 분석하기가 까다롭다.
'Study > ML | DL' 카테고리의 다른 글
[Python] OpenCV를 이용한 이미지/영상 처리 - 모폴로지 변환(Opening & Closing) (0) | 2023.06.14 |
---|---|
[Python] OpenCV를 이용한 이미지/영상 처리 - 모폴로지 변환(dilate, erode) (1) | 2023.06.13 |
[Python] OpenCV를 이용한 이미지/영상 처리 - 이진화(Binarization), TrackBar (0) | 2023.06.07 |
[Python] OpenCV를 이용한 반 자동 문서스캐너 구현하기2 - 반 자동 문서 스캐너 구현 (0) | 2023.06.01 |
[Python] OpenCV를 이용한 반 자동 문서스캐너 구현하기1 - Mouse event (0) | 2023.05.25 |