IT/AI\ML

[python/OpenCV] 이미지 처리 예제 Q01~Q05

개발자 두더지 2020. 4. 20. 13:04
728x90

문제 링크

https://github.com/yoyoyo-yo/Gasyori100knock/tree/master/Question_01_10#q1-%E3%83%81%E3%83%A3%E3%83%8D%E3%83%AB%E5%85%A5%E3%82%8C%E6%9B%BF%E3%81%88

 

yoyoyo-yo/Gasyori100knock

画像処理100本ノックして画像処理を画像処理して画像処理するためのもの For Japanese, English and Chinese - yoyoyo-yo/Gasyori100knock

github.com


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() 함수의 인자를 이미지 파일이 있는 곳으로 경로 설정을 해줬더니 에러는 발생하지 않았다.

https://qiita.com/right1121/items/af0c3555b75fd3e51bdf

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파일 형식으로 작업 디렉토리에 보존된다.

형식을 지정하고 싶다면 첫번째 인자에 파일 확장명도 같이 입력하면 된다.

http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_gui/py_image_display/py_image_display.html


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 でグレースケール(grayscale)化 - Qiita

Python でカラー画像をグレースケール化する方法のまとめです。 特によく使われそうな OpenCV, PIL(Pillow), scikit-image で処理するサンプル例を紹介します。 前提知識 カラー画像のグレ...

qiita.com

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/

 

【Python/OpenCV】大津の手法で二値化処理

Python版OpenCV(cv2.threshold)もしくはNumPyで大津の手法(判別分析法)を実装し、画像を二値化する方法をソースコード付きで解説します。

algorithm.joho.info

위 블로그의 글을 참고하면 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/

 

【Python/OpenCV】RGBからHSVに変換(cv2.cvtColor)

PythonとOpenCVを用いて画像をRGBからHSVに変換する方法をソースコード付きで解説します。

algorithm.joho.info

이 문제도 역시 공식을 몰라도 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

 

画像の彩度、明度を変える - Pythonでいろいろやってみる

opencvのimreadで読み込んだ画像はBGR(青緑赤)の3チャンネルからなるndarrayなので、これを色空間変換により色相(Hue)、彩度(Saturation)、明度(Value)からなるHSV色空間に変換し、彩度と明度を調整します。 関連記事 ルックアップテーブルによる画像コントラストの補正 特定の色を別の色に変換する(赤い服を緑の服に変える) 使った関数・メソッド cv2.imread() : 画像ファイルの読み出し cv2.cvtColor() : 画像の色空間変換 cv2.threshol

tat-pytone.hatenablog.com

위의 블로그의 코드를 참고하여 변환한 답변 코드는 아래와 같다.

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()
728x90