문제 링크
Q01. 채널 변경(チャネル入れ替え)
이미지를 읽어올 때, 기존의 RGB를 BGR의 순서로 바꾸기
아래의 코드로 이미지의 붉은 성분을 추출할 수 있다.
cv2.imread()함수에서는 채널이 BGR의 순서로 바뀌는 것을 주의!
import cv2
img = cv2.imread("assets/imori.jpg")
red = img[:, :, 2].copy()
TypeError: 'NoneType' object is not subscriptable에러 바로 앞의 miniconda 설정 방법대로 한 상태로 링크에 있는 문제 코드 그대로 입력하면 파이썬 nonetype 에러를 만나게 될 가능성이 크다. 문제 해결은 의외로 간단했다. cv2.imread() 함수의 인자를 이미지 파일이 있는 곳으로 경로 설정을 해줬더니 에러는 발생하지 않았다. |
A01. 채널 변경(チャネル入れ替え) 답안
import cv2
# function: BGR -> RGB
def BGR2RGB(img):
b = img[:, :, 0].copy()
g = img[:, :, 1].copy()
r = img[:, :, 2].copy()
# RGB > BGR
img[:, :, 0] = r
img[:, :, 1] = g
img[:, :, 2] = b
return img
# Read image
img = cv2.imread("assets/imori.jpg")
# BGR -> RGB
img = BGR2RGB(img)
# Save result
cv2.imwrite("out.jpg", img)
cv2.imshow("result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
[위의 코드에서 사용된 개념] 1. Python def문 (사용자 정의 함수) 상세한 설명은 아래의 링크를 참조 https://www.codingfactory.net/10034 2. cv2.imwrite('image', img) 이미지를 보존하는 함수 첫 번째 인자로는 파일명, 두번째 인자는 보존하고 싶은 이미지다. 이 명령에 따라, 이미지 데이터가 png파일 형식으로 작업 디렉토리에 보존된다. 형식을 지정하고 싶다면 첫번째 인자에 파일 확장명도 같이 입력하면 된다. |
Q02. 그레이스케일화(グレースケール化)
그레이 스케일이란 이미지의 휘도(;일정한 면적에서 나오는 밝기)표현 방법 중 하나로 아래의 식으로 계산된다.
Y = 0.2126 R + 0.7152 G + 0.0722 B
A02. 그레이스케일화(グレースケール化)의 답안
import cv2
import numpy as np
# Gray scale
def BGR2GRAY(img):
b = img[:, :, 0].copy()
g = img[:, :, 1].copy()
r = img[:, :, 2].copy()
# Gray scale
out = 0.2126 * r + 0.7152 * g + 0.0722 * b
out = out.astype(np.uint8)
return out
# Read image
img = cv2.imread("assets/imori.jpg").astype(np.float)
# Grayscale
out = BGR2GRAY(img)
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
[위의 코드에서 사용된 개념] 1. Numpy의 array형변환 (사용예) x.astype(dtype) 변수 x를 dtype의 자료형으로 변경하여 출력 Numpy에 관련된 공식 홈 페이지 : https://numpy.org/ +Numpy에 대해서는 자세하게 공부할 필요가 있기 때문에 따로 정리하여 포스팅할 예정 |
https://qiita.com/yoya/items/dba7c40b31f832e9bc2a
Python으로 그레이스케일화하는 다양한 방법에 대한 소개는 위의 블로그를 참고하면 좋다.
Q03. 2진화(2値化,binarization)
2진화는 이미지의 그레이 값에 따라 픽셀을 흑백(0과 1 또는 255)의 2단계로만 변환 처리하는 것을 의미한다.
이러한 이진화 작업을 거치면 이미지가 담고 있는 객체를 배경에서 분리할 수 있다.
이 문제에서는 그레이 스케일의 한계를 128로 설정한 식을 사용할 것이다.
y = { 0 (if y < 128)
255 (else)
A03. 2진화(2値化,binarization)
import cv2
import numpy as np
# Gray scale
def BGR2GRAY(img):
b = img[:, :, 0].copy()
g = img[:, :, 1].copy()
r = img[:, :, 2].copy()
# Gray scale
out = 0.2126 * r + 0.7152 * g + 0.0722 * b
out = out.astype(np.uint8)
return out
# binalization
def binarization(img, th=128):
img[img < th] = 0
img[img >= th] = 255
return img
# Read image
img = cv2.imread("assets/imori.jpg").astype(np.float32)
# Grayscale
out = BGR2GRAY(img)
# Binarization
out = binarization(out)
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
Q04. 오츠 이진화(大津の二値化,Otsu Thresholding)
2진화로 분리할 때 어떤 그레이값으로 픽셀을 분리해야하는가라는 문제가 생긴다.
문제 3번에서 임의로 값을 지정했었기 때문에 모든 사진에 사용할 수 없다.
그래서 임계값( Thresholding value)의 설정에 대한 여러가지 알고리즘이 알려져 있는데 그 중에서 통계적인 방법을 이용한 Otsu의 알고리즘이 가장 자연스러운 임계값 설정 기준을 나타내준다.
조금 더 자세한 설명은 아래의 블로그를 참조하자.
https://j07051.tistory.com/364
계산법은 클래스 내의 분산과 클래스간 분산의 비를 사용한다.
그레이스케일의 휘도치(픽셀값)의 히스토그램은 아래와 같다.
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('assets/imori.jpg')
gray = 0.2126 * img[..., 2] + 0.7152 * img[..., 1] + 0.0722 * img[..., 0]
plt.hist(gray.ravel(), bins=255, rwidth=0.8, range=(0, 255))
plt.xlabel('value')
plt.ylabel('appearance')
plt.show()
임계치를 기준으로 분리되는 화소를 각각 0,1로 한다.
... 클래스 0,1에 소속된 화소 수
... 클래스 0,1에 소속된 화소치의 분산
... 클래스 0,1에 소속된 화소수의 평균치
... 이미지 전체의 화소수의 평균치
... 클래스 0,1에 소속된 화소수의 합산치
라고 한다면 클래스 0,1의 분산의 가중치 조합을 나타내는 클래스내의 분산은 다음의 식과 같다.
클래스 0,1의 평균치가 이미지전체의 평균부터 얼마정도 떨어져 있는가를 표시하는 클래스 분산화가 수식이 된다.
분산의 정도는 클래스 내 분산이 적고, 클래스간의 분산가 크도록 정의된다.
이미지 저체의 화소는 분산이 클래스 내 분산과 클래스간 분산의 합이 된다.
따라서 분리의정도 X는 아래 식으로 정의된다.
이 분산도는 최대가 되면 좋다.
즉, 클래스간 분산가 최대가 되면 좋다.
종합하자면 역치를 [0, 255]의 각 값에서 클래스 간 분산도를 계산해, 최대가 된 역치가 최적의 임계치가 된다.
A04. 오츠 이진화(大津の二値化,Otsu Thresholding)
import cv2
import numpy as np
# Gray scale
def BGR2GRAY(img):
b = img[:, :, 0].copy()
g = img[:, :, 1].copy()
r = img[:, :, 2].copy()
# Gray scale
out = 0.2126 * r + 0.7152 * g + 0.0722 * b
out = out.astype(np.uint8)
return out
# Otsu Binalization
def otsu_binarization(img):
max_sigma = 0
max_t = 0
H, W = img.shape
# determine threshold
for _t in range(1, 256):
v0 = out[np.where(out < _t)]
m0 = np.mean(v0) if len(v0) > 0 else 0.
w0 = len(v0) / (H * W)
v1 = out[np.where(out >= _t)]
m1 = np.mean(v1) if len(v1) > 0 else 0.
w1 = len(v1) / (H * W)
sigma = w0 * w1 * ((m0 - m1) ** 2)
if sigma > max_sigma:
max_sigma = sigma
max_t = _t
# Binarization
print("threshold >>", max_t)
th = max_t
out[out < th] = 0
out[out >= th] = 255
return out
# Read image
img = cv2.imread("assets/imori.jpg").astype(np.float32)
# Grayscale
out = BGR2GRAY(img)
# Otsu's binarization
out = otsu_binarization(out)
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
https://algorithm.joho.info/programming/python/opencv-otsu-thresholding-py/
위 블로그의 글을 참고하면 OpenCV로 더 간단하게 오츠의 이진화를 실행할 수 있다는 사실을 알 수 있다.
import cv2
import numpy as np
img = cv2.imread("assets/imori.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
ret, th = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)
cv2.imwrite("otsu.jpg",th)
cv2.imshow("result",th)
cv2.waitKey(0)
cv2.destroyAllWindows()
그럼 이렇게 더 짧은 코드로 원하는 이미지로 변환시킬 수 있다.
위 코드를 작성하는 도중에 아래와 같은 오류를 만났는데,
File "c:/Users/NEXTkey_1107/Miniconda3/envs/gasyori100/test04.py", line 8, in <module> ret, th = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU) cv2.error: OpenCV(4.2.0) C:\projects\opencv-python\opencv\modules\imgproc\src\thresh.cpp:1529: error: (-215:Assertion failed) src.type() == CV_8UC1 in function 'cv::threshold' |
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)를 gray = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)로 잘 못 입력하여 그레이 스케일화 되지 않은 상태에서 오츠의 이진화를 시키려하여 생긴 오류였다.
Q05. HSV 변환 (HSV変換)
HSV 변환을 실현하여, 색상 H를 전환하자.
HSV 변환이란 Hue(색상), Saturation(채도), Value(명도)로 색을 표현하는 방법이다.
- Hue란? 색상을 0에서 360로 표현해, 빨강이나 파랑 등의 색의 종류를 표시한다. ( 0 <= H < 360) 색상은 다음의 색에 대응한다.
- Saturation란? 색의 선명도이다. Saturation이 낮아지면 회색 색상이 더 두드러지게 표현되어 칙칙한 색이 된다. ( 0 <= S < 360)
- Value란? 색의 명도이다. Value값이 높아지면 흰색에 가까워지고, Value이 낮아지면 검은 색에 가까워 진다. ( 0 <= V < 1)
R,G,B가 [0,1]의 범위라고 가정한다면,
RGB -> HSV변환은 아래의 식이 적용된다.
Max = max(R,G,B)
Min = min(R,G,B)
H = { 0 (if Min=Max)
60 x (G-R) / (Max-Min) + 60 (if Min=B)
60 x (B-G) / (Max-Min) + 180 (if Min=R)
60 x (R-B) / (Max-Min) + 300 (if Min=G)
V = Max
S = Max - Min
HSV -> RGB변환은 아래의 식이 적용된다.
C = S
H' = H / 60
X = C (1 - |H' mod 2 - 1|)
(R,G,B) = (V - C) (1,1,1) + { (0, 0, 0) (if H is undefined)
(C, X, 0) (if 0 <= H' < 1)
(X, C, 0) (if 1 <= H' < 2)
(0, C, X) (if 2 <= H' < 3)
(0, X, C) (if 3 <= H' < 4)
(X, 0, C) (if 4 <= H' < 5)
(C, 0, X) (if 5 <= H' < 6)
여기서는 색상 Hue를 전환(180을 가산)하여, 변환해보자.
A05. HSV 변환 (HSV変換) 답안
import cv2
import numpy as np
# BGR -> HSV
def BGR2HSV(_img):
img = _img.copy() / 255.
hsv = np.zeros_like(img, dtype=np.float32)
# get max and min
max_v = np.max(img, axis=2).copy()
min_v = np.min(img, axis=2).copy()
min_arg = np.argmin(img, axis=2)
# H
hsv[..., 0][np.where(max_v == min_v)]= 0
## if min == B
ind = np.where(min_arg == 0)
hsv[..., 0][ind] = 60 * (img[..., 1][ind] - img[..., 2][ind]) / (max_v[ind] - min_v[ind]) + 60
## if min == R
ind = np.where(min_arg == 2)
hsv[..., 0][ind] = 60 * (img[..., 0][ind] - img[..., 1][ind]) / (max_v[ind] - min_v[ind]) + 180
## if min == G
ind = np.where(min_arg == 1)
hsv[..., 0][ind] = 60 * (img[..., 2][ind] - img[..., 0][ind]) / (max_v[ind] - min_v[ind]) + 300
# S
hsv[..., 1] = max_v.copy() - min_v.copy()
# V
hsv[..., 2] = max_v.copy()
return hsv
def HSV2BGR(_img, hsv):
img = _img.copy() / 255.
# get max and min
max_v = np.max(img, axis=2).copy()
min_v = np.min(img, axis=2).copy()
out = np.zeros_like(img)
H = hsv[..., 0]
S = hsv[..., 1]
V = hsv[..., 2]
C = S
H_ = H / 60.
X = C * (1 - np.abs( H_ % 2 - 1))
Z = np.zeros_like(H)
vals = [[Z,X,C], [Z,C,X], [X,C,Z], [C,X,Z], [C,Z,X], [X,Z,C]]
for i in range(6):
ind = np.where((i <= H_) & (H_ < (i+1)))
out[..., 0][ind] = (V - C)[ind] + vals[i][0][ind]
out[..., 1][ind] = (V - C)[ind] + vals[i][1][ind]
out[..., 2][ind] = (V - C)[ind] + vals[i][2][ind]
out[np.where(max_v == min_v)] = 0
out = np.clip(out, 0, 1)
out = (out * 255).astype(np.uint8)
return out
# Read image
img = cv2.imread("assets/imori.jpg").astype(np.float32)
# RGB > HSV
hsv = BGR2HSV(img)
# Transpose Hue
hsv[..., 0] = (hsv[..., 0] + 180) % 360
# HSV > RGB
out = HSV2BGR(img, hsv)
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
https://algorithm.joho.info/programming/python/opencv-rgb-to-hsv-color-space/
이 문제도 역시 공식을 몰라도 OpenCV로 간단하게 해결 가능하다.
import cv2
import numpy as np
img = cv2.imread("assets/imori.jpg")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
cv2.imwrite("hsv.jpg",hsv)
cv2.imshow("result",hsv)
cv2.waitKey(0)
cv2.destroyAllWindows()
그러나 위의 코드는 문제의 답변과 정확하게 일치하지 않는다.
문제에서는 Hue를 전환(180을 가산)하여야 하기 때문이다.
각 색상 요소를 조정하여 변환하는 방법은 아래의 블로그를 참고하였다.
https://tat-pytone.hatenablog.com/entry/2019/04/14/193237
위의 블로그의 코드를 참고하여 변환한 답변 코드는 아래와 같다.
import cv2
import numpy as np
img = cv2.imread("assets/imori.jpg")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Transpose Hue
hsv[..., 0] = (hsv[..., 0] + 180) % 360
out = cv2.cvtColor(hsv,cv2.COLOR_HSV2BGR)
cv2.imwrite("hsv.jpg",out)
cv2.imshow("result",out)
cv2.waitKey(0)
cv2.destroyAllWindows()
'IT > AI\ML' 카테고리의 다른 글
[python/OpenCV] 이미지 처리 예제 Q11~Q15 (0) | 2020.04.20 |
---|---|
[python/OpenCV] 이미지 처리 예제 Q06~Q10 (0) | 2020.04.20 |
[python/OpenCV] 이미지 처리의 기본 (1) | 2020.04.20 |
[python] miniconda 설치, 환경 설정 및 패키지 설치(Windows) (0) | 2020.04.19 |
[AI/ML] 딥러닝, 머신러닝 입문자를 위한 커리큘럼, 강좌 정리 (0) | 2020.04.19 |